65.9K
CodeProject 正在变化。 阅读更多。
Home

使用JavaScript进行游戏开发

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.98/5 (40投票s)

2013年5月16日

CPOL

13分钟阅读

viewsIcon

205154

使用 EaseJS 库通过 JavaScript 进行游戏开发的快速简易教程。

引言

对我而言,游戏开发是一种爱好,或许还不止于此。随着游戏吸引力和功能性的不断提升,游戏开发可能会变得复杂。理论上,人人都知道的语言都可以用来开发游戏(当然,有些语言会让开发过程变得非常困难)。归根结底,游戏开发不必复杂或困难,你可以非常轻松地完成它。

背景

虽然没有研究证明这一点,但 JavaScript 可能是世界上最流行的语言,这当然是因为互联网以及支持平板电脑和移动设备的 HTML 5 的发展。

因此,我选择发布这篇关于如何使用 HTML 5 和 JavaScript 进行游戏开发的教程。

在开始之前,让我们先来谈谈游戏开发的基础知识。

游戏循环

游戏开发中最重要的元素之一叫做游戏循环,游戏循环负责通过绘制游戏图形来建立游戏。游戏循环允许游戏在不进行用户新操作的情况下继续进行。

那么如何创建游戏循环呢?下面的示例是许多开发者首先想到的创建后台进程的方法,但可能不是最有效的方法。

while (!MyGame.stopped) { 
  MyGame.update(); 
} 

游戏循环的主要功能之一是调用 update 函数,该函数负责更新游戏(图形、对象位置等)。

如果我们使用 While 语句调用 update 函数,浏览器很可能会阻止该函数,而且我们无法有效控制 update 函数的调用频率,换句话说,我们无法控制 FPS 速率。

创建游戏循环的另一种解决方案是使用 setInterval 函数,这样我们就可以轻松控制 update 函数的调用时间。

MyGame.fps = 60;
MyGame.update = function () {
    // Move game parts
};
// Start the game loop
MyGame._intervalId = setInterval(Game.update, 1000 / Game.fps);
// To stop the game, use the following:
clearInterval(MyGame._intervalId); 

当我写 FPS 时,我说的不是 - 第一人称射击,而是 每秒帧数 - 帧率(也称为帧频率)是成像设备生成称为帧的唯一连续图像的频率(速率)。
人眼及其大脑每秒可以处理 10 到 12 个单独的图像,并将它们视为独立的,因此为了提供动画效果(游戏、电影等),FPS 应该更高。

第一部分 - EASEJS 入门

尽管游戏循环的创建看起来很简单,但我不会为我的游戏构建它,我将使用 EASEJS 库,它将提供游戏循环和一些其他功能来帮助我构建游戏。

EaselJS 为使用丰富的图形和 HTML5 Canvas 的交互提供了直接的解决方案。它提供了对 Flash 开发者来说熟悉的 API,同时拥抱了 JavaScript 的特点。它包含一个完整的、分层的显示列表、一个核心交互模型以及使 Canvas 使用起来更轻松的辅助类。

canvas 元素是 HTML5 的一部分,允许动态、可脚本化的 2D 形状和位图图像渲染。它是一个低级、过程式的模型,用于更新位图,并且没有内置的场景图。

<script type="text/javascript" src="js/easeljs/geom/Matrix2D.js"></script>
<script type="text/javascript" src="js/easeljs/geom/Rectangle.js"></script>
<script type="text/javascript" src="js/easeljs/events/EventDispatcher.js"></script>
<script type="text/javascript" src="js/easeljs/utils/UID.js"></script>
<script type="text/javascript" src="js/easeljs/utils/Ticker.js"></script>
<script type="text/javascript" src="js/easeljs/utils/SpriteSheetUtils.js"></script>
<script type="text/javascript" src="js/easeljs/display/DisplayObject.js"></script>
<script type="text/javascript" src="js/easeljs/display/Container.js"></script>
<script type="text/javascript" src="js/easeljs/display/Stage.js"></script>
<script type="text/javascript" src="js/easeljs/display/SpriteSheet.js"></script>
<script type="text/javascript" src="js/easeljs/display/BitmapAnimation.js"></script>
<script type="text/javascript" src="js/easeljs/display/Graphics.js"></script>
<script type="text/javascript" src="js/easeljs/display/Bitmap.js"></script>
<script type="text/javascript" src="js/easeljs/display/Text.js"></script> <span style="font-size: 9pt;"> </span> 

在 Body 部分添加 Canvas 元素,Canvas 将是游戏容器。

<canvas id="gameCanvas" width="600" height="100"></canvas> 

现在让我们开始使用 EASEJS,首先我们需要创建一个新的类型为 Stage 的对象。

Stage 是显示列表的根级别容器。每次调用其 Stage/tick 方法时,它都会将其显示列表渲染到目标 canvas。

stage = new createjs.Stage(id("gameCanvas")); 

初始化 Stage 对象后,我们就可以开始向游戏中添加元素了,方法之一是创建一个新的 Bitmap 对象(也是 EASEJS 的一部分),它接收图像路径,然后使用 addChild 函数将新的 Bitmap 对象添加到 Stage 中。当所有游戏元素都已添加到 Stage 对象后,我们需要通过调用 createjs.Ticker.setFPS(30); 来启动游戏循环,最后注册到 "tick" 事件(一旦 tick 事件被调用,就是时候更新游戏了…)。

Ticker 提供了一个集中的 tick 或心跳广播,在设定的时间间隔内进行。监听器可以订阅 tick 事件,以便在设定的时间间隔过去时收到通知。

请注意,tick 事件调用的间隔是目标间隔,在 CPU 负载高的时候可能会以较慢的间隔广播。Ticker 类使用静态接口(例如 Ticker.getPaused()),不应实例化。

var stage, image, fpstext;
function init() {
    stage = new createjs.Stage(id("gameCanvas"));
    image = new createjs.Bitmap("assets/hill.png");
    stage.addChild(image);
    createjs.Ticker.setFPS(30);
    createjs.Ticker.addEventListener("tick", tick);
} 

现在我们需要实现 tick 函数,在本演示中,我们将图像向右移动 10 像素,当图像到达 600 像素时,我们将图像返回到起始位置(x= 0),在更改图像属性后,我们需要调用 stage.update() 函数来刷新 canvas。

function tick() {
    image.x += 10;
    if (image.x > 600)
        image.x = 0;
    stage.update();
} 

在我们看到结果之前,让我们向 Stage 添加一个额外的元素,一个 Text 元素。我想在 Stage 上显示 FPS 率,在 Init 函数中添加以下代码,将 Text 元素添加到 Stage 中。

fpstext = new createjs.Text("fps:", "20px Arial", "#7a1567");
fpstext.x = 0;
fpstext.y = 20;
stage.addChild(fpstext); 

要在 tick 函数中添加当前 FPS 率,请添加以下代码。

 fpstext.text = Math.round(createjs.Ticker.getMeasuredFPS()) + " fps"; 

查看演示 

第二部分 - 精灵图 (Sprites)

在第一部分中,我们了解了 EASEJS 的基础知识,从向 Stage 添加元素到如何移动这些元素。在这一部分,我们将使用 Sprite 来显示玩家对象在不同模式下的状态。(跳跃、奔跑、游戏风格等)。

精灵图是直接绘制到渲染目标上的 2D 位图,不使用管线进行变换、光照或效果。精灵图通常用于显示生命值条、生命数或分数等文本。

精灵图是一张图片,其中包含多张图片,我们可以通过指定该图片的坐标来显示精灵图中的任何图片。

首先,让我们开始将玩家精灵图加载到游戏中,重要的是要确保在启动游戏循环之前图片已加载,这就是我注册 onload 事件的原因,一旦加载事件被调用,我就会调用 start 函数。

var stage, player, playerImage, _action;
var playerWH = 64;
var frequency = 4;
function init() {
    playerImage = new Image();
    playerImage.src = "assets/Player.png";
    playerImage.onload = start;
} 

在第一部分中,我们使用了 Bitmap 对象来显示 Stage 中的图像,因为我们正在使用 Sprite,所以我们需要使用另一个能够处理 Sprite 图像的对象。

SpriteSheet - 封装与精灵表相关的属性和方法。精灵表是一系列图像(通常是动画帧)组合成一个更大的图像(或多个图像)。例如,一个包含八个 100x100 图像的动画可以组合成一个 400x200 的精灵表(每行 4 帧,共 2 行)。

在创建 Stage 对象后,我们将创建 playerSprite,它代表一个 SpriteSheet 对象。传递给 SpriteSheet 构造函数的数据定义了三个关键信息:

  1. 要使用的图像。
  2. 单个图像帧的位置。此数据可以有两种表示方式:作为顺序的、等大小帧的规则网格,或者作为单独定义的、变大小帧的不规则(非顺序)排列。
  3. 同样,动画也可以有两种方式表示:作为由起始帧和结束帧 [0,3] 定义的顺序帧系列,或者作为帧列表 [0,1,2,3]。

function start() {
    stage = new createjs.Stage(id("gameCanvas"));
    var playerSprite = new createjs.SpriteSheet({
        animations:
        {
            "walk": [0, 9, null],
            "fall": [10, 21, null],
            "jump": [22, 32, null],
            "gamgam": [34, 64, null],
            "stand": [44, 44, null],
            "special_combo": [22, 32, "gamgam"]
        },
        images: [playerImage],
        frames:
            {
                height: playerWH,
                width: playerWH,
                regX: 0,
                regY: 0,
            } 
<span style="font-size: 9pt;">    });</span> 
<span style="font-size: 9pt;">}  </span><span style="font-size: 9pt;"> </span>

上面的代码通过指定图像起始 ID 和结束 ID 来定义我们玩家的不同模式:跳跃、行走、下落等。从下图可以看出,行走动作从图像 ID = 0 开始到图像 ID = 9 结束。

加载精灵图并定义所有玩家动作后,我们将使用 BitmapAnimation 对象来帮助我们逐个播放这些图像以创建动画效果。

BitmapAnimation -显示来自精灵图图像的帧或帧序列(即动画)。精灵图是将一系列图像(通常是动画帧)组合成一张图像。例如,一个包含 8 个 100x100 图像的动画可以组合成一个 400x200 的精灵图(每行 4 帧,共 2 行)。您可以显示单个帧、将帧作为动画播放,甚至将动画排序。

start 函数中,添加以下代码来播放行走动作。

player = new createjs.BitmapAnimation(playerSprite);
player.x = id("gameCanvas").width / 2;
player.y = id("gameCanvas").height - playerWH;
//player.currentFrame = 2;
player.gotoAndPlay("walk");
stage.addChild(player);
createjs.Ticker.setFPS(60);
createjs.Ticker.addEventListener("tick", tick); 

现在您可以看到玩家在屏幕上行走或执行其他动作。我添加了以下代码来允许在游戏运行时更改 FPS、速度和频率。

switch (_id) {
    case "fps":
        createjs.Ticker.setFPS(parseInt(value));
        break;
    case "velocity":
        player.vX = parseInt(value);
        break;
    case "frequency":
        frequency = parseInt(value);
        start();
        break;
}  

查看演示 

第三部分 - 移动玩家

第二部分展示了如何使用 Sprite 来显示我们玩家的不同模式(跳跃、行走、下落等),现在我们将学习如何在游戏空间中移动我们的玩家。

为了实现这一点,我们需要修改 tick 函数(我们的游戏循环 update):

if (player.x >= id("gameCanvas").width - playerWH) {
    // You reached the end -  We need to walk left
}
if (player.x < playerWH) {
    // You reached the end -  We need to walk right
} 

在上面的代码中,我们定义了游戏边界,如果玩家到达游戏右边界,我们希望玩家向左移动,反之亦然。

要更改玩家方向(左或右),我们将使用玩家对象也包含的 direction 属性(BitmapAnimation)

if (player.x >= id("gameCanvas").width - playerWH) {
    // You reached the end -  We need to walk left
    player.direction = -90;
}
if (player.x < playerWH) {
    // You reached the end -  We need to walk right
    player.direction = 90;
}  

更改玩家方向后,我们要做的就是通过更改玩家的 x 属性来移动玩家。

player.direction == 90 ? player.x += player.vX : player.x -= player.vX; 

完整的 tick 函数代码:

 function tick() {           
   if (player.x >= id("gameCanvas").width - playerWH) {
       // You reached the end -  We need to walk left
       player.direction = -90;
       player.gotoAndPlay("walk")
   }

   if (player.x < playerWH) {
       // You reached the end -  We need to walk right
       player.direction = 90;
       player.gotoAndPlay("walk_h");
   }

   // Moving the sprite based on the direction & the speed
   player.direction == 90 ? player.x += player.vX : player.x -= player.vX;
    
stage.update();
} 
现在我们面临一个问题,即使玩家向右移动,图像也没有翻转。

 

我们可以使用 CSS 3 scaleX 来翻转图像,但这会损害游戏性能。EASEJS 为我们提供了解决这个问题的方案,我们可以使用 addFlippedFrames 函数。addFlippedFrames 通过水平、垂直或同时翻转原始帧来扩展现有的精灵表,并添加适当的动画和帧数据。翻转后的动画名称将附加后缀(_h、_v、_hv,根据情况)。确保在调用此方法之前精灵表图像已完全加载。

所以在创建玩家对象之前,让我们添加以下行:

//( spriteSheet  horizontal  vertical  both ) 
createjs.SpriteSheetUtils.addFlippedFrames(playerSprite, true, false, false); 

start 函数应该如下所示:

function start() {
stage = new createjs.Stage(id("gameCanvas"));

var playerSprite = new createjs.SpriteSheet({
	animations:
	{
		"walk": [0, 9, null, frequency],
		"fall": [10, 21, null, frequency],
		"jump": [22, 32, null, frequency],
		"gamgam": [34, 64, null, frequency],
		"stand": [44, 44, null, frequency],
		"special_combo": [22, 32, "gamgam"]
	},
	images: [playerImage],
	frames:
		{
			height: playerWH,
			width: playerWH,
			regX: 0,
			regY: 0,
		}
});

createjs.SpriteSheetUtils.addFlippedFrames(playerSprite, true, false, false);

var fps = parseInt(id("fps").value);
var velocity = parseInt(id("velocity").value);

player = new createjs.BitmapAnimation(playerSprite);
player.x = id("gameCanvas").width / 2;
player.y = id("gameCanvas").height - playerWH;
player.vX = velocity;
_action = "walk";
player.gotoAndPlay("walk");

stage.addChild(player);

createjs.Ticker.setFPS(fps);
createjs.Ticker.useRAF = true;
createjs.Ticker.addEventListener("tick", tick);
}

在使用 addFlippedFrame 函数后,每个动画(动作 - 跳跃、行走等)都将复制一份以支持其他视图,翻转视图的名称末尾将带有 _h。因此,如果您想使用翻转图像向左行走,则需要调用 gotoAndPlay 函数并传入 "walk_h" 动作名称。

function tick() {
    if (_action.indexOf("walk") != -1) {
        if (player.x >= id("gameCanvas").width - playerWH) {
            // You reached the end -  We need to walk left
            player.direction = -90;
            player.gotoAndPlay("walk")
        }
        if (player.x < playerWH) {
            // You reached the end -  We need to walk right
            player.direction = 90;
            player.gotoAndPlay("walk_h");
        }
        // Moving the sprite based on the direction & the speed
        player.direction == 90 ? player.x += player.vX : player.x -= player.vX;
    }
    stage.update();
}  

查看演示

第四部分 - 创建游戏环境

在第三部分中,我们学习了如何在游戏空间中移动玩家,以及翻转玩家动画以支持水平和垂直视图。

在本帖中,我们将讨论游戏“氛围”,通过向游戏中添加背景和其他元素,但不仅仅是静态图像,而是动画图像,这将创造一个生动的游戏氛围。

在我们开始设计游戏之前,让我们先花点时间谈谈 PreloadJS 库,它也属于 CreateJS(EaseJS 也属于该库)。

PreloadJS 为 HTML 应用程序的内容预加载提供了一种一致的方式。预加载可以使用 HTML 标签和 XHR 进行。

所以,让我们向我们的 HTML 页面添加一个额外的 JavaScript 文件:

<script type="text/javascript" src="js/preloadjs-0.3.0.min.js"></script>  

现在,让我们添加一个名为 start 的额外函数 - 这个函数将负责在游戏开始之前加载所有游戏资源:

 function start() {           
    manifest = [
        { src: "assets/sky.png", id: "sky" },
        { src: "assets/ground.png", id: "ground" },
        { src: "assets/logo.png", id: "sun" },
        { src: "assets/hill.png", id: "hill" }
    ];
    loader = new createjs.LoadQueue(false);
    loader.onFileLoad = handleFileLoad;
    loader.onComplete = handleComplete;
    loader.loadManifest(manifest);
}  

正如您所见,我们定义了一个 manifest 对象(可以是任何名称),在该对象内,我们定义了在开始游戏之前所需的所有资源。

下载完成后,将调用 onComplete 事件,对于每个资源,将调用 onFileLoad 事件。

下面的代码会将每个资源添加到 assets 数组中:

var assets = [];
function handleFileLoad(event) {
    assets.push(event.item);
}  

在我们继续将图形实现到游戏中之前,让我们先看看它。

如图所示,这是地面的一小部分 - 我们将看到如何轻松地复制它来创建一个连续的地面贯穿整个游戏。

Name 
地面
山丘
太阳
天空

所有资源都已添加到 assets 数组后,我们就可以开始将它们放置在游戏中了。

我们将添加一个名为 handleComplete 的新函数,它将添加 assets 数组中的每个资源。现在重要的是要理解 PreloadJS 不仅在后台加载了这些资源,还根据资源的类型对它们进行了分类,并为它们分配了一个唯一的 ID。为了检索从 PreloadJS 库加载的对象,我们将使用 getResult 函数,并传入项的唯一 ID。

让我们开始放置地面图像,地面应该在整个游戏宽度上重复,这样它看起来就像一个连续的地板。第一步是创建一个新的 Shape 对象,其中包含地面图像。从下面的代码可以看出,Shape 对象的 w 属性设置为游戏宽度,这不会拉伸图像,而是在整个宽度上重复它。

最后,我们将地面 Shape 添加到 stage 中。

查看演示 4.1 

function handleComplete() {
    buildPlayerSprite();
    for (var i = 0; i < assets.length; i++) {
        var item = assets[i];
        var _id = item.id;
        var result = loader.getResult(_id);
        if (item.type == createjs.LoadQueue.IMAGE) {
            var bmp = new createjs.Bitmap(result);
        }
        switch (_id) {          
            case "ground":
                ground = new createjs.Shape();
                var g = ground.graphics;
                g.beginBitmapFill(result);
                g.drawRect(0, 0, w, 79);
                ground.y = h - 79;
                break;          
        }
    }
    stage.addChild(ground, player);
    player.gotoAndPlay("walk_h");
    
    var fieldValue = id("fps");
    var fps = parseInt(fieldValue.value);
    createjs.Ticker.setFPS(fps);
    createjs.Ticker.useRAF = true;
    createjs.Ticker.addEventListener("tick", tick);
}  

查看演示 4.1 

从演示中可以看到玩家正在连续的地面上奔跑,但我们希望创建移动效果,使其看起来像是地面随着玩家一起移动。

要创建这种效果,我们需要更新 tick 函数并更改地面的 x 属性。

function tick() {
          var outside = w + 20;
        var position = player.x + player.vX;
        player.x = (position >= outside) ? -200 : position;
        ground.x = (ground.x - 10);
    }
    stage.update();
} 

查看演示 4.2

现在我们可以看到地面随着玩家移动,但是地面在右侧被切断了,因为它不够长,所以我们需要增加地面的 Shape 宽度。

case "ground":
   ground = new createjs.Shape();
   var g = ground.graphics;
   g.beginBitmapFill(result);
   g.drawRect(0, 0, w+330, 79);
   ground.y = h - 79;
   break; 

上面的代码显示地面 Shape 的宽度现在等于 w+330,这只是解决方案的一部分,因为地面 Shape 将从屏幕上消失。为了完全解决这个问题,我们需要通过添加我们添加的附加宽度的模数来更新 tick 函数。

这将确保地面完整地保留在游戏边界内。

ground.x = (ground.x - 10) % 330; 

查看演示 4.3 

现在我们了解了如何将对象添加到游戏舞台并创建效果和形状操作的基本示例,我们可以添加其余的游戏对象了。

* 注意 - 我添加了以下事件 - stagemousedown - 用于知道用户何时点击了游戏舞台。

function handleComplete() {
    buildPlayerSprite();
    for (var i = 0; i < assets.length; i++) {
        var item = assets[i];
        var _id = item.id;
        var result = loader.getResult(_id);
        if (item.type == createjs.LoadQueue.IMAGE) {
            var bmp = new createjs.Bitmap(result);
        }
        switch (_id) {
            case "sky":
                var g = new createjs.Graphics()
                g.beginBitmapFill(result);
                g.drawRect(0, 0, w * 2, h)
                sky = new createjs.Shape(g);
                break;
            case "ground":
                ground = new createjs.Shape();
                var g = ground.graphics;
                g.beginBitmapFill(result);
                g.drawRect(0, 0, w + 330, 79);
                ground.y = h - 79;
                break;
            case "hill":
                hill = new createjs.Shape(new createjs.Graphics().beginBitmapFill(result).drawRect(0, 0, w, 159));
                hill.x = 0;
                hill.scaleX = 3;
                hill.y = 163;
                break;
            case "sun":
                var g = new createjs.Graphics();
                g.beginBitmapFill(result);
                g.drawRect(0, 0, 129, 129);
                sun = new createjs.Shape(g);
                sun.x = w;
                sun.y = 37;
                break;
        }
    }
    stage.addChild(sky, ground, hill, sun, player);
    player.gotoAndPlay("walk_h");
    stage.addEventListener("stagemousedown", function () {
        play("jump_h");
    });
    var fieldValue = id("fps");
    var fps = parseInt(fieldValue.value);
    createjs.Ticker.setFPS(fps);
    createjs.Ticker.useRAF = true;
    createjs.Ticker.addEventListener("tick", tick);
} 

最后,就像我们为地面 Shape 所做的那样,我们需要更改 tick 函数来操作这些对象。
 
这次我们添加了另一个参数来计算何时一个对象放置在游戏边界之外,一旦一个对象离开了游戏边界,我们就可以改变它的 x 属性并将其返回到游戏中。

 function tick() {
    if (_action.indexOf("walk") != -1 || _action.indexOf("jump") != -1) {
        var outside = w + 20;
        var position = player.x + player.vX;
        player.x = (position >= outside) ? -200 : position;
        sky.x = (sky.x - 5) % w;
        hill.x = (hill.x - 2) % w;
        ground.x = (ground.x - 10) % 330;
        sun.x = (sun.x - 1);
        if (sun.x <= -135) { sun.x = outside + 50; }
    }
    stage.update();
} 

查看演示 

第五部分 - 碰撞与跳跃

现在进入本教程的最后一部分,对象碰撞

对于这个游戏,我们想创建一个基本的逻辑来处理对象碰撞 -> 如果玩家碰到岩石对象,游戏应该停止,玩家应该死亡(不是真的... 大笑)。

 

我们将岩石对象添加到我们的 Stage 中(步骤 4),方法是在 handleComplete 函数下添加以下代码:

 

case "rock":
    var g = new createjs.Graphics()
    g.beginBitmapFill(result);
    g.drawRect(0, 0, 45, 44)
    rock = new createjs.Shape(g);
    rock.y = h - 119;
    rock.x = w;
    rock.height = 44;
    rock.width = 45;
    break;

现在,要在游戏中显示岩石,请添加以下代码。

 stage.addChild(sky, ground, hill, sun, player, rock); 

现在我们游戏中有了岩石对象,我们应该允许玩家跳跃以避免碰到岩石。要做到这一点,我们需要注册 stagemousedown 事件来捕获鼠标点击事件,一旦事件被调用,我们将播放跳跃动画并将 isJumping 对象设置为 true。

我们将以下代码放在 handleComplete 函数中,就在 setFPS 函数之前。

 stage.addEventListener("stagemousedown", function () {
    if (isJumping) return;
    play("jump_h");
    gameOver = false;
    isJumping = true;
});  

下一步是改变玩家跳跃时的 Y 值,以便跳到岩石上方。以下代码不是完成此任务的最佳方法,但对于本教程来说它简单快捷。

代码检查我们的玩家是否不在空中(以避免双重跳跃),并启动一个计时器,该计时器将在 1 秒后结束。一旦计时器完成,它将把玩家带回地面,但在此之前,它会将 Y 值增加 4 像素。当玩家在空中且达到最大高度时,计时器将 Y 值减少 4 像素。

function handleJump() {
    if (isJumping) {
        if (onTheAir == null) {
            onTheAir = setTimeout(function () {
                isJumping = false;
                player.y = playerBaseY;
                onTheAir = null;
                goingDown = false;
                top = false;
            }, 1000);
        }
        if (goingDown && player.y <= playerBaseY) {
            player.y += 4;
        }
        else {
            player.y -= 4;
            if (player.y <= maxJumpHeight)
                goingDown = true;
        }
    }
}  

要调用此代码,请在 tick 函数下添加以下代码:

function tick() {
    handleJump();
    ...
}  

现在我们可以通过在游戏上单击鼠标左键来执行跳跃。

最后一步是应用对象碰撞,所以如果玩家碰到岩石,我们需要停止游戏并播放 fall 动画。我建议您阅读 Object/Object Intersection 以查找和理解您可以进行对象碰撞的不同方法。

 function checkRectIntersection(r1, r2) {
    var deltax = r1.x - r2.x;
    var deltay = r1.y - r2.y;
    var dist = 25;
    if (Math.abs(deltax) < dist && Math.abs(deltay) < dist) {
        if (Math.sqrt(deltax * deltax + deltay * deltay) < dist) {
            return true;
        }
    }
    return false;
} 

添加 HandleCollision 函数,该函数将由 tick 函数每次调用以检查碰撞:

function HandleCollisions() {
    var a = getCollideableItemBounds(player);
    var b = getCollideableItemBounds(rock);

    var oppss = checkRectIntersection(a, b);

    if (oppss && !gameOver) {
        console.log(oppss);
        gameOver = true;
        play("fall_h");
    }
} 

查看演示 - 最终 

尽情享用!

© . All rights reserved.