Holy crap that's a lot to digest! Thanks! A lot of ideas in here to
play with. For me and my purposes with Processing the big challenge
has been the fact that I have little say about the draw loop. It just
fires (or I can call it manually with a redraw, but then it just
executes whatever is in that function). I had a nice recursive
function with no state in the beginning but the first fire of draw
would set it off and it'd draw a bunch of the screen and I'd never see
anything after that from it (cause it'd never return and we'd not get
to the next draw call/frame). Think there might be ways around that
just not sure yet.

Thanks again, and I'll certainly post with an update with where I get
(probably won't be able to dive into it again until the weekend sadly :
(  ).

On Apr 14, 1:00 pm, Ken Wesson <kwess...@gmail.com> wrote:
> On Wed, Apr 13, 2011 at 1:22 PM, Brandon Ferguson <bnfergu...@gmail.com> 
> wrote:
> > I'm not sure if this is the place to ask this but I've been struggling
> > with a few things in the world of Clojure. I've been using Processing
> > to learn Clojure (since I'm somewhat familiar with Processing) but the
> > tough part has been dealing with things like x, y positions of objects
> > without keeping some global state (I'm not sure if it's even
> > possible).
>
> If you're currently using atoms, you already have a function to
> compute the new position from the old, say, next-position, which you
> use with something like (swap! ball-pos next-position).
>
> Now consider this: (iterate next-position initial-ball-pos). That
> evaluates to an (infinite!) sequence of ball positions. You could have
> an animation loop step along this sequence, render a frame, wait,
> step, render, wait, etc. until the ESC key is hit (or whatever).
>
> For multiple balls that might interact with one another, a given
> ball's next position becomes a function of the other ball positions
> and not just its own. So you end up with:
>
> (iterate next-world-state initial-world-state)
>
> with world states being e.g. maps of ball-IDs to ball-positions or
> something. Obviously, other kinds of changeable world state can be
> included, too, e.g. Pong paddle positions, or the locations and amount
> of damage taken by Arkanoid bricks.
>
> This works until you get interactive (e.g. letting a human player
> control a paddle or something). At that point, iterate becomes a bit
> icky because the function becomes impure (as it polls the input
> devices).
>
> Then you probably just want two components:
>
> 1. A Swing GUI that captures mouse and keyboard events in the game's
> JPanel as well as rendering the frames there.
> 2. A game loop.
>
> The simplest case uses the Swing EDT as the game loop, with a Timer
> triggering game updates. But that requires mutable state for the game
> state to persist between Timer events.
>
> The other case keeps most of the game state immutable, but requires a
> bit of mutability to pass input to the game from the GUI:
>
> 1. The Swing event handlers post input messages to a
> LinkedBlockingQueue, or update atoms or refs holding the current
> position of the mouse and state (down or up) of relevant mouse buttons
> and keyboard keys.
>
> 2. The game loop runs in its own thread and looks something like this:
>
> (loop [foo bar other game state variables]
>   ...
>     (recur new-foo new-bar new-other ...)...)
>
> The game checks the input state, or drains the LinkedBlockingQueue, or
> whatever when it comes around to updating the player's position.
>
> One way to do it is just to have a set of game world objects:
>
> (loop [time (System/currentTimeMillis)
>        world (hash-set (cons (new-player) (generate-initial-world-state)))]
>   (let [world (map (partial update-position world) world)
>         new-objects (mapcat new-things world)
>         damages (reduce (partial merge-with +) {} (map do-damage world))
>         world (concat
>                 new-objects
>                 (remove dead?
>                   (map (partial apply-damages damages) world)))]
>     (SwingUtilities/invokeAndWait
>       (fn []
>         (let [g (get-the-jpanel's-double-buffer-graphics)]
>           (doseq [obj world] (draw-on! obj g)))
>         (flip-the-jpanel's-double-buffer!)))
>     (if (some is-player? world)
>       (let [elapsed (- (System/currentTimeMillis) time)
>             sl (- *ms-per-frame* elapsed)]
>         (if (> sl 0) (Thread/sleep sl))
>         (recur (System/currentTimeMillis) world))
>       world)))
>
> Here, new-player generates a new player object and
> generate-initial-world-state generates a seq of other objects for the
> initial world-state. The update-position function takes the world and
> a game object and runs the latter's ai, returning a new game object
> representing the old one's changed position (and possibly other state
> changes). The world is passed in so that collision checks can be done
> when needed.
>
> The new-things function allows some game objects to potentially
> generate more (e.g. shooting projectiles); it can return nil if the
> object hasn't spawned anything, or a seq of new things. For instance,
> if the player shoots a rocket, it may return [(new-rocket player-x
> player-y player-dx player-dy)]; when the rocket hits something and
> explodes, it may itself return (repeatedly *num-shards*
> #(generate-shrapnel rocket-x rocket-y)).
>
> The do-damage function takes a game object and returns a map of zero
> or more other game objects that are damaged by that game object. This
> usually involves collision, which is handled in update-position, so
> update-position needs to cache the damage information for do-damage to
> retrieve. For instance, that rocket's update-position can discover
> that it has collided with something and set the rocket's health to
> zero as well as store a map of things within the blast radius and the
> damage they should take. The new-things function sees the rocket's
> health at zero and spawns decorative shrapnel objects, since it just
> exploded; these will draw themselves and move for a short time in a
> straight line but that's it. The do-damage function just retrieves the
> map stored during update-position.
>
> The apply-damages function takes a damages map and a world object,
> looks that object up in the map, if not found returns it unchanged,
> otherwise returns a copy with reduced health. The dead? predicate just
> sees if a game object's health is zero or lower.
>
> Then there's draw-on!, which takes an object and a Graphics. Without
> that you'd be playing blind. It and flip-the-jpanel's-double-buffer!
> are the only impure functions here, besides the player object's
> update-position AI which needs to poll the keyboard/mouse state info.
>
> So there's a basic skeleton for a functional game loop. The loop exits
> if there's no player object in the world, which happens if all player
> objects get nailed by the dead? predicate, so quitting can be
> implemented by having the player's update-position check for a quit
> keypress and set the player's health to zero (by returning a player
> object with no health) if necessary.
>
> The loop returns the final world-state, so it can be checked to decide
> what kind of game-over screen to display. (If the game has a victory
> condition, when this is detected the game can "kill" the player and
> also put something in the world-state to allow victory to be
> distinguished from quit or the player actually died.) You can also add
> a surrounding loop to allow multiple lives. Pause can simply pause in
> the middle of the player's update AI; the GUI won't hang since it's
> not the EDT. The pause would of course replace the UI appropriately
> until unpaused, and check periodically for the unpause (or resort to
> Java's wait/notify to avoid a polling loop and use less CPU).
>
> Of course, the above is somewhat naive. In particular the world state
> is just an unordered seq of objects. This will scale poorly when the
> game world has a big and complex scrollable map, or very many objects
> to check for collisions. Then you'll want something smarter, with
> "geography", such as an interval tree on the x coordinates or
> something.
>
> Note that a lot of game objects would display, but not do anything
> (e.g. explosion and shrapnel graphics, particles, etc.); there should
> be a decorative? predicate and collision checks can use (remove
> decorative? world) to skip the things that should not collide with the
> target. (The decorative? objects' own update AI presumably completely
> eschews collision checks.) Other objects might not be visible, while
> having an effect, e.g. an in-game timer that causes a timed effect.
> If, for instance, new enemies are to be spawned randomly, this thing
> can have a no-op draw-on! and a no-op update-position, but its
> new-things can occasionally return a non-empty collection. Other
> timers are better embedded elsewhere; e.g. if the player picks up an
> invulnerability powerup, the time remaining can be stored in the
> player object, or even the time when it will wear off. The latter
> makes things very simple:
>
> (let [invulnerable? (> 0 (- (:invulnerable-until this)
> (System/currentTimeMillis)))]
>   ...)
>
> with invulnerability pickup working simply by making the return value
> use (+ System/currentTimeMillis 5000) or whatever as the
> :invulnerable-until key's value.
>
> There's a few other things that could be done. For example, there's
> nothing in there about sound effects, though since that's part of the
> game's "display" perhaps sound can be handled with graphics in
> draw-on!. That isolates more side effects into this bang-function.
> (Music can be cued in the player's draw-on!; it can check the current
> level and the invulnerability timer, among other things, to decide
> what music to play or continue playing, buffering a bit more of it for
> the Java Sound engine to play back.) Another is that everything
> non-insubstantial gets checked for collision with a given other object
> twice, first A's update function checking for collision with B and
> then vice versa. This could be simplified by adding a separate pass
> over the objects to find collisions:
>
> (loop [w (seq world) cmap {}]
>   (let [x (first w)]
>     (if-let [r (next w)]
>       (recur
>         r
>         (assoc
>           cmap
>           x
>           (loop [ww r colliders []]
>             (let [y (first r)
>                   colliders (if (collides? y x) (conj colliders y) colliders)]
>               (if-let [rr (next ww)]
>                 (recur rr colliders)
>                 colliders))))))))
>
> This should return a map whose keys are game objects and values are
> vectors; if objects A and B collide, *either* A's vector will contain
> B or vice-versa. With this, you'd pass A's vector rather than the
> world to update-position when calling update-position for object A:
> (map #(update-position (cmap %) %) world) rather than (map (partial
> update-position world) world). The update-position code for A would
> have to know what to do if it found B in this vector, and vice-versa.
>
> The code above would be further modified for interval trees, of
> course: the second loop ...
>
> read more »

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to