Phaser arcade physics, use overlap to switch only once but keeps firing - javascript

I'm working on an action game using the Phaser framework, and I want the player to be able to touch certain switch tiles to turn other enemies off and back on. So it should work like this:
The player overlaps a special switch tile
Execute the switch action, only once
Ignore further overlapping until player moved off of the tile
When player moves off a switch tile, only then should it start checking for overlap again
Repeat from step 1 when player overlaps again (or another switch tile)
I'm using arcade physics and the overlap function is working. But the problem is that the overlap keeps firing over and over again, like every single frame. What would be the best way in Phaser to get the desired result?
See screenshot below of what I mean, and I've created a sandox of my code example here:
https://phaser.io/sandbox/edit/zEVOQfgA

Your code is not working because you are resetting okoverlap to 0 on each update. What you need to do is to set the okoverlap to a state and change that state at suitable time.
I've fixed your code by saving the property in game object in create.
game.flags = {};
game.flags.okoverlap = 0;
Then in update function I've checked that property and current overlap status.
function update() {
if(game.flags.okoverlap === 1 && !checkOverlap(mushroom, theswitch)) {
game.flags.okoverlap = 0;
}
game.physics.arcade.overlap(mushroom, theswitch, handleCollide, null, this);
}
function checkOverlap(spriteA, spriteB) {
var boundsA = spriteA.getBounds();
var boundsB = spriteB.getBounds();
return Phaser.Rectangle.intersects(boundsA, boundsB);
}
The logic inside handleCollide function is changed to
if (game.flags.okoverlap != 1) {
game.flags.okoverlap = 1;
doSwitch();
}
Finally replace okoverlap in render with game.flags.okoverlap to prevent ReferenceError
game.debug.text('overlap: ' + (game.flags.okoverlap == 1 ? 'YES': (game.flags.okoverlap == -1 ? 'partial': 'no')), 20, 40);
Working sample here - https://phaser.io/sandbox/edit/ikJBIznv

I've been playing around some more and found a solution, using 2 global variables. One variable frameoverlap to check if there is an overlap, and one variable doswitch to see if the switch action was already done. Then you can handle it all in the update() function.
function update() {
// assume no overlap
frameoverlap = 0;
// do arcade.overlap
game.physics.arcade.overlap(mushroom, theswitch, handleCollide, null, this);
// check flags after arcade.overlap
if (frameoverlap == 1) {
if (doswitch == 0) {
doswitch = 1; // remember the switch was done
doSwitch();
}
} else {
if (doswitch == 1) {
doswitch = 0; // stepping off the switch tile
}
}
}
See updated code here
https://phaser.io/sandbox/edit/VTenTwgh

Related

Logic: Prevent infinite loop when using portals

In a game that I am currently working on, there are portals, which are linked in pairs of two. Whenever the player enters a portal, it should be teleported to the other portal that is linked to the portal that they just entered. The problem is, that once it is teleported over, the other portal see's that the player is collding with it, and will teleport it back to the first, which will send it back and so on. The code for this porject is large and over many files, but here is the most important:
(Javascript, ignore the frame and pause vaibles, those are for animation)
export class Portal extends gameObject{
constructor(x, y, size, id){
super(x, y, size);
this.currentFrame = 0;//updated when (below) > (2 below)
this.countingFrame =0;//update every time;
this.pause = 3;//frames between frames
this.id = id;
this.justVisited = false;
}
update(player, portals){
if(this.countingFrame >= this.pause){
this.currentFrame = (this.currentFrame + 1) % 4;
this.countingFrame = 0;
}else{
this.countingFrame ++;
}
if(super.checkPlayerCollision(player)){
this.justVisited = true;
for(let i in portals){
if(this.id === portals[i].id && portals[i] !== this && !portals[i].justVisited){
player.x = portals[i].x;
player.y = portals[i].y;
}
}
}else{
for(let i in portals){
if(this.id === portals[i].id && portals[i] !== this && portals[i].justVisited){
this.justVisited = false;
}
}
}
}
render(canvas, spritesheet){
super.render(canvas, spritesheet, this.currentFrame , 26);
}
}
This is one attempt at patching it up, but it still failed. How can I make it so that the player goes throught the first one as soon as it collides, comes out the second, and won't retrun to the first untill the player moves off and then back on?
EDIT:After a little bit of seaching, the answer that was most often given to this question was "Move the player outside the reach of the second portal when it teleports over there". This will not work in my case, because when the player updates, his position changes with respect to the amount of time seince the last frame. In a perfet world, that would be ok, but because it is not perfect, the player cannot reliably be on a single point. Also, it would look weird if I moved it out of the hitbox entirely, so that doesn't work either.

Detecting Collision With Object in Loop

Is there a way to detect collision with an object created using a while loop?
I'm repeating an image across the screen using a while loop:
this.spikeX = 0;
while (this.spikeX < this.world._width) {
this.spike = this.add.sprite(this.spikeX, 0, 'spikes');
this.physics.arcade.enable(this.spike);
this.gameObjects.push(this.spike);
this.spikeX += (this.spike.width * 0.75);
}
I have a collision function:
collision: function(obj1, obj2) {
if (obj1.body.x < obj2.body.x + obj2.body.width &&
obj1.body.x + obj1.body.width > obj2.body.x &&
obj1.body.y < obj2.body.y + obj2.body.height &&
obj1.body.height + obj1.body.y > obj2.body.y) {
return true;
}
},
if(this.collision(this.player, this.spike)) {
console.log('spike');
}
When I call this function in the update function it doesn't detect collision but works when I just create a single spike outside the while loop.
That's probably because you have only one this.spike.
If you have multiple sprites, you need to put them into a group, and create them like this:
spike_group = game.add.group();
spike = spikes.create(spikeX, 0, 'spikes');
Then you check collision for each spike inside spike_group and a player.
And, why don't you use arcade collision like this:
// inside update function
physics.arcade.collide(player, spike_group , overlap_spikes, this);
// out of update function
function overlap_spikes()
{
console.log("touch spike");
}
Note: These examples does not use 'this' keyword.

Collision detection is not returning inside limits

Full code here
I am trying to setup the functions to detect collisions and for now just log to the console. This is the section for checkCollision function;
Player.prototype.update = function(dt) {
checkCollision(this.leftLimit, this.rightLimit);
this.leftLimit = this.x - 40.5;
this.rightLimit = this.x + 40.5;
}
function checkCollision(playerl,playerr) {
for (var i = 0; i < 5; i++) {
var thisEnemy = allEnemies[i];
if (thisEnemy.leftlimit > playerl && thisEnemy.rightLimit < playerr) {console.log("1")}
else {console.log('else')}
}
}
Question
The character is never registering as colliding with the enemy, why is this not working?
Testing/Debugging
I know this function is working as consoles logging else, I've also put logging in other locations and when in the Enemy.prototype.update function, console was showing values like 202.000000093, since the for..else function is using < or >, not absolute values, that should be fine, but still nothing is matching inside the player left and right limits. I also tried changing the Enemy limits to be smaller, +/- 40.5, incase the enemy was too wide to fit inside the player limits.
player.leftLimit and player.rightLimit are undefined when checkCollision method is first running
I added a better if statement to check if there's a collision;
if (
thisEnemy.leftLimit < player.rightLimit &&
thisEnemy.rightLimit > player.leftLimit &&
thisEnemy.upperLimit > player.lowerLimit &&
thisEnemy.lowerLimit < player.upperLimit) {
console.log("collision");
}

How can I load only one model at a time in three.js?

I am working on a program that adds items to a three.js scene, positioning them at coordinates based on how many items already exist in the scene. Currently, the problem I'm running into is that when the user chooses a "Work at Height", two people are added to the scene. These are two separate function calls, but when the scene finishes, both people are at the same coordinates. This also happens when the user clicks to add multiple people and does not wait for each to load.
Is there a way I can force my loading function to wait for the first object to finish loading so that it loads only one model at a time?
This mention of the LoadingManager got me thinking, and tried I to use that by saving the number of files loaded and total as variables, then comparing them in a while loop, but, as expected, that just slowed the scripts down so much that my browser wanted me to stop them.
// adds objects to the basket with the appropriate rotation
function createObjectBasket(filePath, scale, position, name, type) {
// Load in the object and add it to the scene.
var loader = new THREE.ObjectLoader(manager);
/*while (gl_itemsLoaded < gl_itemsTotal) {
}*/
loader.load( filePath, function(object, materials){
// rotate
object.rotation.x = - ((Math.PI / 2) - (Math.PI / 18));
object.rotation.y = - (Math.PI - (Math.PI / 5));
object.rotation.z = 0; //- (Math.PI / 120);
// scale
object.scale.set(scale, scale, scale);
// translate
object.position.set(position.x, position.y, position.z);
// set name for easy access
object.name = name;
// add to scene
scene.add(object);
if (type == "basket") {
// add to object array for easy access
basketContents["basket"].push(object);
}
else if (type == "person") {
// add to object array for easy access
basketContents["people"].push(object);
}
else if (type == "tool") {
// add to object array for easy access
basketContents["tools"].push(object);
}
else if (type == "attachment") {
// add to object array for easy access
basketContents["attachments"].push(object);
}
});
}
Where I'm having the issue, this code is called (indirectly) via the following two lines:
objectAddRemove("person", "CWM_A");
objectAddRemove("person", "CWM_B");
Which then execute this function that calls createObjectBasket:
// add/remove objects to/from basket on click
function objectAddRemove(type, objectName) {
// if adding items
if (addRemove == "add") {
// determine if there is still room to add more objects
var room = true;
// can have <= 3 people total, so if there are 3, can't add more
if ((type == "person") && basketContents["people"].length >= 3) {
room = false;
return;
}
// no current restrictions on tools
/*else if ((type == "tool")) {
}*/
// can only have 1 of each attachment
else if ((type == "attachment") && (object[objectName]["contentsCount"] > 0)) {
room = false;
return;
}
if (room == true) {
// if it's a person
if (type == "person") {
// if it's a man
if (objectName.indexOf("M") >= 0) {
// add model
createObjectBasket(("models/" + objectName + ".json"), 1.5, personCoordsMan[basketContents["people"].length + 1], objectName, "person");
}
// if it's a woman
else {
// add model
createObjectBasket(("models/" + objectName + ".json"), 1.5, personCoordsWoman[basketContents["people"].length + 1], objectName, "person");
}
}
// if it's a tool
else if (type == "tool") {
/*createObjectBasket(("models/" + objectName + ".json"), 1.5, toolCoords[basketContents["tools"].length + 1], objectName, "tool");*/
createObjectBasket(("models/" + objectName + ".json"), 1.5, toolCoords, objectName, "tool");
}
// if it's an attachment
else if (type == "attachment") {
createObjectBasket(("models/" + objectName + ".json"), .04, attachmentCoords[objectName], objectName, "attachment");
}
// increase count
object[objectName]["contentsCount"] += 1;
console.log(objectName);
$('#' + objectName).children('.status').children('.checkMark').show();
}
}
// if removing items
else if (addRemove == "remove") {
// remove objects from arrays
if (type == "person") {
removeObjectArray("people", objectName);
// if person is found (and removed), rearrange all people to accommodate
if (itemFound == true) {
for (i = 0; i < basketContents["people"].length; ++i) {
if (basketContents["people"][i].name.indexOf("M") >= 0) {
basketContents["people"][i].position.set(personCoordsMan[i+1].x, personCoordsMan[i+1].y, personCoordsMan[i+1].z);
}
else {
basketContents["people"][i].position.set(personCoordsWoman[i+1].x, personCoordsWoman[i+1].y, personCoordsWoman[i+1].z);
}
}
}
}
else if (type == "tool") {
removeObjectArray("tools", objectName);
// if tool is found (and removed), rearrange all tools to accommodate
/*if (itemFound == true) {
}*/
}
else if (type == "attachment") {
removeObjectArray("attachments", objectName);
}
// if all objects of that id have been removed, hide remove x mark
if (object[objectName]["contentsCount"] <= 0) {
$('#' + objectName).children('.status').children('.xMark').hide();
}
// if, after removing, person/object count is now 0, no remaining items can be removed, so switch to add
if ((steps[currentStep] == "people") && (basketContents["people"].length <= 0)) {
addItems();
}
else if ((steps[currentStep] == "objects") && ((basketContents["tools"].length + basketContents["attachments"].length) <= 0)) {
addItems();
}
}
// if no remaining items can be removed on this page
else {
addItems();
}
}
objectAddRemove is also called whenever a person clicks on an image representing the desired object, so I need a way to wait for models from previous clicks to load, also (in addition to models loaded automatically through the code).
To test/view further code, you can visit this link. Select a "Work at Height", weight unit, then skip to "Add Operators". It will show that two people are in the basket (checked) but only one is visible in the basket. If you click "Remove Items" then the visible person to remove the visible person, the hidden one will show.
Thank you so much!!!
Loading external data almost always happens asynchronously and therefore making sure one model is loaded after another requires handling the onLoad events.
I'm not sure if LoadingManager supports loading models one after another.
But you can implement a simple loading queue yourself. Something in the line of:
var myQueue = ['model1.dae', 'model2.dae'];
function loadFromQueue()
{
if (myQueue.length == 0) return;
// Takes the first name from array and remove it from the array
var name = myQueue.shift();
// Call the loader and provide an onLoad event handler
loader.load(name, function(model){
// Do what you need to do with the model,
// usually it's scene.add(model) and some transformations.
// Call the next item in the queue
loadFromQueue();
});
}
Now this is a very crude queue and there are better ways to do it. But I'm using it the simplest demonstration how you can use the onLoad event handler to load models one after another.
I presume the next hurdle you'll hit will be how to pass some values to the event handler. Come back again then!
Thanks to the suggestion from #Matey, I ended up just using timeouts to prevent the objects from displaying on top of one another. I was hoping to avoid that, since the amount of time it takes can vary by model and environment, but that was the only thing that worked for the clicks. The load queue was also still loading the models on top of one another, and I decided since I was already risking the timeout, it would be easier to implement that than to fix it.
I added timeouts to my click event using the accepted answer here, and then just added a timeout to the second person I was adding:
objectAddRemove("person", "CWM_A");
// add second person on timeout so people don't appear on top of each other
setTimeout(function() {
objectAddRemove("person", "CWM_B");
},
That was the only place that automatically added objects needed some delay, so hopefully that does it. I'm just a little concerned with how it will function across different machines.
I actually ended up getting around this by taking a different approach - I needed variables to keep track of the objects, so I checked whether objects had been loaded based on the variable values (which were set quickly) rather than the number of three.js objects (which took much longer to load). This worked much, much better and is more reliable across systems than using a timeout.

Prevent touching corners (JS Game)

How can I prevent this map generator from creating touching corners like this:
-X
X-
Or
X-
-X
Here is a simplified example of the generator: http://jsfiddle.net/fDv9C/2/
Your question answers itself, almost.
Here's the fiddle: http://jsfiddle.net/qBJVY/
if (!!grid[y][x] && !!grid[y+1][x+1] && !grid[y+1][x] && !grid[y][x+1]) {
good=false;
grid[y+1][x]=2;
}
It simply checks for the combinations you do not want and patches them up. It always adds a grid point so as not to disconnect any parts of the map.
This in turn may lead to another situation where the issue may occur, but if it changed anything (that is, if it found a problem), it will simply check again. This can be optimized, for instance by recursively adjusting whatever was changed, but usually it only needs 1 or 2 passes. There's a limiter on there to not allow more than 100 passes, just in case there is some unforeseen circumstance in which it cannot fix it (I can't think of such a situation, though :) ).
Because of the way that you are creating board it's very difficulty to do this checking during generation. I create simple function that check board after. It's using flood algorithm. Here is the fiddle http://jsfiddle.net/jzTEX/8/ (blue background is original map, red background is map after checking)
Basically we create second array grid2. After filling grid we run recursively floodV function
function floodV(x,y) {
var shiftArray = [[0,1],[0,-1],[1,0],[-1,0]];
grid2[y][x]=1;
for(var k=0;k<4;k++) {
var x1=x+shiftArray[k][0];
var y1=y+shiftArray[k][1];
if(grid[y1][x1] == 1 && grid2[y1][x1] == 0 && checkV(x1,y1)) {
grid2[y1][x1] = 1;
floodV(x1,y1);
}
}
}
with the check function
function checkV(x,y) {
var checkVarr = [[-1,-1], [-1,1], [1,1], [1,-1]];
for(var k=0;k<4;k++) {
if(grid[y+checkVarr[k][0]][x+checkVarr[k][1]] == 1 && grid[y+checkVarr[k][0]][x] == 0 && grid[y][x+checkVarr[k][1]] == 0 && grid2[y+checkVarr[k][0]][x+checkVarr[k][1]] == 1)
return false;
}
return true;
}
This isn't perfect because we can sometimes throw away big parts of the map but if we try to start adding new elements we have to check whole map again (in worths case).
This is what I did: http://jsfiddle.net/fDv9C/13/
Where's the magic happening? Scroll down to lines 53 through 58:
var bottom = y_next + 1;
var left = x_next - 1;
var right = x_next + 1;
var top = y_next - 1;
if (grid[top][left] || grid[top][right] ||
grid[bottom][left] || grid[bottom][right]) continue;
In short your touching corner points can only occur at the computed next position. Hence if any one of the four corner neighbors of the next position exists, you must compute another next position.
You may even decrement the counter i when this happens to get as many paths as possible (although it doesn't really make a big difference):
var bottom = y_next + 1;
var left = x_next - 1;
var right = x_next + 1;
var top = y_next - 1;
if (grid[top][left] || grid[top][right] ||
grid[bottom][left] || grid[bottom][right]) {
i--;
continue;
}
See the demo here: http://jsfiddle.net/fDv9C/12/
Edit: I couldn't resist. I had to create an automatic map generator so that I needn't keep clicking run: http://jsfiddle.net/fDv9C/14/

Categories