TicTacToe
From Blue Mars Developer Guidebook
|
|
Contents |
Overview
The Tic-Tac-Toe sample is another example that elaborates on the dynamic object mechanism, which will give you the ability to synchronize data across all clients over the network, and allow everyone to see the same results in Blue Mars. It will have a ownership conflict. But it will resolve in few second or reset both conflicted player.
Prerequisite
To use this example you should be familiar with the following concepts.
- How to create a City.
- How to use the Asset Browser.
- How to set up an original entity.
- Basic knowledge of Lua scripting.
- Knowledge of the Dynamic Object framework and how your avatar is synchronized across clients via TKserver (wiki page to be added).
How to install TicTacToe in your City
Please follow these steps, described below, to install the TicTacToe sample.
- Download TicTacToe.zip.
- Open your City data with the City Editor.
- Use the Asset Browser to copy the data.
- Import the layer file.
- Test.
Download TicTacToe.zip
Click the link below to download TicTacToe.zip. We recommend that you save the file on your desktop first then unzip it.
- File:TicTacToe.zip
- This .zip file contains the following data:
- Objects/*
- Entities/SampleTicTacToeBoard.ent
- Scripts/Entities/Minigames/TicTacToe/SampleTicTacToeBoard.lua
- TicTacToeLayer.lyr
Open your City data with the City Editor
...Or you may create a new City for this sample.
Use the Asset Browser to copy the data
The Asset Browser will maintain all file paths for both material files(.MTL) and the layer file(.LYR), so you do not need to change the file paths by hand.
Please copy the unzipped TicTacToe data into your City folder by dragging.
- Browse to your level directory in the Asset Browser
- Browse to the TicTacToe folder in a Windows Explorer window
- Select and drag everything inside the TicTacToe folder (Entities, Objects, Scripts folders and the TicTacToeLayer.lyr) to the Asset Browser at the same time
- Reload your level before moving on to the next step
Import the layer file
You can import the "TicTacToeLayer.lyr" file from the Layer tab in the RollUpBar.
Every City has a different land size and coordinates, so after importing the layer file you may need to run Select All Objects
then use "[Modify] -> [Go to selection]" menu
to see the TicTacToe board.
Test
As you may know, Dynamic Objects were designed to synchronizing data across clients. However, the City Editor does not have network connecting capabilities. Therefore, this example will only work with the Check in BlueMars mode.
Detailed explanation
Flow network
You can find the flow network for this TicTacToe board from the SampleTicTacToeBoard1 entity which is placed under the table.
- In the flow network, you can see 9 ARItem entities in the upper side of the screen. Those are used for catching the mouse click event and passing the event to the SampleTicTacToeBoard entity. That entity will then synchronize the board information across clients by using the dynamic object mechanism.
- Another ARItem entity in the bottom of the screen will trigger the reset callback of SampleTicTacToeBoard to reset all information.
Entity
We've provided one sample entity for this demo named SampleTicTacToeBoard, which is saved in Entities/SampleTicTacToeBoard.ent. You can access this entity from the Entity button in the Objects Rollup Bar.
Diagram
There are only 2 states in this sample. The Click event will trigger a state change. Most of the time,the onUpdate routine runs once every frame and waits for the trigger.
Lua Script
The main part of this example is a lua script, which you can find in Scripts/Entities/Minigames/TicTacToe/SampleTicTacToeBoard.lua.
- This entity toggles the status between "Circle" and "Cross" to indicate which game piece will be placed on the board at the next click.
- When the user clicks the board, it will toggle the status of the entity and synchronize the status of game pieces on the board (PieceStateTable) via the UpdateState function of the dynamic object.
- To use the UpdateState function, the user has to take ownership of the dynamic object to avoid conflict with another user.
- The ARDynamicObject.Setup(SampleTicTacToeBoard) command will make this entity dynamic.
- Description
for initialization SampleTicTacToeBoard = {} Constructor. PieceObjSetup() Part of OnInit but will be executed one frame later. OnInit() Initialize function. Game State (Turn base) Cross Status "Cross" means that a cross game piece will be placed on the next mouse click event. Circle Status "Circle" means that a circle game piece will be placed on the next mouse click event.
Synchronization Mechanism SynchronizedParameters() This callback function will be called by the dynamic object mechanism once per second, but the timing may vary. GamePieceUpdate() Will be called from the user click event to set the NeedUpdate flag which is used at OnUpdate of both states. DrawTable() Update display of all game pieces on the table. This function is always called from OnUpdate of each status. for flow network Event_Click??() Plug for the flow network which will place the game piece on the table. ResetGame() Plug for the flow network which will reset the table.
- Source Code
-------------------------
-- Entity Definition
-------------------------
SampleTicTacToeBoard = {
Properties = { },
States = { "Cross", "Circle" },
Editor = { },
}
local LinkTargetList = -- Piece place holder object link. These name are linked from SampleTicTacToeBoard entity.
{
"Pos_00" ,"Pos_01" ,"Pos_02" ,
"Pos_10" ,"Pos_11" ,"Pos_12" ,
"Pos_20" ,"Pos_21" ,"Pos_22"
} ;
function SampleTicTacToeBoard:SynchronizedParameters()
-- define which slot value will syncronize.
return { PieceStateTable = self.PieceStateTable };
end
-------------------------
-- Initialization
-------------------------
function SampleTicTacToeBoard:PieceObjSetup ()
-- ARItem Entity Set from linked entity
for i = 1, 9 do
self.GamePiece[i] = self:GetLinkTarget(LinkTargetList[i]);
end
-- Rendering Object set
self.PieceCircleCGF = self:GetLinkTarget( "Circle" ).Properties.fileModel;
self.PieceCrossCGF = self:GetLinkTarget( "Cross" ).Properties.fileModel;
-- Asign rendering object to Entity Slot then use Entity:DrawSlot to choose
for i = 1, 9 do
self.GamePiece[i]:LoadObject(1, self.PieceCircleCGF) -- for Circle
self.GamePiece[i]:DrawSlot(1, 0) -- CGF using in DrawSlot must have a same material file.
-- Object is switched, but material will stay. So muat use same material table for all object.
end
for i = 1, 9 do
self.GamePiece[i]:LoadObject(2, self.PieceCrossCGF) -- for Cross
self.GamePiece[i]:DrawSlot(2, 0)
end
end
function SampleTicTacToeBoard:OnInit ()
-- This function called only once when loaded,
self.GamePiece = { } -- Table for Local Game Piece Obj for draw
self.PieceStateTable = { } -- Table for keep state locally and syncronize
for i = 1, 9 do
self.PieceStateTable[i] = 0 --State value : 0 = none , 1 = Cross, 2 = Circle
end
self.NeedUpdate = false -- Update flag
ARLazyUtils.OnNextFrame( -- Execute on next frame, System may not load entity yet.
function ()
self:PieceObjSetup();
self:GotoState("Circle")
end)
end
-------------------------
-- Upddate Functions
-------------------------
function SampleTicTacToeBoard:GamePieceUpdate ()
-- This function will prepare update syncronization.
-- Local state updated with click event, but it is not propergated.
-- Take ownership of dynamic object then propergate to others.
self:TakeOwnership()
self.NeedUpdate = true
end
function SampleTicTacToeBoard:StateUpdate (NewState)
-- It called from State Machine for every frame.
-- After click event, wait owner ship change, then propergate update.
-- If not as owner, then get update from others.
if self:IsOwnedByLocalAvatar() then
if self.NeedUpdate then -- update when have-ownership & NeedUpdate-ON
self.NeedUpdate = false
self:UpdateState( NewState , { PieceStateTable = self.PieceStateTable }) -- Send updated status to others, BUT may failed,.
end
elseif (self.NeedUpdate == false) and self.SyncParameters.PieceStateTable then -- syncronize from oppornent player state
for i = 1,9 do
self.PieceStateTable[i] = self.SyncParameters.PieceStateTable[i]
end
end
end
function SampleTicTacToeBoard:DrawTable ()
-- It called from state Machine for every frame. for render pieces on table.
-- It uses Drawslot, Clickable object have both circle and cross object on slot.
DEBUG ( 1, string.format( "Table = %s" , self:GetName()));
for i = 1, 9 do
DEBUG ( i+1, string.format( "State Value on %s = %2d:", self.GamePiece[i]:GetName() , self.PieceStateTable[i]));
if self.PieceStateTable[i] == 1 then
self.GamePiece[i]:DrawSlot(1, 0)
self.GamePiece[i]:DrawSlot(2, 1) -- Draw Cross
elseif self.PieceStateTable[i] == 2 then
self.GamePiece[i]:DrawSlot(1, 1) -- Draw Circle
self.GamePiece[i]:DrawSlot(2, 0)
else -- No Draw
self.GamePiece[i]:DrawSlot(1, 0)
self.GamePiece[i]:DrawSlot(2, 0)
end
end
end
-------------------------
-- Event Triggers
-------------------------
--- When click, update local color value if color = 0
function SampleTicTacToeBoard:OnClickFunction (i)
-- it called after click piece on table.
-- only update with currently empty and avatar is inside of area.
if (self.PieceStateTable[i] == 0) then
self.PieceStateTable[i] = self.CurrentState;
self:GamePieceUpdate();
else
CryAction.Persistant2DText("You can't replace piece.",ARDebugSize,{x=1,y=1,z=1},"ARDebugMessage", 3);
end
end
--- Click event hundler
function SampleTicTacToeBoard.Event_Click00(self) self:OnClickFunction(1); end
function SampleTicTacToeBoard.Event_Click01(self) self:OnClickFunction(2); end
function SampleTicTacToeBoard.Event_Click02(self) self:OnClickFunction(3); end
function SampleTicTacToeBoard.Event_Click10(self) self:OnClickFunction(4); end
function SampleTicTacToeBoard.Event_Click11(self) self:OnClickFunction(5); end
function SampleTicTacToeBoard.Event_Click12(self) self:OnClickFunction(6); end
function SampleTicTacToeBoard.Event_Click20(self) self:OnClickFunction(7); end
function SampleTicTacToeBoard.Event_Click21(self) self:OnClickFunction(8); end
function SampleTicTacToeBoard.Event_Click22(self) self:OnClickFunction(9); end
-- Reset button pressed
function SampleTicTacToeBoard.ResetGame(self) -- this function Called from FlowGraph need to be this Style
-- it called after click on reset button.
-- after reset new game will start with "Circle" state.
--initialize slot value
for i = 1, 9 do
self.PieceStateTable[i] = 0
end
if ( "Cross" == self:GetState()) then -- New turn start from Circle always.
self:GamePieceUpdate()
else
self:GotoState("Cross")
self:GamePieceUpdate();
end
end
-- Click Event Trigger setting for Entity (FlowGraph)
SampleTicTacToeBoard.FlowEvents = {
-- This definition MUST locate after all functions defined.
Inputs = {
Click00 = {SampleTicTacToeBoard.Event_Click00, "bool"},
Click01 = {SampleTicTacToeBoard.Event_Click01, "bool"},
Click02 = {SampleTicTacToeBoard.Event_Click02, "bool"},
Click10 = {SampleTicTacToeBoard.Event_Click10, "bool"},
Click11 = {SampleTicTacToeBoard.Event_Click11, "bool"},
Click12 = {SampleTicTacToeBoard.Event_Click12, "bool"},
Click20 = {SampleTicTacToeBoard.Event_Click20, "bool"},
Click21 = {SampleTicTacToeBoard.Event_Click21, "bool"},
Click22 = {SampleTicTacToeBoard.Event_Click22, "bool"},
Reset = {SampleTicTacToeBoard.ResetGame, "bool"},
},
Outputs = { },
}
-------------------------
-- State Machine
-------------------------
-- There are only 2 state. I try to make simple mechanism for this.
-- Machine moved between only "Circle" and "Cross" for next piece.
-- It will cause to ownership confliction.
-- But state confliction will be resolved in few second or Reset event.
SampleTicTacToeBoard.Cross = { -- Next turn is Cross
OnBeginState = function (self)
self.CurrentState = 1
self:Activate(1)
end, --- commma is nessesary
OnUpdate = function (self)
self:StateUpdate("Circle")
self:DrawTable()
end,
OnEndState = function (self)
self:ReleaseOwnership() -- it called just before chnaging state. Expecting second update event was delivered.
end,
}
SampleTicTacToeBoard.Circle = {
OnBeginState = function (self)
self.CurrentState = 2
self:Activate(1)
end,
OnUpdate = function (self)
self:StateUpdate("Cross")
self:DrawTable()
end,
OnEndState = function (self)
self:ReleaseOwnership()
end,
}
-------------------------
-- ETC
-------------------------
-------- Setup DynamicObject
ARDynamicObject.Setup(SampleTicTacToeBoard)
--------- Debug use
function DEBUG ( pos, str )
HUD.Draw2DLabel( 20 , pos*10+20, 1, str)
end
See Also

