我编写的游戏的性能一直存在很大的问题。出于某种原因,游戏时口吃,似乎要么绘制同一帧两次,要么跳过该帧的绘制。正如您在图像中看到的那样,由于某种原因,当发生这种“抽动/停顿”时,FPS下降到30,帧时间是原来的两倍。这些口吃非常频繁,每1-5秒发生一次。我已经测试了内存泄漏,没有异常,测试了垃圾回收,没有异常。在update()函数中使用requestAnimationFrame调用它们。这是一个jsfiddle我放在一起。只需右键单击输出,转到时间轴和配置文件20秒钟。您会看到FPS随机下降到30。在小提琴上,大多数代码来自我添加的外部文件,因此对该问题并不重要。我在579行添加了一行,以显示重要代码的起始位置。导致此问题的两个函数是moveAllGameRectangles();。renderTheGameObjects(); 在线上,小提琴上是1180和1098。如果您可以看一下这两个功能,并告诉我是否看到可以改善性能的任何东西,那就太好了。我出于测试目的删除了碰撞检测。我几个月来一直遇到这个问题,所以任何帮助都将非常巨大!
(function() { var c = document.querySelector("canvas"); var ds = c.getContext("2d"); c.width = window.innerWidth; c.height = window.innerHeight; var theMaxWidth = c.width / 4.5; if (c.height === 1743 || c.height === 1744 || c.height === 1740) { var gameVelocity = 60; } else { var gameVelocity = 70; } var so = Math.round(c.height / gameVelocity + 65); var halfVelocity = gameVelocity * 4; var halfVelocityModulus = gameVelocity * 2; var modulusNumber = 17; var OPENING = 0 var LOADING = 1 var BUILD_MENU = 2 var BUILD_MAP = 3 var PLAYING = 4 var LOST = 5 var SETTINGS = 6 assetsLoaded = []; cargoAnimation = []; gameObjectsArray = []; menuObjectsArray = []; movingMenuObjectsArray = []; assetsToLoad = []; whatToMove = []; gameObjectsPlayingArray = []; movingGameObjectsArray = []; lossObjectsArray = []; settingsObjectArray = []; messages = []; theMaxArray = []; messagesHighScore = []; settingsTextArray = []; loadTheFontBeforeArray = []; randomNumber = 0; randomGameNumber = 0; var touchAllowed = false; var collisionOrNot = false; var gameRunning = true; var loaded = false; var settingsItem1Checked = false; var practiceModeOn = false; var interval = null; var gameInterval = null; assetsLoaded = 0; doneLoading = false; var gameState = OPENING; var cargo = new Image(); cargo.src = "../www/img/cargo.png"; cargoAnimation.push(cargo); var menuObjects = new Image(); menuObjects.src = "../www/img/loadingScreenSpriteSheet.png"; assetsToLoad.push(menuObjects); var gameObjects = new Image(); gameObjects.src = "../www/img/gamePlayingSprites.png"; assetsToLoad.push(gameObjects); function loadHandler() { assetsLoaded++; if (assetsLoaded === assetsToLoad.length) { touchAllowed = true; gameState = BUILD_MENU; } } window.setTimeout(function() { window.addEventListener("touchstart", touchUpHandler, false); }); c.addEventListener("touchstart", touchdownhandler, false); window.setTimeout(function() { gameState = LOADING; }); // backgroundSquare menu var bSquare = Object.create(mainObject); bSquare.sourceX = 0; bSquare.sourceY = 0; bSquare.sourceWidth = 256; bSquare.sourceHeight = 264; bSquare.width = c.width / 2 + c.width / 10; bSquare.height = c.height / 3; bSquare.x = c.width / 5; bSquare.y = c.height / 2 - c.height / 6; menuObjectsArray.push(bSquare); lossObjectsArray.push(bSquare); // loss menu var lSquare = Object.create(mainObject); lSquare.sourceX = 0; lSquare.sourceY = 360; lSquare.sourceWidth = 228; lSquare.sourceHeight = 92; lSquare.width = c.width / 2 - c.width / 35; lSquare.height = c.height / 9; lSquare.x = c.width / 3 - c.width / 14; lSquare.y = c.height / 3 + c.height / 25; lossObjectsArray.push(lSquare); // play button var play = Object.create(mainObject); play.sourceX = 256; play.sourceY = 0; play.sourceWidth = 66; play.sourceHeight = 64; play.width = c.width / 5; play.height = c.height / 9; play.x = c.width / 3 - c.width / 14; play.y = c.height / 2 + c.height / 100; menuObjectsArray.push(play); lossObjectsArray.push(play); // settingsButton var settings = Object.create(mainObject); settings.sourceX = 322; settings.sourceY = 0; settings.sourceWidth = 66; settings.sourceHeight = 64; settings.width = c.width / 5; settings.height = c.height / 9; settings.x = c.width / 3 + c.width / 5 + c.width / 150; settings.y = c.height / 2 + c.height / 100; menuObjectsArray.push(settings); lossObjectsArray.push(settings); // logo var logo = Object.create(mainObject); logo.sourceX = 0; logo.sourceY = 265; logo.sourceWidth = 228; logo.sourceHeight = 92; logo.width = c.width / 2 - c.width / 35; logo.height = c.height / 9; logo.x = c.width / 3 - c.width / 14; logo.y = c.height / 3 + c.height / 25; menuObjectsArray.push(logo); // firstButton var button1 = Object.create(mainObject); button1.sourceX = 0; button1.sourceY = 257; button1.sourceWidth = 103; button1.sourceHeight = 120; button1.width = c.width / 4; button1.height = c.height / 4; button1.x = 0; button1.y = c.height - c.height / 6; gameObjectsPlayingArray.push(button1); // secondButton var button2 = Object.create(mainObject); button2.sourceX = 103; button2.sourceY = 257; button2.sourceWidth = 103; button2.sourceHeight = 120; button2.width = c.width / 4; button2.height = c.height / 4; button2.x = c.width / 4; button2.y = c.height - c.height / 6; gameObjectsPlayingArray.push(button2); // thirdButton var button3 = Object.create(mainObject); button3.sourceX = 207; button3.sourceY = 257; button3.sourceWidth = 103; button3.sourceHeight = 120; button3.width = c.width / 4; button3.height = c.height / 4; button3.x = c.width / 4 + c.width / 4; button3.y = c.height - c.height / 6; gameObjectsPlayingArray.push(button3); // fourButton var button4 = Object.create(mainObject); button4.sourceX = 309; button4.sourceY = 257; button4.sourceWidth = 103; button4.sourceHeight = 120; button4.width = c.width / 4; button4.height = c.height / 4; button4.x = c.width / 4 + c.width / 4 + c.width / 4; button4.y = c.height - c.height / 6; gameObjectsPlayingArray.push(button4); // theGameCharacter var theGameCharacter = Object.create(mainObject); theGameCharacter.sourceX = 0; theGameCharacter.sourceY = 380; theGameCharacter.sourceWidth = 60; theGameCharacter.sourceHeight = 60; theGameCharacter.width = c.width / 6; theGameCharacter.height = c.height / 11; theGameCharacter.x = c.width / 24; theGameCharacter.y = c.height * .75 - c.height / 15; gameObjectsPlayingArray.push(theGameCharacter); var settingsMenu = Object.create(mainObject); settingsMenu.sourceX = 256; settingsMenu.sourceY = 64; settingsMenu.sourceWidth = 66; settingsMenu.sourceHeight = 64; settingsMenu.width = c.width / 6; settingsMenu.height = c.height / 11; settingsMenu.x = c.width / 20; settingsMenu.y = c.height / 35; settingsObjectArray.push(settingsMenu); var settingsMenuNonChecked = Object.create(mainObject); settingsMenuNonChecked.sourceX = 256; settingsMenuNonChecked.sourceY = 180; settingsMenuNonChecked.sourceWidth = 182; settingsMenuNonChecked.sourceHeight = 50; settingsMenuNonChecked.width = c.width / 2; settingsMenuNonChecked.height = c.height / 13; settingsMenuNonChecked.x = c.width / 10; settingsMenuNonChecked.y = c.height / 5; settingsObjectArray.push(settingsMenuNonChecked); timerMessage = Object.create(messageObject); timerMessage.x = c.width / 2; timerMessage.y = c.height / 10; timerMessage.font = getFont(); timerMessage.fillStyle = "#3000ff"; timerMessage.visible = true; messages.push(timerMessage); timerMessageHighScore = Object.create(messageObject); timerMessageHighScore.x = c.width / 2; timerMessageHighScore.y = c.height / 5; timerMessageHighScore.font = getFont(); timerMessageHighScore.fillStyle = "#3000ff"; timerMessageHighScore.visible = true; messagesHighScore.push(timerMessageHighScore); settingsText = Object.create(messageObject); settingsText.x = c.width / 1.6; settingsText.y = c.height / 4.7; settingsText.font = getSmallerFont(); settingsText.fillStyle = "#3000ff"; settingsText.visible = true; settingsText.text = "Practice Mode" settingsTextArray.push(settingsText); loadTheFontBefore = Object.create(messageObject); loadTheFontBefore.font = getSmallerFont(); loadTheFontBefore.fillStyle = "#3000ff"; loadTheFontBefore.x = -c.width; loadTheFontBefore.y = -c.height; loadTheFontBefore.visible = false; loadTheFontBeforeArray.push(loadTheFontBefore); function getFont() { var size = c.width / 20 * 2 return (size | 0) + 'px neuropolitical rg'; } function getSmallerFont() { var size = c.width / 30 * 2 return (size | 0) + 'px neuropolitical rg'; } update(); function update() { ds.clearRect(0, 0, c.width, c.height); //console.log(cargoAnimation.length, gameObjectsArray.length, menuObjectsArray.length, movingMenuObjectsArray.length, assetsToLoad.length, whatToMove.length, gameObjectsPlayingArray.length, movingGameObjectsArray.length, lossObjectsArray.length, settingsObjectArray.length, messages.length) req = requestAnimationFrame(update, c); switch (gameState) { case LOADING: loadHandler(); break; case BUILD_MENU: moveAllRectangles(); renderMenuObjects(); beforeLoadTheFont(); break; case BUILD_MAP: moveAllGameRectangles(); renderTheGameObjects(); checkForCollisonsRectangles(); break; case PLAYING: break; case SETTINGS: renderSettingsObjects(); renderSettingText(); break; } } function checkForCollisonsRectangles() { for (var i = 0; i < movingGameObjectsArray.length; i++) { var collisionOrNot = hitTestRectangle(theGameCharacter, movingGameObjectsArray[i]); if (collisionOrNot && movingGameObjectsArray[0].y > theGameCharacter.y + theGameCharacter.height - c.height / 80) { collisionOrNot === false; return; } if (collisionOrNot) { stoptimer(); resettimer(); touchAllowed = true; window.cancelAnimationFrame(req); displayRestartMenu(); gameRunning = false; logHighScore(); showHighScore(); return; } } } function logHighScore() { console.log(practiceModeOn) if (practiceModeOn === false) { theMaxArray.push(timerMessage.text); Array.max = function(theMaxArray) { return Math.max.apply(Math, theMaxArray); }; var maximum = Array.max(theMaxArray); timerMessageHighScore.text = maximum } } function showHighScore() { console.log(practiceModeOn) if (practiceModeOn === false) { if (messagesHighScore.length !== 0) { for (var i = 0; i < messagesHighScore.length; i++) { var message = messagesHighScore[i]; if (message.visible) { ds.font = message.font; ds.fillStyle = message.fillStyle; ds.textBaseline = message.textBaseline; ds.textAlign = 'center'; ds.fillText(message.text, message.x, message.y); } } } } } function renderSettingText() { if (settingsTextArray.length !== 0) { for (var i = 0; i < settingsTextArray.length; i++) { var message = settingsTextArray[i]; if (message.visible) { ds.font = message.font; ds.fillStyle = message.fillStyle; ds.textBaseline = message.textBaseline; ds.textAlign = 'center'; ds.fillText(message.text, message.x, message.y); } } } } function beforeLoadTheFont() { if (loaded === true) { return; } if (loadTheFontBeforeArray.length !== 0) { for (var i = 0; i < loadTheFontBeforeArray.length; i++) { var message = loadTheFontBeforeArray[i]; ds.font = message.font; ds.fillStyle = message.fillStyle; ds.textBaseline = message.textBaseline; ds.textAlign = 'center'; ds.fillText(message.text, message.x, message.y); } } loaded = true; console.log("runn") } function displayRestartMenu() { if (lossObjectsArray.length !== 0) { for (var i = 0; i < lossObjectsArray.length; i++) { var sprite = lossObjectsArray[i]; ds.drawImage( menuObjects, sprite.sourceX, sprite.sourceY, sprite.sourceWidth, sprite.sourceHeight, sprite.x, sprite.y, sprite.width, sprite.height ); } } } function touchUpHandler() { touchX = event.targetTouches[0].pageX - c.offsetLeft; touchY = event.targetTouches[0].pageY - c.offsetTop; if (hitTestPoint(touchX, touchY, play)) { if (gameState === BUILD_MENU) { movingMenuObjectsArray = []; startstoptimer(); gameState = BUILD_MAP; gameRunning = true; touchAllowed = false; } else { if (touchAllowed === true) { movingMenuObjectsArray = []; startstoptimer(); movingGameObjectsArray = []; update(); gameState = BUILD_MAP; gameRunning = true; touchAllowed = false; } } } if (hitTestPoint(touchX, touchY, settings)) { if (touchAllowed === true) { gameState = SETTINGS; if (!gameRunning) { update(); movingGameObjectsArray = []; } gameRunning = true; } } if (hitTestPoint(touchX, touchY, settings)) { if (touchAllowed === true) { gameState = SETTINGS; if (!gameRunning) { update(); movingGameObjectsArray = []; } gameRunning = true; } } if (hitTestPoint(touchX, touchY, settingsMenu)) { if (gameState === SETTINGS) { gameState = BUILD_MENU; } } if (hitTestPoint(touchX, touchY, settingsMenuNonChecked)) { console.log(settingsItem1Checked) if (settingsItem1Checked === true) { console.log("doing") if (c.height === 1743 || c.height === 1744 || c.height === 1740) { gameVelocity = 60; } else { gameVelocity = 70; } modulusNumber = 17; settingsMenuNonChecked.sourceY = 180; settingsItem1Checked = false; practiceModeOn = false; return; } if (settingsItem1Checked === false) { console.log("doing") if (c.height === 1743 || c.height === 1744 || c.height === 1740) { gameVelocity = 120; } else { gameVelocity = 140; } modulusNumber = 30; settingsMenuNonChecked.sourceY = 128; settingsItem1Checked = true; practiceModeOn = true; return; } } } function touchdownhandler() { touchX = event.targetTouches[0].pageX - c.offsetLeft; touchY = event.targetTouches[0].pageY - c.offsetTop; if (hitTestPoint(touchX, touchY, button1)) { theGameCharacter.x = c.width / 23; } if (hitTestPoint(touchX, touchY, button2)) { theGameCharacter.x = c.width * .25 + c.width / 23; } if (hitTestPoint(touchX, touchY, button3)) { theGameCharacter.x = c.width * .5 + c.width / 23; } if (hitTestPoint(touchX, touchY, button4)) { theGameCharacter.x = c.width * .75 + c.width / 23; } } function renderTheGameObjects() { if (movingGameObjectsArray.length !== 0) { for (var i = 0; i < movingGameObjectsArray.length; i++) { var sprite = movingGameObjectsArray[i]; sprite.y += sprite.vy; ds.drawImage( gameObjects, sprite.sourceX, sprite.sourceY, sprite.sourceWidth, sprite.sourceHeight, sprite.x, sprite.y, sprite.width, sprite.height ); if (sprite.y > c.height - c.height / 6) { var removeThis = sprite; } } } removeTheMenuObject(removeThis, movingGameObjectsArray); if (messages.length !== 0) { for (var i = 0; i < messages.length; i++) { var message = messages[i]; if (message.visible) { ds.font = message.font; ds.fillStyle = message.fillStyle; ds.textBaseline = message.textBaseline; ds.textAlign = 'center'; ds.fillText(message.text, message.x, message.y); } } } if (gameObjectsPlayingArray.length !== 0) { for (var i = 0; i < gameObjectsPlayingArray.length; i++) { var sprite = gameObjectsPlayingArray[i]; ds.drawImage( gameObjects, sprite.sourceX, sprite.sourceY, sprite.sourceWidth, sprite.sourceHeight, sprite.x, sprite.y, sprite.width, sprite.height ); } } } function hitTestPoint(pointX, pointY, sprite) { return pointX > sprite.left() && pointX < sprite.right() && pointY > sprite.top() && pointY < sprite.bottom(); } function renderMenuObjects() { if (menuObjectsArray.length !== 0) { for (var i = 0; i < menuObjectsArray.length; i++) { var sprite = menuObjectsArray[i]; ds.drawImage( menuObjects, sprite.sourceX, sprite.sourceY, sprite.sourceWidth, sprite.sourceHeight, sprite.x, sprite.y, sprite.width, sprite.height ); } } } function renderSettingsObjects() { if (settingsObjectArray.length !== 0) { for (var i = 0; i < settingsObjectArray.length; i++) { var sprite = settingsObjectArray[i]; ds.drawImage( menuObjects, sprite.sourceX, sprite.sourceY, sprite.sourceWidth, sprite.sourceHeight, sprite.x, sprite.y, sprite.width, sprite.height ); } } } function moveAllGameRectangles() { gameInterval = gameInterval + 1; if (gameInterval % modulusNumber === 0 || gameInterval === 1) { var randomGameNumber = Math.floor((Math.random() * 4) + 1); if (randomGameNumber === 1) { //the green game object var leftSideSpriteGame1 = Object.create(mainObject); leftSideSpriteGame1.sourceX = 0; leftSideSpriteGame1.sourceY = 0; leftSideSpriteGame1.sourceWidth = 310; leftSideSpriteGame1.sourceHeight = 64; leftSideSpriteGame1.width = c.width * .75; leftSideSpriteGame1.height = c.height / 14; leftSideSpriteGame1.x = 0; leftSideSpriteGame1.y = -65; leftSideSpriteGame1.vy = c.height / gameVelocity; movingGameObjectsArray.push(leftSideSpriteGame1); } if (randomGameNumber === 2) { //the red game object var leftSideSpriteGame2 = Object.create(mainObject); leftSideSpriteGame2.sourceX = 0; leftSideSpriteGame2.sourceY = 64; leftSideSpriteGame2.sourceWidth = 207; leftSideSpriteGame2.sourceHeight = 64; leftSideSpriteGame2.width = c.width * .5; leftSideSpriteGame2.height = c.height / 14; leftSideSpriteGame2.x = 0 leftSideSpriteGame2.y = -65; leftSideSpriteGame2.vy = c.height / gameVelocity; movingGameObjectsArray.push(leftSideSpriteGame2); var rightSideSpriteGame2 = Object.create(mainObject); rightSideSpriteGame2.sourceX = 310; rightSideSpriteGame2.sourceY = 64; rightSideSpriteGame2.sourceWidth = 103; rightSideSpriteGame2.sourceHeight = 64; rightSideSpriteGame2.width = c.width * .25; rightSideSpriteGame2.height = c.
Blindman67.. 5
无需优化
我刚刚看了一下代码,并在小提琴上进行了介绍。好消息是,您无需优化代码,您的运行速度约为最大值的5%。
在您发布的时间轴图像中,您可以看到您的代码大多数时候几乎没有执行任何操作。优化总是好的,但是对于您的应用程序,将中间时间从95%增加到96%并不值得。
最佳负载V过载
我包括一张图像,该图像显示时间轴上每秒60fps的最佳负载应该是什么样子,然后过载的帧率看起来就可以每秒管理30帧,并且由于存在30帧而使帧率从30跳到20太多的事情要做。
与OP的相比
[
2 看其他地方
当您比较它们时,您可以看到您的游戏中几乎没有任何东西发生,大多数帧是空闲时间。您的代码可以达到接近1000 Fps的速度,并且仍有空闲时间。帧速率的问题出在其他地方,要么是占用CPU时间,要么是共享GPU占用了带宽和RAM。
如果您始终打开开发工具,那么您所做的很多
console.log
事情都会产生很大的影响。console.log
当您不再需要调用时,将其保留在代码之外。在设备上没有更多信息的情况下,您不能在其上运行该设备,以及该设备在运行哪些其他应用程序,我无法为您提供明确的解决方案。关闭尽可能多的应用程序和服务,直到发现改进为止。它最有可能只是导致它的一个不良应用。
现在的坏消息
您说您不关心GC。好吧,如果您想扩展游戏,就应该非常担心。使用CPU事件探查器,您的游戏会生成足够多的不需要的RAM,以使GC占总处理时间的2-5%(不包括空闲时间)。您很幸运,您还有余时间,但GC会注意并是导致帧速率不一致的主要原因之一。相比之下,具有良好V坏代码(100,000行代码)的代码如果出现,将永远不会使GC的峰值超过0.1%。
每次运行一些创建
Array
或的代码Object
时,或者在使用new Something()
或Object.create(foo)
创建必须清理的内容时,都应考虑这种方式。如果这样做,那么每一帧都会在RAM中不断产生垃圾。结果是GC的负载恒定,随着代码复杂度的增加,负载只会变得更糟。游戏循环的GC绝对不要超过0.5%,如果做得好,它应该保持在0.1%以下切勿在游戏循环中使用新游戏
同为
foo = []
,obj = {}
,obj = Object.create(blah)
特别是最后一个。您不仅要为已经具有RAM的对象请求更多的内存,而且Object.create
是创建javascript具有的新对象的最慢方法。情况变得更糟,不仅是创建对象的最慢方式,而且创建了访问和使用最慢的对象。忘记您甚至知道该功能已经存在。错误的内存使用示例。
// creates an object function GameThing(a1,a2,a4,a4){ this.a1 = a1; this.a2 = a2; this.a3 = a3; this.a4 = a4; this.data = [1,2,3,4,5,6,7,8,9,10]; // create a new array in game loop BAD } function rand(){return Math.random()};); var gameThing; function mainLoop(){ // using new in the main loop BAD gameThing = new GameThing(rand(),rand(),rand(),rand()); // dumps the old RAM // for GC and uses some // more for the next loop. }应该怎么做
// creates an object function GameThing(a1,a2,a4,a4){ this.a1 = a1; this.a2 = a2; this.a3 = a3; this.a4 = a4; this.data = [1,2,3,4,5,6,7,8,9,10]; } function reuseGameThing(thing,a1,a2,a3,a4); thing.a1 = a1; thing.a2 = a2; thing.a3 = a3; thing.a4 = a4; thing.data[1] = 0; // don't create a new array. The memory has be set thing.data[2] = 1; // aside for it already, why dump it to reset the values thing.data[3] = 2; // just overwrite the old values. The time save thing.data[4] = 3; // from needless GC hits is well worth he little ... // extra time for set each item ... thing.data[9] = 10; } function rand(){return Math.random()};); // create the object ONCE only. It create a ram buffer for your data // that should not need to be deleted for the life time of the current game state var gameThing = new GameThing(rand(),rand(),rand(),rand()); function mainLoop(){ // Instead of dumping the old reuse its memory reuseGameThing(gameThing,rand(),rand(),rand(),rand()); // so much quicker as there is no need to find and assign new RAM and // GC will not be called }不必担心内存泄漏。您必须非常努力地做到这一点,并且将涉及DOM。您只需触摸DOM。
不要使用Getter和Setters
我从来不明白为什么Javascript编码器会这样做。Getter和Setter用于您没有直接访问权限的私有属性。它们也可以通过设置时检查数据来确保永远不会损害类的状态。它是在获取数据类型时提供抽象。它们用于面向对象的语言,而Javascript并非如此,也永远不可能如此。
当值与getter调用一样可访问时,没有理由使用getter /您所做的就是增加CPU的工作,以获取getter引用,然后将其推入调用堆栈,调用函数,然后获取值引用,将其移至返回引用,弹出调用堆栈并返回流,获取返回引用并将其移至我们想要的变量中。
只需获取值引用,然后将其移至您想要的变量中,便可以更快地完成。Javascript没有私有属性。拥有隐藏属性的唯一方法是通过闭包,并且只应关闭未在其范围之外使用的数据。不要使用它们,尤其是不要在游戏中使用它们。
您的游戏(很多)中还有很多可以优化的东西,但我认为您会全力以赴寻找导致帧不一致的原因,而且您的游戏仅使用了1/60帧时间的5%,因此您就像您在世界上的所有时间一样,可以忽略我所说的一切。
希望我听起来不刺耳。但是,当我看到热心的新程序员从不懂Java语言的旧C ++ / C#Java编码器中吸取了坏习惯时,这便引起了我的注意。
1> Blindman67..:无需优化
我刚刚看了一下代码,并在小提琴上进行了介绍。好消息是,您无需优化代码,您的运行速度约为最大值的5%。
在您发布的时间轴图像中,您可以看到您的代码大多数时候几乎没有执行任何操作。优化总是好的,但是对于您的应用程序,将中间时间从95%增加到96%并不值得。
最佳负载V过载
我包括一张图像,该图像显示时间轴上每秒60fps的最佳负载应该是什么样子,然后过载的帧率看起来就可以每秒管理30帧,并且由于存在30帧而使帧率从30跳到20太多的事情要做。
与OP的相比
[
2 看其他地方
当您比较它们时,您可以看到您的游戏中几乎没有任何东西发生,大多数帧是空闲时间。您的代码可以达到接近1000 Fps的速度,并且仍有空闲时间。帧速率的问题出在其他地方,要么是占用CPU时间,要么是共享GPU占用了带宽和RAM。
如果您始终打开开发工具,那么您所做的很多
console.log
事情都会产生很大的影响。console.log
当您不再需要调用时,将其保留在代码之外。在设备上没有更多信息的情况下,您不能在其上运行该设备,以及该设备在运行哪些其他应用程序,我无法为您提供明确的解决方案。关闭尽可能多的应用程序和服务,直到发现改进为止。它最有可能只是导致它的一个不良应用。
现在的坏消息
您说您不关心GC。好吧,如果您想扩展游戏,就应该非常担心。使用CPU事件探查器,您的游戏会生成足够多的不需要的RAM,以使GC占总处理时间的2-5%(不包括空闲时间)。您很幸运,您还有余时间,但GC会注意并是导致帧速率不一致的主要原因之一。相比之下,具有良好V坏代码(100,000行代码)的代码如果出现,将永远不会使GC的峰值超过0.1%。
每次运行一些创建
Array
或的代码Object
时,或者在使用new Something()
或Object.create(foo)
创建必须清理的内容时,都应考虑这种方式。如果这样做,那么每一帧都会在RAM中不断产生垃圾。结果是GC的负载恒定,随着代码复杂度的增加,负载只会变得更糟。游戏循环的GC绝对不要超过0.5%,如果做得好,它应该保持在0.1%以下切勿在游戏循环中使用新游戏
同为
foo = []
,obj = {}
,obj = Object.create(blah)
特别是最后一个。您不仅要为已经具有RAM的对象请求更多的内存,而且Object.create
是创建javascript具有的新对象的最慢方法。情况变得更糟,不仅是创建对象的最慢方式,而且创建了访问和使用最慢的对象。忘记您甚至知道该功能已经存在。错误的内存使用示例。
// creates an object function GameThing(a1,a2,a4,a4){ this.a1 = a1; this.a2 = a2; this.a3 = a3; this.a4 = a4; this.data = [1,2,3,4,5,6,7,8,9,10]; // create a new array in game loop BAD } function rand(){return Math.random()};); var gameThing; function mainLoop(){ // using new in the main loop BAD gameThing = new GameThing(rand(),rand(),rand(),rand()); // dumps the old RAM // for GC and uses some // more for the next loop. }应该怎么做
// creates an object function GameThing(a1,a2,a4,a4){ this.a1 = a1; this.a2 = a2; this.a3 = a3; this.a4 = a4; this.data = [1,2,3,4,5,6,7,8,9,10]; } function reuseGameThing(thing,a1,a2,a3,a4); thing.a1 = a1; thing.a2 = a2; thing.a3 = a3; thing.a4 = a4; thing.data[1] = 0; // don't create a new array. The memory has be set thing.data[2] = 1; // aside for it already, why dump it to reset the values thing.data[3] = 2; // just overwrite the old values. The time save thing.data[4] = 3; // from needless GC hits is well worth he little ... // extra time for set each item ... thing.data[9] = 10; } function rand(){return Math.random()};); // create the object ONCE only. It create a ram buffer for your data // that should not need to be deleted for the life time of the current game state var gameThing = new GameThing(rand(),rand(),rand(),rand()); function mainLoop(){ // Instead of dumping the old reuse its memory reuseGameThing(gameThing,rand(),rand(),rand(),rand()); // so much quicker as there is no need to find and assign new RAM and // GC will not be called }不必担心内存泄漏。您必须非常努力地做到这一点,并且将涉及DOM。您只需触摸DOM。
不要使用Getter和Setters
我从来不明白为什么Javascript编码器会这样做。Getter和Setter用于您没有直接访问权限的私有属性。它们也可以通过设置时检查数据来确保永远不会损害类的状态。它是在获取数据类型时提供抽象。它们用于面向对象的语言,而Javascript并非如此,也永远不可能如此。
当值与getter调用一样可访问时,没有理由使用getter /您所做的就是增加CPU的工作,以获取getter引用,然后将其推入调用堆栈,调用函数,然后获取值引用,将其移至返回引用,弹出调用堆栈并返回流,获取返回引用并将其移至我们想要的变量中。
只需获取值引用,然后将其移至您想要的变量中,便可以更快地完成。Javascript没有私有属性。拥有隐藏属性的唯一方法是通过闭包,并且只应关闭未在其范围之外使用的数据。不要使用它们,尤其是不要在游戏中使用它们。
您的游戏(很多)中还有很多可以优化的东西,但我认为您会全力以赴寻找导致帧不一致的原因,而且您的游戏仅使用了1/60帧时间的5%,因此您就像您在世界上的所有时间一样,可以忽略我所说的一切。
希望我听起来不刺耳。但是,当我看到热心的新程序员从不懂Java语言的旧C ++ / C#Java编码器中吸取了坏习惯时,这便引起了我的注意。