Javascript Reset a function after collision right - javascript

I'm trying to reset a bar if my player walks on a tile (water1, water2, water3 variables). When the player collides with one of these three tiles, the bar resets. If my player didn't walk on the tiles and the bar is on 100, you get a Game Over screen.
It works so far, but the problem is, when the player collides with the water tile, the bar resets to start, but the counter to the Game Over screen didn't reset.
For example: If I reset the bar when it's at 40%, it goes back to 0, but after the bar goes another 60% I get the Game Over Screen, not after 100% like I want.
var water1 = {x: 352, y: 64, width: 32, height: 32}
var water2 = {x: 352, y: 96, width: 32, height: 32}
var water3 = {x: 384, y: 64, width: 32, height: 32}
function thirst() {
var elem2 = document.getElementById("durst");
var width = 1;
var id2 = setInterval(frame, 1000);
function frame() {
if (player.xPos < water1.x + water1.width &&
player.xPos + player.width > water1.x &&
player.yPos < water1.y + water1.height &&
player.height + player.yPos > water1.y) {
thirst();
return;
} if (player.xPos < water2.x + water2.width &&
player.xPos + player.width > water2.x &&
player.yPos < water2.y + water2.height &&
player.height + player.yPos > water2.y) {
thirst();
return;
} if (player.xPos < water3.x + water3.width &&
player.xPos + player.width > water3.x &&
player.yPos < water3.y + water3.height &&
player.height + player.yPos > water3.y) {
thirst();
return;
} if (width >= 100) {
clearInterval(id2);
} else {
width++;
elem2.style.width = width + '%';
} if(width == 100) {
location.href = '666_GameOver.html';
}
}
}
CSS
#balkenDurst {
width: 5%;
background-color: #0000ff;
z-index: 4;
position: absolute;
margin-left: 8% ;
}
#durst {
width: 0%;
height: 30px;
background-color: #ffffff;
z-index: 4;
}
HTML
<div id="balkenDurst">
<div id="durst"></div>
</div>
Maybe you have an idea what's wrong.
I know I could write it with less code, but I'm learning and I'm really glad it works so far.

Programming with objects
Okay, i see your close to your target with the collision detection and the methods you use.
There are some issues with the code, and thats for you to fix.
But i got some pointers for you.
Water tiles
In your code:
if (player.xPos < water1.x + water1.width &&
player.xPos + player.width > water1.x &&
player.yPos < water1.y + water1.height &&
player.height + player.yPos > water1.y) {
thirst();
return;
}
This is repeated multiple times. Try to avoid repeating the same code over and over.
You can easily change it to become a function you can call multiple times.
Here is an example:
function hit( tile) {
//Hits on x axis
if(player.xPos < tile.x + tile.width && player.xPos + player.width > tile.x) {
//Hits on y axis
if (player.yPos < tile.y + tile.height && player.yPos + player.height > tile.y) {
return true;
}
}
return false;
}
Note: Is it not easier to read the code now?
Now since we made it into a fuction it can take in any tile!
Like say water tiles.
//Defines our water tiles in an array. (Easier to use)
var waterTiles = [
{x: 352, y: 64, width: 32, height: 32},
{x: 352, y: 96, width: 32, height: 32},
{x: 384, y: 64, width: 32, height: 32},
];
//Checks if any of the water tiles hit
function checkHits() {
for (var i = 0; i < waterTiles.length; i++) {
//Use our method above.
var check = hit ( waterTiles[i] );
if (check) {
//Code to check if it hits.
}
}
}
This is nice and all, but you did not answer my question.
Okay so going to the thirst logic of your game.
How you are setting with with of the thirst bar is not shown in your code above.
But since your thirst bar is not reseting probably it sounds like a variable not being reset correctly.
If you have a var thirst defined and a var width of the bar. You need to change both.
So in your thirst method set the thirst level: player.thirst = 60; (example).
Then in your frame() method (usualy called "update" in game loops) get this thirt something like: var width = player.thirst
When you decrease it (each frame?) Just set the variable again: player.thirst = player.thirst - 1;

Related

How do I test if 2 canvas images are less than x number of pixels away on JS canvas?

I am making a game where npcs are everwhere, and I want to interact with them. So put an Image of an exclamation point above their head, which the math worked out okay for positions. But testing the range between the player and the NPC was my trouble. I will shorten the code down as there are more things than just this, also not including the movement. If the players X or Y is in range of let's say 20 canvas pixels, than show the mark. Otherwise, don't.
const canvas = document.querySelector("canvas")
const context = canvas.getContext("2d")
NPCs = [
{
"name": "tim",
"sprite": {
"up": null, // too lazy for that art
"down": null, // down too
"left": "images/tim-left.svg",
"right": "images/tim-right.svg"
},
"x": 50,
"y" 50,
"onInteract": function(){}, // i dont wanna recreate it
}
]
function gameLoop() {
// once again, no movement code shown
NPCs.forEach(npc=>{
const NPCimg = new Image()
NPCimg = npc.sprite.right
context.drawImage(NPCimg,npc.x,npc.y,canvas.width/10,canvas.width/10) // small, i know
if (true/*weird cody statement*/) {
context.drawImage(/*no one cares*/)
}
})
}
setInerval(gameLoop,1000/60) // 60 is a fixed number for now
/*Not really any CSS but margin auto*/
<canvas width="100" height="75"></canvas>
You can use a standard rectangle collision formula and add whatever distance you want, (20) here.
function collision(player, obj) {
if (
player.x + player.w + 20 < obj.x ||
player.x > obj.x + obj.w + 20 ||
player.y + player.h + 20 < obj.y ||
player.y > obj.y + obj.h + 20
) {
return;
}
//do something or return true
}
You can also use Pythagorean Theorem for distance.
function distance(player, npc) {
//center of rectangle
let pX = player.x + player.w/2
let pY = player.y + player.h/2
let npcX = npc.x + npc.w/2
let npcY = npc.y + npc.h/2
return Math.hypot(pX - npcX, pY - npcY)
}
Then just call it where you need it
if (distance(player, npc) <= (player.w + npc.w + 20) || distance(player, npc) <= (player.h + npc.h + 20)) {
//do something
}

How to get an image to move up and down with javascript

Using html and javascript, i create variables in js to corrospond to the buttons i made in html, they are to make an image move left, right, up, and down.
the image move left and right just fine but i can't seem to get it to move up or down. i thought it should work similary to moving it left and right but it doesnt seem to work.
here is the code i have
leftButton.addEventListener("click", moveLeft);
rightButton.addEventListener("click", moveRight);
downButton.addEventListener("click", moveDown);
rocketPicture.style.position = "relative";
rocketPicture.style.left = '0px';
function moveLeft(){
rocketPicture.style.left = parseInt(rocketPicture.style.left ) - 10 + 'px';
}
function moveRight(){
rocketPicture.style.left = parseInt(rocketPicture.style.left ) + 10 + 'px';
}
function moveDown(){
rocketPicture.style.top = parseInt(rocketPicture.style.top) + 10 + 'px';
You have assigned an initial value - 0 - for style.left, but not for style.top, hence checking its value gives undefined.
Now, parseInt(undefined) returns NaN, and doing anything mathematical with NaN just puts more NaN in this world.
Solution: assign an initial value for style.top in the same way you did for style.left.
As a sidenote, it might be worthwhile to unify all those move functions in the same step func, like this:
function move(dir, delta) {
const prev = parseInt( rocketPicture.style[dir] );
// assert !Number.isNaN(prev)
rocketPicture.style[dir] = prev + delta + 'px';
}
function moveUp() { move('top', -10) }
function moveDown() { move('top', 10) }
function moveLeft() { move('left', -10) }
function moveRight() { move('left', 10) }
Why not use Element#getBoundingClientRect ? It provides information regarding your element including its top and left position without having to convert the values with parseInt.
It looks something like this:
{
"x": 50,
"y": 100,
"width": 124,
"height": 119.19999694824219,
"top": 100,
"right": 174,
"bottom": 219.1999969482422,
"left": 50
}
Working solution:
const box = document.getElementById("item");
function vertical(value){
const {top} = box.getBoundingClientRect();
box.style.top = `${top + value}px`;
}
function horizontal(value){
const {left} = box.getBoundingClientRect();
box.style.left = `${left + value}px`;
}
#item {
padding: 50px;
position: absolute;
background-color: purple;
color: white;
left: 50px;
top: 100px;
}
<div id="item">
box
</div>
<button onclick="vertical(-10)">Up</button>
<button onclick="vertical(10)">Down</button>
<button onclick="horizontal(-10)">Left</button>
<button onclick="horizontal(10)">Right</button>

how to make animation with javascript

I have a task to make animation with JavaScript.
Basically I have two squares (red and yellow) and a two buttons (button 1 and button 2).
When I click on button1 the red square goes from the (top-left corner) to the (bottom-right corner).
I need to make another button (button2) such that when I click on it I need the red square to go back to the beginning.
I need it to do the opposite move (moving from the bottom-right corner to the top-left corner).
What changes should I do in the second function?
here is the code
function myMove1() {
var elem = document.getElementById("animate");
var pos = 0;
var id = setInterval(frame, 5);
function frame() {
if (pos == 350) {
clearInterval(id);
} else {
pos++;
elem.style.top = pos + 'px';
elem.style.left = pos + 'px';
}
}
}
function myMove2() {
}
#container {
width: 400px;
height: 400px;
position: relative;
background: yellow;
}
#animate {
width: 50px;
height: 50px;
position: absolute;
background-color: red;
}
<p>
<button onclick="myMove1()">button 1</button>
<button onclick="myMove2()">button 2</button>
</p>
<div id="container">
<div id="animate"></div>
</div>
I'm going to assume the teacher is trying to teach basic javascript, and tell you how I'd solve this with the parts you've provided.
That said, your commenters are correct, requestAnimationFrame is the right tool here. Also, the 5 ms delay on your interval is really short (125fps). If you made this number, I'd suggest changing it to 16, which is roughly 60fps.
// We want each function to be able to see these vars.
var pos = 0;
// Either -1, 0, or 1, depending on if were moving forward, backwards or
// stopped.
var direction = 0;
// This var now serves dual purpose, either its a number which is the
// interval id or its falsy, which we can use to understand the animation
// has stopped.
var id = null;
// Doing this here, will save the browser from having to redo this step on
// each frame.
var elem = document.getElementById("animate");
// Render the elem to the correct starting location.
elem.style.top = pos + 'px';
elem.style.left = pos + 'px';
// A single animation function.
function frame() {
// Assume we are heading for 350.
var goal = 350
if (direction < 0) {
// unless the goal is -1, when the goal is zero.
goal = 0
}
if (pos != goal) {
pos += direction;
elem.style.top = pos + 'px';
elem.style.left = pos + 'px';
} else {
// Reset all the shared vars.
direction = 0;
clearInterval(id);
id = null;
}
}
function myMove1() {
if (id) {
clearInterval(id)
}
direction = 1;
id = setInterval(frame, 5);
}
function myMove2() {
if (id) {
clearInterval(id)
}
direction = -1;
id = setInterval(frame, 5);
}
#animate {
position: absolute;
width: 10px;
height: 10px;
background-color: red;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<p>
<button onclick="myMove1()">button 1</button>
<button onclick="myMove2()">button 2</button>
</p>
<div id="container">
<div id="animate"></div>
</div>
</body>
</html>
What you're asking is straightforward: take the function you already wrote and change the increment direction on pos. The only difference is you'll need to keep track of x and y coordinates separately since they move in opposite directions. I used this object initialized to the start position of the box:
pos = {x: 350, y: 0};
function myMove1() {
var elem = document.getElementById("animate");
var pos = 0;
var id = setInterval(frame, 5);
function frame() {
if (pos == 350) {
clearInterval(id);
} else {
pos++;
elem.style.top = pos + 'px';
elem.style.left = pos + 'px';
}
}
}
function myMove2() {
var elem = document.getElementById("animate");
var pos = {x: 350, y: 0};
var id = setInterval(frame, 5);
function frame() {
if (pos.y >= 350 || pos.x <= 0) {
clearInterval(id);
} else {
pos.x--;
pos.y++;
elem.style.top = pos.y + 'px';
elem.style.left = pos.x + 'px';
}
}
}
#container {
width: 400px;
height: 400px;
position: relative;
background: yellow;
}
#animate {
width: 50px;
height: 50px;
position: absolute;
background-color: red;
}
<p>
<button onclick="myMove1()">button 1</button>
<button onclick="myMove2()">button 2</button>
</p>
<div id="container">
<div id="animate"></div>
</div>
However, these functions aren't reusable without parameters; this code is WET (wrote everything twice). The animation is brittle because each click creates a new timeout (you can spam the buttons and watch it crumble). Entities in the animation have no state. If you want to change the position or add another box, you have to we-write and duplicate all of your code again.
With that in mind, here's a sketch to illustrate a somewhat improved version as food for thought. The functions and objects are more general and don't need to be re-written for new movements you decide to add. The Box class keeps track of entity state over time. requestAnimationFrame() is used to update and draw all entities on the screen at once, avoiding the many problems with setTimeout.
const lerp = (v0, v1, t) => (1 - t) * v0 + t * v1;
const dist = (a, b) => ((a.x - b.x) ** 2 + (a.y - b.y) ** 2) ** 0.5;
class Box {
constructor(elem, pos, size, color, speed) {
this.elem = elem;
this.speed = speed;
this.from = this.to = this.pos = pos;
this.t = 0;
this.elem.style.position = "absolute";
this.elem.style.background = color;
this.elem.style.height = `${size}px`;
this.elem.style.width = `${size}px`;
this.elem.style.top = `${this.pos.y}px`;
this.elem.style.left = `${this.pos.x}px`;
}
move(to) {
this.from = {x: this.pos.x, y: this.pos.y};
this.to = {x: to.x, y: to.y};
this.t = 0;
}
update() {
if (dist(this.pos, this.to) > 1) {
this.pos.x = lerp(this.from.x, this.to.x, this.t);
this.pos.y = lerp(this.from.y, this.to.y, this.t);
this.elem.style.top = `${this.pos.y}px`;
this.elem.style.left = `${this.pos.x}px`;
this.t += this.speed;
}
}
}
const data = [
{color: "red", pos: {x: 0, y: 0}, size: 10},
{color: "yellow", pos: {x: 350, y: 0}, size: 10},
];
const elems = document.getElementsByClassName("box");
const boxes = [];
for (let i = 0; i < elems.length; i++) {
boxes.push(new Box(elems[i], data[i].pos, data[i].size, data[i].color, 0.01));
}
function myMove1() {
boxes[0].move({x: 350, y: 350});
boxes[1].move({x: 0, y: 350});
}
function myMove2() {
boxes[0].move({x: 0, y: 0});
boxes[1].move({x: 350, y: 0});
}
(function render() {
boxes.forEach(e => e.update());
requestAnimationFrame(render);
})();
<p>
<button onclick="myMove1()">button 1</button>
<button onclick="myMove2()">button 2</button>
</p>
<div id="container">
<div class="box"></div>
<div class="box"></div>
</div>
Lastly, consider using CSS animations, JS canvas or an animation framework to do this sort of task; these tools will abstract away a lot of the math and state representation that animations involve.

Fabric.js: Properties of group members not updating after scaling

I'm using Fabric.js to create a group of two rectangles that are lying next to each other. I want the left one to change it's color when I'm moving my mouse over it. Therefore I check if the position of the mouse-cursor is within the the boundary of the rectangle or not.
This works fine until I scale the group...
I made a few tests and found out that the properties of the group members don't change. So while the rectangles become larger they still show their old sizes.
This is my code:
farbicCanvas = new fabric.Canvas('c');
farbicCanvas.on('object:scaling', function(e)
{
//Show the sizes
console.log("group width: " + e.target.getWidth());
console.log("rect1 width: " + e.target.item(0).getWidth());
console.log("rect2 width: " + e.target.item(1).getWidth());
});
farbicCanvas.on('mouse:move', function(e)
{
if(e.target !== null)
{
point = new getMouseCoords(farbicCanvas, event);
//Check if cursor is within the boundary of rect2
if(point.posX >= e.target.item(1).getLeft() + e.target.getLeft() + e.target.getWidth()/2
&& point.posX <= e.target.item(1).getLeft() + e.target.getLeft() + e.target.getWidth()/2 + e.target.item(1).getWidth()
&& point.posY >= e.target.getTop()
&& point.posY <= e.target.getTop() + e.target.item(1).getHeight())
{
e.target.item(1).set({ fill: 'rgb(0,0,255)' });
farbicCanvas.renderAll();
}
else
{
farbicCanvas.getObjects()[0].item(1).set({ fill: 'rgb(0,255,0)' });
farbicCanvas.renderAll();
}
}
else
{
farbicCanvas.getObjects()[0].item(1).set({ fill: 'rgb(0,255,0)' });
farbicCanvas.renderAll();
}
});
var rect1 = new fabric.Rect(
{
left: 100,
top: 100,
width: 100,
height: 75,
fill: 'rgb(255,0,0)',
opacity: 0.5
});
var rect2 = new fabric.Rect(
{
left: rect1.getLeft() + rect1.getWidth(),
top: rect1.getTop(),
width: 100,
height: 75,
fill: 'rgb(0,255,0)',
opacity: 0.5
});
group = new fabric.Group([rect1, rect2]);
farbicCanvas.add(group);
function getMouseCoords(canvas, event)
{
var pointer = canvas.getPointer(event.e);
this.posX = pointer.x;
this.posY = pointer.y;
}
I've also made a Fiddle: https://jsfiddle.net/werschon/0uduqfpe/3/
If I make the group larger, my 'mouse-detection' doesn't work anymore, because the properties like left, top or width are not updating.
What do I need to do, to update the properties? Or is there another way of detecting if the mouse-cursor is above the rectangle?
Thanks.
I found a solution (Thanks to John Morgan): I have to calculate the properties of the group members by myself. With getScaleX() and getScaleY() I can get the scale-factors of the group. Then I need to multiply the properties of the group members with the appropriate scale-factor. As result I get the new values of the group member. In my example I only needed getScaleX() to calculate the width of rect2 so it looks like this: e.target.item(1).getWidth() * e.target.getScaleX().
This is the new code:
farbicCanvas = new fabric.Canvas('c');
farbicCanvas.on('object:scaling', function(e)
{
console.log("group width: " + e.target.getWidth());
console.log("rect1 width: " + e.target.item(0).getWidth() * e.target.getScaleX());
console.log("rect2 width: " + e.target.item(1).getWidth() * e.target.getScaleX());
});
farbicCanvas.on('mouse:move', function(e)
{
if(e.target !== null)
{
point = new getMouseCoords(farbicCanvas, event);
if(point.posX >= e.target.getLeft() + e.target.getWidth() - e.target.item(1).getWidth() * e.target.getScaleX()
&& point.posX <= e.target.getLeft() + e.target.getWidth()
&& point.posY >= e.target.getTop()
&& point.posY <= e.target.getTop() + e.target.getHeight())
{
e.target.item(1).set({ fill: 'rgb(0,0,255)' });
farbicCanvas.renderAll();
}
else
{
e.target.item(1).set({ fill: 'rgb(0,255,0)' });
farbicCanvas.renderAll();
}
}
else
{
farbicCanvas.getObjects()[0].item(1).set({ fill: 'rgb(0,255,0)' });
farbicCanvas.renderAll();
}
});
farbicCanvas.on('mouse:out', function(e)
{
farbicCanvas.getObjects()[0].item(1).set({ fill: 'rgb(0,255,0)' });
farbicCanvas.renderAll();
});
var rect1 = new fabric.Rect(
{
left: 100,
top: 100,
width: 100,
height: 75,
fill: 'rgb(255,0,0)',
opacity: 0.5
});
var rect2 = new fabric.Rect(
{
left: rect1.getLeft() + rect1.getWidth(),
top: rect1.getTop(),
width: 100,
height: 75,
fill: 'rgb(0,255,0)',
opacity: 0.5
});
group = new fabric.Group([rect1, rect2]);
farbicCanvas.add(group);
function getMouseCoords(canvas, event)
{
var pointer = canvas.getPointer(event.e);
this.posX = pointer.x;
this.posY = pointer.y;
}
I've also created a new Fiddle. So now my mouse-detection works even after scaling

javascript - dynamic variable name of setInterval()

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div onmouseover="animatedStickers($(this), 17, 5, 4, 2, 3)" style="float: left; background-image: url('http://googledrive.com/host/0B-UH4_eX_YisdlJ4cU9qZ1lwM3c/Tuzki1.png'); background-size: 380px 304px; cursor: pointer; height: 64px; width: 64px; background-position: -6px -6px; color: transparent">Tuzki</div>
<div onmouseover="animatedStickers($(this), 16, 4, 4, 4, 3)" style="float: left; background-image: url('http://googledrive.com/host/0B-UH4_eX_YisdlJ4cU9qZ1lwM3c/Tuzki2.png'); background-size: 304px 304px; cursor: pointer; height: 64px; width: 64px; background-position: -6px -6px; color: transparent">Tuzki</div>
<div onmouseover="animatedStickers($(this), 22, 5, 5, 2, 3)" style="float: left; background-image: url('http://googledrive.com/host/0B-UH4_eX_YisdlJ4cU9qZ1lwM3c/Tuzki3.png'); background-size: 380px 380px; cursor: pointer; height: 64px; width: 64px; background-position: -6px -6px; color: transparent">Tuzki</div>
<script>
var loop = 1;
var stickerInterval = function (element, x, y, last) {
var pos = $(element).css('backgroundPosition').split(' ');
var xPos = parseInt(pos[0].split('px')[0]) - 6 - 6 - 64;
var yPos = parseInt(pos[1].split('px')[0]);
var maxX = ((-6 - 6 - 64) * (x - 1)) - 6;
var maxY = ((-6 - 6 - 64) * last) - 6;
if (loop == y && xPos == maxY) {
// end 1 turn
loop = 1;
xPos = -6;
yPos = -6;
} else if (loop < y && xPos < maxX) {
xPos = -6;
yPos = yPos -6 -6 -64;
loop++;
}
$(element).css('background-position', xPos + 'px ' + yPos + 'px');
};
var animatedStickers = function (element, total, x, y, last, times) {
$(element).removeAttr('onmouseover');
var interval = setInterval(function () { stickerInterval(element, x, y, last) }, 175);
setTimeout(function () {
clearInterval(interval);
loop = 1;
$(element).css('background-position', '-6px -6px');
$(element).attr('onmouseover', 'animatedStickers($(this), ' + total + ', ' + x + ', ' + y + ', ' + last + ', ' + times + ')')
}, 175 * total * times);
};
</script>
I wanna use multiple setInterval() and clear all of them NOT in a time.
<div id="div1" onmouseover="divOver($(this))"></div>
<div id="div2" onmouseover="divOver($(this))"></div>
<script>
var divOver = function (element) {
var id = $(element).attr('id'); // get id
//call setInterval() without the id
var interval = setInterval(function(){ /* do something... */ }, 500);
//clear interval after 1s
setTimeout(function(){ clearInterval(interval) }, 1000);
};
</script>
That code doesn't work fine if I mouseover 2 divs at the same time.
I think: The first I mouseover on div1, function divOver creates a variable name interval. After that (haven't cleared interval yet), I mouseover on div2, function divOver comtinues creating a new variable with the same name interval. So, the first interval can be overridden. Is it right?
To avoid that problem, I think about using setInterval() with id. Something's like this:
var id = $(element).attr('id');
//var interval_ + id = setInterval(function(){ /* do something... */ }, 500);
But that's not javascript syntax. Can you give me any idea to fix this problem?
To answer your question how to maintain a record of different intervals at the same time and being able to start and stop them outside the function scope.
You need to keep an associative arrays with the intervals, such that there can be many intervals at the same time.
<div id="div1" onmouseover="divOver($(this))"></div>
<div id="div2" onmouseover="divOver($(this))"></div>
<script>
var intervals = []
var divOver = function (element) {
var id = element.attr('id'); // get id
//call setInterval() with the id
intervals['i'+id] = setInterval(function(){ /* do something... */ }, 500);
//clear interval after 1s
setTimeout(function(){ clearInterval(intervals['i'+id]) }, 1000);
};
</script>
Though as already mentioned this does most likely not solve your real problem.

Categories

Resources