Plastic Gem #39: Plastic Tweaker Animated GUI
by Anthony Rosenbaum · 08/14/2008 (6:07 am) · 3 comments
Download Code File

Animated GUI
Difficulty: Moderate
This article will look at a basic scripted data driven state machine to control GUI transitions. It is presumed you have already integrated, Plastic Gems, Ease and Animated GUI and the Plastic Tweaker, into your code base.
1) Execute new files
In plastic/init
Place as first executed file
2) Execute plastic manager to the server too
In plastic/server/init.cs
Place as first executed file
3) add files and folder to root of the plastic folder
A quick look thru code shows three main areas, the state machine, actions, and animData
The stateManager is a scriptobject which will monitor the current and previous states and in turn sends the callback onStateChanged() at the appropriate time. Each state is also a scriptobject, a state will automatically be added to the base stateManager, unless specified to an alternate stateManager thru the state's onAdd() method.
Abstracting the stateManager allow the designer to have separate managers handling unique task all thru the onStateChanged() callback. In this case we have made a MenuManager which will do three distinct actions for each state change; a prep action, an out action and an in action. Because this machine is responsible for changing GUIs we also execute two additional methods after each action has completed: onDialogGone() and onStateChangeFinish(). Additionally MenuManager prevents input from the user during state changes.
An Action is a scriptobjects which does a particular animation. Each type of Action needs to have a doAction() method to handle the actual GUI animation. In our case we have three custom actions, GUIAlphaAction, GUIMoveAction, and GUIMultiAction. The first two actions are responsible for animating a GUI's alpha and position while the last action was designed to nest multiple actions. Each action contains a scripteobject of class animData additionally the actions also contains the values for an animations' delay, duration and ease on any one action. By abstracting the animData, as such values for the start and end of a GUI's alpha it allows animData to be reused.
Application! Inheritance is the key
What does all this mean to you, well first off the majority of the functionality is already written, what you will have to do is replicate the implementation of the core classes. Because the animation data is stored in the animData and Action classes we can put tweaker comments within the scriptobjects and then adjust the actions with the Plastic tweaker.
We are going to build as GUI system designed to load a splash screen, fade from that screen to a main menu screen, which will fade buttons in, and make one more screen to fade from the main menu to another GUI page. This new GUI layer will be put on top of the existing menus.
So first the first thing is to make some gui files. We will using the GuiPlasticBitmapCtrl nested within GuiControl for background images and buttons.
4)Copy and save this file a GUISplash.gui
5)Copy and save this file a GUIMainMenu.gui
6)Copy and save this file a GUIHelpMenu.gui
Now we will make .cs files in order to stub out the interface functions for the buttons.
7)Copy and save this file a GUIMainMenu.cs
8)Copy and save this file as GUIHelpMenu.cs
Now we are going to define the states
9)Copy and save this file as GUIStates.cs
Now we will provide the gui Actions
10)Copy and save this file as GUIActions.cs
Now that we have a template of data lets make a custom GUI machine to handle it.
11)Copy and save this file as GUIMachine.cs
12)Now to execute all our new files, in ~/client/init.cs add this to clientInit()
13)Finally we will need a button on the normal gui to kick it off add this to the mainMenu.gui
Before we test we need to fill in the stubs in the .cs files so that when we press a button it does something.
14)In GUIMainMenu.cs change the functions to be:
15)In GUIHelpMenu.cs change the functions to be
16)Execute and press the GuiMachine on the main menu. Simple, but ugly right, time to do some tweaking, open the tweaker (Ctrl + T).
17)Use the drop down to find SplashGui adjust SplashGuiInAction to Out and Quintic, then change SplashGuiOutAction to In and Quintic.
18)Use the drop down to find GuiMenuGui adjust the ease on GuiMenuInAction, to be Out and Quintic
19)Repeat the process with GuiMenuExitButtonInAction, and GuiMenuHelpButtonInAction.
20)Finish the process by finding GuiHelpGui in the dropdown and changing GuiHelpInAction, GuiHelpExitButtonInAction to Out and Quintic
21)Save and press the GuiManager button and watch a much smoother transition.
Next Gem
Seeing we have focused on the Plastic Tweaker rather comprehensively we will use the next few Gems to return to improvement to the Torque Game Engine before our summer series runs out. Plastic Gem 40 will look at how to add custom menu items to TGE and TGEA editors.

Animated GUI
Difficulty: Moderate
This article will look at a basic scripted data driven state machine to control GUI transitions. It is presumed you have already integrated, Plastic Gems, Ease and Animated GUI and the Plastic Tweaker, into your code base.
1) Execute new files
In plastic/init
Place as first executed file
// NOTE: it is important to execute the "manager" files first in each group
exec("./plasticManager.cs");
exec("./state/stateManager.cs");
exec("./state/state.cs");
exec("./animData/animDataManager.cs");
exec("./animData/animData.cs");
exec("./animData/stdAnimData.cs");
exec("./action/actionManager.cs");
exec("./action/action.cs");
exec("./action/actionObject.cs");
exec("./menu/menuManager.cs");2) Execute plastic manager to the server too
In plastic/server/init.cs
Place as first executed file
exec("./../plasticManager.cs"); // this is executed on both server & client...3) add files and folder to root of the plastic folder
A quick look thru code shows three main areas, the state machine, actions, and animData
The stateManager is a scriptobject which will monitor the current and previous states and in turn sends the callback onStateChanged() at the appropriate time. Each state is also a scriptobject, a state will automatically be added to the base stateManager, unless specified to an alternate stateManager thru the state's onAdd() method.
Abstracting the stateManager allow the designer to have separate managers handling unique task all thru the onStateChanged() callback. In this case we have made a MenuManager which will do three distinct actions for each state change; a prep action, an out action and an in action. Because this machine is responsible for changing GUIs we also execute two additional methods after each action has completed: onDialogGone() and onStateChangeFinish(). Additionally MenuManager prevents input from the user during state changes.
An Action is a scriptobjects which does a particular animation. Each type of Action needs to have a doAction() method to handle the actual GUI animation. In our case we have three custom actions, GUIAlphaAction, GUIMoveAction, and GUIMultiAction. The first two actions are responsible for animating a GUI's alpha and position while the last action was designed to nest multiple actions. Each action contains a scripteobject of class animData additionally the actions also contains the values for an animations' delay, duration and ease on any one action. By abstracting the animData, as such values for the start and end of a GUI's alpha it allows animData to be reused.
Application! Inheritance is the key
What does all this mean to you, well first off the majority of the functionality is already written, what you will have to do is replicate the implementation of the core classes. Because the animation data is stored in the animData and Action classes we can put tweaker comments within the scriptobjects and then adjust the actions with the Plastic tweaker.
We are going to build as GUI system designed to load a splash screen, fade from that screen to a main menu screen, which will fade buttons in, and make one more screen to fade from the main menu to another GUI page. This new GUI layer will be put on top of the existing menus.
So first the first thing is to make some gui files. We will using the GuiPlasticBitmapCtrl nested within GuiControl for background images and buttons.
4)Copy and save this file a GUISplash.gui
//--- OBJECT WRITE BEGIN ---
new GuiControl(GUISplash) {
canSaveDynamicFields = "0";
Profile = "GuiContentProfile";
HorizSizing = "width";
VertSizing = "height";
position = "0 0";
Extent = "800 600";
MinExtent = "8 8";
canSave = "1";
Visible = "0";
hovertime = "1000";
alpha = "0";
color = "255 255 255 0";
unitClip = "0 0 1 1";
useVariable = "0";
tile = "0";
new GuiPlasticBitmapCtrl(GUISplashPanel) {
canSaveDynamicFields = "0";
Profile = "GuiContentProfile";
HorizSizing = "width";
VertSizing = "height";
position = "0 0";
Extent = "800 600";
MinExtent = "8 8";
canSave = "1";
Visible = "1";
hovertime = "1000";
alpha = "1";
color = "255 255 255 255";
unitClip = "0 0 1 1";
dml = "./PlasticGames.jpg";
useVariable = "0";
tile = "0";
};
};
//--- OBJECT WRITE END ---5)Copy and save this file a GUIMainMenu.gui
//--- OBJECT WRITE BEGIN ---
new GuiControl(GUIMainMenu) {
canSaveDynamicFields = "0";
Profile = "GuiContentProfile";
HorizSizing = "width";
VertSizing = "height";
position = "0 0";
Extent = "800 600";
MinExtent = "8 8";
canSave = "1";
Visible = "1";
hovertime = "1000";
alpha = "0";
color = "255 255 255 0";
unitClip = "0 0 1 1";
dml = "./beachComber002_crop.jpg";
useVariable = "0";
tile = "0";
new GuiPlasticBitmapCtrl(GuiMenuGuiPanel) {
canSaveDynamicFields = "0";
Profile = "GuiContentProfile";
HorizSizing = "width";
VertSizing = "height";
position = "0 0";
Extent = "800 600";
MinExtent = "8 8";
canSave = "1";
Visible = "1";
hovertime = "1000";
alpha = "1";
color = "255 255 255 255";
unitClip = "0 0 1 1";
dml = "./beachComber002_crop.jpg";
useVariable = "0";
tile = "0";
new GuiPlasticBitmapCtrl(GuiMenuExitButton) {
canSaveDynamicFields = "0";
Profile = "GuiButtonProfile";
HorizSizing = "right";
VertSizing = "top";
position = "36 413";
Extent = "110 110";
MinExtent = "8 8";
canSave = "1";
Visible = "1";
Command = "";
hovertime = "1000";
alpha = "1";
color = "255 255 255 255";
unitClip = "0 0 1 1";
text = "Exit!";
groupNum = "-1";
dml = "./button.dml";
new GuiMLTextCtrl() {
profile = "GuiTextProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "50 50";
extent = "24 18";
minExtent = "8 8";
visible = "1";
text = "Exit";
maxLength = "255";
alpha = "1";
helpTag = "0";
};
};
new GuiPlasticBitmapCtrl(GuiMenuHelpButton) {
canSaveDynamicFields = "0";
Profile = "GuiButtonProfile";
HorizSizing = "right";
VertSizing = "top";
position = "150 413";
Extent = "110 110";
MinExtent = "8 8";
canSave = "1";
Visible = "1";
Command = "";
hovertime = "1000";
alpha = "1";
color = "255 255 255 255";
unitClip = "0 0 1 1";
text = "Exit!";
groupNum = "-1";
dml = "./button.dml";
new GuiMLTextCtrl() {
profile = "GuiTextProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "50 50";
extent = "24 18";
minExtent = "8 8";
visible = "1";
text = "Help";
maxLength = "255";
alpha = "1";
helpTag = "0";
};
};
};
};
//--- OBJECT WRITE END ---6)Copy and save this file a GUIHelpMenu.gui
//--- OBJECT WRITE BEGIN ---
new GuiControl(GUIHelpMenu) {
canSaveDynamicFields = "0";
Profile = "GuiContentProfile";
HorizSizing = "width";
VertSizing = "height";
position = "0 0";
Extent = "800 600";
MinExtent = "8 8";
canSave = "1";
Visible = "0";
hovertime = "1000";
alpha = "1";
color = "255 255 255 0";
unitClip = "0 0 1 1";
dml = "./ioc/grandpas_hut.jpg";
useVariable = "0";
tile = "0";
new GuiPlasticBitmapCtrl(GuiHelpGuiPanel) {
canSaveDynamicFields = "0";
Profile = "GuiContentProfile";
HorizSizing = "width";
VertSizing = "height";
position = "0 0";
Extent = "800 600";
MinExtent = "8 8";
canSave = "1";
Visible = "1";
hovertime = "1000";
alpha = "1";
color = "255 255 255 255";
unitClip = "0 0 1 1";
dml = "./ioc/grandpas_hut.jpg";
useVariable = "0";
tile = "0";
new GuiPlasticBitmapCtrl(GuiHelpExitButton) {
canSaveDynamicFields = "0";
Profile = "GuiButtonProfile";
HorizSizing = "right";
VertSizing = "top";
position = "36 413";
Extent = "110 110";
MinExtent = "8 8";
canSave = "1";
Visible = "1";
Command = "";
hovertime = "1000";
alpha = "1";
color = "255 255 255 255";
unitClip = "0 0 1 1";
text = "Exit!";
groupNum = "-1";
dml = "./button.dml";
new GuiMLTextCtrl() {
profile = "GuiTextProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "50 50";
extent = "30 18";
minExtent = "8 8";
visible = "1";
text = "Back";
maxLength = "255";
alpha = "0";
helpTag = "0";
};
};
};
};
//--- OBJECT WRITE END ---Now we will make .cs files in order to stub out the interface functions for the buttons.
7)Copy and save this file a GUIMainMenu.cs
// GuiMenuExitButton
// GuiMenuHelpButton
//exit button
function GuiMenuExitButton::onMouseDown(%this, %item, %pts, %obj)
{
}
function GuiMenuExitButton::onMouseUp(%this, %item, %pts, %obj)
{
}
//Help button
function GuiMenuHelpButton::onMouseDown(%this, %item, %pts, %obj)
{
}
function GuiMenuHelpButton::onMouseUp(%this, %item, %pts, %obj)
{
}8)Copy and save this file as GUIHelpMenu.cs
// GUIHelpMenu
// GuiHelpExitButton
//exit button
function GuiHelpExitButton::onMouseDown(%this, %item, %pts, %obj)
{
}
function GuiHelpExitButton::onMouseUp(%this, %item, %pts, %obj)
{
}Now we are going to define the states
9)Copy and save this file as GUIStates.cs
//Gui States
// Plastic games splash screen
new ScriptObject(SplashState : GuiStateBase)
{
event[GoToMainMenu] = "MainMenuState";
};
new ScriptObject(MainMenuState : GuiStateBase)
{
event[GoToHelpGui] = "HelpGuiState";
event[Quit] = "QuitState";
};
new ScriptObject(QuitState : GuiStateBase)
{
// you really can't go anywhere from the quit state but
// we have to define SOMETHING or torque script chokes...
event[GoToMainMenu] = "MainMenuState";
};
new ScriptObject(HelpGuiState : GuiStateBase)
{
event[GoToMainMenu] = "MainMenuState";
};
//Now we will provide the gui Actions
10)Copy and save this file as GUIActions.cs
//------------
// splash
//------------
new ScriptObject(SplashInAction : GUIAlphaActionBase)
{
object = GUISplashPanel;
//M[SplashGui
animData = "FadeInData"; // (values=allAnimData)
duration = "1.0";
ease = "0 0 "; // TypeEaseF
delay = "0.5";
//M]
};
new ScriptObject(SplashPrepAction : SplashInAction)
{
object = GUISplashPanel;
duration = 0.0;
};
new ScriptObject(SplashOutAction : GUIAlphaActionBase)
{
object = GUISplashPanel;
//M[GUISplashPanel
animData = "FadeOutData"; // (values=allAnimData)
duration = "0.5";
ease = "0 0"; // TypeEaseF
delay = "0.5";
//M]
};
//---------
//main menu
//---------
new ScriptObject(GuiMenuInAction : GUIAlphaActionBase)
{
object = GuiMenuGuiPanel;
//M[ GuiMenuGui
animData = "FadeInData"; // (values=allAnimData)
duration = "3.0";
ease = " 0 0"; // TypeEaseF
delay = "0.2";
//M]
};
new ScriptObject(GuiMenuPrepAction : GuiMenuInAction)
{
object = GuiMenuGuiPanel;
duration = 0.0;
};
new ScriptObject(GuiMenuOutAction : GUIAlphaActionBase)
{
object = GuiMenuGuiPanel;
//M[GuiMenuGui
animData = "FadeOutData"; // (values=allAnimData)
duration = "1.0";
ease = "0 0"; // TypeEaseF
delay = "0.5";
//M]
};
new ScriptObject(GuiMenuExitButtonInAction : GUIAlphaActionBase)
{
object = GuiMenuExitButton;
//M[GuiMenuGui
animData = "FadeInData"; // (values=allAnimData)
duration = "1.0";
ease = "0 0"; // TypeEaseF
delay = "1.2";
//M]
};
new ScriptObject(GuiMenuHelpButtonInAction : GUIAlphaActionBase)
{
object = GuiMenuHelpButton;
//M[GuiMenuGui
animData = "FadeInData"; // (values=allAnimData)
duration = "1.0";
ease = "0 0"; // TypeEaseF
delay = "1.6";
//M]
};
//-------multi
new ScriptObject(GuiMenuMultiInAction : GUIMultiActionBase)
{
subAction0 = "GuiMenuInAction";
subAction2 = "GuiMenuHelpButtonInAction";
subAction3 = "GuiMenuExitButtonInAction";
};
//---------
//help menu
//---------
new ScriptObject(GuiHelpInAction : GUIAlphaActionBase)
{
object = GuiHelpGuiPanel;
//M[ GuiHelpGui
animData = "FadeInData"; // (values=allAnimData)
duration = "1.0";
ease = "0 0"; // TypeEaseF
delay = "0.2";
//M]
};
new ScriptObject(GuiHelpPrepAction : GuiHelpInAction)
{
object = GuiHelpGuiPanel;
duration = 0.0;
};
new ScriptObject(GuiHelpOutAction : GUIAlphaActionBase)
{
object = GuiHelpGuiPanel;
//M[ GuiHelpGui
animData = "FadeOutData"; // (values=allAnimData)
duration = "1.0";
ease = "0 0"; // TypeEaseF
delay = "0.5";
//M]
};
new ScriptObject(GuiHelpExitButtonInAction : GUIAlphaActionBase)
{
object = GuiHelpExitButton;
//M[ GuiHelpGui
animData = "FadeInData"; // (values=allAnimData)
duration = "1.0";
ease = "0 0"; // TypeEaseF
delay = "0.7";
//M]
};
//-------multi
new ScriptObject(GuiHelpMultiInAction : GUIMultiActionBase)
{
subAction0 = "GuiMenuInAction";
subAction1 = "GuiHelpExitButtonInAction";
};Now that we have a template of data lets make a custom GUI machine to handle it.
11)Copy and save this file as GUIMachine.cs
new ScriptObject(GuiMachine)
{
};
new ScriptObject(GuiMenuMonitor )
{
class = MenuManager;
manager = GuiStateManager;
};
new ScriptObject(GuiStateManager )
{
class = StateManager;
};
new ScriptObject(GuiStateBase)
{
class = State;
manager = GuiStateManager;
};
function startGuiMachine(){
// start the state machine...
GuiStateManager.originalGui = Canvas.getcontent();
GuiStateManager.startStateMachine(GuiMachine, "SplashState");
}
function stopGuiMachine(%prevStateName)
{
GuiMenuMonitor.onDialogGone(%prevStateName);
// stop the state machine...
GuiStateManager.stopStateMachine();
// go back to main menu...
Canvas.SetContent(GuiStateManager.originalGui);
}
function GuiMachine::onStateChanged(%this, %manager, %currStateName, %event, %prevStateName)
{
if (%currStateName $= "QuitState")
{
stopGuiMachine(%prevStateName);
return;
}
// seperate all below into MenuManager
GuiMenuMonitor.onStateChanged(%currStateName, %prevStateName);
}
function GuiMenuMonitor::findDialogForState(%this, %stateName)
{
%gui = "";
switch$( %stateName){
case "SplashState": %gui = GUISplash;
case "MainMenuState": %gui = GUIMainMenu;
case "HelpGuiState": %gui = GUIHelpMenu;
default : echo("GuiMenuMonitor::findDialogForState No Gui State" SPC %stateName);
}
return %gui;
}
function GuiMenuMonitor::findPrepActionForState(%this, %stateName)
{
%action = "";
switch$( %stateName){
case "SplashState": %action = SplashPrepAction;
case "MainMenuState": %action = GuiMenuPrepAction;
case "HelpGuiState": %action = GuiHelpPrepAction;
default : echo("GuiMenuMonitor::findPrepActionForState No Gui State" SPC %stateName);
}
return %action;
}
function GuiMenuMonitor::findInActionForState(%this, %stateName)
{
%action = "";
switch$( %stateName){
case "PlasticSplashState": %action = SplashInAction;
case "MainMenuState": %action = GuiMenuMultiInAction;
case "HelpGuiState": %action = GuiHelpMultiInAction;
default : echo("GuiMenuMonitor::findInActionForState No Gui State" SPC %stateName);
}
return %action;
}
function GuiMenuMonitor::findOutActionForState(%this, %stateName)
{
%action = "";
switch$( %stateName){
case "SplashState": %action = SplashOutAction;
case "MainMenuState": %action = GuiMenuOutAction;
case "HelpGuiState": %action = GuiHelpOutAction;
default : echo("GuiMenuMonitor::findOutActionForState No Gui State" SPC %stateName);
}
return %action;
}
function GuiMenuMonitor::onStateChangeFinish(%this, %stateName)
{
echo("GuiMenuMonitor::onStateChangeFinish");
MenuManager::onStateChangeFinish(%this, %stateName);
%command = "";
switch$( %stateName){
case "SplashState": %command = "GoToMainMenu";
case "MainMenuState": %command = "";
case "HelpGuiState": %command = "";
default : echo("GuiMenuMonitor::onStateChangeFinish No Gui State" SPC %stateName);
}
if(%command !$="")
%this.Manager.fireEvent(%command);
}12)Now to execute all our new files, in ~/client/init.cs add this to clientInit()
exec("./scripts/plastic/guiMachine.cs");
exec("./scripts/plastic/guiStates.cs");
exec("./scripts/plastic/guiActions.cs");
exec("./ui/plastic/GUISplash.gui");
exec("./ui/plastic/GUIMainMenu.gui ");
exec("./ui/plastic/GUIHelpMenu.gui ");
exec("./scripts/plastic/GUIMainMenu.cs");
exec("./scripts/plastic/GUIHelpMenu.cs");13)Finally we will need a button on the normal gui to kick it off add this to the mainMenu.gui
new GuiButtonCtrl(MainMenuMachine) {
canSaveDynamicFields = "0";
Profile = "GuiButtonProfile";
HorizSizing = "right";
VertSizing = "top";
position = "179 390";
Extent = "110 20";
MinExtent = "8 8";
canSave = "1";
Visible = "1";
Command = "startGuiMachine();";
hovertime = "1000";
alpha = "1";
color = "255 255 255 255";
unitClip = "0 0 1 1";
text = "GuiMachine";
groupNum = "-1";
buttonType = "PushButton";
}; Before we test we need to fill in the stubs in the .cs files so that when we press a button it does something.
14)In GUIMainMenu.cs change the functions to be:
function GuiMenuExitButton::onMouseDown(%this, %item, %pts, %obj)
{
%this.setframe(1);
}
function GuiMenuExitButton::onMouseUp(%this, %item, %pts, %obj)
{
%this.setframe(0);
GuiMenuMonitor.onBindUsed("Quit");
}
//Help button
function GuiMenuHelpButton::onMouseDown(%this, %item, %pts, %obj)
{
%this.setframe(1);
}
function GuiMenuHelpButton::onMouseUp(%this, %item, %pts, %obj)
{
%this.setframe(0);
GuiMenuMonitor.onBindUsed("GoToHelpGui");
}15)In GUIHelpMenu.cs change the functions to be
function GuiHelpExitButton::onMouseDown(%this, %item, %pts, %obj)
{
%this.setframe(1);
}
function GuiHelpExitButton::onMouseUp(%this, %item, %pts, %obj)
{
%this.setframe(0);
GuiMenuMonitor.onBindUsed("GoToMainMenu");
}16)Execute and press the GuiMachine on the main menu. Simple, but ugly right, time to do some tweaking, open the tweaker (Ctrl + T).
17)Use the drop down to find SplashGui adjust SplashGuiInAction to Out and Quintic, then change SplashGuiOutAction to In and Quintic.
18)Use the drop down to find GuiMenuGui adjust the ease on GuiMenuInAction, to be Out and Quintic
19)Repeat the process with GuiMenuExitButtonInAction, and GuiMenuHelpButtonInAction.
20)Finish the process by finding GuiHelpGui in the dropdown and changing GuiHelpInAction, GuiHelpExitButtonInAction to Out and Quintic
21)Save and press the GuiManager button and watch a much smoother transition.
Next Gem
Seeing we have focused on the Plastic Tweaker rather comprehensively we will use the next few Gems to return to improvement to the Torque Game Engine before our summer series runs out. Plastic Gem 40 will look at how to add custom menu items to TGE and TGEA editors.
About the author

Torque 3D Owner DALO