Scriptable Control Object
by Michael Bacon · 12/07/2007 (1:26 pm) · 3 comments
Introduction:
I have found the need to process input on the server side for doing things other than controlling my player or camera. Whats more, I wanted to be able to easily script my input reactions. Specifically I wanted to be able to script my player (or AI player) to move in a certain fashion, on a grid for instance.
My initial thought was to just direct my player from the functions bound to the keys but I quickly realized that this would only tell client side objects to do anything. The information needed to be sent to the server.
The second thought was to send an event. Another moment of thought told me that this was a bad way to go. So many inputs, so many events, so little bandwidth...
I came to the only viable solution. Control information is already collected and transfered quite well by the Move Manager. It would be a waste of bandwidth to send more control information on top of this. Lets use the existing information, collect it on the server and process it with a script.
The final object is extremely simple and could be reduced more by getting rid of the two unneeded object references (player and camera). They are there so that you can easily derive new scriptable control objects from this code and use those objects in your code.
I am posting the code below as it is extremely short. It is composed from two individual files (a header and a source) and can easily be split back into its parts for easy derivation.
It is likely that you will never need to derive a new class from this code, most things can be done from script. If thats the case, just use as-is.
Remeber that since the script method is a datablock method you can easily change the datablock on your already created controller to modify it's input behavior to a new function.
USAGE:
CODE: ScriptInputController.cc
I have found the need to process input on the server side for doing things other than controlling my player or camera. Whats more, I wanted to be able to easily script my input reactions. Specifically I wanted to be able to script my player (or AI player) to move in a certain fashion, on a grid for instance.
My initial thought was to just direct my player from the functions bound to the keys but I quickly realized that this would only tell client side objects to do anything. The information needed to be sent to the server.
The second thought was to send an event. Another moment of thought told me that this was a bad way to go. So many inputs, so many events, so little bandwidth...
I came to the only viable solution. Control information is already collected and transfered quite well by the Move Manager. It would be a waste of bandwidth to send more control information on top of this. Lets use the existing information, collect it on the server and process it with a script.
The final object is extremely simple and could be reduced more by getting rid of the two unneeded object references (player and camera). They are there so that you can easily derive new scriptable control objects from this code and use those objects in your code.
I am posting the code below as it is extremely short. It is composed from two individual files (a header and a source) and can easily be split back into its parts for easy derivation.
It is likely that you will never need to derive a new class from this code, most things can be done from script. If thats the case, just use as-is.
Remeber that since the script method is a datablock method you can easily change the datablock on your already created controller to modify it's input behavior to a new function.
USAGE:
[b]datablock ShapeBaseData(MyControllerData)
{
// A datablock requires at least one field to be created.
// We could specify an actual shape for the controller.
shapeName=""; // I prefer to use none.
};[/b]
// example modifed from starter.fps
function GameConnection::onClientEnterGame(%this)
{
commandToClient(%this, 'SyncClock', $Sim::Time - $Game::StartTime);
// Create a new camera object.
%this.camera = new Camera() {
dataBlock = Observer;
};
MissionCleanup.add( %this.camera );
%this.camera.scopeToClient(%this);
// Setup game parameters, the onConnect method currently starts
// everyone with a 0 score.
%this.score = 0;
// Create a player object.
[b]// make sure you spawn an ai player[/b]
%this.spawnPlayer();
[b]// create OUR control object
%this.input = new ScriptInputController() {
datablock = MyControllerData;
// I like to give my controller references to my player and camera
// if they change, be sure to update or recreate your controller
player = %this.player;
camera = %this.camera;
};
%this.setControlObject(%this.input);[/b]
}
[b]function MyControllerData::processMove(%datablock, %this)
{
// %this contains these fields from the Move structure:
// mx - x movement -1 to 1
// my
// mz
// myaw - rotational values maybe in the -PI to PI range
// mpitch
// mroll
// mfreelook - boolean is freelook down
// mtrigger0 - boolean
// mtriggerX - all triggers are represented
if (!isObject(%this.player))
return; // we need a player
if (%this.my != 0) {
// do y movement
} else if (%this.mx != 0) {
// do x movement
}
}[/b]CODE: ScriptInputController.cc
//-----------------------------------------------------------------------------
// Copyright (C) Brian West, 2007
//-----------------------------------------------------------------------------
#include "dgl/dgl.h"
#include "game/gameConnection.h"
//- Class Defintion ----------------------------------------------------------
class ScriptInputController : public ShapeBase
{
typedef ShapeBase Parent;
protected:
// I have these objects referenced in code for convenience.
// If you don't plan to use them in any code routines you can
// ignore or remove them.
// Any variables set and used from script will still work (see
// documentation for dynamic variables).
ShapeBase* mPlayer;
ShapeBase* mCamera;
Move mLastMove;
public:
DECLARE_CONOBJECT( ScriptInputController );
ScriptInputController();
static void initPersistFields();
bool onAdd();
void onRemove();
void processTick(const Move* move);
};
//----------------------------------------------------------------------------
IMPLEMENT_CO_NETOBJECT_V1( ScriptInputController );
//----------------------------------------------------------------------------
ScriptInputController::ScriptInputController() {
mPlayer = NULL;
mCamera = NULL;
mLastMove = NullMove;
}
void ScriptInputController::initPersistFields() {
Parent::initPersistFields();
addField("player", TypeSimObjectPtr, Offset(mPlayer, ScriptInputController));
addField("camera", TypeSimObjectPtr, Offset(mCamera, ScriptInputController));
addField("mx", TypeF32, Offset(mLastMove.x, ScriptInputController));
addField("my", TypeF32, Offset(mLastMove.y, ScriptInputController));
addField("mz", TypeF32, Offset(mLastMove.z, ScriptInputController));
addField("myaw", TypeF32, Offset(mLastMove.yaw, ScriptInputController));
addField("mpitch", TypeF32, Offset(mLastMove.pitch, ScriptInputController));
addField("mroll", TypeF32, Offset(mLastMove.roll, ScriptInputController));
addField("mfreelook", TypeF32, Offset(mLastMove.freeLook, ScriptInputController));
char tname[32];
for (int i = 0; i < MaxTriggerKeys; i++) {
dSprintf(tname, sizeof(tname), "mtrigger%d", i);
addField(tname, TypeBool, Offset(mLastMove.trigger[i], ScriptInputController));
}
}
bool ScriptInputController::onAdd() {
if (Parent::onAdd())
return true;
scriptOnAdd();
return false;
}
void ScriptInputController::onRemove() {
Parent::onRemove();
scriptOnRemove();
}
void ScriptInputController::processTick(const Move* move) {
// Only process on the server
// You could change this if you want to react on the client also
// For our purposes we tell other objects to act and that is good enough
if (!isServerObject())
return;
// Send move to script
if (move) {
mLastMove = *move;
Con::executef(getDataBlock(), 3, "processMove", scriptThis());
} else {
mLastMove = NullMove;
}
}
#2
I almost went down the commandToServer route, cheers for saving me from that :)
01/12/2008 (10:25 am)
Nice job. I almost went down the commandToServer route, cheers for saving me from that :)
#3
06/19/2008 (12:59 pm)
How does this work exactly. Can I tell the object in question to move in the direction the player is facing. Which sections should I look at. or would this all need to be done in the script? 
Torque Owner Michael Bacon
Default Studio Name
Create in-game puzzles that allow you to control each piece with minimal effort. Sure you still gotta get them to move but now you have the right information in the right spot.
If you wanted to get elaborate you could modify this code to optionally send the move to the player as well! This means you script areas of your mission that modify control behavior or allow specific control actions without having to bind/rebind keys.
Thats enough, I'm giving away too many secrets!