Saturday, February 15, 2014

Starting out with Javascript - "It's single threaded?"

This post is about the Javascript "game-loop" and passive event handling for game-state updates v.s. the traditional game-loop.

Ok, so lets get the things we know out of the way.

1) Games depend on hundreds/thousands of logical computations per second.
    Because we want games that are more and more realistic.  Or at the very least,
    feature rich.

2) Multiple threads help the CPU process information in parallel; faster.
    For instance; game logic that isn't UI dependent (like flying birds and grass
    animations) can be farmed out to separate threads.  These UI independent
    threads can update game-state whenever we want them to.

    It is kind of confusing to refer to these kind of updates as game-state though.
    If it doesn't affect player experience AT ALL (if it is purely visual), is it really game-state?
    I say "No" it is not.  It is "environment" state.  This kind of state should be kept separate
    from game logic for many reasons.  My favorite is it allows us to replace the entire environment
    outright.  Go modularity!  

    We must now remember that Javascript is NOT multi-threaded so we are cut off from this
    optimization.

3) Games need a Game-loop:
    ( Take Input -> Update Game-state -> Draw New Game-state)
     
        a) Take Input:
                   We have event handlers that listen for user input.  
                   These event handlers place update requests in an event queue.
                   Normally the updateGameState() loop "pops" this event queue
                   every time it fires off.  In javascript however, this is where we have a
                   second tier of listeners that make calls to requestAnimationFrame().
     
        b) Update Game-state:
                 Update functions validate user requests and execute appropriate state changes.
             **Hopefully I can explain this right...
               
                 In a traditional game loop you have a draw method that is called around, lets
                 say, 60fps and a logic update method that is called around 30x per sec.
                 The draw method will have some kind of smoothing effect like LERPing or
                 tweening to keep the animation from looking like a clock tick.

                 In our Scrounge World Model the logic engine's update is only called 10x per sec.
                 Which means we do 6 frames of smoothing per logical update.
             
                 **Remember:
                 Javascript isn't multi-threaded so we can't have this kind of "forced" update
                 loop.  This is partly because HTML doesn't support state-fulness.

                 I have to make you look up HTMLs state-less nature on your own as I am no
                 protocol expert.  However I can explain how this affects us and how we create our
                 Javascript equivalent.

                 The big difference in a Javascript game-loop is our updateGameState() method is
                 no longer a naive loop.  Instead we have a super EventListener() method that
                 passively listens for changes to game-state.

                 What this looks like on the outside:
                         function gameLoop(){
                                 updateGameState();
                                 drawNewGameState();
                                 requestAnimationFrame(gameLoop);
                         }
                         //get things started by calling the function
                         requestAnimationFrame(gameLoop);

                Which frankly isn't that far off from the traditional structure.
                But with two main differences:
                    1- the updateGameState method is passive     ...and...                  
                    2- requestAnimationFrame is in control of the game loop

                What this looks like on the inside:
                        function updateGameState(){
                                //updateGameState() just wraps all of the eventListeners for UI.
                             
                                mouseEventListeners();  //onclick, mouse over, etc.. would be wrapped in here
                                keyboardEventListeners();  //keypressed, keyup, keydown, etc...
                       }
               
                **Note: There is some "preventDefault()" method that has to be called within the
                              individual event listeners in order to prevent the regular browser functionality
                              from firing off that I don't really understand how to use yet.
                              Mainly, I need to figure out how to use the right mouse button and suppress
                              it's default function.

                If you know how to use Javascript DOM event listeners then handling UI should be
                fairly straight forward.

          c) Draw New Game-state:
                    Render the updated game-state to the screen.

                    The draw method is a wrapper method that resides within the game-loop and 
                    calls every game entity's own draw method for the given update type.  
                 
                    i.e.  The event handler X() fires off logic method BadguyOnCollision() which
                           will queue two methods when triggered:
                           whoDidIHit() and damageWhoIHit()  (or perhaps just itself but whatever). 

                           We "pop" the event queue as discussed previously and perform update.
                         
                           whoDidIHit() searches the entity tree to find the entities involved in the
                           collision and damageWhoIHit() performs the entity damage logic (my AC v.s.
                           your THAC0 anybody?) and updates the appropriate entity's life totals (the game-
                           state).

                          damageWhoIHit() calls the appropriate damage animations for the entities and
                          then requestAnimationFrame() decides when to render them to the screen.
                         

Hopefully this all made sense as it is still fresh and squishy in my own mind.  As these ideas start to solidify I may come back to these posts and clean them up and expand them if time permits.

Thanks for reading,
-Clay Francisco
                       





No comments:

Post a Comment