8 years ago
by Joncom
This post is a work in progress.
I made this plugin:
https://github.com/Joncom/impactjs-fixed-tick
What does it do?
- adds new property
ig.system.tickRate
to the engine
-
tickRate
means "how many times per second to
update
the game"
- each
update
advances the game by a constant (no longer variable) time
- if
tickRate
is 10, then each
update
will advance the game exactly 100 ms
-
update
is called as needed to fulfill
tickRate
, instead of exactly once per
draw
Here is a
demo.
Why use it?
- if you need or want your game to be more deterministic for some reason, such as you want to create a replay system, or you have a multiplayer game that needs to run identical simulations on the client and the server
How to use it?
- simply run your game as usual
- set
ig.system.tickRate
to something else if the default 60 is not what you want
-
TODO: explain panic() function
Further Reading
Work is based on this article:
http://www.isaacsukin.com/news/2015/01/detailed-explanation-javascript-game-loops-and-timing
Another excellent article on the subject:
http://gafferongames.com/game-physics/fix-your-timestep/
8 years ago
by Bum
Hey this is real cool! Any chance of adding smooth interpolation to create a slow down/bullet-time effect in the engine with this?
8 years ago
by Joncom
Quote from Bum
Hey this is real cool! Any chance of adding smooth interpolation to create a slow down/bullet-time effect in the engine with this?
Hey Bum. I think you can already achieve that without this patch?
ig.Timer.timeScale = 0.1;
8 years ago
by Joncom
It occurred to me that directly patching the engine is not ideal because it violates the
Open/Closed Principle:
software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification
Therefore, I created a plugin version:
<plugin code removed, see
https://github.com/Joncom/impactjs-fixed-tick instead>
The plugin does work, however there is an issue; It seems there's a 50/50 chance the debug module will not work each time the browser is refreshed.
Let me explain.
The debug module uses
inject
to hook into the
ig.System.run
function for the purpose of updating the on-screen performance graphs, framerate, etc.
The problem is that we need to execute the "fixed-tick" plugin before this happens, because we need to outright replace the
run
function first, so that the new version of the
run
function is the one the debug module hooks into.
And the following will not not guarantee any particular dependency load order.
ig.module(
'game.main'
)
.requires(
'plugins.fixed-tick',
'impact.debug.debug',
'impact.game'
)
.defines(function(){
MyGame = ig.Game.extend({
// ...
});
// ...
});
I guess the problem stems from the fact that I&
039;m still violating the "open/closed principle". I'm replacing #ig.System.run
rather than extending it. I'm not sure this can be helped though... Hmm...
Edit: Just thinking out loud. If the CommonJS module system were used, then dependency load order would be guaranteed, and this issue would not be.
8 years ago
by Joncom
More thinking out loud. A possible work-around for the above issue could be to modify the module system via
ImpactMixin such that load order is guaranteed.
8 years ago
by Cakebit
Thanks for the plugin Joncom.
It should be super useful in my multiplayer endeavors.
I noticed when running the plugin in my project is that
‘this.delegate.update is not a function’
indicates that there is no update function in the ig.System class. Is that a method you left out - and should be injected by the plugin?
8 years ago
by Joncom
Quote from Cakebit
indicates that there is no update function in the ig.System class. Is that a method you left out - and should be injected by the plugin?
Actually,
delegate
refers to the
ig.game
object. Therefore there really should be an
update
function there!
Since
delegate
seems to not reference your game, maybe you could drop a breakpoint on the line throwing that error and see what its value is?
8 years ago
by Cakebit
Ah! You are correct Joncom.
The reason my
delegate
was returning
undefined
was due to the fact that I was using Dominic's (slightly hacky) Impact Splash Loader which set it's class as the delegate (instead of ig.game).
(Splash Loader On Github)
Now to see if I can get both to play nicely together. Something as simple as
if(this.delegate){
this.delegate.update();
}
acts as a work-around, but yields a weird transition when the delegate switches to ig.game.
It's probably something I need to look deeper into...
Thanks again!
8 years ago
by Joncom
Glad that mystery is solved. :)
May I ask what is weird about the transition?
8 years ago
by Cakebit
The loader runs just fine, but it doesn&
039;t to take over the #delegate
to fade into the game... (again, this could be something I'm overlooking - I haven't had the time to dive right into the system class yet)
Link to PasteBin Source
You can drop the loader directly into any Impact Project, and then add it to your ig.main arguments. (Also, you may want to change the
run
function name to
update
if testing with the Tick plugin).
:)
8 years ago
by Joncom
Perhaps the transition looks weird because maybe you replaced ig.game.run
(which normally did an update
and a draw
) with an ig.game.update
but left out the ig.game.draw
?
8 years ago
by Cakebit
Quote from Joncom
Perhaps the transition looks weird because maybe you replaced ig.game.run
(which normally did an update
and a draw
) with an ig.game.update
but left out the ig.game.draw
?
The
draw
function was left empty, simply to override the parent loader&
039;s #draw
function :)
It appears the problem was springing up when switching the
delegate
. The
ig.system
function
setDelegate
was expecting a
run
function in the new delegate (and throwing an error when not found). The fix was using your plugin to also overwrite the
setDelegate
method in ig.system to no longer require
run
, then use the
update
function, instead of the
run
function in the loader.
An example of the code used would look like
setDelegate: function(object) {
if (typeof(object.update) == 'function' && typeof(object.draw) == 'function') {
this.delegate = object;
this.startRunLoop();
} else {
throw ('System.setDelegate: No update() or draw() function in object');
}
}
instead of
setDelegate: function(object) {
if (typeof(object.run) == 'function') {
this.delegate = object;
this.startRunLoop();
} else {
throw ('System.setDelegate: No run() function in object');
}
},
Making this minor change fixes compatibility problems when switching the
delegate
. :)
Thanks for the help - the plugin is super.
Pastebin of Loader Code |
Pastebin of Plugin Changes
8 years ago
by Joncom
Quote from Cakebit
The ig.system
function setDelegate
was expecting a run
function in the new delegate (and throwing an error when not found).
That is strange because your game still has a
run
function. Notice that the plugin simply overrides the
run
function in
ig.Game
. The splash loader plugin has one too. So, what supposedly has no
run
function?
8 years ago
by Cakebit
Quote from Joncom
That is strange because your game still has a run
function. Notice that the plugin simply overrides the run
function in ig.Game
. The splash loader plugin has one too. So, what supposedly has no run
function?
Sorry for the confusion, I modified the SplashLoader to change the function name from
run
to
update
so that it would be called by this plugin before each frame.
You are correct, another option would be to keep an empty run function in the loader class, and have a separate update function.
8 years ago
by Joncom
Found a bug in the plugin.
It allowed
ig.system.tick
to be non-constant.
The issue was due to small floating point precision errors.
For example, at 60 FPS, ticks might look like this:
0.016666666666666666
0.016666666666666734
0.016666666666666579
However now with
the fix, all such ticks are precisely
0.016666666666666666
.
Page 1 of 1
« first
« previous
next ›
last »