ActionKid-0.1.0.0: src/Main.hs
{-# LANGUAGE TemplateHaskell, NoMonomorphismRestriction #-}
{-# OPTIONS_GHC -fno-full-laziness -fno-cse #-}
import ActionKid
import ActionKid.Utils
import Control.Lens
import Control.Monad.State
-- This is an example of how to use ActionKid to make games with Haskell.
-- ActionKid is inspired by Actionscript, so you will see some
-- similarities.
--
-- Every video game has some objects on the screen that interact with each
-- other. For example, in a game of Mario, you will have Mario himself,
-- goombas, mushrooms, pipes etc. These objects are called "movie clip"s in
-- ActionKid terminology. Any data type that is an instance of the
-- MovieClip class can be used in your game.
-- So first, make a data type for every object you will have in your game.
-- In this demo game, we just have a player that will move around.
--
-- Every constructor must have `Attributes` as it's last field.
data Player = Player { _pa :: Attributes }
-- Ok, you have a Player type. Now before you can use it in your game,
-- make it an instance of MovieClip. You can do this automatically with
-- `deriveMC`:
deriveMC ''Player
-- Now that the player is a MovieClip, you can write code like this:
--
-- > player.x += 10
--
-- and the player will move 10 pixels to the right!
-- More on this later.
-- You also need a data type that will be the game state.
data GameState = GameState {
_player :: Player,
_ga :: Attributes
}
-- Use this convenience function to make MovieClip instances
-- for your data types automatically.
deriveMC ''GameState
-- Next, I suggest you make lenses for all of your data types.
-- If you don't know how lenses work, check out the intro README here:
-- https://github.com/ekmett/lens
--
-- and this tutorial:
-- https://www.fpcomplete.com/school/to-infinity-and-beyond/pick-of-the-week/basic-lensing
--
-- Lenses are great for working with nested data structures, and writing
-- functional code that looks imperative. Both are big plusses for game
-- development.
-- So this step is optional, but recommended.
makeLenses ''Player
makeLenses ''GameState
-- Finally, you need to make both Player and GameState
-- instances of Renderable. This defines how they will be shown on the
-- screen.
--
-- The `render` function returns a Gloss Picture:
--
-- http://hackage.haskell.org/package/gloss-1.8.2.2/docs/Graphics-Gloss-Data-Picture.html
instance Renderable Player where
render p = color blue $ box 50 50
-- Here we are just rendering the player as a blue box. With ActionKid,
-- you can also use an image from your computer instead:
--
-- > render p = image "images/player.png"
-- To render the game state, we just render the player.
-- To do that, use the `display` function. `display` will render
-- the player at the right x and y coordinates.
instance Renderable GameState where
render gs = display (_player gs)
-- If the game state has multiple items, you can render them all by
-- concatenating them:
--
-- > render gs = display (_player1 gs) <> display (_player2 gs)
-- this is the default game state. The player starts at coordinates (0,0)
-- (the bottom left of the screen).
-- NOTE: For the `Attributes` field, you can just use `def`. This will set
-- the correct default attributes for an object.
--
-- So this creates the game state with a player in it. Both have default
-- attributes.
gameState = (GameState (Player def) def)
-- All of the core game logic takes place in this monad transformer stack.
-- The State is the default game state we just made.
type GameMonad a = StateT GameState IO a
--------------------------------------------------------------------------
--------------------------------------------------------------------------
-- Ok, now we are done specifying all the data types and how they should
-- look! Now it's time to implement the core game logic. There are two
-- functions you need to define:
--
-- 1. An event handler (for key presses/mouse clicks)
-- 2. A game loop.
--
-- The event handler listens for user input, and moves the player etc.
-- The game loop is where the rest of the logic happens: firing bullets,
-- hitting an enemy, animations etc etc.
--------------------------------------------------------------------------
--------------------------------------------------------------------------
-- This is the event handler. Since we are using lenses, this logic is
-- really easy to write.
eventHandler :: Event -> GameMonad ()
eventHandler (EventKey (SpecialKey KeyLeft) Down _ _) = player.x -= 10
eventHandler (EventKey (SpecialKey KeyRight) Down _ _) = player.x += 10
eventHandler (EventKey (SpecialKey KeyUp) Down _ _) = player.y += 10
eventHandler (EventKey (SpecialKey KeyDown) Down _ _) = player.y -= 10
eventHandler _ = return ()
-- This is the main loop. It does nothing right now.
mainLoop :: Float -> GameMonad ()
mainLoop _ = return ()
-- Now lets run the game! The run function takes:
-- 1. the title for the window of the game
-- 2. the size of the window
-- 3. the initial game state
-- 4. the eventHandler function
-- 5. the main loop function
main = run "demo game" (500, 500) gameState eventHandler mainLoop