mischief-render-0.1.0.0: src/Mischief/Animation.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
module Mischief.Animation
( Animator (..),
AnimatorTag (..),
AnimationPlugin,
input,
inputIfNeq,
shift,
Animation,
Animations (..),
selAnim,
Spritesheet,
noAnimation,
)
where
import Control.Monad
import Data.Data
import GHC.Float (int2Float)
import Mischief.Animation.Animations (Animations (..), selAnim)
import Mischief.Animation.Animator
import Mischief.Animation.Spritesheet
import Mischief.Assets
import Mischief.ECS
import Mischief.ECS.Time qualified as Time
import Mischief.Render.Sprite.Common
data Animator where
Animator :: forall a. (AnimatorTag a) => a -> Animator
instance Component Animator where
onSet =
[ hook $ \HookContext {entity} -> do
Just (Animator (_ :: a)) <- single [q|entity. Animator|]
insert (AnimatorInput (defInput @a)) entity
]
data AnimatorInput where
AnimatorInput :: (Typeable a) => a -> AnimatorInput
deriving (Component)
newtype AnimationElapsedTime = AnimationElapsedTime Float deriving anyclass (Component)
data CurrentAnimation = CurrentAnimation {animation :: AssetRef Animation, index :: Int} deriving anyclass (Component)
data AnimatorState where
AnimatorState :: (Typeable a) => a -> AnimatorState
deriving (Component)
data AnimationPlugin
instance Plugin AnimationPlugin where
init = do
spawn_ $ Observer onSetAnimator
spawn_ $ Observer onSetAnimatorState
schedule @Update $ systems progressAnimations
schedule @Update $ systems transitionWhen & after progressAnimations
onSetAnimator :: OnSet Animator -> System ()
onSetAnimator OnSet {entity} = do
Just (Animator (_ :: anim)) <- single [q|entity. Animator|]
insert (AnimatorState $ start @anim, CurrentAnimation (animation $ start @anim) 0, AnimationElapsedTime 0) entity
qloadedAnim :: AssetRef Animation -> Query System ()
qloadedAnim anim = do
Animation {spritesheet} <- qasset' anim
void $ qasset' $ kdlToAssetRef spritesheet
onSetAnimatorState :: OnSet AnimatorState -> System ()
onSetAnimatorState OnSet {entity} = query_ $ do
(Animator (_ :: tag), AnimatorState (state :: state)) <- [q|entity. Animator, AnimatorState|]
case eqT @state @(State tag) of
Nothing -> qlift $ warn [i|Animator state doesn't match the animator's expected state|]
Just Refl -> qlift $ setNewAnimation entity (animation @tag state)
setNewAnimation :: Entity -> AssetRef Animation -> System ()
setNewAnimation entity animation' = do
waitForAnimation animation' $ do
Just animation <- asset animation'
case animation of
Animation {dummy = True} -> do
insert (AnimationElapsedTime 0, CurrentAnimation noAnimation 0) entity
animation -> query_ $ do
slice <- qmaybe =<< qanimFrameCoords 0 animation
image <- qanimImage animation
qpure entity
& qinsert
( const
( CurrentAnimation animation' 0,
AnimationElapsedTime 0,
Sprite {image},
slice
)
)
progressAnimations :: System ()
progressAnimations = query_ $ do
delta <- Time.qdelta
(entity, CurrentAnimation {animation = animation', index}, AnimationElapsedTime time) <- [q|Entity, CurrentAnimation, AnimationElapsedTime|]
animation <- qasset animation'
Control.Monad.when animation.dummy qstop
let time' = time + delta
let frames = nframes animation
let totalTime = case animation.time of
FpsTime t -> 1 / t
TotalTime t -> t / int2Float frames
Control.Monad.when (time < totalTime) $ do
qpure entity & qinsert (const $ AnimationElapsedTime time')
qstop
image <- qanimImage animation
slice <- qanimFrameCoords (index + 1) animation
case slice of
Nothing -> Control.Monad.when animation.loop $ do
slice <- qmaybe =<< qanimFrameCoords 0 animation
qpure entity
& qinsert
( const
( CurrentAnimation animation' 0,
Sprite {image},
slice,
AnimationElapsedTime 0
)
)
Just slice -> do
qpure entity
& qinsert
( const
( CurrentAnimation animation' (index + 1),
Sprite {image},
slice,
AnimationElapsedTime 0
)
)
input :: forall a. (AnimatorTag a) => (Input a -> Input a) -> Entity -> System ()
input f e = query_ $ do
AnimatorInput (input :: b) <- [q|e. AnimatorInput|]
case eqT @b @(Input a) of
Nothing -> pure () & qwarn (const [i|#{e} doesn't have an animator of type #{typeRep (Proxy @a)}!|])
Just Refl -> qpure e & qinsert (const $ AnimatorInput $ f input)
inputIfNeq :: forall a. (AnimatorTag a, Eq (Input a)) => (Input a -> Input a) -> Entity -> System ()
inputIfNeq f e = query_ $ do
AnimatorInput (input :: b) <- [q|e. AnimatorInput|]
case eqT @b @(Input a) of
Nothing -> pure () & qwarn (const [i|#{e} doesn't have an animator of type #{typeRep (Proxy @a)}!|])
Just Refl -> Control.Monad.when (input /= f input) $ qpure e & qinsert (const $ AnimatorInput $ f input)
transitionWhen :: System ()
transitionWhen = query_ $ do
(e, AnimatorInput (input :: inp), AnimatorState (state :: state), CurrentAnimation {animation = animation', index}, AnimationElapsedTime elapsed, Animator (_ :: tag)) <- [q|E, AnimatorInput, AnimatorState, CurrentAnimation, AnimationElapsedTime, Animator|]
animation <- qasset animation'
let frames = nframes animation
let totalTime = case animation.time of
FpsTime t -> 1 / t
TotalTime t -> t / int2Float frames
let status = if index == frames - 1 && elapsed >= totalTime then Finished else Running
case eqT @inp @(Input tag) of
Nothing -> pure () & qwarn (const [i|Animator and Input Type not matching on #{e}|])
Just Refl -> do
case eqT @state @(State tag) of
Nothing -> pure () & qwarn (const [i|Animator and State Type not matching on #{e}|])
Just Refl -> do
let x = checkTransitions @tag state input Nothing status
case x of
Nothing -> pure ()
Just state -> qlift $ do
insert (AnimatorState state) e
waitForAnimation :: AssetRef Animation -> System () -> System ()
waitForAnimation a s = flip runWhenOne (const s) $ do
a <- qasset a
qasset $ kdlToAssetRef a.spritesheet
-- whenLoaded' (kdlToAssetRef a.spritesheet) $ const $ do
-- err "ok this also passed.."
-- s
shift :: forall a. (Typeable a) => a -> Entity -> System ()
shift shift e = query_ $ do
(AnimatorInput (inp :: inp), AnimatorState (state :: state), CurrentAnimation {animation = animation', index}, AnimationElapsedTime elapsed, Animator (_ :: tag)) <- [q|e. AnimatorInput, AnimatorState, CurrentAnimation, AnimationElapsedTime, Animator|]
animation <- qasset animation'
let frames = nframes animation
let totalTime = case animation.time of
FpsTime t -> 1 / t
TotalTime t -> t / int2Float frames
let status = if index == frames - 1 && elapsed >= totalTime then Finished else Running
case eqT @a @(Shift tag) of
Nothing -> pure () & qwarn (const [i|Animator type doesn't exist on #{e}|])
Just Refl -> do
case eqT @state @(State tag) of
Nothing -> pure () & qwarn (const [i|Animator and State Type not matching on #{e}|])
Just Refl -> do
case eqT @inp @(Input tag) of
Nothing -> pure () & qwarn (const [i|Animator and Input Type not matching on #{e}|])
Just Refl -> do
let x = checkTransitions @tag state inp (Just shift) status
case x of
Nothing -> pure ()
Just state -> qlift $ do
insert (AnimatorState state) e