Search code examples
javascriptp5.js

Bouncing ball not staying the color assigned to it


I am writing a program for a ball to bounce off the edges of a box and change colors (much like the DVD logo). The problem is that once it changes colors, the fill function for the box overwrites the ball's color. I have tried conditionals and different function arrangements and asked my classmates for help, but none were helpful.

    //
// Bounce1
// A simple bouncing ball - it has perfect bounces,
// it never slows down.

//These variables draw the canvas and the inner box
let canvasW = 900;
let canvasH = 700;
let innerborder = 100;

// These variables store the position, size, and speed.
let positionX = 300;
let positionY = 300;
let radius = 20;
let velocityX = 3;
let velocityY = 5;

//This variable helps with color change
let change = false;

function setup() {
  createCanvas(canvasW, canvasH);
 
}

function draw() {
  drawBackground();
  moveBall();
  drawBall();
}

function drawBackground(){
  background(220);
  innerRect();
  
}

function innerRect(){
  fill (71, 71, 71);
  rect (innerborder, 100, 700, 500);
}

function moveBall(){
  // move the ball
  positionX = positionX + velocityX;
  positionY = positionY + velocityY;

  const rightEdge = width - 100;
  const leftEdge = 0 + 100;
  const topEdge = 0 + 100;
  const bottomEdge = height - 100;

  // test to see if it hit an edge
  if (positionX + radius > rightEdge) {
    // hit the right edge
    velocityX = velocityX * -1;
    positionX = rightEdge - radius;
    change = true;
    if (change == true){
      changeColor();
      change = false
    }
    else if (change == false){
      noFill();
    }
  }
  else if (positionX - radius < leftEdge) {
    // hit the left edge
    velocityX = velocityX * -1;
    positionX = leftEdge + radius;
    change = true;
    if (change == true){
      changeColor();
      change = false
      }
    else if (change == false){
      noFill();
    }
  }

  if (positionY + radius > bottomEdge) {
    // hit the bottom edge
    velocityY = velocityY * -1;
    positionY = bottomEdge - radius;
    change = true;
    if (change == true){
      changeColor();
      change = false
      }
    else if (change == false){
      noFill();
    }
  }
  else if (positionY - radius < topEdge) {
    // hit the top edge
    velocityY = velocityY * -1;
    positionY = topEdge + radius;
    change = true;
    if (change == true){
      changeColor();
      change = false
      }
    else if (change == false){
      noFill();
    }
}
}

function drawBall(){
  // draw the ball
  ellipse(positionX, positionY, radius * 2, radius * 2);
}

function changeColor(){
  //Colors
  let r = random(0, 255);
  let g = random(0, 255);
  let b = random(0, 255);

  fill (r, g, b);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.1/p5.js"></script>


Solution

  • Try storing the current ball colors in variables so you can re-call fill(ballRed, ballGreen, ballBlue) just before redrawing the ball. changed isn't necessary and doesn't really help with the fact that innerRect()'s fill() will override any other fill() calls that happened on previous frames.

    const canvasW = 900;
    const canvasH = 700;
    const innerborder = 100;
    let positionX = 300;
    let positionY = 300;
    let radius = 20;
    let velocityX = 3;
    let velocityY = 5;
    let r, g, b;
    let rightEdge;
    let leftEdge;
    let topEdge;
    let bottomEdge;
    
    function setup() {
      createCanvas(canvasW, canvasH);
      rightEdge = width - 100;
      leftEdge = 0 + 100;
      topEdge = 0 + 100;
      bottomEdge = height - 100;
      changeColor();
    }
    
    function draw() {
      drawBackground();
      moveBall();
    
      if (checkCollision()) {
        resolveCollision();
        changeColor();
      }
    
      drawBall();
    }
    
    function drawBackground() {
      background(220);
      innerRect();
    }
    
    function innerRect() {
      fill(71, 71, 71);
      rect(innerborder, 100, 700, 500);
    }
    
    function moveBall() {
      positionX = positionX + velocityX;
      positionY = positionY + velocityY;
    }
    
    const checkCollision = () =>
      positionX + radius > rightEdge ||
      positionX - radius < leftEdge ||
      positionY + radius > bottomEdge ||
      positionY - radius < topEdge
    ;
    
    const resolveCollision = () => {
      if (positionX + radius > rightEdge) {
        // hit the right edge
        velocityX = velocityX * -1;
        positionX = rightEdge - radius;
      } else if (positionX - radius < leftEdge) {
        // hit the left edge
        velocityX = velocityX * -1;
        positionX = leftEdge + radius;
      }
    
      if (positionY + radius > bottomEdge) {
        // hit the bottom edge
        velocityY = velocityY * -1;
        positionY = bottomEdge - radius;
      } else if (positionY - radius < topEdge) {
        // hit the top edge
        velocityY = velocityY * -1;
        positionY = topEdge + radius;
      }
    };
    
    function drawBall() {
      fill(r, g, b);
      ellipse(positionX, positionY, radius * 2, radius * 2);
    }
    
    function changeColor() {
      r = random(0, 255);
      g = random(0, 255);
      b = random(0, 255);
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.js"></script>

    After that, you might want to consider refactoring the code to put all of the loose variables and functions associated with a ball into an object. Something like

    const ball = {
      x: 42,
      y: 42,
      r: 0,
      b: 0,
      g: 0,
      radius: 20,
      vx: 3,
      vy: 5,
      changeColor() {
        // ...
      },
      draw() {
        // ...
      },
      // ...
    };
    

    and use it with ball.changeColor(). This logical grouping makes the code easier to read and extend.