How Enemy Actions Work

Each enemy has an ordered list of EnemyAction ScriptableObjects. Every turn, the enemy's task returns its current action to the TaskSchedule, which runs it in parallel with the player's instruction.

EnemyController implements ITaskReceiver → returns a Task each turn Task runs EnemyAction.GetInstruction() → EnemyInstruction.Run(context) EnemyInstruction.Tick(context) ← your logic goes here

Step-by-Step

  1. Create the C# class in Assets/Scripts/Enemies/Instructions/
    Extend EnemyInstruction.
  2. Override displayName
    Shown in the enemy's action HUD indicator during execution.
  3. Override Execute()
    Instant effects (damage, state changes). Can be empty.
  4. Override Tick(ExecutionContext ctx)
    Coroutine logic. Use controller to access the enemy's position, animation, and health. Use ctx.player to read player state.
  5. Create an EnemyAction ScriptableObject
    Right-click in the Project → Create > Loop Adventure > Enemy Action. Assign your new instruction type.
  6. Add to the enemy
    In the enemy prefab's EnemyController component, add the new EnemyAction asset to the action list.

Example — Heal

Restores 2 HP to the enemy every turn.

using System.Collections;
using UnityEngine;

namespace LoopAdventure
{
    public class EnemyInstruction_Heal : EnemyInstruction
    {
        public override string displayName => "Heal";

        public override void Execute()
        {
            controller.GetComponent<EnemyHealth>().Heal(2);
        }

        protected override IEnumerator Tick(ExecutionContext ctx)
        {
            Execute();
            controller.PlayAnimation("Heal");
            yield return new WaitForSeconds(0.8f);
        }
    }
}

Example — Arrow Shot

Fires a projectile aimed at the player's position.

using System.Collections;
using UnityEngine;

namespace LoopAdventure
{
    public class EnemyInstruction_ArrowShot : EnemyInstruction
    {
        public override string displayName => "Arrow Shot";

        [SerializeField] Projectile projectilePrefab;
        [SerializeField] int        damage = 1;

        public override void Execute() { }

        protected override IEnumerator Tick(ExecutionContext ctx)
        {
            controller.PlayAnimation("Attack Side");

            Vector2 dir = (ctx.player.position
                         - (Vector2)controller.transform.position).normalized;

            Projectile p = Object.Instantiate(
                projectilePrefab, controller.transform.position, Quaternion.identity);
            p.Launch(dir, damage);

            yield return new WaitForSeconds(1f);
        }
    }
}

Player vs Enemy Instructions

Player InstructionEnemy Instruction
Base classPlayerInstructionEnemyInstruction
RunsOne per turn, in sheet orderIn parallel with the player's turn
Character refctx.playercontroller field
Direction sourceSet by the player in the UISet in the EnemyAction ScriptableObject
Registered inInstructionSheet paletteEnemyController action list
ℹ️
Animation state names Call controller.PlayAnimation("StateName") using the exact state name from the enemy's Animator Controller. Common states: "Idle", "Attack Side", "Attack Up", "Block", "Die".