如何以实用有效的方式处理组件和游戏对象之间的事件?

时间:2015-10-05 08:12:24

标签: lua event-handling game-engine gameobject

我最近正在开发一个2D游戏框架,所有这些都是关于如何管理游戏对象及其之间的消息。我已经学习了ECS(实体组件系统),这是一个数据驱动和属性 - 管理游戏对象的中心方式。但是我不会使用它,因为没有合适的ECS框架可以使用我正在使用的游戏引擎,而且我最近还没有计划实现它。换句话说,我们不会谈论ECS和数据驱动以及以财产为中心的方式或其他内置支持的游戏引擎。

NOT DISCUSS

  • ECS
  • 以数据驱动和以财产为中心的方式来处理事件和管理对象
  • Unity或类似的东西

我尝试做的是通过传统的以对象为中心(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的攻击范围内不断攻击目标。要通过以下步骤实现此游戏逻辑:

  1. A使用AI组件查找路径点并使用Move组件更改位置。
  2. A使用GameObjectManager查询移动时可以攻击哪些目标。
  3. 获取目标后,A将迭代目标并在每个目标游戏对象上调用它们的handleEvent(event)方法,该方法也来自超级游戏对象,并通知&#34; takeDamage&#34;事件
  4. Target将通过其Health组件以适当的方式处理事件。
  5. 要点:

    我使用GameObjectManager使游戏对象彼此通信,而不是使用pub / sub或sig / slot模型。因为我认为它不需要订阅游戏对象感兴趣的特定事件。你只需要在handleEvent(event)方法中切换来处理你的事件,它比pub / sub更干净。另一个原因是你总是不需要播放一个事件,因为你总能得到&#34;目标&#34;这是你想要得到的,这是更有效的。我还认为游戏对象本身就是一个&#34; hub&#34;从另一个组件中获取组件。总之,通过GameObjectManager对象到对象的通信,以及通过游戏对象本身进行组件到组件的通信。并且不允许一个游戏对象直接与另一个游戏对象的组件通信。

    我想问的问题:

    1. 我做的这种设计是对还是对?
    2. 在这种设计中是否有一些明显的缺点,或者在某些情况下无法解决?
    3. 有些教程告诉我,我应该将handleEvent部分放在组件中,而不是将它放在游戏对象中。并且游戏对象应该只关注从其他游戏对象接收事件并将其广播到它拥有的所有组件而不是自己处理它。但是我不明白的是,如果我将handleEvent部分放在组件中,我可以确保这个特定的事件处理程序适用于具有此组件的每个游戏对象。例如,A和B都有一个Health组件,用于检测游戏对象是否已死。如果A死了,游戏逻辑就是从场景中删除A,没有别的。但是如果B死了,除了删除它之外,我还想播放声音或动画。换句话说,A和B的死亡游戏逻辑可能完全不同。在我的设计中,没有这样的问题,因为我使用它拥有的组件处理游戏对象中的事件。游戏对象自然是可以区分的 问题是我是否遗漏了一些东西才能理解教程告诉我的事情?

0 个答案:

没有答案