I want to move an object in Haskell Gloss every frame a key is pressed, not just the one frame that the key is started being pressed. (Example: While 'w' key is pressed, accelerate object every frame)
Edit: I tried using the second parameter of EventKey but to no avail.
My code:
--TODO - Holding keys doesn't work yet
handleKeys :: Event -> AsteroidsGame -> AsteroidsGame
handleKeys (EventKey (Char char) _ _ _) game
| char == 'w' = move 0 1
| char == 'a' = move (-1) 0
| char == 's' = move 0 (-1)
| char == 'd' = move 1 0
where move x y = game {player = accelerateObject (player game) x y}
handleKeys _ game = game
accelerateObject :: Object -> Float -> Float -> Object
accelerateObject obj hor ver = obj {vel = (vx + hor, vy + ver)}
where (vx, vy) = vel obj
As OP correctly deduced, gloss gives you input events ("key was just pressed", "mouse was just moved"), rather than input state ("key is currently pressed", "mouse is at x,y"). There doesn't seem to be a built-in way to see input state on each frame, so we'll have to make our own workaround. Thankfully, this isn't too difficult!
For a simple working example, we'll make an incredibly fun "game" where you can watch a counter count upwards while the space bar is pressed. Riveting. This approach generalises to handling any key presses, so it'll be easy to extend to your case.
The first thing we need is our game state:
import qualified Data.Set as S
data World = World
{ keys :: S.Set Key
, counter :: Int }
We keep track of our specific game state (in this case just a counter), as well as state for our workaround (a set of pressed keys).
Handling input events just involves either adding a key to our set of currently pressed keys or removing it:
handleInput :: Event -> World -> World
handleInput (EventKey k Down _ _) world = world { keys = S.insert k (keys world)}
handleInput (EventKey k Up _ _) world = world { keys = S.delete k (keys world)}
handleInput _ world = world -- Ignore non-keypresses for simplicity
This can easily be extended to handle eg. mouse movement, by changing our World type to keep track of the last known coordinates of the cursor, and setting it in this function whenever we see an EventMotion event.
Our frame-to-frame world update function then uses the input state to update the specific game state:
update :: Float -> World -> World
update _ world
| S.member (SpecialKey KeySpace) (keys world) = world { counter = 1 + counter world }
| otherwise = world { counter = 0 }
If the spacebar is currently pressed (S.member (SpecialKey KeySpace) (keys world)), increment the counter - otherwise, reset it to 0. We don't care about how much time as elapsed between frames so we ignore the float argument.
Finally we can render our game and play it:
render :: World -> Picture
render = color white . text . show . counter
main :: IO ()
main = play display black 30 initWorld render handleInput update
where
display = InWindow "test" (800, 600) (0, 0)
initWorld = World S.empty 0
Related
I'm trying to make the folowing function:
repcountIORIban :: IORef -> Int -> Int -> Int -> Int -> Lock -> IORef -> Lock -> Int -> Int -> IO ()
repcountIORIban count number lower modulus amountthreads lock done lock2 difference rest = do
if rest > number
then let extra = 1
else let extra = 0
if number + 1 < amountthreads
then
forkIO $ realcountIORIban(count lower (lower + difference + extra - 1) modulus lock done lock2)
repcountIORIban (count (number + 1) (lower + difference + extra) modulus amountthreads lock done lock2 difference rest)
else
forkIO $ realcountIORIban(count lower (lower + difference + extra - 1) modulus lock done lock2)
But I can't run the program from which this function is a part of. It gives me the error:
error: parse error on input `else'
|
113 | else let extra = 0
| ^^^^
I've got this error a lot of times withing my program but I don't know what I'm doing wrong.
This is incorrect, you can't let after then/else and expect those lets to define bindings which are visible below.
do if rest > number
then let extra = 1 -- wrong, needs a "do", or should be "let .. in .."
else let extra = 0
... -- In any case, extra is not visible here
Try this instead
do let extra = if rest > number
then 1
else 0
...
Further, you need then do if after that you need to perform two or more actions.
if number + 1 < amountthreads
then do
something
somethingElse
else -- use do here if you have two or more actions
...
I'm trying to use the value of a textarea with js_of_ocaml. Here is my source code :
let matrix () =
let text1 = get_textarea "input_1" in
let btn1 = get_elem "btn_1" in
text1##placeholder <- Js.string "Write your matrix !";
btn1##textContent <- Js.some (Js.string "Calculate");
btn1##onclick <- Dom_html.handler (if Matrix.is_matrix (Js.to_string text1##value)
then (let matrix = Matrix.get_matrix (Js.to_string text1##value) in
fun ev -> (set_matrix_caracteristics (Matrix.nb_lines matrix) (Matrix.nb_columns matrix) (Matrix.determinant matrix) ; Js._false))
else fun ev -> (error_input_matrix() ; Js._false))
I want to do some calculs on a matrix, and the matrix is write by the user through a textarea with an html interface.
I think the problem is that the value of text1don't change, even if I write something in the textarea. Whatever the input on the textarea is, the result is the same.
Do anyone know how to use the value write by the user ?
Thanks !
Your probleme here is that you evaluate text1##value before defining the handler function, so the value is evaluated when the handler is installed, and then never changed. the following should works
btn1##onclick <- Dom_html.handler (
fun ev ->
if Matrix.is_matrix (Js.to_string text1##value)
then let matrix = Matrix.get_matrix (Js.to_string text1##value) in
(set_matrix_caracteristics (Matrix.nb_lines matrix) (Matrix.nb_columns matrix) (Matrix.determinant matrix) ; Js._false)
else error_input_matrix() ; Js._false
)
For an Elm (0.13) game I'm developing, I'd like to have reconfigurable inputs. A simplified version of the model that I've got for this is
type Controls = {
up: KeyCode,
down: KeyCode,
left: KeyCode,
right: KeyCode
}
type Player = {
...
controls: Controls,
...
}
type Game = {
state: State,
players: [Player]
}
For the game loop, I'm using a standard foldp construction, where I'd like the input to be dependent of the current state of the game. So far I tried the following:
gameState =
let
initialGame = (newGame initialActive)
in
foldp update initialGame (input initialGame)
but of course, the input signal generating function (input : Game -> Signal Input) keeps on using the initial game, and not the changed gamestate.
I've searched troughout the internet to find a solution to this, but I couldn't find anything in this direction. Is there a way to do it the way I was trying (by including the controls in the model), or will I have to release the controls from the model by making the controls signals themselves?
There is a closely related question here: Creating custom keyboard controls [Elm]
If you want to, you can keep the Controls part of your game state. The way to do this is not to base your input on the initialGame, but to take more general input from which you can always extract what you need. In this case that would be Keyboard.keysDown. Then you can get the input for each player using:
playerInput : Controls -> [KeyCode] -> { x : Int, y : Int }
playerInput {up,down,left,right} kd =
List.filter (\ky -> List.member ky [up,down,left,right]) kd |>
List.foldl (\ky st -> if | ky == up -> { st | y <- st.y + 1 }
| ky == down -> { st | y <- st.y - 1 }
| ky == left -> { st | x <- st.x - 1 }
| ky == right -> { st | x <- st.x + 1 }
) {x=0,y=0}
If you want to remove Controls from Game you can hoist it up into the definition of the input for the foldp.
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
What would be the typical game skeleton for a Haskell game, let's say a simple shoot them up for instance?
I am particularly interested on the data structure, and how to manage the update of all the elements in the world, in regards of immutability issue.
Just saying that
new_world=update_world(world)
is a little bit simplistic. For instance, how to deal with multiple interaction that can occurs between element of the world (collisions, reaction to player, etc....) especially when you have a lot of 'independent' elements impacted.
The main concern is about immutability of the world, which makes very difficult to update a "small" part of the world based on another subset of the world.
I love gloss (gloss 2D library. it's very closed to your needs (I think)
A very simple example
import Graphics.Gloss
import Graphics.Gloss.Interface.Pure.Game
-- Your app data
data App = App { mouseX :: Float
, mouseY :: Float
}
-- Draw world using your app data
-- Here thing about *draw* your world (e.g. if radius > MAX then red else blue)
drawWorld (App mousex mousey) = color white $ circle mousex
-- Handle input events and update your app data
-- Here thing about user interaction (e.g. when press button start jump!)
handleEvent
(EventMotion (x, y)) -- current viewport mouse coordinates
(App _ _) = App x y
handleEvent e w = w
-- Without input events you can update your world by time
-- Here thing about time (e.g. if jumping use `t` to compute new position)
handleTime t w = w
runApp =
play
( InWindow "Test" (300, 300) (0, 0) ) -- full screen or in window
black -- background color
20 -- frames per second
( App 0 0 ) -- your initial world
drawWorld -- how to draw the world?
handleEvent -- how app data is updated when IO events?
handleTime -- how app data is updated along the time?
-- enjoy!
main = runApp
One simple example modifying some data structure (a list of circle radius) along the three event handlers (draw, input and time)
import Graphics.Gloss
import Graphics.Gloss.Interface.Pure.Game
import System.IO.Unsafe
data App = App { mouseX :: Float
, mouseY :: Float
, circleList :: [Float]
, lastTime :: Float
, currTime :: Float
}
drawWorld app =
color white $ pictures $ map circle $ circleList app
handleEvent
(EventMotion (x, y)) -- current viewport mouse coordinates
app = app { mouseX = x, mouseY = y,
-- drop circles with radius > mouse **MOVED** position
circleList = filter (<(abs x)) $ circleList app }
handleEvent e app = app
handleTime t app =
app { currTime = currTime', lastTime = lastTime', circleList = circleList' }
where currTime' = currTime app + t
-- create new circle each 1 second
createNew = currTime' - lastTime app > 1
lastTime' = if createNew then currTime' else lastTime app
-- each step, increase all circle radius
circleList' = (if createNew then [0] else []) ++ map (+0.5) (circleList app)
runApp =
play
( InWindow "Test" (300, 300) (0, 0) )
black
20
( App 0 0 [] 0 0 )
drawWorld
handleEvent
handleTime
main = runApp
with result
Well i have some trouble removing an object from the game, the thing is that i have a player class (made out of a metatable), inside it i have a variable called sprite that holds the address for the image sprite i will draw onScreen, so when i create the object i don't draw the sprite right away, for that i made a function draw (this is just to explain what i have). in the game.lua i draw the player by calling that function, and afterwards i want to delete my instance of player (so that way the image onscreen dissapears also)... thats all, i tried player:removeSelf(), display.remove(player) and one of them threw me an error (attemp to call field 'removeSelf' (a nil value)) and the other one runs fine but it doesn't change the fact that the player is still there (i can acces it's functions and the sprite is still shown onscreen... well here is my code:
**********************************************************
game.lua:
**********************************************************
---------------------------------------------------------------------------------
-- BEGINNING OF YOUR IMPLEMENTATION
---------------------------------------------------------------------------------
local _W, _H = display.contentWidth * 0.5, display.contentHeight * 0.5
local background, player, land
local spriteSizeX, spriteSizeY = 60,70
-- Called when the scene's view does not exist:
function scene:createScene( event )
local group = self.view
local BG = display.newGroup()
local stage = display.newGroup()
local foreground = display.newGroup()
group:insert(BG)
group:insert(stage)
group:insert(foreground)
-----------------------------------------------------------------------------
-- CREATE display objects and add them to 'group' here.
-- Example use-case: Restore 'group' from previously saved state.
-----------------------------------------------------------------------------
background = display.newImage("assets/backgrounds/oliveBackground.png", true)
background.x, background.y = _W, _H
BG:insert( background )
player = playerClass.new()
player:draw(15,57.5,foreground)
--player:movePlayer(300,140)
terrain = {}
local m,j = 0,0
for i = 1, 16 do
local l = 1
for k = 3, 10 do
land = landClass.new({posx = i, posy = k})
table.insert(terrain, land)
m = m +1
terrain[m]:draw( spriteSizeX/4 + ((spriteSizeX/2) * j), spriteSizeY/4 + ((spriteSizeY/2) * l) + 5, stage)
l = l + 1
end
j = j+1
end
-- remove an asset doesn't work
--display.remove(terrain[1]:getSprite())
--terrain[1].removeSelf()
-- terrain[1] = nil
player:destroy()
end
**********************************************************
player.lua:
**********************************************************
-- player class
local player = {}
local player_mt = { __index = player }
--[[
-- attributes
local sprite, coins, speed
]]--
function player.new() -- constructor
local newPlayer = {
sprite = "assets/char/miner.png",
coins = 1000,
speed = 1
}
return setmetatable( newPlayer, player_mt )
end
-- local function, works only when called from inside this class
local function getName()
-- print("")
end
function player:draw(x,y,group)
sprite = display.newImage(self.sprite)
sprite.x, sprite.y = x, y
sprite.xScale, sprite.yScale = 0.5, 0.5
group:insert(sprite)
end
function player:movePlayer(posx,posy)
transition.to(sprite, { x = posx, y = posy, time=500, delay=0, alpha=1.0 })
end
function player:destroy()
-- none of them work
-- self.sprite = nil
-- self.sprite.removeSelf()
end
return player
After creating your sprite using display.newImage, you did not store it in the instance.
self.sprite just has the string value "assets/char/miner.png"
in your draw function add
self.spriteObject = sprite
and in your destroy function ,
self.spriteObject:removeSelf()
You can use one of these functions for your related object:
removeSelf()
destroy()