I'm currently creating a game that involves coins that come up from the bottom of the screen in a random X position. If the hero overlaps/collides with the coins, the user gets a point.
My issue is, when I put my collision detection in a fast interval function, it seems that the collision is not registering at some instances.
I've replicated the game in the jsfiddle below (originally it uses the phone's accelerometer to move the hero but for this example I've quickly made it a left/right key based control) -
http://jsfiddle.net/wpavxn6k/1/
Snippet of the interval below -
//keep checking to see if collision has occured
window.setInterval(function () {
if (hitTest(app.hero, app.coinClassUnique) == true) {
app.coinClassUnique.css('display', 'none');
app.score++;
$("h2").html(app.score);
}
}, 5);
//measure the width/height and position of the coin div and the hero image to determine if they are overlapping
function hitTest(a, b) {
var aPos = a.offset();
var bPos = b.offset();
var aLeft = aPos.left;
var aRight = aPos.left + a.width();
var aTop = aPos.top;
var aBottom = aPos.top + a.height();
var bLeft = bPos.left;
var bRight = bPos.left + b.width();
var bTop = bPos.top;
var bBottom = bPos.top + b.height();
return !(bLeft > aRight || bRight < aLeft || bTop > aBottom || bBottom < aTop);
}
My question is whether you can see an obvious flaw in my code that may be affecting performance, or if there are any tips of how I can improve performance and collision detection in a real-time based game like this.
I just wanted to post the answer for this question incase someone encounters a similar issue. The interval of the game was happening at different times so I rebuilt the game inside a Canvas element with requestFrameAnimation. This way, all the elements reload at once.
I had some issues like images not loading properly etc. at first, which is why I avoided using canvas - but with the help of preloading and rendering the images into separate canvases that are hidden off screen - I was able to tackle these issues.
Related
I need to scale lots of text nodes in the browser (support of all modern desktop and mobile browsers).
If I am right there are two options that offer good performance: scaling text objects in Canvas or scaling text nodes in the DOM using transform:matrix.
I have created a scenario to test both versions but the results are inconclusive. Uncomment testDOM() or testCanvas() function to start the test. (I am using JQuery and CreateJS framework because it was convenient. It is possible to use vanilla JS but I don't think that is the bottleneck here). (It matters what portion of the screen you actually see so please switch to full screen view in codepen).
http://codepen.io/dandare/pen/pEJyYG
var WIDTH = 500;
var HEIGHT = 500;
var COUNT = 200;
var STEP = 1.02;
var MIN = 0.1;
var MAX = 10;
var stage;
var canvas;
var bg;
var canvasTexts = [];
var domTexts = [];
var domMatrix = [];
var dom;
function testDOM() {
for (var i = 0; i < COUNT; i++) {
var text = $("<div>Hello World</div>");
var scale = MIN + Math.random() * 10;
var matrix = [scale, 0, 0, scale, Math.random() * WIDTH, Math.random() * HEIGHT];
text.css("transform", "matrix(" + matrix.join(',') + ")");
domTexts.push(text);
domMatrix.push(matrix);
}
dom = $('#dom');
dom.append(domTexts);
setTimeout(tickDOM, 1000);
}
function tickDOM() {
for (var i = 0; i < domTexts.length; i++) {
var text = domTexts[i];
var matrix = domMatrix[i];
var scale = matrix[0];
scale *= STEP;
if (scale > MAX)
scale = MIN;
matrix[0] = matrix[3] = scale;
text.css("transform", "matrix(" + matrix.join(',') + ")");
}
requestAnimationFrame(tickDOM);
}
function testCanvas() {
$('#dom').hide();
stage = new createjs.Stage('canvas');
createjs.Touch.enable(stage);
createjs.Ticker.timingMode = createjs.Ticker.RAF;
canvas = stage.canvas;
devicePixelRatio = window.devicePixelRatio || 1;
stage.scaleX = devicePixelRatio;
stage.scaleY = devicePixelRatio;
console.log('devicePixelRatio = ' + devicePixelRatio);
stage.mouseMoveOutside = true;
stage.preventSelection = false;
stage.tickEnabled = false;
stage.addChild(bg = new createjs.Shape());
bg.graphics.clear();
bg.graphics.f('#F2F2F2').drawRect(0, 0, 2 * WIDTH, HEIGHT);
canvas.width = 2 * WIDTH * devicePixelRatio;
canvas.height = HEIGHT * devicePixelRatio;
canvas.style.width = 2 * WIDTH + 'px';
canvas.style.height = HEIGHT + 'px';
stage.update();
for (var i = 0; i < COUNT; i++) {
var text = new createjs.Text("Hello World", "10px", "#333333");
text.scaleX = text.scaleY = MIN + Math.random() * 10;
text.x = Math.random() * WIDTH;
text.y = Math.random() * HEIGHT;
stage.addChild(text);
canvasTexts.push(text);
}
stage.update();
setTimeout(tickCanvas, 1000);
}
function tickCanvas() {
for (var i = 0; i < canvasTexts.length; i++) {
var text = canvasTexts[i];
text.scaleX = text.scaleY *= STEP;
if (text.scaleX > MAX)
text.scaleX = text.scaleY = MIN;
}
stage.update();
requestAnimationFrame(tickCanvas);
}
testDOM();
//testCanvas();
My questions:
Is it possible to improve the performance of my tests? Am I doing something wrong?
The first 5-10 seconds are significantly slower but I don't understand why. Does the browser somehow cashes the text objects after some time? If yes, is the test unusable for real world scenario testing where the objects don't zoom in a loop for longer period of time?
According to the Chrome Profiling tool the DOM version leaves 40% more idle time (is 40% more faster) than the Canvas version but the Canvas animation looks much smoother (after the initial 5-10 seconds of lagging), how should I interpret the Profiling tool results?
In the DOM version I am trying to hide the parent of the text nodes before I apply the transformations and then unhide it but it probably does not matter because transform:matrix on absolutely positioned element does not cause reflow, am I right?
The DOM text nodes have some advantages over the Canvas nodes like native mouse over detection with cursor: pointer or support for decorations (you can not have underlined text in Canvas). Anything else I should know?
When setting the transform:matrix I have to create a string that the compiler must to parse back to numbers, is there a more efficient way of using transform:matrix?
Q.1
Is it possible to improve the performance of my tests? Am I doing
something wrong?
Yes and no. (yes improve and no nothing inherently wrong (ignoring jQuery))
Performance is browser, and device dependent, for example Firefox handles objects better than arrays, while Chrome prefers arrays. There is a long list of differences just for the javascript.
Then the rendering is a dependent on the hardware, How much memory, what capabilities, and the particular drivers. Some hardware hates state changes, while others handle them at full speed. Limiting state changes can improve the speed on one machine while the extra code complexity will impact devices that don't need the optimisation.
The OS also plays a part.
Q.2
The first 5-10 seconds are significantly slower but I don't understand
why. Does the browser somehow cashes the text objects after some time?
If yes, is the test unusable for real world scenario testing where the
objects don't zoom in a loop for longer period of time?
Performance testing in Javascript is very complicated and as a whole application (like your test) is not at all practical.
Why slow?
Many reasons, moving memory to the display device, javascript optimising compilers that run while the codes runs and will recompile if it sees fit, this impacts the performance Un-optimised JS is SLOOOOOWWWWWWWW... and you are seeing it run unoptimised.
As well. In an environment like code pen you are also having to deal with all its code that runs in the same context as yours, it has memory, dom, cpu, GC demands in the same environment as yours and thus your code can not be said to be isolated and profiling results accurate.
Q.3
According to the Chrome Profiling tool the DOM version leaves 40% more
idle time (is 40% more faster) than the Canvas version but the Canvas
animation looks much smoother (after the initial 5-10 seconds of
lagging), how should I interpret the Profiling tool results?
That is the nature of requestAnimationFrame (rAF), it will wait till the next frame is ready before it calls your function. Thus if you run 1ms past 1/60th of a second you have missed the presentation of the current display refresh and rAF will wait till the next is due 1/60th minus 1ms before presentation and the next request is called. This will result in ~50% idle time.
There is not much that can be done than make you render function smaller and call it more often, but then you will get extra overhead with the calls.
rAF can be called many times during a frame and will present all renders during that frame at the same time. That way you will not get the overrun idle time if you keep an eye on the current time and ensure you do not overrun the 1/60th second window of opportunity.
Q.4
In the DOM version I am trying to hide the parent of the text nodes
before I apply the transformations and then unhide it but it probably
does not matter because transform:matrix on absolutely positioned
element does not cause reflow, am I right?
Reflow will not be triggered until you exit the function, hiding the parent at the start of a function and then unhiding it at the end will not make much difference. Javascript is blocking, that means nothing will happen while you are in a function.
Q.5
The DOM text nodes have some advantages over the Canvas nodes like
native mouse over detection with cursor: pointer or support for
decorations (you can not have underlined text in Canvas). Anything
else I should know?
That will depend on what the intended use is. DOM offers a full API for UI and presentation. Canvas offers rendering and pixel manipulation. The logic I use is if it takes more code to do it via DOM then canvas, then it is a canvas job and visa versa
Q.6
When setting the transform:matrix I have to create a string that the
compiler must to parse back to numbers, is there a more efficient way
of using transform:matrix?
No. That is the CSS way.
I have a page that is basically a large canvas with a lot of small icons connected with lines, and the user needs to be able to pan/zoom around. I've got everything working, but its very choppy. It seems that the repaining is the problem (if I remove the icons it becomes very smooth), but if I run Chrome's profiler, none of my functions are taking up any significant time at all.
Are there any better approaches to panning, without having to repaint everything? For instance in WinAPI, there was a function that scrolled the window content and only invalidated the thin region that just scrolled into view. Is there any way to do something similar in Javascript/canvas, since all I really need is to move the entire window?
I have tried making a giant canvas with everything pre-painted on it, that is then moved around with scrollLeft/scrollTop, but that takes way too much memory (what else should I expect from a 4000x4000 image) and makes zoom very slow instead.
Here's the page if anyone is interested, the code is pretty readable I hope:
http://poe.rivsoft.net/
You will have to just put up with some slower parts. Consider creating dirty regions. These are areas that need to be redrawn when panning. Keep a back buffer the same size as the canvas. When panning copy from the back buffer to its self the area that remains visible and mark the newly visible area as dirty. Then every frame rerender only the dirty areas onto the back buffer. For zooming you can zoom the back buffer and re render when the user pauses or incrementally, this will create a pixelated view (like google maps) when zooming in or aliasing and dirty areas on the sides when zooming out, until you update it.
You can also limit the amount of dirty area redrawn each frame so maintaining a constant frame rate. It will not look as nice but it will improve the panning and zooming. On my machine it runs well (nice job BTW) so you may want to consider implementing optimisations only on machines that can not handle the load.
Also looking at the function DrawNode there is lots of room for optimisation as you have a lot of redundant code (especially once all assets have loaded)
This is just a suggestion as I do not know if nodes are unique or if the x, y coords change, but that can be accommodated as well. You have a lot of searching and checks that should be avoided. The use of strings instead of numbers or booleans to check for status and type is also slow.
function DrawNode(ctx, node, x, y, active) {
// Has this node got quick render information
if (node.qNode) {
// if so render the quick version
var qn = node.qNode; // creating the var qn and then qn.? is quicker than access node.qNode.?
ctx.drawImage(qn.image, qn.coords.x, qn.coords.y, qn.coords.w, qn.coords.h, qn.x, qn.y, qn.size, qn.size);
return;
}
var type = NodeTypes[node.type];
var frameType = "frame" + (active ? "Active" : "Inactive"); // active should be a boolean
if (type && type.size && node.type !== "jewel") { // should be !node.isJewel with isJewwl a boolean
var spriteType = node.type;
if (node.type !== "mastery") // Should be boolean
spriteType += (active ? "Active" : "Inactive");
var sprites = SkillTree.skillSprites[spriteType][3];
var image = GetImage("Assets/" + sprites.filename);
var coords = sprites.coords[node.icon];
if (image && image.loaded && coords) {
ctx.drawImage(image, coords.x, coords.y, coords.w, coords.h,
x - type.size * 0.5, y - type.size * 0.5, type.size, type.size);
// add the information to quickly render the node next time.
// You may want to add sub objects for Mastery Active,inactive
node.qNode = {
image : image,
coords : coords,
x : x - type.size * 0.5,
y : y - type - sise * 0.5,
size : type.size
}
} else if (!image || !image.loaded) {
return false;
}
}
// same deal for the other type.
}
When optimising you start at the slowest point and make that code as efficient as possible, then work your way out. It is well written code but it has no eye for speed so I would say there is lots more room for improvement in the code.
To demonstrate this (I'm working on a much more compressed Fiddle), open this in chrome, and move Jay-Z using your arrow keys and catch about 4 - 5 (sometimes more!) cakes.
You will notice that there is a massive cupcake on the left side of the screen now.
I update the cakes' positions in my handleTick function, and add new cakes on a time interval. Here are both of those:
/*This function must exist after the Stage is initialized so I can keep popping cakes onto the canvas*/
function make_cake(){
var path = queue.getItem("cake").src;
var cake = new createjs.Bitmap(path);
var current_cakeWidth = cake.image.width;
var current_cakeHeight = cake.image.height;
var desired_cakeWidth = 20;
var desired_cakeHeight = 20;
cake.x = 0;
cake.y = Math.floor((Math.random()*(stage.canvas.height-35))+1); //Random number between 1 and 10
cake.scaleX = desired_cakeWidth/current_cakeWidth;
cake.scaleY = desired_cakeHeight/current_cakeHeight;
cake.rotation = 0;
cake_tray.push(cake);
stage.addChild(cake);
}
And the setInterval part:
setInterval(function(){
if (game_on == true){
if (cake_tray.length < 5){
make_cake();
}
}
else{
;
}
},500);
stage.update is also called from handleTick.
Here is the entire JS file
Thanks for looking into this. Note once again that this only happens in Chrome, I have not seen it happen on Firefox. Not concerned with other browsers at this time.
Instead of using the source of your item, it might make more sense to use the actual loaded image. By passing the source, the image may have a 0 width/height at first, resulting in scaling issues.
// This will give you an actual image reference
var path = queue.getResult("cake");
How would a cheat/auto moving paddle in this hidden html game look like?
There is a
<div id="breakout-ball"><span>●</span></div>
and a
<div id="breakout-paddle"></div>
when moving the mouse the paddle is moved horizontally. How can I connect the movement of the ball with the paddle?
This question will become "community wiki" as soon as possible.
function cheat() {
var ball = document.getElementById('breakout-ball');
var paddle = document.getElementById('breakout-paddle');
paddle.style.left = ball.style.left;
setTimeout(cheat, 20);
}
cheat();
// Add this via the FireBug console.
I modified your solution slightly to account for the scenario of the ball going off screen and breaking the hack and also the ball getting jammed into the corner.
function cheat() {
var ball = document.getElementById('breakout-ball');
var paddle = document.getElementById('breakout-paddle');
var buffer = Math.floor((Math.random()*100)+1);
var leftVal = parseInt(ball.style.left, 10);
if (ball.style.left) {
paddle.style.left = (leftVal - buffer) + 'px';
}
setTimeout(cheat, 100);
}
cheat();
To be honest though, if you are going down that road why not do this?
function cheat() {
var paddle = document.getElementById('breakout-paddle');
paddle.style.width = '100%';
}
cheat();
Anyway I'm going to continue to dig into the code and do some deeper manipulation
in chrome, search the game, ctrl+J, paste the following in, and press enter.
This is a simple goal:
//get the ball's X position from its CSS
function ballx(){
return parseFloat(document.querySelector("#breakout-ball").style.left.split("px")[0]);
}
function update(e){
//throws an exception when the game isn't up. Can be really annoying.
try{document.querySelector("#breakout-paddle").style.left = (ballx() - 75)+"px";}
catch(ex){}
}
var intervalTimer = setInterval(update, 125);//let it have a single weakness in case someone else tries it while I am around.
The lower the interval rate, the faster the paddle will move relative to the ball, but the slower the game goes. The maximum rate can be given with a simple requestAnimationFrame cycle, but that slows the browser down a lot(at least on my laptop).
I tried changing the paddle's size. It doesn't really work.
I'm sure it would be simpler with jQuery, but why make cheating easy when doing it hard core is already so easy?
I have come up with a new way to solve this problem. Whenever the screen size or window size changes, the paddle changes size based on the screen size. To combat this, I simply just added another section that splits the size of the paddle in half then uses that value to find the centre of the paddle. Very simple and very effective.
function autoMove() {
var ball = document.getElementById('breakout-ball')
var paddle = document.getElementById('breakout-paddle')
var leftVal = parseInt(ball.style.left, 10)
var paddleWidth = parseFloat(paddle.style.width, 10) / 2
paddle.style.left = (leftVal - paddleWidth) + 'px'
setTimeout(autoMove, 20)
}
autoMove();
Also, didn't like the function name. It looks way too suspicious so changed it. Hope this helps.
I am working on a server side physics experiment where the user controls an object through a socket. The problem I am running into results when the user moves the object outside the boundaries of the world.
I am using Box2Djs as installed through npm.
I create world 500x500 and then attach the following listener to it:
var boundaryListener = new b2d.b2BoundaryListener();
boundaryListener.Violation = function (body) {
//we will move this body to the opposite side
var position = body.GetWorldCenter();
//snap to opposite side
if (position.x < 0) {
position.x = worldAABB.upperBound.x + position.x;
}
if (position.y < 0) {
position.y = worldAABB.upperBound.y + position.y;
}
if (position.x > worldAABB.upperBound.x) {
position.x -= worldAABB.upperBound.x;
}
if (position.y > worldAABB.upperBound.y) {
position.y -= worldAABB.upperBound.y;
}
body.m_flags = body.m_flags & (~b2d.b2Body.e_frozenFlag); //does nothing :(
}
this.world.SetBoundaryListener(boundaryListener);
worldAABB is the b2AABB that the world uses as a boundary.
The problem is that I have noticed that when the boundary listener is fired, the flags are set to 22 which is allowSleep, frozen, and island flags. It would seem that when a b2Body passes outside the world boundary, it is frozen. That last line is an attempt to unfreeze the body by messing with the internal flags, but I have a distinct feeling that's the wrong way to do it.
How can I unfreeze the body? There are no functions that clear the frozen flags that I can see (the javascript is over 10,000 lines long so I honestly haven't read the whole thing) and placing some bodies as walls seems to have no effect (the user's object passes right through them).
My walls are created like so:
//create walls
var wallShape = new b2d.b2PolygonDef();
wallShape.SetAsBox(500, 10);
wallShape.density = 0.0;
wallShape.friction = 0.3;
var bodyDef = new b2d.b2BodyDef();
bodyDef.position.Set(250, 20);
var north = this.world.CreateBody(bodyDef);
north.CreateShape(wallShape);
bodyDef = new b2d.b2BodyDef();
bodyDef.position.Set(250, 499);
var south = this.world.CreateBody(bodyDef);
south.CreateShape(wallShape);
bodyDef = new b2d.b2BodyDef();
bodyDef.position.Set(499,250);
bodyDef.angle = Math.PI / 2;
var east = this.world.CreateBody(bodyDef);
east.CreateShape(wallShape);
bodyDef = new b2d.b2BodyDef();
bodyDef.position.Set(1, 250);
bodyDef.angle = Math.PI / 2;
var west = this.world.CreateBody(bodyDef);
west.CreateShape(wallShape);
Does anyone have any insights on how to fix this? There is very very little documentation I can find on using Box2D in javascript aside from the flash documentation that the website points to (which doesn't match half the time) and the C++ documentation which doesn't even talk about freezing.
It would probably be helpful to mention that the world has no gravity and all the objects have some linear and angular damping (its supposed to be a psuedo-in-space feel).
I had investigated Box2Djs source, and found next thing. Every time step Box2Djs checks if the body is inside the world boundaries. If body is out of range, then it "frozing", i.e. its excluding from collision detection. There this code (Body.js line 414):
Freeze: function(){
this.m_flags |= b2Body.e_frozenFlag;
this.m_linearVelocity.SetZero();
this.m_angularVelocity = 0.0;
for (var s = this.m_shapeList; s != null; s = s.m_next)
{
s.DestroyProxy();
}
}
Pay attention, this check performs every time step (b2Island.js 244). So, if you set e_frozenFlag at boundary listener, it will do nothing: flag will be set up at next time step. Thats more, after body had frozen, it losses its veolcity and its shapes looses theirs proxies in broad phase (as you can see from code above). Looks like proxies are not restroing automaticly, so, reseting flag is not enough.
I also not found somewhere in Box2Djs interface or logic for unfreezing bodies. Doing this manually is some kind of dirty trick, because you should acces BroadPhase, which is Box2Djs internal. Thats more, it dont help you, because on freezing body losses its velociy. But, as I see, you need continue body moving.
Solution is next. You should prevent body frozing at all in order to keep body in simulation after it moved out of world boundaries. It may be done by next trick. First, set world boundary with some large value. Then, set contact listener, and when body touches the walls, perform your boundary violation logic.
How to set contact listener in C++ you can see there: https://www.iforce2d.net/b2dtut/collision-callbacks Sory, I dont know java script and can't say, how to do this in Box2Djs.