Skip to content

Instantly share code, notes, and snippets.

@mrkybe
Created November 18, 2025 14:09
Show Gist options
  • Select an option

  • Save mrkybe/b0be172de271d902c2730b9319c34609 to your computer and use it in GitHub Desktop.

Select an option

Save mrkybe/b0be172de271d902c2730b9319c34609 to your computer and use it in GitHub Desktop.
using BehaviorDesigner.Runtime;
using BehaviorDesigner.Runtime.Tasks;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class MobController : EntController
{
[SerializeField]
public float MovementSpeed;
[SerializeField]
public AnimationClip _Idle;
[SerializeField]
public AnimationClip _Walk;
[SerializeField]
public AnimationClip _Hurt;
[SerializeField]
public AnimationClip _Fall;
[SerializeField]
public AnimationClip _GetUp;
[SerializeField]
public AnimationClip _KnockedOver;
[SerializeField]
public AnimationClip _Attack;
[SerializeField]
public AnimationClip _Dead;
private BehaviorTree bTree;
private CharHealth myHealth;
private SharedVector2 AI_MovementInput;
private SharedFloat AI_MovementSpeedInput;
private SharedBool AI_AttackPressed;
private new void Awake()
{
base.Awake();
bTree = GetComponent<BehaviorTree>();
myHealth = GetComponent<CharHealth>();
DamageBoxesThatHit = new HashSet<DamageBox>();
CantMoveCauses.Add(() => { return IsFalling; });
CantMoveCauses.Add(() => { return animController.IsPlaying(_Attack); });
CantMoveCauses.Add(() => { return animController.IsPlaying(_Hurt); });
CantMoveCauses.Add(() => { return animController.IsPlaying(_Fall); });
CantMoveCauses.Add(() => { return animController.IsPlaying(_GetUp); });
CantMoveCauses.Add(() => { return animController.IsPlaying(_KnockedOver); });
CantMoveCauses.Add(() => { return myHealth.IsAlive == false; });
CantAttackCauses.Add(() => { return IsFalling; });
CantAttackCauses.Add(() => { return animController.IsPlaying(_Hurt); });
CantAttackCauses.Add(() => { return animController.IsPlaying(_Fall); });
CantAttackCauses.Add(() => { return animController.IsPlaying(_GetUp); });
CantAttackCauses.Add(() => { return animController.IsPlaying(_KnockedOver); });
IsAttackingCauses.Add(() => { return animController.IsPlaying(_Attack); });
IsMovingOrIdleCauses.Add(() => { return animController.IsPlaying(_Idle); });
IsMovingOrIdleCauses.Add(() => { return animController.IsPlaying(_Walk); });
AI_MovementInput = (SharedVector2)bTree.GetVariable(nameof(AI_MovementInput));
AI_MovementSpeedInput = (SharedFloat)bTree.GetVariable(nameof(AI_MovementSpeedInput));
AI_AttackPressed = (SharedBool)bTree.GetVariable(nameof(AI_AttackPressed));
myHealth.TookDamageEvent += MyHealth_TookDamageEvent;
myHealth.DiedEvent += MyHealth_DiedEvent;
}
private void Start()
{
LevelController.Instance.Register(this);
}
private int HurtCombo = 0;
protected override void MyHealth_TookDamageEvent(object sender, CharHealth.TookDamageArgs e)
{
Debug.Log(e.Message);
HurtCombo++;
if (HurtCombo == 1)
{
if (AnimationPriorityLevel < 2)
{
AnimationPriorityLevel = 2;
var state = PlayAnimation(_Hurt);
LockFlipMode();
state.Events.OnEnd = () =>
{
HurtCombo = 0;
AnimationPriorityLevel = 0;
MovementState(true);
UnlockFlipMode();
};
}
}
if (HurtCombo >= 2)
{
if (animController.IsPlaying(_Hurt) && e.DamageSource.MonoBehavior.GetType() == typeof(DamageBox))
{
DamageBox dmgBox = (DamageBox)e.DamageSource.MonoBehavior;
var dmgDir = (transform.position.XY() - dmgBox.transform.root.position.XY()).normalized;
rb.AddForce(dmgDir * 40);
if (HurtCombo == 2)
{
var state = PlayAnimation(_Hurt, resetIfAlreadyPlaying: true);
state.Events.OnEnd = () =>
{
HurtCombo = 0;
AnimationPriorityLevel = 0;
MovementState(true);
UnlockFlipMode();
};
}
}
}
if (HurtCombo == 3)
{
AnimationPriorityLevel = 3;
var state = PlayAnimation(_Fall);
LockFlipMode();
rb.drag = 0f;
state.Events.OnEnd = () =>
{
rb.drag = 20f;
var state2 = PlayAnimation(_KnockedOver);
state2.Events.OnEnd = () =>
{
var state3 = PlayAnimation(_GetUp);
state3.Events.OnEnd = () =>
{
rb.isKinematic = false;
HurtCombo = 0;
AnimationPriorityLevel = 0;
MovementState(true);
UnlockFlipMode();
};
};
};
}
}
protected override void ApplyMovementAndAttacks()
{
if (CanMove)
{
if (moveDirection != Vector2.zero)
{
rb.velocity = moveDirection * MovementSpeed * 1f;
}
else
{
rb.drag = 20f;
}
}
}
private HashSet<DamageBox> DamageBoxesThatHit;
private bool AttackWasInterrupted = false;
public bool StartBasicAttack(Attack action)
{
if (!animController.IsPlaying(_Attack) && !IsAttacking && !CantAttack)
{
PlayAnimation(_Attack);
DamageBoxesThatHit.Clear();
AttackWasInterrupted = false;
LockFlipMode();
InterruptCurrentAnimationCallback = () =>
{
AttackWasInterrupted = true;
animState.Events.OnEnd();
};
animState.Events.OnEnd = () =>
{
InterruptCurrentAnimationCallback = null;
if (DamageBoxesThatHit.Any())
{
action.Done(Attack.AttackResult.HIT);
}
else if (AttackWasInterrupted)
{
action.Done(Attack.AttackResult.INTERRUPTED);
}
else
{
action.Done(Attack.AttackResult.MISSED);
}
UnlockFlipMode();
MovementState(true);
AttackWasInterrupted = false;
};
return true;
}
action.Done(Attack.AttackResult.DIDNT_START);
return false;
}
protected new void Update()
{
base.Update();
UpdateAnimator();
MovementSpeed = AI_MovementSpeedInput.Value;
}
protected new void UpdateAnimator()
{
if (myHealth.IsAlive)
{
base.UpdateAnimator();
if (flipMode == FlipMode.NearestEnemy)
{
float a = Mathf.Sign(transform.position.x - PlayerController.Instance.transform.position.x);
transform.localScale = new Vector3(-a, 1, 1);
}
else if (flipMode == FlipMode.Velocity)
{
transform.localScale = new Vector3(Mathf.Sign(rb.velocity.x), 1, 1);
}
if (CanMove)
{
MovementState();
}
}
}
private void MovementState(bool FinishedAttack = false)
{
if (!IsAttacking || FinishedAttack)
{
if (moveDirection.magnitude < 0.1f)
{
PlayAnimation(_Idle);
}
else
{
if (flipMode == FlipMode.MovementDirection)
{
PlayAnimation(_Walk);
}
else
{
if (moveDirection.x > 0 && transform.localScale.x > 0 || moveDirection.x < 0 && transform.localScale.x < 0)
{
PlayAnimation(_Walk, -1f);
}
else
{
PlayAnimation(_Walk, 1f);
}
}
}
}
}
protected override void GetMovementAndAttacksInput()
{
moveDirection = AI_MovementInput.Value;
}
protected override void MyHealth_DiedEvent(object sender, CharHealth.DiedArgs e)
{
AnimationPriorityLevel = 10;
AIAttackPermissionCoordinator.Instance.FreeAcquiredPositions(this);
var state = PlayAnimation(_Fall);
bTree.DisableBehavior();
Debug.Log(e.Message);
state.Events.OnEnd = () =>
{
PlayAnimation(_Dead);
var feet = transform.root.GetComponent<CircleCollider2D>();
feet.enabled = false;
myHealth.TookDamageEvent -= MyHealth_TookDamageEvent;
myHealth.DiedEvent -= MyHealth_DiedEvent;
Destroy(myHealth);
rb.isKinematic = true;
rb.velocity = Vector2.zero;
ZDepthOffset = -10;
};
}
public override void RegisterDamageBoxEnabled(DamageBox dmgBox)
{
base.RegisterDamageBoxEnabled(dmgBox);
dmgBox.DidDamageEvent += DmgBox_DidDamageEvent;
}
private void DmgBox_DidDamageEvent(object sender, DamageBox.DidDamageArgs e)
{
Debug.Log("damage box did damage");
DamageBoxesThatHit.Add((DamageBox)sender);
}
public override void RegisterDamageBoxDisabled(DamageBox dmgBox)
{
base.RegisterDamageBoxDisabled(dmgBox);
dmgBox.DidDamageEvent -= DmgBox_DidDamageEvent;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment