///|
pub enum LoopMode {
Once
Loop
} derive(Debug, Eq, ToJson, FromJson)
///|
pub struct AnimationClip {
name : String
frames : Array[String]
durations : Array[Int]
direction : Direction
loop_mode : LoopMode
} derive(Debug, Eq, ToJson, FromJson)
///|
pub struct StateTransition {
from : String
event : String
to : String
} derive(Debug, Eq, ToJson, FromJson)
///|
pub struct AnimationMachine {
initial : String
clips : Array[AnimationClip]
transitions : Array[StateTransition]
} derive(Debug, Eq, ToJson, FromJson)
///|
pub suberror AnimationError {
TagOutOfRange(String)
EmptyTagName
} derive(Debug, Eq, ToJson, FromJson)
///|
fn frame_indices(tag : FrameTag) -> Array[Int] {
let values : Array[Int] = []
match tag.direction {
Reverse =>
for i = tag.to; i >= tag.from; i = i - 1 {
values.push(i)
}
PingPong => {
for i in tag.from..<=tag.to {
values.push(i)
}
if tag.to > tag.from {
for i = tag.to - 1; i > tag.from; i = i - 1 {
values.push(i)
}
}
}
Forward =>
for i in tag.from..<=tag.to {
values.push(i)
}
}
values
}
///|
fn clip_from_tag(sheet : SpriteSheet, tag : FrameTag) -> AnimationClip {
let names : Array[String] = []
let durations : Array[Int] = []
for i in frame_indices(tag) {
let frame = sheet.frames[i]
names.push(frame.filename)
durations.push(frame.duration)
}
{
name: tag.name,
frames: names,
durations,
direction: tag.direction,
loop_mode: Loop,
}
}
///|
pub fn build_machine(
sheet : SpriteSheet,
initial? : String = "",
) -> AnimationMachine {
let clips = sheet.tags.filter_map(fn(tag) {
if tag.from < 0 || tag.to < tag.from || tag.to >= sheet.frames.length() {
None
} else {
Some(clip_from_tag(sheet, tag))
}
})
let start = if initial == "" && clips.length() > 0 {
clips[0].name
} else {
initial
}
{ initial: start, clips, transitions: [] }
}
///|
pub fn build_machine_checked(
sheet : SpriteSheet,
initial? : String = "",
) -> AnimationMachine raise {
for tag in sheet.tags {
if tag.name == "" {
raise EmptyTagName
}
if tag.from < 0 || tag.to < tag.from || tag.to >= sheet.frames.length() {
raise TagOutOfRange(tag.name)
}
}
build_machine(sheet, initial~)
}
///|
pub fn AnimationClip::total_duration(self : AnimationClip) -> Int {
self.durations.fold(init=0, (total, duration) => total + duration)
}
///|
pub fn AnimationMachine::clip_named(
self : AnimationMachine,
name : String,
) -> AnimationClip? {
match self.clips.search_by(fn(clip) { clip.name == name }) {
Some(index) => Some(self.clips[index])
None => None
}
}
///|
pub fn AnimationMachine::transition_target(
self : AnimationMachine,
from~ : String,
event~ : String,
) -> String? {
match
self.transitions.search_by(fn(transition) {
transition.from == from && transition.event == event
}) {
Some(index) => Some(self.transitions[index].to)
None => None
}
}
///|
pub fn AnimationMachine::with_transition(
self : AnimationMachine,
from~ : String,
event~ : String,
to~ : String,
) -> AnimationMachine {
let transitions = self.transitions.copy()
transitions.push({ from, event, to })
{ ..self, transitions, }
}
///|
pub fn AnimationMachine::to_json_string(
self : AnimationMachine,
indent? : Int = 2,
) -> String {
self.to_json().stringify(indent~)
}