前言
我使用 canvas 来实现渲染部分 ,用 Css 来控制元素这看上去并不靠谱。
说一下流程是怎样的。通过操纵键盘的上下左右来让元素移动。
第一步 我们需要一个游戏环境和一个可操作元素
- 首先我们用canvas来创建一个画布作为游戏的舞台。 建立一个 .vue 文件 然后添加上 canvas 标签将该元素添加到 vue refs对象里,以供后面的调用。
<template>
<div>
<canvas ref="canvas"></canvas>
</div>
</template>
<script>
export default {
name: 'dome'
}
</script>
<style scoped>
/* CSS */
</style>
然后添加上 要使用的属性
export default {
name: 'dome',
data () {
return {
ctx:null, //getContext('2d') 对象
keysDown:{}, //用户 按下键盘时临时存储
hero:/* 角色对象属性 */{
image:null,
width:150,
hiegth:150,
speed:256,
x:0,
y:0
},
then:null, //记录上次 调用的时间
/* canvas 画布高度与宽度 */
canvasWidth:window.innerWidth,
canvasHeight:window.innerHeight,
}
},
}
第二步
开始写逻辑
建立 initCanvas() 方法里然后 设置初始化参数
initCanvas()/* 初始化 */ {
console.log("初始化canvas", this.$refs.canvas);
this.then = Date.now(); //先储存一次
//获取 操作对象
this.ctx = this.$refs.canvas.getContext('2d');
//设置 高度 宽度
this.$refs.canvas.width = this.canvasWidth;//window.innerWidth;
this.$refs.canvas.height = this.canvasHeight;//window.innerHeight;
//设置 其他选项
this.drawSmile();
},
drawSmile() {
//设置图片
this.hero.image = new Image();
//加载成功 将图片 渲染
this.hero.image.onload = () =>{
this.ctx.drawImage(this.hero.image, 0, 0,this.hero.width,this.hero.hiegth);
}
this.hero.image.src='./src/renderer/assets/dynamic_icon.png';
},
控制部分
因为在前端开发中,一般是用户触发了点击事件然后才去执行动画或发起异步请求之类的,但这里希望游戏的逻辑能够更加紧凑同时又要及时响应输入。所以我们就把用户的输入先保存下来而不是立即响应。为此,用keysDown这个对象来保存用户按下的键值(keyCode),如果按下的键值在这个对象里,那么就做相应处理。
默认是键盘输入,所以我们监听keysdown和keysup。
mounted()/* vue 生命周期 挂载后*/{
//检听键盘按下
addEventListener("keydown", (e)=> {
this.keysDown[e.keyCode] = true;
// console.log(this.keysDown);
// console.log(this.ctx);
}, false);
//监听键盘松开
addEventListener("keyup", (e)=> {
delete this.keysDown[e.keyCode];
}, false);
},
用于更新画面的update函数,会被规律的重复调用。首先它检查用户当前按住的是不是方向键,然后再让元素朝元素相应方向移动。这个传入的modifier变量是基于1开始且随时间变化的一个因子。例如1秒过去了,它的值就是1,元素的速度将会乘以1,也就是每秒移动256像素;如果半秒钟则它的值为0.5,元素的速度就乘以0.5也就是说这半秒内元素以正常速度一半的速度移动。理论上说因为这个update方法被调用的非常快且频繁,所以modifier的值会很小,但有了这一因子后,不管我们的代码跑得快慢,都能够保证元素的移动速 度是恒定的。
我们还要给移动增加边界检查,不然会跑到地图外面去了。
methods: {
update(modifier)/* 监听按键 modifier系数 */{
if (this.keysDown[38] && this.hero.y > 0) { // Player holding up
this.hero.y -= this.hero.speed * modifier;
//console.log('|');
}
if (40 in this.keysDown && this.hero.y < this.canvasHeight - this.hero.hiegth) { // Player holding down
this.hero.y += this.hero.speed * modifier;
}
if (37 in this.keysDown && this.hero.x > 0) { // Player holding left
this.hero.x -= this.hero.speed * modifier;
}
if (39 in this.keysDown && this.hero.x < this.canvasWidth - this.hero.width) { // Player holding right
this.hero.x += this.hero.speed * modifier;
// console.log('|');
}
}
}
render() 的作用是 将画布清除 渲染上新画面 此方法每一帧都会调用。
render()/* 渲染 */{
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
this.ctx.drawImage(this.hero.image,this.hero.x, this.hero.y,this.hero.width,this.hero.hiegth);
// Score
}
reset方法用于开始新一轮游戏,在这个方法里我们将元素放回画布的中心
reset()/* 初始 人物位置等相关属性 */{
this.hero.x = this.canvasWidth / 2;
this.hero.y = this.canvasHeight / 2;
}
主函数控制整个游戏的流程,先是拿到当前的时间用来计算时间差(距离上次主函数被调用时过了多少毫秒)。得到modifier后除以1000(也就是1秒中的毫秒数)再传入update函数。最后调用render 函数并且将本次的时间保存下来。
update() 方法是 每次渲染觉定好 下次 元素的渲染位置 。
main()/* 启动 */{
var now = Date.now();
var delta = now - this.then;
this.update(delta/1000);
this.render();
this.then = now;
//requestAnimationFrame 每帧都会调用方法
window.requestAnimationFrame(()=>this.main());
},
完整代码
export default {
data () {
return {
ctx:null,
keysDown:{},
hero:{
image:null,
width:150,
hiegth:150,
speed:256,
x:0,
y:0
},
then:null,
canvasWidth:window.innerWidth,
canvasHeight:window.innerHeight,
}
},
mounted()/* vue 生命周期 挂载后*/{
//初始化
this.initCanvas();
this.reset();
//启动
this.main();
// 当调整窗口大小时重绘canvas
window.onresize = () => {
this.initCanvas();
}
//检听键盘按下
addEventListener("keydown", (e)=> {
this.keysDown[e.keyCode] = true;
// console.log(this.keysDown);
// console.log(this.ctx);
}, false);
//监听键盘松开
addEventListener("keyup", (e)=> {
delete this.keysDown[e.keyCode];
}, false);
},
methods: {
initCanvas()/* 初始化 */ {
console.log("初始化canvas", this.$refs.canvas);
this.then = Date.now();
//获取 操作对象
this.ctx = this.$refs.canvas.getContext('2d');
//设置 高度 宽度
this.$refs.canvas.width = this.canvasWidth;//window.innerWidth;
this.$refs.canvas.height = this.canvasHeight;//window.innerHeight;
//设置 其他选项
this.drawSmile();
},
drawSmile() {
//设置图片
this.hero.image = new Image();
//加载成功 将图片 渲染
this.hero.image.onload = () =>{
this.ctx.drawImage(this.hero.image, 0, 0,this.hero.width,this.hero.hiegth);
}
this.hero.image.src='./src/renderer/assets/dynamic_icon.png';
},
update(modifier)/* 监听按键 modifier系数 */{
if (this.keysDown[38] && this.hero.y > 0) { // Player holding up
this.hero.y -= this.hero.speed * modifier;
//console.log('|');
}
if (40 in this.keysDown && this.hero.y < this.canvasHeight - this.hero.hiegth) { // Player holding down
this.hero.y += this.hero.speed * modifier;
}
if (37 in this.keysDown && this.hero.x > 0) { // Player holding left
this.hero.x -= this.hero.speed * modifier;
}
if (39 in this.keysDown && this.hero.x < this.canvasWidth - this.hero.width) { // Player holding right
this.hero.x += this.hero.speed * modifier;
// console.log('|');
}
},
render()/* 渲染 */{
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
this.ctx.drawImage(this.hero.image,this.hero.x, this.hero.y,this.hero.width,this.hero.hiegth);
// Score
},
main()/* 启动 */{
var now = Date.now();
var delta = now - this.then;
this.update(delta/1000);
this.render();
this.then = now;
//requestAnimationFrame 每帧都会调用方法
window.requestAnimationFrame(()=>this.main());
},
reset()/* 初始 人物位置等相关属性 */{
this.hero.x = this.canvasWidth / 2;
this.hero.y = this.canvasHeight / 2;
}
}
}