目前为止,角色除了基本的移动外还什么都不能做,于是我打算先实现角色的攻击动画。角色的普通攻击一共可以分为三个阶段:
一段斩 | |
---|---|
二段斩 | |
三段斩 | |
移动攻击 | |
跳跃攻击 |
// {
let loadBatch = {
count: 0,
total: assetList.length,
cb: callback
};
for (let i = 0; i {
onLoadedCallback(img, loadBatch);
};
img.src = assetName;
gCachedAssets[i] = img;
} else if (assetType === 1) {
let script = document.createElement('script');
script.addEventListener('load', () =>{
onLoadedCallback(script, loadBatch);
});
script.src = assetName;
gCachedAssets[i] = script;
document.getElementsByTagName('head')[0].appendChild(script);
}
} else {
onLoadedCallback(gCachedAssets[i], loadBatch);
}
}
},
onLoadedCallback = (asset, batch) =>{
batch.count++;
if (batch.count === batch.total) {
batch.cb(asset);
}
},
getAssetTypeFromExtension = (assetName) =>{
if (assetName.indexOf('.jpg') !== -1 || assetName.indexOf('.jpeg') !== -1 || assetName.indexOf('.png') !== -1) {
return 0;
}
if (assetName.indexOf('.js') !== -1 || assetName.indexOf('.json') !== -1) {
return 1;
}
return - 1;
};
(function() {
var canvas = document.createElement('canvas'),
a = document.getElementById('a');
canvas.id = 'c1';
canvas.width = 640;
canvas.height = 506;
a.appendChild(canvas);
var c = document.getElementById('c1'),
ctx = c.getContext('2d'),
lastTime = 0,
elapsed,
paused = false,
raqId,
playerSpriteSheet = new Image(),
canvasBG = new Image(),
now;
loadAssets(['http://files.cnblogs.com/files/undefined000/game.min.js?v=7'],function() {
playerSpriteSheet.src = imageData;
canvasBG.src = background;
let player = new Player(new Vector(5, 2), ctx, undefined, playerSpriteSheet);
function loop() {
draw();
}
function stop() {
cancelAnimationFrame(raqId)
}
function draw() {
ctx.clearRect(0, 0, c.width, c.height);
ctx.drawImage(canvasBG, 0, 0, 240, 160, 0, 0, 640, 506);
now = +new Date;
if (lastTime !== 0) {
elapsed = Math.min(now - lastTime, 16);
} else elapsed = 16;
player.update(elapsed);
lastTime = now;
raqId = requestAnimationFrame(draw);
}
loop();
window.addEventListener('keyup',(e) => {
if(e.keyCode === 80) {
paused = !paused;
if(paused) {
stop();
let txt = 'Pause';
ctx.font = '50px Source Han Serif';
ctx.fillStyle = '#f00';
ctx.fillText(txt, (c.width - ctx.measureText(txt).width) / 2, c.height / 2);
} else {
loop();
}
}
});
});
})();
// ]]>
之前已经使用了状态机来控制角色的行为,现在再用它来分析角色攻击阶段所发生的事情:
这里把攻击分为三种状态,目的是为了方便控制和在状态间转化,以下是updateIdle中的部分代码,用于站立过渡到攻击状态:
updateIdle:function() {
//省略部分代码
if (key[74]) { //攻击
if (!this.attacking && this.keyPressCounter++===1) {
this.attacking = true;
this.state = STATE.ATTACKING; this.play(); //播放攻击动画
}
} else {
this.attacking = false;
this.keyPressCounter = 0;
}
}
然后由攻击状态可以过渡到二段攻击状态,也可以不进行任何操作恢复到站立状态:
updateAttacking:function() {
let walked = false;
//若此阶段没有按下攻击键,则将连发锁重置
if (!key[74]) this.keyPressCounter = 0; if (key[85]) this.dashing = true; //在攻击过程中如果按下移动键,则在动画第8帧时就能移动而不用等到动画结束
if (key[65] || key[68]) {
walked = true;
if (this.getCurrentFrameIndex() >= 8) {
this.state = STATE.WALKING;
this.play();
}
} //仅动画帧<=7时按下攻击键可出现二段斩
if (this.getCurrentFrameIndex() <= 7) {
//如果没有连发锁,一直按住攻击键就能触发二段斩,不符合游戏逻辑
if (key[74] && this.keyPressCounter++===1) {
this.comboCounter = 2;
}
}
//动画开始到结束期间没有执行任何操作则执行此段分支恢复到Idle状态
else if (this.isAnimationEnd()) {
this.state = STATE.IDLE;
this.play();
} if (this.comboCounter === 2) { //执行二段斩
if (this.getCurrentFrameIndex() <= 7) {
this.state = STATE.ATTACKING_2ND;
this.play();
}
}
}
根据这种思路,可以很快完成updateAttacking2nd
和updateAttacking3nd
方法。
除此之外,角色还有很多种攻击方式,有时间的话会继续更新。
2017/4/09 更新角色跳跃
2017/4/21 更新角色冲刺
2017/5/01 更新角色状态机
2017/5/16 更新角色攻击动画
2017/5/22 更新角色移动攻击动画
2017/5/24 更新角色跳跃攻击动画