I have a game object with 4 states: Divisible, Mergeable, Selectable, and Attackable. I am about to put them together to form a StateManager sort of MonoBehaviour script, so the game object will be able to switch to one of the 4 states given when certain conditions and player conditions / actions were met.
What is the best way of designing a state management system for a single game object that is flexible and understandable to use?
Flexible, as in similar to C++/Java switch-case and enum checking.
Understandable, as in, once Update() detects a state has been changed, immediately transition to that state and do state logic accordingly.
However, because Unity is built with Components in mind, I am not familiar with implementing a Component-based state manager, especially for player input and networking. Therefore, I am asking for the best method, recommended by experts in Unity game development.
// To be attached on each game object that can have state change
public class StateChangeable: MonoBehaviour
{
public enum StateType { Divisible, Mergeable, Selectable, Attackable };
public event System.Action<StateChangeable, StateType> EventChangeState;
private StateType state;
public virtual StateType State
{
get
{
return state;
}
set
{
if (state == value)
return;
state = value;
if (EventChangeState != null)
EventChangeState(this, state);
}
}
}
So the idea is to attach this behaviour in objects that can have a state.
Now this class is prepared to be used either by inheritance or as component.
Let’s use as example that there is a Person game object using states.
By inheritance, for example (in this case you just need to attach this one below in each game object):
public class Person: StateChangeable
{
public override StateType State
{
get
{
return base.State;
}
set
{
base.State = value;
if (base.State == StateType.Attackable)
Debug.Log("Now I can attack!");
}
}
}
And now following the component pattern (you have to attach both StateChangeable and Person at same game object):
public class Person : MonoBehaviour
{
StateChangeable StateChangeable;
void Awake()
{
StateChangeable = GetComponent<StateChangeable>();
StateChangeable.EventChangeState += StateChangeable_EventChangeState;
}
void OnDisable()
{
StateChangeable.EventChangeState -= StateChangeable_EventChangeState;
}
void StateChangeable_EventChangeState(StateChangeable stateChangeable, StateChangeable.StateType state)
{
if (state == StateType.Attackable)
Debug.Log("Now I can attack!");
}
}
Both methods are ok to use. A nice way to chose the better approach is to answer the following question for each entity relationship: Is a or Has a. At least in my projects, more common is to have “has a” so I tend to use more components (but also inheritance some times).
Using the example above, the way I designed my “Person” game object is it has 4 states. That means, I need to have 1 StateManager component, plus the number of States that the game object will have, right?
There’s no need for static instances? As in, the StateManager can be unique per instances of the same game object throughout the duration of the game? I am having trouble trying to mash (or visualize) multicasting delegates with networking concepts, so I can send messages from client to server and get them to change states. I thought the game might need a global single instance of StateManager, that handles multiple instances of the game object, “Person”.
Or I am making it more complicated than it sounds?
I think you’re making it out to be more complicated than it sounds.
you can still use enums in C#/Unity. Enums exist!
Have you never done a state machine in Java or C++ that used objects to represent the states? That’s how a component based state machine would work, just that the objects are also components.
what is the “best”? What is defined as the “best”? If you want to have a discussion about what programming practices are better than others, you’re going about it a bit odd of a manner.
Here’s a sample code from one of my projects (I just copy/paste, so you may skip a few of them.):
/**
* ALL WORKS COPYRIGHTED TO The Pokémon Company and Nintendo. I REPEAT, THIS IS A CLONE.
*
* YOU MAY NOT SELL COMMERCIALLY, OR YOU WILL BE PROSECUTED BY The Pokémon Company AND Nintendo.
*
* THE CREATOR IS NOT LIABLE FOR ANY DAMAGES DONE. FOLLOW LOCAL LAWS, BE RESPECTFUL, AND HAVE A GOOD DAY!
* */
package level;
import resources.Art;
import screen.BaseBitmap;
public class PixelData {
// This class contains all of the area's pixel color, pixel's properties, pixel flags to check, etc.
// This object will be loaded along with other PixelData objects when loading an area.
/*
* Pixel data types: (Including alpha values)
*
* 0xFF00FF00: Flat grass (Can be walked, no Pokémon) 0xFF0000DD: Ledges Horizontal 0xFF0000AA: Small tree
*
* Anything else: Flat grass.
*/
// This is also the ID number for the pixel.
private int color;
// If false, it's an obstacle.
private boolean[] facingsBlocked = new boolean[4];
public int xPosition;
public int yPosition;
// This represents the art resource this PixelData object is representing.
// This can also give flexibility when it comes to puzzle-themed areas.
// Now adding animations.
public BaseBitmap[] bitmap;
public BaseBitmap[] biomeBitmap;
public int bitmapTick;
public int biomeBitmapTick;
// This can also be "isWayPoint".
private boolean isWarpZone;
private int targetArea;
private int targetSector;
private int groundHeight;
private boolean triggerFlag;
private int targetMovementScriptID;
public PixelData(int pixel, int x, int y) {
this.xPosition = x;
this.yPosition = y;
this.color = pixel;
this.targetArea = -1;
this.targetSector = -1;
this.groundHeight = 0; // Default
this.bitmapTick = 0;
this.biomeBitmapTick = 0;
this.triggerFlag = false;
this.targetMovementScriptID = 0;
int alpha = (pixel >> 24) & 0xFF;
int red = (pixel >> 16) & 0xFF;
int green = (pixel >> 8) & 0xFF;
int blue = pixel & 0xFF;
setProperties(alpha, red, green, blue);
prepareBitmap(alpha, red, green, blue);
}
public void enableTrigger() {
this.triggerFlag = true;
}
public void disableTrigger() {
this.triggerFlag = false;
}
public boolean hasTriggerEvent() {
return this.triggerFlag;
}
public int getTargetScriptID() {
return this.targetMovementScriptID;
}
public void disableWarpZone() {
this.isWarpZone = false;
}
public void enableWarpZone() {
this.isWarpZone = true;
}
public boolean isWarpZoneEnabled() {
return this.isWarpZone;
}
/**
* Sets the bitmap tile the pixel data is representing.
*
* <p>
* When setting the bitmap, first it must set the bitmap to something other than null. Since the bitmap variable holds an array, it takes in at least 1 bitmap pre-loaded by the Art class. Then, once the bitmap is set, it must break all the way outside of the nested switch conditions, otherwise,
* setting bitmaps will overwrite correct data with incorrect data.
*
* <p>
* If the bitmap stays null, the bitmap will then be set to "NO PNG" error bitmap, which when loaded into the game, the game will not crash, and the developers/players can tell where the bitmap loading has gone wrong.
*
* <p>
* If the bitmap is an animated bitmap, the Art class will load the animated bitmap into an array. The next step would be to just pass the array to this bitmap.
*
* @param alpha
* The alpha value of the pixel data's color.
* @param red
* The red value of the pixel data's color.
* @param green
* The green value of the pixel data's color.
* @param blue
* The blue value of the pixel data's color.
* @return Nothing.
* */
public void prepareBitmap(int alpha, int red, int green, int blue) {
switch (alpha) {
case 0x01: // Path
this.bitmap = new BaseBitmap[1];
// Tile Type
switch (red) {
case 0x00: // Grass Path
this.bitmap[0] = Art.grass;
break;
case 0x01: // Mountain Ground Path
this.bitmap[0] = Art.mt_ground;
break;
case 0x02: // Road Path
this.bitmap[0] = Art.path;
break;
case 0x03: // Hardwood Floor (Indoors)
this.bitmap[0] = Art.hardwood_indoors;
break;
case 0x04: // Tatami Floor Type 1 (Indoors)
this.bitmap[0] = Art.tatami_1_indoors;
break;
case 0x05: // Tatami Floor Type 2 (Indoors)
this.bitmap[0] = Art.tatami_2_indoors;
break;
default:
break;
}
switch (green) { // Area Type
case 0x00:
this.biomeBitmap = new BaseBitmap[1];
this.biomeBitmap[0] = Art.grass; // Forest
break;
case 0x01:
//TODO: Change this biome bitmap to something that represents the city even more.
this.biomeBitmap = new BaseBitmap[1];
this.biomeBitmap[0] = Art.path; //City
case 0x02:
this.biomeBitmap = new BaseBitmap[1];
this.biomeBitmap[0] = Art.mt_ground; // Mountain
break;
case 0x03:
this.biomeBitmap = new BaseBitmap[1];
this.biomeBitmap[0] = Art.water[0];
//TODO: Add more area type biome bitmaps here to the Path. (Refer to documentation.)
default:
break;
}
break;
case 0x02: // Ledge
{
//TODO: Add biome bitmaps to ledge.
this.bitmap = new BaseBitmap[1];
switch (red) {
case 0x00: // Bottom
this.bitmap[0] = Art.ledge_bottom;
break;
case 0x01: // Bottom left
this.bitmap[0] = Art.ledge_bottom_left;
break;
case 0x02: // Right
this.bitmap[0] = Art.ledge_left;
break;
case 0x03: // Top Left
this.bitmap[0] = Art.ledge_top_left;
break;
case 0x04: // Top
this.bitmap[0] = Art.ledge_top;
break;
case 0x05: // Top Right
this.bitmap[0] = Art.ledge_top_right;
break;
case 0x06: // Left
this.bitmap[0] = Art.ledge_right;
break;
case 0x07: // Bottom Right
this.bitmap[0] = Art.ledge_bottom_right;
break;
// ---------------------------------------------------------
case 0x08:
this.bitmap[0] = Art.ledge_mt_bottom;
break;
case 0x09:
this.bitmap[0] = Art.ledge_mt_bottom_left;
break;
case 0x0A:
this.bitmap[0] = Art.ledge_mt_left;
break;
case 0x0B:
this.bitmap[0] = Art.ledge_mt_top_left;
break;
case 0x0C:
this.bitmap[0] = Art.ledge_mt_top;
break;
case 0x0D:
this.bitmap[0] = Art.ledge_mt_top_right;
break;
case 0x0E:
this.bitmap[0] = Art.ledge_mt_right;
break;
case 0x0F:
this.bitmap[0] = Art.ledge_mt_bottom_right;
break;
// ---------------------------------------------------------
case 0x10:
this.bitmap[0] = Art.ledge_inner_bottom;
break;
case 0x11:
this.bitmap[0] = Art.ledge_inner_bottom_left;
break;
case 0x12:
this.bitmap[0] = Art.ledge_inner_left;
break;
case 0x13:
this.bitmap[0] = Art.ledge_inner_top_left;
break;
case 0x14:
this.bitmap[0] = Art.ledge_inner_top;
break;
case 0x15:
this.bitmap[0] = Art.ledge_inner_top_right;
break;
case 0x16:
this.bitmap[0] = Art.ledge_inner_right;
break;
case 0x17:
this.bitmap[0] = Art.ledge_inner_bottom_right;
break;
}
break;
}
case 0x03: // Obstacles
this.bitmap = new BaseBitmap[1];
switch (red) {
case 0x00: // Small Tree
this.bitmap[0] = Art.smallTree;
break;
case 0x01: // Logs
this.bitmap[0] = Art.logs;
break;
case 0x02: // Planks
this.bitmap[0] = Art.planks;
break;
case 0x03: // Scaffolding (Left)
this.bitmap[0] = Art.scaffolding_left;
break;
case 0x04: // Scaffolding (Right)
this.bitmap[0] = Art.scaffolding_right;
break;
case 0x05: // Sign
this.bitmap[0] = Art.sign;
break;
case 0x06: // Workbench Left
this.bitmap[0] = Art.workbench_left;
break;
case 0x07: // Workbench Right
this.bitmap[0] = Art.workbench_right;
break;
case 0x08: // Dead small tree
this.bitmap[0] = Art.deadSmallTree;
break;
}
break;
case 0x04: // Warp point (Refer to documentation for flaws.)
this.bitmap = new BaseBitmap[1];
this.bitmap[0] = Art.forestEntrance;
break;
case 0x05: // Area Sector Point (Refer to documentation.)
this.bitmap = new BaseBitmap[1];
// TODO: Add new bitmaps for connection points to make them blend in with the surroundings.
// TODO: Create more biome bitmaps.
this.bitmap[0] = Art.grass;
break;
case 0x06: // Stairs
this.bitmap = new BaseBitmap[1];
switch (red) {
case 0x00:
this.bitmap[0] = Art.stairs_bottom;
break;
case 0x01:
this.bitmap[0] = Art.stairs_left;
break;
case 0x02:
this.bitmap[0] = Art.stairs_top;
break;
case 0x03:
this.bitmap[0] = Art.stairs_right;
break;
case 0x04:
this.bitmap[0] = Art.stairs_mt_bottom;
break;
case 0x05:
this.bitmap[0] = Art.stairs_mt_left;
break;
case 0x06:
this.bitmap[0] = Art.stairs_mt_top;
break;
case 0x07:
this.bitmap[0] = Art.stairs_mt_right;
break;
}
break;
case 0x07: { // Water
// Always start with the first frame of any animation.
// TODO: Add more water tiles with borders.
// FIXME: Make the borders a bit more thicker.
switch (red) {
case 0x00: // Pure water, no border.
this.bitmap = Art.water;
break;
case 0x01: // Left Border
this.bitmap = Art.water_left;
break;
case 0x02: // Top Left Border
this.bitmap = Art.water_top_left;
break;
case 0x03: // Top Border
this.bitmap = Art.water_top;
break;
case 0x04: // Top Right Border
this.bitmap = Art.water_top_right;
break;
case 0x05: // Right Border
this.bitmap = Art.water_right;
break;
}
break;
}
case 0x08: // House
this.bitmap = new BaseBitmap[1];
switch (red) { // House related tiles. Way too many to list them orderly.
case 0x00: // Bottom wall
this.bitmap[0] = Art.house_bottom;
break;
case 0x01: // Bottom left wall
this.bitmap[0] = Art.house_bottom_left;
break;
case 0x02: // Bottom right wall
this.bitmap[0] = Art.house_bottom_right;
break;
case 0x03: //Center wall
this.bitmap[0] = Art.house_center;
break;
case 0x04: //Center wall with windows in center
this.bitmap[0] = Art.house_center_windows_center;
break;
case 0x05: //Center wall with windows on left
this.bitmap[0] = Art.house_center_windows_left;
break;
case 0x06: //Center wall with windows on right
this.bitmap[0] = Art.house_center_windows_right;
break;
case 0x07: //Left wall
this.bitmap[0] = Art.house_left;
break;
case 0x08: //Left wall with windows on right
this.bitmap[0] = Art.house_left_windows_right;
break;
case 0x09: //Right wall
this.bitmap[0] = Art.house_right;
break;
case 0x0A: //Right wall with windows on left
this.bitmap[0] = Art.house_right_windows_left;
break;
case 0x0B: // Single Roof left
this.bitmap[0] = Art.changeColors(Art.house_roof_left, WorldConstants.convertToAreaColor(green));
// this.bitmap[0] = Art.house_roof_left;
break;
case 0x0C: // Single Roof middle
this.bitmap[0] = Art.changeColors(Art.house_roof_middle, WorldConstants.convertToAreaColor(green));
break;
case 0x0D: // Single Roof right
this.bitmap[0] = Art.changeColors(Art.house_roof_right, WorldConstants.convertToAreaColor(green));
break;
}
break;
case 0x09: // House Door
this.bitmap = new BaseBitmap[1];
this.bitmap[0] = Art.house_door;
break;
case 0x0A: // Item
this.bitmap = new BaseBitmap[1];
this.bitmap[0] = Art.item;
break;
case 0x0B: // Carpet Floor (Indoors)
this.bitmap = new BaseBitmap[1];
this.bitmap[0] = Art.carpet_indoors;
this.biomeBitmap = Art.exit_arrow;
break;
case 0x0C: // Carpet Floors (Outdoors)
this.bitmap = new BaseBitmap[1];
this.bitmap[0] = Art.carpet_outdoors;
this.biomeBitmap = Art.exit_arrow;
break;
case 0x0D: //Starting position when game has initialized;
this.bitmap = new BaseBitmap[1];
switch (red){
case 0x01:
default:
this.bitmap[0] = Art.grass;
break;
}
break;
default: // Any other type of tiles.
break;
}
if (this.bitmap == null) {
this.bitmap = new BaseBitmap[1];
this.bitmap[0] = Art.error;
}
if (this.biomeBitmap == null) {
this.biomeBitmap = new BaseBitmap[1];
this.biomeBitmap[0] = Art.grass; // By default, biome bitmap should be grass.
}
}
/**
* Prepares the bitmap if the color has been pre-determined.
*
* <p>
* Will cause undefined behaviors if the colors have not been set yet.
*
* @return Nothing.
* */
public void prepareBitmap() {
int alpha = (this.color >> 24) & 0xFF;
int red = (this.color >> 16) & 0xFF;
int green = (this.color >> 8) & 0xFF;
int blue = this.color & 0xFF;
this.prepareBitmap(alpha, red, green, blue);
}
/**
* Sets the properties of a given pixel data. This is where the game gets the area's information on what the player should do and don't.
*
* <p>
* Some of the features are currently unused. Especially collision detection.
*
* <p>
* Only the ones that set target areas, warp zone areas, etc. are the ones being used.
*
* @param alpha
* The alpha value of the pixel data's color.
* @param red
* The red value of the pixel data's color.
* @param green
* The green value of the pixel data's color.
* @param blue
* The blue value of the pixel data's color.
* @return Nothing.
* */
public void setProperties(int alpha, int red, int green, int blue) {
// TODO: Refactor the code to make it more readable and more modular than if...elses.
this.targetArea = 0;
this.isWarpZone = false;
this.groundHeight = 0;
switch (alpha) {
case 0x01: // Grass
this.groundHeight = blue;
break;
case 0x02: // Ledges
break;
case 0x03: // Obstacles
this.facingsBlocked[0] = this.facingsBlocked[1] = this.facingsBlocked[2] = this.facingsBlocked[3] = false;
break;
case 0x04: // Warp Point
// this.targetArea = red;
// FIXME: Level Editor must set target area ID to at least 1 or above.
this.targetArea = WorldConstants.isModsEnabled.booleanValue() ? red + 1001 : red;
this.isWarpZone = true;
// this.facingsBlocked[0] = this.facingsBlocked[1] = this.facingsBlocked[2] = this.facingsBlocked[3] = true;
break;
case 0x05: // ACP (Refer to documentation.)
// this.targetArea = red;
// FIXME: Level Editor must set target area ID to at least 1 or above.
this.targetArea = WorldConstants.isModsEnabled.booleanValue() ? red + 1001 : red;
this.targetSector = green;
this.isWarpZone = false;
// this.facingsBlocked[0] = this.facingsBlocked[1] = this.facingsBlocked[2] = this.facingsBlocked[3] = true;
break;
case 0x06: // Stairs
break;
case 0x07: // Water
// TODO: Needs to do something with this. It must not block the player, however, without special boolean value, it will always block player from advancing.
// this.facingsBlocked[0] = this.facingsBlocked[1] = this.facingsBlocked[2] = this.facingsBlocked[3] = true;
case 0x08: // House
// this.facingsBlocked[0] = this.facingsBlocked[1] = this.facingsBlocked[2] = this.facingsBlocked[3] = false;
break;
case 0x09: // House Door
// this.targetArea = red;
// FIXME: Level Editor must set target area ID to at least 1 or above.
this.targetArea = WorldConstants.isModsEnabled.booleanValue() ? red + 1001 : red;
this.isWarpZone = true;
break;
case 0x0A: // Item
break;
case 0x0B: // Carpets
case 0x0C: // Carpets
// FIXME: Level Editor must set target area ID to at least 1 or above.
this.targetArea = WorldConstants.isModsEnabled.booleanValue() ? red + 1001 : red;
break;
case 0x0D: //Default Starting Position
break;
default:
this.facingsBlocked[0] = this.facingsBlocked[1] = this.facingsBlocked[2] = this.facingsBlocked[3] = false;
break;
}
}
public int getColor() {
return this.color;
}
// public int getParentAreaID() {
// return parentArea;
// }
public int getTargetAreaID() {
return targetArea;
}
public boolean[] isWalkThroughable() {
return this.facingsBlocked;
}
public int getTargetSectorID() {
return this.targetSector;
}
public void tick() {
this.bitmapTick++;
if (this.bitmapTick >= this.bitmap.length)
this.bitmapTick = 0;
this.biomeBitmapTick++;
if (this.biomeBitmapTick >= this.biomeBitmap.length)
this.biomeBitmapTick = 0;
}
public BaseBitmap getBitmap() {
return this.bitmap[this.bitmapTick];
}
public int getGroundHeight() {
return this.groundHeight;
}
public BaseBitmap getBiomeBitmap() {
return this.biomeBitmap[this.biomeBitmapTick];
}
}
Yes, I have. I am asking if there are better ways of implementing states that are recommended and used in Unity, and how it is used, henceforth.
In the code above, I used static instances rather than objects used to represent states. I have trouble picturing how multiple state managers on a single component work together. My game involves using 1 game object for everything (well, state managers if this is considered), and it is by design, hence it is tough to visualize.
You have a misspelling in #region. It was spelled, “#regoin Properties”
I will see what I can do with the component-based states. If I have any other questions regarding component-based state machines, I will post in a new thread.
Alright, maybe that is way too subjective for me to define. “Commonly used/recommended” is a better way to put it?.
Sorry if it sounds rude, but I guess that you need a firmer grasp on component system inside Unity, the Monobehaviour worflow and associated events. It’s obvious that you have good experience in general programming but I recommend a step back and check manuals, tutorials from the start on using Unity.
I already watched everything. I already done multiple practice projects, as well as finished Unity game prototypes. I am just new to this Event Manager programming design, particularly. I am really close to getting it to work, is just that I need a bit more examples. That is all.
Sorry to give you such a bad impression. I didn’t really mean it.
I see. Let me explain in more details the example code that you replied with doubts.
The idea is to have the state class as a limited behaviour to be attached in each object. So, for example, a GameObject (in scene or as a prefab) named Person would have 3 attached behaviours: Transform (default), StateChangeable and Person. The person get the stateChangeable reference at the awake, and store it for later use:
public class Person : MonoBehaviour
{
// A pointer to the state changeable component attached to this same game object
StateChangeable StateChangeable;
void Awake()
{
// Get the instance on the same object
StateChangeable = GetComponent<StateChangeable>();
// Register as observer on state change event
StateChangeable.EventChangeState += StateChangeable_EventChangeState;
}
void OnDisable()
{
// Unregister from state change event
StateChangeable.EventChangeState -= StateChangeable_EventChangeState;
}
// This is the handler for state change event
void StateChangeable_EventChangeState(StateChangeable stateChangeable, StateChangeable.StateType state)
{
// Just a example on the action to this new state
if (state == StateType.Attackable)
Debug.Log("Now I can attack!");
}
}
This is all. Any change on the state event on this object will be captured by this person script using it in component way.
For example, you could have another script attached that changes the state on the StateChangeable in this object with information from a remote source in a multiplayer environment.
Sounds like I don’t need to implement it along with Update() or OnGUI() in the EventManager class. But my main doubts are still not answered, which is how you would trigger the events. Does the event needs to be triggered one time for that state, or only when the state changes? The more I write this event handling code, the more I felt I am reinventing the wheel, since all of these can be triggered with just Input.GetMouse/Down/Up() methods.
//Events
//Events must be void and have 0 or 1 parameter.
public delegate void OnLeftClickEvent(GameObject go);
public event OnLeftClickEvent OnLeftClickUp;
public event OnLeftClickEvent OnLeftClickHold;
public event OnLeftClickEvent OnLeftClickDown;
public void Update() {
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 100f)) {
if (Input.GetMouseButtonUp(0)){
if (OnLeftClickUp != null) {
OnLeftClickUp(hit.transform.gameObject);
}
}
else if (Input.GetMouseButtonDown(0)){
if (OnLeftClickDown != null) {
OnLeftClickDown(hit.transform.gameObject);
}
}
else if (Input.GetMouseButton(0)) {
if (OnLeftClickHold != null) {
OnLeftClickHold(hit.transform.gameObject);
}
}
}
}
using UnityEngine;
using System.Collections;
public class SelectState : MonoBehaviour {
public void Awake() {
EventManager.Instance.OnLeftClickUp += SelectState_OnLeftClickUp;
EventManager.Instance.OnLeftClickDown += SelectState_OnLeftClickDown;
EventManager.Instance.OnLeftClickHold += SelectState_OnLeftClickHold;
}
public void OnDisable() {
EventManager.Instance.OnLeftClickUp -= SelectState_OnLeftClickUp;
EventManager.Instance.OnLeftClickDown -= SelectState_OnLeftClickDown;
EventManager.Instance.OnLeftClickHold -= SelectState_OnLeftClickHold;
}
public void SelectState_OnLeftClickUp(GameObject go) {
if (go != null && go.Equals(this.gameObject)) {
//Do action after releasing mouse button.
}
}
public void SelectState_OnLeftClickDown(GameObject go) {
if (go != null && go.Equals(this.gameObject)) {
//Do action after clicking mouse button.
}
}
public void SelectState_OnLeftClickHold(GameObject go) {
if (go != null && go.Equals(this.gameObject)) {
//Do action after holding mouse button.
}
}
}