Today, we officially started writing our game engine GDE-X.
For a game, we can regard it as a state machine-in fact, the changes in different pictures and scenes in the game are the switching between States of a finite state machine.
It can be roughly understood as follows:
While (not Exit)
{
Switch (state)
{
Case state1:
Do_state1 ();
Case state2:
Do_state2 ();
....
}
}
Another layer is actually that each status has an "inbound" and "outbound"
We only need to implement a scheduling mechanism to schedule the specific content of the current status and running status, and schedule other statuses after the operation is complete.
We define an enumerative gamestatus, which stores the States to be used in all games. (Specific to the game may be the main menu, system settings, game, game End, read progress ...... Even the specific pictures of the game)
Here we only provide two States for testing, empty and test.
/// <Summary> <br/> // game status <br/> /// </Summary> <br/> Public Enum gamestatus <br/> {< br/> Empty = 0, <br/> test <br/>}
For each status, we provide a "function class", that is, the logic code that the game should run in this status. We call it statemachineclass (state machine class)
Create statemachinebase and statemachine interfaces based on their commonalities. Features:
Statemachinebase:
1. You need to get the game engine instance (rendering handles and resources in the engine are stored in the engine instance)
2. A method is required for scheduling the game engine to execute the next state (out of State)
Istatemachine:
1. You need to provide functional entry for this status. (Which code is run when the task is scheduled to this State)
The reference code is as follows:
/// <Summary> <br/> // state machine node interface class <br/> /// </Summary> <br/> Public interface istatemachine <br/> {<br/> /// <summary> <br/> // run the state machine node <br/> /// </Summary> <br/> void run (); <br/>}</P> <p> // <summary> <br/> // state machine base class, pass in the Game Engine instance <br/> /// </Summary> <br/> public class statemachinebaseclass <br/>{< br/> Public gameengine engineinstance {set; get ;} </P> <p> /// <summary> <br/> // specifies the execution status of the Game Engine master state machine <br/> /// </Summary> <br />/// <Param name = "nextstatus"> Status name </param> <br/> protected void runnextstatus (gamestatus nextstatus) <br/>{< br/> gameengine. currentstatus = nextstatus; <br/> compositiontarget. rendering + = new eventhandler (gameengine. nextstatus); <br/>}< br/>}
If you are careful, you may think that the implementation of the runnextstatus function is very strange. Don't worry, I will explain it later.
Then we need to implement the scheduling mechanism of the entire game engine state machine.
First, we need to bind the "status" and "function class. The Dictionary of C # is used here.
Then I can use m_statehash [Status name] To get the function class instance, and then extract the function class interface run method to get the function class code entry in scheduling.
/// <Summary> <br/> // state machine ing <br/> /// </Summary> <br/> static private dictionary <gamestatus, istatemachine> m_statehash = NULL; </P> <p> /// <summary> <br/> // initialization <br/> /// </Summary> <br/> private void statemachineinit () <br/>{< br/> currentstatus = gamestatus. test; <br/> m_statehash = new dictionary <gamestatus, istatemachine> (); </P> <p> // state machine class corresponding to the registration status <br/> registstatemachine (gamestatus. empty, new emptystatus (); <br/> registstatemachine (gamestatus. test, new teststatus ()); <br/>}</P> <p> // <summary> <br/> // register the state machine <br/> /// </Summary> <br />/// <Param name = "status"> Status </param> <br/> /// <Param name = "class"> state machine class </param> <br/> private void registstatemachine (gamestatus status, statemachinebaseclass stateclass) <br/>{< br/> stateclass. engineinstance = This; <br/> m_statehash.add (status, stateclass as istatemachine); <br/>}
We have to provide an entry to the startup of the entire game state machine.
/// <Summary> <br/> // start the state machine <br/> /// </Summary> <br/> static public void run () <br/>{< br/> m_statehash [currentstatus]. run (); <br/>}
Provides a variable to record the current game status
/// <Summary> <br/> // current game status <br/> /// </Summary> <br/> static public gamestatus currentstatus {set; get ;}
Finally, how can we schedule and run the next state after a State ends?
My original implementation was to add a static method nextstatus in gameengine, and then run gameengine. nextstatus (XXX) at the end of the function class ). -- There is a paradox .. In the game engine scheduling and running function class, the function class blocks and calls the static method of the game engine... Check the stack .. Sure enough, as expected, all the previously running "function classes" did not release the memory! In this case, the memory will collapse soon.
So how can our programs call the game engine method in the function class without blocking to implement state machine scheduling?
-- This is the strange code just now.
CompositiontargetThe object can create a Custom Animation Based on the callback of each frame.
In the function classCompositiontargetRegister a method in gameengine for scheduling the state machine.
Then implement this in gameengine:
/// <Summary> <br/> // The state machine executes the next state, used to call a state machine <br/> /// </Summary> <br/> /// <Param name = "sender"> </param> <br/> /// <Param name = "E"> </param> <br/> static public void nextstatus (Object sender, eventargs e) <br/>{< br/> m_statehash [currentstatus]. run (); <br/> compositiontarget. rendering-= nextstatus; <br/>}
Too nice. When the next frame is scheduled, the original Stack has been released, and non-blocking calls to the gameengine class methods are implemented!
Debug: Open the stack View bar. It is very nice that there is only one layer of stack ~
Let's use our state machine!
Write two function classes. Load a background image on the main interface and wait for two seconds until the next status is displayed.
These two classes need to inherit from our function class base class and Interface Class
Public class emptystatus: statemachinebaseclass, istatemachine <br/>{< br/> brush brush_instance = new imagebrush {imagesource = resmanager. getimage ("/test/1.jpg") }; <br/> Public void run () <br/>{< br/> engineinstance. rootcanvas. background = brush_instance; <br/> thread. sleep (2000); <br/> runnextstatus (gamestatus. test); <br/>}</P> <p> public class teststatus: statemachinebaseclass, istatemachine <br/>{< br/> brush brush_instance = new imagebrush {imagesource = resmanager. getimage ("/test/2.png") }; <br/> Public void run () <br/>{< br/> engineinstance. rootcanvas. background = brush_instance; <br/> thread. sleep (2000); <br/> runnextstatus (gamestatus. empty); <br/>}< br/>}
Check the result --
The background switch is quite successful.
Stack records are quite pleasing to the eye.
Memory usage is very stable.
This completes our game engine state machine.