Skip to content

Commit 63f17a1

Browse files
Example showing how to do frame-based animation
1 parent 507f4d3 commit 63f17a1

File tree

2 files changed

+197
-0
lines changed
  • assets/multimedia/animation
  • tutorials/gameconcepts/multimedia/animation

2 files changed

+197
-0
lines changed

assets/multimedia/animation/pftcc.png

72.9 KB
Loading
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
-- IDEAS: when a sheet is loaded, add the FPS for the animation
2+
-- and pass the delta time to the function that moves animations
3+
-- forward
4+
--
5+
-- Make managers monads, or better, monad transformers.
6+
--
7+
-- One will be IO-based, the other won't
8+
9+
import Control.Monad
10+
import Data.IORef
11+
import Data.List
12+
import qualified Data.Map as M
13+
import Data.Word
14+
import Graphics.UI.SDL as SDL
15+
import Graphics.UI.SDL.Image as SDL
16+
17+
main = do
18+
SDL.init [InitVideo]
19+
screen <- SDL.setVideoMode 480 320 32 [HWSurface, DoubleBuf]
20+
21+
spriteAnim <- loadTimedSpriteAnim "assets/multimedia/animation/pftcc.png" 80 14 coordsF
22+
23+
let assetMgr = mkAssetManager
24+
visualMgr = mkVisualManager
25+
(n', visualMgr', assetMgr') = registerAsset assetMgr visualMgr (TimeBasedAnim spriteAnim)
26+
27+
clock <- newSDLClock
28+
29+
lastTime <- readIORef clock
30+
31+
go assetMgr' visualMgr' n' clock lastTime
32+
33+
go assetMgr visualMgr spriteAnim clock last = do
34+
35+
-- Constant FPS
36+
-- SDL.delay 10
37+
delayNextFrame 16 clock
38+
39+
-- Animations (pure)
40+
ticks <- getTicks
41+
let dt = fromIntegral (ticks - last)
42+
let visualMgr' = advanceAllAnim assetMgr visualMgr dt
43+
print dt
44+
45+
-- Rendering
46+
screen <- getVideoSurface
47+
48+
let format = SDL.surfaceGetPixelFormat screen
49+
white <- SDL.mapRGB format 0xFF 0xFF 0xFF
50+
SDL.fillRect screen Nothing white
51+
52+
showSpriteAnim assetMgr visualMgr' screen spriteAnim
53+
54+
SDL.flip screen
55+
56+
-- Loop
57+
go assetMgr visualMgr' spriteAnim clock ticks
58+
59+
type SpriteSheetFrameArea = Int -> Rect
60+
61+
coordsF :: SpriteSheetFrameArea
62+
coordsF = spriteSheetCoords 5 73 68
63+
64+
-- * Coordinates of sprite in left-to-right top-bottom equal-size sprite sheet
65+
spriteSheetCoords :: Int -> Int -> Int -> Int -> Rect
66+
spriteSheetCoords cols w h n = Rect x y w h
67+
where x = c * w
68+
y = r * h
69+
c = n `mod` cols
70+
r = n `div` cols
71+
72+
-- * Visual manager and asset manager
73+
-- Visual manager is pure, asset manager is impure
74+
type VisualManagerInfo = M.Map VisualId VisualRepr
75+
76+
type VisualId = Int
77+
data VisualRepr = SpriteAnimationState (AssetId, Int)
78+
| TimeSpriteAnimationState (AssetId, Int, Int)
79+
80+
mkVisualManager :: VisualManagerInfo
81+
mkVisualManager = M.empty
82+
83+
visualManagerRegister :: VisualManagerInfo -> VisualRepr -> (VisualId, VisualManagerInfo)
84+
visualManagerRegister m r = (newReprId, m')
85+
where m' = M.insert newReprId r m
86+
newReprId = nextFreeKey m 0
87+
88+
-- * Asset manager
89+
mkAssetManager :: AssetManagerInfo
90+
mkAssetManager = M.empty
91+
92+
type AssetManagerInfo = M.Map AssetId AssetRepr
93+
94+
assetManagerRegister :: AssetManagerInfo -> AssetRepr -> (AssetId, AssetManagerInfo)
95+
assetManagerRegister m r = (newAssetId, m')
96+
where m' = M.insert newAssetId r m
97+
newAssetId = nextFreeKey m 0
98+
99+
nextFreeKey :: (Ord k, Enum k) => M.Map k v -> k -> k
100+
nextFreeKey m k = firstHole k sortedKeys
101+
where sortedKeys = sort (M.keys m)
102+
103+
-- | Find the first hole in a list
104+
firstHole :: (Ord r, Enum r) => r -> [r] -> r
105+
firstHole n [] = n
106+
firstHole n (m:ms)
107+
| n < m = n
108+
| otherwise = firstHole (succ n) ms
109+
110+
type AssetId = Int
111+
data AssetRepr = SpriteAnim SpriteAnim
112+
| TimeBasedAnim TimeBasedAnim
113+
114+
type SpriteAnim = ( SDL.Surface -- Sheet
115+
, Int -- Num frames
116+
, [Rect] -- Coordinates
117+
)
118+
119+
type TimeBasedAnim = (SpriteAnim, Int) -- MS per frame
120+
121+
-- IDEAS: add a displacement vector
122+
loadSpriteAnim :: FilePath -> Int -> (Int -> Rect) -> IO SpriteAnim
123+
loadSpriteAnim fp numFrames frameCoords = do
124+
sheet <- load fp
125+
let frames = map frameCoords [0..numFrames-1]
126+
return (sheet, numFrames, frames)
127+
128+
-- IDEAS: add a displacement vector
129+
loadTimedSpriteAnim :: FilePath -> Int -> Int -> (Int -> Rect) -> IO TimeBasedAnim
130+
loadTimedSpriteAnim fp ms numFrames frameCoords = do
131+
sheet <- load fp
132+
let frames = map frameCoords [0..numFrames-1]
133+
return ((sheet, numFrames, frames), ms)
134+
135+
advanceAllAnim :: AssetManagerInfo -> VisualManagerInfo -> Int -> VisualManagerInfo
136+
advanceAllAnim assetMgr visualMgr ms =
137+
fmap (advanceSpriteAnim assetMgr ms) visualMgr
138+
139+
advanceSpriteAnim :: AssetManagerInfo -> Int -> VisualRepr -> VisualRepr
140+
advanceSpriteAnim assetManagerInfo ms info@(TimeSpriteAnimationState (asset, n, lms)) =
141+
case M.lookup asset assetManagerInfo of
142+
(Just (TimeBasedAnim ((_, l, _),mms))) -> let numCycles = (lms + ms) `div` mms
143+
restMS = (lms + ms) `mod` mms
144+
in TimeSpriteAnimationState (asset, (n+numCycles) `mod` l, restMS)
145+
_ -> info
146+
147+
148+
advanceSpriteAnim assetManagerInfo ms info@(SpriteAnimationState (asset, n)) =
149+
case M.lookup asset assetManagerInfo of
150+
(Just (SpriteAnim (_, l, _))) -> SpriteAnimationState (asset, (n+1) `mod` l)
151+
_ -> info
152+
153+
-- ** Rendering
154+
showSpriteAnim :: AssetManagerInfo -> VisualManagerInfo -> Surface -> VisualId -> IO ()
155+
showSpriteAnim assetMgr visualMgr screen anim = do
156+
case M.lookup anim visualMgr of
157+
Nothing -> return ()
158+
159+
Just (TimeSpriteAnimationState (a, f,oms)) -> case M.lookup a assetMgr of
160+
Just (TimeBasedAnim ((s, _, r),_)) -> do
161+
let rect = r!!f
162+
SDL.blitSurface s (Just rect) screen Nothing
163+
return ()
164+
_ -> return ()
165+
Just (SpriteAnimationState (a, f)) -> case M.lookup a assetMgr of
166+
Just (SpriteAnim (s, _, r)) -> do
167+
let rect = r!!f
168+
SDL.blitSurface s (Just rect) screen Nothing
169+
return ()
170+
_ -> return ()
171+
172+
registerAsset :: AssetManagerInfo -> VisualManagerInfo -> AssetRepr -> (VisualId, VisualManagerInfo, AssetManagerInfo)
173+
registerAsset assetMgr visualMgr asset@(TimeBasedAnim _) = (n', visualMgr', assetMgr')
174+
where (n', visualMgr') = visualManagerRegister visualMgr (TimeSpriteAnimationState (n, 0, 0))
175+
(n, assetMgr') = assetManagerRegister assetMgr asset
176+
registerAsset assetMgr visualMgr asset@(SpriteAnim _) = (n', visualMgr', assetMgr')
177+
where (n', visualMgr') = visualManagerRegister visualMgr (SpriteAnimationState (n, 0))
178+
(n, assetMgr') = assetManagerRegister assetMgr asset
179+
180+
-- * Abstract SDL Clock
181+
type SDLClock = IORef Word32
182+
183+
newSDLClock = do
184+
n <- SDL.getTicks
185+
newIORef n
186+
187+
updateSDLClock clock = do
188+
n <- SDL.getTicks
189+
writeIORef clock n
190+
191+
delayNextFrame :: Word32 -> SDLClock -> IO ()
192+
delayNextFrame ms clock = do
193+
n <- SDL.getTicks
194+
o <- readIORef clock
195+
let diff = n - o
196+
when (ms > diff) $ SDL.delay (ms - diff)
197+
writeIORef clock n

0 commit comments

Comments
 (0)