HTML5 Canvas游戏循环增量时间计算 [英] HTML5 Canvas game loop delta time calculations

查看:215
本文介绍了HTML5 Canvas游戏循环增量时间计算的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是游戏开发的新手。目前我正在为 js13kgames 比赛做一个游戏,所以游戏应该很小,这就是为什么我不使用任何的现代流行框架。



在开发我的无限游戏循环时,我发现了几个文章和建议来实现它。现在它看起来像这样:

  self.gameLoop = function(){
self.dt = 0;

var now;
var lastTime = timestamp();
var fpsmeter = new FPSMeter({decimals:0,graph:true,theme:'dark',left:'5px'})

function frame(){
fpsmeter.tickStart();
now = window.performance.now();

//第一个变量 - delta增加..
self.dt = self.dt + Math.min(1,(now-lastTime)/ 1000);

//第二个变量 - delta是稳定的。
self.dt =(now - lastTime)/ 16;
self.dt =(self.dt> 10)? 10:self.dt;

self.clearRect();

self.createWeapons();
self.createTargets();

self.update('weapons');
self.render('weapons');

self.update('targets');
self.render('targets');

self.ticks ++;

lastTime = now;
fpsmeter.tick();
requestAnimationFrame(frame);
}

requestAnimationFrame(frame);
};

所以问题出现在 self.dt 我最终发现第一个变体不适合我的游戏,因为它增加了永远,武器的速度也随之增加(例如 this.position.x + =(Math.cos第二个变体看起来更合适,但是它对应于这个变量。种循环( http://codeincomplete.com/posts/2013/12/4/)

游戏引擎的一个很好的解决方案是在对象和实体中思考可以把你的世界里所有的东西想象成对象和实体,然后你想创建一个游戏对象管理器,其中包含所有游戏对象的列表,然后你想在引擎中创建一个通用的通信方法,这样游戏对象就可以使事件触发器。你的游戏中的实体,例如玩家不需要固有的任何东西,以获得渲染到屏幕或碰撞检测的能力。你可以简单地在游戏引擎正在寻找的实体中使用通用方法。然后让游戏引擎按照自己的意愿处理实体。您的游戏中的实体可以在游戏中随时创建或销毁,因此您不应在游戏循环中对任何实体进行硬编码。



您将需要游戏引擎中的其他对象来响应引擎已收到的事件触发器。这可以使用实体中的方法来完成,游戏引擎将检查该方法以查看该方法是否可用以及是否将事件传递给实体。不要硬编码任何你的游戏逻辑到引擎,它搞乱了可移植性,并限制了你的游戏扩展的能力。



您的代码的问题是首先调用不同的对象呈现和更新不正确的顺序。您需要调用所有更新,然后按照该顺序调用所有呈现。另一个是你的方法硬编码对象到循环中会给你很多的问题,当你想要的对象不再在游戏中,或者如果你想添加更多的对象到游戏后面。



您的游戏对象将有更新() render code>你的游戏引擎将在对象/实体中寻找该函数,并在每一帧中调用它。你可以得到非常花哨,让引擎工作在一个方式来检查游戏对象/实体是否有调用它们之前的功能。例如你可能想要一个有 update()的对象,但是不会在屏幕上显示任何东西。你可以通过在引擎检查之前使游戏对象函数可选。它也是一个良好的习惯,为所有游戏对象有一个 init()函数。当游戏引擎启动场景并创建对象时,它将首先通过调用游戏对象 init()在第一次创建对象时调用 update()这种方式,你可以有一个函数,你只运行一次创建和另一个运行每一帧。



由于 window.requestAnimationFrame(frame); 会给你〜60fps。所以如果你跟踪帧计数,你可以知道已经过去了多少时间。然后,游戏中的不同对象(基于游戏中的设置点和帧计数)基于新的帧计数确定它做了多少事情。

  window.requestAnimationFrame = window.requestAnimationFrame || function(callback){window.setTimeout(callback,16)}; 
gameEngine = function(){
this.frameCount = 0;
self = this;

this.update = function(){
//循环你的对象并运行每个对象更新函数
}

this.render = function (){
//循环你的对象并运行每个对象渲染函数
}

this.frame = function(){
self.update();
self.render();
self.frameCount ++;
window.requestAnimationFrame(frame);
}
this.frame();
};

我创建了一个完整的游戏引擎,位于 https://github.com/Patrick-W-McMahon/Jinx-Engine/tree/Dev 代码 https://github.com/Patrick-W -McMahon / Jinx-Engine / blob / Dev / JinxEngine.js 你会看到一个完全功能的游戏引擎在100%在javascript中构建。它包括事件处理程序,并允许使用事件调用堆栈传递到引擎的对象之间的操作调用。请查看一些示例 https://github.com/Patrick -W-McMahon / Jinx-Engine / tree / Dev / examples ,你会看到它是如何工作的。引擎可以运行大约100,000个对象,每帧以60fps的速率渲染和执行。这是在核心i5上测试的。不同的硬件可能会有所不同。鼠标和键盘事件内置到引擎中。传递给引擎的对象只需要监听引擎传递的事件。目前正在为更复杂的游戏建立场景管理和多场景支持。该引擎还支持高像素密度屏幕。



查看我的源代码应该可以帮助您构建一个功能更全面的游戏引擎。



我还想指出,当你准备重新绘制时,你应该有 requestAnimationFrame()而不是先前的(也就是在游戏循环的结尾)。在循环开始时你不应调用 requestAnimationFrame()的一个好的例子是,如果你使用canvas缓冲区。如果你在开始调用 requestAnimationFrame(),然后开始绘制到画布缓冲区,你可以结束它绘制一半的新帧,而另一半是旧的帧。这将在每个帧上发生,这取决于与重绘周期(60fps)相关的完成缓冲器所花费的时间。但在同一时间,你会最终重叠每一帧,所以缓冲区会得到更多的混乱,因为它循环其自身。这就是为什么当缓冲区完全准备好绘制到画布时,您只应调用 requestAnimationFrame()。通过让 requestAnimationFrame()到最后你可以让它跳过一个重绘,如果缓冲区没有准备好绘制,所以每个重绘是如预期的绘制。 requestAnimationFrame()在游戏循环中的位置有很大的不同。


I'm new to game development. Currently I'm doing a game for js13kgames contest, so the game should be small and that's why I don't use any of modern popular frameworks.

While developing my infinite game loop I found several articles and pieces of advice to implement it. Right now it looks like this:

self.gameLoop = function () {
        self.dt = 0;

        var now;
        var lastTime = timestamp();
        var fpsmeter = new FPSMeter({decimals: 0, graph: true, theme: 'dark', left: '5px'});

        function frame () {
            fpsmeter.tickStart();
            now = window.performance.now();

            // first variant - delta is increasing..
            self.dt = self.dt + Math.min(1, (now-lastTime)/1000);

            // second variant - delta is stable.. 
            self.dt = (now - lastTime)/16;
            self.dt = (self.dt > 10) ? 10 : self.dt;

            self.clearRect();

            self.createWeapons();
            self.createTargets();

            self.update('weapons');
            self.render('weapons');

            self.update('targets');
            self.render('targets');

            self.ticks++;

            lastTime = now;
            fpsmeter.tick();
            requestAnimationFrame(frame);
        }

        requestAnimationFrame(frame);
};

So the problem is in self.dt I've eventually found out that first variant is not suitable for my game because it increases forever and the speed of weapons is increasing with it as well (e.g. this.position.x += (Math.cos(this.angle) * this.speed) * self.dt;..

Second variant looks more suitable, but does it correspond to this kind of loop (http://codeincomplete.com/posts/2013/12/4/javascript_game_foundations_the_game_loop/)?

解决方案

A great solution to your game engine would be to think in objects and entities. You can think of everything in your world as objects and entities. Then you want to make a game object manager that will have a list of all your game objects. Then you want to make a common communication method in the engine so game objects can make event triggers. The entities in your game for example a player would not need to inherent from anything to get the ability to render to the screen or have collision detection. You would simple make common methods in the entity that the game engine is looking for. Then let the game engine handle the entity as it would like. Entities in your game can be created or destroyed at anytime in the game so you should not hard-code any entities at all in the game loop.

You will want other objects in your game engine to respond to event triggers that the engine has received. This can be done using methods in the entity that the game engine will check to see if the method is available and if it is would pass the events to the entity. Do not hard code any of your game logic into the engine it messes up portability and limits your ability to expand on the game later on.

The problem with your code is first your calling different objects render and updates not in the correct order. You need to call ALL your updates then call ALL your renders in that order. Another is your method of hard coding objects into the loop is going to give you a lot of problems, when you want one of the objects to no longer be in the game or if you want to add more objects into the game later on.

Your game objects will have an update() and a render() your game engine will look for that function in the object/entity and call it every frame. You can get very fancy and make the engine work in a way to check if the game object/entity has the functions prior to calling them. for example maybe you want an object that has an update() but never renders anything to the screen. You could make the game object functions optional by making the engine check prior to calling them. Its also good practice to have an init() function for all game objects. When the game engine starts up the scene and creates the objects it will start by calling the game objects init() when first creating the object then every frame calling update() that way you can have a function that you only run one time on creation and another that runs every frame.

delta time is not really needed as window.requestAnimationFrame(frame); will give you ~60fps. So if you're keeping track of the frame count you can tell how much time has passed. Different objects in your game can then, (based off of a set point in the game and what the frame count was) determine how long its been doing something based off its new frame count.

window.requestAnimationFrame = window.requestAnimationFrame || function(callback){window.setTimeout(callback,16)};
gameEngine = function () {
        this.frameCount=0;
        self=this;

        this.update = function(){
           //loop over your objects and run each objects update function
        }

        this.render = function(){
          //loop over your objects and run each objects render function
        }

        this.frame = function() {
            self.update();
            self.render();
            self.frameCount++;
            window.requestAnimationFrame(frame);
        }
        this.frame();
};

I have created a full game engine located at https://github.com/Patrick-W-McMahon/Jinx-Engine/tree/Dev if you review the code at https://github.com/Patrick-W-McMahon/Jinx-Engine/blob/Dev/JinxEngine.js you will see a fully functional game engine built 100% in javascript. It includes event handlers and permits action calls between objects that are passed into the engine using the event call stack. check out some of the examples https://github.com/Patrick-W-McMahon/Jinx-Engine/tree/Dev/examples where you will see how it works. The engine can run around 100,000 objects all being rendered and executed per frame at a rate of 60fps. This was tested on a core i5. different hardware may vary. mouse and keyboard events are built into the engine. objects passed into the engine just need to listen for the event passed by the engine. Scene management and multi scene support is currently being built in for more complex games. The engine also supports high pixel density screens.

Reviewing my source code should get you on the track for building a more fully functional game engine.

I would also like to point out that you should have requestAnimationFrame() called when you're ready to repaint and not prior (aka at the end of the game loop). One good example why you should not call requestAnimationFrame() at the beginning of the loop is if you're using a canvas buffer. If you call requestAnimationFrame() at the beginning, then begin to draw to the canvas buffer you can end up having it draw half of the new frame with the other half being the old frame. This will happen on every frame depending on the time it takes to finish the buffer in relation to the repaint cycle (60fps). But at the same time you would end up overlapping each frame so the buffer will get more messed up as it loops over its self. This is why you should only call requestAnimationFrame() when the buffer is fully ready to draw to the canvas. by having the requestAnimationFrame() at the end you can have it skip a repaint if the buffer is not ready to draw and so every repaint is drawn as it is expected. The position of requestAnimationFrame() in the game loop has a big difference.

这篇关于HTML5 Canvas游戏循环增量时间计算的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆