Skip to content

Instantly share code, notes, and snippets.

@adammyhre
Last active November 17, 2025 06:34
Show Gist options
  • Select an option

  • Save adammyhre/747371bc14470f3b31fdbb62d758e9bd to your computer and use it in GitHub Desktop.

Select an option

Save adammyhre/747371bc14470f3b31fdbb62d758e9bd to your computer and use it in GitHub Desktop.
Curiously Recurring Template Pattern (CRTP) in C#
using UnityEngine;
abstract class Base<TDerived> where TDerived : Base<TDerived> {
public void DoWork() {
((TDerived)this).Work(); // Call derived class method, strongly typed
}
public TDerived SetName(string value) {
Debug.Log($"#Base# Setting name to {value}");
return (TDerived)this; // Fluent interface
}
protected abstract void Work();
}
class Worker : Base<Worker> {
protected override void Work() {
UnityEngine.Debug.Log("#Worker# Worker is doing work.");
}
}
abstract class Cloneable<TDerived> where TDerived : Cloneable<TDerived>, new() {
public TDerived Clone() {
return new TDerived();
}
}
class Enemy : Cloneable<Enemy> { }
abstract class Builder<TDerived> where TDerived : Builder<TDerived> {
protected string name;
public TDerived SetName(string value) {
name = value;
return (TDerived)this; // returns the *derived* type
}
}
class EnemyBuilder : Builder<EnemyBuilder> {
int health;
public EnemyBuilder SetHealth(int value) {
health = value;
return this;
}
public Enemy Build() {
return new Enemy(name, health);
}
}
using System.Collections.Generic;
using UnityEngine;
public class MoveState : CharacterState<MoveState> {
protected override void OnEnter() {
Debug.Log("#State# Entered Move");
}
protected override void OnExit() {
Debug.Log("#State# Exited Move");
}
protected override void OnTick(float dt) {
// Movement logic...
// ConsoleProDebug.Watch("DeltaTime in Move State", dt.ToString());
}
}
public class IdleState : CharacterState<IdleState> {
protected override void OnEnter() {
Debug.Log("#State# Entered Idle");
}
protected override void OnExit() {
Debug.Log("#State# Exited Idle");
}
protected override void OnTick(float dt) {
// Idle logic...
}
}
public class CharacterStateMachine {
CharacterState current;
public void ChangeState<TState>(TState newState) where TState : CharacterState<TState> {
current?.Exit();
current = newState;
current.Enter();
}
public void Tick(float dt) {
// current?.Tick(dt);
if (current == null) return;
var t = current.GetTransition();
if (t != null) {
t.Apply(this);
return;
}
current.Tick(dt);
}
}
public abstract class CharacterState<TState> : CharacterState where TState : CharacterState<TState> {
// Methods in here can explicitly use `(TState)this`.
// This is especially useful if this class needs to return a value of TState.
public override void Enter() {
((TState)this).OnEnter();
}
public override void Exit() {
((TState)this).OnExit();
}
public override void Tick(float dt) {
((TState)this).OnTick(dt);
}
protected abstract void OnEnter();
protected abstract void OnExit();
protected abstract void OnTick(float dt);
}
public abstract class CharacterState {
readonly List<Transition> transitions = new List<Transition>();
public Transition GetTransition() {
for (int i = 0; i < transitions.Count; i++) {
if (transitions[i].Evaluate()) return transitions[i];
}
return null;
}
public void SetTransition<TState>(Transition<TState> t) where TState : CharacterState<TState> {
transitions.Add(t);
}
public abstract void Enter();
public abstract void Exit();
public abstract void Tick(float dt);
}
using System;
public abstract class Transition {
public abstract bool Evaluate();
public abstract void Apply(CharacterStateMachine machine);
}
public class Transition<TState> : Transition where TState : CharacterState<TState> {
readonly Func<bool> condition;
readonly TState target;
public Transition(TState target, Func<bool> condition) {
this.target = target;
this.condition = condition;
}
public override bool Evaluate() => condition();
public override void Apply(CharacterStateMachine machine) {
machine.ChangeState(target); // Type-safe generic dispatch
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment