格雷格机器人-使用TypeScript进行游戏开发(译文)
By robot-v1.0
本文链接 https://www.kyfws.com/games/greg-the-robot-game-dev-with-typescript-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 31 分钟阅读 - 15217 个词 阅读量 0格雷格机器人-使用TypeScript进行游戏开发(译文)
原文地址:https://www.codeproject.com/Articles/1214880/Greg-The-Robot-Game-Dev-with-TypeScript
原文作者:Marcelo Ricardo de Oliveira
译文由本站 robot-v1.0 翻译
前言
A shoot’n’up HTML5 game made with Phaser game framework
使用Phaser游戏框架制作的HTML5游戏
[
](https://marcelooliveira.github.io/GregTheRobot/GregTheRobot/)
介绍(Introduction)
在本文中,我将向您展示一个使用TypeScript的HTML5"向上射击"风格的游戏以及一个名为的精美游戏开发库(In this article, I’ll present you an HTML5 “shoot’em’up” style game using TypeScript and a nice game development library called) Phaser
.(.)
这个项目是对80年代优秀的街机游戏的致敬,更具体地说是Konami在1986年制作的令人敬畏的游戏" Knightmare".(This project is a tribute to good ol’ arcade games from the 80s, more specifically the awesome game “Knightmare” made by Konami in 1986.)
希望本文对寻求基础游戏结构的人有吸引力.我相信TypeScript部分是该项目的优势之一,因为它提高了开发过程中的生产率,这不仅限于游戏项目,而且通常适用于JavaScript开发.(Hopefully, this article will appeal to those looking for a basic game structure to build upon. I believe the TypeScript part is one of the strong points of this project, because it increases productivity in development process, which is not restricted to game projects but applies for JavaScript development in general.)
背景(Background)
2017年8月,举行了一场针对游戏的Hackathon竞赛.我的一些同事(In August 2017, there was a Hackathon competition focused on games. Some of my colleagues at) Caelum Ensino eInovação(Caelum Ensino e Inovação) 决定参加比赛,但成员人数不断增加,因此我们不得不将他们分为具有不同项目的不同团队.最终,我们提出了使用Unity,JavaScript和Phaser/TypeScript的不同游戏.(decided to join the competition, but the number of members kept growing, so we had to split them into different teams with different projects. Eventually, we came up with different games using Unity, JavaScript and Phaser/TypeScript.) 我担任其中一个团队的开发人员一职,在讨论了可能的项目一段时间后,我确实提出了一个建议,团队的其他成员也同意了.当时,我的灵感来自(I got the position as the developer of one of the teams, and after a while talking about possible projects, I did put forward one suggestion, and the rest of the team agreed. At the time, I was inspired by) 骑士(Knightmare) ,这是Konami于1986年为MSX计算机平台制作的一款出色游戏,显然这是互联网,HTML或JavaScript时代之前的时代. Konami用出色的声音和图形制作了一款出色的游戏,并将其塞入仅32KB的RAM内存盒中!(, an excellent game made by Konami in 1986 for the MSX computer platform, which obviously was a time way before the age of internet, HTML or JavaScript. Konami managed to take an amazing game with great sound and graphics and cram it into a cartridge of a mere 32KB of RAM memory!) 图1-1986年的Konami’s Knightmare(Figure 1 - Konami’s Knightmare from 1986)但是显然,技术必须有所不同.我们无意剥夺Konami的角色,图形和音乐,因此我们不得不提出自己的故事情节和艺术.我们确实进行了一次集思广益的会议,并讨论了大约一个小时的想法,最后,我们同意将主角(英雄)做成一个贫穷但讨人喜欢的机器人,在一个邪恶的机器人统治的机器人荒原上冒险冒险.(But obviously, the technology had to be different. We didn’t intend to rip off Konami’s characters, graphics and music, so we had to come up with our own storyline and art. We did set up a brainstorming session and discussed the ideas for about one hour and finally, we agreed to make the main character (the hero) as a poor but likeable robot engaging in an adventure across a robotic wasteland dominated by an evil robot.) 这些想法源于我们的脑海,我们开始开发,但不幸的是,在集思广益之后的某个时候,我们发现,如果我们实现了想象中的一切,我们将无法满足黑客马拉松的最后期限.因此,我们不得不删除游戏中许多不太重要的功能.(The ideas were flowing from our minds, and we started the development, but unfortunately at some point after the brainstorm, we figured out that we wouldn’t meet the hackathon deadline if we implemented everything we imagined. So we had to cut off many of the not-so-vital features of the game.) 该游戏完全使用Phaser游戏库在HTML5/JavaScript上制作.尽管形式上有很大不同,但它与上述原始的Konami游戏有很多共同之处,例如:(The game was entirely made on HTML5/JavaScript using the Phaser game library. Although very different in form, it shares with the original Konami’s game mentioned above many aspects, such as:)
- 一个孤独的英雄(A lone hero)
- “向上射击"平台(The “shoot’em’up” platform)
- 垂直自动滚动背景(The vertical auto-scrolling background)
- “倾盆大雨"的敌人(The “raining down” enemies)
- 级别老板(The level bosses)
致谢(Acknowledgements)
普里西拉`桑尼索(Priscila Sonnesso)(Priscila Sonnesso)
她借助一个简单的循环合成器为游戏编写了声音和背景音乐.声音指示事件,例如玩家何时获得能量或何时死亡.背景音乐是一个类似于控制论环境的简单循环.灵感来自德国电子音乐乐队Kraftwerk,该乐队以其关于机器人和未来世界的主题而闻名于八十年代.(She composed the sounds and background music for the game with the help of a simple loop synthesizer. The sounds indicate events such as when the player is obtaining energy or when he is dying. The background music is a simple loop that resembles a cybernetic environment. The inspiration was the German electronic music band Kraftwerk, famous in the eighties for its themes about robots and futuristic world.)
考埃费利佩(Kauê Felipe)
他研究背景图形以与关卡地图相匹配.这些地图由纯文本ASCII字符文件定义,其中的点表示自由正方形空间,” X"表示不可穿透的墙.他使用Paint.NET组成图形,以便图像与地图边界和潜在障碍物完全匹配.(He worked on the background graphics to match with the level maps. These maps are defined by plain text ASCII character files, where a dot represents a free square space, and an “X” means an impassable wall. He used Paint.NET to compose the graphics so that the image would match exactly the boundaries of the map and the underlying obstacles.)
佩德罗`伊曼纽尔(Pedro Emanuel)
他用学校的笔记本画了游戏中的人物:机器人格雷格(Greg)和老板.佩德罗(Pedro)是卡通和动漫的忠实拥护者,他的贡献使我们的游戏获得了很好的卡通般的感觉.(He used a school notebook to draw the characters of the game: Greg the Robot and the Bosses. Pedro is a big fan of cartoon and anime and his contribution gave our game a nice cartoonish feel.)
马可`布鲁诺(Marco Bruno)
我们的团队经理.组织团队,提供想法并指导我们的发展.(Our team manager. Organized the team, provided ideas and directed our development.)
软件需求(Software Requirements)
Visual Studio 2017(Visual Studio 2017)
在下载游戏源代码之前,请先安装(Before downloading the game source code, please install) Visual Studio社区2017(Visual Studio Community 2017) 或更高,这是该项目所需的集成开发环境.(or superior, which is the integrated development environment required for this project.)
打字稿(TypeScript)
您可以从下面的链接下载Visual Studio的TypeScript开发套件.您将能够为任何Web浏览器,任何机器或操作系统开发大规模JavaScript应用程序. TypeScript生成符合JavaScript标准的清晰易读的代码.(You can download the TypeScript Development Kit for Visual Studio from the link below. You will be able to develop large-scale JavaScript applications for any web browser, any machine or operating system. TypeScript generates a clean and readable code that adheres to JavaScript standards.)
移相器(Phaser)
本文随附的源代码已经包含了运行该应用程序所需的所有Phaser库文件.但是您可能想从头开始使用Typescript创建一个全新的Phaser项目,在这种情况下,您可以按照以下说明进行操作(The source code that comes with this article already contains all Phaser library files required for the application to run. But you might want to create a brand new Phaser project with Typescript from scratch, and in this case you can follow this) 将Phaser与TypeScript一起使用(Using Phaser with TypeScript) 教程.(tutorial.)
移相器(Phaser)
图2-Phaser的"火星人"徽标(Figure 2 - Phaser’s “Martian” logo)Phaser是基于JavaScript/HTML5的2D游戏开发库.它包含许多内置功能,可用于多种游戏风格和场景.例如,它具有精灵的概念.游戏中的子画面是包含游戏角色(例如英雄和敌人)定义的二维位图.不仅如此,子画面文件还几乎总是包含构成角色动画的不同角色框架.公平地说,许多游戏库也处理精灵.但是Phaser带有内置的碰撞检测和处理机制,该机制非常易于设置和实施.(Phaser is a 2D game development library based on JavaScript/HTML5. It contains many built-in features useful for a great number of game styles and scenarios. For example, it has the concept of sprites. Sprites in a game are the 2 dimensional bitmaps that contain the definition for the game characters, such as the hero and the enemies. Not only that, sprite files also almost always contain the different character frames that comprises the character’s animation. To be fair, many game libraries also deal with sprites. But Phaser comes with a built-in collision detection and handling mechanism that is very easy to set up and implement.)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Greg The Robot</title>
<link rel="shortcut icon" href="greg.ico" />
<link rel="stylesheet" href="app.css" type="text/css" />
<script src="phaser/phaser.js"></script>
<script src="app.js"></script>
</head>
<body>
<h1></h1>
<div id="content"></div>
</body>
</html>
清单1-典型的Index.html文件,显示phaser.js,app.js和app.css依赖项(Listing 1 - The typical Index.html file showing phaser.js, app.js and app.css dependencies)像许多游戏框架一样,Phaser基于更新/渲染循环.这些循环将游戏执行的两个基本方面分开:每个游戏更新循环使游戏有机会更新游戏状态和每个角色的背景状态,例如位置,得分等.它用于处理游戏的逻辑数据和行为.另一方面,渲染循环是我们告诉要显示图形的地方:例如,假设您有两个不同的精灵,一个用于英雄,另一个用于其阴影.游戏如何确定应在背景和前景上显示哪一个?它是渲染循环,描述了精灵在屏幕上出现的顺序.(Like many game frameworks, Phaser is based on update / render loops. These loops separate two fundamental aspects of the game execution: each game update loop gives the game the opportunity to update the game state and each character’s background states, such as position, score, and so on. It is used to handle the game’s logical data and behavior. On the other hand, the render loop is where we tell the graphics to be displayed: for example, suppose you have two different sprites, one for the hero and another for its shadow. How the game decides which one should be displayed on the background and foreground? It’s the render loop that describes the order in which the sprites will appear on screen.)
class GregTheRobot {
game: Phaser.Game;
statusBar: Phaser.BitmapText;
constructor() {
this.game = new Phaser.Game(512, 512, Phaser.AUTO, 'content', {
create: this.create, preload: this.preload
});
}
.
.
.
//a lot more code here...
.
.
.
}
window.onload = () => {
var game = new GregTheRobot();
};
清单2-window.onload事件调用的GregTheRobot类.(Listing 2 - the GregTheRobot class being called by window.onload event.)幸运的是,除非您真的需要更改事物在屏幕上自然显示的方式,否则Phaser不需要您实现渲染循环.它假定应该渲染每个精灵,除非您以编程方式销毁它或将其可见性更改为隐藏.(Fortunately, unless you really need to change how things naturally appear on screen, Phaser does not require you to implement the render loop. It assumes that every sprite should be rendered, unless you programmatically destroy it or change its visibility to hidden.) 如果您从小处着手,并继续向游戏中逐渐添加Phaser功能,则感觉就像是学习曲线很短.如果您的游戏代码不受控制地增长,并且不时清理和重构JavaScript代码,事情就会变得更加复杂.幸运的是,Phaser软件包还附带TypeScript支持,这有助于避免许多编程错误.(If you start small and keep slowly adding Phaser features to your game, it feels like a short learning curve. Things can become more complicated if your game code grows without control and if you don’t clean up and refactor your JavaScript code every now and then. Fortunately, the Phaser packages also comes with TypeScript support, which helps in avoiding many programming mistakes.)
图3-具有典型Phaser文件的VS 2017 Project Explorer(Figure 3 - VS 2017 Project Explorer featuring typical Phaser files)至于Phaser背后的技术:它在内部使用Canvas和WebGL渲染器,并可以基于浏览器支持自动在它们之间交换.这样可以在台式机和移动设备上快速进行照明渲染.移相器的使用为卓越做出了贡献(As for the technology behind Phaser: it uses both a Canvas and WebGL renderer internally and can automatically swap between them based on browser support. This allows for lighting fast rendering across desktop and mobile. Phaser uses and contributes towards the excellent)**Pixi.js(Pixi.js)**用于渲染的库.(library for rendering.)
预载器(Preloader)
游戏通常需要自动解析和处理许多资源,例如图像,声音,spritesheets,tilemap,JSON,XML.它们已准备好在游戏中使用并存储在全局缓存中. Phaser可以用一行代码加载每个资产.(A game usually requires many resources, such as images, sounds, spritesheets, tilemaps, JSON, XML, which are parsed and handled automatically. They are ready for use in game and stored in global cache. Phaser can load each asset with a single line of code.)
您可以通过调用(You can load assets in a Phaser project by invoking the) game.load
函数内部的方法(method inside of a function called) preload
.程序启动时,Phaser总是寻找名为”(. When the program starts, Phaser always look for the function named “) preload
并加载其中定义的资产.(” and load the assets defined in it.)
preload() {
//menu & splash screen images
this.game.load.spritesheet('menu', 'assets/backgrounds/menu.png', 512, 384);
this.game.load.spritesheet('splash1', 'assets/backgrounds/splash1.png', 512, 384);
//game state JavaScript files
this.game.load.script('baseState', 'gamestates/baseState.js');
this.game.load.script('menu', 'gamestates/menu.js');
this.game.load.script('splash1', 'gamestates/splash.js')
this.game.load.script('gameOver', 'gamestates/gameOver.js')
this.game.load.script('level', 'gamestates/level.js');
this.game.load.script('theEnd', 'gamestates/theEnd.js')
//classes for player, enemies, boss, etc.
this.game.load.script('player', 'player/player.js');
this.game.load.script('playerBullet', 'player/playerBullet.js');
this.game.load.script('playerState', 'player/playerState.js');
this.game.load.script('boss', 'enemies/boss.js');
this.game.load.script('enemy', 'enemies/enemy.js');
this.game.load.script('battery', 'extras/battery.js');
//level intro images
this.game.load.image('level1', 'assets/backgrounds/level01.jpg');
this.game.load.image('level2', 'assets/backgrounds/level02.jpg');
this.game.load.image('level3', 'assets/backgrounds/level03.jpg');
//spritesheets for every character in the game
this.game.load.spritesheet('player', 'assets/sprites/player.png', 32, 32);
this.game.load.spritesheet('battery', 'assets/sprites/battery.png', 32, 32);
this.game.load.spritesheet('boss1', 'assets/sprites/boss1.png', 96, 96);
//...etc...
this.game.load.spritesheet('enemy1', 'assets/sprites/enemy1.png', 32, 32);
//...etc...
this.game.load.spritesheet('playerBullet',
'assets/sprites/PlayerBullet1SpriteSheet.png', 32, 32);
//sound & music resources
this.game.load.audio('start', ['assets/audio/start-level.wav']);
this.game.load.audio('intro', ['assets/audio/sound-intro.wav']);
//...etc...
//the bitmap containing the game fonts
this.game.load.bitmapFont('bitmapfont',
'assets/fonts/bitmapfont_0.png', 'assets/fonts/bitmapfont.xml');
}
清单3-GregTheRobot.preload()方法(Listing 3 - the GregTheRobot.preload() method)以上(The above) preload
函数处理以下类型的资产:(function deals with the following types of assets:)
- 精灵表(spritesheets)
- 图片(images)
- 音讯(audios)
- 位图字体(bitmap fonts)
请注意,所有这些资产都是通过传递一个(Notice that all these assets are loaded by passing a)
string
键作为参数.稍后将在需要资源时使用它来识别正确的资产.通过预加载资产,我们可以确保程序不会在游戏过程中遭受延迟,因为所有资源已在内存中可用.(key as a parameter. This will later on be used to identify the correct asset when the resource is needed. By preloading assets, we can assure the program will not suffer delays during the game play because all resources are already available in memory.)
动画(Animation)
Phaser支持具有固定框架大小的经典Spritesheet,纹理打包程序以及Flash CS6 CC JSON文件和XML文件.(Phaser supports classic spritesheets with a fixed framed size, texture packer and flash CS6 CC JSON files and XML files.) 对于这个项目,我决定使用(For this project, I decided to use the)固定尺寸(fixed framed size)精灵表.(spritesheets.)
打字稿(TypeScript)
TypeScript是由Anders Hejlsberg(由C#,Delphi和Turbo Pascal共同创建/设计)共同创建并由Microsoft开发的一种免费和开放源代码编程语言,旨在改善和保护JavaScript代码的生产.它是JavaScript的超集,这意味着每个格式正确的JavaScript也是TypeScript代码.由于网络浏览器无法识别TypeScript,因此在将其发送到网络浏览器之前,必须先将其反编译(或我们通常所说的反编译)为JavaScript.(TypeScript is a free and open source programming language co-created by Anders Hejlsberg (who co-created/designed C#, Delphi and Turbo Pascal) and developed by Microsoft, which aims to improve and secure the production of JavaScript code. It’s a superset of JavaScript, which means that every well-formed JavaScript is also a TypeScript code. Since web browsers don’t recognize TypeScript, it has to be transcompiled (or as we usually call it, transpiled) into JavaScript before they are sent to the web browser.) TypeScript支持ECMAScript 6规范并提供类型检查功能.这意味着,如果您创建一个自定义函数,分别接受一个字符串和一个数字作为参数,则不能使用两个字符串,两个数字或任何与函数签名不完全匹配的值的组合来调用该函数.这种类型检查功能可实现设计时调试,而这是普通的旧JavaScript无法实现的.这也意味着更快的开发,因为类型不匹配的错误将更快地被发现,而不是潜伏在代码发布到生产环境之前.(TypeScript supports the ECMAScript 6 specification and provides type-checking capabilities. This means that if you create a custom function accepting as parameters a string and a number, respectively, you can’t call such function with two strings, or two numbers, or any other combination of values that doesn’t match exactly the function signature. This type-checking feature enables design-time debugging, in a way that could never be accomplished with plain old JavaScript. That also means faster development, since a type mismatch bug will be caught sooner, instead of lurking in the dark until the code is released to production.) TypeScript的另一个不错的方面是您可以获取一个JavaScript项目,并开始一点一点地一次将其迁移到TypeScript,因为每个JavaScript也是TypeScript.(Another nice aspect of TypeScript is that you can take a JavaScript project and start migrating it to TypeScript little by little, one file at a time, since every JavaScript is also TypeScript.) 至于在Phaser项目中使用TypeScript,您可以阅读以下文章:(As for working with TypeScript in a Phaser project, you can read the article:) 将Phaser与TypeScript一起使用(Using Phaser with TypeScript) .(.)
图4-典型的Phaser TypeScript文件(Figure 4 - the typical Phaser TypeScript files)使用TypeScript,Phaser的开发将变得更加容易.例如,在传统的JavaScript代码中,大多数情况下,IDE无法弄清变量属于哪种类型,因此它无法准确地确定给定对象在给定时刻真正可用的成员.输入"(With TypeScript, Phaser development can be much easier. For example, in traditional JavaScript code, most of the time, the IDE can’t figure out to which type the variable belongs, so it can’t tell exactly which members of a given object are really available at a given moment when you are typing the “)对象+点+成员(object + dot + member)".这是因为JavaScript是一种(”. This happens because JavaScript is a)弱类型语言(weakly typed language).(.) 使用TypeScript时,我们可以将类型与对象相关联,因此在这种情况下,IDE现在可以预测哪些成员属于该对象:(When we work with TypeScript, we can associate types to our objects, so in this case the IDE can now predict which members belong to the object:)
我们可以使用TypeScript进行编码,但是最终,Web浏览器将只能识别JavaScript.这是当(We can code in TypeScript, but in the end the web browser will only recognize JavaScript. This is when the)转译器(transpiler)启动.例如,观察以下TypeScript代码:(kicks in. For example, observe the following TypeScript code:)
class Player implements IPlayer {
level: BaseLevel;
game: Phaser.Game;
cursors: Phaser.CursorKeys;
layer: Phaser.TilemapLayer;
bulletSound: Phaser.Sound;
diedSound: Phaser.Sound;
damageSound: Phaser.Sound;
rechargeSound: Phaser.Sound;
sprite: Phaser.Sprite;
isWeaponLoaded: boolean;
velocity: number;
walkingVelocity: number;
state: IPlayerState;
power: number;
constructor(
level: Level1, cursors: Phaser.CursorKeys,
layer: Phaser.TilemapLayer, bulletSound: Phaser.Sound,
diedSound: Phaser.Sound, damageSound: Phaser.Sound,
rechargeSound: Phaser.Sound) {
this.level = level;
this.game = level.game;
this.cursors = cursors;
this.layer = layer;
this.bulletSound = bulletSound;
this.diedSound = diedSound;
this.damageSound = damageSound;
this.rechargeSound = rechargeSound;
this.damageSound.onStop.add(function () {
this.sprite.animations.play('run');
}.bind(this));
this.power = 10;
this.create();
}
清单4-Player类的片段,显示强类型的TypeScript代码(Listing 4 - Fragment of the Player class showing strongly typed TypeScript code)现在注意当从TypeScript生成JavaScript时如何从代码中剥离类型.(Now notice how the types are stripped from the code when the JavaScript is generated from TypeScript;)
class Player {
constructor(level, cursors, layer, bulletSound, diedSound, damageSound, rechargeSound) {
this.level = level;
this.game = level.game;
this.cursors = cursors;
this.layer = layer;
this.bulletSound = bulletSound;
this.diedSound = diedSound;
this.damageSound = damageSound;
this.rechargeSound = rechargeSound;
this.damageSound.onStop.add(function () {
this.sprite.animations.play('run');
}.bind(this));
this.power = 10;
this.create();
}
清单5-将同一片段"转换"为JavaScript代码(Listing 5 - The same fragment “transpiled” into JavaScript code)## 任务(Mission)
自发性(Robocity)
游戏发生在反乌托邦式的未来城市大都会(Robocity),这个地方被机器人小人弄乱了.这座城市曾经是一个电子天堂,技术公民在这里生活在他们的异步行为,可预测的循环和执行随机任务中.(The game takes place in the dystopian futuristic metropolis of Robocity, a place perverted by a robotic villain. The city used to be an electronic paradise, in which the techno-citizens lived in harmony with their asynchronous behaviors, predictable loops and performing random tasks.)
机器人格雷格(Greg the Robot)
Greg是一个不循规蹈矩且自我意识的机器人.他是旧时代机器人的回忆标本,它免除了新机器人订单的束缚,因为它们的电子零件太旧了,无法被新订单入侵.由于他的所有朋友现在都是奴隶,格雷格突然成为了一个毫无疑问的英雄.他发现问题所在,因此决定采取行动.他的步伐缓慢,行为举止不稳定,但他知道Robocity的未来取决于他的成功.(Greg is a non-conformist and self-aware robot. He is a reminiscent specimen from an old age generation of robots which was spared the yoke of the New Robotic Order, because their electronic components are so old they can’t be hacked by the New Order. Since all his friends are now slaves, all of a sudden Greg becomes an unsuspected hero. He sees what’s wrong and decides to take action. His walking is slow and his behavior is erratic, but he knows the future of Robocity depends on his success.)
敌人(Enemies)
图5-不同类型的关卡敌人(Figure 5 - different types of level enemies)我们英雄的任务是走这条路,与敌人作战并击败每个关卡的头目.他的敌人是昔日和平的Robocity公民,如今在邪恶的Robotron的oke锁下转变为奴才.机器人Greg行走时,他会失去能量单位,因此他必须通过避免不必要的跑步来节省能量.幸运的是,沿途可以找到电池,因此格雷格(Greg)可以在走路时补充电池.(Our hero’s mission is to walk down the path, battling enemies and defeating each level’s boss. His enemies are the formerly peaceful citizens of Robocity, now converted into minions under the yoke of the evil Robotron. As Greg the Robot walks, he loses units of energy, so he must save energy by avoiding running without necessity. Fortunately, there are battery cells that can be found along the way, so Greg can replenish his batteries as he walks.)
老板(Bosses)
图6-标高Bos-o-Boss(Figure 6 - Bulb-o-Boss the Level Boss)每个关卡都在由Robotron将军之一守卫的最后竞技场结束.我们的英雄必须击败老板并越过通往新关卡的大门.(Each level ends in a final arena guarded by one of Robotron’s generals. Our hero must defeat the boss and cross the gate that leads to the next level.)
游戏观看次数(Game Views)
图7-游戏菜单图像(Figure 7 - the game Menu image)### 游戏菜单(Game Menu) 游戏菜单仅显示游戏标题,格雷格(Greg)机器人图像以及要求玩家按空格键开始的文本.(The game menu shows only the game title, the Greg the robot image and a text asking the player to press space bar to start.) 玩家按下空格键后,将进入下一个游戏视图:关卡演示屏幕.(Once the player press the space bar, he is taken to the next game view: the level presentation screen.)
等级介绍(Level Presentation)
图8-级别表示(Figure 8 - Level presentation)关卡展示视图仅显示带有关卡编号的文本.(The level presentation view only shows a text with the level number.)
关卡场景(Level Scenes)
图9-1级典型屏幕(Figure 9 - The Level 1 typical screen)关卡视图是游戏真正发生的地方.在这里,您可以通过光标键控制英雄并射击弹丸以消灭敌人.(The level view is where the game really happens. Here, you control the hero through the cursor keys and shoot the projectiles to eliminate the enemies.) 当您失去能量时,屏幕下部的电源条长度会减小.每当您与敌人碰撞或被某些弹丸击中时,就会损失一定的能量.但是您可以沿途捕获电池,并且能量最多可增加100%的电量.(As you lose energy, the length of the power bar in the lower portion of the screen decreases. Whenever you collide with an enemy or get hit by some projectile, you lose a certain amount of energy. But you can capture battery cells along the way, and your energy increases up to 100% of your power capacity.)
游戏结束(Game Over)
当格雷格的能量栏变空时,游戏结束.之后,将玩家重定向到菜单屏幕以进行新的游戏回合.(The game ends when Greg’s energy bar becomes empty. After that, the player is redirected to the menu screen for a new game round.)
游戏角色(Game Characters)
播放器(Player)
图10-Greg机器人行走(Figure 10 - Greg the robot walking)我们的英雄格雷格以(Our hero Greg is represented by the) Player
类,该类又实现了(class, which in turn implements the) IPlayer
接口.该界面具有速度字段,子画面字段和布尔值字段,用于指示是否加载了武器.(interface. This interface has a speed field, a sprite field and a boolean value field indicating whether the weapon is loaded or not.)
图11-Player类图(Figure 11 - the Player class diagram)速度是一个恒定值,它决定了玩家按下箭头按钮时英雄移动多快的距离.例如:(The velocity is a constant value and determines how fast our hero moves to cover a distance when the player press the arrow buttons. For example:)
runLeft() {
this.sprite.body.velocity.x = -this.velocity;
}
清单6-Player.runLeft()方法(Listing 6 - The Player.runLeft() method)的(The) sprite
是一种对象,用于存储我们的英雄(和敌人)的图形位图定义,并描述它们在屏幕上的显示方式.(is a type of object that stores the graphical bitmap definition for our hero (and the enemies) and describes how they are shown on screen.)
this.sprite = this.game.add.sprite(this.game.world.centerX - 16,
this.game.world.height - 64, 'player');
清单7-Player.setup()方法中的Sprite分配(Listing 7 - Sprite assignment inside Player.setup() method)布尔值指示是否加载了武器.我们英雄发射的每发枪弹都会清空武器,这使他无法同时投掷许多弹丸.(The boolean value indicates whether the weapon is loaded or not. Each shot fired from our hero empties the weapon and this prevents him from throwing many projectiles at the same time.)
if (this.player.isWeaponLoaded && keyboard.isDown(Phaser.KeyCode.SPACEBAR)) {
this.player.isWeaponLoaded = false;
this.player.shoot();
}
else if (!keyboard.isDown(Phaser.KeyCode.SPACEBAR)) {
this.player.isWeaponLoaded = true;
}
清单8-使用PlayerState.update()方法的武器加载管理(Listing 8 - Weapon loading management at PlayerState.update() method)Player对象具有一个(The Player object has a) wasHit
事件,如果敌人和头目与玩家精灵发生碰撞,则称为事件.(event, which is called in case of collisions of enemies and bosses with the player sprite.)
wasHit() {
this.damageSound.play();
this.sprite.animations.play('hit');
this.decreasePower(1);
}
分配给(The behavior classes assigned to the) Player
类作为(class as the) state
字段定义其可能的状态:(field define their possible states:)跑步(running)和(and)垂死(dying),由类提供(, which are provided by the classes) PlayerStateRunning
和(and) PlayerStateDying
分别.的构造函数(respectively. The constructor of the) Player
类定义武器已加载.在设置(class defines that the weapon is loaded. At the setup of) Player
我们定义类:精灵,动画,基本速度,步行速度以及英雄的尺寸.(class, we define: the sprite, the animations, the base speed, the walking speed and also the dimensions of the hero.)
击中Greg时,我们播放声音并显示与损坏相对应的动画序列.并且能量必须相应地降低.(When Greg is hit, we play the sound and also show the animation sequence corresponding to the damage. And the energy must be decreased accordingly.)
wasHit() {
this.damageSound.play();
this.sprite.animations.play('hit');
this.decreasePower(1);
}
清单9-Player.wasHit()方法(Listing 9 - The Player.wasHit() method)```js decreasePower(energyAmount: number): boolean { if (this.power - energyAmount > 0) { this.power -= energyAmount; this.level.updatePowerBar(); return true; } else { this.power = 0; this.level.updatePowerBar(); this.state = new PlayerStateDying(this); this.diedSound.play(); this.level.playerStateChanged(this.state); return false; } }
清单10-Player.decreasePower()方法(*Listing 10 - The Player.decreasePower() method*)在"充电能量"事件中,我们播放充电声音并增加电源栏中的电源.(*At the “recharge energy” event, we play the recharging sound and increase the power supply in the power bar.*)
```js
recharged(charge: number) {
this.rechargeSound.play();
this.increasePower(charge);
}
increasePower(energyAmount: number): boolean {
if (this.power + energyAmount < 100) {
this.power += energyAmount;
this.level.updatePowerBar();
return true;
}
else {
this.power = 100;
this.level.updatePowerBar();
return false;
}
}
清单11-Player.recharged()和Player.increasePower()方法(Listing 11 - The Player.recharged() and Player.increasePower() methods)随着英雄的复活,我们建立其初始状态,并将其默认动画设置为行走.这样,我们确保玩家继续玩.(As the hero ressurects, we reestablish its initial state, and set up its default animation as walking. By doing this, we ensure the player keeps playing.)
的(The) walk
方法将播放器精灵向上移动,并在没有播放器干扰的情况下自动调用.仅执行此操作是为了补偿不断下拉水平背景的垂直自动滚动.否则,播放器精灵将与所有内容一起向下滚动,最终出现在屏幕外.(method moves the player sprite in an upward direction and is called automatically without the player’s interference. It is executed only to compensate the vertical autoscroll that keeps pulling down the level background. Otherwise, the player sprite would be scrolled down along with everything and would end up offscreen.)
walk() {
if (this.noCursorKeyDown()) {
this.sprite.body.velocity.y = - this.walkingVelocity;
}
}
清单12-Player.walk()方法通过向上移动播放器来补偿垂直自动滚动.(Listing 12 - The Player.walk() method compensates the vertical autoscroll by moving the player upwards.)运行事件用于根据按下的箭头按钮更新速度矢量的方向.(The run events are used to update the direction of the speed vector according to the arrow buttons pressed.)
runUp() {
this.sprite.body.velocity.y = -this.velocity;
}
runDown() {
this.sprite.body.velocity.y = this.velocity;
}
runLeft() {
this.sprite.body.velocity.x = -this.velocity;
}
runRight() {
this.sprite.body.velocity.x = this.velocity;
}
清单13-Player运行方法(Listing 13 - The Player run methods)```js update(cursors: Phaser.CursorKeys, keyboard: Phaser.Keyboard, camera: Phaser.Camera) { if (cursors.up.isDown) { this.player.runUp(); } else if (cursors.down.isDown) { if (this.player.sprite.body.y < camera.y + camera.height - this.player.sprite.height) { this.player.runDown(); } }
if (cursors.left.isDown) {
this.player.runLeft();
}
else if (cursors.right.isDown) {
this.player.runRight();
}
清单14-调用Player.run *方法的PlayerStateRunning.update()方法(*Listing 14 - The PlayerStateRunning.update() method invoking the Player.run* methods*)射击事件触发一种方法,在该方法中,将播放射击声音,并将带有子弹精灵的新对象添加到游戏中.该弹丸可击中并伤害敌人.(*The shoot event triggers a method where the shooting sound is played and a new object with the bullet sprite is added to the game. This projectile can hit enemies and damage them.*)
```js
firePlayerBullet() {
let playerBullet: PlayerBullet =
new PlayerBullet(this, this.layer, this.bulletSound, this.player, this.boss);
playerBullet.setup();
this.playerBullets.push(playerBullet);
if (this.player.decreasePower(1)) {
this.updatePowerBar();
}
}
清单15-BaseLevel.firePlayerBullet()方法(Listing 15 - The BaseLevel.firePlayerBullet() method)每当我们的英雄受到伤害时,其能量会根据伤害类型而降低.如果剩余能量降至零,那么格雷格将遭受致命的打击.在这种情况下,我们显示(Whenever our hero is damaged, its energy is decreased accordingly to the damage type. If the remaining energy drops to zero, then Greg has suffered a fatal blow. In such case, we show the) sprite
Greg的动画被杀死,随后是Game Over音乐.之后,播放器将进入开始菜单.(animation of Greg being killed, followed by the Game Over music. After that, the player is taken to the start menu.)
另一方面,当能量增加时,功率条会根据接收到的量而增加.条形图中的最大能量级别为100.(On the other hand, when the energy increases, the power bar grows according to the amount received. The maximum energy level in the bar is 100.)
敌人(Enemies)
每一个(Every) enemy
类继承自(class inherits from an) abstract
类,其中包含:(class, which contains:)
- 加载的武器指示器(The loaded weapon indicator)
- 速度矢量(The speed vector)
- 敌人阵地(The enemy position)
- 敌人编号和编号(Enemy number and id)
图12-Enemy/BaseEnemy类图(Figure 12 - The Enemy/BaseEnemy class diagram)的(The) BaseEnemy
类包含处理与播放器碰撞的功能.每当确认碰撞时,玩家都会受到相当于敌人力量的伤害.但是之后,敌人被摧毁了.的(class contains the function that handles collisions against the player. Whenever a collision is confirmed, the player suffers a damage corresponding to the enemy strength. But after that, the enemy is destroyed. The) BaseEnemy
还可以检查敌人与其他敌人的碰撞.在这种情况下,它们都不会被销毁,但这可以确保它们不会重叠,从而为游戏带来更多真实感.(also allows the enemy to be checked for collisions against other enemies. In this case, none of them are destroyed, but this ensures they are not overlapped, and it brings more realism to the game.)
图13-Bee-bee 16机器人(Figure 13 - Bee-bee 16 robot)
图14-Bulb-o-boss级守护者(Figure 14 - Bulb-o-boss level guardian)## 动画(Animation)
水平滚动(Level Scrolling)
scroll() {
this.game.camera.y -= this.getScrollStep();
if (this.game.camera.y > 0) {
this.statusBar.position.y -= this.getScrollStep();
this.powerBar.position.y -= this.getScrollStep();
if (this.player.state instanceof PlayerStateRunning) {
this.player.walk();
}
}
this.game.time.events.add(Phaser.Timer.SECOND / 32, this.scroll.bind(this));
}
清单16-BaseLevel.scroll()方法(Listing 16 - The BaseLevel.scroll() method)### 播放器动画(Player Animation) 对于这个项目,我决定使用(For this project, I decided to use the)固定尺寸(fixed framed size)精灵表:(spritesheets:)
图15-格雷格(Greg)的Spritesheets,显示了步行程序(Sprite 0-3)和垂死的动画(4-7).(Figure 15 - Greg’s spritesheets showing both walking routine (sprites 0-3) and dying animation (4-7).)```C++ add(name: string, frames?: number[] | string[], frameRate?: number, loop?: boolean, useNumericIndex?: boolean): Phaser.Animation;
清单17-Phaser的AnimationManager.add()方法签名(*Listing 17 - Phaser's AnimationManager.add() method signature*)与相位器(*With Phaser's*) `AnimationManager` 类中,我们可以添加新的帧动画,稍后将用于表示玩家的不同状态:(*class, we can add new frame animations that will later on be used to represent the player's different states:*)
```C++
setup() {
.
.
.
this.sprite.animations.add('run', [0, 1, 2, 3], 4, true);
this.sprite.animations.add('hit', [4, 5, 6, 7, 4, 5, 6, 7], 10, true);
this.sprite.animations.add('die', [4, 5, 6, 7, 4, 5, 6, 7], 10, true);
this.sprite.animations.play('run');
.
.
.
}
清单18-Player.setup()方法(Listing 18 - The Player.setup() method)例如,播放器的"(Take for example, the Player’s “)击中”(hit")以上动画.当英雄受到伤害时播放.现在看看执行此动画所需的参数:(animation above. It is played when the hero suffers a damage. Now take a look at the parameters needed to perform this animation:)
- 名称:(name:)
hit
- 框架:(frames:)
[4, 5, 6, 7, 4, 5, 6, 7]
- frameRate:(frameRate:)
10
- 循环:(loop:)
true
这意味着当英雄被击中时,具有以上帧索引的帧将以每秒10帧的帧速率进行动画处理.此外,动画是循环播放的,这意味着帧序列将无限期重复.(This means that when the hero is hit, the frames with the frame indexes above will be animated with a frame rate of 10 frames per second. Also, the animation is in loop, which means the frame sequence will repeat indefinitely.)
声音(Sounds)
游戏中的声音不仅是使游戏更加有趣的资源,而且它们起着非常重要的作用:它们定义了事件之间的间隔.例如;当用户按下Spake键时(The sound in this game as not just a resource to make the game more interesing, but they play a very important role: they define the interval between events. For example; when the user presses the spake key in)菜单(Menu)屏幕上会开始播放音乐,但直到音乐结束才真正开始1级:(screen, the music will begin, but the level 1 will not really start until the music ends:)
update() {
if (this.game.input.keyboard.isDown(Phaser.KeyCode.SPACEBAR)) {
this.pushSpaceKey.alpha = 0;
this.game.add.tween(this.pushSpaceKey).to({ alpha: 1 }, 100,
Phaser.Easing.Linear.None, true, 0, 50, true);
this.startSound.play();
}
}
清单19-Menu.update()方法(Listing 19 - The Menu.update() method)上面的代码如何工作?(How the above code works?)
- 首先,程序检查是否按下空格键(First, the program checks whether the space bar key is pressed)
- 如果按下,(If it’s pressed, the “)按空格键(PUSH SPACE KEY)“文本变为透明(即我们将颜色Alpha设置为零)(” text becomes transparent (i.e., we set the color alpha to zero))
- 然后,通过在颜色alpha 0和1之间交替来使文本无限闪烁.(Then we make the text blink indefinitely by alternating between color alpha 0 and 1. We call the)
tween
方法. " Tween"是"中间"的缩写,即在帧密钥之间生成中间帧的过程.(method. “Tween” is short for “betweening”, that is, the process of generating intermediate frames between frame keys.) - 在不停止闪烁动画的情况下,我们播放(Without stopping the blinking animation, we play the)
startSound
音乐.(music.) 但是程序如何知道音乐何时结束?为了说明这一点,我们必须在(But how does the program know when the music ends? To explain that, we must see the code where the)startSound
字段已初始化:(field is initialized:)
create() {
.
.
.
this.startSound = this.game.add.audio('start');
this.startSound.onStop.add(function () {
this.game.state.start('splash1');
}.bind(this));
}
清单20-Menu.create()方法(Listing 20 - The Menu.create() method)现在我们可以看到(Now we can see the) onStop
的事件(event of) startSound
正在实施.当声音结束时,该功能将启动并进入游戏级别(being implemented. When the sound ends, the function will kick in and the game level) splash1
将开始.(will start.)
同样,每个其他游戏声音将确定下一个事件何时开始.(Likewise, each of the other game sounds will determine when the next event will begins.)
- 背景音乐(Background music)
- 射击(Shooting)
- 能量增加/减少(Energy up / down)
- 游戏结束(Game over)
碰撞(Collisions)
您可以通过引入物理约束来为游戏带来更多真实感,例如(You can bring more realism to the game by introducing physics constraints, such as)碰撞(collisions).否则,这些精灵将相互重叠或像鬼影一样漂浮在屏幕外.(. Otherwise, the sprites would overlap each other or just float away offscreen, like ghosts.)
玩家x地图(Player x Map)
级别地图已建立限制(左右边界,以及途中的障碍物),因此我们不能让我们的英雄绕过任何这些限制.幸运的是,Phaser有一个不错的碰撞检测库,它使我们能够定义玩家不能与游戏中的另一个给定对象重叠.我们通过调用(The level map has established limits (left & right margins, plus obstacles on the way), so we can’t allow our hero to bypass any of these limits. Fortunately, Phaser has a nice collision detection library, that allows us to define that the player cannot overlaps another given object in the game. We do this by calling the) game.physics.arcade.collide()
方法,将玩家的精灵和(method, passing as arguments both the player’s sprite and the)水平层(level layer)目的:(object:)
this.game.physics.arcade.collide(this.sprite, this.layer);
清单21-Player.update()方法内部的冲突处理(Listing 21 - Collision handling inside Player.update() method)#### 玩家x敌人(Player x Enemy)
我们不能允许玩家和敌人彼此重叠.我们通过调用(We can’t allow the player and enemies to overlap each other. We determine that they collide by calling the) game.physics.arcade.collide()
方法,但在这种情况下,我们还提供了一个函数,该函数将定义玩家被敌人的身体击打后将发生的行为:(method, but in this case, we also provide a function that will define which behavior will take place once a player is hit by the enemy’s body:)
- 我们使玩家受到伤害,并且(We make the player suffer a damage, and)
- 我们消灭敌人的精灵,将其从游戏中移除(We destroy the enemy’s sprite, removing it from the game)
checkPlayerCollisions() {
this.game.physics.arcade.collide(this.sprite, this.player.sprite, function () {
this.level.playerWasHit(this);
this.sprite.destroy();
}.bind(this));
}
清单22-Enemy.checkPlayerCollisions()方法内部的冲突处理(Listing 22 - Collision handling inside Enemy.checkPlayerCollisions() method)### 子弹x敌人(Bullet x Enemy) 子弹是摆脱远距离敌人的好方法,而不会被击中和造成伤害.发射子弹后,我们会检查子弹是否与敌人相撞,在这种情况下,我们会同时摧毁敌人和子弹.(Bullets are a nice way to get rid of enemies at long distance, without being hit and suffering damage. Once a bullet is fired, we check whether it collided or not with an enemy, and in this case, we destroy both the enemy and the bullet.)
this.level.enemies.forEach(enemy => {
this.game.physics.arcade.collide(this.sprite, enemy.sprite, function () {
this.destroyed = false;
this.level.playerBulletHit(this, enemy);
this.sprite.destroy();
enemy.sprite.destroy();
}.bind(this));
});
清单23-PlayerBullet.update()方法内部的冲突处理(Listing 23 - Collision handling inside PlayerBullet.update() method)### 敌人x敌人(Enemy x Enemy) 我们游戏中的敌人不是吃豆人的幽灵,因此它们不应重叠.因此,当它们碰撞时,它们仍然保持分离状态,我们不会破坏任何东西.我们只是给游戏带来了更多的真实感.(Enemies in our game are not Pac Man ghosts, so they shouldn’t overlap. So, when they collide, they still keep separated we don’t destroy anything. We just bring a little more realism to the game.)
checkEnemyCollisions(enemies: BaseEnemy[]) {
enemies.forEach(other => {
if (this.id != other.id) {
this.game.physics.arcade.collide(this.sprite, other.sprite, function () {
}.bind(this));
}
});
}
清单24-Enemy.checkEnemyCollisions()方法内部的冲突处理(Listing 24 - Collision handling inside Enemy.checkEnemyCollisions() method)### 玩家x老板(Player x Boss) 与上司的碰撞几乎与与小敌人的碰撞相同,即对玩家造成伤害.但是在这种情况下,老板并没有被摧毁.(A collision with the boss is almost the same as the collistion with a minor enemy, that is, causing damage to the player. But in this case the boss isn’t destroyed.)
this.game.physics.arcade.collide(this.sprite, this.player.sprite, function () {
if (this.player.sprite.animations.currentAnim.name == 'run') {
this.player.wasHit(this);
}
}.bind(this));
清单25-Boss.update()方法内部的冲突处理(Listing 25 - Collision handling inside Boss.update() method)### 子弹x老板(Bullet x Boss)
关卡头可以在被摧毁之前承受许多子弹.但是下面的代码只是从玩家项目符号的角度描述了行为.的(The level boss can sustain a number of bullets before being destroyed. But the code below just describes the behavior from the player bullet viewpoint. The) Boss
损害管理在内部(damage management is inside the) Boss
类.(class.)
this.game.physics.arcade.collide(this.sprite, this.boss.sprite, function () {
this.sprite.destroy();
this.level.playerBulletHit(this, this.boss);
this.boss.wasHit();
this.destroyed = false;
}.bind(this));
清单26-PlayerBullet.update()方法内部的冲突处理(Listing 26 - Collision handling inside PlayerBullet.update() method)## 等级图(Level Map) 如前所述,每个关卡都有一个背景图片,随着我们的英雄走这条路,图片会向下滚动.(As said before, each level has a background image that will scroll down as our hero walks the path.) 每个级别还具有一个关联的纯文本文件,其中每个字符代表背景图像中的32x32位置:(Each level also has a associated plain text file, where each character represents a 32x32 position in the background image:)
图16-Map01.txt文件映射到Level01.png背景图像.(Figure 16 - Map01.txt file mapped to Level01.png background image.)如我们所见,点代表地图中的32x32空空间,(As we can see, the dot represents an empty 32x32 space in the map, and the)**X(X)**对应对象可以是图像中的任何障碍物,例如墙壁,管道,迷宫等.(corresponds can be any of the obstacles in the image, such as walls, pipes, maze and so on.) 其他字符,例如小写字母((Other characters, such as lower case letters ()A B C D(a, b, c, d)),对应于在地图向下滚动时应放置的不同敌人.(), correspond to different enemies that should be placed as the map scrolls down.) 在启动级别时,它将加载纯文本文件并使用ASCII文件内容填充矩阵:(As the level is started, it loads the plain text file and populates a matrix with the ASCII file contents:)
let lines : string[] = this.readFile("/assets/maps/Map0" +
this.levelNumber + ".txt").split('\n');
for (let y = 0; y < lines.length; y++) {
let line : string = lines[y];
let lineArray : number[] = new Array(line.length);
for (let x = 0; x < line.length; x++) {
let char : string = line[x];
if (char == 'X') {
this.map.putTile(1, x, y, this.layer);
lineArray[x] = 1;
}
else {
lineArray[x] = 0;
}
}
}
清单27-在BaseLevel.setupMap()方法中读取地图(Listing 27 - Reading the map at the BaseLevel.setupMap() method)加载矩阵后,我们仍然必须构建在地图中找到的所有对象,例如敌人和炮台.让我们看看它们是如何映射的:(Once the matrix is loaded, we still have to build all the objects found in the map, such enemies and batteries. Let’s see how they are mapped:)
图17-Map01.txt中的敌人和电池位置.(Figure 17 - Enemies and battery locations from Map01.txt.)的(The) BaseLevel
类拥有一个实例(class holds an instance for) enemies
还有一个(and another one for) batteries
.根据对象的内容填充这些对象的数组(. These arrays of objects are populated according to the contents of the) mapAsStringArray
矩阵.这些敌人中的每一个都将在地图中找到的位置实例化.(matrix. Each of these enemies are instantiated with the position found in the map.)
setupEnemies(mapAsStringArray: string[]) {
this.enemies = [];
let enemycodes : string = 'abcde';
for (let y = 0; y < mapAsStringArray.length; y++) {
let line : string = mapAsStringArray[y];
for (let x = 0; x < line.length; x++) {
let char : string = line[x];
let indexOf : number = enemycodes.indexOf(char);
if (indexOf >= 0) {
let enemy: BaseEnemy;
let id: number = this.enemies.length + 1;
switch (indexOf) {
case 0:
enemy = new EnemyA(this, this.game, this.layer, this.bulletSound,
this.player, x * 32, y * 32, indexOf + 1, id);
break;
case 1:
enemy = new EnemyB(this, this.game, this.layer, this.bulletSound,
this.player, x * 32, y * 32, indexOf + 1, id);
break;
case 2:
enemy = new EnemyC(this, this.game, this.layer, this.bulletSound,
this.player, x * 32, y * 32, indexOf + 1, id);
break;
case 3:
enemy = new EnemyD(this, this.game, this.layer, this.bulletSound,
this.player, x * 32, y * 32, indexOf + 1, id);
break;
case 4:
enemy = new EnemyE(this, this.game, this.layer, this.bulletSound,
this.player, x * 32, y * 32, indexOf + 1, id);
break;
}
enemy.setup();
this.enemies.push(enemy);
}
}
}
}
清单28-BaseLevel.setupEnemies()方法(Listing 28 - The BaseLevel.setupEnemies() method)## 结论(Conclusion) 非常感谢您的时间和耐心.我希望本文或该项目的代码能以某种方式对您有所帮助,引起人们的兴趣,或者至少给您一些有关游戏开发的见解.在我看来,(Thank you very much for you time and patience. I hope the article or this project’s code have somehow helped you, induced interest or at least gave you some insights about game development. In my opinion,)移相器(Phaser)易于使用,学习曲线短,提供良好的支持并具有(is easy to use, has a short learning curve, provides good support and has an) 活跃社区(active community) .(.) 随意使用代码.如果您有任何想法,投诉或建议,请使用下面的评论部分.另外,如果您想使用Phaser创建游戏项目,请告诉我.(Feel free to use the code. If you have any ideas, complaints or suggestions, please use the comment section below. Also, if you want to create a game project using Phaser, please let me know.)
历史(History)
- 2017/11/27-初始版本(2017/11/27 - Initial version)
- 2017/11/28-添加了级别地图说明(2017/11/28 - Added level map explanation)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
HTML HTML5 Typescript Javascript VS2013 Canvas game 新闻 翻译