1

我正在尝试在 DirectX9 中编写回调事件系统。我正在尝试使用方法函数指针来触发鼠标点击事件;但我遇到了一些问题。我的游戏使用游戏状态管理器来管理渲染。我所有的游戏状态都派生自一个基类 AbstractGameState。

我有一个带有这个特定方法的精灵对象:

m_NewGameSprite->OnClick(this, &MainMenuState::StartGame);

MainMenuState 是我的游戏所处的当前游戏状态,而 StartGame 是该类的 void 方法部分。我想将函数指针存储在我的精灵类中的一个变量中,以便我可以在用户单击时执行它。

template <typename T>
void OnClick(GameState* LinkedState, void (T::*EventPointer)())
{
    m_LinkedGameState = LinkedState;
    m_EventPointer = EventPointer; // <- Doesnt compile
}

我试过向下转换指针,但这并没有真正奏效。

我的精灵类也包含这两个变量

void                (GameState::*m_EventPointer)();
GameState*          m_LinkedGameState;

任何帮助,将不胜感激

4

5 回答 5

2

我真的不知道为什么你的任务不起作用,毫无疑问,litb 很快就会解释原因。Boost.Function是一个美观、通用、类型安全和标准的函数对象,几乎可以在所有情况下用作函数指针的替代品。我会这样做:

typedef boost::function0<void> Event;
Event m_Event;

请注意,事件类现在封装了函数调用的状态,包括您要调用它的对象。通常你也使用Boost.Bind来创建一个闭包,但你也可以很容易地绑定到一个自由函数或其他一些函数对象。

void OnClick(Event &event)
{
  m_Event=event;
}

像这样称呼它:

OnClick(boost::bind(&MainMenuState::StartGame, this));

使用这种方案,您实际上不需要存储“链接的游戏状态”——它被封装在事件对象中。

于 2009-06-25T23:55:12.330 回答
1

我有一个非常相似的问题。如果我没看错,你就是在尝试编写一个几乎像ActionEventJava 一样的系统。看起来像这样:

Component.addActionListener( new ActionEventListener( new ActionEvent(
            public void execute() { /* Component clicked Do Something */ } )));

在 C++ 中,有一个非常相似的事情要做,如下所示。

在我看来,这要好得多,因为您实际上可以重用方法调用并随意修改它们,这为您提供了更大的灵活性。

事件.hpp

#pragma once

class Event 
{
    public:
        Event(void) { }
        ~Event(void) { }

    public:
        //Stores a function to callback. Example shown in Main.cpp
        void operator += (void (*callback)())
        {
            this->callback = callback;
        }

        //Executes our stored call back method at any given time.
        void execute(void)
        {
            callback();
        }

        //Variable of type VOID* -> This is a function which is stored + executed.
        private:
            void (*callback)();
};

主文件

#include <iostream>
#include "Event.hpp"

using namespace std;

void print(void)
{
    cout << "Hello This Works" << endl;
}

void print2(void)
{
    cout << "Success again!" << endl;
}

int main()
{
    Event task_event;
    task_event += print;
    task_event.execute();
    task_event += print2;
    task_event.execute();
    system("pause");
    return 0;
}
于 2012-08-11T00:44:19.487 回答
1

这里的问题是StartGame不能使用任何GameState实例调用它的this参数。它只能通过this类型参数调用MainMenuState

void (GameState::*)()指向在 中定义的MainMenuState方法,该方法必须是也在 中定义的虚拟方法GameState

我建议不要尝试存储成员函数指针,而是存储“命令对象”(或仿函数)指针,使用类似:

class Callback
{
public:
    virtual void Execute() = 0;
};

然后定义一个这样的实现:

template <typename TClass>
class MemberCallback : public Callback
{
public:

    MemberCallBack(TClass * pThis, void (TClass::*pFunc)() )
    {
        m_pThis = pThis;
        m_pFunc = pFunc;
    }
    void Execute()
    {
        m_pThis->*m_pFunc();
    }

private:
    TClass * m_pThis;
    void (TClass::*m_pFunc)();
};

然后,您可以定义 type 的成员Callback *

于 2009-06-25T23:56:24.027 回答
0

你为什么在那里使用模板函数? OnClick不会为TGameState 以外的值编译或工作。

但是,在这种情况下,我通常不会使用指向成员函数的指针。我会创建一个函数对象,重载operator(),然后传递它们。这样更好:语法更清晰,如果您发现需要,您可以在函数对象中存储一些状态。

于 2009-06-25T23:49:22.777 回答
0

您应该将参数作为:

void (GameState::*EventPointer)()

因为一旦它是一个指针,它就不能像那样被向下转换(它没有关于基类中是否存在相同指针的信息)。这些功能必须在 GameState 上才能工作(可能是虚拟的)

然而!由于您正在执行回调(基本上是观察者模式),您可能会仔细研究boost::signals。它将完成所有这些(以及更多)。您不必将函数添加到 GameState。

class Foo {
 // ...
 template <typename T>
 boost::signals::connection OnClick(const boost::function<void ()>& func)
 {
    return sig.connect(func);
 }

 void FireClick() { sig_(); }
private:
  boost::signal<void ()> sig_;
};

int this_will_be_called() { }

Foo f = // ...;

f.OnClick(boost::bind(&this_will_be_called));
于 2009-06-26T00:06:05.740 回答