【JavaScript游戏开发】使用HTML5+Canvas+JavaScript 封装的一个超级马里奥游戏(包含源码)
这个游戏基本上是建立在JavaScript模块化的开发基础上进行封装的,对游戏里面需要使用到的游戏场景进行了封装,分别实现了Game,Sprite,enemy,player, base,Animation 等游戏类,后续代码还可以继续优化,最终实现的效果如下:
其他的所有核心代码已经开源:https://github.com/xiugangzhang/SuperMarioGame
在线预览游戏效果:
http://htmlpreview.github.io/?https://github.com/xiugangzhang/SuperMarioGame/blob/master/index.html
感兴趣的朋友可以对其进行继续优化,加上后续的其他功能!
其中游戏Game类代码如下:
// 完成Game类的封装
function Game(cfg) {
for (var attr in cfg) {
// 这里的this指的就是Game的对象
this[attr] = cfg[attr];
}
}
// 定义原型方法和属性
Game.prototype = {
constructor: Game,
// 游戏画布的初始化(这个是游戏画布的默认宽度和高度)
width: 640,
height: 480,
// 画布canvas和绘图句柄gc(在构造函数中已经完成了初始化)
canvas: null,
gc: null,
// 帧速率和时间间隔
FPS: 40,
sleep: 0,
// 游戏中的精灵
sprites: null,
// 游戏中的运动的背景
skyOffset: 0,
grassOffset: 0,
treeOffset: 0,
nearTreeoffset: 0,
TREE_VELOCITY: 20,
FAST_TREE_VELOCITY: 40,
SKY_VELOCITY: 8,
GRASS_VELOCITY: 75,
lastTime: 0,
lastUpdateFPS: 0,
lastUpdateTime: 0,
// 游戏场景的初始化(主要场景参数的初始化处理)
init: function () {
// 直接手动创建canvas元素
this.canvas = document.createElement("canvas");
this.canvas.width = this.width;
this.canvas.height = this.height;
document.body.appendChild(this.canvas);
// 设置我的绘图句柄
this.gc = this.canvas.getContext("2d");
// 初始化键盘的事件
this.initEvent();
// 帧速率不为空,设置我的间隔时间
if (this.FPS) {
this.sleep = Math.floor(1000 / this.FPS);
}
// 当前的精灵(要么是自己, 要么是一个空数组)
this.sprites = this.sprites || [];
// 对每一个精灵完成初始化
for (var i = 0, len = this.sprites.length; i < len; i++) {
this.sprites[i].init();
}
},
// 初始化键盘的事件
initEvent: function () {
// 按下按键
document.addEventListener("keydown", function (ev) {
keyState[ev.keyCode] = true;
console.log(keyState);
}, true);
// 松开按键
document.addEventListener("keyup", function (ev) {
keyState[ev.keyCode] = false;
console.log(keyState);
}, true);
},
// 游戏开始, 就进入到主循环
start: function () {
// this指向的是Game这个对象
var Me = this;
// 记录一下,游戏开始的时间
Me.startTime = Date.now();
// 主循环
this.mainLoop = setInterval(function () {
// 距离上一次间隔的时间
var deltaTime = Me.sleep;
// 在主循环的执行过程中来实现碰撞检测的功能(一直在不断地检测是否发生了碰撞)
Me.run(deltaTime);
}, Me.sleep);
},
// 主循环中需要执行的操作
run: function (deltaTime) {
// 显示当前游戏持续进行的时间(玩家在这个游戏中持续的时间就是他的分数)
var playedTime = Date.now() - this.startTime;
// 在主界面上面显示时间(span标签)
document.getElementById("timeCount").innerHTML = playedTime.toString();
document.getElementById("lifeCount").innerHTML = this.sprites[0].HP.toString();
// 开始碰撞检测
var coll = this.checkCollide();
// 只要coll不为空, 就说明有其他玩家和我发生了碰撞
if (coll) {
// 如果发生敌人和玩家的碰撞, 就结束游戏(有三个生命值)
if (this.sprites[0].HP > 0) {
this.sprites[0].HP--;
}
}
// 我是精灵角色中的第0个角色,直接得到我的生命值并显示
document.getElementById("lifeCount").innerHTML = this.sprites[0].HP.toString();
if (this.sprites[0].HP == 0) {
// 1. 清空主循环中的定时器
clearInterval(this.mainLoop);
alert("Game Over.\n Your score : " + playedTime);
// 2.直接退出程序
return;
}
// 更新画布
this.update(deltaTime);
// 清空画布
this.clear(deltaTime);
// 重绘画布
this.draw(deltaTime);
// 进入主循环之后, 还要不断地处理接收键盘事件
this.handleInput();
},
// 开始实现碰撞的检测, 返回true就表示发生了玩家和敌人的碰撞
checkCollide: function () {
// 1.拿到我的玩家这个对象
var player = this.sprites[0];
// 注意这里是从第一个场景中的人物和我来逐一检测(我是第0个人物, 其他的都是敌人)
for (var i = 1, len = this.sprites.length; i < len; i++) {
var sprite = this.sprites[i];
// 对于游戏场景中的除了自己的其他所有的精灵和我一一进行碰撞检测
var coll = sprite.collideWidthOther(player);
if (coll) {
return coll;
}
}
return false;
},
// 更新精灵的状态
update: function (deltaTime) {
for (var i = 0, len = this.sprites.length; i < len; i++) {
var sprite = this.sprites[i];
// 开始更新每一个精灵的坐标状态(运动状态信息)
sprite.update(deltaTime);
}
},
// 清空画布信息
clear: function () {
// 清空画布
this.gc.clearRect(0, 0, this.canvas.width, this.canvas.height);
var fps = this.caculateFPS();
this.fps = fps;
// 显示帧速率到画布上面
var now = Date.now();
if (now - this.lastUpdateTime > 1000) {
this.lastUpdateTime = now;
this.lastUpdateFPS = fps;
document.getElementById("fps").innerText = this.lastUpdateFPS.toFixed();
}
this.initGameMap();
},
// 绘制背景地图
initGameMap: function () {
var fps = this.fps;
// 实现移动的位移量
this.skyOffset = this.skyOffset < this.canvas.width ?
this.skyOffset + this.SKY_VELOCITY / fps : 0;
this.grassOffset = this.grassOffset < this.canvas.width ?
this.grassOffset + this.GRASS_VELOCITY / fps : 0;
this.treeOffset = this.treeOffset < this.canvas.width ?
this.treeOffset + this.TREE_VELOCITY / fps : 0;
this.nearTreeOffset = this.nearTreeOffset < this.canvas.width ?
this.nearTreeOffset + this.FAST_TREE_VELOCITY / fps : 0;
var sky = ImgCache["sky"],
tree = ImgCache["tree-twotrunks"],
nearTree = ImgCache["smalltree"],
grass = ImgCache["grass"],
grass2 = ImgCache["grass2"];
this.gc.save();
this.gc.translate(-this.skyOffset, 0);
this.gc.drawImage(sky, 0, 0);
this.gc.drawImage(sky, sky.width - 2, 0);
this.gc.restore();
this.gc.save();
this.gc.translate(-this.treeOffset, 0);
this.gc.drawImage(tree, 100, 240);
this.gc.drawImage(tree, 1100, 240);
this.gc.drawImage(tree, 400, 240);
this.gc.drawImage(tree, 1400, 240);
this.gc.drawImage(tree, 700, 240);
this.gc.drawImage(tree, 1700, 240);
this.gc.restore();
this.gc.save();
this.gc.translate(-this.nearTreeOffset, 0);
this.gc.drawImage(nearTree, 250, 240);
this.gc.drawImage(nearTree, 1250, 240);
this.gc.drawImage(nearTree, 800, 240);
this.gc.drawImage(nearTree, 1800, 240);
this.gc.restore();
this.gc.save();
this.gc.translate(-this.grassOffset, 0);
this.gc.drawImage(grass, 0, this.canvas.height - grass.height);
this.gc.drawImage(grass, grass.width - 5,
this.canvas.height - grass.height);
this.gc.drawImage(grass2, 0, this.canvas.height - grass2.height);
this.gc.drawImage(grass2, grass2.width,
this.canvas.height - grass2.height);
this.gc.restore();
},
// 绘制背景滚动的效果
caculateFPS: function (now) {
if (now == undefined) {
now = Date.now();
}
var fps = 1000 / (now - this.lastTime);
this.lastTime = now;
return fps;
},
// 开始重新绘制精灵
draw: function (deltaTime) {
for (var i = 0, len = this.sprites.length; i < len; i++) {
var sprite = this.sprites[i];
// 开始绘制
sprite.draw(this.gc);
}
},
// 游戏中的处理用户的输入
handleInput: function () {
for (var i = 0, len = this.sprites.length; i < len; i++) {
var sprite = this.sprites[i];
// 先判断一下,这个精灵有没有handleInput属性
if (sprite.handleInput) {
// 如果这个精灵有这个属性或者方法的话, 就去调用精灵自己的处理函数
sprite.handleInput();
}
}
}
}
鉴于代码太多,已经将SuperMario的所有核心代码开源:
开源地址:https://github.com/xiugangzhang/SuperMarioGame

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
「python」ghost 编码问题
ghost 参考 Alex-金角大王在知乎上关于python编码的回答,链接:https://www.zhihu.com/question/31833164/answer/381137073 这一篇总结的标题之所以是「ghost」鬼魂,是因为这个问题就像「ghost」一样,随时会出现在我的代码,我的程序中,时不时会冒出来,然后需要解决。 UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position 205: illegal multibyte sequence" 首先编码有很多种例如: ASCII 占1个字节,只支持英文 GB2312 占2个字节,支持6700+汉字 GBK GB2312的升级版,支持21000+汉字 Shift-JIS 日本字符 ks_c_5601-1987 韩国编码 TIS-620 泰国编码 虽然每个国家都有自己的编...
-
下一篇
phpmyadmin 503错误无法访问的解决方法
昨天ytkah更新了一些服务器软件,今天访问数据库居然出现503错误,主要提示如下。点开phpmyadmin设置,查看了一下端口,没有改动;重启了一下phpmyadmin也不能运行;再看了一下php版本,发现默认值是“纯静态”,这个应该跟php版本有关,因为phpmyadmin也是运行在php上,纯静态是错误的 1 The server is temporarily unable to service your request due to maintenance downtime or capacity problems. Please try again later. phpmyadmin设置 phpmyadmin管理的php版本修改,选择php-71保存后,再访问phpmyadmin控制面板,没问题了!如果还是访问不了,多试几次。 更多详情请访问503错误。
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- 2048小游戏-低调大师作品
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- MySQL数据库在高并发下的优化方案
- Dcoker安装(在线仓库),最新的服务器搭配容器使用
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Docker快速安装Oracle11G,搭建oracle11g学习环境