Skip to content

Instantly share code, notes, and snippets.

@CoffeeVampir3
Last active October 30, 2022 18:17
Show Gist options
  • Save CoffeeVampir3/f0431af759d84ff4e98867995735c371 to your computer and use it in GitHub Desktop.
Save CoffeeVampir3/f0431af759d84ff4e98867995735c371 to your computer and use it in GitHub Desktop.
Example of a real State Machine
using System;
using UnityEngine;
namespace Vampire.CG
{
[Serializable]
public class CardDraggingState : IState
{
[SerializeField]
private Transform transform;
[SerializeField]
private CanvasGroup canvasGroup;
private Vector3 mouseOffset;
public void Enter()
{
canvasGroup.blocksRaycasts = false;
mouseOffset = transform.position - GraphicsHelper.GetMousePos();
}
public void Exit()
{
canvasGroup.blocksRaycasts = true;
}
public void Update()
{
mouseOffset.x *= .80f;
mouseOffset.y *= .80f;
transform.position = GraphicsHelper.GetMousePos() + mouseOffset;
}
}
}
using System;
using _Testing;
using UnityEngine;
using UnityEngine.EventSystems;
using VampireLibrary;
namespace Vampire.CG
{
public class CardStateMachine : MonoBehaviour, ITargetable,
IDragHandler, IBeginDragHandler, IEndDragHandler
{
[SerializeReference]
private CardDraggingState draggingState = new();
[SerializeReference]
private FloatingToTargetState floatToTargetState = new();
[SerializeReference]
private DroppedOnValidTargetState droppedOnTargetState = new();
private readonly StateMachine stateMachine = new();
private readonly PtrTo<Vector3> sharedV3 = new();
private bool isDragging = false;
public void Awake()
{
sharedV3.referenceValue = transform.position;
var initialState = new IdleState();
floatToTargetState.Init(sharedV3);
droppedOnTargetState.Init(sharedV3);
stateMachine.AddTransition(initialState, draggingState,
() => isDragging && !droppedOnTargetState.HasSuccessfullyDropped);
stateMachine.AddTransition(draggingState, droppedOnTargetState,
() => !isDragging && ScanForValidTargets());
stateMachine.AddTransition(droppedOnTargetState, floatToTargetState,
() => true);
stateMachine.AddTransition(draggingState, floatToTargetState,
() => !isDragging);
stateMachine.AddTransition(floatToTargetState, initialState,
() => floatToTargetState.currentCompletion >= 1f);
stateMachine.SetState(initialState);
}
private bool CheckCosts()
{
if (!TryGetComponent<CardCosts>(out var costs))
{
Debug.Log(
"Costs: ".ColorString(Color.green) + name +
" does not have costs so it is considered free.".ColorString(Color.yellow));
return true;
}
return costs.CheckCosts();
}
private bool CheckOccupation(GameObject dropTarget)
{
return false;
/*
//Is the target occupiable, can we occupy it?
var occupier = GetComponent<IOccupier>();
if (occupier == null)
return true;
return dropTarget is not IOccupiable occupiable || occupiable.CanBeOccupied();
*/
}
private bool CheckBehaviours(GameObject dropTarget)
{
/*
var dropBehaviours = GetComponents<IDroppable>();
foreach (var behaviour in dropBehaviours)
{
//Is this behaviour a valid target?
if (!dropTarget.CanDropOn(behaviour.ValidTargets))
{
Debug.Log("Targeting: ".ColorString(Color.green) + name + "'s behaviour of type: " + behaviour.GetType() +
" was not valid to be dropped on ".ColorString(Color.red) + dropTarget);
continue;
}
droppedOnTargetState.dropBehaviour = behaviour;
return true;
}
*/
return false;
}
private bool ScanForValidTargets()
{
/*
bool droppedOnAnything = transform.TryRaycastFor<IDropTarget>(out var dropTargets);
if (!droppedOnAnything) return false;
foreach (var dropTarget in dropTargets)
{
cardEffectDropTarget.currentTarget = dropTarget.TargetObject;
droppedOnTargetState.dropTarget = dropTarget;
if (CheckBehaviours(dropTarget) //Order matters
&& CheckOccupation(dropTarget)
&& CheckCosts())
{
return true;
}
}
*/
return false;
}
private void Update() => stateMachine.Tick();
//Targetable.
public void OnPointerClick(PointerEventData eventData)
{
if (!TargetingHelper.ValidateTarget(GetComponent<ObjectSymbols>()))
{
eventData.Use();
return;
}
TargetingHelper.OnTargeted(this);
eventData.Use();
}
public bool canDrag = false;
//Draggable.
public void OnBeginDrag(PointerEventData eventData)
{
if (!canDrag)
{
OnPointerClick(eventData);
return;
}
if (!TargetingHelper.ValidateTarget(GetComponent<ObjectSymbols>()))
{
eventData.Use();
return;
}
transform.SetAsLastSibling();
isDragging = true;
}
public void OnEndDrag(PointerEventData eventData)
{
isDragging = false;
}
public void OnDrag(PointerEventData eventData)
{
//Empty
}
}
}
namespace VampireLibrary
{
public static class DictionaryExtension
{
/// <summary>
/// Adds an item to the given dictionary where the value is a list. If the list
/// is uninitialized it creates a new list before adding the item.
/// </summary>
public static void AddListItem<IndexType, ListType, ValueType>
(this Dictionary<IndexType, ListType> dict, IndexType key, ValueType value)
where ListType : IList, new()
{
if (!dict.TryGetValue(key, out var val))
{
val = new ListType();
dict.Add(key, val);
}
val.Add(value);
}
public static void AddHashItem<IndexType, HashType, ValueType>
(this Dictionary<IndexType, HashType> dict, IndexType key, ValueType value)
where HashType : HashSet<ValueType>, new()
{
if (!dict.TryGetValue(key, out var val))
{
val = new HashType();
dict.Add(key, val);
}
val.Add(value);
}
}
}
using System;
using UnityEngine;
namespace Vampire.CG
{
[Serializable]
public class DroppedOnValidTargetState : IState
{
public GameObject dropTarget;
public IDroppable dropBehaviour;
[NonSerialized]
public bool HasSuccessfullyDropped = false;
private PtrTo<Vector3> floatTarget;
[SerializeField]
private GameObject cardObject;
[SerializeField]
private EffectTarget effectDropTarget;
public void Init(PtrTo<Vector3> sharedV3)
{
floatTarget = sharedV3;
}
public void PayCardCosts()
{
if (!cardObject.TryGetComponent<CardCosts>(out var costs))
return;
costs.PayCosts();
}
public void Execute()
{
/*
var occupier = cardObject.GetComponent<IOccupier>();
if (occupier != null && dropTarget is IOccupiable occupiable)
occupiable.Occupy(occupier);
floatTarget.referenceValue = dropTarget.Position;
effectDropTarget.currentTarget = dropTarget.TargetObject;
PayCardCosts();
HasSuccessfullyDropped = true;
dropBehaviour.Run(dropTarget);
*/
}
public void Enter()
{
Execute();
}
public void Exit()
{
//Empty
}
public void Update()
{
//Empty
}
}
}
using System;
using UnityEngine;
namespace Vampire.CG
{
[Serializable]
public class FloatingToTargetState : IState
{
[SerializeField]
private float floatSpeed = 1f;
[SerializeField]
private float floatPower = 1f;
[SerializeField]
private Transform transform;
[NonSerialized]
public float currentCompletion = 0;
private Vector3 startPosition;
private PtrTo<Vector3> endPosition;
private float travelRate;
private float dist;
public void Init(PtrTo<Vector3> sharedTargetPosition)
{
endPosition = sharedTargetPosition;
}
public void Enter()
{
startPosition = transform.position;
currentCompletion = 0f;
dist = Vector2.Distance(endPosition.referenceValue, startPosition);
travelRate = Mathf.Pow(dist, floatPower * floatPower) * floatSpeed;
}
public void Exit()
{
//empty
}
public void Update()
{
currentCompletion += Time.deltaTime * travelRate * travelRate;
transform.position = Vector2.Lerp(startPosition, endPosition.referenceValue, currentCompletion);
}
}
}
namespace Vampire.CG
{
public class IdleState : IState
{
public void Enter()
{
//Empty
}
public void Exit()
{
//Empty
}
public void Update()
{
//Empty
}
}
}
namespace Vampire
{
public interface IState
{
void Enter();
void Exit();
void Update();
}
}
namespace Vampire.CG
{
public class PtrTo<T>
where T : struct
{
public T referenceValue;
public PtrTo() { }
public PtrTo(T initialValue)
{
referenceValue = initialValue;
}
public static implicit operator T(PtrTo<T> val) => val.referenceValue;
}
}
using System;
using System.Collections.Generic;
using VampireLibrary;
namespace Vampire
{
public class StateMachine
{
private IState currentState;
private readonly Dictionary<IState, List<Transition>> allTransitions = new();
private readonly List<Transition> anyTransitions = new();
private static readonly List<Transition> emptyTransitions = new(0);
private List<Transition> currentTransitions;
private record Transition
{
public readonly IState state;
public readonly Func<bool> condition;
public Transition(IState state, Func<bool> condition)
{
this.state = state;
this.condition = condition;
}
}
public void AddTransition(IState from, IState to, Func<bool> condition)
=> allTransitions.AddListItem(from, new Transition(to, condition));
public void AddAnyTransition(IState to, Func<bool> condition)
=> anyTransitions.Add(new Transition(to, condition));
public void SetState(IState targetState)
{
currentState?.Exit();
currentState = targetState;
currentTransitions =
!allTransitions.TryGetValue(currentState, out var transitions) ?
emptyTransitions : transitions;
currentState.Enter();
}
public void Tick()
{
foreach (var trans in anyTransitions)
{
if (!trans.condition.Invoke()) continue;
SetState(trans.state);
goto StateChanged;
}
foreach (var trans in currentTransitions)
{
if (!trans.condition.Invoke()) continue;
SetState(trans.state);
goto StateChanged;
}
StateChanged:
currentState.Update();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment