I'm working on a text-based Animation Graph right now that drives a SpineSkeleton if that sounds interesting to you.
It's very programming heavy right now, but I already have it working in my game engine and it lets me do things like combo chains, pause one animation to play another then resume, contextual adaptation between 20% and 80% of an animation, etc... but it doesn't do Blend Trees well. Here's a preview if you're interested.
Some Vocab:
Branch = a thing that decides to move from one Stance to Another
Stance = basically a Mechanim State with extra properties
Signal = a message passed to the BranchProcessingMachine
List<Stance> StanceList = new List<Stance>()
{
// Ground Movement
new Stance { Name = "Idle", Animation = "Idle", Looping = true, BundleNames = {"Ground Common", "Ground Common - Echelon Only"}, },
new Stance { Name = "Idle Look Up", Animation = "Idle Look Up", Looping = true, BundleNames = {"Ground Common", "Ground Common - Echelon Only"}, },
new Stance { Name = "Run Forward", Animation = "Run Forward", Looping = true, BundleNames = {"Ground Common", "Ground Common - Echelon Only"}, },
new Stance { Name = "Run Backward", Animation = "Run Backward", Looping = true, BundleNames = {"Ground Common", "Ground Common - Echelon Only"}, },
}
Stances are connected to each other by branches like these. The really cool thing here is that you can just any code you want inside the Condition of a branch to allow it to trigger.
// Stance:Branch Map
Dictionary<string, List<Branch> > StanceBranches = new Dictionary<string, List<Branch> > ()
{
{
"Idle",
new List<Branch>()
{
new Branch {
Condition = context => { return context.Units["Source"].Unit[PS.InputUpward] > 0.5f; },
NextStance = "Idle Look Up",
},
// + Ground Common
}
},
{
"Idle Look Up",
new List<Branch>()
{
new Branch {
Condition = context => { return context.Units["Source"].Unit[PS.InputUpward] < 0.5f; },
NextStance = "Idle",
},
// + Ground Common
}
},
{
"Run Forward",
new List<Branch>()
{
new Branch {
Condition = context => { return context.Units["Source"].Unit[PS.InputForward] < 0.1f; },
NextStance = "Idle",
},
// + Ground Common
}
},
{
"Run Backward",
new List<Branch>()
{
new Branch {
Condition = context => { return context.Units["Source"].Unit[PS.InputForward] > -0.1f; },
NextStance = "Idle",
},
// + Ground Common
}
},
Some branches have "Actions" (Func<MyArg, bool>) which are called when that branch is taken by the machine.
{
"Ground Common - Echelon Only",
new BranchBundle()
{
Name = "Ground Common - Echelon Only",
Branches = new List<Branch>()
{
new Branch {
Signal = Signal.DoJump,
Condition = context => { return context.Units["Source"].Unit[PS.Grounded]; },
Action = DoSparkJump,
NextStance = "Jump Start",
},
new Branch {
Signal = Signal.DoDash,
Condition = context => { return ( Time.time >= fLastDash + DashCooldown ); },
Action = Dash,
NextStance = "Blink Out",
},
}
}
},
And finally, some branches have "automatic" transitions:
// Air Stuff
{
"Jump Start",
new List<Branch>()
{
new Branch {
On = 1.0f,
NextStance = "Jump Upward",
},
new Branch {
Condition = context => {
var src = context.Units["Source"];
return Mathf.Sign(src.Unit.GetCurrentVelocity().y) != src.Unit.GetUpwardDirection() ;
},
NextStance = "Jump Falling",
},
// + Air Common
// + Air Common - Echelon Only
}
},
In this case, as soon at the jump start animation stops running, it begins the "Jump Upward" looping animation... or if for some reason a monster pushes you down, it cancels the "Jump Upwards" animation right away and beings playing a "Fall" animation.
The technique driving this style of state machine is very similar with Mechanim, with a programmer-oriented focus and was used by studios that made God of War and Capcom fighting games, so if deep combo chains are something you want, it might be just right for you.