所以我告诉你,你现在有所有你需要开始制作游戏的技能。什么?你不相信我?让我向你证明吧!让我们接近如何使一个简单的对象集合和敌人回避游戏称为宝藏猎人。(你会找到它的examples 文件夹。)
宝藏猎人是一个最简单的完整的游戏,你可以使用迄今为止所学到的工具之一的一个很好的例子。使用键盘箭头键帮助资源管理器找到宝藏,并把它带到出口。六个blob怪物在地牢墙之间上下移动,如果他们击中探险家,他变得半透明,右上角的健康表收缩。如果所有的健康用完,“你失去!”显示在舞台上; 如果资源管理器到达带有宝藏的出口,则显示“You Won!”。虽然它是一个基本的原型,宝藏猎人包含大多数元素,你会发现在更大的游戏:纹理地图集图形,交互性,碰撞和多个游戏场景。让我们来看看游戏是如何组合在一起的,这样你可以使用它作为你自己的游戏的起点。
代码结构
打开treasureHunter.html文件,你会看到所有的游戏代码都在一个大文件。这里是一个鸟瞰图如何组织所有的代码。
//Setup Pixi and load the texture atlas files - call the `setup` //function when they've loaded function setup() { //Initialize the game sprites, set the game `state` to `play` //and start the 'gameLoop' } function gameLoop() { //Runs the current game `state` in a loop and renders the sprites } function play() { //All the game logic goes here } function end() { //All the code that should run at the end of the game } //The game's helper functions: //`keyboard`, `hitTestRectangle`, `contain` and `randomInt`
使用它作为你的世界地图的游戏,我们看看每个部分如何工作。
在设置功能中初始化游戏
一旦纹理地图集图像已加载,setup函数就会运行。它只运行一次,并允许您为您的游戏执行一次性设置任务。它是创建和初始化对象,精灵,游戏场景,填充数据数组或解析加载的JSON游戏数据的好地方。
这里是setup对Treasure Hunter中函数的简略视图,以及它执行的任务。
function setup() { //Create the `gameScene` group //Create the `door` sprite //Create the `player` sprite //Create the `treasure` sprite //Make the enemies //Create the health bar //Add some text for the game over message //Create a `gameOverScene` group //Assign the player's keyboard controllers //set the game state to `play` state = play; //Start the game loop gameLoop(); }
最后两行的码,state = play;并且gameLoop()也许是最重要的。运行gameLoop游戏引擎上的开关,并使得该play函数在连续循环中被调用。但在我们看看它是如何工作之前,让我们看看setup函数中的具体代码是什么。
创建游戏场景
该setup函数创建两个Container称为gameScene和的组 gameOverScene。每个都添加到舞台上。
gameScene = new Container(); stage.addChild(gameScene); gameOverScene = new Container(); stage.addChild(gameOverScene);
作为主游戏的一部分的所有精灵都被添加到 gameScene组中。应该在游戏结束时显示的文本上的游戏将添加到gameOverScene组中。
虽然它是在setup函数中创建的,但是gameOverScene 当游戏第一次启动时它不应该是可见的,所以它的visible 属性被初始化为false。
gameOverScene.visible = false;
你会看到前方的是,当比赛结束时,gameOverScene的visible 属性将被设置为true以显示出现在比赛结束的文本。
做地牢,门,探险家和宝藏
玩家,出口门,宝箱和地牢背景图像都是由纹理地图框架制成的精灵。非常重要的是,他们都被添加为孩子gameScene。
//Create an alias for the texture atlas frame ids id = resources["images/treasureHunter.json"].textures; //Dungeon dungeon = new Sprite(id["dungeon.png"]); gameScene.addChild(dungeon); //Door door = new Sprite(id["door.png"]); door.position.set(32, 0); gameScene.addChild(door); //Explorer explorer = new Sprite(id["explorer.png"]); explorer.x = 68; explorer.y = gameScene.height / 2 - explorer.height / 2; explorer.vx = 0; explorer.vy = 0; gameScene.addChild(explorer); //Treasure treasure = new Sprite(id["treasure.png"]); treasure.x = gameScene.width - treasure.width - 48; treasure.y = gameScene.height / 2 - treasure.height / 2; gameScene.addChild(treasure);
将它们保存在gameScene组中将使我们能够在游戏完成时隐藏gameScene和显示gameOverScene游戏。
制作blob的怪物
六个blob怪物在一个循环中创建。给每个斑点一个随机的初始位置和速度。垂直速度交替地乘以1或-1针对每个斑点,并且这是什么导致每个斑点沿与它旁边的相反的方向移动。每个创建的Blob怪物都被推入一个名为的数组 blobs。
var numberOfBlobs = 6, spacing = 48, xOffset = 150, speed = 2, direction = 1; //An array to store all the blob monsters blobs = []; //Make as many blobs as there are `numberOfBlobs` for (var i = 0; i < numberOfBlobs; i++) { //Make a blob var blob = new Sprite(id["blob.png"]); //Space each blob horizontally according to the `spacing` value. //`xOffset` determines the point from the left of the screen //at which the first blob should be added var x = spacing * i + xOffset; //Give the blob a random `y` position var y = randomInt(0, stage.height - blob.height); //Set the blob's position blob.x = x; blob.y = y; //Set the blob's vertical velocity. `direction` will be either `1` or //`-1`. `1` means the enemy will move down and `-1` means the blob will //move up. Multiplying `direction` by `speed` determines the blob's //vertical direction blob.vy = speed * direction; //Reverse the direction for the next blob direction *= -1; //Push the blob into the `blobs` array blobs.push(blob); //Add the blob to the `gameScene` gameScene.addChild(blob); }
做健康吧
当你玩Treasure Hunter时,你会注意到当探险家触及其中一个敌人时,屏幕右上角健康条的宽度减小。这个健康吧怎么样?它只是两个重叠的矩形在完全相同的位置:后面的黑色矩形,和前面的红色矩形。它们被分组成一个healthBar 组。然后healthBar将其添加到gameScene并定位在舞台上。
//Create the health bar healthBar = new PIXI.DisplayObjectContainer(); healthBar.position.set(stage.width - 170, 6) gameScene.addChild(healthBar); //Create the black background rectangle var innerBar = new PIXI.Graphics(); innerBar.beginFill(0x000000); innerBar.drawRect(0, 0, 128, 8); innerBar.endFill(); healthBar.addChild(innerBar); //Create the front red rectangle var outerBar = new PIXI.Graphics(); outerBar.beginFill(0xFF3300); outerBar.drawRect(0, 0, 128, 8); outerBar.endFill(); healthBar.addChild(outerBar); healthBar.outer = outerBar;
您可以看到一个名为的属性outer已添加到 healthBar。它只引用outerBar(红色矩形),以便以后访问方便。
healthBar.outer = outerBar;
你不必这样做; 但是,嘿为什么不!这意味着如果你想控制红色的宽度outerBar,你可以写一些光滑的代码,看起来像这样:
healthBar.outer.width = 30;
这是非常整洁和可读性,所以我们会保持它!
制作消息文本
当游戏结束时,根据游戏的结果,一些文本显示“你赢了!”或“你失去了!”。这是使用文本精灵并将其添加到gameOverScene。由于 gameOverScene的visible属性设置为false游戏开始时,你无法看到这个文本。这里是来自setup 函数的代码,它创建消息文本并将其添加到 gameOverScene。
message = new Text( "The End!", {font: "64px Futura", fill: "white"} ); message.x = 120; message.y = stage.height / 2 - 32; gameOverScene.addChild(message);
玩游戏
所有的游戏逻辑和使sprite移动的代码发生在play函数内部,它在连续循环中运行。这里有一个play功能的概述
function play() { //Move the explorer and contain it inside the dungeon //Move the blob monsters //Check for a collision between the blobs and the explorer //Check for a collision between the explorer and the treasure //Check for a collsion between the treasure and the door //Decide whether the game has been won or lost //Change the game `state` to `end` when the game is finsihed }
让我们来了解所有这些功能如何工作。
移动资源管理器
使用键盘控制浏览器,并且其代码与您之前学习的键盘控制代码非常相似。该keyboard对象修改资源管理器的速度,并且速度加在里面探险的位置play 的功能。
explorer.x += explorer.vx; explorer.y += explorer.vy;
包含运动
但是新的东西是,探险家的运动包含在地牢的墙壁内。绿色轮廓显示了探险者的运动的极限。
这是通过调用自定义函数的帮助完成的 contain
contain(explorer, {x: 28, y: 10, width: 488, height: 480});
contain有两个参数。第一个是你想保留的精灵。第二个是与任何对象x,y,width和 height属性定义的矩形区域。在此示例中,包含对象定义了刚好稍微偏离舞台并且小于舞台的区域。它匹配地牢墙的尺寸。
这里的contain功能,所有这些工作。该函数检查sprite是否已经越过包含对象的边界。如果它有,代码将sprite移回到边界。该contain函数还返回collision值为“top”,“right”,“bottom”或“left”的变量,这取决于sprite命中的边界的哪一侧。(collision会undefined如果精灵没有击中任何的界限。)
function contain(sprite, container) { var collision = undefined; //Left if (sprite.x < container.x) { sprite.x = container.x; collision = "left"; } //Top if (sprite.y < container.y) { sprite.y = container.y; collision = "top"; } //Right if (sprite.x + sprite.width > container.width) { sprite.x = container.width - sprite.width; collision = "right"; } //Bottom if (sprite.y + sprite.height > container.height) { sprite.y = container.height - sprite.height; collision = "bottom"; } //Return the `collision` value return collision; }
你会看到collision返回值将如何使用在代码前面,使blob的怪物反弹在顶部和底部的地下城墙之间。
移动怪物
该play功能还移动的怪物,保持它们包含在地牢的墙壁内,并检查每一个与玩家的碰撞。如果一个斑点撞到地牢的顶壁或底壁,它的方向是相反的。所有这一切都是在一个forEach循环的帮助下完成的,该循环遍历每个帧上blob的blobs数组中的每个sprite 。
blobs.forEach(function(blob) { //Move the blob blob.y += blob.vy; //Check the blob's screen boundaries var blobHitsWall = contain(blob, {x: 28, y: 10, width: 488, height: 480}); //If the blob hits the top or bottom of the stage, reverse //its direction if (blobHitsWall === "top" || blobHitsWall === "bottom") { blob.vy *= -1; } //Test for a collision. If any of the enemies are touching //the explorer, set `explorerHit` to `true` if(hitTestRectangle(explorer, blob)) { explorerHit = true; } });
你可以在上面的代码中看到contain 函数的返回值是用来使blob从墙上弹起。一个变量called blobHitsWall用于捕获返回值:
var blobHitsWall = contain(blob, {x: 28, y: 10, width: 488, height: 480});
blobHitsWall通常会undefined。但是如果blob击中顶壁,blobHitsWall将具有值“top”。如果blob命中底壁,blobHitsWall将具有值“bottom”。如果这些情况之一true,你可以通过反转它的速度颠倒blob的方向。这里是这样做的代码:
if (blobHitsWall === "top" || blobHitsWall === "bottom") { blob.vy *= -1; }
乘以blob的vy(垂直速度)值-1将翻转它的运动方向。
检查冲突
上面的循环中的代码hitTestRectangle用来确定是否有任何敌人触摸了资源管理器。
if(hitTestRectangle(explorer, blob)) { explorerHit = true; }
如果hitTestRectangle返回true,这意味着有一直碰撞和称为变量explorerHit被设定为true。如果explorerHit 是true,该play函数使浏览器半透明,并减少条的宽度health1个像素。
if(explorerHit) { //Make the explorer semi-transparent explorer.alpha = 0.5; //Reduce the width of the health bar's inner rectangle by 1 pixel healthBar.outer.width -= 1; } else { //Make the explorer fully opaque (non-transparent) if it hasn't been hit explorer.alpha = 1; }
如果 explorerHit是false,则explorer的alpha属性保持为1,这使得它完全不透明。
该play功能还检查宝库和资源管理器之间的碰撞。如果有一个命中,treasure被设置为资源管理器的位置,有轻微的偏移。这使它看起来像探险家携带的宝藏。
这里是这样做的代码:
if (hitTestRectangle(explorer, treasure)) { treasure.x = explorer.x + 8; treasure.y = explorer.y + 8; }
到达出口门并结束游戏
游戏有两种方式可以结束:如果你把宝藏带到出口,你可以赢;如果你失去健康,你可以失去。
要赢得比赛,宝箱只需要触摸出口门。如果发生这种情况,游戏state设置为end,message文本显示“你赢了”。
if (hitTestRectangle(treasure, door)) { state = end; message.text = "You won!"; }
如果你的健康,你失去了游戏。游戏state也设置为end,message文本显示“你失去了!
if (healthBar.outer.width < 0) { state = end; message.text = "You lost!"; }
但这是什么意思?
state = end;
你会记住从前面的例子,gameLoop不断更新一个函数调用 state每秒60次。这是gameLoop做到这一点:
function gameLoop(){ //Loop this function 60 times per second requestAnimationFrame(gameLoop); //Update the current game state state(); //Render the stage renderer.render(stage); }
您还记得我们最初设定的值 state来play,这就是为什么play函数在一个循环中运行。通过设置state到end我们告诉我们想要另一个函数的代码,被称为end在循环中运行。在一个更大的游戏中,你可以有一个tileScene状态,并为每个游戏关卡,如 leveOne,levelTwo和levelThree。
那么这个end功能是什么呢?这里是!
function end() { gameScene.visible = false; gameOverScene.visible = true; }
它只是翻转游戏场景的可见性。这是隐藏gameScene和显示gameOverScene游戏结束时。
这是一个非常简单的例子,如何切换游戏的状态,但你可以拥有尽可能多的游戏状态,你喜欢在你的游戏,并填充他们需要尽可能多的代码。只需将值更改为state要在循环中运行的任何函数。
这真的是所有的宝藏猎人!有了更多的工作,你可以把这个简单的原型成一个完整的游戏 - 试试吧!