2025-09-10 10:53:04 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// SURGE FRAMEWORK
|
|
|
|
|
|
/// Author: Bob Berkebile
|
|
|
|
|
|
/// Email: bobb@pixelplacement.com
|
|
|
|
|
|
///
|
|
|
|
|
|
/// StateMachine main class.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
|
|
|
|
// Used to disable the lack of usage of the exception in a try/catch:
|
|
|
|
|
|
#pragma warning disable 168
|
|
|
|
|
|
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using System.Collections;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System;
|
|
|
|
|
|
using UnityEngine.Events;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Pixelplacement
|
|
|
|
|
|
{
|
|
|
|
|
|
[RequireComponent (typeof (Initialization))]
|
Refactoring of the interaction system and preliminary integration of save/load functionality across the game. (#44)
### Interactables Architecture Refactor
- Converted composition to inheritance, moved from component-based to class-based interactables. No more requirement for chain of "Interactable -> Item" etc.
- Created `InteractableBase` abstract base class with common functionality that replaces the old component
- Specialized child classes: `Pickup`, `ItemSlot`, `LevelSwitch`, `MinigameSwitch`, `CombinationItem`, `OneClickInteraction` are now children classes
- Light updates to the interactable inspector, moved some things arround, added collapsible inspector sections in the UI for better editor experience
### State Machine Integration
- Custom `AppleMachine` inheritong from Pixelplacement's StateMachine which implements our own interface for saving, easy place for future improvements
- Replaced all previous StateMachines by `AppleMachine`
- Custom `AppleState` extends from default `State`. Added serialization, split state logic into "EnterState", "RestoreState", "ExitState" allowing for separate logic when triggering in-game vs loading game
- Restores directly to target state without triggering transitional logic
- Migration tool converts existing instances
### Prefab Organization
- Saved changes from scenes into prefabs
- Cleaned up duplicated components, confusing prefabs hierarchies
- Created prefab variants where possible
- Consolidated Environment prefabs and moved them out of Placeholders subfolder into main Environment folder
- Organized item prefabs from PrefabsPLACEHOLDER into proper Items folder
- Updated prefab references - All scene references updated to new locations
- Removed placeholder files from Characters, Levels, UI, and Minigames folders
### Scene Updates
- Quarry scene with major updates
- Saved multiple working versions (Quarry, Quarry_Fixed, Quarry_OLD)
- Added proper lighting data
- Updated all interactable components to new architecture
### Minor editor tools
- New tool for testing cards from an editor window (no in-scene object required)
- Updated Interactable Inspector
- New debug option to opt in-and-out of the save/load system
- Tooling for easier migration
Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com>
Reviewed-on: https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction/pulls/44
2025-11-03 10:12:51 +00:00
|
|
|
|
public class StateMachine : MonoBehaviour
|
2025-09-10 10:53:04 +02:00
|
|
|
|
{
|
|
|
|
|
|
//Public Variables:
|
|
|
|
|
|
public GameObject defaultState;
|
|
|
|
|
|
public GameObject currentState;
|
|
|
|
|
|
public bool _unityEventsFolded;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Should log messages be thrown during usage?
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[Tooltip("Should log messages be thrown during usage?")]
|
|
|
|
|
|
public bool verbose;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Can States within this StateMachine be reentered?
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[Tooltip("Can States within this StateMachine be reentered?")]
|
|
|
|
|
|
public bool allowReentry = false;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Return to default state on disable?
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[Tooltip("Return to default state on disable?")]
|
|
|
|
|
|
public bool returnToDefaultOnDisable = true;
|
|
|
|
|
|
|
|
|
|
|
|
//Publice Events:
|
|
|
|
|
|
public GameObjectEvent OnStateExited;
|
|
|
|
|
|
public GameObjectEvent OnStateEntered;
|
|
|
|
|
|
public UnityEvent OnFirstStateEntered;
|
|
|
|
|
|
public UnityEvent OnFirstStateExited;
|
|
|
|
|
|
public UnityEvent OnLastStateEntered;
|
|
|
|
|
|
public UnityEvent OnLastStateExited;
|
|
|
|
|
|
|
|
|
|
|
|
//Public Properties:
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Internal flag used to determine if the StateMachine is set up properly.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public bool CleanSetup
|
|
|
|
|
|
{
|
|
|
|
|
|
get;
|
|
|
|
|
|
private set;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Are we at the first state in this state machine.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public bool AtFirst
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
return _atFirst;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private set
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_atFirst)
|
|
|
|
|
|
{
|
|
|
|
|
|
_atFirst = false;
|
|
|
|
|
|
if (OnFirstStateExited != null) OnFirstStateExited.Invoke ();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
_atFirst = true;
|
|
|
|
|
|
if (OnFirstStateEntered != null) OnFirstStateEntered.Invoke ();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Are we at the last state in this state machine.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public bool AtLast
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
return _atLast;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private set
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_atLast)
|
|
|
|
|
|
{
|
|
|
|
|
|
_atLast = false;
|
|
|
|
|
|
if (OnLastStateExited != null) OnLastStateExited.Invoke ();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
_atLast = true;
|
|
|
|
|
|
if (OnLastStateEntered != null) OnLastStateEntered.Invoke ();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//Private Variables:
|
|
|
|
|
|
bool _initialized;
|
|
|
|
|
|
bool _atFirst;
|
|
|
|
|
|
bool _atLast;
|
|
|
|
|
|
|
|
|
|
|
|
//Public Methods:
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Change to the next state if possible.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public GameObject Next (bool exitIfLast = false)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (currentState == null) return ChangeState (0);
|
|
|
|
|
|
int currentIndex = currentState.transform.GetSiblingIndex();
|
|
|
|
|
|
if (currentIndex == transform.childCount - 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (exitIfLast)
|
|
|
|
|
|
{
|
|
|
|
|
|
Exit();
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
return currentState;
|
|
|
|
|
|
}
|
|
|
|
|
|
}else{
|
|
|
|
|
|
return ChangeState (++currentIndex);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Change to the previous state if possible.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public GameObject Previous (bool exitIfFirst = false)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (currentState == null) return ChangeState(0);
|
|
|
|
|
|
int currentIndex = currentState.transform.GetSiblingIndex();
|
|
|
|
|
|
if (currentIndex == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (exitIfFirst)
|
|
|
|
|
|
{
|
|
|
|
|
|
Exit();
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
return currentState;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else{
|
|
|
|
|
|
return ChangeState(--currentIndex);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Exit the current state.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void Exit ()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (currentState == null) return;
|
|
|
|
|
|
Log ("(-) " + name + " EXITED state: " + currentState.name);
|
|
|
|
|
|
int currentIndex = currentState.transform.GetSiblingIndex ();
|
|
|
|
|
|
|
|
|
|
|
|
//no longer at first:
|
|
|
|
|
|
if (currentIndex == 0) AtFirst = false;
|
|
|
|
|
|
|
|
|
|
|
|
//no longer at last:
|
|
|
|
|
|
if (currentIndex == transform.childCount - 1) AtLast = false;
|
|
|
|
|
|
|
|
|
|
|
|
if (OnStateExited != null) OnStateExited.Invoke (currentState);
|
|
|
|
|
|
currentState.SetActive (false);
|
|
|
|
|
|
currentState = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Changes the state.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public GameObject ChangeState (int childIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (childIndex > transform.childCount-1)
|
|
|
|
|
|
{
|
|
|
|
|
|
Log("Index is greater than the amount of states in the StateMachine \"" + gameObject.name + "\" please verify the index you are trying to change to.");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return ChangeState(transform.GetChild(childIndex).gameObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Changes the state.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public GameObject ChangeState (GameObject state)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (currentState != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!allowReentry && state == currentState)
|
|
|
|
|
|
{
|
|
|
|
|
|
Log("State change ignored. State machine \"" + name + "\" already in \"" + state.name + "\" state.");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (state.transform.parent != transform)
|
|
|
|
|
|
{
|
|
|
|
|
|
Log("State \"" + state.name + "\" is not a child of \"" + name + "\" StateMachine state change canceled.");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Exit();
|
|
|
|
|
|
Enter(state);
|
|
|
|
|
|
|
|
|
|
|
|
return currentState;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Changes the state.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public GameObject ChangeState (string state)
|
|
|
|
|
|
{
|
|
|
|
|
|
Transform found = transform.Find(state);
|
|
|
|
|
|
if (!found)
|
|
|
|
|
|
{
|
|
|
|
|
|
Log("\"" + name + "\" does not contain a state by the name of \"" + state + "\" please verify the name of the state you are trying to reach.");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return ChangeState(found.gameObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Internally used within the framework to auto start the state machine.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void Initialize()
|
|
|
|
|
|
{
|
|
|
|
|
|
//turn off all states:
|
|
|
|
|
|
for (int i = 0; i < transform.childCount; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
transform.GetChild(i).gameObject.SetActive(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Internally used within the framework to auto start the state machine.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void StartMachine ()
|
|
|
|
|
|
{
|
|
|
|
|
|
//start the machine:
|
|
|
|
|
|
if (Application.isPlaying && defaultState != null) ChangeState (defaultState.name);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//Private Methods:
|
|
|
|
|
|
void Enter (GameObject state)
|
|
|
|
|
|
{
|
|
|
|
|
|
currentState = state;
|
|
|
|
|
|
int index = currentState.transform.GetSiblingIndex ();
|
|
|
|
|
|
|
|
|
|
|
|
//entering first:
|
|
|
|
|
|
if (index == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
AtFirst = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//entering last:
|
|
|
|
|
|
if (index == transform.childCount - 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
AtLast = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Log( "(+) " + name + " ENTERED state: " + state.name);
|
|
|
|
|
|
if (OnStateEntered != null) OnStateEntered.Invoke (currentState);
|
|
|
|
|
|
currentState.SetActive (true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Log (string message)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!verbose) return;
|
|
|
|
|
|
Debug.Log (message, gameObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|