【问题标题】:Engine Scripting and Class Structure引擎脚本和类结构
【发布时间】:2010-11-04 19:04:53
【问题描述】:

我已经开始使用 C#/XNA 编写 2d JRPG 引擎,我对类结构和游戏事件有一些疑问。

在“游戏制作者”类型的环境中,您有一个游戏脚本,它根据用户输入运行事件等。这通常不是面向对象的,也没有对类等的引用。

现在在我自己的引擎中,编写游戏事件的最佳方式是什么?

假设您与屏幕上的 NPC 进行交互,那么在 OO 语言中如何处理该事件?我假设我将在角色类中有一个事件,并且我假设该事件将分配给加载地图的部分,但该事件将链接到哪里?哪个类将包含事件逻辑?

编辑:我应该注意,我不希望将其转换为完全可定制的引擎供其他人使用,因此完整的脚本语言实现并不是我真正想要的。

class Character {
...
public delegate void InteractionEventHandler(object sender, EventArgs e);
...
public event InteractionEventHandler Interact;

...

}

class Map {

public Map(){

...

//add a new character to the NPC collection
newchar.Interact += ???
...
}

}

目前我的班级结构相当简陋,因为我需要在更进一步之前解决这个问题,但它看起来像这样。

Main 类包含 Map 类的一个实例。

Map 类通过内容管道加载 xml 中的地图。

地图实例包含对地图中包含的所有对象和 NPC 的引用。

Map 类创建这些对象的新实例,这些对象都继承自 Drawable 类,该类包含将对象绘制到屏幕所需的基本方法和属性。这些实例都添加到 Map 类中的集合中。

Character 类是继承自 Drawable 类并包含 Interact 事件的类之一。

正如我所说,该项目基于固定时间步长的 XNA 框架,因此引擎在每次滴答时都会在 Main 类中运行 Update 和 Draw 方法。

更新方法包含处理玩家输入的逻辑。所以玩家可以移动到地图上的一个NPC并按下交互按钮。逻辑检查玩家面前的 NPC 并触发其交互事件……然后会发生什么?

【问题讨论】:

标签: c# xna


【解决方案1】:

事件不应附加任何逻辑;它们只是一条消息,一种告诉感兴趣的组件发生了什么的技术,但如何处理该事件是所述组件(也称为监听器)的责任。在你的情况下,那是你的 NPC。

但我不明白你关于“事件不是 OO”的论点,是什么让你相信?不能面向对象的事件是怎么回事?

现在关于如何处理事件和整个过程,我没有使用 C# 的经验,但我已经在 C++ 中完成了我的分享,这是我反复使用的一种方法,它足够灵活,可以满足我的需求:

我有一个事件元类,每个特定事件都可以从它子类化(即,UI 事件、NPC 事件等)。还有一个 EventManager 可以创建和销毁所有事件(“发射器”,需要触发事件的组件,会为实例调用管理器,即 GameObjects),最重要的是,它具有处理队列。

Emitter 对象会将事件“挂钩”到队列中,EventManager 会将它们分派给“Listeners”。现在,监听器对事件感兴趣,因此可以将自己注册到某些事件或其类型。一旦管理器将事件分派给这些侦听器,就由他们来执行与该事件相关的逻辑。

最终,拥有一个我必须从中继承子类的事件元类的设计对我来说不是很灵活,而且这是一项乏味的工作,所以我采用了另一种方法,我可以将属性任意附加到事件对象,并且处理组件必须知道如何处理它。这当然会影响性能,但对我来说这是必要的,因为我在 Lua 而不是 C++ 中触发和处理事件逻辑。

如果您正在制作一款 RPG 游戏,那么事件和脚本几乎是您游戏的主体,在开始编写代码之前,您确实需要对这两个方面进行简洁的设计。我建议先阅读Gamedev.net 上的事件处理文章。

最后,我不确定它是否对您有帮助,但我曾在使用 C++/Lua 制作网络游戏时发布过一次有关事件的信息,考虑到网络的性质,设计可能比您需要的更复杂游戏,但无论如何你都可以查看here

更新:我将向您展示如何使用我的系统的示例,但我必须首先明确:您使用 C# 和 XNA 是有原因的,而我不能误认为 XNA 提供的正是您所需要的,因此我建议不要使用我的事件管理系统或任何人的事件管理系统,因为我的事件管理系统与 C++ 及其模板机制紧密耦合,可能对您没有用处。该框架应该可以满足您的需求,因此请先检查一下。

// singleton
class EventManager {
  public:
  Event* createEvt<EventType*>();
  void subscribe(Event*, Listener*);
  void hook(Event*);
  void update();
}

class Listener {
  // tells evt manager we want to listen to these events
  void register(Event* someEvtType, Handler* myMethod);

  // adds event to the queue (called by the EventManager)
  void enqueue(Event* someEvt); 

  // processes events in the queue
  void process();

  protected:
  // calls every subscribed handler to process the event
  // a handler is only a functor that is bound to an event
  // when calling "register()"
  void dispatch(Event*);

  map<Event*, list<Handler*>> myHandlers; // an event could have multiple handlers (methods)
}

class GameObject : public Listener {

}

class NPC : public GameObject {
  protected:
    void walkTo(NPCEvent_Walk* evt);
    void talk(NPCEvent_Talk* evt);
}

// inside wherever you're gonna trigger the event, i presumed on mouseclick here
void InputManager::mouseClicked(MouseEvent* evt) {
  // we want the NPC to move
  NPCEvent_Walk* evtWalk = EventManager->createEvt<NPCEvent_Walk>();
  evtWalk->setX(...);
  evtWalk->setY(...);
  // and talk
  NPCEvent_Talk* evtTalk = EventManager->createEvt<NPCEvent_Talk>();
  evtTalk->setText(...);

  // when the master queue is empty, these events will be
  // dispatched to their registered Listeners which will
  // process them on their own terms
  EventManager->hook(evtWalk);
  EventManager->hook(evtTalk);
}

// as an NPC, i'd like to be notified of NPCEvents
NPC::NPC() {
  register(NPCEvent_Walk*, &NPC::walkTo);
  register(NPCEvent_Talk*, &NPC::talk);
}

void NPC::walkTo(NPCEvent_Walk*) {
  // move it
  // ...

  // are we done?
  //  return true;
  // if we need more updates, return false and we will be called again
  //  return false;
}

void NPC::talk(NPCEvent_Talk*) {
  // update UI components or whatever
  // ...
  return true;
}

显然,此代码将无法工作,因为它缺少所有实现细节,甚至在语法上也不正确,但它应该给你一个想法。但是,正如我上面所说,如果您使用 XNA,则不需要这样做。

【讨论】:

  • 好的,我想我理解您的解决方案,但您能否举个简短的例子来给我一些帮助?如果我们假设 NPC 类有 WalkTo 和 Say 方法,并且当与我交互时,我希望 NPC 的特定实例步行到特定位置,然后说出在你的事件“管道”中如何工作,我猜你可以叫它?当我说脚本中的事件不是面向对象时,我真正的意思是它们处于一种无定形的“全局脚本”中,并且不依赖于任何特定的类。
  • 我可以加我的 2 美分吗?问题是脚本处理程序本身就是一个类而不是它。你需要在你的角色基类中实现一些方法,比如“WalkTo”和“Say”,还需要在你的相机上实现“看”某些地方等等。然后,您可以设计一个简单的脚本来运行(即被中断),您的脚本处理程序知道如何处理该脚本。如果您也通过界面执行此操作,则它必须是全局的,因此可以是 iWalkable、iTalkable 等。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-14
相关资源
最近更新 更多