UPDATE: I wanted to let you know that I'd solved this problem, if not necessarily answered my own question. Rather than trying to target the element of the push array, I did this:
In
var ground=[], water=[], enemies=[], environment=[];
I added
tokens=[];
to the end.
Then I wrote a new function, updateTokens:
function updateTokens() {
for (var i = 0; i < tokens.length; i++) {
tokens[i].update();
tokens[i].draw();
if (player.minDist(tokens[i]) <= player.width - platformWidth / 2) {
gameOver();
}
}
if (tokens[0] && tokens[0].x < - platformWidth) {
tokens.splice(0, 1);
}
}
(The 'gameOver();' function is just for testing purposes).
Then I added 'spawnTokensSprites();' to the spawnSprites(); function.
Next, I wrote a new function to spawnTokensSprites:
function spawnTokensSprites() {
if (score > 0 && rand(0, 20) === 0 && platformHeight < 3 {
if (Math.random() > 0.5) {
tokens.push(new Sprite(
canvas.width + platformWidth % player.speed,
platformBase - platformHeight * platformSpacer - platformWidth,
'tokens'
));
}
}
}
Then I added 'updateTokens();' to the animate function, and finally, I added 'tokens=[]' to the 'startGame()' function.
So there it is. Not the answer to the original question, but a working solution to the problem I had. I hope this can help someone else, which is why I updated my post.
---------------------------------------------------------
I'm well out of my depth here. I've been following this tutorial to make an HTML5 canvas game. It's great, and I've learned loads, but I'm totally stuck.
I have a vector for sprites
function Sprite(x, y, type) {
this.x = x;
this.y = y;
this.width = platformWidth;
this.height = platformWidth;
this.type = type;
Vector.call(this, x, y, 0, 0);
this.update = function () {
this.dx = -player.speed;
this.advance();
};
this.draw = function () {
ctx.save();
ctx.translate(0.5, 0.5);
ctx.drawImage(assetLoader.imgs[this.type], this.x, this.y);
ctx.restore();
};
}
Sprite.prototype = Object.create(Vector.prototype);
from which all sprites inherit. To generate 'token' sprites, I use this function:
function spawnEnvironmentSprites() {
if (score > 0 && rand(0, 20) === 0 && platformHeight < 3) {
if (Math.random() > 0.5) {
environment.push(new Sprite(
canvas.width + platformWidth % player.speed,
platformBase - platformHeight * 2 * platformSpacer - platformWidth,
'tokens'
));
}
else if (platformLength > 2) {
environment.push(new Sprite(
canvas.width + platformWidth % player.speed,
platformBase - platformHeight * 1.5 * platformSpacer - platformWidth,
'tokens'
));
}
}
}
Which draws the sprites to the screen. What I cannot do is single these type of sprites out from the array for collision detection. I've tried to use an 'if' statement from the 'updateEnemies' function:
if (player.minDist(enemies[i]) <= player.width - platformWidth/2) {
gameOver();
}
}
within the 'updateEnvironment' function, where I substitute 'enemies' for 'environment', but because I don't know how to specify 'tokens' from 'environment', using this line results in no environment sprites being drawn to the canvas, including platforms. Earlier today, I tried including 'tokens' in 'enemySprites', which worked in so much as when the player collided, the gameOver() function ran, but I couldn't then figure out how to give 'tokens' different behaviour (ie. Score increase rather than gameOver). I should mention that the sprites are called this way because of the assetLoader function I've used.
So long story short, how can I collide with 'tokens' and have everything else behave as it should?
Thanks in advance.
EDIT: Sorry, I forgot to mention that the array for environment is declared in a variable that looks like this:
var ground = [], water = [], enemies = [], environment = [];
Thanks.
UPDATE: Ok, I still haven't sorted this out but I've made a change that I think will make things a bit easier for you to help me with(!)
I've removed 'tokens' from the 'spawnEnvironmentSprites()' function and added a condition to 'spawnEnemySprites' that pushes 'tokens'. This means they now have the same behaviour as my other 'enemies' i.e.. gameOver() is run upon collision detection. My question now is, how do I target 'tokens' in this line of code so that I can give that collision different behaviour? I'd like the 'token' sprite to disappear and the score to increase, but I don't know how to target just the 'tokens' part of the array. The line that deals with the collisions is:
if (player.minDist(enemies[i]) <= player.width - platformWidth/2) {
gameOver();
}
}
Can you help? Thanks in advance.
Related
I'm still pretty new to this, so I don't know how to create a collider. My end goal is to have a game like the chrome dinosaur game. Same principles, and all. My question is, though, how do I even make a collider. I will be using a .gif for the "dinosaur". I'd like to make it where if this collider were to touch another collider, the game stops and a "game over" is shown. I have tried to create a collider, but they just keep showing up underneath the screen where the game is shown. Ant tips, tricks, or advice? Thanks
Code is as follows:
let img; //background
var bgImg; //also the background
var x1 = 0;
var x2;
var scrollSpeed = 4; //how fast background is
let music; //for music
let catBus; //catbus
//collider variables
let tinyToto;
let tiniestToto;
let hin;
let totoWithBag;
let noFace;
let happySoot;
var mode; //determines whether the game has started
let gravity = 0.2; //jumping forces
let velocity = 0.1;
let upForce = 7;
let startY = 730; //where cat bus jumps from
let startX = 70;
let totoX = 900;
let totoY = 70;
let tinToX = 900;
let tinToY = 70;
var font1; //custom fonts
var font2;
p5.disableFriendlyErrors = true; //avoids errors
function preload() {
bgImg = loadImage("backgwound.png"); //importing background
music = loadSound("catbus theme song.mp3"); //importing music
font1 = loadFont("Big Font.TTF");
font2 = loadFont("Smaller Font.ttf");
//tinyToto.setCollider("rectangle",0,25,75,75)
}
function setup() {
createCanvas(1000, 1000); //canvas size
img = loadImage("backgwound.png"); //background in
x2 = width;
music.loop(); //loops the music
catBus = {
//coordinates for catbus
x: startX,
y: startY,
};
/*
tinyToto = {
x: totoX,
y: totoY,
}
tinTo = {
x : tinToX,
y: tinToY,
}
*/
catGif = createImg("catgif.gif"); //creates catbus
catGif.position(catBus.x, catBus.y); //creates position
catGif.size(270, 100); //creates how big
/*
tinyToto = createImg("TinyToto.gif")
tinyToto.position(tinyToto.x, tinyToto.y)
tinyToto.size(270,100)
tiniestTo = createImg("tiniest Toto.gif")
tiniestTo.position(tinToX.x, tinToY.y)
tiniestTo.size(270,100)
*/
mode = 0; //game start
textSize(50); //text size
}
function draw() {
let time = frameCount; //start background loop
image(img, 0 - time, 0);
image(bgImg, x1, 2, width, height);
image(bgImg, x2, 2, width, height);
x1 -= scrollSpeed;
x2 -= scrollSpeed;
if (x1 <= -width) {
x1 = width;
}
if (x2 <= -width) {
x2 = width;
} //end background loop
fill(128 + sin(frameCount * 0.05) * 128); //text colour
if (mode == 0) {
textSize(20);
textFont(font1);
text("press SPACE to start the game!", 240, 500); //what text to type
}
fill("white");
if (mode == 0) {
textSize(35);
textFont(font2);
text("CATBUS BIZZARE ADVENTURE", 90, 450); //what text to type
}
catBus.y = catBus.y + velocity; //code for jumping
velocity = velocity + gravity;
if (catBus.y > startY) {
velocity = 0;
catBus.y = startY;
}
catGif.position(catBus.x, catBus.y);
//setCollider("tinyToto")
}
function keyPressed() {
if (keyCode === 32 && velocity == 0) {
//spacebar code
mode = 1;
velocity += -upForce;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script>
well, this is how I would generally do that kind of thingy:
function draw(){
for(let i in objects) // objects would be cactuses or birds
if(objects[i].x > player.x &&
objects[i].x < player.x + player.width &&
objects[i].y > player.y &&
objects[i].y < player.y + player.height){
noLoop()
// maybe do something else here
} // you could also use: for(let object of objects)
}
or if you want to do class stuff:
let player = new Player()
class Entity {
hasCollided_pointRect(_x, _y, _width, _height){
if(this.x > _x &&
this.x < _x + _width &&
this.y > _y &&
this.y < _y + _height){
return true
}
}
}
class Cactus extends Entity {
update(){
if(hasCollided_pointRect(player.x, player.y, player.width, player.height))
lossEvent()
}
}
class Player {
// ...
}
function lossEvent(){
noLoop()
}
this is a pretty classy way to do it and for a small game you really don't need all of this
also MDN has a nice article on rect with rect & point with rect collisions,
point with point collision is just (x == x && y == y)
https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection
this is one of my recent loss "functions":
if(flag.health <= 0){
noLoop()
newSplashText("You lost!\nPress F5 to restart!", "center", "center", 1)
}
The way I handled game states in my Processing games was by making seperate classes for them. Then my main sketch's draw function looked something like
fun draw()
{
currentState.draw();
}
Each gamestate then acted as their own sketches (for example a menu screen, playing, game over, etc), and had a reference to the main sketch which created the states. They would then alter the main's currentState to, i.e., a new GameOverState() etc. where needed.
For now, don't worry about doing that too much if all you want a really simple gameoverscreen with an image and some text.
I would suggest a structure like this instead. Use this pseudocode in your main draw function:
fun draw()
{
if (gameOver)
{
// show game over screen
img(gameOver);
text("game over!");
// skip rest of the function
return;
}
// normal game code goes here
foo();
bar();
// update game over after this frame's game code completes
gameOver = checkGameOver();
}
Now you need a way of checking for a collision to determine the result of checkGameOver()
For the collision handling, check out Jeffrey Thompson's book/website on collision handling. It's an amazing resource, I highly recommend you check it out.
From the website I just linked, here's an excerpt from the website talking about handling collisions between 2d rectangles.
And here's a modified version of the collision handling function listed there (I updated the variable names to be a little more intuitive)
boolean rectRect(float rect1X, float rect1Y, float rect1Width, float rect1Height, float rect2X, float rect2Y, float rect2Width, float r2h)
{
// are the sides of one rectangle touching the other?
if (rect1X + rect1Width >= rect2X && // r1 right edge past r2 left
rect1X <= rect2X + rect2Width && // r1 left edge past r2 right
rect1Y + rect1Height >= rect2Y && // r1 top edge past r2 bottom
rect1Y <= rect2Y + r2h)
{ // r1 bottom edge past r2 top
return true;
}
return false;
You can use that function in your checkGameOver() function which would return a bool depending on whether your collision criteria are met.
For your game, you would loop over every obstacle in your game and check whether the dino and the obstacle overlap.
Pseudocode:
boolean checkGameOver()
{
foreach (Obstacle obstacle in obstacles)
{
if (rectRect(dino, obstacle))
{
return true;
}
}
return false;
}
Been following along with Dan Shiffmans videos, trying to brush up on my Object orientated programing using classes.
Ive wrote some code that generates bubbles with random diameters at random positions, using p5's noise function to give the bubbles some movement.
My intention is for the bubbles to pop (be removed from the array with splice()) every time a bubble reaches the edges of the canvas or when two or more bubbles intersect.
The code run as desired, but after a while it crashes throwing up the error "Uncaught TypeError: Cannot read property 'x' of undefined (sketch: line 15)"
Ive tried hacking around but no joy, if anyone could shed some light on why this error occurs, or general pointers on my approach i would be most grateful. Here's the code in question.
var balls = [];
function setup() {
createCanvas(400, 400);
}
function draw() {
background(220);
for (var i = 0; i < balls.length; i++) {
balls[i].showBall();
balls[i].moveBall();
for (var j = 0; j < balls.length; j++) {
if (balls[i].x < 0 + balls[i].r ||
balls[i].x > width - balls[i].r ||
balls[i].y < 0 + balls[i].r ||
balls[i].y > height - balls[i].r ||
balls[i].life >= 220 ||
i != j && balls[i].intersect(balls[j])) {
balls.splice(i, 1);
}
}
}
}
class Ball {
constructor(x, y, r) {
this.x = x;
this.y = y;
this.r = r;
this.t = 0.0;
this.t2 = 107.0;
this.life = 0;
}
showBall() {
noFill();
stroke(this.life);
strokeWeight(2);
ellipse(this.x, this.y, this.r * 2);
}
moveBall() {
this.x += map(noise(this.t), 0, 1, -1, 1) * 0.5;
this.y += map(noise(this.t2), 0, 1, -1, 1) * 0.5;
this.t += 0.02;
this.life += 0.15;
}
intersect(other) {
if (dist(this.x, this.y, other.x, other.y) < this.r + other.r) {
return true;
} else {
return false;
}
}
}
function randBubbleGen() {
let foo = floor(random(11));
console.log(foo);
if (foo >= 5) {
let b = new Ball(random(41, 359),
random(41, 359),
random(5, 40));
balls.push(b);
}
}
setInterval(randBubbleGen, 1000);
<script src="https://cdn.jsdelivr.net/npm/p5#1.3.1/lib/p5.js"></script>
Thanks!
P
The first part of the problem is a classic splice issue:
const arr = [..."abcde"];
for (let i = 0; i < arr.length; i++) {
if (i === 2) { // some arbitrary condition
arr.splice(i, 1);
}
else {
console.log(i, arr[i]);
}
}
What happened here? After splicing the element at index 2, "c", the length of arr becomes 4, yet i++ still happens, skipping an element "d" which is never printed or visited in the loop. The solution is to i-- after each splicing operation or iterate in reverse so that splice doesn't cause unvisited elements to be skipped.
As for your error, the problem is that your inner loop over all j splices out an element, then continues on, acting as if balls[i] wasn't just removed. Based on the above demonstration, we know that after balls.splice(i, 1), balls[i] becomes the next element after the original i for the rest of that iteration of the outer loop body. This is a bug because some collisions will be skipped for i+1 after the spliced element, but won't cause errors unless i happens to be the last element in the balls array. In that case, balls[i+1] is undefined and you can't access properties on undefined.
The solution is to break out of the inner j loop after splicing out an element. That's in addition to iterating in reverse or using i-- after each splice call to avoid skipping balls.
From a time complexity standpoint, splice is a poor choice because it's O(n). If you have n collisions in the balls array, you'll need to loop over it a factor of n times, causing a triply-nested loop running in your update code.
A better general approach is to create a new array of elements that survived a round, then resassign that new array to the old balls array after the frame. This involves some allocation overhead.
Other tips:
Use const instead of let wherever possible.
Use let instead of var for loop counters and mutable variables. Ideally, never use var or let, although p5 promotes mutability due to its window-attached functions.
Prefer forEach and for ... of loops to classical C-style for loops.
You can return dist(this.x, this.y, other.x, other.y) < this.r + other.r since it's already a boolean, no need for if bool return true else return false verbosity.
Keep rendering and position updating separate as much as possible. It probably doesn't matter much for this animation, but as things get more complex, it can be odd when something dies but still gets rendered for a frame as is the case here.
Move the collision detection and edge detection to external functions -- the if (balls[i].x < 0 + balls[i].r ... condition is difficult to read.
I have an object called Scene and it stores screen dimensions as well as an array of Shape objects, each of which have an x and y coordinate. I would like to scramble a Scene object and get a scrambledScene object so that I can compare the scrambledScene to the original. Here is the function I've come up with:
function scrambleScene(scene) {
var scrambledScene = {...scene};
for(var i = 0; i < scene.shapes.length; i++) {
scrambledScene.shapes[i].x = getRandInt(0, scene.width);
scrambledScene.shapes[i].y = getRandInt(0, scene.height);
}
return(scrambledScene);
}
The problem is when this function is run, it returns an object that is identical to the Scene object that was passed to it. Nothing was changed. Here are definitions of the functions I used. I am quite new to OOP so feel free to give pointers (unlike JS, why does var clone = object just return a pointer and not another object?!!?!?!!).
function getRandInt(min, max) {
return(Math.floor(Math.random() * (max - min + 1)) + 1);
}
function Shape(shapeX, shapeY, shapeSize, shapeType) {
this.x = shapeX;
this.y = shapeY;
this.size = shapeSize;
this.type = shapeType;
//circle uses arc() function, which takes the center of the circle as a reference
//circle sets the radius to this.size
//square uses rect() function, which take the upper left corner as a reference
//square sets the height and width of the square to this.size
if(this.type !== 'circle' && this.type !== 'square') {
console.error('Invalid shapeType: ', this.shapeType);
}
this.centerX = function() {
if(this.type === 'square') {
return(this.size/2 - this.x);
}
if(this.type === 'circle') {
return(this.x);
}
}
this.centerY = function() {
if(this.type === 'square') {
return(this.size/2 - this.y);
}
if(this.type === 'circle') {
return(this.y);
}
}
this.isInside = function(point_x, point_y) {
if(this.type === 'square') {
//canvas y increases as you go down
//if point_x is greater than x + size, then its far too right
//if point_x is less than x, then its far too left
//if point_y is greater than y + size, then its below
//if point_y is less than y, then its above
//if none of these are true then its inside
if(point_x <= x + size && point_x >= x && point_y <= y + size && point_y >= y) return(true);
}
if(this.type === 'circle') {
//checks distance from point to center of the circle
//if its greater than the radius of the circle then its outside
//if its less than or equal to the radius then its inside
const distance = Math.sqrt(Math.pow(point_x - x, 2) - Math.pow(point_y - y, 2));
if(this.distance >= size) return(true);
}
}
}
function Scene(numberOfShapes, sceneWidth, sceneHeight) {
this.shapes = [];
for(var i = 0; i < numberOfShapes; i++) {
var shapeType = Math.random() < 0.5 ? 'square' : 'circle';
this.shapes[i] = new Shape(getRandInt(0, sceneWidth), getRandInt(0, sceneHeight), 100, shapeType)
}
this.width = sceneWidth;
this.height = sceneHeight;
}
So the Object was being changed, it was just that the clone of that Object was also being changed to be the exact same.
In the scrambleScene function I tried to create is what's called a shallow clone of the Scene object passed. What I really needed was a deep clone. A shallow clone is like if you copied a folder with files inside on Windows, but instead of actually copying the files inside, the files were replaced with shortcuts to those files. If you edit the original file and open the shortcut, you will see the edit because its just a shortcut, not a new file. A deep clone is similar to when you copy a folder with all the files inside. If you edit an original file, its copy won't be changed because they are two different files.
If you continue the analogy, when I ran the scrambleScene function it edited the original file, and when I look at the Shapes, it was the same because it was just a shortcut, not a new file. I needed to copy the file, not create a shortcut.
You can create a completely independent object by using JSON. There are some limitations, but for my case it worked.
I have a 3D Render of moving cubes, they are different colors, so it's like a rainbow. But I want to know if there is a way to make the squares pulse colors.
https://repl.it/#AlexanderLuna/R-A-I-N-B-O-W#index.html
colorMode(HSB, nums.x * nums.y, 1, 1) Is your answere.
Apply it in the update function and play around with the colors by altering 'nums.x * nums.y' values.
Use a timer or a simple tick (you can simply do tick++ in the update function) as a modifier until it reaches a certain iteration and then reset (or jump the value). You should get the desired effect.
Okey.. apparently I'm procrastinating and spent the last hour playing around with your Repl..
This might not be exactly what you're after, but maybe it'll help some..
class Cube {
constructor(x_, y_, z_, size_, offset_) {
this.x = x_;
this.y = y_;
this.z = z_;
this.size = size_;
this.offset = offset_;
this.angle = 0;
this.tick = 1; // starting point
this.hueSpeed = 2; // tick modifier
}
update(f) {
this.y = map(f(this.angle + this.offset), -1, 1, this.size / 2, height - this.size / 2);
this.angle += 0.05;
colorMode(HSB, this.tick, 1, 1);
/**
* The request is there to simply regulate the frequency of the tick a bit..
* Though we do need to cancel the previous request if hadn't yet fired
* Which I'm apparently to lazy to do atm
*/
window.requestAnimationFrame((e)=>{
this.tick += this.hueSpeed;
(this.tick > 150 || this.tick < 2) && (this.hueSpeed *= -1);
});
}
render() {
push();
stroke(0);
translate(this.x, this.y, this.z);
box(this.size);
pop();
}
}
This is the cube.js script file, the only one altered.
I am attempting to make a simple version of flappy bird to help me learn JavaScript. I am using p5.js. I have made a constructor function for the walls that move across the screen, here is the code:
function Wall() {
this.xw = width- 20;
this.width = 20;
this.len = Math.floor(Math.random() * 200) + 50
this.rects = function() {
rect(this.xw,0,this.width,this.len);
translate(0,height);
rect(this.xw,0,this.width,-height + 70 + this.len);
}
this.update = function(){
this .xw -= 1;
}
}
I want these objects to move across the screen after eachother
I used this code to attempt to do this. However my problem is that it is only producing one wall.
if(frameCount % 40 == 0) {
walls.push(new Wall());
}
for(i = 0; i < walls.length; i++){
walls[i].rects();
walls[i].update();
}
I have also created an empty array and in the setup function I have written:
var walls = [];
walls.push(new Wall());
Any help is much appreciated.
It's been a while, but if you're still stuck here, I noticed something.
It looks like your function is fine, but you've got a little typo. An extra space is preventing this.xw from decrementing when the update method is called (this .xw instead of this.xw).
You can confirm in your console by typing walls.length and watching each wall get added to the array. But then they're all stacked on top (z-axis) of each other with the same position because each wall's xw attribute isn't changing.
For clarity, here's the change:
function Wall() {
this.xw = width- 20;
this.width = 20;
this.len = Math.floor(Math.random() * 200) + 50
this.rects = function() {
rect(this.xw,0,this.width,this.len);
translate(0,height);
rect(this.xw,0,this.width,-height + 70 + this.len);
}
this.update = function(){
this.xw -= 1; // <-- this line here, remove the space
}
}