310 likes | 494 Views
运用 Entity/Component 架构的游戏引 擎 快速 开 发 HTML5 游戏. @ 原木博皞. About Me. 唐博皞 Boyd Tang @原木博皞 Startuper/ 原木游戏工作室 Co-Founder 独立游 戏开发 者 折腾 Flash/HTML5 游 戏 高 端骨灰级游戏玩 家 ( 高玩 , 恩 = -= ). Outline. 游戏编程的演变 – 从数据驱动的 GameObject 结构再到 Entity/Component 架构 架构思路 - Entity/Component 的实现特点
E N D
运用Entity/Component架构的游戏引擎快速开发HTML5游戏运用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结构下添加功能呢? 我要这样? 还是这样?
更复杂的情况呢… • Physics ?
经典GameObject结构的问题 很多功能无法单纯靠继承实现,最终的代码结构并不是一个有向无环图 类继承导致难以轻易改变结构 功能全都向上依赖 子类的数据爆炸,大量冗余数据和方法并导致内存消耗过大
从GameObject到GameEntity 游戏中所有的Object都是GameEntity GameEntity只是一个容器 GameEntity里含有一堆独立功能的Components Component之间的互相访问,通过GameEntity传递 Component可以实时增减,动态为GameEntity增减功能
Entity/Component架构的实现特点 • GameEntity是一个容器,其主要功能仅仅是增减Components • 组件(Component)才是功能的携带者,组件可以有一定依赖关系。 • 例如: Animated组件依赖于Drawable组件 • 属性(Property)是组件间互相访问的主要实现方法。属性由特定组件具有的一系列提供setter和getter方法。 • 例如: Position组件提供x,y属性,Update组件进行修改。
一些关于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 • 本期的重点介绍
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格为grass2 grass3:[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 屏幕底下的一行小字=-= 谢谢大家听我扯了这么久!