我最近正在开发一个2D游戏框架,所有这些都是关于如何管理游戏对象及其之间的消息。我已经学习了ECS(实体组件系统),这是一个数据驱动和属性 - 管理游戏对象的中心方式。但是我不会使用它,因为没有合适的ECS框架可以使用我正在使用的游戏引擎,而且我最近还没有计划实现它。换句话说,我们不会谈论ECS和数据驱动以及以财产为中心的方式或其他内置支持的游戏引擎。
NOT DISCUSS
我尝试做的是通过传统的以对象为中心(OOP)的方式处理事件和管理游戏对象,该方式考虑整个世界是由游戏对象构建的。但新的东西是我仍然想在游戏框架中使用组件。这里的组件不会像在ECS中那样起作用,ECS只是纯数据而根本没有游戏逻辑。这里的组件具有与特定低级引擎子系统的链接或者具有它的每个游戏对象的常用游戏逻辑。例如,我有一个音频组件代码( Lua ),如下所示:
local Audio = class("Audio",Component)
function Audio:ctor( params )
-- body
if params == nil then
params = {}
end
self.type = ComponentType["Audio"]
self.super.ctor(self.params)
end
function Audio:play( soundId )
-- call audio sub system engine method to play sound
end
return Audio
我还有一个像这样的健康组件:
local Health = class("Health",Component)
function Health:ctor( params )
-- body
if params == nil then
params = {}
end
self.type = ComponentType["Health"]
self.health = 100
self.super.ctor(self.params)
end
function Health:addHealth( num )
self.health = self.health + num
end
return Health
整个设计可以通过这个图表来说明:
GameObject 1 <------ *Component
| Render
| Position
| Health
| Audio
| ... ...
游戏中的每个游戏对象都直接从超级游戏对象继承,并且没有后代从自身派生。所以游戏对象的继承层次结构应该只有两个层次。我们可以这样做,因为游戏对象是由组件组成的,这些组件封装了处理游戏逻辑的能力,例如播放声音&#34;而不是继承基础游戏对象来获得能力。但我计划让组件具有OOP传统的继承层次来处理游戏中的复杂情况。它应该是这样的:
Render
/
/
Customed Render(which has customed shader)
超级游戏对象负责常见操作,例如&#34;获取组件&#34;和这样的代码:
local GameObject = class("GameObject")
function GameObject:ctor( params )
-- contain all the components the game object has
self.components = {}
end
function GameObject:update( dt )
-- body
for i,component in ipairs(self.components) do
component:update(dt)
end
end
function GameObject:addComponent( component )
-- body
table.insert(self.components,component)
end
function GameObject:getComponent( type )
-- body
for i,component in ipairs(self.components) do
if type == component.type then
return component
end
end
end
function GameObject:handleEvent( event )
-- body
assert(false,"derived classes from GameObject must implement handleEvent method")
end
return GameObject
作为超级游戏对象的派生类的游戏对象真正负责创建所需的组件和事件处理。它是这样的:
local A = class("A",GameObject)
function A:ctor( params )
-- body
self.super.ctor(self,params)
self.initComponents()
end
function A:initComponents( ... )
-- body
local render = Render:create()
local health = Health:create()
self:addComponent(render)
self:addComponent(health)
end
function A:handleEvent( event )
-- body
if event.type == "addHealth" then
self:getComponent("Health"):addHealth(event.data.num)
end
if event.type == "takeDamage" then
self:getComponent("Health"):takeDamage(event.data.num)
end
end
return A
GameObjectManager主要负责维护所有游戏对象和游戏对象查询的生命周期,例如&#34;我希望获得爆炸半径内的所有游戏对象&#34;。它看起来像这样:
local GameObjectManager = class("GameObjectManager")
local instance = nil
function GameObjectManager:getInstance( ... )
-- body
if instance == nil then
instance = GameObjectManager:create()
end
return instance
end
-- pseudocode just for simplicity here. Just imagine that it can handle
-- all kinds of query and return the proper game objects
function GameObjectManager:query( ... )
... ...
end
return GameObjectManager
到目前为止,我可以在塔防游戏的场景下使用这个游戏设计:
游戏对象A伴随着设计师设计的路径,同时在A的攻击范围内不断攻击目标。要通过以下步骤实现此游戏逻辑:
要点:
我使用GameObjectManager使游戏对象彼此通信,而不是使用pub / sub或sig / slot模型。因为我认为它不需要订阅游戏对象感兴趣的特定事件。你只需要在handleEvent(event)方法中切换来处理你的事件,它比pub / sub更干净。另一个原因是你总是不需要播放一个事件,因为你总能得到&#34;目标&#34;这是你想要得到的,这是更有效的。我还认为游戏对象本身就是一个&#34; hub&#34;从另一个组件中获取组件。总之,通过GameObjectManager对象到对象的通信,以及通过游戏对象本身进行组件到组件的通信。并且不允许一个游戏对象直接与另一个游戏对象的组件通信。
我想问的问题: