Delta3D9教程:添加消息发送和可激活体

前面的教程我们创建了一个坦克角色,并通过场景编辑器STAGE,将它加载到场景中,并添加自带的地形,使坦克在贴近地面。
本节将设计让坦克响应Enter按下事件来启动引擎,并按向上的按键让坦克移动起来,即为当我们按下Enter键的时候发送一个消息,然后让坦克接受消息并根据消息控制引擎的开启和关闭,然后按下向上键,并在tick事件中让坦克向前贴地移动。

游戏事件


1、简介

Delta3D事件本身只是简单的字符串标识,当某个事件发生后以消息的形式发出,每一个游戏事件都代表一个单独的行为,如发现了苹果,解救了人质等,我们常常混淆了游戏事件和游戏消息。

  • 游戏事件是一个简单的数据结构GameEvent
  • 游戏消息是用来承载GameEvent,然后通过消息发送器发送出去的

Delta3D提供了一个简单的事件管理层,是一个单例类:dtCore::GameEventManager,通过它,可在任何地方查询任何游戏事件。

2、发送事件的过程:


  1. 创建事件并将其注册的事件管理器
m_toggleEngineEvent = new dtCore::GameEvent("ToggleEngine");
dtCore::GameEventManager::GetInstance().AddEvent(*m_toggleEngineEvent);

此时已经创建了游戏事件,然后再通过游戏消息进行承载后发送给其他角色或组件。
2. 创建游戏消息并发送
游戏消息是一种只有一个游戏事件作为参数的简单的消息类型。发送消息如下:

dtCore::RefPtr<dtGame::GameEventMessage> eventMsg;
this->GetGameManager()->GetMessageFactory().CreateMessage(dtGame::MessageType::INFO_GAME_EVENT, eventMsg);
eventMsg->SetGameEvent(*m_toggleEngineEvent);
this->GetGameManager()->SendMessage(*eventMsg);

以上代码通过游戏管理器的消息工厂创建了一个游戏消息,然后向该消息绑定事件,并通过游戏管理器发送该消息。

可激活体


1、简介

可以把可激活体想象成事件回调,它可以像属性一样被处理,即可激活体和方法调用的概念就像属性和数据成员的概念。
可激活体在角色代理被创建的时候创建,有名称,通过名称可对其进行访问。,以下是创建可激活体的示例:

// 这是系统认的可激活体,即父类定义的虚函数,子类继承可重写实现,用于系统消息的处理
void TankActor::ProcessMessage(const dtGame::Message& message)
{
	const dtGame::GameEventMessage& eventMsg = static_cast<const dtGame::GameEventMessage&>(message);
	if (eventMsg.GetGameEvent() != nullptr) 
	{
		auto eventName = eventMsg.GetGameEvent()->GetName();
		if (eventName == "ToggleEngine") // 启动引擎的消息
		{
			m_isEngineRunning = !m_isEngineRunning;
			m_dust->SetEnabled(m_isEngineRunning); // m_dust在头文件中定义的粒子系统,dtCore::RefPtr<dtCore::ParticleSystem>,在OnEnteredWorld中创建
			/*
					m_dust = new dtCore::ParticleSystem;
					m_dust->LoadFile("resources/Particles/dust.osg");
					m_dust->SetEnabled(false);
					AddChild(m_dust);
			*/
		}
		else if (eventName  == "SpeedBoost")
		{
			setVeLocity(m_veLocity + -5);
		}
	}
}

在这里插入图片描述

2、创建可激活体


为了创建自定义的可激活体,我们要创建自己的消息处理函数并把它封装到一个可激活体重,这个工作可以在游戏角色代理GameActorProxy的BuildInvokables()函数中完成,BuildInvokables在游戏角色被创建时调用

2.1 创建格式

void TankActorProxy::BuildInvokables()
{
	dtActors::GameMeshActor::BuildInvokables();
	TankActor* actor = this->GetDrawable<TankActor>();
	// 创建自定义的可激活体
	this->AddInvokable(*new dtGame::Invokable("MyInvokableName", 
dtUtil::MakeFunctor(&TankActor::MyInvokableFunc, actor))); // MyInvokableFunc为自定义响应函数,原型为:void MyInvokable(const dtGame::Message& message);
}

2.2 注册可激活体

我们需要将可激活体注册到游戏管理器GameManager,一般在Proxy的OnEnterWorld()中完成这项工作,该函数会在角色被添加到游戏管理器中的时候被调用,创建和注册自定义的可激活体需要使用相同的名称,如下:

// TankActorProxy.cpp
void TankActorProxy::OnEnteredWorld()
{
			// 注册自定义的可激活体
	RegisterForMessages(MyGameMessageType::MyInvokableMessageType, "MyInvokableName");
	// 注册消息有两种方式
    /*
        RegisterForMessageAboutOtherActor() 和 RegisterForMessageAboutSelf()
        第二种可以只处理自己Actor发送的消息,即别的Actor关闭了引擎,不会影响自己
        需要在创建事件时设置角色Id,eventMsg->setAboutActorId();
    */
	// 以下为系统认的可激活体注册方式
	// 注册游戏消息,会自动发送到ProcessMessage可激活体
	RegisterForMessages(dtGame::MessageType::INFO_GAME_EVENT);

	// 注册TickLocal、TickRemote,只能选其中之一
	if (!IsRemote())
    {
        RegisterForMessages(dtGame::MessageType::TICK_LOCAL, dtGame::GameActorProxy::TICK_LOCAL_INVOKABLE);
    }
    else
    {
        RegisterForMessages(dtGame::MessageType::TICK_REMOTE, dtGame::GameActorProxy::TICK_REMOTE_INVOKABLE);
    }
}

注,自定以的可激活体的消息类型如果是系统内置的类型,查询dtGame::MessageType类中的定义即可,如果要自定义消息类型,则继承dtGame::MessageType并实现,dtGame::MessageType提供了方便的宏定义来创建自定义消息类型,如下:

// MyGameMessageType.h
#ifndef MyGameMessageType_h__
#define MyGameMessageType_h__

#include <dtGame/messagetype.h>

DT_DECLARE_MESSAGE_TYPE_CLASS_BEGIN(MyGameMessageType, DELTA3D_EXPORT)
static const MyGameMessageType  MyInvokableMessageType;
DT_DECLARE_MESSAGE_TYPE_CLASS_END()

#endif // MyGameMessageType_h__
// MyGameMessageType.cpp
#include "MyGameMessageType.h"

DT_IMPLEMENT_MESSAGE_TYPE_CLASS(MyGameMessageType)
// 10000为自定义的消息类型标识,需要区分系统内置的消息类型标识
const MyGameMessageType MyGameMessageType::MyInvokableMessageType("InvokableMessageType","InvokableMessageType","InvokableMessageType Test.",10000, (dtGame::GameEventMessage*)(NULL));

只有在适当的地方,通过上述游戏事件中讲的事件发送后,就会触发绑定的MyInvokableFunc方法

3、添加输入组件

现在我们已经有了一些行为来处理游戏事件消息,但编译后,并不会做任何事情,因为,并没有任何地方发送这些事件。我们需要响应我们的按键,并做出响应。
这里就需要一个组件,并且游戏管理器最主要是和消息、角色及组件打交道的。
我们定义一个自定义的捕获键盘和鼠标事件的组件,它继承自dtGame::BaseInputComponent,它本身知道如何捕获键盘和鼠标事件,我们要做的就是重载鼠标键盘消息响应函数添加自定义的处理即可:

// MyInputComponent.h
#ifndef MyInputComponent_h__
#define MyInputComponent_h__

#include "delta3d.h"

class MyInputComponent : public dtGame::BaseInputComponent
{
public:
	// 传递name参数,在任何地方可通过dtGame::GameManager::GetInstance(name)->GetComponentByName获取组件
	MyInputComponent(const std::string& name);

	// 处理键盘事件
	virtual bool HandleKeypressed(const dtCore::Keyboard* keyboard, int key) override;

protected:
	~MyInputComponent();

private:
	dtCore::RefPtr<dtCore::GameEvent> m_toggleEngineEvent;
	dtCore::RefPtr<dtCore::GameEvent> m_speedBoost;
	dtCore::RefPtr<dtCore::GameEvent> m_testInvokable;

	void fireGameEvent(const dtCore::GameEvent& event);
};

#endif // MyInputComponent_h__
// MyInputComponent.cpp
#include "MyInputComponent.h"

MyInputComponent::MyInputComponent(const std::string& name)
	: dtGame::BaseInputComponent(name)
{
	// 创建事件
	m_toggleEngineEvent = new dtCore::GameEvent("TogglerEngine");
	dtCore::GameEventManager::GetInstance().AddEvent(*m_toggleEngineEvent);

	m_speedBoost = new dtCore::GameEvent("SpeedBoost");
	dtCore::GameEventManager::GetInstance().AddEvent(*m_speedBoost);

	m_testInvokable = new dtCore::GameEvent("MyInvokableName");
	dtCore::GameEventManager::GetInstance().AddEvent(*m_testInvokable);
}

MyInputComponent::~MyInputComponent()
{

}

void MyInputComponent::fireGameEvent(const dtCore::GameEvent& event)
{
	// 创建事件消息对象
	dtCore::RefPtr<dtGame::GameEventMessage> eventMsg;
	// 创建事件消息
	this->GetGameManager()->GetMessageFactory().CreateMessage(dtGame::MessageType::INFO_GAME_EVENT, eventMsg);
	// 设置事件到消息对象
	eventMsg->SetGameEvent(event);
	// 发送消息,编译发现winuser.h中有#define SendMessage,造成这里报错,如果你报错
	/* 建议使用取消winuser.h的宏定义
		#ifdef SendMessage
		#undef SendMessage
		#endif
	*/
	this->GetGameManager()->SendMessage(*eventMsg);
}

// 处理键盘按下事件
bool MyInputComponent::HandleKeypressed(const dtCore::Keyboard* keyboard, int key)
{
	bool handle = true; // false,表示事件继续传递

	switch(key)
	{
	// 这里使用的是底层3rd osg库的枚举,建议复制到dtCore命名空间中,便于记忆
	case osgGA::GUIEventAdapter::KEY_Return: 
		// 回车键加速
		fireGameEvent(*m_speedBoost);
		break;
	case dtCore::KEY_Space:
		// 空格启动或关闭引擎
		fireGameEvent(*m_toggleEngineEvent);
		break;
	case dtCore::KEY_F9:
		fireGameEvent(*m_testInvokable);
		break;
	default:
		handle = false;
		break;
	}

	if (!handle)
	{
		return BaseInputComponent::HandleKeypressed(keyboard, key);
	}

	return handle;
}

为了处理键盘事件,需要将自定义的组件添加到游戏管理器中,一般在GameEntryPoint类中的OnStartup函数添加,即本示例第8讲中的BlogTutorialGameEntryPoint类中添加以下代码

	// 添加输入处理组件
	MyInputComponent* inputComponent = new MyInputComponent("MyInputComponent");
	gamemanager.AddComponent(*inputComponent);

编译后,按下空格键,在TankActor的ProcessMessage中即可触发断点,并可看到界面上坦克下方出现灰尘效果

在这里插入图片描述


按下自定义的F9按钮,也会触发到我们自定义的可激活体:

在这里插入图片描述

相关文章

显卡天梯图2024最新版,显卡是电脑进行图形处理的重要设备,...
初始化电脑时出现问题怎么办,可以使用win系统的安装介质,连...
todesk远程开机怎么设置,两台电脑要在同一局域网内,然后需...
油猴谷歌插件怎么安装,可以通过谷歌应用商店进行安装,需要...
虚拟内存这个名词想必很多人都听说过,我们在使用电脑的时候...