我应该使用什么技术来促进XNA GameComponents之间(或游戏中任何类型的组件之间)的通信?


9

我从第一个“适当的”游戏项目开始,并且不可避免地遇到障碍,试图决定XNA中的游戏组件应该如何通信。

从以前的(Java)GUI编程事件来看,处理程序和侦听器似乎是前进的道路。因此,我将拥有某种类型的事件总线,该事件总线可以接受事件注册,并可以订阅这些事件的类,并由处理程序来处理它们。例如(伪代码):

class SpriteManager
    Update(){
        if(player.collidesWith(enemy)
             // create new 'PlayerCollisionEvent'
    }

class HUDManager
    onPlayerCollisionEvent(){
        // Update the HUD (reduce lives etc)
    }

但是,我不确定完全完成此操作所需的代码设置(在C#中)。是什么跟踪事件(某种公共汽车?),它的结构如何?

在游戏服务上似乎也有很多提及,您可以在Game.cs主类中注册一个GameComponent,然后从引用了主要“ Game”对象的代码中的任何位置获取它。我已经使用SpriteBatch对象尝试了此操作,这看起来非常简单..但是,我看不到它像事件模型那样灵活。

以敌人死亡时为例。我们要更新游戏分数。使用服务,我可以获得对在Game1中创建并作为服务添加的StateManager对象的引用,然后将“得分”设置为新值。我认为“ onEnemyDeath”事件可以由多个类以不同的方式处理,但是由相关“敌人死亡检测”部分中的一行代码启动,比单独转换每个必需的GameComponent然后调用任何东西更好。方法是必需的。

还是这些次等策略比其他策略好?

我意识到这和游戏通信范例一样,在一定程度上是我对C#的不充分了解,但是我真的很想弄清楚这一基本知识。

更新资料

看过服务时,我不太相信它的细节-它基本上是在传递一个全局变量(据我了解)。

更新2

看过有关事件处理和测试示例代码的基础教程之后,看来事件将是我所讨论的逻辑选择。但我在所看到的示例中使用的却很少。有一些明显的原因为什么不应该这样做?


我建议您先尝试FlatRedBall。它位于XNA之上,使生活变得非常轻松。
ashes999 2011年

3
在跳到引擎之前,我很想真正了解发生了什么。
codinghands

我认为您在这里的失败实际上是缺乏有关C#的信息。C#通常使用事件来促进类之间的通信。实际取决于您的操作方式-XNA提供了SpriteBatch类,大多数工作由您来编写。在深入研究细节之前,引擎将为您提供一个在更高层次上工作原理的扎实思路。
ashes999 2011年

1
我在一定程度上表示同意,但是从我的理解来看,GameComponents之间的大多数类间通信可以使用服务来完成。我习惯于使用事件。我不确定哪个是最好的,或者是否有有效的替代方法(单身,

Answers:


4

从以前的(Java)GUI编程事件来看,处理程序和侦听器似乎是前进的道路。因此,我将拥有某种类型的事件总线,该事件总线可以接受事件注册,并可以订阅这些事件的类,并由处理程序来处理它们。

您对C#中的事件总线了解不多的原因是,根据我的经验,我认为Java之外的任何人都不会将这种事情称为“总线”。更常见的术语可能是消息队列,事件管理器,信号/插槽,发布/订阅等。

您不需要其中任何一个就可以制作一款可以正常运行的游戏,但是如果您对这种系统感到满意,那么它也会在这里为您提供满意的服务。不幸的是,没有人能确切地告诉您如何制作一个,因为每个人甚至都不会使用它们,他们的滚动方式会有所不同。(例如,我没有。)您可以从一个简单System.Collections.Generic.List<Event>的队列开始,您的各种事件是Event的子类,并列出每种事件类型的订阅者列表。对于队列中的每个事件,获取订阅者列表,并为每个订阅者进行调用handle(this_event)。然后将其从队列中删除。重复。

在游戏服务上似乎也有很多提及,您可以在Game.cs主类中注册一个GameComponent,然后从引用了主要“ Game”对象的代码中的任何位置获取它。我已经使用SpriteBatch对象尝试了此操作,这看起来非常简单..但是,我看不到它像事件模型那样灵活。

它可能不那么灵活,但是更容易调试。事件和消息比较棘手,因为它们将调用函数与被调用函数分离开,这意味着调试时调用堆栈并不是很有用,中间操作可能会导致某些中断,而这些中断通常会起作用,而如果未正确连接,则可能会静默失败, 等等。当涉及到尽早发现错误并拥有清晰明确的代码时,“抓住组件并调用所需组件”这一显式方法非常有效。它只会导致紧密耦合的代码和许多复杂的依赖关系。

我意识到这和游戏通信范例一样,在一定程度上是我对C#的不充分了解,但是我真的很想弄清楚这一基本知识。

C#与Java几乎是完全相同的语言。您在Java中所做的任何事情都可以用C#完成,只是使用不同的语法。如果您在翻译概念时遇到问题,可能仅是因为您只知道Java特定的名称。


谢谢@Kylotan。出于好奇,您自己使用什么系统进行类间通信-服务方法还是其他方法?
codinghands

AFAIK,事件总线类似于消息传递队列,但是用于事件。
ashes999 2011年

2
@codinghands,我通常不使用任何形式化的类间通信方法。我只是保持简单-如果一个类需要调用另一个类,它通常要么已经作为成员引用了第二个类,要么将第二个类作为参数传入。但是有时候,当我使用Unity引擎时,该方法与您所谓的“服务”方法类似,因为我会按名称查找具有所需组件的对象,然后查找该组件在对象上,然后在该组件上调用方法。
Kylotan

@ ashes999-在这种情况下,消息和事件几乎是同一件事。如果将事件放在队列,总线或其他任何东西上,则实际上存储的是一条消息,指示事件已发生。同样,将消息放入队列意味着发生了生成该消息的事件。观察异步函数调用的不同方法。
Kylotan

抱歉,我还没有将其标记为“已回答”,但是我认为将会有更多的观点,因为每个游戏都需要以某种方式在组件之间进行通信,而与语言无关……我们涵盖了主要的可能性吗?
codinghands

1

您是否尝试过仅使用简单的静态事件?例如,如果您想让分数班知道敌人何时死亡,您可以执行以下操作:

分数

class Score
{
    public int PlayerScore { get; set; }

    public Score()
    {
        EnemySpaceship.Death += new EventHandler<SpaceshipDeathEventArgs>(Spaceship_Death);
    }

    private void Spaceship_Death(object sender, SpaceshipDeathEventArgs e)
    {
        PlayerScore += e.Points;
    }
}

敌人太空飞船

class EnemySpaceship
{
    public static event EventHandler<SpaceshipDeathEventArgs> Death;

    public void Kill()
    {
        OnDeath();
    }

    protected virtual void OnDeath()
    {
        if (Death != null)
        {
            Death(this, new SpaceshipDeathEventArgs(50));
        }
    }
}

SpaceshipDeathEventArgs.cs

class SpaceshipDeathEventArgs : EventArgs
{
    public int Points { get; private set; }

    internal SpaceshipDeathEventArgs(int points)
    {
        Points = points;
    }
}


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.