component based html5 game engine

Post on 22-May-2015

884 views 5 download

description

介绍Entity/Component游戏框架for html5 by @原木博皞(http://weibo.com/boisgames)

Transcript of component based html5 game engine

@ 原木博皞

运用 Entity/Component 架构的游戏引擎快速开发 HTML5 游戏

About Me

唐博皞 Boyd Tang@ 原木博皞 Startuper/ 原木游戏工作室 Co-Founder独立游戏开发者折腾 Flash/HTML5 游戏高端骨灰级游戏玩家 ( 高玩 , 恩 =-=)

Outline

游戏编程的演变 – 从数据驱动的 GameObject 结构再到 Entity/Component 架构

架构思路 - Entity/Component 的实现特点CraftyJS - HTML5 中的 Component Based 游

戏引擎Demo/Practice – HTML5 CodeJam 48 小时作

如果要做一个游戏,你会怎么写?

有一个 Main 函数 function main() {

while(/* 游戏没结束 */){ /* 在循环里写游戏更新逻辑 */ }

/* 游戏结束出现游戏结果 */ }

更新逻辑是啥?一个 player 和一堆 monster player.update(); // player 需要不断更新

for( var i = 0; i< MAX_MONS; i++){monsters[i].update(); // 怪物也是

}那如果 Player 和 Monster 有通用的功能呢?

GameObject – 经典的数据驱动结构

游戏世界中所有的物体都是GameObject 角色,怪物,环境障碍,车辆,

子弹,摄像头,触发器,灯光现在在 Main 函数中怎么写?

for( var i=0; i<numGameObjects; i++)

gameObjects[i].update();

GameObject 是什么呢?由数据驱动,而非代码写死

GameObject

Drawable

Static Simulated

Player Monster

Trigger

我要这样? 还是这样?

GameObject

Drawable

Animated

Static Simulated

Player Monster

Trigger

如果想在 GameObject 结构下添加功能呢?

GameObject

Drawable

Static Simulated

Animated

Player Monster

Trigger

更复杂的情况呢…

GameObject

Drawable

Static Simulated

Animated

Player Monster

Trigger

Physics ?

经典 GameObject 结构的问题

很多功能无法单纯靠继承实现,最终的代码结构并不是一个有向无环图

类继承导致难以轻易改变结构功能全都向上依赖子类的数据爆炸,大量冗余数据和方法并导致内存消

耗过大

那神马是 Entity/Component 架构?

从 GameObject 到 GameEntity

游戏中所有的 Object 都是 GameEntity

GameEntity 只是一个容器GameEntity 里含有一堆独立功能的 Components

Component 之间的互相访问,通过 GameEntity

传递Component 可以实时增减,动态为 GameEntity

增减功能

现在的 GameEntity 结构

Player

Drawable

Update

Animated

Position

Physics

Monster

Drawable

Update

Position

Physics

Props

Drawable

Animated

Position

Physics

Trigger

Update

Entity/Component 架构的实现特点

GameEntity 是一个容器,其主要功能仅仅是增减Components

组件 (Component) 才是功能的携带者,组件可以有一定依赖关系。 例如 : Animated 组件依赖于 Drawable 组件

属性 (Property) 是组件间互相访问的主要实现方法。属性由特定组件具有的一系列提供 setter 和 getter 方法。 例如 : Position 组件提供 x,y 属性, Physics 组件会使用到。

一些关于 Entity/Component 的常见问题

一个 GameEntity 可以有多个相同的 Component 么? 是的,理论上是可以的。一般 GameEntity 的实现可以根据

Component 的 ClassName 来进行 hash ,也可以通过给Component 实例设置 Name 进行 hash 。如果需要避免冲突,可以选择根据 ClassName 来实现。

传统的 GameObject 有个统一的 update , GameEntity的 update 呢,是一个 Component 嘛? 其实并不是这样的, GameEntity 的 update 可以是 interface ,当

Component 实现此 interface 后,即具备了 update 功能。也可以是事件驱动下, Component 监听发到 GameEntity 的 update 事件。

整个游戏世界中 GameEntity 是全都是同级的么? GameEntity 的实现时,可以增加 GameEntityGroup 的实现 () 。

这样就可以对其进行分组管理。

使用 Entity/Component 架构的游戏引擎

大型 3D 游戏引擎 – Unity

大家应该都知道这名字吧=-=

Flash 游戏引擎 – PushButton

推荐 PBE2 ,比 PBE1 轻量很多

HTML5 游戏引擎 – CraftyJS

本期的重点介绍 Github 上可搜到

Crafty – 用 JS将灵活发挥到极致

轻量的体积: 14.5KB (Minified&Gzipped)

类似 JQuery 的选择器用于 GameEntity 的选择同时支持 Canvas或者 DOM 进行渲染

( 不用 Canvas渲染连 IE6 都能跑 !)

事件驱动,有非常好用的 Event 系统支持 SpriteSheet ,碰撞检测,声音等

Crafty 的选择器

通过查询 Component 来进行选择 Crafty(“mycomp”); Crafty(“hello 2D mycomp”); Crafty(“hello, 2D, mycomp”);

第一个返回全部具有 mycomp 组件的 GameEntity第二个返回全部同时具有 hello, 2D 和 mycomp 组

件的 GameEntity ( AND操作)第三个返回至少有这些组件之一的全部

GameEntity(OR操作)

Crafty 的基本使用

创建GameEntity var player = Crafty.e();

为 GameEntity 添加 Component player.addComponent(“2D, DOM”) // 添加 2D 组件,使用

DOM渲染 也可以在创建GameEntity 时添加 Crafty.e(“2D, DOM”);

为 GameEntity 设置属性 player.attr({ x:5, y:5, w:100, h:100}); // 当具有 2D 组件时,

GameEntity 即拥有了 x,y,w,h等属性级联操作

player.addComponent(“2D, color”).color(“red”).attr({ w:100, h:100});

自定义你的 Compoent

使用 Crafty.c() 方法进行自定义 Crafty.c(“mycomp”, { // 为自定义组件命名

init: function() { //init 函数在被组建被添加时调用 // 自定义组件函数中的 this ,均指向所属的 Entity this.requires(“2D, Color”); //requires 方法可指定依赖

组件 this.w = 32; this.h = 32; this.color(“red”); // 也可以这样写 : this.attr({w:32, h:32 }).color(“red”);},myfunc: function() { /*我的方法实现 */ }

})

Crafty 的事件系统

通常事件会在自定义组件的 init 中绑定 Init: function() {

this.requires(“2D, DOM, Mouse”);// 可以使用 bind 方法绑定事件this.bind(“Enterframe”, function( e) { //Enterframe

可任意监听 … // 即 update 函数});this.bind(“Click”, function(e){ // 当添加 Mouse 组件后可用 … // 当鼠标点击Entity 时的处理函数})

}

事件触发 : this.trigger(“event” , data);

属性与 Setter 方法、 Change 事件

属性获取、更改 (若无 setter, 首次使用即定义 ) attr(name, value)或者 attr({name:value})均更改属性 attr(name) 可获取属性

针对特定属性的处理: setter Crafty.c(“mycomp”, {

_hp: 100,init: function() { this.setter(“hp”, function(v){ this._hp = v; /* 一些

判断 */});}

});针对全部属性改变的事件:Change

this.bind(“Change”, function(){ /* 一些判断 */ })

Crafty 中的关卡场景定义

关卡场景定义 Crafty.scene(“sceneName”, function(){

…//执行一些函数,创建一堆 Entity ,设置背景, bhla bhla…});

关卡场景调用 Crafty.scene(“sceneName”); // =_____= 没啥好说的…

关卡场景切换时发生了啥? 开始某关卡时, Stage 当前所有具有 2D 组件的 Entity 都会被销毁

若希望保留某些 Entity ,给它添加 Persist 组件即可 关卡切换时 SceneChange 事件将触发

引擎的启动与停止

Crafty.init([width, height]); 长宽默认是全屏 引擎 init 后,将持续触发 Enterframe 事件 , 即为游戏提供

update 。 引擎启动瞬间,将触发 Load 事件

Crafty.stop(); 当引擎停止后将移除 Enterframe 事件的 setInterval 同时也将移除全部在 Stage 中的元素

使用 SpriteSheet

使用 Crafty.sprite 函数创建 Sprite 组件 Crafty.sprite(16, 16, “spirte.png”, { // 定义每格长宽

grass1:[0,0], // 将此 SpriteSheet 的 0,0格位置定义为grass1

grass2:[1,0], // 同上定义 1, 0格为 grass2grass3:[2,0],grass4:[3,0],flower:[0,1], // flower 定义在 0,1格bush:[0,2],player:[0,3], // 玩家定义在 0, 3格位置

});定义 Sprite 组件后,创建GameEntity

Crafty.e(“2D, DOM, flower”);// 此时组件已定义,将渲染 0,1 格的 flower

使用 Sprite 动画

添加 SpriteAnimation 组件即可使用 Sprite 动画 var player = Crafty.e(“2D, DOM, player,

SpriteAnimation”).attr({ x:100, y: 100}).animate(“left”, 6, 3, 8) // 定义 left 为 x:6, y:3格

开始到 x:8格.animate(“right”, 9, 3, 11)// 同上.animate(“up”, 3, 3, 5).animate(“down”, 0, 3, 2);

播放动画 player.animate(“left”, 10); // 在 10帧时间内播放 left 动画

其他函数 stop(), reset(), isPlaying(name)

Crafty 中的碰撞检测

基于 Crafty.Polygon或者 Crafty.Circle 的碰撞检测添加 Collision 组件后即可使用

var player = Crafty.e(“2D, DOM, player, Collision”).collision() //默认创建基于 x,y,w,h 的 Polygon 用于检

测.onHit(“flower”, function(){ …// 进行一些处理});

onHit 方法是自动对具有某个 Component 进行检测 当与具有该组件的 GameEntity碰撞时即调用注册的函数

也可以直接调用 hit(compName)检测 此方法返回 Boolean

Crafty 中的键盘操控

基本组件: Keyboard 组件 函数: isDown(key) // 可以在 Crafty.keys 中找到映射 事件触发: KeyDown , KeyUp

高级组件:Multiway 组件(依赖 Keyboard) 函数:multiway([speed], {W: -90, S:90, D:0, A:180} ); 事件触发:NewDirection( 方向切换时 ), Moved( 发生移动

时 )

便捷组件: Fourway, Twoway ( 依赖 Multiway) fourway // wasd四方向控制 twoway // ad 双方向, w跳可在 Gravity 组件中使用

Crafty 中的鼠标操控

基本组件 : Mouse 组件 函数: areaMap(polygon) // 传入一个 Crafy.Polygon 作为检测区

事件触发 : MouseOver, MouseOut, MouseUp, MouseDown, Click

高级组件:Draggable 组件(依赖 Mouse 组件) 函数 : startDrag, stopDrag, enableDrag, disableDrag 事件触发: StartDrag, StopDrag

对于移动平台的 Touch ,同样使用 Mouse 组件 暂无对 MultiTouch 的支持

资源预加载与音频播放

资源预加载与音频分别需要 Image 和 Audio 的支持使用 Crafty.load 进行资源预加载

Crafy.load([/*资源 url 列表 */], onComplete, onProgress, onError);

预加载可以先定义一个 loading 的 scene ,进入并进行Crafy.load 。在 onComplete 函数中切换到 main scene

音频的命名和播放 命名: Crafty.audio.add(id, urls); //urls 传入多种格式,播放时引擎会选择其中一个兼容的格式

播放: Crafty.audio.play(id);

Crafy与其他 JS框架相结合进行游戏开发

Crafty 在创建 Stage 时会查找 id 为 cr-stage 的div

Crafty 引擎擅长处理具有游戏性 GameEntity

UI 并不是 Crafy擅长的,可以选择其他框架 当然使用 DOM 组件处理 UI 也是可以的。

为其他 JS框架定义 Component 以传递数据对中文的处理最好使用 DOM

一些 Crafty 的 Demo 和 Practice

官方的 Demo RPG Isometric Asteroids Connect4 FruitAssassion

HTML5 CodeJam 上的 Practice 杀戮者

屏幕底下的一行小字 = - =

谢谢大家听我扯了这么久!

Q&A