diff options
-rw-r--r-- | Demos/Pong/Main.hs | 26 | ||||
-rw-r--r-- | Demos/Pong/Pong.hs | 85 | ||||
-rw-r--r-- | Spear.cabal | 6 | ||||
-rw-r--r-- | Spear/App.hs | 7 | ||||
-rw-r--r-- | Spear/Math/Spatial.hs | 12 | ||||
-rw-r--r-- | Spear/Step.hs | 65 | ||||
-rw-r--r-- | Spear/Window.hs | 44 |
7 files changed, 120 insertions, 125 deletions
diff --git a/Demos/Pong/Main.hs b/Demos/Pong/Main.hs index 0237a26..21fcb0c 100644 --- a/Demos/Pong/Main.hs +++ b/Demos/Pong/Main.hs | |||
@@ -14,6 +14,7 @@ import Spear.Render.Core.State | |||
14 | import Spear.Render.Immediate | 14 | import Spear.Render.Immediate |
15 | import Spear.Window | 15 | import Spear.Window |
16 | 16 | ||
17 | import Control.Monad (when) | ||
17 | import Data.Maybe (mapMaybe) | 18 | import Data.Maybe (mapMaybe) |
18 | 19 | ||
19 | 20 | ||
@@ -28,7 +29,7 @@ data GameState = GameState | |||
28 | app = App step render resize | 29 | app = App step render resize |
29 | 30 | ||
30 | main = | 31 | main = |
31 | withWindow (900, 600) (Just "Pong") initGame endGame $ | 32 | withWindow (1920, 1200) (Just "Pong") initGame endGame $ |
32 | loop app | 33 | loop app |
33 | 34 | ||
34 | initGame :: Window -> Game () GameState | 35 | initGame :: Window -> Game () GameState |
@@ -44,13 +45,22 @@ endGame = do | |||
44 | step :: Elapsed -> Dt -> [InputEvent] -> Game GameState Bool | 45 | step :: Elapsed -> Dt -> [InputEvent] -> Game GameState Bool |
45 | step elapsed dt inputEvents = do | 46 | step elapsed dt inputEvents = do |
46 | gs <- getGameState | 47 | gs <- getGameState |
47 | let events = translateEvents inputEvents | 48 | events <- processInput (window gs) |
49 | --when (events /= []) $ gameIO . putStrLn $ show events | ||
48 | modifyGameState $ \gs -> | 50 | modifyGameState $ \gs -> |
49 | gs | 51 | gs |
50 | { world = stepWorld (realToFrac elapsed) (realToFrac dt) events (world gs) | 52 | { world = stepWorld (realToFrac elapsed) (realToFrac dt) events (world gs) |
51 | } | 53 | } |
52 | return (not $ exitRequested inputEvents) | 54 | return (not $ exitRequested inputEvents) |
53 | 55 | ||
56 | processInput :: Window -> Game GameState [GameEvent] | ||
57 | processInput window = processKeys window | ||
58 | [ (KEY_A, MoveLeft) | ||
59 | , (KEY_D, MoveRight) | ||
60 | ] | ||
61 | |||
62 | exitRequested = elem (KeyDown KEY_ESC) | ||
63 | |||
54 | render :: Game GameState () | 64 | render :: Game GameState () |
55 | render = do | 65 | render = do |
56 | gameState <- getGameState | 66 | gameState <- getGameState |
@@ -79,7 +89,7 @@ renderBackground = | |||
79 | let pmin = 0 :: Float | 89 | let pmin = 0 :: Float |
80 | pmax = 1 :: Float | 90 | pmax = 1 :: Float |
81 | in do | 91 | in do |
82 | immSetColour (vec4 0.6 0.35 0.6 1.0) | 92 | immSetColour (vec4 0.0 0.25 0.41 1.0) |
83 | immDrawQuads2d [ | 93 | immDrawQuads2d [ |
84 | (vec2 pmin pmin | 94 | (vec2 pmin pmin |
85 | ,vec2 pmax pmin | 95 | ,vec2 pmax pmin |
@@ -98,7 +108,6 @@ renderGO go = do | |||
98 | ,vec2 xmax ymax | 108 | ,vec2 xmax ymax |
99 | ,vec2 xmin ymax)] | 109 | ,vec2 xmin ymax)] |
100 | 110 | ||
101 | -- TODO: Fix the resize hang. | ||
102 | resize :: WindowEvent -> Game GameState () | 111 | resize :: WindowEvent -> Game GameState () |
103 | resize (ResizeEvent w h) = | 112 | resize (ResizeEvent w h) = |
104 | let r = fromIntegral w / fromIntegral h | 113 | let r = fromIntegral w / fromIntegral h |
@@ -112,12 +121,3 @@ resize (ResizeEvent w h) = | |||
112 | modifyGameState $ \state -> state { | 121 | modifyGameState $ \state -> state { |
113 | viewProjection = Matrix4.ortho left right bottom top (-1) 1 | 122 | viewProjection = Matrix4.ortho left right bottom top (-1) 1 |
114 | } | 123 | } |
115 | |||
116 | translateEvents = mapMaybe translateEvents' | ||
117 | where translateEvents' (KeyDown KEY_LEFT) = Just MoveLeft | ||
118 | translateEvents' (KeyDown KEY_RIGHT) = Just MoveRight | ||
119 | translateEvents' (KeyUp KEY_LEFT) = Just StopLeft | ||
120 | translateEvents' (KeyUp KEY_RIGHT) = Just StopRight | ||
121 | translateEvents' _ = Nothing | ||
122 | |||
123 | exitRequested = elem (KeyDown KEY_ESC) | ||
diff --git a/Demos/Pong/Pong.hs b/Demos/Pong/Pong.hs index 104a92e..dd8855b 100644 --- a/Demos/Pong/Pong.hs +++ b/Demos/Pong/Pong.hs | |||
@@ -24,13 +24,14 @@ import Data.Monoid (mconcat) | |||
24 | 24 | ||
25 | -- Configuration | 25 | -- Configuration |
26 | 26 | ||
27 | padSize = vec2 0.07 0.02 | 27 | padSize = vec2 0.07 0.015 |
28 | ballSize = 0.012 :: Float | 28 | ballSize = 0.012 :: Float |
29 | ballSpeed = 0.6 :: Float | 29 | ballSpeed = 0.7 :: Float |
30 | initialBallVelocity = vec2 1 1 | 30 | initialBallVelocity = vec2 1 1 |
31 | maxBounceAngle = (65::Float) * (pi::Float)/(180::Float) | 31 | maxBounceAngle = (65::Float) * (pi::Float)/(180::Float) |
32 | playerSpeed = 1.0 :: Float | 32 | playerSpeed = 1.0 :: Float |
33 | enemySpeed = 3.0 :: Float | 33 | enemySpeed = 7.0 :: Float |
34 | enemyMomentum = 0.1 :: Float | ||
34 | initialEnemyPos = vec2 0.5 0.9 | 35 | initialEnemyPos = vec2 0.5 0.9 |
35 | initialPlayerPos = vec2 0.5 0.1 | 36 | initialPlayerPos = vec2 0.5 0.1 |
36 | initialBallPos = vec2 0.5 0.5 | 37 | initialBallPos = vec2 0.5 0.5 |
@@ -40,9 +41,7 @@ initialBallPos = vec2 0.5 0.5 | |||
40 | data GameEvent | 41 | data GameEvent |
41 | = MoveLeft | 42 | = MoveLeft |
42 | | MoveRight | 43 | | MoveRight |
43 | | StopLeft | 44 | deriving (Eq, Ord, Show) |
44 | | StopRight | ||
45 | deriving (Eq, Ord) | ||
46 | 45 | ||
47 | -- Game objects | 46 | -- Game objects |
48 | 47 | ||
@@ -79,17 +78,9 @@ instance Spatial GameObject Vector2 Angle Transform2 where | |||
79 | transform = basis | 78 | transform = basis |
80 | 79 | ||
81 | 80 | ||
82 | stepWorld :: Elapsed -> Dt -> [GameEvent] -> [GameObject] -> [GameObject] | ||
83 | stepWorld elapsed dt evts gos = map (update elapsed dt evts gos) gos | ||
84 | |||
85 | update :: Elapsed -> Dt -> [GameEvent] -> [GameObject] -> GameObject -> GameObject | ||
86 | update elapsed dt evts gos go = | ||
87 | let (go', s') = runStep (gostep go) elapsed dt gos evts go | ||
88 | in go' {gostep = s'} | ||
89 | |||
90 | ballBox, padBox :: AABB2 | 81 | ballBox, padBox :: AABB2 |
91 | ballBox = AABB2 (vec2 (-s) (-s)) (vec2 s s) where s = ballSize | 82 | ballBox = AABB2 (vec2 (-s) (-s)) (vec2 s s) where s = ballSize |
92 | padBox = AABB2 (-padSize) padSize | 83 | padBox = AABB2 (-padSize) padSize |
93 | 84 | ||
94 | newWorld = | 85 | newWorld = |
95 | [ GameObject ballBox (makeAt initialBallPos) $ stepBall initialBallVelocity, | 86 | [ GameObject ballBox (makeAt initialBallPos) $ stepBall initialBallVelocity, |
@@ -98,23 +89,30 @@ newWorld = | |||
98 | ] | 89 | ] |
99 | where makeAt = newTransform2 unitx2 unity2 | 90 | where makeAt = newTransform2 unitx2 unity2 |
100 | 91 | ||
92 | |||
93 | stepWorld :: Elapsed -> Dt -> [GameEvent] -> [GameObject] -> [GameObject] | ||
94 | stepWorld elapsed dt evts gos = map (update elapsed dt evts gos) gos | ||
95 | |||
96 | update :: Elapsed -> Dt -> [GameEvent] -> [GameObject] -> GameObject -> GameObject | ||
97 | update elapsed dt evts gos go = | ||
98 | let (go', s') = runStep (gostep go) elapsed dt gos evts go | ||
99 | in go' {gostep = s'} | ||
100 | |||
101 | -- Ball steppers | 101 | -- Ball steppers |
102 | 102 | ||
103 | stepBall vel = collideBall vel .> moveBall | 103 | stepBall vel = collideBall vel .> moveBall |
104 | 104 | ||
105 | -- TODO: in collideBall and paddleBounce, we should an apply an offset to the | ||
106 | -- ball when collision is detected. | ||
107 | collideBall :: Vector2 -> Step [GameObject] e GameObject (Vector2, GameObject) | 105 | collideBall :: Vector2 -> Step [GameObject] e GameObject (Vector2, GameObject) |
108 | collideBall vel = step $ \_ dt gos _ ball -> | 106 | collideBall vel = step $ \_ dt gos _ ball -> |
109 | let (AABB2 pmin pmax) = translate (position ball) (aabb ball) | 107 | let (AABB2 pmin pmax) = translate (position ball) (aabb ball) |
110 | collideSide = x pmin < 0 || x pmax > 1 | 108 | sideCollision = x pmin < 0 || x pmax > 1 |
111 | collideBack = y pmin < 0 || y pmax > 1 | 109 | backCollision = y pmin < 0 || y pmax > 1 |
112 | collidePaddle = any (collide ball) (tail gos) | 110 | flipX v@(Vector2 x y) = if sideCollision then vec2 (-x) y else v |
113 | flipX v@(Vector2 x y) = if collideSide then vec2 (-x) y else v | 111 | flipY v@(Vector2 x y) = if backCollision then vec2 x (-y) else v |
114 | flipY v@(Vector2 x y) = if collideBack then vec2 x (-y) else v | ||
115 | vel' = normalise . (\v -> foldl (paddleBounce ball) v (tail gos)) . flipX . flipY $ vel | 112 | vel' = normalise . (\v -> foldl (paddleBounce ball) v (tail gos)) . flipX . flipY $ vel |
116 | -- A small delta to apply when collision occurs. | 113 | collision = vel' /= vel |
117 | delta = (1::Float) + if collideSide || collideBack || collidePaddle then (2::Float)*dt else (0::Float) | 114 | -- Apply offset when collision occurs to avoid sticky collisions. |
115 | delta = (1::Float) + if collision then (3::Float)*dt else (0::Float) | ||
118 | in ((ballSpeed * delta * vel', ball), collideBall vel') | 116 | in ((ballSpeed * delta * vel', ball), collideBall vel') |
119 | 117 | ||
120 | paddleBounce :: GameObject -> Vector2 -> GameObject -> Vector2 | 118 | paddleBounce :: GameObject -> Vector2 -> GameObject -> Vector2 |
@@ -139,37 +137,38 @@ collide go1 go2 = | |||
139 | (AABB2 (Vector2 xmin2 ymin2) (Vector2 xmax2 ymax2)) = | 137 | (AABB2 (Vector2 xmin2 ymin2) (Vector2 xmax2 ymax2)) = |
140 | translate (position go2) (aabb go2) | 138 | translate (position go2) (aabb go2) |
141 | in not $ | 139 | in not $ |
142 | xmax1 < xmin2 | 140 | xmax1 < xmin2 || |
143 | || xmin1 > xmax2 | 141 | xmin1 > xmax2 || |
144 | || ymax1 < ymin2 | 142 | ymax1 < ymin2 || |
145 | || ymin1 > ymax2 | 143 | ymin1 > ymax2 |
146 | 144 | ||
147 | moveBall :: Step s e (Vector2, GameObject) GameObject | 145 | moveBall :: Step s e (Vector2, GameObject) GameObject |
148 | moveBall = step $ \_ dt _ _ (vel, ball) -> (translate (vel * dt) ball, moveBall) | 146 | moveBall = step $ \_ dt _ _ (vel, ball) -> (translate (vel * dt) ball, moveBall) |
149 | 147 | ||
150 | -- Enemy stepper | 148 | -- Enemy stepper |
151 | 149 | ||
152 | stepEnemy = movePad | 150 | stepEnemy = movePad 0 .> clamp |
153 | 151 | ||
154 | movePad :: Step s e GameObject GameObject | 152 | movePad :: Float -> Step [GameObject] e GameObject GameObject |
155 | movePad = step $ \elapsed _ _ _ pad -> | 153 | movePad previousMomentum = step $ \_ dt gos _ pad -> |
156 | let enemyY = 0.9 | 154 | let ball = head gos |
157 | p = vec2 px enemyY | 155 | offset = (x . position $ ball) - (x . position $ pad) |
158 | px = | 156 | chaseVector = enemySpeed * offset |
159 | (sin (enemySpeed * elapsed) * (0.5::Float) + (0.5::Float)) | 157 | momentum = previousMomentum + enemyMomentum * chaseVector |
160 | * ((1::Float) - (2::Float) * x padSize) | 158 | vx = chaseVector + momentum |
161 | + x padSize | 159 | in (translate (vec2 (vx * dt) 0) pad, movePad momentum) |
162 | in (setPosition p pad, movePad) | 160 | |
161 | sign :: Float -> Float | ||
162 | sign x = if x >= 0 then 1 else -1 | ||
163 | 163 | ||
164 | -- Player stepper | 164 | -- Player stepper |
165 | 165 | ||
166 | stepPlayer = sfold moveGO .> clamp | 166 | stepPlayer = sfold moveGO .> clamp |
167 | 167 | ||
168 | moveGO = | 168 | moveGO = mconcat |
169 | mconcat | 169 | [ swhen MoveLeft $ moveGO' (vec2 (-playerSpeed) 0) |
170 | [ switch StopLeft sid MoveLeft (moveGO' $ vec2 (-playerSpeed) 0), | 170 | , swhen MoveRight $ moveGO' (vec2 playerSpeed 0) |
171 | switch StopRight sid MoveRight (moveGO' $ vec2 playerSpeed 0) | 171 | ] |
172 | ] | ||
173 | 172 | ||
174 | moveGO' :: Vector2 -> Step s e GameObject GameObject | 173 | moveGO' :: Vector2 -> Step s e GameObject GameObject |
175 | moveGO' dir = step $ \_ dt _ _ go -> (translate (dir * dt) go, moveGO' dir) | 174 | moveGO' dir = step $ \_ dt _ _ go -> (translate (dir * dt) go, moveGO' dir) |
diff --git a/Spear.cabal b/Spear.cabal index b044ae2..c327cf9 100644 --- a/Spear.cabal +++ b/Spear.cabal | |||
@@ -129,12 +129,14 @@ library | |||
129 | 129 | ||
130 | hs-source-dirs: . | 130 | hs-source-dirs: . |
131 | 131 | ||
132 | ghc-options: -O2 | 132 | ghc-options: -O2 -g |
133 | 133 | ||
134 | ghc-prof-options: -O2 -fprof-auto -fprof-cafs | 134 | ghc-prof-options: -O2 -g -fprof-auto -fprof-cafs |
135 | 135 | ||
136 | executable pong | 136 | executable pong |
137 | hs-source-dirs: Demos/Pong | 137 | hs-source-dirs: Demos/Pong |
138 | main-is: Main.hs | 138 | main-is: Main.hs |
139 | other-modules: Pong | 139 | other-modules: Pong |
140 | build-depends: base, Spear, OpenGL | 140 | build-depends: base, Spear, OpenGL |
141 | ghc-options: -O2 -g | ||
142 | ghc-prof-options: -O2 -g -fprof-auto -fprof-cafs | ||
diff --git a/Spear/App.hs b/Spear/App.hs index f70dd06..b0c7141 100644 --- a/Spear/App.hs +++ b/Spear/App.hs | |||
@@ -57,16 +57,19 @@ loop' :: | |||
57 | loop' window ddt inputTimer elapsed timeBudget app = do | 57 | loop' window ddt inputTimer elapsed timeBudget app = do |
58 | timer <- gameIO $ tick inputTimer | 58 | timer <- gameIO $ tick inputTimer |
59 | 59 | ||
60 | inputEvents <- gameIO $ pollInputEvents window | ||
61 | |||
60 | let timeBudgetThisFrame = timeBudget + deltaTime timer | 62 | let timeBudgetThisFrame = timeBudget + deltaTime timer |
61 | let steps = timeBudgetThisFrame `div` ddt | 63 | let steps = timeBudgetThisFrame `div` ddt |
62 | 64 | ||
65 | --gameIO . putStrLn $ "Steps: " ++ show steps | ||
66 | |||
63 | continue <- and <$> forM [1..steps] (\i -> do | 67 | continue <- and <$> forM [1..steps] (\i -> do |
64 | let t = timeDeltaToSec $ elapsed + i * ddt | 68 | let t = timeDeltaToSec $ elapsed + i * ddt |
65 | let dt = timeDeltaToSec ddt | 69 | let dt = timeDeltaToSec ddt |
66 | inputEvents <- gameIO $ pollInputEvents window | ||
67 | stepApp app t dt inputEvents) | 70 | stepApp app t dt inputEvents) |
68 | 71 | ||
69 | let elapsed' = elapsed + steps * ddt | 72 | let elapsed' = elapsed + steps * ddt |
70 | let timeBudget' = timeBudgetThisFrame `mod` ddt | 73 | let timeBudget' = timeBudgetThisFrame `mod` ddt |
71 | 74 | ||
72 | when continue $ do | 75 | when continue $ do |
diff --git a/Spear/Math/Spatial.hs b/Spear/Math/Spatial.hs index bfab6c2..b6a3ede 100644 --- a/Spear/Math/Spatial.hs +++ b/Spear/Math/Spatial.hs | |||
@@ -88,22 +88,22 @@ class (Positional a v, Rotational a v r) => Spatial a v r t | a -> t where | |||
88 | move :: Positional a v => Float -> (a -> v) -> a -> a | 88 | move :: Positional a v => Float -> (a -> v) -> a -> a |
89 | move delta axis a = translate (axis a * delta) a | 89 | move delta axis a = translate (axis a * delta) a |
90 | 90 | ||
91 | -- | Move the spatial upwards. | 91 | -- | Move the spatial along its right axis. |
92 | moveRight delta = move delta right | 92 | moveRight delta = move delta right |
93 | 93 | ||
94 | -- | Move the spatial downwards. | 94 | -- | Move the spatial along its left axis. |
95 | moveLeft delta = moveRight (-delta) | 95 | moveLeft delta = moveRight (-delta) |
96 | 96 | ||
97 | -- | Move the spatial upwards. | 97 | -- | Move the spatial along its up axis. |
98 | moveUp delta = move delta up | 98 | moveUp delta = move delta up |
99 | 99 | ||
100 | -- | Move the spatial downwards. | 100 | -- | Move the spatial along its down axis. |
101 | moveDown delta = moveUp (-delta) | 101 | moveDown delta = moveUp (-delta) |
102 | 102 | ||
103 | -- | Move the spatial forwards. | 103 | -- | Move the spatial along its forward axis. |
104 | moveFwd delta = move delta forward | 104 | moveFwd delta = move delta forward |
105 | 105 | ||
106 | -- | Move the spatial backwards. | 106 | -- | Move the spatial along its backward axis. |
107 | moveBack delta = moveFwd (-delta) | 107 | moveBack delta = moveFwd (-delta) |
108 | 108 | ||
109 | -- | Make the spatial look at the given point. | 109 | -- | Make the spatial look at the given point. |
diff --git a/Spear/Step.hs b/Spear/Step.hs index cb4f71c..a860247 100644 --- a/Spear/Step.hs +++ b/Spear/Step.hs | |||
@@ -21,6 +21,7 @@ module Spear.Step | |||
21 | (.>), | 21 | (.>), |
22 | (<.), | 22 | (<.), |
23 | szip, | 23 | szip, |
24 | swhen, | ||
24 | switch, | 25 | switch, |
25 | multiSwitch, | 26 | multiSwitch, |
26 | ) | 27 | ) |
@@ -36,8 +37,8 @@ type Elapsed = Float | |||
36 | type Dt = Float | 37 | type Dt = Float |
37 | 38 | ||
38 | -- | A step function. | 39 | -- | A step function. |
39 | newtype Step state events input a = Step | 40 | newtype Step state events a b = Step |
40 | { runStep :: Elapsed -> Dt -> state -> events -> input -> (a, Step state events input a) | 41 | { runStep :: Elapsed -> Dt -> state -> events -> a -> (b, Step state events a b) |
41 | } | 42 | } |
42 | 43 | ||
43 | instance Functor (Step s e a) where | 44 | instance Functor (Step s e a) where |
@@ -73,29 +74,12 @@ ssnd = spure snd | |||
73 | 74 | ||
74 | -- | Construct a step that folds a given list of inputs. | 75 | -- | Construct a step that folds a given list of inputs. |
75 | -- | 76 | -- |
76 | -- The step is run N+1 times, where N is the size of the input list. | 77 | -- The step is run once per input, or not at all if the list is empty. |
77 | sfold :: Step s (Maybe e) a a -> Step s [e] a a | 78 | sfold :: Step s (Maybe e) a a -> Step s [e] a a |
78 | sfold s = Step $ \elapsed dt g es a -> | 79 | sfold s = Step $ \elapsed dt g es a -> |
79 | case es of | 80 | let (a', s') = foldl' f (a, s) es |
80 | [] -> | 81 | f (a, s) e = runStep s elapsed dt g (Just e) a |
81 | let (b', s') = runStep s elapsed dt g Nothing a | 82 | in (a', sfold s') |
82 | in (b', sfold s') | ||
83 | es -> | ||
84 | let (b', s') = sfold' elapsed dt g s a es | ||
85 | in (b', sfold s') | ||
86 | |||
87 | sfold' :: | ||
88 | Elapsed -> | ||
89 | Dt -> | ||
90 | s -> | ||
91 | Step s (Maybe e) a a -> | ||
92 | a -> | ||
93 | [e] -> | ||
94 | (a, Step s (Maybe e) a a) | ||
95 | sfold' elapsed dt g s a = foldl' f (a', s') | ||
96 | where | ||
97 | f (a, s) e = runStep s elapsed dt g (Just e) a | ||
98 | (a', s') = runStep s elapsed dt g Nothing a | ||
99 | 83 | ||
100 | -- Combinators | 84 | -- Combinators |
101 | 85 | ||
@@ -117,9 +101,22 @@ szip f (Step s1) (Step s2) = Step $ \elapsed dt g e d -> | |||
117 | (b, s2') = s2 elapsed dt g e d | 101 | (b, s2') = s2 elapsed dt g e d |
118 | in (f a b, szip f s1' s2') | 102 | in (f a b, szip f s1' s2') |
119 | 103 | ||
120 | -- | Construct a step that switches between two steps based on input. | 104 | -- | Construct a step that is executed when the given event occurs. |
105 | swhen :: Eq e => e -> Step s () a a -> Step s (Maybe e) a a | ||
106 | swhen expectedEvent step = Step $ \elapsed dt state maybeEvent a -> | ||
107 | case maybeEvent of | ||
108 | Nothing -> (a, swhen expectedEvent step) | ||
109 | Just event -> | ||
110 | if event == expectedEvent | ||
111 | then let (a', step') = runStep step elapsed dt state () a | ||
112 | in (a', swhen expectedEvent step') | ||
113 | else (a, swhen expectedEvent step) | ||
114 | |||
115 | -- | Construct a step that switches between two steps based on input events. | ||
116 | -- | ||
117 | -- The current step runs with every 'runStep' even when there are no new events. | ||
121 | -- | 118 | -- |
122 | -- The initial step is the first one. | 119 | -- The initial step is the identity, 'sid'. |
123 | switch :: | 120 | switch :: |
124 | Eq e => | 121 | Eq e => |
125 | e -> | 122 | e -> |
@@ -127,16 +124,8 @@ switch :: | |||
127 | e -> | 124 | e -> |
128 | Step s (Maybe e) a a -> | 125 | Step s (Maybe e) a a -> |
129 | Step s (Maybe e) a a | 126 | Step s (Maybe e) a a |
130 | switch flag1 s1 flag2 s2 = switch' s1 flag1 s1 flag2 s2 | 127 | switch = switch' sid |
131 | 128 | ||
132 | switch' :: | ||
133 | Eq e => | ||
134 | Step s (Maybe e) a a -> | ||
135 | e -> | ||
136 | Step s (Maybe e) a a -> | ||
137 | e -> | ||
138 | Step s (Maybe e) a a -> | ||
139 | Step s (Maybe e) a a | ||
140 | switch' cur flag1 s1 flag2 s2 = Step $ \elapsed dt g e a -> | 129 | switch' cur flag1 s1 flag2 s2 = Step $ \elapsed dt g e a -> |
141 | case e of | 130 | case e of |
142 | Nothing -> | 131 | Nothing -> |
@@ -146,11 +135,15 @@ switch' cur flag1 s1 flag2 s2 = Step $ \elapsed dt g e a -> | |||
146 | let next | 135 | let next |
147 | | e' == flag1 = s1 | 136 | | e' == flag1 = s1 |
148 | | e' == flag2 = s2 | 137 | | e' == flag2 = s2 |
149 | | otherwise = cur | 138 | | otherwise = cur |
150 | (a', s') = runStep next elapsed dt g e a | 139 | (a', s') = runStep next elapsed dt g e a |
151 | in (a', switch' s' flag1 s1 flag2 s2) | 140 | in (a', switch' s' flag1 s1 flag2 s2) |
152 | 141 | ||
153 | -- | Construct a step that switches among multiple steps based on input. | 142 | -- | Construct a step that switches among multiple steps based on input events. |
143 | -- | ||
144 | -- The current step runs with every 'runStep' even when there are no new events. | ||
145 | -- | ||
146 | -- The initial step is the identity, 'sid'. | ||
154 | multiSwitch :: (Eq e, Ord e) => [(e, Step s (Maybe e) a a)] -> Step s (Maybe e) a a | 147 | multiSwitch :: (Eq e, Ord e) => [(e, Step s (Maybe e) a a)] -> Step s (Maybe e) a a |
155 | multiSwitch xs = multiSwitch' Nothing sid (Map.fromList xs) | 148 | multiSwitch xs = multiSwitch' Nothing sid (Map.fromList xs) |
156 | 149 | ||
diff --git a/Spear/Window.hs b/Spear/Window.hs index 3cdc5f5..be52080 100644 --- a/Spear/Window.hs +++ b/Spear/Window.hs | |||
@@ -127,10 +127,11 @@ setup (w, h) windowTitle = do | |||
127 | 127 | ||
128 | GLFW.makeContextCurrent maybeWindow | 128 | GLFW.makeContextCurrent maybeWindow |
129 | 129 | ||
130 | GLFW.swapInterval 1 -- Enable vsync. | ||
131 | |||
130 | GLFW.setWindowCloseCallback window . Just $ onWindowClose closeRequest | 132 | GLFW.setWindowCloseCallback window . Just $ onWindowClose closeRequest |
131 | GLFW.setWindowSizeCallback window . Just $ onResize windowEvents | 133 | GLFW.setWindowSizeCallback window . Just $ onResize windowEvents |
132 | GLFW.setKeyCallback window . Just $ onKey inputEvents | 134 | GLFW.setKeyCallback window . Just $ onKey inputEvents |
133 | GLFW.setCharCallback window . Just $ onChar inputEvents | ||
134 | GLFW.setMouseButtonCallback window . Just $ onMouseButton inputEvents | 135 | GLFW.setMouseButtonCallback window . Just $ onMouseButton inputEvents |
135 | GLFW.setCursorPosCallback window . Just $ onMouseMove mousePos inputEvents | 136 | GLFW.setCursorPosCallback window . Just $ onMouseMove mousePos inputEvents |
136 | 137 | ||
@@ -178,19 +179,16 @@ onWindowClose closeRequest window = putMVar closeRequest True | |||
178 | -- the last in a poll can be ignored, we just replace the contents of the mvar | 179 | -- the last in a poll can be ignored, we just replace the contents of the mvar |
179 | -- here instead of adding the event to the list. | 180 | -- here instead of adding the event to the list. |
180 | onResize :: MVar [WindowEvent] -> GLFW.WindowSizeCallback | 181 | onResize :: MVar [WindowEvent] -> GLFW.WindowSizeCallback |
181 | onResize windowEvents window w h = putMVar windowEvents [ResizeEvent w h] | 182 | onResize windowEvents window w h = modifyMVar_ windowEvents (return <$> const [ResizeEvent w h]) |
182 | 183 | ||
183 | onKey :: MVar [InputEvent] -> GLFW.KeyCallback | 184 | onKey :: MVar [InputEvent] -> GLFW.KeyCallback |
184 | onKey events window key _ GLFW.KeyState'Pressed _ = addEvent events $ KeyDown (fromGLFWkey key) | 185 | onKey events window key _ GLFW.KeyState'Pressed _ = addEvent events $ KeyDown (fromGLFWkey key) |
185 | onKey events window key _ GLFW.KeyState'Released _ = addEvent events $ KeyUp (fromGLFWkey key) | 186 | onKey events window key _ GLFW.KeyState'Released _ = addEvent events $ KeyUp (fromGLFWkey key) |
186 | onKey events window key _ GLFW.KeyState'Repeating _ = return () | 187 | onKey events window key _ GLFW.KeyState'Repeating _ = return () |
187 | 188 | ||
188 | onChar :: MVar [InputEvent] -> GLFW.CharCallback | ||
189 | onChar events window char = addEvent events $ KeyDown . fromGLFWkey . read $ [char] | ||
190 | |||
191 | onMouseButton :: MVar [InputEvent] -> GLFW.MouseButtonCallback | 189 | onMouseButton :: MVar [InputEvent] -> GLFW.MouseButtonCallback |
192 | onMouseButton events window button GLFW.MouseButtonState'Pressed _ = addEvent events $ MouseDown (fromGLFWbutton button) | 190 | onMouseButton events window button GLFW.MouseButtonState'Pressed _ = addEvent events $ MouseDown (fromGLFWbutton button) |
193 | onMouseButton events window button GLFW.MouseButtonState'Released _ = addEvent events $ MouseUp (fromGLFWbutton button) | 191 | onMouseButton events window button GLFW.MouseButtonState'Released _ = addEvent events $ MouseUp (fromGLFWbutton button) |
194 | 192 | ||
195 | onMouseMove :: MVar MousePos -> MVar [InputEvent] -> GLFW.CursorPosCallback | 193 | onMouseMove :: MVar MousePos -> MVar [InputEvent] -> GLFW.CursorPosCallback |
196 | onMouseMove oldPos events window x y = do | 194 | onMouseMove oldPos events window x y = do |
@@ -208,45 +206,45 @@ replaceMVar mvar val = tryTakeMVar mvar >> putMVar mvar val | |||
208 | addEvent :: MVar [a] -> a -> IO () | 206 | addEvent :: MVar [a] -> a -> IO () |
209 | addEvent mvar val = | 207 | addEvent mvar val = |
210 | tryTakeMVar mvar >>= \xs -> case xs of | 208 | tryTakeMVar mvar >>= \xs -> case xs of |
211 | Nothing -> putMVar mvar [val] | 209 | Nothing -> putMVar mvar [val] -- >> (putStrLn $ show val) |
212 | Just events -> putMVar mvar (val : events) | 210 | Just events -> putMVar mvar (val : events) -- >> (putStrLn $ show (val:events)) |
213 | 211 | ||
214 | -- Input | 212 | -- Input |
215 | 213 | ||
216 | -- | Run the game action when the key is down. | 214 | -- | Run the game action when the key is down. |
217 | whenKeyDown :: GLFW.Window -> Key -> Game s a -> Game s () | 215 | whenKeyDown :: GLFW.Window -> Key -> Game s a -> Game s () |
218 | whenKeyDown = whenKeyInState (== GLFW.KeyState'Pressed) | 216 | whenKeyDown = whenKeyInState GLFW.KeyState'Pressed |
219 | 217 | ||
220 | -- | Run the game action when the key is up. | 218 | -- | Run the game action when the key is up. |
221 | whenKeyUp :: GLFW.Window -> Key -> Game s a -> Game s () | 219 | whenKeyUp :: GLFW.Window -> Key -> Game s a -> Game s () |
222 | whenKeyUp = whenKeyInState (== GLFW.KeyState'Released) | 220 | whenKeyUp = whenKeyInState GLFW.KeyState'Released |
223 | 221 | ||
224 | whenKeyInState :: (GLFW.KeyState -> Bool) -> GLFW.Window -> Key -> Game s a -> Game s () | 222 | whenKeyInState :: GLFW.KeyState -> GLFW.Window -> Key -> Game s a -> Game s () |
225 | whenKeyInState pred window key game = do | 223 | whenKeyInState state window key game = do |
226 | isDown <- fmap pred $ gameIO . GLFW.getKey window . toGLFWkey $ key | 224 | isDown <- fmap (==state) $ gameIO . GLFW.getKey window . toGLFWkey $ key |
227 | when isDown $ void game | 225 | when isDown $ void game |
228 | 226 | ||
229 | -- | Process the keyboard keys, returning those values for which their | 227 | -- | Check whether the given keys are pressed and return the value associated |
230 | -- corresponding key is pressed. | 228 | -- with each of the pressed keys. |
231 | processKeys :: GLFW.Window -> [(Key, a)] -> Game s [a] | 229 | processKeys :: Window -> [(Key, a)] -> Game s [a] |
232 | processKeys window = foldM f [] | 230 | processKeys window = foldM f [] |
233 | where | 231 | where |
234 | f acc (key, result) = do | 232 | f acc (key, result) = do |
235 | isDown <- | 233 | isDown <- |
236 | fmap (== GLFW.KeyState'Pressed) $ | 234 | fmap (== GLFW.KeyState'Pressed) $ |
237 | gameIO . GLFW.getKey window . toGLFWkey $ | 235 | gameIO . GLFW.getKey (glfwWindow window) . toGLFWkey $ |
238 | key | 236 | key |
239 | return $ if isDown then result : acc else acc | 237 | return $ if isDown then result : acc else acc |
240 | 238 | ||
241 | -- | Process the mouse buttons, returning those values for which their | 239 | -- | Check whether the given buttons are pressed and return the value associated |
242 | -- corresponding button is pressed. | 240 | -- with each of the pressed buttons. |
243 | processButtons :: GLFW.Window -> [(MouseButton, a)] -> Game s [a] | 241 | processButtons :: Window -> [(MouseButton, a)] -> Game s [a] |
244 | processButtons window = foldM f [] | 242 | processButtons window = foldM f [] |
245 | where | 243 | where |
246 | f acc (button, result) = do | 244 | f acc (button, result) = do |
247 | isDown <- | 245 | isDown <- |
248 | fmap (== GLFW.MouseButtonState'Pressed) $ | 246 | fmap (== GLFW.MouseButtonState'Pressed) $ |
249 | gameIO . GLFW.getMouseButton window . toGLFWbutton $ | 247 | gameIO . GLFW.getMouseButton (glfwWindow window) . toGLFWbutton $ |
250 | button | 248 | button |
251 | return $ if isDown then result : acc else acc | 249 | return $ if isDown then result : acc else acc |
252 | 250 | ||