HTML5 2D平台游戏开发#1
在Web领域通常会用到一组sprite来展示动画,这类动画从开始到结束往往不会有用户参与,即用户很少会用控制器(例如鼠标、键盘、手柄、操作杆等输入设备)进行操作。但在游戏领域,sprite动画与控制器的操作是密不可分的。最近在写一个小游戏,涉及到很多知识点,于是打算把这些内容通过一些Demo总结出来备忘。
这是第一阶段的运行效果,用键盘A、D来控制人物左右移动,空格/K控制人物跳跃,U键冲刺:
// = this.frame.duration) {
this.index++;
this.elapsed -= this.frame.duration;
}
if(this.index >= this.length) {
if(this.animation.options.repeats) this.index = this.animation.options.startFrame;
else this.index--;
}
this.frame = this.animation.frames[this.index];
};
AnimationPlayer.prototype.setAnimation = function(animation,reset) {
this.animation = animation;
this.length = this.animation.frames.length;
};
var key = {};
var keyDef = [65,68,32,85,75];
var lastKey = 0;
var lastFrameRight;
var lastFrameLeft;
function handleKeyDown(e) {
if(keyDef.indexOf(e.keyCode) !== -1) e.preventDefault();
key[e.keyCode] = 1;
lastKey = e.keyCode;
}
function handleKeyUp(e) {
if(keyDef.indexOf(e.keyCode) !== -1) e.preventDefault();
key[e.keyCode] = 0;
}
window.addEventListener('keydown',handleKeyDown);
window.addEventListener('keyup',handleKeyUp);
//人物站立时的帧动画
var idle = [
{x:0,y:0,w:35,h:41,offsetX:0,offsetRX:0,offsetY:0,pivot:{x:0.5,y:1},duration:2000},
{x:37,y:0,w:36,h:41,offsetX:1.5,offsetRX:0,offsetY:0,pivot:{x:0.5,y:1},duration:100},
{x:75,y:0,w:36,h:41,offsetX:1.5,offsetRX:0,offsetY:0,pivot:{x:0.5,y:1},duration:100},
{x:113,y:0,w:36,h:41,offsetX:1.5,offsetRX:0,offsetY:0,pivot:{x:0.5,y:1},duration:100},
{x:151,y:0,w:36,h:41,offsetX:1.5,offsetRX:0,offsetY:0,pivot:{x:0.5,y:1},duration:100},
{x:189,y:0,w:36,h:41,offsetX:1.5,offsetRX:0,offsetY:0,pivot:{x:0.5,y:1},duration:100},
{x:227,y:0,w:36,h:41,offsetX:1.5,offsetRX:0,offsetY:0,pivot:{x:0.5,y:1},duration:100},
{x:265,y:0,w:35,h:41,offsetX:0,offsetRX:0,offsetY:0,pivot:{x:0.5,y:1},duration:100},
{x:302,y:0,w:35,h:41,offsetX:0,offsetRX:0,offsetY:0,pivot:{x:0.5,y:1},duration:100}
];
//人物移动时的帧动画
var move = [
{x:0,y:44,w:33,h:39,offsetX:-1,offsetRX:-1,offsetY:0,pivot:{x:0.5,y:1},duration:80},
{x:35,y:44,w:29,h:39,offsetX:-1,offsetRX:-1,offsetY:0,pivot:{x:0.568966,y:0.948718},duration:80},
{x:66,y:44,w:36,h:39,offsetX:3,offsetRX:3,offsetY:0,pivot:{x:0.555556,y:1},duration:80},
{x:104,y:44,w:40,h:39,offsetX:3,offsetRX:3,offsetY:0,pivot:{x:0.5,y:1},duration:80},
{x:146,y:44,w:32,h:39,offsetX:-1,offsetRX:-1,offsetY:0,pivot:{x:0.5,y:1},duration:80},
{x:180,y:44,w:22,h:39,offsetX:-6,offsetRX:-6,offsetY:0,pivot:{x:0.5,y:1},duration:80},
{x:204,y:44,w:30,h:39,offsetX:-2,offsetRX:-2,offsetY:0,pivot:{x:0.5,y:1},duration:80},
{x:236,y:44,w:36,h:39,offsetX:1,offsetRX:1,offsetY:0,pivot:{x:0.5,y:1},duration:80},
{x:274,y:44,w:37,h:39,offsetX:1,offsetRX:1,offsetY:0,pivot:{x:0.5,y:1},duration:80},
{x:313,y:44,w:35,h:39,offsetX:-4,offsetRX:2,offsetY:0,pivot:{x:0.442857,y:1},duration:80},
{x:350,y:44,w:32,h:39,offsetX:-1,offsetRX:-1,offsetY:0,pivot:{x:0.5,y:1},duration:80}
];
var readyToJump = [
{x:0,y:88,w:37,h:48,offsetX:1,offsetRX:0,offsetY:5,pivot:{x:0.5,y:1},duration:100}
];
var up = [
{x:39,y:88,w:32,h:48,offsetX:-1,offsetRX:0,offsetY:5,pivot:{x:0.5,y:1},duration:100},
{x:73,y:88,w:29,h:48,offsetX:-3,offsetRX:0,offsetY:0,pivot:{x:0.5,y:1},duration:100},
{x:104,y:88,w:31,h:48,offsetX:0,offsetRX:0,offsetY:0,pivot:{x:0.532258,y:1},duration:100},
{x:137,y:88,w:29,h:48,offsetX:-3,offsetRX:0,offsetY:0,pivot:{x:0.5,y:1},duration:100},
{x:168,y:88,w:29,h:48,offsetX:-3,offsetRX:0,offsetY:0,pivot:{x:0.5,y:1},duration:100}
];
var down = [
{x:199,y:88,w:32,h:48,offsetX:-1,offsetRX:0,offsetY:0,pivot:{x:0.5,y:1},duration:100},
{x:233,y:88,w:38,h:48,offsetX:4,offsetRX:0,offsetY:0,pivot:{x:0.578947,y:1},duration:100},
{x:273,y:88,w:35,h:48,offsetX:2,offsetRX:0,offsetY:0,pivot:{x:0.557143,y:1},duration:100},
{x:310,y:88,w:35,h:48,offsetX:3,offsetRX:0,offsetY:0,pivot:{x:0.585714,y:1},duration:100},
{x:347,y:88,w:30,h:48,offsetX:-2,offsetRX:0,offsetY:0,pivot:{x:0.5,y:1},duration:100},
{x:379,y:88,w:30,h:48,offsetX:-2,offsetRX:0,offsetY:0,pivot:{x:0.5,y:1},duration:100},
{x:411,y:88,w:33,h:48,offsetX:-1,offsetRX:0,offsetY:0,pivot:{x:0.5,y:1},duration:100},
{x:446,y:88,w:36,h:48,offsetX:1,offsetRX:0,offsetY:0,pivot:{x:0.5,y:1},duration:100}
];
var readyToDash = [
{x:0,y:140,w:36,h:37,offsetX:1,offsetRX:0,offsetY:-5,offsetRY:-5,pivot:{x:0.5,y:1},duration:40},
{x:38,y:140,w:42,h:37,offsetX:4,offsetRX:0,offsetY:-5,offsetRY:-5,pivot:{x:0.5,y:1},duration:40}
];
//冲刺中
var dash = [
{x:82,y:140,w:45,h:37,offsetX:5,offsetRX:0,offsetY:-5,offsetRY:-5,pivot:{x:0.5,y:1},duration:40},
{x:129,y:140,w:45,h:37,offsetX:5,offsetRX:0,offsetY:-5,offsetRY:-5,pivot:{x:0.5,y:1},duration:40},
{x:176,y:140,w:45,h:37,offsetX:5,offsetRX:0,offsetY:-5,offsetRY:-5,pivot:{x:0.5,y:1},duration:40},
{x:223,y:140,w:45,h:37,offsetX:5,offsetRX:0,offsetY:-5,offsetRY:-5,pivot:{x:0.5,y:1},duration:40}
];
//冲刺结束
var endDash = [
{x:270,y:140,w:40,h:37,offsetX:3,offsetRX:0,offsetY:-5,offsetRY:-5,pivot:{x:0.5,y:1},duration:100},
{x:312,y:140,w:36,h:37,offsetX:-2,offsetRX:0,offsetY:-5,offsetRY:-5,pivot:{x:0.444444,y:1},duration:100}
];
//实例化人物站立
var animation = new Animation(idle,{repeats:true,startFrame:0});
var playerIdle = new AnimationPlayer(animation);
//实例化人物移动
var animation2 = new Animation(move,{repeats:true,startFrame:0});
var playerMove = new AnimationPlayer(animation2);
//角色即将起跳
var animation3 = new Animation(readyToJump,{repeats:false,startFrame:0});
var playerReadyToJump = new AnimationPlayer(animation3);
//角色跳跃上升
var animation4 = new Animation(up,{repeats:true,startFrame:0});
var playerUp = new AnimationPlayer(animation4);
//角色下降
var animation5 = new Animation(down,{repeats:true,startFrame:0});
var playerDown = new AnimationPlayer(animation5);
//角色即将冲刺
var animation6 = new Animation(readyToDash,{repeats:false,startFrame:0});
var playerReadyToDash = new AnimationPlayer(animation6);
//角色冲刺中
var animation7 = new Animation(dash,{repeats:true,startFrame:0});
var playerDash = new AnimationPlayer(animation7);
//角色结束冲刺
var animation8 = new Animation(endDash,{repeats:false,startFrame:0});
var playerEndDash = new AnimationPlayer(animation8);
var raqId,
lastAnimationFrameTime = 0,
paused,
elapsed = 0,
now;
var motion = {
idle:playerIdle,
move:playerMove,
readyToJump:playerReadyToJump,
isUp:playerUp,
isDown:playerDown,
readyToDash:playerReadyToDash,
dash:playerDash,
endDash:playerEndDash
},
canDoubleJump = true,
keyPressCounter = 0,
keyPressCounter2 = 0,
direction = 1,
playerState = 'idle',
currentMotion = undefined,
onGround = true, //标识角色是否在地面上
jumping = false, //标识角色是否处于跳跃中
dashing = false, //标识角色是否处于冲刺中
speedX = 0, //角色水平移动速度
vy = 0, //角色垂直移动速度
vx = 0, //角色水平移动速度
isUp = false, //角色是否处于跳跃上升阶段
isDown = false, //角色是否处于跳跃下降阶段
gravity = 0.5, //重力加速度
x = 0,
y = 300,
keyLock = false,
moveSpeed = 4;
function resetFrameExceptCurrent(act) {
var keys = Object.keys(motion).filter(function(key) {
return key !== act;
}).forEach(function(key) {
motion[key].reset();
});
}
function drawBackGround() {
ctx.drawImage(bg,0,0,canvas.width,canvas.height);
}
bg.addEventListener('load',function() {
document.addEventListener('visibilitychange',function() {
if(document.visibilityState === 'hidden') {
paused = true;
console.log('游戏暂停中');
} else {
paused = false;
console.log('游戏运行中');
}
});
function draw(time) {
ctx.clearRect(0,0,canvas.width,canvas.height);
drawBackGround();
now = +new Date;
if(paused) {
setTimeout(function() {
raqId = requestAnimationFrame(draw,canvas);
},200);
} else {
if(lastAnimationFrameTime !== 0) {
elapsed = Math.min(now - lastAnimationFrameTime,16);
}
//debugger
lastAnimationFrameTime = now;
if(onGround && !jumping && !dashing) {
playerState = 'idle';
vx = 0;
}
if(key[65]) {//按下A键
if(onGround && !jumping && !dashing) playerState = 'move';
direction = -1;
if(!dashing) vx = moveSpeed * direction;
}
if(key[68]) {//按下D键
if(onGround && !jumping && !dashing) playerState = 'move';
direction = 1;
if(!dashing) vx = moveSpeed * direction;
}
if(key[85]) {//U键冲刺
if(keyPressCounter2++ === 1 && onGround) {
dashing = true;
dashLifeTime = 100;
}
if(dashLifeTime > 0) {
if(dashLifeTime >= 80) playerState = 'readyToDash';
else if(dashLifeTime 20) playerState = 'dash';
else playerState = 'endDash';
dashLifeTime -= 5;
if(onGround) vx = moveSpeed * 2 * direction;
} else if(dashLifeTime 0 && !onGround) {
playerState = 'isDown';
}
vx = moveSpeed * direction;
}
if(key[65] || key[68]) {
if(onGround && !dashing) {
vx = moveSpeed * direction;
playerState = 'move';
} else if(dashing) {
vx = moveSpeed * 2 * direction;
}
}
//todo 按住冲刺和方向和跳跃的问题
if((key[65] || key[68]) && !onGround && jumping) {
//设定按键顺序防止在空中按下冲刺键也能冲刺
if(lastKey !== 85 && lastKey !== 65 && lastKey !== 68) {
if(dashing) vx = moveSpeed * 2 * direction;
else vx = moveSpeed * direction;
}
}
} else {
keyPressCounter2 = 0;
dashing = false;
dashLifeTime = 100;
}
if(key[32] || key[75]) {//按下空格跳跃
if(keyPressCounter++ === 1) {
if(onGround && !jumping) {
playerState = 'readyToJump';
jumping = true;
onGround = false;
canDoubleJump = true;
vy = -12;
} else {
if(vy 0 && !onGround) {
playerState = 'isDown';
}
if(canDoubleJump) {
canDoubleJump = false;
vy = -12;
}
}
} else {
if(vy 0 && !onGround) {
playerState = 'isDown';
} else if(onGround) {
//在地面的情况下,如果同时按住跳跃和移动,将动作设置为move。
if(!key[65] && !key[68]) {
playerState = 'idle';
}
else playerState = 'move';
}
/*if(lastKey === 85) {//防止跳跃中按住冲刺键冲刺
if(jumping) dashing = false;
}*/
}
} else {//没有按下跳跃键时
keyPressCounter = 0;
vy = Math.max(vy,-6);
jumping = false;
if(vy 0 && !onGround) {
playerState = 'isDown';
}
}
vy += gravity;
x = Math.min(x,580);
x = Math.max(x,0);
y += vy;
x += vx;
if(y >= 300) {
y = 300;
vy = 0;
onGround = true;
jumping = false;
}
currentMotion = motion[playerState];
resetFrameExceptCurrent(playerState);
currentMotion.update(elapsed);
if(direction === 1) {
if(vx > moveSpeed) {
if(lastFrameRight !== undefined) {
drawCopyFrame(
img,
currentMotion.frame.x,currentMotion.frame.y,
currentMotion.frame.w,currentMotion.frame.h,
lastX,lastY,
currentMotion.frame.w*1.5,currentMotion.frame.h*1.5
);
}
} else {
drawCopyFrame.copyFrames = [];
}
ctx.drawImage(
img,
currentMotion.frame.x,currentMotion.frame.y,
currentMotion.frame.w,currentMotion.frame.h,
x - currentMotion.frame.offsetX,y - currentMotion.frame.offsetY,
currentMotion.frame.w*1.5,currentMotion.frame.h*1.5
);
lastFrameRight = currentMotion.frame;
lastX = x - currentMotion.frame.offsetX - 10;
lastY = y - currentMotion.frame.offsetY;
//ctx.restore();
// ctx.beginPath();
// ctx.rect(x - currentMotion.frame.offsetX,y,currentMotion.frame.w*1.5,currentMotion.frame.h*1.5);
// ctx.closePath();
// ctx.stroke();
} else {
ctx.save();
ctx.scale(-1,1);
if(Math.abs(vx) > moveSpeed) {
if(lastFrameLeft !== undefined) {
drawCopyFrame(
img,
currentMotion.frame.x,currentMotion.frame.y,
currentMotion.frame.w,currentMotion.frame.h,
lastX,lastY,
currentMotion.frame.w*1.5,currentMotion.frame.h*1.5
);
}
} else {
drawCopyFrame.copyFrames = [];
}
ctx.drawImage(
img,
currentMotion.frame.x,currentMotion.frame.y,
currentMotion.frame.w,currentMotion.frame.h,
-currentMotion.frame.w*1.5 + currentMotion.frame.offsetRX - x,y - currentMotion.frame.offsetY,
currentMotion.frame.w*1.5,currentMotion.frame.h*1.5
);
lastFrameLeft = currentMotion.frame;
lastX = -currentMotion.frame.w*1.5 + currentMotion.frame.offsetRX - x - 10;
lastY = y - currentMotion.frame.offsetY;
ctx.restore();
}
raqId = requestAnimationFrame(draw,canvas);
}
};
draw();
//raqId = requestAnimationFrame(draw,canvas);
});
function drawCopyFrame(img,sx,sy,sw,sh,dx,dy,dw,dh) {
var that = {
img:img,
opacity:0.7,
sx:sx,sy:sy,
sw:sw,sh:sh,
dx:dx,dy:dy,
dw:dw,dh:dh
};
drawCopyFrame.copyFrames.push(that);
drawCopyFrame.copyFrames = drawCopyFrame.copyFrames.filter((el)=>el.opacity > 0);
drawCopyFrame.copyFrames.forEach((el) => {
el.opacity = Number((el.opacity - 0.1).toFixed(2));
ctx.save();
ctx.globalAlpha = el.opacity;
ctx.drawImage(
el.img,
el.sx,el.sy,
el.sw,el.sh,
el.dx,el.dy,
el.dw,el.dh
);
ctx.restore();
});
}
drawCopyFrame.copyFrames = [];
img.src = imageData;
})();
// ]]>
动画帧播放器
要生成一组动画,首先需要一个能够播放各个动画帧的方法。新建一个构造函数Animation:
/**
*@param frames {Array} 元数据
*@param options {Object} 可选配置
*/
function Animation(frames,options) {
this.frames = frames || [{ x: 0, y: 0, w: 0, h: 0, duration: 0 }];
this.options = options || {
repeats:false, //是否重复播放
startFrame:0 //起始播放的位置
};
}
说明一下上面的代码,函数有两个参数,其中frames为元数据(metaData),用于标识一组sprite的坐标信息,为何需要这个数据呢,先来看一张图:
可以发现每一帧的sprite大小都不一致,特别是第二排,并不是规则的sprite,因此需要将各帧的位置大小等信息标识出来。例如这是第一排的元数据:
//人物站立时的帧动画
var idle = [
{x:0,y:0,w:40,h:41,offsetY:0,offsetX:-5,duration:2000},
{x:40,y:0,w:40,h:41,offsetY:0,offsetX:-5,duration:120},
{x:80,y:0,w:40,h:41,offsetY:0,offsetX:-5,duration:120},
{x:120,y:0,w:40,h:41,offsetY:0,offsetX:-5,duration:120},
{x:160,y:0,w:40,h:41,offsetY:0,offsetX:-5,duration:120},
{x:200,y:0,w:40,h:41,offsetY:0,offsetX:-5,duration:120},
{x:240,y:0,w:40,h:41,offsetY:0,offsetX:-5,duration:120},
{x:280,y:0,w:40,h:41,offsetY:0,offsetX:-5,duration:120},
{x:320,y:0,w:40,h:41,offsetY:0,offsetX:-5,duration:120}
];
其中x,y代表所使用sprite的位置,w,h表示该sprite的宽高,offset用于修正sprite的位置,duration表示该sprite持续的时间。
题外话:如果手工处理这些sprite信息是相当繁琐的,有一款软件叫做TexturePacker专门用来生成sprite sheets。
接着新建一个AnimationPlayer来管理Animation:
//@param animation {Object} Animation的实例
function AnimationPlayer(animation) {
var ani = animation || new Animation();
this.length = 0; //标记该组sprite中一共有几个动作
//当前组的sprite中正在执行的动作,例如idle[1]表示正在进行idle组中的第二个动画帧
this.frame = undefined;
this.index = 0;
this.elapsed = 0; //标记每帧的运行时间 this.setAnimation(ani);
this.reset();
}
//重置动画
AnimationPlayer.prototype.reset = function() {
this.elapsed = 0;
this.index = 0;
this.frame = this.animation.frames[this.index];
}; AnimationPlayer.prototype.setAnimation = function(animation) {
this.animation = animation;
this.length = this.animation.frames.length;
}; AnimationPlayer.prototype.update = function(dt) {
this.elapsed += dt; if (this.elapsed >= this.frame.duration) {
this.index++;
this.elapsed -= this.frame.duration;
} if (this.index >= this.length) {
if (this.animation.options.repeats) this.index = this.animation.options.startFrame;
else this.index--;
} this.frame = this.animation.frames[this.index];
};
最后在使用的时候将其实例化:
//站立
var animation = new Animation(idle, {
repeats: true,
startFrame: 0
});
var playerIdle = new AnimationPlayer(animation); //移动
var animation2 = new Animation(move, {
repeats: true,
startFrame: 0
});
var playerMove = new AnimationPlayer(animation2);
游戏循环
游戏运行的机制就是在每一次GameLoop中更新所有游戏元件的状态,例如更新元件的位置,碰撞检测,销毁元件等等。大体来说代码一般都具有以下结构:
(function render() {
//清除画布
context.clearRect(0,0,canvas.width,canvas.height); //执行游戏逻辑
//将更新状态后的元件重新绘制到画布上 requestAnimationFrame(render); //进入下一次游戏循环
})();
在本Demo的GameLoop中主要执行的逻辑有:
- 计算本次GameLoop与上次间隔的时间
基于时间的运动(time-base)是保证游戏运行良好的关键,假设有两台设备,一台每1秒执行一次游戏循环,另一台每2秒执行一次,并且物体以每次5px的速度移动,那么在2秒后第一台设备中的物体移动了2X5=10px,第二台设备中的物体移动了1X5=5px。很显然,经过相同的时间但最终物体达到了不同的位置,这是不合理的。如果采用基于时间的运动,则通过公式s += vt可以发现,第一台设备在经过两秒后移动的距离为5X1+5X1=10px,第二台设备移动的距离为5X2=10px,于是两台设备达到了一致的效果。更新后的render方法代码如下:
var lastAnimationFrameTime = 0,
elapsed = 0,
now; (function render() {
//清除画布
context.clearRect(0,0,canvas.width,canvas.height); now = +new Date; if (lastAnimationFrameTime !== 0) {
elapsed = Math.min(now - lastAnimationFrameTime, 16);
}
lastAnimationFrameTime = now; //执行游戏逻辑
//将更新状态后的元件重新绘制到画布上 requestAnimationFrame(render); //进入下一次游戏循环
})();
- 检测输入并绘制元件
if (key[65]) { //按下A键
playerState = 'move';
direction = 0;
x -= moveSpeed;
} else if (key[68]) { //按下D键
playerState = 'move';
direction = 1;
x += moveSpeed;
} else {
playerState = 'idle';
} currentMotion = motion[playerState];
currentMotion.update(elapsed); if (direction === 1) {
ctx.drawImage(img, currentMotion.frame.x + currentMotion.frame.offsetX, currentMotion.frame.y, currentMotion.frame.w, currentMotion.frame.h, x, 300, currentMotion.frame.w * 1.5, currentMotion.frame.h * 1.5);
} else {
//图片翻转,如有需要可以复习以前总结的知识点
/*http://www.cnblogs.com/undefined000/p/flip-an-image-with-the-html5-canvas.html*/
ctx.save();
ctx.scale( - 1, 1);
ctx.drawImage(img, currentMotion.frame.x + currentMotion.frame.offsetX, currentMotion.frame.y, currentMotion.frame.w, currentMotion.frame.h, -currentMotion.frame.w * 1.5 + currentMotion.frame.offsetX - x, 300, currentMotion.frame.w * 1.5, currentMotion.frame.h * 1.5);
ctx.restore();
}
游戏暂停
如果在游戏运行期间窗口失去焦点,则应当暂停游戏,因为此时浏览器会以低帧率运行游戏以节省开销,这样导致的结果就是当玩家返回窗口时,deltaTime会有爆炸性的增长,从而使元件更新异常。最常见的是一些碰撞检测不能正常工作或者游戏人物高速移动。因此当窗口失去焦点时,应当暂停游戏。在主流浏览器中,可以用下面的代码标识暂停:
document.addEventListener('visibilitychange',function() {
if (document.visibilityState === 'hidden') {
paused = true;
console.log('游戏暂停中');
} else {
paused = false;
console.log('游戏运行中');
}
});
同时更新render方法:
(function render() {
//省略部分代码以节省篇幅
if (paused) {
setTimeout(function() {
requestAnimationFrame(draw);
},200);
} else {
//执行游戏逻辑
requestAnimationFrame(render); //进入下一次游戏循环
}
})();
Summary
以上就是这个Demo的主要知识点,暂时先总结到这,后面有时间还会陆续更新。
更新日志
2017/4/9 更新角色跳跃
2017/4/21 更新角色冲刺
2017/5/1 更新角色状态机
2017/5/16 更新角色攻击动画
HTML5 2D平台游戏开发#1的更多相关文章
- HTML5 2D平台游戏开发#4状态机
在实现了<HTML5 2D平台游戏开发——角色动作篇之冲刺>之后,我发现随着角色动作的增加,代码中的逻辑判断越来越多,铺天盖地的if() else()语句实在让我捉襟见肘: 这还仅仅是角色 ...
- HTML5 2D平台游戏开发#6地图绘制
此前已经完成了一部分角色的动作,现在还缺少可以交互的地图让游戏看起来能玩.不过在开始之前应当考虑清楚使用什么类型的地图,就2D平台游戏来说,一般有两种类型的地图,Tile-based和Art-base ...
- HTML5 2D平台游戏开发#11斜坡物理
在游戏中会经常遇到斜坡地形,比如众所周知的魂斗罗,角色可以在坡上移动和跳跃: 斜坡在2D游戏中很常见,处理起来也较为棘手.最初我打算用分离轴定律来实现,在建立了一个物理模型之后: 发现上坡时没什么问题 ...
- HTML5 2D平台游戏开发#8指令技
一般在动作游戏中,玩家可以通过对输入设备输入一系列的指令让角色完成某个或多个特定的动作.以格斗游戏<拳皇>为例,键入↓↘→↘↓↙← + A or C可以触发IORI的必杀技八稚女: 通过一 ...
- HTML5 2D平台游戏开发#7Camera
在庞大的游戏世界中,玩家不能一览地图全貌,而是只能看到其中一部分,并一步步探索,这时就要用到一种技术来显示局部的地图,游戏术语称为摄像机(Camera).下面两张图中的白色矩形框表示了Camera的作 ...
- HTML5 2D平台游戏开发#5攻击
目前为止,角色除了基本的移动外还什么都不能做,于是我打算先实现角色的攻击动画.角色的普通攻击一共可以分为三个阶段: 一段斩 二段斩 三段斩 移动攻击 跳跃攻击 触发方式为角色站立时按下J(攻击)键,角 ...
- HTML5 2D平台游戏开发#10Wall Jump
这个术语不知道怎么翻译比较贴切,但并不妨碍对字面意思的理解,大概就是飞檐走壁.比如: 这是游戏<忍者龙剑传>中的场景,玩家可以通过操纵角色在墙面上移动并跳跃. 首先需要实现角色抓墙这一动作 ...
- HTML5 2D平台游戏开发#9蓄力技
在很多动作游戏中,玩家操控的角色可以施放出比普通攻击更强力的蓄力技,一般操作为按住攻击键一段时间然后松开,具体效果像下面这张图: 要实现这个操作首先要记录下按键被按住的时间,初始是0: this.sa ...
- HTML5 2D平台游戏开发#2跳跃与二段跳
在上一篇<Canvas制作时间与行为可控的sprite动画>中已经实现了角色的左右移动,本篇继续实现角色的一系列动作之一:跳跃.先来看看最终效果: 要实现跳跃,必须模拟垂直方向的速度和重力 ...
随机推荐
- BZOJ 3509: [CodeChef] COUNTARI
3509: [CodeChef] COUNTARI Time Limit: 40 Sec Memory Limit: 128 MBSubmit: 883 Solved: 250[Submit][S ...
- dva脚手架 dva-cli 配置roadhogrc,antd-mobile样式按需加载 不生效的问题
1.新安装dva-cli脚手架版本0.9.2,dva版本是2.4.1,roadhogrc版本是2.4.9 roadhogrc2 与1 的区别把roadhogrc 改成了webpackrc 所以配置an ...
- 组合模式Composite Pattern(转)
什么是组合模式呢?简单来说组合模式就是将对象合成树形结构以表示“部分整体”的层次结构,组合模式使用户对单个对象和组合对象使用具有一致性. 组合模式(Composite Pattern)有时候又叫部分- ...
- i2c 协议解析【转】
转自:http://blog.csdn.net/g_salamander/article/details/8016698 版权声明:本文为博主原创文章,未经博主允许不得转载. 1.基本概念 主机 ...
- 4C 2018 倒数的字符序列
输入L,N 将长度为L的小写字符串序列,比如L=3,序列为aaa,aab,aac,...aba,...zzz,求倒数第N个字符串序列是什么.输入3 7421 得到pat 第一感觉是A,B,C...,Z ...
- 详解BitMap算法
所谓的BitMap就是用一个bit位来标记某个元素所对应的value,而key即是该元素,由于BitMap使用了bit位来存储数据,因此可以大大节省存储空间. 1. 基本思想 首先用一个简单的例子 ...
- idea---搭建maven,tomcat入门
这篇随笔讲讲idea工具的安装和使用和在idea中搭建maven的分享. 一.概念 1.IntelliJ IDEA是什么? DEA 全称 IntelliJ IDEA,是java编程语言开发的集成环境. ...
- asp.net站点从2003服务器迁移到2008服务器出现定义了重复的“system.web.extensions/scripting/scriptResourceHandler”节的问题解决
解决方法: 1.从4.0降到2.0. 2.直接删除整个节点,如下:
- Mac git 的使用
1. mac 安装git brew install git 2.初使化 git config --global user.name "mygit" git config --glo ...
- 七天学会ASP.NET MVC(七)——创建单页应用 【转】
http://www.cnblogs.com/powertoolsteam/p/MVC_Seven.html 系列文章 七天学会ASP.NET MVC (一)——深入理解ASP.NET MVC 七天学 ...