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.
Ok, so lets get the things we know out of the way.
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().
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.
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