Collision detection for one frame in JavaScript - javascript

I'm playing around with the Crafty.js game engine. I have a small game where the player is a ball and he drops down to platforms. Every time he hits the next platform, he gains a point. The score is stored as a variable and displayed on screen. I'm using Crafty's collision detection to detect when the player hits a new platform. If you're not familiar with Crafty, it's pretty simple, whenever the player hits the new platform, an event is fired and I can add one to the score.
My problem:
The game is running around 60fps. Every time the canvas reloads, Crafty will detect whether or not the player is actually touching the platform. This results in my score variable incrementing by one every single frame the player is touching a level. This is far from what I want. I want the score to be incremented ONCE per platform. This is a problem that I don't know how to fix.
Other things I've tried to solve it:
I also considered constantly measuring the distance that the player is from the starting point then I could tell (by division) what platform the player is on (since the platforms are equally vertically spaced). This was a problem however since Crafty was having issues giving me the current location of the player.
What I think would work: I think if I could have an event fired on the first frame that the player hits each platform, then that might work. (side note, If the player stays on one platform and jumps up and lands on the same platform a second time, I only want ONE point to be added. Not double jumping)
What I need from you guys: Have you ever had this issue? I really need to finish this game. And This minor technical problem is preventing me from finishing it. I'd love somebodys input.

The simplest solution would seem to be setting up a variable for each platform that tracks whether the player has landed on that platform or not. Then, whenever the ball is in contact with a platform that hasn't been landed on yet, award a point and mark that platform as landed on.
If the platforms are in linear sequence, you could even have a single integer variable that tracks which platform the player is on.

I wanted to expand on Taymon's answer because it seems like a really good solution for this problem. I would simply add a boolean attribute flag to the platform component that determines whether or not it has been counted.
Crafty.c('Platform', { isCounted: false })
Then, the logic for handling that hit would check that flag before counting it.
Here's a live example:
var score = 0;
Crafty.init(800, 600, $('#game')[0])
Crafty.background('blue')
/**
* because of the way gravity works
* (or maybe I just don't really understand it),
* I had to make separate entities for the
* gravity and the actual hit testing for each
* platform.
**/
Crafty.c('Platform', {
platform: function(x, y, visId) {
this.addComponent('2D, DOM')
.attr({
h: 20,
w: 200,
x: x,
y: y - 10,
visId: visId
});
return this;
},
isCounted: false,
visId: 0,
vis: function() {
return Crafty(this.visId);
}
});
Crafty.c('PlatformVis', {
platformVis: function(x, y) {
this.addComponent('2D, DOM, Color, PlatformVis')
.color('green')
.attr({
h: 20,
w: 200,
x: x,
y: y
});
return this;
}
});
// make some platforms
for (var i = 0; i < 5; ++i) {
var x = i * 200;
var y = i * 75 + 92;
var vis = Crafty.e('PlatformVis').platformVis(x, y)
Crafty.e('Platform').platform(x, y, vis.getId());
}
// player
Crafty.e('2D, DOM, Color, Twoway, Gravity, Collision')
.color('red')
.twoway(6, 14)
.gravity('PlatformVis')
.gravityConst(.8)
.attr({
x: 0,
y: 0,
h: 32,
w: 32
})
.checkHits('Platform')
.bind('HitOn', function(e) {
var platform = e[0].obj;
if (!platform.isCounted) {
platform.isCounted = true;
platform.vis().color('yellow');
Crafty('Score').text(++score);
}
}, this);
// score hud
Crafty.e('2D, DOM, Text, Score')
.text(score)
.textColor('white')
.textFont({
size: '32px'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/crafty/0.6.3/crafty-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>Player uses the <b>Twoway</b> component, so use arrows/wasd to move/jump</p>
<div id="game"></div>

Related

Handle movement after collision is detected

I'm making a small platformer in js and I'm having trouble with the collisions. Unfortunately it seems that 90% of the info online is detecting the collisions, and not what comes after. I can easily detect collisions as everything in my game is an axis aligned 2d rectangle, but handlining those collisions is the hard part.
I've tried moving the player up to the nearest floor when a collision is detected, but it also happens when you collide with a wall. So I tried calculating the closest face and snapping the player there, but it leads to all kinds of weirdness. Here is the code I have so far (the current code is just for floor collisions now, but the same principal can be applied to the rest of the directions)
if (collided) {
let ld = {'a': 'l', 'b': Math.abs(player.left.x - col.left.x)}
let rd = {'a': 'r', 'b': Math.abs(player.right.x - col.right.x)}
let td = {'a': 't', 'b': Math.abs(player.top.y - col.top.y)}
let bd = {'a': 'b', 'b': Math.abs(player.bottom.y - col.bottom.y)}
let dirs = [ld, rd, td, bd]
let nearestFace = dirs.hasMin('b').a
if (nearestFace == 'b') {
player.grounded = true
player.yvel = 0
player.pos.y = col.top.y + player.size.y/2
} else {
player.grounded = false
}
}
Your code seems to not check where the nearest face collides just that it is the nearest face after collision. Which could cause this kind of problem:
If your game is in 2d, everything is a rectangle, and nothing has an angle, then you have a collision problem known as AABB (Axis-Aligned Bounding Boxes) collision, it's a great keyword to search for in your case and an amazing starting point.
First, I advise you to predict collisions instead of dealing with them after they happen because of potential problems like this:
Moreover, if you wish an advanced tutorial on this, the above image comes from this tutorial: Swept AABB Collision Detection and Response. It probably has everything you need.
In short, the input for your collision handling logic should be your objects old position, current size, its velocity, and all other collidable objects (their size and positions). The response should be the expected behavior of your object (i.e. the new velocity, corrected for 'avoiding' collision). That way you can easily test scenarios and your implementation with ease, consider this:
const player = {x: 0, y: 0, width: 10, height: 10, xvel: 12, yvel: 0};
// player.right = {x: player.x + player.width, y: player.y} // Your engine does this, right?
const collidableList = [{x: 15, y: 0, width: 5, height: 10}];
const newVelocity = handleCollision(player, collidableList);
I imagine that the player position is aligned at top left, so in this case you can predict that your new velocity ought to be xvel = 5, yvel = 0. Which means you just have to create a handleCollision that works for this test case, and then you could run multiple tests to make sure that the collision is behaving nicely on edge cases such as when nothing collides and when two things collides and one is closer than the other.
The main idea behind this collision is to find the velocity that is closest to zero as necessary to AVOID a collision in the next frame.
For example, imagine a scenario where the player is moving to the right. Let's disregard the Y axis because it should only be used to detect if the objects will collide in the future and will not influence the calculations of the horizontal velocity itself. If the player is moving to the right, it must check the distance between the left side of every object to the right side of the player, if this space is smaller than the velocity, it will obviously cause a collision, like this:
let targetVelocityX = player.xvel;
for (const collidable of ...) {
// ...
if (targetVelocityX > 0) {
// We are moving to the right so:
// Figure out how much space do we have between the objects
const leftOverSpace = collidable.left.x - player.right.x;
// Is our velocity larger than the space we have available?
if (targetVelocityX > leftOverSpace) {
// We must restrict the velocity because the player will collide otherwise
// We can only move as much as we have space available
targetVelocityX = leftOverSpace;
}
} else if (targetVelocityX < 0) {
// Moving to the left...
}
Using the test case I made before, we should get leftOverSpace = 5, which is smaller than our targetVelocityX = 12, so the new velocity will be 5, which will make it touch the collidable object, and in the next frame our player position will be x = 5 and xvel = 5, if we run it again the collision logic will tell us that the left over space is zero so the horizontal velocity will be set to zero which means we cannot move to the right anymore because we are touching the object.
I should remind you that this is different than the tutorial I linked above, the tutorial tries to find the time where the collision happened as a floating point number which is useful if you want to conserve the velocity to deflect a pong ball, or slide the object along, which are some of his examples.

Faster scrolling/panning with large canvas?

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.

Javascript sprites moving between two points

I'm wanting to get some sprites moving between two points in my (very basic) javascript game. Each sprite is randomly placed in the level, so I want them to move back and forth between their base position. I have the code below
function Taniwha(pos) {
this.basePos = this.pos;
this.size = new Vector(0.6, 1);
this.move = basePos + moveDist(5,0));
}
Taniwha.prototype.type = "taniwha"
var moveSpeed = 4;
Taniwha.prototype.act = function(step) {
this.move = ???
and this is where I get stuck. I'm not sure how to tell it to go left, back to base pos, right then back to base pos again (I plan to loop this). Does anyone have any advice? (also using Eloquen Javascript's example game as an outline/guide for this, if how I'm doing things seems odd!)
For horizontal movement, change x coordinate of the position.
var pos = { x: 1, y: 2 };
pos.x++ ; // This will move right
pos.x-- ; // This will move left
Likewise for vertical movement. You also need to update the coordinates after change for the object which you are drawing.
In truth ,there are lots of library to develop a game.
Use those, control a sprite is very easy.
Like:
Pixijs
CreateJS
Both of them are opensource project, you can watch and learn the source.
And have lots of examples and document.

Is possible to have an event linked to object's own parameter?

I have an object (lets say a circle) with specific parameters (x,y and radius).
var circle = new Kinetic.Circle({
x: 100,
y: 100,
radius: 20
}
The radius of the circle is thereafter dynamically changed by the user.
Now I want a listener that continuously observes the radius, and every time the radius goes below 5, it deletes the circle. Something like:
circle.on("radiusLessThanFive",function (e) {
this.remove();
}
So how do I configure this radiusLessThanFive event that constantly listens to the radius of the circle?
Well, unfortunately, there isn't something built in that would do that. So you just have to make it part of your application logic.
Let's say you have a layer called shapesLayer. You can do:
function radiusLessThanFive(){
var circles = shapesLayer.get('.circle'); // I can't remember the getting all circles syntax, but something like this
for ( var i=0; i<circles.length; i++) {
if(cicrles[i].getRadius() < 5 ){
circles[i].remove();
shapesLayer.draw();
}
}
}
While this is not the most efficient solution ever, it gets the job done... unless you know how to create your own custom events in javascript (which would have to do the same thing as the above function anyways).
In case you'd like to take the custom event road, here is a link: http://www.sitepoint.com/javascript-custom-events/
Edit: regarding comment below
function radiusLessThanFive(myCircle){ //function which checks radius of single circle and deletes if less than 5
if(myCircle.getRadius() < 5 ){
myCircle.remove();
myCircle.getParent().draw();
}
}
}

Attempting to get two simultaneous joysticks working with touch events

I've been battling with this for two days. I'm using an HTML5/JS game engine called ImpactJS and someone made a very useful plugin to create joystick touch zones for mobile devices. The idea is that you specify a zone on the screen where the joystick is activated when that area is touched.
The code for the plugin I am using is here. I have modified it slightly to add "curPos" (x and y coordinates of the spot that the user is currently touching), otherwise all code is identical. I have been trying to solve this problem myself as well as contacting the original creator, but they seem to be unreachable at this time and I'm getting nowhere.
I'm sure I'm doing something very wrong here, but while I can get the touch zones to work perfectly on their own, every time I try to use both joysticks at the same time they partially overwrite each other.
I have specified two zones as follows when my game initializes:
this.joystick1 = new TouchJoystickZone(0, 0, ig.system.width / 2, ig.system.height);
this.joystick2 = new TouchJoystickZone(ig.system.width / 2, 0, ig.system.width / 2, ig.system.height);
this.joystick1 is responsible for player rotation. this.joystick2 is responsible for player acceleration. In my Player entity I have the following movement code for the two joysticks. Again, this works perfectly when I only have one finger on the screen/one joystick in use:
if( ig.ua.mobile ) {
// ROTATION
if (ig.game.joystick1.touchStart.x > 0 && ig.game.joystick1.touchStart.x < ig.system.width/2) {
if (Math.abs(ig.game.joystick1.delta.x) >= 50 || Math.abs(ig.game.joystick1.delta.y) >= 50) {
this.joystickAngle = ig.game.Controller.toDegrees(ig.game.Controller.joystickAngle());
if (this.angle > this.joystickAngle + 20) {
this.angle -= this.turnSpeed * ig.system.tick;
}
else if (this.angle < this.joystickAngle - 20) {
this.angle += this.turnSpeed * ig.system.tick;
}
else {
this.angle = this.joystickAngle;
}
}
}
// THRUST
if (ig.game.joystick2.touchStart.x > ig.system.width / 2) {
if (ig.game.joystick2.delta.y <= -50) {
this.accel.x = Math.cos(this.angle*Math.PI/180)*this.thrust;
this.accel.y = (Math.sin(this.angle*Math.PI/180)*this.thrust);
this.fuel -= 0.1;
}
else if (ig.game.joystick2.delta.y >= 50) {
this.accel.x = Math.cos(this.angle*Math.PI/180)*-this.thrust;
this.accel.y = (Math.sin(this.angle*Math.PI/180)*-this.thrust);
this.fuel -= 0.1;
}
}
else {
this.accel.x = 0;
this.accel.y = 0;
}
}
As soon as I place a second finger on the screen, however, the first joystick becomes overwritten. I can rotate and then move or move and then rotate and it works fine, but I need to do both at the same time.
I found out that touchStart.x and touchStart.y seems to be being set for both joysticks when I tap to use the other stick and not just the relevant joystick1 or joystick2, even though in the plugin code those coordinates are only meant to be affected if the touch for that joystick is within the specified zone. I believe this is partly what is contributing to the issue. At this stage I've spent hours trying to figure this out and am just as lost as when I started.
Can someone possibly point me in the right direction with using both of these joysticks at the same time?
The joystick script you are using is only looking for the first finger. The following is from lines 47-48
var x = ev.touches[0].pageX - pos.left,
y = ev.touches[0].pageY - pos.top;
The '0' determines which finger to track.
The script would have the be changed to be aware which finger is on which element and then only track that finger. You could do that by either determining which element was pressed first or by location.
JSFiddle Example: http://jsfiddle.net/7WR88/5/
http://www.sitepen.com/blog/2008/07/10/touching-and-gesturing-on-the-iphone/
As mentioned by #Dcullen, there may be multiple touches in each event starting at the first touch.
It would be a simple solution to iterate through the ev.touches collection and see if each touch falls into a hotzone. If it falls under hot zone 1, treat it as a touch for joystick 1. If it falls under hotzone 2, treat it as a touch for joystick 2.
This would mean that it doesn't matter in which order the touches appear, because they will always map to the correct joystick if they are near to it.

Categories