Patrick Hurst | 16 Feb 05:31 2013

Netwire, keyboard events, and games

I'm using netwire to build a game; one of the things the player can do is move around using WASD. I want to use key *events* as the 'basis' of my wires, not the entire state vector of the keyboard so that, for example, a press event on D increments the velocity by (200, 0) and a release event decrements it by that much. However, this has the problem that I can't actually get the velocity unless I have an event to feed into the wires, which means I can only update the screen when the user presses or releases a key. While this might make for interesting gameplay, it's not what I want.

is the right thing to do to make the input something like a Maybe KeyEvent instead, and pass in Nothing whenever I want to get output without an event for, e.g., a render?
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe <at> haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe
Ertugrul Söylemez | 17 Feb 00:14 2013
Picon

Re: Netwire, keyboard events, and games

Patrick Hurst <lightquake <at> amateurtopologist.com> wrote:

> I'm using netwire to build a game; one of the things the player can do
> is move around using WASD. I want to use key *events* as the 'basis'
> of my wires, not the entire state vector of the keyboard so that, for
> example, a press event on D increments the velocity by (200, 0) and a
> release event decrements it by that much. However, this has the
> problem that I can't actually get the velocity unless I have an event
> to feed into the wires, which means I can only update the screen when
> the user presses or releases a key. While this might make for
> interesting gameplay, it's not what I want.
>
> is the right thing to do to make the input something like a Maybe
> KeyEvent instead, and pass in Nothing whenever I want to get output
> without an event for, e.g., a render?

That highly depends on how you want to process the keys.  For real-time
games you probably want continuous "key held" events instead of
instantaneous "key down" and "key up" events.  The nicest way here is to
use a reader monad below the wire with a set of currently pressed keys:

    data GameState =
        GameState {
          gsKeyDown :: Set Key
          {- ... -}
        }

    type GameWire = WireM (Reader GameState)

That way you can write your event wires like identity wires, which makes
using them much more convenient.  Here is one way to write such an event
wire:

    keyDown :: Key -> GameWire a a
    keyDown key =
        mkFixM $ \_ x -> do
            pressed <- asks (S.member key . gsKeyDown)
            return (if pressed then Right x else Left mempty)

This wire acts like the identity wire when the key is pressed and
inhibits otherwise.  From such a wire you can easily construct a
velocity wire for one direction:

    direction :: Key -> Double -> GameWire a Double
    direction key speed =
        pure speed . keyDown key <|> 0

When the key is held down, this wire has the 'speed' value, otherwise it
has the value 0.  A one-dimensional velocity is thus just the sum of two
of these directions:

    velocity1 :: Key -> Key -> Double -> GameWire a Double
    velocity1 upKey downKey speed =
        direction upKey speed +
        direction downKey (-speed)

A two-dimensional velocity is just a pair of one-dimensional velocities:

    velocity2 speed =
        velocity1 W S speed &&&
        velocity1 A D speed

Finally all you need is to turn the velocity into a position.  So
integrate it:

    position = integral_ (x0, y0) . velocity2 1

I hope this helps.

Greets,
Ertugrul

--

-- 
Not to be or to be and (not to be or to be and (not to be or to be and
(not to be or to be and ... that is the list monad.
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe <at> haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe
Kata Recurse | 17 Feb 00:27 2013

Re: Netwire, keyboard events, and games

On Feb 16, 2013, at 18:14, "Ertugrul Söylemez" <es <at> ertes.de> wrote:

> Patrick Hurst <lightquake <at> amateurtopologist.com> wrote:
>
>> I'm using netwire to build a game; one of the things the player can do
>> is move around using WASD. I want to use key *events* as the 'basis'
>> of my wires, not the entire state vector of the keyboard so that, for
>> example, a press event on D increments the velocity by (200, 0) and a
>> release event decrements it by that much. However, this has the
>> problem that I can't actually get the velocity unless I have an event
>> to feed into the wires, which means I can only update the screen when
>> the user presses or releases a key. While this might make for
>> interesting gameplay, it's not what I want.
>>
>> is the right thing to do to make the input something like a Maybe
>> KeyEvent instead, and pass in Nothing whenever I want to get output
>> without an event for, e.g., a render?
>
> That highly depends on how you want to process the keys.  For real-time
> games you probably want continuous "key held" events instead of
> instantaneous "key down" and "key up" events.  The nicest way here is to
> use a reader monad below the wire with a set of currently pressed keys:
>
>    data GameState =
>        GameState {
>          gsKeyDown :: Set Key
>          {- ... -}
>        }
>
>    type GameWire = WireM (Reader GameState)
>
> That way you can write your event wires like identity wires, which makes
> using them much more convenient.  Here is one way to write such an event
> wire:
>
>    keyDown :: Key -> GameWire a a
>    keyDown key =
>        mkFixM $ \_ x -> do
>            pressed <- asks (S.member key . gsKeyDown)
>            return (if pressed then Right x else Left mempty)
>
> This wire acts like the identity wire when the key is pressed and
> inhibits otherwise.  From such a wire you can easily construct a
> velocity wire for one direction:
>
>    direction :: Key -> Double -> GameWire a Double
>    direction key speed =
>        pure speed . keyDown key <|> 0
>
> When the key is held down, this wire has the 'speed' value, otherwise it
> has the value 0.  A one-dimensional velocity is thus just the sum of two
> of these directions:
>
>    velocity1 :: Key -> Key -> Double -> GameWire a Double
>    velocity1 upKey downKey speed =
>        direction upKey speed +
>        direction downKey (-speed)
>
> A two-dimensional velocity is just a pair of one-dimensional velocities:
>
>    velocity2 speed =
>        velocity1 W S speed &&&
>        velocity1 A D speed
>
> Finally all you need is to turn the velocity into a position.  So
> integrate it:
>
>    position = integral_ (x0, y0) . velocity2 1
>
> I hope this helps.
>
>
> Greets,
> Ertugrul
>
> --
> Not to be or to be and (not to be or to be and (not to be or to be and
> (not to be or to be and ... that is the list monad.
> _______________________________________________
> Haskell-Cafe mailing list
> Haskell-Cafe <at> haskell.org
> http://www.haskell.org/mailman/listinfo/haskell-cafe

This is the approach that I currently use; it was pointed out to me
that polling the state of the keys on every input is considered bad
practice since it means that key clicks that happen in between physics
updates don't get registered at all, hence why I wanted to use a more
event-driven approach.
Ertugrul Söylemez | 17 Feb 03:12 2013
Picon

Re: Netwire, keyboard events, and games

Kata Recurse <lightquake <at> amateurtopologist.com> wrote:

> This is the approach that I currently use; it was pointed out to me
> that polling the state of the keys on every input is considered bad
> practice since it means that key clicks that happen in between physics
> updates don't get registered at all, hence why I wanted to use a more
> event-driven approach.

The idea of this approach is that the set of currently pressed keys is
constructed by the application loop, not the wire itself.  Every event
triggers another step of the wire.

One problem with that approach is that the rendering may be much slower
than a wire step, so you get event congestion and thus delayed
responses.  This suggests that it would pay off to handle all events in
a single instant, but that is much more difficult than it sounds.  What
should a wire do, if in a single instant a key is pressed and released,
or if it is pressed multiple times?

A simple solution exists:  Wires can deal with a time delta of 0, so
step the wire with dt = 0 for all queued events before you render the
next frame.  There is nothing wrong with mixing this with sessions.  In,
say, SDL terms, if the event is NoEvent, use stepSession* and render.
If it's any other event, use stepWire* and don't render.

Greets,
Ertugrul

--

-- 
Key-ID: E5DD8D11 "Ertugrul Soeylemez <es <at> ertes.de>"
FPrint: BD28 3E3F BE63 BADD 4157  9134 D56A 37FA E5DD 8D11
Keysrv: hkp://subkeys.pgp.net/
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe <at> haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe

Gmane