Skip to content

Instantly share code, notes, and snippets.

@tomaszczerminski
Created February 15, 2021 09:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomaszczerminski/130e49310a6cae86504453e11758f634 to your computer and use it in GitHub Desktop.
Save tomaszczerminski/130e49310a6cae86504453e11758f634 to your computer and use it in GitHub Desktop.
using System.Diagnostics.CodeAnalysis;
using Sirenix.OdinInspector;
using Sirenix.Serialization;
namespace SDK.Gameplay.States
{
// We need it in order to enable/disable states
[SuppressMessage("ReSharper", "Unity.RedundantEventFunction")]
public abstract class AbstractState : SerializedMonoBehaviour
{
private void OnEnable()
{
}
private void OnDisable()
{
}
}
}
using SDK.Gameplay.States;
using Sirenix.OdinInspector;
using UnityEngine;
using Random = UnityEngine.Random;
namespace SDK.Gameplay.Unit.States
{
public sealed class Dead : AbstractState
{
private static readonly int DieAnimation = Animator.StringToHash("die");
[ShowInInspector] private AudioSource AudioSource { get; set; }
[ShowInInspector] private Animator Animator { get; set; }
public void Enter()
{
enabled = true;
AudioSource.volume = Random.Range(0.5f, 0.7f);
AudioSource.pitch = Random.Range(0.7f, 1f);
Animator.SetTrigger(DieAnimation);
}
private void Awake()
{
AudioSource = GetComponent<AudioSource>();
Animator = GetComponent<Animator>();
}
}
}
using SDK.Gameplay.Commands;
using SDK.Gameplay.States;
using Sirenix.OdinInspector;
using UniRx;
using UnityEngine;
namespace SDK.Gameplay.Unit.States
{
public sealed class Idle : AbstractState
{
private static readonly int IdleAnimation = Animator.StringToHash("idle_1");
private static readonly IMessageBroker Broker = MessageBroker.Default;
[ShowInInspector] private Animator Animator { get; set; }
[ShowInInspector] private UsingSkill UsingSkill { get; set; }
[ShowInInspector] private TakingDamage TakingDamage { get; set; }
[ShowInInspector] private TakingHeal TakingHeal { get; set; }
[ShowInInspector] private Moving Moving { get; set; }
[ShowInInspector] private Entity Entity { get; set; }
public void Enter()
{
enabled = true;
Animator.SetTrigger(IdleAnimation);
}
private void Exit()
{
enabled = false;
}
private void Awake()
{
Entity = GetComponent<Entity>();
Animator = GetComponent<Animator>();
UsingSkill = GetComponent<UsingSkill>();
TakingDamage = GetComponent<TakingDamage>();
Moving = GetComponent<Moving>();
Broker.Receive<AttackCommand>()
.Where(_ => isActiveAndEnabled)
.Where(command => int.Parse(command.UnitId) == Entity.Id)
.Do(_ => Exit())
.Subscribe(command => UsingSkill.Enter(command.Target, command.Skill))
.AddTo(this);
Broker.Receive<TakeDamageCommand>()
.Where(_ => isActiveAndEnabled)
.Where(command => command.Target.Id == Entity.Id)
.Do(_ => Exit())
.Subscribe(command => TakingDamage.Enter(command.Damage))
.AddTo(this);
Broker.Receive<MoveCommand>()
.Where(_ => isActiveAndEnabled)
.Where(command => command.Entity.Id == Entity.Id)
.Subscribe(command => Moving.Enter(null))
.AddTo(this);
Broker.Receive<TakeHealCommand>()
.Where(_ => isActiveAndEnabled)
.Where(command => command.Target.Id == Entity.Id)
.Subscribe(command => TakingHeal.Enter(command.Heal))
.AddTo(this);
}
}
}
using System.Collections;
using SDK.Gameplay.States;
using Sirenix.OdinInspector;
using UniRx;
using UnityEngine;
namespace SDK.Gameplay.Unit.States
{
public class Invisible : AbstractState
{
[ShowInInspector] private Visible Visible { get; set; }
[ShowInInspector] private SpriteRenderer[] Renderers { get; set; }
private const float Speed = 0.5f;
public void Enter()
{
enabled = true;
MainThreadDispatcher.StartFixedUpdateMicroCoroutine(HideRenderers());
}
private IEnumerator HideRenderers()
{
var visible = true;
while (visible)
{
// ReSharper disable once LocalVariableHidesMember
foreach (var renderer in Renderers)
{
var color = renderer.color;
color = new Color
{
r = color.r,
g = color.g,
b = color.b,
a = color.a - Speed * Time.fixedDeltaTime
};
renderer.color = color;
visible = visible && color.a != 0;
}
yield return false;
}
}
private void Exit()
{
enabled = false;
Visible.Enter();
}
private void Awake()
{
Renderers = GetComponentsInChildren<SpriteRenderer>();
Visible = GetComponent<Visible>();
}
}
}
using System.Collections;
using JetBrains.Annotations;
using SDK.Gameplay.States;
using SDK.Gameplay.Values;
using Sirenix.OdinInspector;
using UniRx;
using UnityEngine;
using UnityEngine.Rendering;
namespace SDK.Gameplay.Unit.States
{
public sealed class Moving : AbstractState
{
private static readonly int MoveAnimation = Animator.StringToHash("move");
[ShowInInspector] public SortingGroup SortingGroup { get; private set; }
[ShowInInspector] private AudioSource AudioSource { get; set; }
[ShowInInspector] private HealthBar HealthBar { get; set; }
[ShowInInspector] private Animator Animator { get; set; }
[ShowInInspector] private Idle Idle { get; set; }
[ShowInInspector] private Entity Entity { get; set; }
[UsedImplicitly]
public void OnStep()
{
}
[UsedImplicitly]
public void OnMoveStarted()
{
}
[UsedImplicitly]
public void OnMoveFinished()
{
Exit();
Idle.Enter();
}
public void Enter(Tile tile)
{
enabled = true;
Animator.SetTrigger(MoveAnimation);
MainThreadDispatcher.StartUpdateMicroCoroutine(Move(tile));
}
private void Exit()
{
enabled = false;
Idle.Enter();
}
private IEnumerator Move(Tile tile)
{
var current = Entity.CurrentTile;
SortingGroup.sortingOrder = current.Row > tile.Row
? tile.SortingOrder
: current.SortingOrder;
// Entity.CurrentTile = tile;
var position = tile.transform.position;
TurnTowards(position);
var target = new Vector2
{
x = position.x,
y = position.y + 1
};
var time = Time.time;
var start = transform.position;
var distance = Vector3.Distance(start, target);
while (new Vector2(transform.position.x - target.x, transform.position.y - target.y) != Vector2.zero)
{
var covered = (Time.time - time) * 65;
var fraction = covered / distance;
transform.position = Vector3.Lerp(start, target, fraction * Time.fixedDeltaTime);
yield return false;
}
SortingGroup.sortingOrder = tile.SortingOrder;
}
private void TurnTowards(Vector3 target)
{
var position = transform.position;
// ReSharper disable once Unity.InefficientPropertyAccess
var isFacingLeft = (int) transform.rotation.y != 0;
// ReSharper disable once InvertIf
if (isFacingLeft && position.x < target.x || !isFacingLeft && position.x > target.x)
{
transform.Rotate(0, isFacingLeft ? -180 : 180, 0, Space.Self);
HealthBar.transform.Rotate(0, isFacingLeft ? -180 : 180, 0, Space.Self);
}
}
private void Awake()
{
Entity = GetComponent<Entity>();
SortingGroup = GetComponent<SortingGroup>();
AudioSource = GetComponent<AudioSource>();
Animator = GetComponent<Animator>();
Idle = GetComponent<Idle>();
}
}
}
using System;
using JetBrains.Annotations;
using SDK.Extensions;
using SDK.Gameplay.Events.Infrastructure;
using SDK.Gameplay.States;
using SDK.Gameplay.Values;
using SDK.Tools;
using Sirenix.OdinInspector;
using Sirenix.Serialization;
using UniRx;
using UnityEngine;
using Logger = SDK.Tools.Logger;
namespace SDK.Gameplay.Unit.States
{
public sealed class TakingDamage : AbstractState
{
private static readonly int[] TakeDamageAnimations =
{
Animator.StringToHash("take-damage_1"),
Animator.StringToHash("take-damage_2")
};
private static readonly IMessageBroker Broker = MessageBroker.Default;
[OdinSerialize] [Required] private Popup Popup { get; set; }
[OdinSerialize] [Required] private Vector3 PopupOffset { get; set; }
[ShowInInspector] private HealthBar HealthBar { get; set; }
[ShowInInspector] private Animator Animator { get; set; }
[ShowInInspector] private Idle Idle { get; set; }
[ShowInInspector] private Dead Dead { get; set; }
[ShowInInspector] private Entity Entity { get; set; }
private Logger _logger;
[UsedImplicitly]
public void TakeDamage()
{
}
[UsedImplicitly]
public void OnTakeDamageFinished()
{
Exit();
if (Entity.CurrentHealth >= 0)
{
Idle.Enter();
return;
}
Dead.Enter();
}
public void Enter(int damage)
{
enabled = true;
Animator.SetTrigger(TakeDamageAnimations.Random());
// some effects - like heal or stun - may deal no damage
if (damage == 0) return;
var current = Entity.CurrentHealth;
damage = Math.Min(current, Math.Abs(damage));
Entity.CurrentHealth -= damage;
CreatePopup(damage);
}
private void CreatePopup(int damage)
{
var popup = Instantiate(Popup);
popup.Value = damage;
popup.transform.position = transform.position + PopupOffset;
}
private void Exit()
{
enabled = false;
Idle.Enter();
}
private void Awake()
{
HealthBar = GetComponentInChildren<HealthBar>();
Animator = GetComponent<Animator>();
Idle = GetComponent<Idle>();
Dead = GetComponent<Dead>();
_logger = LogManager.GetLogger(name, typeof(TakingDamage));
}
}
}
using SDK.Gameplay.States;
using SDK.Gameplay.Values;
using Sirenix.OdinInspector;
using Sirenix.Serialization;
using UniRx;
using UnityEngine;
namespace SDK.Gameplay.Unit.States
{
public class TakingHeal : AbstractState
{
[OdinSerialize] [Required] private Popup Popup { get; set; }
[OdinSerialize] [Required] private Vector3 PopupOffset { get; set; }
[ShowInInspector] private ReactiveProperty<int> MaximumHealth { get; set; }
[ShowInInspector] private ReactiveProperty<int> CurrentHealth { get; set; }
[ShowInInspector] private Idle Idle { get; set; }
public void Enter(int heal)
{
enabled = true;
CreatePopup(heal);
Exit();
}
private void CreatePopup(int heal)
{
var popup = Instantiate(Popup);
popup.Value = heal;
popup.transform.position = transform.position + PopupOffset;
}
private void Exit()
{
enabled = false;
Idle.Enter();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using SDK.Gameplay.States;
using SDK.Gameplay.Values;
using Sirenix.OdinInspector;
using Sirenix.Serialization;
using UniRx;
using UnityEngine;
namespace SDK.Gameplay.Unit.States
{
public sealed class UsingSkill : AbstractState
{
private static readonly IMessageBroker Broker = MessageBroker.Default;
[OdinSerialize] [Required] private IList<Skill> Skills { get; set; }
[ShowInInspector] private HealthBar HealthBar { get; set; }
[ShowInInspector] private AudioSource AudioSource { get; set; }
[ShowInInspector] private Animator Animator { get; set; }
[ShowInInspector] private Idle Idle { get; set; }
private Skill _skill;
private Entity _target;
[UsedImplicitly]
public void UseSkill()
{
_skill.Activate();
}
[UsedImplicitly]
public void OnAnimationFinished()
{
Exit();
Idle.Enter();
}
public void Enter(Entity target, string skillId)
{
var skill = Skills.FirstOrDefault(_ => _.Id == skillId) ??
throw new ArgumentOutOfRangeException(
$"GAME OBJECT: {name}, METHOD: {nameof(Enter)}, PARAMETER: {nameof(skillId)}, VALUE: {skillId}");
_skill = skill;
_target = target;
_skill.Target = _target;
enabled = true;
Animator.SetTrigger(skill.Id);
}
private void Exit()
{
enabled = false;
Idle.Enter();
}
private void Awake()
{
HealthBar = GetComponentInChildren<HealthBar>();
AudioSource = GetComponent<AudioSource>();
Animator = GetComponent<Animator>();
Idle = GetComponent<Idle>();
}
}
}
using System;
using System.Collections;
using SDK.Gameplay.Unit.States;
using Sirenix.OdinInspector;
using UniRx;
using UnityEngine;
namespace SDK.Gameplay.States
{
public class Visible : AbstractState
{
[ShowInInspector] private Invisible Invisible { get; set; }
[ShowInInspector] private SpriteRenderer[] Renderers { get; set; }
private const float Speed = 0.5f;
public void Enter()
{
enabled = true;
MainThreadDispatcher.StartFixedUpdateMicroCoroutine(ShowRenderers());
}
private IEnumerator ShowRenderers()
{
var invisible = true;
while (invisible)
{
// ReSharper disable once LocalVariableHidesMember
foreach (var renderer in Renderers)
{
var color = renderer.color;
color = new Color
{
r = color.r,
g = color.g,
b = color.b,
a = color.a + Speed * Time.fixedDeltaTime
};
renderer.color = color;
invisible = invisible && Math.Abs(color.a - 255) > float.Epsilon;
}
yield return false;
}
}
private void Exit()
{
enabled = false;
Invisible.Enter();
}
private void Awake()
{
Renderers = GetComponentsInChildren<SpriteRenderer>();
}
}
}
@tomaszczerminski
Copy link
Author

In order to copy-paste this FSM you need UniRx and Odin plugins for Unity3d

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment