【JavaScript游戏开发】使用HTML5+Canvas+JavaScript 封装的一个超级马里奥游戏(包含源码)

简介: 版权声明:本文为博主原创文章,未经博主允许不得转载。更多学习资料请访问我爱科技论坛:www.52tech.tech https://blog.csdn.net/m0_37981569/article/details/81941806 ...
版权声明:本文为博主原创文章,未经博主允许不得转载。更多学习资料请访问我爱科技论坛:www.52tech.tech https://blog.csdn.net/m0_37981569/article/details/81941806

这个游戏基本上是建立在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

 

 

相关文章
|
9天前
|
设计模式 JavaScript
JS发布订阅模式封装(纯手工)
发布订阅模式是JS常用的设计模式,在面试中也会经常遇到,以下是我的手写实现方式,经测试效果不错,小伙伴们们可以直接拷贝使用。
|
7天前
|
XML JavaScript 前端开发
JavaWeb基础4——HTML,JavaScript&CSS
HTML,JavaScript&CSS、元素、标签、css 选择器、属性、JavaScript基础语法、JavaScript对象、BOM浏览器对象模型、DOM文档对象模型、事件监听、正则对象RegExp/ES6
JavaWeb基础4——HTML,JavaScript&CSS
|
30天前
|
Dart 前端开发 Java
|
6天前
|
前端开发
在 HTML canvas 绘制文本
在 HTML canvas 绘制文本
8 0
|
30天前
|
数据可视化 前端开发 JavaScript
Echarts+JS实现智慧门店可视化大屏!!附源码!!
Echarts+JS实现智慧门店可视化大屏!!附源码!!
|
30天前
|
前端开发 程序员
HTML+CSS+JavaScript制作动态七夕表白网页(含音乐+自定义文字)
一年一度的520情人节/七夕情人节/女朋友生日/程序员表白,是不是要给女朋友或者正在追求的妹子一点小惊喜呢,今天这篇博客就分享下前端代码如何实现HTML+CSS+JavaScript制作七夕表白网页(含音乐+自定义文字)。赶紧学会了,来制作属于我们程序员的浪漫吧!
41 0
HTML+CSS+JavaScript制作动态七夕表白网页(含音乐+自定义文字)
|
30天前
|
数据可视化 前端开发 JavaScript
Echarts+JS实现农业指挥舱可视化大屏!!附源码!!
Echarts+JS实现农业指挥舱可视化大屏!!附源码!!
|
20天前
|
前端开发 JavaScript 搜索推荐
打造个人博客网站:从零开始的HTML、CSS与JavaScript之旅
在这个数字时代,拥有一个个性化的网络空间已成为许多人的梦想。本文将引导你了解如何从零开始,使用HTML、CSS和JavaScript创建属于自己的博客网站。我们将探索这些技术的基础概念,并通过实际代码示例展示如何将静态页面转变为动态交互式网站。无论你是编程新手还是希望扩展技能的开发者,这篇文章都将为你提供一条清晰的学习路径。【8月更文挑战第31天】
|
20天前
|
Java 开发者 关系型数据库
JSF与AWS的神秘之旅:如何在云端部署JSF应用,让你的Web应用如虎添翼?
【8月更文挑战第31天】在云计算蓬勃发展的今天,AWS已成为企业级应用的首选平台。本文探讨了在AWS上部署JSF(JavaServer Faces)应用的方法,这是一种广泛使用的Java Web框架。通过了解并利用AWS的基础设施与服务,如EC2、RDS 和 S3,开发者能够高效地部署和管理JSF应用。文章还提供了具体的部署步骤示例,并讨论了使用AWS可能遇到的挑战及应对策略,帮助开发者更好地利用AWS的强大功能,提升Web应用开发效率。
42 0
|
20天前
|
移动开发 前端开发 JavaScript
前端表单验证的完美攻略:HTML5属性与JavaScript方法的无缝对接,让你的Web应用数据输入既安全又优雅
【8月更文挑战第31天】本文介绍前端表单验证的重要性及其实现方法,利用HTML5的内置属性如`required`、`pattern`和`minlength`进行基本验证,并借助JavaScript处理复杂逻辑,如密码确认。通过示例代码详细展示了如何结合两者实现高效且友好的表单验证,同时使用CSS增强用户体验。此方法简化开发流程并提升验证效果。
22 0