简体   繁体   中英

Splice (index,1) is removing more than one element from array

I am making a simple game in HTML Canvas. As a part of it, in the background i want to make falling stars which create the illusion of travelling. After the star reaches the end of the canvas i want to remove it. Each star is an instance of Star class, and depending on it's radius it has a different velocity. This is where problems start. When using constant velocity for every star, they disappear one by one like they should be. When velocity is changed, stars that "overtake" slower stars, when reached the end of the canvas, do not only dissapear themselves but also remove every star that was in array before them. I have tried many solutions described below:

let canvas = document.querySelector("canvas");
let c = canvas.getContext('2d');

Star Class declaration:

class Star{
    constructor(x, y, radius, color, velocity){
        this.x = x;
        this.y = y;
        this.radius = radius;
        this.color = color;
        this.velocity = velocity
    }

    draw(){
        c.globalCompositeOperation='destination-over'
        c.beginPath()
        c.arc(this.x, this.y, this.radius, 0, Math.PI*2, false);
        c.fillStyle = this.color;
        c.shadowColor= "white"
         c.shadowBlur=12
        c.fill();
        c.shadowBlur=0
    }
   
    update(){
        this.draw();
        this.y = this.y + this.radius/2;
    
    }
}

Creating stars and adding it to array

let stars = [];
function createStar(){
    setInterval(()=>{
    //Create random radius and X position
    let randomRadius = Math.floor((Math.random()*5))
    let randomXPosition = Math.floor((Math.random()*780)+15)
    let velocity = 1;
    stars.push(new Star(randomXPosition, -randomRadius, randomRadius, "white",velocity));
    console.log("stars:"+ stars.length);
    },300)
}

Below here I use a function calling itself to clear and refresh the star drawing. I have tried looping through stars array with forEach method, reversed loop (like in example below), I tried putting the if statement with splice function in seperate setTimeout(()=>{},0) or setTimeout(()=>{},10). I tried using the condition like

(forEach method removes stars, however the number of active stars do not remain more or less the same. It constantly slowly increases)

function animate(){
  c.clearRect(0, 0, canvas.width, canvas.height);

   for(let i = stars.length-1; i>=0; i--){
        stars[i].update()
        if (stars[i].y > canvas.height +stars[i].radius ){
            stars.splice(stars[i], 1)
       }
    } 
requestAnimationFrame(animate);
}

animate();
createStar();

I tried using the condition like:

if (stars[i].y > 5000 ){
    stars.splice(stars[i], 1)
}

But it's not how it's supposed to be solved, because stars live for 5000 pixels longer,what makes game laggy.

Just to be clear on the problem. If i generate 5 stars every 300ms and push the into the array called "stars" I get eg [star1, star2, star3, star4, star5]

Lets say star1 and star 2 have small radius and they move slowly. Star3 is bigger therefore it moves faster and overtakes first two. When star3 reaches canvas.height + star[i].radius it disappear just over the canvas, but it also makes every star in array that was before star3 disappear (in this case it's star1 and star2).

This is my first post on stackoverflow. I apologise for all understatements in advance.

HTML Canvas


<body>
    <div class="container">
        <button class="logOutButton">Log Out</button>
        <button class="topScores">Top 10</button>
        <header>
            <p class="hello">Hello <span id="spanName"></span></p>
            <p class="topScore">Your TOP score: <span id="score"></span></p>
        </header>

        <div class="gameTitle">
            <h2 class="title">Space Warrior</h2>
        </div>
        
        <canvas class="canvas" width="800" height="500"></canvas>
       
    </div>
    <script src="User.js"></script>
</body>

EDIT I changed stars.splice(stars[i], 1) to stars.splice(i, 1) - not working

I tried adding another removal array like below but array stars just slowly gets bigger (even though some elements get removed)

 var removeStar = [];
    // stars.forEach((star,starIndex)=>{
    let total = stars.length-1;
    
    for(let i = total; i>=0; i--){
        stars[i].update();

        if (stars[i].y > canvas.height + stars[i].radius){
            removeStar.push(i);
        }
    };

    for(let i = removeStar.length; i>0; i--){
        stars.splice(removeStar[i-1],1)
    }

you wrote: stars.splice(stars[i], 1) first argument should be index which you want to remove...just index stars.splice(i, 1) ..another problem is that you changing the array while looping within it, which is bad idea.

see this answer to very similar question: https://stackoverflow.com/a/65725703/3054380

After fixing the bug mentioned above and adding minVelocity and star limiting condition (because you adding with interval and removing when star goes off the canvas - we need to limit in case interval goes faster)...now everything looks good - working snippet below (added maxStars minVelocity )

 let canvas = document.querySelector(".mycanvas"); let c = canvas.getContext('2d'); canvas.width = 300; canvas.height = 150; let maxStars = 60; let minVelocity = 0.5; let stars = []; class Star { constructor(x, y, radius, color, velocity) { this.x = x; this.y = y; this.radius = radius; this.color = color; this.velocity = velocity } draw() { c.globalCompositeOperation = 'destination-over' c.beginPath() c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false); c.fillStyle = this.color; c.shadowColor = "white" c.shadowBlur = 12 c.fill(); c.shadowBlur = 0 } update() { this.draw(); this.y = this.y + Math.max(minVelocity, this.velocity * this.radius / 2); } } function createStar() { if (stars.length < maxStars) { //Create random radius and X position let randomRadius = Math.floor((Math.random() * 5)); let randomXPosition = Math.floor((Math.random() * (canvas.width - 20)) + 10); let velocity = 1; stars.push(new Star(randomXPosition, -randomRadius, randomRadius, "white", velocity)); console.log("stars:" + stars.length); } } function animate() { c.clearRect(0, 0, canvas.width, canvas.height); for (let i = stars.length - 1; i >= 0; i--) { stars[i].update() if (stars[i].y > canvas.height) { stars.splice(i, 1) } } requestAnimationFrame(animate); } setInterval(createStar, 50); animate();
 .mycanvas { width: 300px; height: 150px; border: 1px solid #f00; }
 <body style="background-color:#000;color:#fff;"> <div class="container"> <canvas class="mycanvas"></canvas> </div> <script src="User.js"></script> </body>

I think I found the issue causing your everexpanding array. It has nothing to do with your splice function, which is implemented incorrectly as webdev-dan mentioned in his answer.

follow this logic

  • The radius has been set with Math.floor((Math.random()*5)) .This means the radius can be 0.
  • In your update method you are increasing y based on the radius in this.y = this.y + this.radius/2 . So this is possibly changing y with 0.
  • In if (stars[i].y > 5000 ) you are removing values of y over 5000.

So if the radius is 0 , y doesn't change, stars are never removed. Nor can you see them visually.

solution

You could guarantee a minimal speed of 1. this.y += Math.max(1, this.radius/2) .

PS: I had to do quite a bit of refactoring to figure this out. You are writing your code in a too complex way. Several types of logic are mixed up.

You really want to separate out your rendering logic from management of the stars object.

It is also quite hard to mentally keep track of the stars array because you are modifying it from anywhere; inside for loops, with async code ( the interval ).

This is what I ended up while trying to clean up your code a bit: https://jsfiddle.net/5hk0vscg/1/ Hope it's useful. Note that it is not a full cleanup, but it's an improvement over your current code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM