前面的教程我们创建了一个坦克角色,并通过场景编辑器STAGE,将它加载到场景中,并添加了自带的地形,使坦克在贴近地面。
本节将设计让坦克响应Enter按下事件来启动引擎,并按向上的按键让坦克移动起来,即为当我们按下Enter键的时候发送一个消息,然后让坦克接受消息并根据消息控制引擎的开启和关闭,然后按下向上键,并在tick事件中让坦克向前贴地移动。
游戏事件
1、简介
Delta3D事件本身只是简单的字符串标识,当某个事件发生后以消息的形式发出,每一个游戏事件都代表一个单独的行为,如发现了苹果,解救了人质等,我们常常混淆了游戏事件和游戏消息。
- 游戏事件是一个简单的数据结构GameEvent
- 游戏消息是用来承载GameEvent,然后通过消息发送器发送出去的
Delta3D提供了一个简单的事件管理层,是一个单例类:dtCore::GameEventManager,通过它,可在任何地方查询任何游戏事件。
2、发送事件的过程:
- 创建事件并将其注册的事件管理器
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按钮,也会触发到我们自定义的可激活体:
原文地址:https://www.jb51.cc/wenti/3280056.html
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。