引擎脚本和类结构

时间:2010-11-03 17:37:38

标签: c# xna

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

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

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

假设您在屏幕上与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类通过Content Pipeline在xml中加载地图。

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

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

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

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

update方法包含处理播放器输入的逻辑。因此,玩家可以在地图上移动到NPC并按下交互按钮。逻辑检查玩家面前的NPC并触发其Interact事件......那么会发生什么?

1 个答案:

答案 0 :(得分:0)

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

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

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

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

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

最终,我必须保留子类的事件元类的设计对我来说不是很灵活,而且这是繁琐的工作,所以我采用了另一种方法,我可以任意地将属性附加到事件对象,处理组件必须知道如何处理它。这当然对性能有影响,但是当我在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,则不需要这样做。