Turn Systems
Five pluggable turn systems — from classic round-robin to press turns and spend/save AP.
ITurnSystem Interface
All turn systems implement ITurnSystem. This is the shared contract that
Battle uses to manage turn order. You can also implement your own.
public interface ITurnSystem
{
void Init(List<ICombatant> combatants);
ICombatant GetNextActor();
IReadOnlyList<ICombatant> GetTimeline();
void OnActorDied(ICombatant combatant);
void OnActorAdded(ICombatant combatant);
void OnTurnEnd();
}
Methods
Init(combatants)— called once when the battle starts. Sets up internal state from the combatant list.GetNextActor()— returns the next combatant who should act, ornullif no one is ready.GetTimeline()— returns a read-only list representing upcoming turn order. Useful for UI previews.OnActorDied(combatant)— notifies the turn system that a combatant died so it can clean up.OnActorAdded(combatant)— notifies the turn system that a combatant was added mid-battle.OnTurnEnd()— called after an actor finishes their turn. Advances internal state.
ClassicTurnSystem
DQ-style speed-sorted round-robin. Simple and predictable.
Constructor
new ClassicTurnSystem() // No parameters
Behavior
- On init (and at the start of each new round), all alive combatants are sorted by
Speeddescending. - Dead combatants are skipped automatically in
GetNextActor(). - Mid-round additions via
OnActorAdded()are appended to the end of the current round. - When all combatants in the round have acted, a new round begins with a fresh speed sort.
Additional Methods
None. ClassicTurnSystem only exposes the ITurnSystem interface.
Usage
var battle = new Battle();
battle.Start(combatants, new ClassicTurnSystem());
var actor = battle.BeginNextTurn(); // Fastest combatant
// ... do action ...
battle.EndTurn();
ATBTurnSystem
Active Time Battle (gauge-based, FF-style). Gauges fill in real time based on speed.
Constructor
new ATBTurnSystem(float gaugeMax = 100f)
gaugeMax— the gauge value a combatant must reach before they can act. Default:100f.
Behavior
- Each combatant has a gauge starting at
0. Tick(deltaTime)fills each alive combatant's gauge bySpeed * deltaTime. Already-full gauges do not overflow.GetNextActor()returns the ready combatant (gauge >= max) with the highest gauge. Ties broken by speed. Returnsnullif no one is ready.- After acting,
OnTurnEnd()resets the actor's gauge to0. - Dead combatants have their gauge removed entirely.
Additional Methods
| Method | Description |
|---|---|
Tick(float deltaTime) | Advances all alive combatants' gauges by Speed * deltaTime. |
IsReady(ICombatant) | Returns true if the combatant's gauge >= max. |
GetGauge(ICombatant) | Returns current gauge value (or 0 if not tracked). |
SetGauge(ICombatant, float) | Sets gauge value directly (e.g. for slow/haste effects). |
HasReadyActor() | Returns true if any alive combatant's gauge >= max. |
Usage
var atb = new ATBTurnSystem(100f);
battle.Start(combatants, atb);
// Game loop
atb.Tick(1f); // Advance time
if (atb.HasReadyActor())
{
var actor = battle.BeginNextTurn();
// ... do action ...
battle.EndTurn();
}
Real-time system.
You drive the clock by calling
Tick() yourself. This works with Unity's
Time.deltaTime, fixed timesteps, or any custom tick rate.
TimelineTurnSystem
Charge Time Battle (timeline-based, FFT-style). Combatants sit on a tick timeline; lower tick = sooner turn.
Constructor
new TimelineTurnSystem(int defaultActionCost = 100, int baseTick = 100)
defaultActionCost— the default cost added after an action. Default:100.baseTick— base tick value used for initial placement and new combatants. Default:100.
Behavior
- On init, each combatant is placed on the timeline at tick
baseTick / Speed. Faster combatants start closer to 0. GetNextActor()returns the alive combatant with the lowest tick. Ties broken by highest speed, then init order.- After acting, the actor's tick advances by
actionCost(default or overridden viaSetActionCost()). - Heavy actions with higher costs push the actor further down the timeline. Light actions let them act again sooner.
- New combatants added mid-battle are placed at
currentTick + baseTick.
Additional Methods
| Method | Description |
|---|---|
SetActionCost(int cost) | Override the cost for the current action. Call between BeginNextTurn() and EndTurn(). Resets to default each turn. |
GetTick(ICombatant) | Returns the combatant's current tick value on the timeline. |
Usage
var timeline = new TimelineTurnSystem(100, 100);
battle.Start(combatants, timeline);
var actor = battle.BeginNextTurn(); // Lowest tick actor
// Light attack — default cost
battle.EndTurn();
var next = battle.BeginNextTurn();
timeline.SetActionCost(200); // Heavy attack — delays next turn more
battle.EndTurn();
Action cost strategy.
Use
SetActionCost() to make different abilities cost different amounts of time.
A quick jab might cost 50, a powerful spell 300. This creates meaningful tactical decisions
about speed vs. power.
PressTurnSystem
SMT-style press turn system. Teams take turns in phases, spending turn points to act.
Constructor
new PressTurnSystem(int maxPointsPerTeam = 0)
maxPointsPerTeam— cap on points per phase.0= auto (one point per alive team member).
Behavior
- Teams are determined from combatant
Teamvalues, sorted ascending. Team 0 goes first. - Each team phase starts with full points = number of alive members (capped by
maxPointsPerTeamif set). - You choose who acts via
SelectActor(), then callGetNextActor()to retrieve them. - Actions consume or convert points. When points are exhausted, the next team's phase begins.
- A KO mid-phase does not retroactively shrink points — dead actors simply cannot be selected.
Properties
| Property | Type | Description |
|---|---|---|
ActiveTeam | int | The team ID currently taking their phase. |
FullPoints | int | Remaining full turn points. |
HalfPoints | int | Remaining half turn points. |
TotalPoints | int | Full + half points combined. |
HasActionsRemaining | bool | true if TotalPoints > 0. |
Additional Methods
| Method | Description |
|---|---|
SelectActor(ICombatant) | Choose who acts next. Must be alive and on the active team. Throws if not. |
ConsumeAction() | Spends one point. Consumes a half point first; if none, consumes a full point. |
ConvertAction() | Converts one full point into a half point. If no full points, consumes a half point instead. |
ConsumeActions(int count) | Consumes multiple points (half first, then full). |
ConsumeAllActions() | Ends the team's phase immediately by zeroing all points. |
Usage
var press = new PressTurnSystem();
battle.Start(combatants, press);
// Team 0's phase — 1 point per alive member
press.SelectActor(myChosenActor);
var actor = battle.BeginNextTurn();
// ... do action ...
press.ConsumeAction();
battle.EndTurn();
// If points remain, select another actor
if (press.HasActionsRemaining)
{
press.SelectActor(anotherActor);
var next = battle.BeginNextTurn();
// ... hit a weakness? Convert instead of consume ...
press.ConvertAction();
battle.EndTurn();
}
You manage the points.
SimpleJRPG does not decide when to consume vs. convert — that is your game logic.
Hitting a weakness, getting a critical, or missing are all your system's responsibility.
Call the appropriate point method based on your rules.
ActionPointTurnSystem
Action Point system. Combatants save or spend Action Points for extra actions.
Constructor
new ActionPointTurnSystem(int defaultAP = 0, int minAP = -4, int maxAP = 4)
defaultAP— starting AP for each combatant. Default:0.minAP— lowest AP can go (going negative means skipping future turns). Default:-4.maxAP— highest AP can be saved. Default:4.
Behavior
- Uses speed-sorted round-robin like
ClassicTurnSystemfor base turn order. - At the start of each round, all alive combatants gain
+1 APpassively (capped atmaxAP). SpendAP()spends 1 AP to queue an extra action this turn. Can be called multiple times. AP can go negative down tominAP.SaveAP()gains 1 AP (capped atmaxAP) and the combatant skips their action.- Combatants with negative AP are skipped when their turn comes — they must wait for passive AP to bring them back to 0.
- If a combatant dies mid-action-chain, remaining extra actions are cleared.
Properties
| Property | Type | Description |
|---|---|---|
MinAP | int | The minimum AP value (constructor param). |
MaxAP | int | The maximum AP value (constructor param). |
Additional Methods
| Method | Description |
|---|---|
GetAP(ICombatant) | Returns the combatant's current Action Points. |
SpendAP() | Spend 1 AP for an extra action this turn. Throws if already at minAP. |
SaveAP() | Gain 1 AP (capped at maxAP). The combatant should skip their action. |
Usage
var ap = new ActionPointTurnSystem(0, -4, 4);
battle.Start(combatants, ap);
var actor = battle.BeginNextTurn();
// Option A: Spend AP — take multiple actions now, risk negative AP
ap.SpendAP(); // AP: 0 -> -1, queues 1 extra action
// ... do first action ...
battle.EndTurn();
// Extra action fires — same actor again
var same = battle.BeginNextTurn();
// ... do second action ...
battle.EndTurn();
// Option B: Save AP — skip turn, bank AP for later
var next = battle.BeginNextTurn();
ap.SaveAP(); // AP +1
battle.EndTurn(); // Skips action
Risk vs. reward.
Spending AP lets you burst multiple actions in one turn but leaves you vulnerable
if AP goes negative. Saving AP is safe but costs a turn. The tension between
the two is the core of this system.
Comparison Table
| Feature | Classic | ATB | Timeline | Press Turn | Action Point |
|---|---|---|---|---|---|
| Turn order | Speed sort | Gauge fill | Tick timeline | Team phase | Round-robin |
| Real-time | No | Yes (Tick) | No | No | No |
| Player choice in order | No | No | Via action cost | SelectActor | Spend/Save AP |
| Team-based | No | No | No | Yes | No |
| Resource management | None | None | Action cost | Turn points | AP |