DtGameBestPractices
From delta3d
Contents |
dtGame Best Practices
dtGame::GameActor
- If you have lots of the same type of GameActor that need to respond to a particular per frame Message, you can increase performance by using a dtGame::GMComponent.
For example, you have 1,000 VehicleActors that need to be Update() every frame. Instead of having each VehicleActor subscribe to the TICK message and calling their own Update() method, have a GMComponentderivative that listens for the TICK message and call Update() on all the VehicleActors in the scene.
Creating Remote Actors
This is how to create an Actor to mimic an external networked Actor
Make sure the GameManager has a DefaultMessageProcessor GMComponent added to it. Note: it is not added by default
dtGame::DefaultMessageProcessor *mp = new dtGame::DefaultMessageProcessor(); mGm->AddComponent( *mp, dtGame::GameManager::ComponentPriority::NORMAL);
Use the GameManager's MessageFactory to create a message:
dtCore::RefPtr<dtGame::ActorUpdateMessage> msg; mGM->GetMessageFactory().CreateMessage(dtGame::MessageType::INFO_ACTOR_CREATED, msg);
Then set the "source" to be a different MachineInfo than the machine the GameManager is running on. A new default MachineInfo seems to suffice:
dtGame::MachineInfo *mMachineInfo = new dtGame::MachineInfo(); msg->SetSource( *mMachineInfo );
Then set the Actor type category and name on the message. Here, "actorType" is the dtDAL::ActorType of the Actor we want to create. Could come from the dtActors/engineactorregistry.h file. Note: It seems the type has to be of the "GameActor" variety and not just a regular "Actor".
msg->SetActorTypeCategory( actorType.GetCategory() ); msg->SetActorTypeName( actorType.GetName() );
Now you can send the message:
mGM->SendMessage(*msg);
dtGame::DefaultMessageProcessor
- The job of the DefaultMessageProcessor (DMP) is to actually do something with the Messages that come from the GameManager, such as apply the ActorProperties to the GameActor. Contrary to the name, the DefaultMessageProcessor is not in the GameManager's system by default. Since the DMP is a GMComponent derivative, it will get all Messages that are sent. It is expected that:
- Users will add a DefaultMessageProcessor (or derivative) to the GameManager using dtGame::GameManager::AddComponent().
- Users will derive and overwrite the virtual methods of DefaultMessageProcessor, if need be.
Maps
Closing Maps
- dtGame::GameManager::CloseCurrentMap() will close the currently opened Map, but only after some frames have passed. This is to allow some Messages related to the state of the map to get sent to actors and GMComponents. Be aware that calling this method and quitting the application immediately will not actually close the map.
- dtDAL::Project::CloseMap() will close the map immediately, without notifying anything else (Actors, GMComponents, etc.). Use this method if you need to close the map at that point, such as shutting down the application. dtGame::GameEntryPoint::OnShutdown() is a good place to call CloseMap(). Note, this will not remove anything from the Scene. Use Scene::RemoveAllDrawables() to manually remove everything from the Scene.
Opening Maps
Note: Make sure a valid "Project Context" is set before attempting to load a Map, using something like this:
dtDAL::Project::GetInstance().SetContext("path/to/my/context");
- If you're using a dtABC::Application derivative without a dtGame::GameManager then use dtABC::BaseABC::LoadMap(). Note: If there is a GameManager present, the Actors loaded from the Map will not be present in the GameManager.
- If you have a dtGame::GameManager, then use dtGame::GameManager::ChangeMap(), or for multiple maps use dtGame::GameManager::ChangeMapSet(). It will take a few frames for the Map to be completely loaded. The GameManager will send out a MessageType::INFO_MAP_LOADED (dtGame/messagetype.h) message when the Map has been loaded.
std::vector<std::string> MapsVector;
MapsVector.push_back("PrototypesMap");
MapsVector.push_back("TerrainMap");
gameManager->ChangeMapSet(MapsVector);
Retrieving Open Map Names
To retrieve the names of the Map(s) that were just loaded, the dtGame::Message could be used:
#include <dtGame/basemessages.h>
void Example::ProcessMessage(const dtGame::Message &message)
{
if(message.GetMessageType() == dtGame::MessageType::INFO_MAP_LOADED)
{
const dtGame::MapMessage *msg = dynamic_cast<const dtGame::MapMessage*>(&message);
if (msg)
{
std::vector<std::string> mapNames;
msg->GetMapNames(mapNames);
}
}
}
Custom dtGame::Messages
A custom derived dtGame::Message is required if you need to have a Message contain data that isn't contained in any existing Message. Only derived dtGame::Messages can add dtGame::Parameters. Typically you add a public API to set and get the parameters of the message which makes it easier to use.
The first step is to create your derived Message. In this example, we add a new Parameter called "MyBool" which stores a boolean value. We also added some public API to get and set that boolean.
class MyMessage : public dtGame::Message
{
public:
MyMessage()
{
AddParameter(new dtGame::BooleanMessageParameter("MyBool"));
}
void Set(bool enable)
{
using namespace dtGame;
MessageParameter *param = GetParameter("MyBool");
if (param != NULL)
{
BooleanMessageParameter *boolParam = static_cast<BooleanMessageParameter*>(param);
boolParam->SetValue(enable);
}
}
bool Get() const
{
using namespace dtGame;
const MessageParameter *param = GetParameter("MyBool");
if (param != NULL)
{
const BooleanMessageParameter *boolParam = static_cast<const BooleanMessageParameter*>(param);
return boolParam->GetValue();
}
}
protected:
virtual ~MyMessage() {}
};
Then we need to derive a custom dtGame::MessageType that will correspond to our custom Message. Note we give it a unique ID number greater than USER_DEFINED_MESSAGE_TYPE.
//header file
class MyMessageType : public dtGame::MessageType
{
public:
static const MyMessageType MY_MESSAGE_TYPE;
private:
MyMessageType(const std::string &name,
const std::string &cat,
const std::string &desc,
const unsigned short int id):
dtGame::MessageType(name, cat, desc, id)
{
}
};
// .cpp file
const MyMessageType MyMessageType::MY_MESSAGE_TYPE("MY_MESSAGE_TYPE", "MINE",
"Custom message",
USER_DEFINED_MESSAGE_TYPE + 1);
Now we can register our custom Message and MessageType with the GameManager:
app.GetGameManager()->GetMessageFactory().RegisterMessageType<MyMessage>(MyMessageType::MY_MESSAGE_TYPE);
dtDAL::EnumActorProperty
Some notes about how to add a property to an ActorProxy that uses an enumeration.
First define the enumeration:
//enum header file .h
#include <dtUtil/enumeration.h>
class ACTOR_LIBRARY_EXPORT TeamRoleEnum: public dtUtil::Enumeration
{
DECLARE_ENUM(TeamRoleEnum); //helper macro defines some methods for us
public:
static TeamRoleEnum NONE;
static TeamRoleEnum PROJECT_LEADER;
static TeamRoleEnum LEAD_PROGRAMMER;
static TeamRoleEnum PROGRAMMER;
static TeamRoleEnum ARTIST;
protected:
TeamRoleEnum(const std::string &name):
dtUtil::Enumeration(name)
{
AddInstance(this); //method defined in the DECLARE_ENUM macro
}
};
//enum source file .cpp
IMPLEMENT_ENUM(TeamRoleEnum);
//instantiate the enumerated values
TeamRoleEnum TeamRoleEnum::NONE("NONE");
TeamRoleEnum TeamRoleEnum::PROJECT_LEADER("PROJECT_LEADER");
TeamRoleEnum TeamRoleEnum::LEAD_PROGRAMMER("LEAD_PROGRAMMER");
TeamRoleEnum TeamRoleEnum::PROGRAMMER("PROGRAMMER");
TeamRoleEnum TeamRoleEnum::ARTIST("ARTIST");
Then add the Property to the ActorProxy:
//actor proxy source file .cpp
void EmployerActorProxy::BuildPropertyMap()
{
Employer &entity = static_cast<Employer&>(GetGameActor());
// don't forget to call the base class
dtActors::GameMeshActorProxy::BuildPropertyMap();
AddProperty(new dtDAL::EnumActorProperty<TeamRoleEnum>(
"TeamRole", "Team Role",
dtDAL::MakeFunctor(entity, &Employee::SetTeamRole),
dtDAL::MakeFunctorRet(entity, &Employee::GetTeamRole),
"Set the role this Employee has in the team",
"Team"));
}
..and add the setter/getter methods and a member to the actor itself:
//actor header file .h
class Employee : public dtActors::GameMeshActor
{
public:
...
void SetTeamRole( TeamRoleEnum &role ) { mTeamRole = &role;}
TeamRoleEnum& GetTeamRole() { return *mTeamRole; }
...
private:
TeamRoleEnum *mTeamRole; //member to store the current role
...
};
// actor source file .cpp
Employee::Employee():
mTeamRole( &TeamRoleEnum::NONE ) // initialize the role member
{
}
...
Now your newly defined Enumeration should show up as a Property in STAGE, with selectable enumerated values.
