1

I am creating this game where you move a block with your mouse and avoid obstacles that are being created from right side of the screen to the left, my cursor used to work fine, so did the obstacle creation, but when i combined them it doesn't seem to work anymore and i can't figure out why, here is the game code,

var myGamePiece;
var myObstacles = [];


function startGame() {
  myGamePiece = new component(30, 30, "red", 10, 120);
  myGameArea.start();
}

var myGameArea = {
  canvas: document.createElement("canvas"),
  start: function() {
    this.canvas.width = 600;
    this.canvas.height = 600;
    this.canvas.style.cursor = "none";
    this.context = this.canvas.getContext("2d");
    document.body.insertBefore(this.canvas, document.body.childNodes[0]);
    this.frameNo = 0;
    this.interval = setInterval(updateGameArea, 20);
    window.addEventListener('mousemove', function(e) {
      myGameArea.x = e.pageX;
      myGameArea.y = e.pageY;

    })
  },
  clear: function() {
    this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
  },
  stop: function() {
    clearInterval(this.interval);
  }
}

function everyinterval(n) {
  if ((myGameArea.frameNo / n) % 1 == 0) {
    return true;
  }
  return false;
}

function component(width, height, color, x, y) {
  this.width = width;
  this.height = height;
  this.speedX = 0;
  this.speedY = 0;
  this.x = x;
  this.y = y;
  this.update = function() {
    ctx = myGameArea.context;
    ctx.fillStyle = color;
    ctx.fillRect(this.x, this.y, this.width, this.height);
  }


  this.crashWith = function(otherobj) {
    var myleft = this.x;
    var myright = this.x + (this.width);
    var mytop = this.y;
    var mybottom = this.y + (this.height);
    var otherleft = otherobj.x;
    var otherright = otherobj.x + (otherobj.width);
    var othertop = otherobj.y;
    var otherbottom = otherobj.y + (otherobj.height);
    var crash = true;
    if ((mybottom < othertop) ||
      (mytop > otherbottom) ||
      (myright < otherleft) ||
      (myleft > otherright)) {
      crash = false;
    }
    return crash;
  }
}

function updateGameArea() {
  var x, y;
  for (i = 0; i < myObstacles.length; i += 1) {
    if (myGamePiece.crashWith(myObstacles[i])) {
      myGameArea.stop();
      return;

      myGameArea.clear();
      myObstacle.x += -1;
      myObstacle.update();
      if (myGameArea.x && myGameArea.y) {
        myGamePiece.x = myGameArea.x;
        myGamePiece.y = myGameArea.y;
      }
      myGamePiece.update();
    }
  }
}
myGameArea.clear();
myGameArea.frameNo += 1;
if (myGameArea.frameNo == 1 || everyinterval(150)) {
  x = myGameArea.canvas.width;
  y = myGameArea.canvas.height - 200;
  myObstacles.push(new component(10, 20, "green", x, y));
}

for (i = 0; i < myObstacles.length; i += 1) {
  myObstacles[i].x += -1;
  myObstacles[i].update();
}

startGame();
canvas {
  border: 1px solid #d3d3d3;
  background-color: #f1f1f1;
}
<body>
  <p>move the cursor to move the blocky boii!</p>
</body>

if you guys figure out what's wrong with it and possibly add why and what am i doing wrong in the code in general (structure, position etc) i would be very grateful, feel free to hate and criticize the code, I am self learner and don't wont to get the wrong habits that will be difficult to get rid of in the future.

thank you for reply

Dip Hasan
  • 225
  • 3
  • 9

1 Answers1

2

I respect anyone who is open to improving, so please let me know if this helps. For further code improvements, tips, etc, I'd like to point you towards Code Review Stack Exchange. I'll put my notes and working code here, but it's bound to get rather long. First, though, your immediate errors are fixed and commented in the snippet below. (Mostly comes down to "fix your curly braces")

Errors Fixed

var myGamePiece;
var myObstacles = [];


function startGame() {
  myGamePiece = new component(30, 30, "red", 10, 120);
  myGameArea.start();
}

var myGameArea = {
  canvas: document.createElement("canvas"),
  start: function() {
    this.canvas.width = 600;
    this.canvas.height = 600;
    this.canvas.style.cursor = "none";
    this.context = this.canvas.getContext("2d");
    document.body.insertBefore(this.canvas, document.body.childNodes[0]);
    this.frameNo = 0;
    this.interval = setInterval(updateGameArea, 20);
    window.addEventListener('mousemove', function(e) {
      myGameArea.x = e.pageX;
      myGameArea.y = e.pageY;

    })
  },
  clear: function() {
    this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
  },
  stop: function() {
    clearInterval(this.interval);
  }
}

function everyinterval(n) {
  if ((myGameArea.frameNo / n) % 1 == 0) {
    return true;
  }
  return false;
}

function component(width, height, color, x, y) {
  this.width = width;
  this.height = height;
  this.speedX = 0;
  this.speedY = 0;
  this.x = x;
  this.y = y;
  this.update = function() {
    ctx = myGameArea.context;
    ctx.fillStyle = color;
    ctx.fillRect(this.x, this.y, this.width, this.height);
  }


  this.crashWith = function(otherobj) {
    var myleft = this.x;
    var myright = this.x + (this.width);
    var mytop = this.y;
    var mybottom = this.y + (this.height);
    var otherleft = otherobj.x;
    var otherright = otherobj.x + (otherobj.width);
    var othertop = otherobj.y;
    var otherbottom = otherobj.y + (otherobj.height);
    var crash = true;
    if ((mybottom < othertop) ||
      (mytop > otherbottom) ||
      (myright < otherleft) ||
      (myleft > otherright)) {
      crash = false;
    }
    return crash;
  }
}

function updateGameArea() {
  var x, y;
  for (i = 0; i < myObstacles.length; i += 1) {
    var myObstacle = myObstacles[i] // problem 3: this was missing
    if (myGamePiece.crashWith(myObstacle)) {
      myGameArea.stop();
      return;
    } // problem 4: needed to be closed

    myGameArea.clear();
    myObstacle.x += -1;
    myObstacle.update();
    if (myGameArea.x && myGameArea.y) {
      myGamePiece.x = myGameArea.x;
      myGamePiece.y = myGameArea.y;
    }
    myGamePiece.update();
  }

  // problem 1, from here down needed moved into the updateGameArea() function
  // problem 2: myGameArea.clear(); needed removed from here
  myGameArea.frameNo += 1;
  if (myGameArea.frameNo == 1 || everyinterval(150)) {
    x = myGameArea.canvas.width;
    y = myGameArea.canvas.height - 200;
    myObstacles.push(new component(10, 20, "green", x, y));
  }

  for (i = 0; i < myObstacles.length; i += 1) {
    myObstacles[i].x += -1;
    myObstacles[i].update();
  }
}  

startGame();
canvas {
  border: 1px solid #d3d3d3;
  background-color: #f1f1f1;
}
<p>move the cursor to move the blocky boii!</p>

Further Notes/Tips

General Development

  • Using some type of tool (like Atom, VSCode, etc) may help you identify where blocks start and end. You can often hover over an opening { and the matching } will get highlighted
  • You should delete obstacles so you don't infinitely spawn new ones
  • Try to keep "circular dependencies" out of your code
  • Time-based, rather than frame-based logic helps keep things moving smoothly. For the most basic approach, you can animate/move entities using the time since the last frame.

JavaScript

  • Knowing how prototypes work in JS can help keep your memory usage down and your game running faster. Your code currently adds new functions to every "component" instance when they get created
  • In the JS community, we have a couple standard style guides and conventions. For example, we usually capitalize constructor functions (and classes from ES2015). Check out some of the more complete guides like Standard and/or AirBnB to know what is considered common. (helps us speak the same language)
  • If you don't already know it, ES2015+ provides a number of improvements (often maintenance related) over older code. For example using const and let is now often more "normal" than using var, as is using arrow functions instead of regular functions. (these decisions are mostly highlighted in the style guides I provided above)
  • Try to avoid polluting the global namespace by creating variables like myGamePiece and myObstacles. Contain them using scoping somehow. In the code below, I used an IIFE to keep variables local to that code. The fewer implicit interactions we have between scripts, the better.
  • Also, avoid global functions, when you can. For example, most events you might need are best handled via .addEventListener(). Your pre-edit question included <body onload="startGame()">, which requires a global function. This can be replaced with a simple startGame() call within a script tag right above </body> OR you can use something like document.addEventListener('DOMContentLoaded', startGame)
  • You can create a better game loop using window.requestAnimationFrame() (see this documentation for more info)
  • Ideally, each of these classes and functions could be split into separate modules/files as they grow, too. It can be incredibly helpful to see the structure/architecture of an application without needing to read through the full code to understand...which leads me to the next section:

Architecture

  • Game developers often split up the parts of their games according to an architecture called ECS. I'd recommend learning how this works before going too deep. It's not considered a perfect architecture at its most basic form, but the goals behind it hold merit.
  • Also check out Functional Programming (FP) and what it considers to be "state". ECS is basically just FP within the context of a game loop.
  • Aligning yourself with the common terminology of each part will help you search for the details you don't yet know. I changed a few names around in my additional snippet below like "component" -> "Entity" and "crash" to "collision/collide" in an effort to align with what these parts are commonly called.

Putting the notes together...

The snippet below still isn't perfect, but I hope it demonstrates most of the points I provided above. I started with your code and slowly went through refactoring steps until it became what you see below:

;(() => {
  class CanvasCamera2D {
    constructor(canvas) {
      this.canvas = canvas
      this.context = canvas.getContext('2d')
    }

    get width() { return this.canvas.width }
    get height() { return this.canvas.height }

    clear() {
      this.context.clearRect(0, 0, this.width, this.height)
    }

    render(fn) {
      fn(this.context, this.canvas)
    }
  }

  class Entity {
    constructor(type, width, height, x = 0, y = 0) {
      this.type = type
      this.width = width
      this.height = height
      this.x = x
      this.y = y
    }

    get left() { return this.x }
    get right() { return this.x + this.width }
    get top() { return this.y }
    get bottom() { return this.y + this.height }

    collidedWith(otherobj) {
      return !(
        (this.bottom < otherobj.top) ||
        (this.top > otherobj.bottom) ||
        (this.right < otherobj.left) ||
        (this.left > otherobj.right)
      )
    }
  }

  const update = (
    { // state
      gamePiece,
      obstacles,
      mouse,
      deltaTime,
      timestamp,
      lastSpawnTime
    },
    { // actions
      stopGame,
      setLastSpawnTime,
      filterObstacles,
      setGamePiecePosition,
    },
    camera
  ) => {

    // Add an obstacle every so many milliseconds
    if ((timestamp - lastSpawnTime) >= 800) {
      obstacles.push(
        new Entity('obstacle', 10, 20, camera.width, camera.height - 200)
      )
      setLastSpawnTime(timestamp)
    }

    // Go through each obstacle and check for collisions
    filterObstacles(obstacle => {
      if (obstacle.collidedWith(gamePiece)) {
        stopGame()
      }

      // Move obstacles until they hit 0 (then remove from the list)
      obstacle.x -= deltaTime / 4
      return !(obstacle.x < 0)
    })

    // Move gamePiece with mouse
    setGamePiecePosition(mouse.x, mouse.y)
  }

  const render = ({ gamePiece, obstacles }, camera) => {
    camera.clear()
    const entities = [gamePiece].concat(obstacles)
    entities.forEach(entity => camera.render(ctx => {
      ctx.fillStyle = entity.type === 'gamePiece' ? 'green' : 'red'
      ctx.fillRect(entity.x, entity.y, entity.width, entity.height)
    }))
  }

  const startGame = (update, render, inState, maxFrameRate) => {
    const state = Object.assign({
      deltaTime: 0,
      timestamp: 0,
      lastSpawnTime: 0,
      gameRunning: true,
    }, inState)
    
    // Created in an effort to avoid direct modification of state
    const actions = {
      stopGame() { state.gameRunning = false },
      setLastSpawnTime(time) { state.lastSpawnTime = time },
      filterObstacles(fn) { state.obstacles = state.obstacles.filter(fn) },
      setGamePiecePosition(x, y) { Object.assign(state.gamePiece, { x, y }) },
      setMousePosition(x, y) { Object.assign(state.mouse, { x, y }) },
    }

    // Set up camera
    const canvas = Object.assign(document.createElement('canvas'), {
      width: 300,
      height: 300,
    })
    document.body.insertBefore(canvas, document.body.childNodes[0])
    const camera = new CanvasCamera2D(canvas)

    // Update state when mouse moves (scaled in case canvas changes in size)
    window.addEventListener('mousemove', e => {
      actions.setMousePosition(
        e.pageX * (canvas.width / canvas.clientWidth),
        e.pageY * (canvas.height / canvas.clientHeight),
      )
    })
    
    // Start game loop
    let lastTimestamp = performance.now()
    const loop = (timestamp) => {
      const delta = timestamp - lastTimestamp
      if (delta > MAX_FRAME_RATE) {
        state.timestamp = timestamp
        state.deltaTime = delta
        
        update(state, actions, camera)
        render(state, camera)
        lastTimestamp = timestamp
        if (!state.gameRunning) return
      }
      requestAnimationFrame(loop)
    }
    loop(performance.now())
  }

  const MAX_FRAME_RATE = 60 / 1000
  startGame(update, render, {
    mouse: { x: 0, y: 0 },
    gamePiece: new Entity('gamePiece', 30, 30, 10, 120),
    obstacles: [],
  }, MAX_FRAME_RATE)
})()
canvas {
  border: 1px solid #d3d3d3;
  background-color: #f1f1f1;
  max-width: 100%;
  cursor: none;
}
<p>move the cursor to move the blocky boii!</p>
BCDeWitt
  • 4,540
  • 2
  • 21
  • 34