Skip to content

Latest commit

 

History

History
615 lines (423 loc) · 13.5 KB

Html5GameProgramming.md

File metadata and controls

615 lines (423 loc) · 13.5 KB

...menustart

...menuend

HTML5 Game Programming

Canvas

<body>
    <canvas> id="my_canvas"</canvas>
</body>
<script>
var canvas = null ;
var context = null ;

setup = function() {
    canvas = document.getElementById( "my_canvas" ) ;
    context = canvas.getContext(  '2d') ;
    canvas.width = 1200 ; // windows.innerWidth ;
    canvas.height = 720 ; // windows.innerHeight ;
} ;

setup();

</script>

Loading an Image

  1. Declare a new Image() object
  2. Declare it's 'onload' method
  3. set Image.src = "url"
    • as soon as the Image.src is set through a value , JS will kick off onload function.
    • because of this, we need to specify the callback function first before setting the source attribute
setup = function() {
    canvas = document.getElementById( "my_canvas" ) ;
    context = canvas.getContext(  '2d') ;
    canvas.width = 1200 ; // windows.innerWidth ;
    canvas.height = 720 ; // windows.innerHeight ;

    img new Image();
    img.onload = onImageLoad;
    img.src = 'ralphyrobot.png'
} ;

onImageLoad = function() {
    console.log( "Image" ) ;
}

drawImage

  • var object = object.drawImage( ... )
  • you can find API details in webplatform.org
onImageLoad = function() {
    console.log( "Image" ) ;
    context.drawImage( img, 192, 192 ) ;
}

播放动画

  • load all animation image assets and place then in the frame array
  • after you images are loaded , you should go through and fill out the rest of the animation function
  • before this you need to have some logic that checks for whether or not the images are loaded
  • and once all of the frames have been loaded , actually does a call to setInterval.
var frameRate = 1000/30 ;
var frame = 0 ;
var assets = [ 'xxx00.png' , 'xxx01.png' , ... ] ;

setup = function() {
    ...

    for (var i =0; i < assets.length ; i++ ) {
        frame.push( new Image() ) ;
        frame[i].src = assets[i] ;
        frame[i].src = assets[i] ;
    }

    setInterval ( animate , frameRate ) ;

} ;

var animate = function() {
    context.clearRect( 0,0,  canvas.width , canvas.height ) ;
    context.drawImage( frames[frame] , 192,192 ) ;
    frame = (frame +1) % frame.length
}

The canvas doesn't actually clear itself each frame. You need call clearRect to clear it.


Atlas

  • it's up to about 6 connection for a modern browse.
  • when the upper limit is reached , the browse will actullay block subsequent requests until an open connection becomes available.

usign output from texture packer

defSprite: function ( name, x, y, w, h , cx ,cy ) {
    var spt = {
        "id" :    name,
        "x" :    x,
        "y" :    y,
        "w" :    w,
        "h" :    h,
        "cx" :    cx==null ? 0 : cx ,
        "cy" :    cy==null ? 0 : cy ,
    } ;
    this.sprites.push(spt) ;
}

parseAtlasDefinition: function( atlasJson ) {
    var parsed = JSON.parse( atlasJson ) ;

    for( var key in parsed.frames ) {
        var sprite = parsed.frames[key];
        // define the center of the sprite as an offset
        var cx = -sprite.frame.w * 0.5 ;
        var cy = -sprite.frame.h * 0.5 ;

        // define the sprite for this sheet
        this.defSprite( key , sprite.frame.x , sprite.frame.y , sprite.frame.w, sprite.frame.h ,  cx, cy) ;
    }
} 

Input

Event listeners

document.getElementById( 'canvas' ).addEventListener( 'mousemove' , onMouseMove ) ;
document.getElementById( 'canvas' ).addEventListener( 'keydown' , onKeyDown ) ;

function onMoseMove() {
    var posx = event.clientX ;
    var posy = event.clientY ;

}

function onKeyDown( event ) {
    var keyId = event.keyID ;  // ASCII value

}

an approache

  • Input manager handle the event , and keep the input temporarily , for "update procedure" to comsume it later.

Physics Engine

player: init()
{
    var physDat = {
        pos: {x,y} ,
        maxx : 0 ,
        size: {x:20, y:20} ,
        velocity: {x:5 , y:5}
    };
    mpPhysBody = PhysicsEngine.newBody() ;
}
  • Directly, the physics body itself exposes a function that allows us to set a linear velocity on it.
function update() {
    var move_dir = new Vec2(0,0) ;
    if(move_dir.LengthSquared()) {
        move_dir.Normalize() ;
        move_dir.Multiply( this.gPlayer0.walkSpeed ) ;  // important  
    }
    this.mpPhysBody.setLinearVelocity( move_dir.x , move_dir.y ) ;
}

Entity

gGameEngine.factory['WeaponInstance'] = WeaponInstanceClass ;

...

var ent = new( entityClass ) (x, y, es) ;

Tiled 工具 可以用来编辑相应的 entity object

Implementing Z-ordering


Physics

Missing

可以把 运动物理前后两帧的 collision box 组成一个 大的 collision box , 做 碰撞检测 , 以防止 missing.

Box2D , JS version

<script src="./box2d.min.js"> </script>

The World

    create: function() {
        this.world = new World() {
            new Vec2( 0 ,0 ) ,    // Gravity vector
            false                 // Boolean for allowing sleep
        } ;
    }

upadte:

    update: function() {
        var start = Data.now();

        // the more iterations, the more accurate the calculations
        this.world.Step (
            PHYSICS_LOOP_HZ ,   // frame-rate
            10,                    // velocity iterations
            10                    // position iteration
        ) ;

        // call the clearforces method of the world object to 
        // remove any forces at every physics update
        this.world.ClearForces() ;

        return (Date.now() - start);
    }

Physics Bodies

registerBody: function( bodyDef ) {
    var body = this.world.CreateBody( bodyDef ) ;
    return body ;
}

addBody : function( entityDef ) {
    var bodyDef = new BodyDef();

    // type:
    // Body.b2_staticBody
    // Body.b2_dynamicBody
    if (entityDef.type== "static") {
        bodyDef.type = Body.b2_staticBody ;
    } else {
        bodyDef.type = Body.b2_dynamicBody ;        
    }

    // Set the position{x,y} member object 
    // of your BodyDef object 
    bodyDef.position.x = entityDef.x ; 
    bodyDef.position.y = entityDef.y ;

    var body = this.registBody(bodyDef) ;
    var fixtureDefinition = new FixtureDef() ;

    if (entityDef.useBouncyFixture ) {
        fixtureDefinition.density = 1.0 ;
        fixtureDefinition.friction = 0 ; 
        fixtureDefinition.restitution = 1.0 ;
    }

    // we now define the shape of this object as a box
    fixtureDefinition.shape = new PolygonShape() ;
    fixtureDefinition.shape.SetAsBox( entityDef.halfWidth, entityDef.halfHeight ) ;
    body.CreateFixture( fixtureDefinition ) ;

    return body ;
}

Destorying Physics Bodies

removeBody: function(obj) {
    this.world.DestoryBody(obj) ;
}

Entities and Physics : TODO


Sound

一般的游戏, 只需要 Sound -> Gain -> OUTPUT 这条线

  • Checking for Compatibility
try {
    this._context = new webkitAudioContext();
} 
catch( e ) {
    alert( "web audio API not supported in this browse" ) ;
}
this._mainNode = this._context.createGainNode(0);
this._mainNode.connect( this._context.destination ) ;
  • Asynchronous Loading
Loaded --> yes -----------------------> callback
       |-> no  --> Xml Http Request --> callback
loadAsync: function( path , callbackFcn ) {
    if(this.clips[path]) {
        callbackFcn( this.clips[path].s ) ;
        return this.clip[path].s ;
    }

    var clip = { s: new Sound() , b: null , l: false  } ;
    this.clips[path] = clip ;
    clip.s.path = path ;

    var request = new XMLHttpRequest();
    request.open( "GET" , path, true ) ;
    request.responseType = "arraybuffer" ;
    request.onload = function() {
        gSM._context.decodeAudioData( request.response ,  
        function(buffer) {
            clip.b = buffer ;
            clip.l = true ; 
            callbackFcn( clip.s ) ;
        }
        function(data) {
            Logger.log( "fail" )
        } ) ;
    }
    request.send();

    return clip.s ;
}
  • Play Sound
playSound: function(path , settings) {
    ...
    var sd = this.clips[path] ;

    // creates a sound source
    var currentClip = gSM._context.createBufferSource() ;

    // tell the source which sound to play
    currentClip.buffer = sd.b ;
    currentClip.gain.value = volumen ;
    currentClip.connect( gSM._mainNode ) ;
    currentClip.loop = looping ;

    // play the source now
    currentClip.noteOn(0) ;
    return true ;
}
  • Stopping Sounds
    • eg: stop all sound , while level changing
    • The way we do that , is by simply disconnection our main node from the node graph ; creating a new one in its place, and connecting it to our output.
stopAll: function() {
    this._mainNode.disconnect();
    this._mainNode = this._context.createGainNode(0) ;
    this._mainNode.connect( this._context.destination ) ;
}
  • Muting Sounds
togglemute: function() {
    if( this._mainNode.gain.value > 0 )
        this._mainNode.gain.value = 0 ;
    else 
        this._mainNode.gain.value = 1 ; 
}
  • Play World Sound ( 远近效果 )
playWorldSound: function( soundURL , x , y ) {
    if(this.gPlayer0 === null ) return ;

    var gMap = gGameEngine.gMap ;

    // fade out volume based upon distance to me 
    var viewSize = Math.max(gMap.viewRect.w . gMap.viewRect.h) * 0.5 ;
    var oCenter = this.gPlayer0.pos ;
    var dx = Math.abs(oCenter.x -x ) ;
    var dy = Math.abs(oCenter.y -y ) ;
    var distToObserver = Math.sqrt( dx * dx , dy * dy ) ;
    var normDist = distToObserver / viewSize ;
    if(normDist > 1 )  normDist = 1 ;
    if(normDist < 0 )  return ;  // don't play

    var volumn = 1.0 - normDist ; 

    var sound = gSM.loadAsync(soundURL  , function( sObj) {
        gSM.playSound(sObj.path , {
            looping: false ,
            volume: volume
        } );
    };
}

Asset Loading

  • all file I/O function in JavaScript are by definition asynchronous.

Asset Manager

  • Cached Asset management
  • Async callbacks on load
  • batches asset loading

Caching images

Batches

  • to allow us to pass a list of files to load , and receive one callback once they've all been loaded
function loadAssets(assetList, callbackFcn) {
    // All the information we need to keep track of 
    // for this batch
    var loadBatch = {
        count: 0  ,
        total: assetList.count , 
        cb: callbackFcn
    };

    for (var i=0 ; i< assetList.length ; i ++ ) {
        if(gCachedAssets[assetname] == null) {
            var img = new Image() ;
            img.onload = function() {
                onLoadedCallback(img, loadBatch) ;
            } ;
            img.src = assetName ;

            gCachedAssets[assetName] = img ;
        } else {
            onLoadedCallback( gCachedAssets[assetList[i]] , loadBatch )
        }
    }
}

function onLoadedCallback( asset , batch ) {
    batch.count ++ ;
    if(batch.count == batch.total) {
        batch.cb(asset) ;
    }
}

Loading javascript