|
| 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