Push Button Controls with Geometry, Part I
From C4 Engine Wiki
Contents |
Overview
The PushButtonController class manages a push button node subtree. It can be attached directly to a geometry node, or to a group node which contains the geometry node as well as two position marker nodes which determine the "up" position of the button, as well as the movement delta for the button to travel along, when it is clicked into its "down" position. Here are the controller settings for the PushButtonController:
For now the PushButtonController is used in tandem with a "Manipulator weapon", which allows the user to click a push button remotely, without being within close proximity of the button. Please see the Future Enhancements section of Part II of this article for details about a future, more traditional user interface that only allows clicking buttons when the player is within close proximity.
Patching the Source Code
The following files are modified for this tutorial:
- MGControllers.cpp
- MGControllers.h
- MGFighter.cpp
- MGFighter.h
- MGGame.cpp
- MGGame.h
- MGInput.cpp
- MGInput.h
- MGMultiplayer.cpp
- MGMultiplayer.h
- MGWeapons.cpp
- MGWeapons.h
- C4NodeInfo.cpp
- Game.txt
- Input.txt
- Engine.cfg
Many of the code snippets in this article have comments that are tagged with the name of the original author of this article, "Huidafa". Please feel free to remove those tags or change them to something else. The original author of this article tags his custom code changes to make it easy to find them in older builds of C4 Engine when he integrates his changes into new builds of C4 Engine as they are released by Terathon Software.
This article (Part I) covers the source code patches for MGControllers.cpp and MGControllers.h. Please see Push Button Controls with Geometry, Part II for the second part of this tutorial which covers patching the remaining source code files.
MGControllers.cpp
In CollectableController::Activate, add the following new case under the section for case kEntityProtonCannon:
// START: [Huidafa]
case kEntityManipulator:
{
player->AcquireWeapon(kWeaponManipulator);
break;
}
// END: [Huidafa]
Now, at the very bottom of MGControllers.cpp, add the following long section of new code:
// START: [Huidafa]
//------------------------------------------------------------------------
PushButtonController::PushButtonController()
:
Controller(kControllerPushButton),
pushButtonState(kPushButtonStateUp),
pushButtonBehavior(kPushButtonBehaviorSpring),
upColor(K::black),
downColor(K::yellow),
disabledColor(K::gray_50),
rolloverUpColor(K::blue),
rolloverDownColor(K::white),
soundRange(100.0f),
upSpeedMetersPerSec(1.0f),
downSpeedMetersPerSec(1.0f),
downPositionOvershootPercent(0),
isBlinking(false),
blinkRate(1),
blinkColor(K::white),
blinkPeriod(10),
emissionAttribute(nullptr),
geometry(nullptr),
isDownMovementAnimated(false),
isUpMovementAnimated(false),
movementDelta(0, 0, 0),
movementDistance(0.0f),
downMovementDuration(0.0f),
upMovementDuration(0.0f),
lastClickTime(TheTimeMgr->GetAbsoluteTime()),
nextBlinkTime(TheTimeMgr->GetAbsoluteTime()),
isBlinked(false),
centerPositionMarker(nullptr)
{
upSoundName[0] = 0;
downSoundName[0] = 0;
disabledSoundName[0] = 0;
rolloverUpSoundName[0] = 0;
rolloverDownSoundName[0] = 0;
}
PushButtonController::PushButtonController(
const PushButtonController& pushButtonController,
Node *target)
:
Controller(kControllerPushButton),
pushButtonState(pushButtonController.pushButtonState),
pushButtonBehavior(pushButtonController.pushButtonBehavior),
upColor(pushButtonController.upColor),
downColor(pushButtonController.downColor),
disabledColor(pushButtonController.disabledColor),
rolloverUpColor(pushButtonController.rolloverUpColor),
rolloverDownColor(pushButtonController.rolloverDownColor),
soundRange(pushButtonController.soundRange),
upSpeedMetersPerSec(pushButtonController.upSpeedMetersPerSec),
downSpeedMetersPerSec(pushButtonController.downSpeedMetersPerSec),
downPositionOvershootPercent(pushButtonController.downPositionOvershootPercent),
isBlinking(pushButtonController.isBlinking),
blinkRate(pushButtonController.blinkRate),
blinkColor(pushButtonController.blinkColor),
blinkPeriod(pushButtonController.blinkPeriod),
emissionAttribute(nullptr),
geometry(pushButtonController.geometry),
isDownMovementAnimated(pushButtonController.isDownMovementAnimated),
isUpMovementAnimated(pushButtonController.isUpMovementAnimated),
upNodePosition(pushButtonController.upNodePosition),
downNodePosition(pushButtonController.downNodePosition),
movementDelta(pushButtonController.movementDelta),
movementDistance(pushButtonController.movementDistance),
downMovementDuration(pushButtonController.downMovementDuration),
upMovementDuration(pushButtonController.upMovementDuration),
lastClickTime(pushButtonController.lastClickTime),
upSoundName(pushButtonController.upSoundName),
downSoundName(pushButtonController.downSoundName),
disabledSoundName(pushButtonController.disabledSoundName),
rolloverUpSoundName(pushButtonController.rolloverUpSoundName),
rolloverDownSoundName(pushButtonController.rolloverDownSoundName),
nextBlinkTime(pushButtonController.nextBlinkTime),
isBlinked(false),
centerPositionMarker(pushButtonController.centerPositionMarker)
{
}
Controller* PushButtonController::Replicate(Node *target) const
{
return (new PushButtonController(*this, target));
}
bool PushButtonController::UnpackChunk(
const ChunkHeader *chunkHeader,
Unpacker& data,
unsigned long unpackFlags)
{
bool unpackedChunk = false;
switch (chunkHeader->chunkType)
{
case 'PBST':
data >> pushButtonState;
unpackedChunk = true;
break;
case 'PBBE':
data >> pushButtonBehavior;
unpackedChunk = true;
break;
case 'PBCU':
data >> upColor;
unpackedChunk = true;
break;
case 'PBCD':
data >> downColor;
unpackedChunk = true;
break;
case 'PBCB':
data >> disabledColor;
unpackedChunk = true;
break;
case 'PBCR':
data >> rolloverUpColor;
unpackedChunk = true;
break;
case 'PBCS':
data >> rolloverDownColor;
unpackedChunk = true;
break;
case 'PBSG':
data >> soundRange;
break;
case 'PBSU':
data >> upSoundName;
unpackedChunk = true;
break;
case 'PBSD':
data >> downSoundName;
unpackedChunk = true;
break;
case 'PBSB':
data >> disabledSoundName;
unpackedChunk = true;
break;
case 'PBSR':
data >> rolloverUpSoundName;
unpackedChunk = true;
break;
case 'PBSS':
data >> rolloverDownSoundName;
unpackedChunk = true;
break;
case 'PBUS':
data >> upSpeedMetersPerSec;
unpackedChunk = true;
break;
case 'PBDS':
data >> downSpeedMetersPerSec;
unpackedChunk = true;
break;
case 'PBDP':
data >> downPositionOvershootPercent;
unpackedChunk = true;
break;
case 'PBBS':
data >> isBlinking;
unpackedChunk = true;
break;
case 'PBBR':
data >> blinkRate;
unpackedChunk = true;
break;
case 'PBBC':
data >> blinkColor;
unpackedChunk = true;
break;
case 'PBBP':
data >> blinkPeriod;
unpackedChunk = true;
break;
}
return unpackedChunk;
}
void PushButtonController::UpdateBlinkAppearance(
unsigned long worldTime)
{
if (isBlinking && (blinkRate > 0.0f))
{
if (worldTime > nextBlinkTime)
{
unsigned long blinkDelta =
static_cast<unsigned long>(1.0f / blinkRate * 1000.0f);
nextBlinkTime = worldTime + blinkDelta;
// [TODO] For efficiency, use preset attribute list.
emissionAttribute = new EmissionAttribute(blinkColor);
attributeList.Purge();
attributeList.Append(emissionAttribute);
// [TODO] Support group nodes.
geometry->SetMaterialAttributeList(&attributeList);
geometry->InvalidateShaderData();
isBlinked = true;
}
else if (isBlinked)
{
unsigned long nextBlinkOffTime =
nextBlinkTime - ((100 - blinkPeriod) / 100.0f * blinkRate * 1000.0f);
if (worldTime > nextBlinkOffTime)
{
UpdateAppearance(pushButtonState);
isBlinked = false;
}
}
}
}
void PushButtonController::UpdateAppearance(
unsigned long inPushButtonState)
{
// [TODO] For efficiency, use cached attribute lists.
// [TODO] Move this section into a function.
emissionAttribute = nullptr;
ColorRGBA emissionColor(upColor);
switch (inPushButtonState)
{
case kPushButtonStateUp:
case kPushButtonStateMovingUp:
emissionColor = upColor;
break;
case kPushButtonStateDown:
case kPushButtonStateMovingDown:
emissionColor = downColor;
break;
case kPushButtonStateDisabled:
emissionColor = disabledColor;
break;
case kPushButtonStateRolloverUp:
emissionColor = rolloverUpColor;
break;
case kPushButtonStateRolloverDown:
emissionColor = rolloverDownColor;
break;
}
emissionAttribute = new EmissionAttribute(emissionColor);
attributeList.Purge();
attributeList.Append(emissionAttribute);
// [TODO] Support group nodes.
geometry->SetMaterialAttributeList(&attributeList);
geometry->InvalidateShaderData();
}
void PushButtonController::UpdatePosition(float worldTime)
{
float elapsedTime = worldTime - lastClickTime;
if (pushButtonState == kPushButtonStateMovingDown)
{
// Comment out this block for all sorts of fun and games :)
if (elapsedTime >= downMovementDuration)
{
GetTargetNode()->SetNodePosition(downNodePosition);
pushButtonState = kPushButtonStateDown;
if (pushButtonBehavior == kPushButtonBehaviorSpring)
{
pushButtonState = kPushButtonStateMovingUp;
}
}
else
{
float timeInterval = (worldTime - lastClickTime) / downMovementDuration;
GetTargetNode()->SetNodePosition(
upNodePosition + movementDelta * timeInterval);
}
GetTargetNode()->Invalidate();
}
else if (pushButtonState == kPushButtonStateMovingUp)
{
if (elapsedTime >= upMovementDuration)
{
GetTargetNode()->SetNodePosition(upNodePosition);
pushButtonState = kPushButtonStateUp;
}
else
{
float timeInterval = (worldTime - lastClickTime) / upMovementDuration;
GetTargetNode()->SetNodePosition(
downNodePosition - movementDelta * timeInterval);
}
GetTargetNode()->Invalidate();
}
}
void PushButtonController::PlaySound(unsigned long inPushButtonState)
{
OmniSource* buttonSound(nullptr);
switch (pushButtonState)
{
case kPushButtonStateUp:
if (upSoundName.Length() > 0)
{
buttonSound = new OmniSource(upSoundName, soundRange);
}
break;
case kPushButtonStateDown:
if (downSoundName.Length() > 0)
{
buttonSound = new OmniSource(downSoundName, soundRange);
}
break;
case kPushButtonStateDisabled:
if (disabledSoundName.Length() > 0)
{
buttonSound = new OmniSource(disabledSoundName, soundRange);
}
break;
case kPushButtonStateRolloverUp:
if (rolloverUpSoundName.Length() > 0)
{
buttonSound = new OmniSource(rolloverUpSoundName, soundRange);
}
break;
case kPushButtonStateRolloverDown:
if (rolloverDownSoundName.Length() > 0)
{
buttonSound = new OmniSource(rolloverDownSoundName, soundRange);
}
break;
}
// Play the button sound.
if (buttonSound != nullptr) // Check for null in case of an unknown state.
{
buttonSound->SetNodePosition(GetTargetNode()->GetNodePosition());
GetTargetNode()->AddNewSubnode(buttonSound);
}
}
static Controller* FindConnectedController(Node* inTargetNode, Type& outConnectorKey)
{
Controller* controller(nullptr);
outConnectorKey = kNodeGroup;
long connectorCount = inTargetNode->GetConnectorCount();
for (long connectorIndex = 0; connectorIndex < connectorCount; ++connectorIndex)
{
Connector* connector(inTargetNode->GetConnector(connectorIndex));
outConnectorKey = connector->GetConnectorKey();
Node* connectedNode = inTargetNode->GetConnectedNode(outConnectorKey);
if (connectedNode != nullptr)
{
controller = connectedNode->GetController();
if (controller != nullptr)
{
break;
}
}
}
return controller;
}
static Controller* FindSuperConnectedController(Node* inTargetNode, Type inTargetKey)
{
Controller* controller(nullptr);
Node* superNode(inTargetNode->GetOwningGroup());
if (superNode != nullptr)
{
Node* testNode = superNode->GetFirstSubnode();
do
{
if (testNode->GetNodeType() == kNodeMarker)
{
Type testKey(kNodeGroup);
Controller* testController = FindConnectedController(testNode, testKey);
if (testKey == inTargetKey)
{
controller = testNode->GetController();
break;
}
}
testNode = superNode->GetNextLevelNode(testNode);
} while (testNode != nullptr);
}
return controller;
}
Controller* PushButtonController::FindMarkerController(Node* inTargetNode)
{
Controller* controller(nullptr);
ConnectorKey connectorKey(kNodeGroup);
// First look for a connector attached directly to the target node,
// which points at a node with an attached controller.
controller = FindConnectedController(inTargetNode, connectorKey);
// No controller found?
if (controller == nullptr)
{
// If we don't even have a connector key, try to grab one off of
// the center position marker, and check if the designer stuffed
// a controller in the node connected to the marker.
if (connectorKey == kNodeGroup && centerPositionMarker != nullptr)
{
controller = FindConnectedController(
centerPositionMarker, connectorKey);
}
// If we found a connectorKey, but still no controller, we're
// probably in a referenced world, so walk up the tree looking
// for a marker with a connector in the super nodes.
if (controller == nullptr && connectorKey != kNodeGroup)
{
controller = FindSuperConnectedController(
inTargetNode, connectorKey);
// Still no controller.
// Make one more attempt at next super-level up.
if (controller == nullptr)
{
controller = FindSuperConnectedController(
inTargetNode->GetOwningGroup(), connectorKey);
}
}
}
return controller;
}
PushButtonController::~PushButtonController()
{
}
bool PushButtonController::ValidNode(const Node *node)
{
// Can be attached directly to a geometry, or to an entire group.
return (node->GetNodeType() == kNodeGeometry) ||
(node->GetNodeType() == kNodeGroup);
}
Function *PushButtonController::ConstructFunction(FunctionType type)
{
switch (type)
{
case kFunctionPushButtonSync:
return (new PushButtonSyncFunction);
case kFunctionPushButtonClick:
return (new PushButtonClickFunction);
// [TODO] Add Blink enable and rate functions.
}
return (nullptr);
}
void PushButtonController::RegisterFunctions(ControllerRegistration *registration)
{
const StringTable *table = TheGame->GetStringTable();
// Register Sync function.
static FunctionRegistration pushButtonSyncRegistration(
kFunctionPushButtonSync, table->GetString(StringID('CTRL', 'PUSH', 'PBSC')));
registration->RegisterFunction(&pushButtonSyncRegistration);
// Register Click function.
static FunctionRegistration pushButtonClickRegistration(
kFunctionPushButtonClick, table->GetString(StringID('CTRL', 'PUSH', 'PBCK')));
registration->RegisterFunction(&pushButtonClickRegistration);
// [TODO] Add Blink enable and rate functions.
}
unsigned long PushButtonController::GetPackSize(unsigned long packFlags) const
{
unsigned long packSize =
Controller::GetPackSize(packFlags) +
// setting count + one for the chunk terminator
sizeof(ChunkHeader) * (kSettingPushButtonCount + 1) +
36 + // six longs + three floats = 9; 4 bytes ea
sizeof(ColorRGBA) * 6 + // six emission colors
Packer::GetStringSize(upSoundName) +
Packer::GetStringSize(downSoundName) +
Packer::GetStringSize(disabledSoundName) +
Packer::GetStringSize(rolloverUpSoundName) +
Packer::GetStringSize(rolloverDownSoundName);
return packSize;
}
void PushButtonController::Pack(Packer& data, unsigned long packFlags) const
{
Controller::Pack(data, packFlags);
data << ChunkHeader('PBST', sizeof(pushButtonState));
data << pushButtonState;
data << ChunkHeader('PBBE', sizeof(pushButtonBehavior));
data << pushButtonBehavior;
data << ChunkHeader('PBCU', sizeof(upColor));
data << upColor;
data << ChunkHeader('PBCD', sizeof(downColor));
data << downColor;
data << ChunkHeader('PBCB', sizeof(disabledColor));
data << disabledColor;
data << ChunkHeader('PBCR', sizeof(rolloverUpColor));
data << rolloverUpColor;
data << ChunkHeader('PBCS', sizeof(rolloverDownColor));
data << rolloverDownColor;
data << ChunkHeader('PBSG', sizeof(soundRange));
data << soundRange;
data << ChunkHeader('PBSU', Packer::GetStringSize(upSoundName));
data << upSoundName;
data << ChunkHeader('PBSD', Packer::GetStringSize(downSoundName));
data << downSoundName;
data << ChunkHeader('PBSB', Packer::GetStringSize(disabledSoundName));
data << disabledSoundName;
data << ChunkHeader('PBSR', Packer::GetStringSize(rolloverUpSoundName));
data << rolloverUpSoundName;
data << ChunkHeader('PBSS', Packer::GetStringSize(rolloverDownSoundName));
data << rolloverDownSoundName;
data << ChunkHeader('PBUS', sizeof(upSpeedMetersPerSec));
data << upSpeedMetersPerSec;
data << ChunkHeader('PBDS', sizeof(downSpeedMetersPerSec));
data << downSpeedMetersPerSec;
data << ChunkHeader('PBDP', sizeof(downPositionOvershootPercent));
data << downPositionOvershootPercent;
data << ChunkHeader('PBBS', 4);
data << isBlinking;
data << ChunkHeader('PBBR', sizeof(blinkRate));
data << blinkRate;
data << ChunkHeader('PBBC', sizeof(blinkColor));
data << blinkColor;
data << ChunkHeader('PBBP', sizeof(blinkPeriod));
data << blinkPeriod;
data << TerminatorChunk;
}
void PushButtonController::Unpack(Unpacker& data, unsigned long unpackFlags)
{
Controller::Unpack(data, unpackFlags);
UnpackChunkList<PushButtonController>(
data, unpackFlags, &PushButtonController::UnpackChunk);
}
long PushButtonController::GetSettingCount(void) const
{
return kSettingPushButtonCount;
}
Setting* PushButtonController::GetSetting(long index) const
{
const StringTable *table = TheGame->GetStringTable();
Setting* setting = nullptr;
switch (index)
{
case kSettingPushButtonState:
{
long selectedIndex = 0;
switch (pushButtonState)
{
case kPushButtonStateUp:
selectedIndex = 0;
break;
case kPushButtonStateDown:
selectedIndex = 1;
break;
case kPushButtonStateDisabled:
selectedIndex = 2;
break;
case kPushButtonStateRolloverUp:
selectedIndex = 3;
break;
case kPushButtonStateRolloverDown:
selectedIndex = 4;
break;
}
const char *title = table->GetString(StringID('CTRL', 'PUSH', 'PBST'));
MenuSetting *menu = new MenuSetting('PBST', selectedIndex, title, 5);
menu->SetMenuItemString(
0, table->GetString(StringID('CTRL', 'PUSH', 'PBST', 'PBSU')));
menu->SetMenuItemString(
1, table->GetString(StringID('CTRL', 'PUSH', 'PBST', 'PBSD')));
menu->SetMenuItemString(
2, table->GetString(StringID('CTRL', 'PUSH', 'PBST', 'PBSB')));
menu->SetMenuItemString(
3, table->GetString(StringID('CTRL', 'PUSH', 'PBST', 'PBRU')));
menu->SetMenuItemString(
4, table->GetString(StringID('CTRL', 'PUSH', 'PBST', 'PBRD')));
setting = menu;
break;
}
case kSettingPushButtonBehavior:
{
long selectedIndex = 2;
switch (pushButtonBehavior)
{
case kPushButtonBehaviorSpring:
selectedIndex = 0;
break;
case kPushButtonBehaviorSticky:
selectedIndex = 1;
break;
case kPushButtonBehaviorToggle:
selectedIndex = 2;
break;
}
const char *title = table->GetString(StringID('CTRL', 'PUSH', 'PBBE'));
MenuSetting *menu = new MenuSetting('PBBE', selectedIndex, title, 3);
menu->SetMenuItemString(
0, table->GetString(StringID('CTRL', 'PUSH', 'PBBE', 'SPRG')));
menu->SetMenuItemString(
1, table->GetString(StringID('CTRL', 'PUSH', 'PBBE', 'STKY')));
menu->SetMenuItemString(
2, table->GetString(StringID('CTRL', 'PUSH', 'PBBE', 'TGGL')));
setting = menu;
break;
}
case kSettingPushButtonUpColor:
{
const char *title = table->GetString(StringID('CTRL', 'PUSH', 'PBCU'));
ColorSetting *colorPicker =
new ColorSetting('PBCU', upColor, title, title);
setting = colorPicker;
break;
}
case kSettingPushButtonDownColor:
{
const char *title = table->GetString(StringID('CTRL', 'PUSH', 'PBCD'));
ColorSetting *colorPicker =
new ColorSetting('PBCD', downColor, title, title);
setting = colorPicker;
break;
}
case kSettingPushButtonDisabledColor:
{
const char *title = table->GetString(StringID('CTRL', 'PUSH', 'PBCB'));
ColorSetting *colorPicker =
new ColorSetting('PBCB', disabledColor, title, title);
setting = colorPicker;
break;
}
case kSettingPushButtonRolloverUpColor:
{
const char *title = table->GetString(StringID('CTRL', 'PUSH', 'PBCR'));
ColorSetting *colorPicker =
new ColorSetting('PBCR', rolloverUpColor, title, title);
setting = colorPicker;
break;
}
case kSettingPushButtonRolloverDownColor:
{
const char *title = table->GetString(StringID('CTRL', 'PUSH', 'PBCS'));
ColorSetting *colorPicker =
new ColorSetting('PBCS', rolloverDownColor, title, title);
setting = colorPicker;
break;
}
case kSettingPushButtonSoundRange:
{
const char *title = table->GetString(StringID('CTRL', 'PUSH', 'PBSG'));
IntegerSetting *integerSetting = new IntegerSetting(
'PBSG',
soundRange,
title,
1, 100, 1); // min, max, step
setting = integerSetting;
break;
}
case kSettingPushButtonUpSoundName:
{
const char *title = table->GetString(StringID('CTRL', 'PUSH', 'PBSU'));
ResourceSetting *resourceSetting = new ResourceSetting(
'PBSU', upSoundName, title, title, SoundResource::GetDescriptor());
setting = resourceSetting;
break;
}
case kSettingPushButtonDownSoundName:
{
const char *title = table->GetString(StringID('CTRL', 'PUSH', 'PBSD'));
ResourceSetting *resourceSetting = new ResourceSetting(
'PBSD', downSoundName, title, title, SoundResource::GetDescriptor());
setting = resourceSetting;
break;
}
case kSettingPushButtonDisabledSoundName:
{
const char *title = table->GetString(StringID('CTRL', 'PUSH', 'PBSB'));
ResourceSetting *resourceSetting = new ResourceSetting(
'PBSB', disabledSoundName, title, title, SoundResource::GetDescriptor());
setting = resourceSetting;
break;
}
case kSettingPushButtonRolloverUpSoundName:
{
const char *title = table->GetString(StringID('CTRL', 'PUSH', 'PBSR'));
ResourceSetting *resourceSetting = new ResourceSetting(
'PBSR', rolloverUpSoundName, title, title, SoundResource::GetDescriptor());
setting = resourceSetting;
break;
}
case kSettingPushButtonRolloverDownSoundName:
{
const char *title = table->GetString(StringID('CTRL', 'PUSH', 'PBSS'));
ResourceSetting *resourceSetting = new ResourceSetting(
'PBSS', rolloverDownSoundName, title, title, SoundResource::GetDescriptor());
setting = resourceSetting;
break;
}
case kSettingPushButtonUpSpeedMetersPerSec:
{
const char *title = table->GetString(StringID('CTRL', 'PUSH', 'PBUS'));
TextSetting *floatTextSetting = new TextSetting(
'PBUS',
Text::FloatToString((float)upSpeedMetersPerSec), title,
64.0F, 7, // width, maximum length
&EditableTextElement::FloatNumberKeyFilter);
setting = floatTextSetting;
break;
}
case kSettingPushButtonDownSpeedMetersPerSec:
{
const char *title = table->GetString(StringID('CTRL', 'PUSH', 'PBDS'));
TextSetting *floatTextSetting = new TextSetting(
'PBDS',
Text::FloatToString((float)downSpeedMetersPerSec), title,
64.0F, 7, // width, maximum length
&EditableTextElement::FloatNumberKeyFilter);
setting = floatTextSetting;
break;
}
case kSettingPushButtonDownPositionOvershootPercent:
{
const char *title = table->GetString(StringID('CTRL', 'PUSH', 'PBDP'));
IntegerSetting *integerSetting = new IntegerSetting(
'PBDP',
downPositionOvershootPercent,
title,
0, 100, 1); // min, max, step
setting = integerSetting;
break;
}
case kSettingPushButtonIsBlinking:
{
const char *title = table->GetString(StringID('CTRL', 'PUSH', 'PBBS'));
BooleanSetting *booleanSetting =
new BooleanSetting('PBBS', isBlinking, title);
setting = booleanSetting;
break;
}
case kSettingPushButtonBlinkRate:
{
const char *title = table->GetString(StringID('CTRL', 'PUSH', 'PBBR'));
FloatSetting *floatSetting = new FloatSetting(
'PBBR',
blinkRate,
title,
1.0f / 10.0f, 10, 0.1f); // min, max, step
setting = floatSetting;
break;
}
case kSettingPushButtonBlinkColor:
{
const char *title = table->GetString(StringID('CTRL', 'PUSH', 'PBBC'));
ColorSetting *colorPicker =
new ColorSetting('PBBC', blinkColor, title, title);
setting = colorPicker;
break;
}
case kSettingPushButtonBlinkPeriod:
{
const char *title = table->GetString(StringID('CTRL', 'PUSH', 'PBBP'));
IntegerSetting *integerSetting = new IntegerSetting(
'PBBP',
blinkPeriod,
title,
0, 100, 1); // min, max, step
setting = integerSetting;
break;
}
}
return setting;
}
void PushButtonController::SetSetting(const Setting *setting)
{
switch (setting->GetSettingIdentifier())
{
case 'PBST': // Push Button State
{
long selection =
static_cast<const MenuSetting *>(setting)->GetMenuSelection();
switch (selection)
{
case 0:
pushButtonState = kPushButtonStateUp;
break;
case 1:
pushButtonState = kPushButtonStateDown;
break;
case 2:
pushButtonState = kPushButtonStateDisabled;
break;
case 3:
pushButtonState = kPushButtonStateRolloverUp;
break;
case 4:
pushButtonState = kPushButtonStateRolloverDown;
break;
}
break;
}
case 'PBBE': // Push Button Behavior
{
long selection =
static_cast<const MenuSetting *>(setting)->GetMenuSelection();
switch (selection)
{
case 0:
pushButtonBehavior = kPushButtonBehaviorSpring;
break;
case 1:
pushButtonBehavior = kPushButtonBehaviorSticky;
break;
case 2:
pushButtonBehavior = kPushButtonBehaviorToggle;
break;
}
break;
}
case 'PBCU': // Up color
{
upColor =
static_cast<const ColorSetting *>(setting)->GetColor();
break;
}
case 'PBCD': // Down color
{
downColor =
static_cast<const ColorSetting *>(setting)->GetColor();
break;
}
case 'PBCB': // Disabled color
{
disabledColor =
static_cast<const ColorSetting *>(setting)->GetColor();
break;
}
case 'PBCR': // Rollover up color
{
rolloverUpColor =
static_cast<const ColorSetting *>(setting)->GetColor();
break;
}
case 'PBCS': // Rollover down color
{
rolloverDownColor =
static_cast<const ColorSetting *>(setting)->GetColor();
break;
}
case 'PBSG': // Sound range
{
soundRange =
static_cast<const IntegerSetting *>(setting)->GetIntegerValue();
break;
}
case 'PBSU': // Up sound
{
upSoundName =
static_cast<const ResourceSetting *>(setting)->GetResourceName();
break;
}
case 'PBSD': // Down sound
{
downSoundName =
static_cast<const ResourceSetting *>(setting)->GetResourceName();
break;
}
case 'PBSB': // Disabled sound
{
disabledSoundName =
static_cast<const ResourceSetting *>(setting)->GetResourceName();
break;
}
case 'PBSR': // Rollover up sound
{
rolloverUpSoundName =
static_cast<const ResourceSetting *>(setting)->GetResourceName();
break;
}
case 'PBSS': // Rollover down sound
{
rolloverDownSoundName =
static_cast<const ResourceSetting *>(setting)->GetResourceName();
break;
}
case 'PBUS': // Up speed
{
const char *text = static_cast<const TextSetting *>(setting)->GetText();
upSpeedMetersPerSec = Text::StringToFloat(text);
break;
}
case 'PBDS': // Down speed
{
const char *text = static_cast<const TextSetting *>(setting)->GetText();
downSpeedMetersPerSec = Text::StringToFloat(text);
break;
}
case 'PBDP': // Down position overshoot
{
downPositionOvershootPercent =
static_cast<const IntegerSetting *>(setting)->GetIntegerValue();
break;
}
case 'PBBS': // Is blinking
{
isBlinking =
static_cast<const BooleanSetting *>(setting)->GetBooleanValue();
break;
}
case 'PBBR': // Blink rate
{
blinkRate =
static_cast<const FloatSetting *>(setting)->GetFloatValue();
break;
}
case 'PBBC': // Blink color
{
blinkColor =
static_cast<const ColorSetting *>(setting)->GetColor();
break;
}
case 'PBBP': // Blink period
{
blinkPeriod =
static_cast<const IntegerSetting *>(setting)->GetIntegerValue();
break;
}
}
}
ControllerMessage *PushButtonController::ConstructMessage(
ControllerMessageType type) const
{
switch (type)
{
case kControllerMessagePushButtonSync:
return new PushButtonSyncMessage(GetControllerIndex(), pushButtonState);
case kControllerMessagePushButtonClick:
return new PushButtonClickMessage(GetControllerIndex());
}
return nullptr;
}
void PushButtonController::ReceiveMessage(
const ControllerMessage *message)
{
switch (message->GetControllerMessageType())
{
case kControllerMessagePushButtonSync:
{
const PushButtonSyncMessage *m =
static_cast<const PushButtonSyncMessage *>(message);
pushButtonState = m->GetPushButtonState(); // synchronize
if ((pushButtonState == kPushButtonStateDown) ||
(pushButtonState == kPushButtonStateMovingDown) ||
(pushButtonState == kPushButtonStateRolloverDown))
{
GetTargetNode()->SetNodePosition(
GetTargetNode()->GetNodePosition() + movementDelta);
}
UpdateAppearance(pushButtonState);
break;
}
case kControllerMessagePushButtonClick:
{
const PushButtonClickMessage *m =
static_cast<const PushButtonClickMessage *>(message);
// Sounds are assigned to the push button's state just before the click.
PlaySound(pushButtonState);
if (pushButtonState == kPushButtonStateUp)
{
if (isDownMovementAnimated)
{
lastClickTime = TheTimeMgr->GetAbsoluteTime();
pushButtonState = kPushButtonStateMovingDown;
}
else // Instant move, or perhaps no move specified.
{
GetTargetNode()->SetNodePosition(
GetTargetNode()->GetNodePosition() + movementDelta);
GetTargetNode()->Invalidate();
pushButtonState = kPushButtonStateDown;
}
Controller* markerController = FindMarkerController(GetTargetNode());
if (markerController != nullptr)
{
markerController->Activate(
GetTargetNode(), // the button that triggered the controller
nullptr); // [TODO] the node that clicked the button
}
}
else if ((pushButtonBehavior == kPushButtonBehaviorToggle) &&
(pushButtonState == kPushButtonStateDown))
{
if (isUpMovementAnimated)
{
lastClickTime = TheTimeMgr->GetAbsoluteTime();
pushButtonState = kPushButtonStateMovingUp;
}
else // Instant move, or perhaps no move specified.
{
GetTargetNode()->SetNodePosition(
GetTargetNode()->GetNodePosition() - movementDelta);
GetTargetNode()->Invalidate();
pushButtonState = kPushButtonStateUp;
}
}
UpdateAppearance(pushButtonState);
break;
}
}
}
Message *PushButtonController::GetInitialStateMessage(void) const
{
return (new PushButtonSyncMessage(GetControllerIndex(), pushButtonState));
}
void PushButtonController::Preprocess(void)
{
Controller::Preprocess();
Node *targetNode = GetTargetNode();
// Test if directly attached to geometry.
// If so, position markers are not available.
if (targetNode->GetNodeType() == kNodeGeometry)
{
geometry = static_cast<Geometry *>(targetNode);
}
else // Walk the group tree looking for the geometry and the two markers.
{
PositionMarker *axisMarker = nullptr;
geometry = nullptr;
Node *group = targetNode;
if (!group->GetManipulator())
{
Node *node = group->GetFirstSubnode();
while (node)
{
NodeType type = node->GetNodeType();
if (type == kNodeMarker)
{
Marker *marker = static_cast<Marker *>(node);
if (marker->GetMarkerType() == kMarkerPosition)
{
PositionMarker *positionMarker =
static_cast<PositionMarker *>(marker);
switch (positionMarker->GetPositionType())
{
case kPositionCenter:
centerPositionMarker = positionMarker;
break;
case kPositionAxis:
axisMarker = positionMarker;
break;
}
}
}
// Grab the first geometry found in the tree.
// [TODO] Find the first geometry with collision-detection enabled.
else if (geometry == nullptr && type == kNodeGeometry)
{
geometry = static_cast<Geometry *>(node);
}
node = node->Next();
}
if (centerPositionMarker != nullptr && axisMarker != nullptr)
{
movementDelta =
axisMarker->GetNodePosition() - centerPositionMarker->GetNodePosition();
movementDistance = Magnitude(movementDelta);
// [TODO] Compare movementDistance against an epsilon
// for minimum movement distance?
if (movementDistance > 0.0f)
{
downMovementDuration =
movementDistance / downSpeedMetersPerSec * 1000.0f;
isDownMovementAnimated = downMovementDuration > 0.0f;
upMovementDuration =
movementDistance / upSpeedMetersPerSec * 1000.0f;
isUpMovementAnimated = upMovementDuration > 0.0f;
if (isDownMovementAnimated || isUpMovementAnimated)
{
upNodePosition = GetTargetNode()->GetNodePosition();
downNodePosition = upNodePosition + movementDelta;
}
}
}
}
}
// The property is attached to a geometry to enable collision detection.
if (geometry != nullptr && !geometry->GetManipulator())
{
geometry->AddProperty(new PushButtonProperty(this));
}
}
void PushButtonController::Move(void)
{
Controller::Move();
if (geometry != nullptr)
{
unsigned long worldTime = TheTimeMgr->GetAbsoluteTime();
UpdateBlinkAppearance(worldTime);
UpdatePosition(static_cast<float>(worldTime));
}
}
void PushButtonController::Sync(unsigned long inPushButtonState)
{
if (geometry != nullptr)
{
TheMessageMgr->SendMessageAll(
PushButtonSyncMessage(GetControllerIndex(), inPushButtonState));
}
}
void PushButtonController::Click(void)
{
if (geometry != nullptr)
{
TheMessageMgr->SendMessageAll(PushButtonClickMessage(GetControllerIndex()));
}
}
//------------------------------------------------------------------------
PushButtonSyncFunction::PushButtonSyncFunction()
:
Function(kFunctionPushButtonSync, kControllerPushButton)
{
pushButtonState = kPushButtonStateDown;
}
PushButtonSyncFunction::PushButtonSyncFunction(
const PushButtonSyncFunction& pushButtonSyncFunction)
:
Function(pushButtonSyncFunction)
{
pushButtonState = pushButtonSyncFunction.pushButtonState;
}
PushButtonSyncFunction::~PushButtonSyncFunction()
{
}
Function *PushButtonSyncFunction::Replicate(void) const
{
return (new PushButtonSyncFunction(*this));
}
unsigned long PushButtonSyncFunction::GetPackSize(unsigned long packFlags) const
{
return (Function::GetPackSize(packFlags) + sizeof(unsigned long));
}
void PushButtonSyncFunction::Pack(Packer& data, unsigned long packFlags) const
{
Function::Pack(data, packFlags);
data << pushButtonState;
}
void PushButtonSyncFunction::Unpack(Unpacker& data, unsigned long unpackFlags)
{
Function::Unpack(data, unpackFlags);
data >> pushButtonState;
}
long PushButtonSyncFunction::GetSettingCount(void) const
{
return 1;
}
Setting *PushButtonSyncFunction::GetSetting(long index) const
{
const StringTable *table = TheGame->GetStringTable();
Setting* setting = nullptr;
if (index == 0)
{
long selectedIndex = 0;
switch (pushButtonState)
{
case kPushButtonStateUp:
selectedIndex = 0;
break;
case kPushButtonStateDown:
selectedIndex = 1;
break;
case kPushButtonStateDisabled:
selectedIndex = 2;
break;
case kPushButtonStateRolloverUp:
selectedIndex = 3;
break;
case kPushButtonStateRolloverDown:
selectedIndex = 4;
break;
}
const char *title = table->GetString(StringID('CTRL', 'PUSH', 'PBST'));
MenuSetting *menu = new MenuSetting('PBST', selectedIndex, title, 5);
menu->SetMenuItemString(
0, table->GetString(StringID('CTRL', 'PUSH', 'PBST', 'PBSU')));
menu->SetMenuItemString(
1, table->GetString(StringID('CTRL', 'PUSH', 'PBST', 'PBSD')));
menu->SetMenuItemString(
2, table->GetString(StringID('CTRL', 'PUSH', 'PBST', 'PBSB')));
menu->SetMenuItemString(
3, table->GetString(StringID('CTRL', 'PUSH', 'PBST', 'PBRU')));
menu->SetMenuItemString(
4, table->GetString(StringID('CTRL', 'PUSH', 'PBST', 'PBRD')));
setting = menu;
}
return setting;
}
void PushButtonSyncFunction::SetSetting(const Setting *setting)
{
Type identifier = setting->GetSettingIdentifier();
if (identifier == 'PBST')
{
long selection =
static_cast<const MenuSetting *>(setting)->GetMenuSelection();
switch (selection)
{
case 0:
pushButtonState = kPushButtonStateUp;
break;
case 1:
pushButtonState = kPushButtonStateDown;
break;
case 2:
pushButtonState = kPushButtonStateDisabled;
break;
case 3:
pushButtonState = kPushButtonStateRolloverUp;
break;
case 4:
pushButtonState = kPushButtonStateRolloverDown;
break;
}
}
}
void PushButtonSyncFunction::Execute(Controller *controller)
{
if (TheMessageMgr->Server())
{
PushButtonController *pushButtonController =
static_cast<PushButtonController *>(controller);
TheMessageMgr->SendMessageAll(PushButtonSyncMessage(
pushButtonController->GetControllerIndex(), pushButtonState));
}
CallCompletionProc();
}
//------------------------------------------------------------------------
PushButtonClickFunction::PushButtonClickFunction()
:
Function(kFunctionPushButtonClick, kControllerPushButton)
{
}
PushButtonClickFunction::PushButtonClickFunction(
const PushButtonClickFunction& pushButtonClickFunction)
:
Function(pushButtonClickFunction)
{
}
PushButtonClickFunction::~PushButtonClickFunction()
{
}
Function *PushButtonClickFunction::Replicate(void) const
{
return (new PushButtonClickFunction(*this));
}
void PushButtonClickFunction::Execute(Controller *controller)
{
if (TheMessageMgr->Server())
{
PushButtonController *pushButtonController =
static_cast<PushButtonController *>(controller);
TheMessageMgr->SendMessageAll(
PushButtonClickMessage(pushButtonController->GetControllerIndex()));
}
CallCompletionProc();
}
//------------------------------------------------------------------------
PushButtonSyncMessage::PushButtonSyncMessage(long controllerIndex)
:
ControllerMessage(kControllerMessagePushButtonSync, controllerIndex)
{
}
PushButtonSyncMessage::PushButtonSyncMessage(
long controllerIndex,
unsigned long inPushButtonState)
:
ControllerMessage(kControllerMessagePushButtonSync, controllerIndex)
{
pushButtonState = inPushButtonState;
}
PushButtonSyncMessage::~PushButtonSyncMessage()
{
}
void PushButtonSyncMessage::Compress(Compressor& data) const
{
ControllerMessage::Compress(data);
data << pushButtonState;
}
bool PushButtonSyncMessage::Decompress(Decompressor& data)
{
if (ControllerMessage::Decompress(data))
{
data >> pushButtonState;
return (true);
}
return (false);
}
//------------------------------------------------------------------------
PushButtonClickMessage::PushButtonClickMessage(long controllerIndex)
:
ControllerMessage(kControllerMessagePushButtonClick, controllerIndex)
{
}
PushButtonClickMessage::~PushButtonClickMessage()
{
}
//------------------------------------------------------------------------
PushButtonProperty::PushButtonProperty(PushButtonController *inPushButtonController)
:
Property(kPropertyPushButton),
pushButtonController(inPushButtonController)
{
SetPropertyFlags(kPropertyNonpersistent);
}
PushButtonProperty::~PushButtonProperty()
{
}
PushButtonController* PushButtonProperty::GetPushButtonController(void) const
{
return pushButtonController;
}
//------------------------------------------------------------------------
// END: [Huidafa]
MGControllers.h
First, find the definition of the enum constant kPropertySecretDoor, and replace that line with the following section:
// START: [Huidafa] kPropertySecretDoor = 'scrt', kPropertyPushButton = 'push' // END: [Huidafa]
Find an enum constant kControllerMessageLightning. Replace that line with the following section:
// START: [Huidafa] kControllerMessageLightning = 0, kControllerMessagePushButtonSync = 0, kControllerMessagePushButtonClick = 1 // END: [Huidafa]
Now find an enum constant kControllerLIghtning. Replace that line with the following section:
// START: [Huidafa] kControllerLightning = 'ltng', kControllerPushButton = 'push' // END: [Huidafa]
Next, find an enum constant kFunctionChangeRotation. Replace that line with the following section:
// START: [Huidafa] kFunctionChangeRotation = 'crot', kFunctionPushButtonSync = 'pbsc', kFunctionPushButtonClick = 'pbck' // [TODO] Add Blink enable and rate functions. // END: [Huidafa]
Finally, at the very bottom of MGControllers.h, add the following long section:
// START: [Huidafa]
//--------------------------------------------------------------------
// States used by the PushButtonController class
enum
{
kPushButtonStateUp = 1,
kPushButtonStateDown = 2,
kPushButtonStateDisabled = 3,
kPushButtonStateRolloverUp = 4,
kPushButtonStateRolloverDown = 5,
// Runtime-only states.
kPushButtonStateMovingDown = 6,
kPushButtonStateMovingUp = 7,
kPushButtonStateOvershooting = 8,
kPushButtonStateBacktracking = 9
};
enum
{
kPushButtonBehaviorSpring = 1,
kPushButtonBehaviorSticky = 2,
kPushButtonBehaviorToggle = 3
};
enum
{
kSettingPushButtonState = 0,
kSettingPushButtonBehavior,
kSettingPushButtonUpColor,
kSettingPushButtonDownColor,
kSettingPushButtonDisabledColor,
kSettingPushButtonRolloverUpColor,
kSettingPushButtonRolloverDownColor,
kSettingPushButtonSoundRange,
kSettingPushButtonUpSoundName,
kSettingPushButtonDownSoundName,
kSettingPushButtonDisabledSoundName,
kSettingPushButtonRolloverUpSoundName,
kSettingPushButtonRolloverDownSoundName,
kSettingPushButtonUpSpeedMetersPerSec,
kSettingPushButtonDownSpeedMetersPerSec,
kSettingPushButtonDownPositionOvershootPercent,
kSettingPushButtonIsBlinking,
kSettingPushButtonBlinkRate,
kSettingPushButtonBlinkColor,
kSettingPushButtonBlinkPeriod,
kSettingPushButtonCount
};
// A push button controller that can be attached to nodes
// which represent push buttons.
//
// Usage Note:
//
// To activate a controller when the push button is clicked, connect
// the target node to a marker with an attached controller.
class PushButtonController : public Controller
{
private:
// START chunk list
unsigned long pushButtonState; // up, down, disabled, rollover
unsigned long pushButtonBehavior; // spring, sticky, toggle
// [TODO] Use full-blown materials instead of simple colors,
// once there is a MaterialSetting class.
ColorRGBA upColor; // emission color
ColorRGBA downColor;
ColorRGBA disabledColor;
ColorRGBA rolloverUpColor;
ColorRGBA rolloverDownColor;
unsigned long soundRange; // meters
ResourceName upSoundName;
ResourceName downSoundName;
ResourceName disabledSoundName;
ResourceName rolloverUpSoundName;
ResourceName rolloverDownSoundName;
float upSpeedMetersPerSec;
float downSpeedMetersPerSec;
unsigned long downPositionOvershootPercent;
bool isBlinking;
float blinkRate;
ColorRGBA blinkColor;
unsigned long blinkPeriod;
// END chunk list; kSettingPushButtonCount items
EmissionAttribute* emissionAttribute;
List<Attribute> attributeList; // [TODO] Cache list per glow color.
Geometry* geometry; // [TODO] Allow grouped geometry.
bool isDownMovementAnimated;
bool isUpMovementAnimated;
Point3D upNodePosition; // starting position
Point3D downNodePosition; // ending position
Vector3D movementDelta;
float movementDistance;
float downMovementDuration; // milliseconds
float upMovementDuration; // milliseconds
float lastClickTime; // milliseconds
unsigned long nextBlinkTime;
bool isBlinked;
Node* centerPositionMarker;
PushButtonController(
const PushButtonController& pushButtonController,
Node *target);
Controller *Replicate(Node *target) const;
bool UnpackChunk(
const ChunkHeader *chunkHeader,
Unpacker& data,
unsigned long unpackFlags);
void UpdateBlinkAppearance(unsigned long worldTime);
void UpdateAppearance(unsigned long inPushButtonState);
void UpdatePosition(float worldTime);
// Plays the sound associated with param <inPushButtonState>.
void PlaySound(unsigned long inPushButtonState);
// Find the controller of the first marker connected to the
// target node of this PushButtonController via a connector
// that has a non-empty key (not kNodeGroup).
//
// Usage Note:
//
// The convention is to connect the target geometry or group
// node to a marker (use a ConnectionMarker for multi-button groups),
// and then put the Action method for the push button in the
// controller script for that marker.
//
// If this push button is in a referenced world, it will walk
// up the tree looking at supernodes for a marker in the parent
// world.
Controller* FindMarkerController(Node* inTargetNode);
public:
PushButtonController();
~PushButtonController();
static bool ValidNode(const Node *node);
static Function *ConstructFunction(FunctionType type);
static void RegisterFunctions(ControllerRegistration *registration);
unsigned long GetPackSize(unsigned long packFlags) const;
void Pack(Packer& data, unsigned long packFlags) const;
void Unpack(Unpacker& data, unsigned long unpackFlags);
long GetSettingCount(void) const;
Setting *GetSetting(long index) const;
void SetSetting(const Setting *setting);
ControllerMessage *ConstructMessage(ControllerMessageType type) const;
virtual void ReceiveMessage(const ControllerMessage *message);
virtual Message *GetInitialStateMessage(void) const;
// Finds the geometry node and attaches a push button property,
// and also finds the optional position and axis markers.
virtual void Preprocess(void);
// Updates the position of the button when animation is set up,
// and also updates the blink appearance.
virtual void Move(void);
// Synchronize the state of the controller and its attached push button.
virtual void Sync(unsigned long inPushButtonState);
// Start a click on the push button.
virtual void Click(void);
};
//--------------------------------------------------------------------
// Function to "Synchronize the state of the controller and its attached push button".
//
// Available in the visual scripting editor for nodes to which a
// PushButtonController has been attached.
class PushButtonSyncFunction : public Function
{
private:
unsigned long pushButtonState;
PushButtonSyncFunction(const PushButtonSyncFunction& pushButtonSyncFunction);
Function *Replicate(void) const;
public:
PushButtonSyncFunction();
~PushButtonSyncFunction();
unsigned long GetPackSize(unsigned long packFlags) const;
void Pack(Packer& data, unsigned long packFlags) const;
void Unpack(Unpacker& data, unsigned long unpackFlags);
long GetSettingCount(void) const;
Setting *GetSetting(long index) const;
void SetSetting(const Setting *setting);
void Execute(Controller *controller);
};
//--------------------------------------------------------------------
// Function to "start a click on a push button".
//
// Available in the visual scripting editor for nodes to which a
// PushButtonController has been attached.
class PushButtonClickFunction : public Function
{
private:
PushButtonClickFunction(const PushButtonClickFunction& pushButtonClickFunction);
Function *Replicate(void) const;
public:
PushButtonClickFunction();
~PushButtonClickFunction();
void Execute(Controller *controller);
};
//--------------------------------------------------------------------
// [TODO] Add Blink enable and rate functions.
//--------------------------------------------------------------------
// Synchronize the state of the controller and its attached push button.
// Sent by the PushButtonController to support multi-player mode.
class PushButtonSyncMessage : public ControllerMessage
{
friend class PushButtonController;
private:
unsigned long pushButtonState; // up, down, disabled, rollover
PushButtonSyncMessage(long controllerIndex);
public:
PushButtonSyncMessage(
long controllerIndex,
unsigned long inPushButtonState);
~PushButtonSyncMessage();
unsigned long GetPushButtonState(void) const
{
return (pushButtonState);
}
void Compress(Compressor& data) const;
bool Decompress(Decompressor& data);
};
//--------------------------------------------------------------------
// Start a click on the push button.
// Sent by the PushButtonController to support multi-player mode.
class PushButtonClickMessage : public ControllerMessage
{
friend class PushButtonController;
public:
PushButtonClickMessage(long controllerIndex);
~PushButtonClickMessage();
};
//--------------------------------------------------------------------
// A property to indicate a geometry which represents a clickable push button.
//
// Automatically added by PushButtonController to any geometry node to which
// it is attached.
class PushButtonProperty : public Property
{
private:
PushButtonController *pushButtonController;
public:
PushButtonProperty(PushButtonController *inPushButtonController);
~PushButtonProperty();
PushButtonController *GetPushButtonController(void) const;
};
//--------------------------------------------------------------------
}
#endif
//------------------------------------------------------------------------
// END: [Huidafa]
Part II
To complete this tutorial, please continue on with Push Button Controls with Geometry, Part II.

