Week 4 — Objects & Physics

Real games are built from objects, not loose variables. This week you organise the scene into reusable objects, follow the mouse, and fire a projectile along a realistic gravity arc.

🎯 Objectives

  • Create objects with constructor functions and the new keyword.
  • Give objects their own data and methods (e.g. draw(), moveit()).
  • Keep every object in an array and redraw them all with one loop.
  • Handle mouse input with mousedown, mousemove and mouseup.
  • Hit-test a click against a circle using squared distance.
  • Turn a drag into a velocity using Math.atan2, Math.cos and Math.sin.
  • Simulate projectile motion with gravity and velocity integration.
  • Detect collisions with the target and the ground.

🛠 Weekly tasks

  1. Objective 1 — create objects with constructor functions and the new keyword: Activity 1 + build cannball1.html.
  2. Objective 2 — give objects their own data and methods (draw(), moveit()): Activity 2 + build cannball1.html.
  3. Objective 3 — keep every object in an array and redraw them with one loop: Activity 3 + build cannball1.html.
  4. Objective 4 — handle mouse input with mousedown, mousemove and mouseup: Activity 4 + build slingshot.html.
  5. Objective 5 — hit-test a click against a circle using squared distance: Activity 5 + build slingshot.html.
  6. Objective 6 — turn a drag into a velocity with Math.atan2, Math.cos and Math.sin: Activity 6 + build cannBall2.html and slingshot.html.
  7. Objective 7 — simulate projectile motion with gravity and velocity integration: Activity 7 + build cannball1.html and cannBall2.html.
  8. Objective 8 — detect collisions with the target and the ground: Activity 8 + build slingshot.html.
  9. Build your own slingshot_yourname.html — using everything you have learned; keep a score of how many times you hit the chicken. (All objectives)

🎓 Lecture activity Open lecture slides →

A 90-minute session of 35 slides. Each concept is taught four ways: the big idea, a short example with its live output, a line-by-line explanation, and a 5-minute “try it” task. The lab work at 1:00 uses the Lab activity below.

Session plan

  1. 0:0010 minWarm-upReview questions on last week
  2. 0:1010 minGame & objectivesThis week’s game, objectives and key words
  3. 0:2035 minConceptsBig idea → code with live output → line by line → try it
  4. 0:555 minCheck understandingCommon mistakes and a 4-question quiz
  5. 1:0025 minLabBuild the files in the Lab activity below
  6. 1:255 minWrap-upBuild-your-own task, marking guide and recap

Concepts this week

  • 1. Objects & arrays — Objects that draw themselves
  • 2. Gravity — Gravity bends the path
  • 3. Angles & rotation — Aim with an angle
  • 4. Mouse events — Press, drag, release

The slides open as a page on this site. Press F for full screen, the arrow keys to move, N for speaker notes, and P to save as PDF (turn on “Background graphics”).

🧪 Lab activity: work through the files Printable lab sheet

Create a folder named Week4 on your PC. Work through the files below in order, creating each one and writing the code shown. Download the materials (chicken.jpg, feathers.gif, hill.jpg, plateau.jpg) into the folder. Save each file and refresh the browser.

Want a head start? A scaffold file starter.html is provided — choose it in the preview dropdown, copy it into your Week4 folder, and complete its TODOs.

  1. Create your Week4 folder

    On your own PC — do this once before starting.

    1. Create a folder named Week4 on your PC.
    2. Download the materials above into it.
    3. Open your Week4 folder in VS Code or Sublime Text.
  2. cannball1.html Objectives 1, 2, 3, 7

    Week4\cannball1.html — your first object, moving under gravity.

    1. [Obj 1, 7] Create cannball1.html and add the page: the canvas, the form and the variables. onLoad="init();" runs the setup, and the form calls fire().
      <!DOCTYPE html>
      <html lang="en">
      <head>
        <title>Cannonball</title>
        <style>
          form { width: 330px; margin: 20px; background-color: brown; padding: 20px; }
        </style>
      </head>
      <body onLoad="init();">
        <canvas id="canvas" width="600" height="400">
          Your browser doesn't support the HTML5 element canvas.
        </canvas>
        <br />
        <form name="f" id="f" onSubmit="return fire();">
          Set velocities and fire the cannonball. <br />
          Horizontal <input name="hv" id="hv" value="10" type="number" min="-100" max="100" /> <br />
          Vertical <input name="vv" id="vv" value="-25" type="number" min="-100" max="100" />
          <input type="submit" value="FIRE" />
        </form>
      
        <script>
          var cwidth = 600, cheight = 400;
          var ctx, everything = [], tid;
          var horvelocity, verticalvel1, verticalvel2, gravity = 2;
          var iballx = 20, ibally = 300;
          // the constructors, the objects and the functions go here
        </script>
      </body>
      </html>
    2. [Obj 1, 2] Add the Ball constructor with its own draw and moveit methods, then the Myrectangle constructor.
      function Ball(sx, sy, rad, style) {
        this.sx = sx; this.sy = sy; this.rad = rad;
        this.fillstyle = style;
        this.draw = drawball;      // the object's own method
        this.moveit = moveball;
      }
      function drawball() {
        ctx.fillStyle = this.fillstyle;
        ctx.beginPath();
        ctx.arc(this.sx, this.sy, this.rad, 0, Math.PI * 2, true);
        ctx.fill();
      }
      function moveball(dx, dy) { this.sx += dx; this.sy += dy; }
      
      function Myrectangle(sx, sy, swidth, sheight, style) {
        this.sx = sx; this.sy = sy; this.swidth = swidth; this.sheight = sheight;
        this.fillstyle = style;
        this.draw = drawrects;
        this.moveit = moverect;
      }
      function drawrects() {
        ctx.fillStyle = this.fillstyle;
        ctx.fillRect(this.sx, this.sy, this.swidth, this.sheight);
      }
      function moverect(dx, dy) { this.sx += dx; this.sy += dy; }
    3. [Obj 1, 3] Create the objects with new and push them into the everything array.
      var cball  = new Ball(iballx, ibally, 10, "rgb(250,0,0)");
      var target = new Myrectangle(300, 100, 80, 200, "rgb(0,5,90)");
      var ground = new Myrectangle(0, 300, 600, 30, "rgb(10,250,0)");
      
      everything.push(target);
      everything.push(ground);
      everything.push(cball);
    4. [Obj 3] Add init() and drawall(). drawall() clears the canvas and calls draw() on every object — the same two lines draw the whole scene.
      function init() {
        ctx = document.getElementById("canvas").getContext("2d");
        drawall();
      }
      function drawall() {
        ctx.clearRect(0, 0, cwidth, cheight);
        for (var i = 0; i < everything.length; i++) {
          everything[i].draw();
        }
      }
    5. [Obj 7, 8] Add change(): integrate the vertical velocity with gravity (using the average of the old and new velocity for a smooth arc), then stop the timer when the ball reaches the target or the ground.
      function change() {
        var dx = horvelocity;
        verticalvel2 = verticalvel1 + gravity;
        var dy = (verticalvel1 + verticalvel2) * 0.5;
        verticalvel1 = verticalvel2;
        cball.moveit(dx, dy);
      
        var bx = cball.sx, by = cball.sy;
        if (bx >= target.sx && bx <= target.sx + target.swidth &&
            by >= target.sy && by <= target.sy + target.sheight) {
          clearInterval(tid);
        }
        if (by >= ground.sy) { clearInterval(tid); }
        drawall();
      }
    6. [Obj 1, 7] Add fire(): reset the ball to its start, read the two numbers, draw, then start the timer.
      function fire() {
        cball.sx = iballx;
        cball.sy = ibally;
        horvelocity = Number(document.f.hv.value);
        verticalvel1 = Number(document.f.vv.value);
        drawall();
        tid = setInterval(change, 100);
        return false;
      }
    7. Save and refresh, then fire a few shots.
  3. cannBall2.html Objectives 1, 2, 3, 6, 7

    Week4\cannBall2.html — cannon with angle and a hill target.

    1. [Obj 1] Create cannBall2.html and add this scaffold code (your cannonball with a rotating cannon and an angle input).
      <!DOCTYPE html>
      <html lang="en">
      <head>
        <title>Cannonball</title>
        <style>
          form { width: 330px; margin: 20px; background-color: brown; padding: 20px; }
        </style>
      </head>
      <body onLoad="init();">
        <canvas id="canvas" width="600" height="400">
          Your browser doesn't support the HTML5 element canvas.
        </canvas>
        <br />
        <form name="f" id="f" onSubmit="return fire();">
          Set velocity, angle and fire cannonball. <br />
          Velocity out of cannon <input name="vo" id="vo" value="10" type="number" min="-100" max="100" />
          <br />
          Angle <input name="ang" id="ang" value="0" type="number" min="0" max="80" />
          <input type="submit" value="FIRE" />
        </form>
      
        <script>
          var cwidth = 600, cheight = 400;
          var ctx;
          var everything = [];
          var tid, outofcannon, horvelocity, verticalvel1, verticalvel2, gravity = 2;
      
          // TODO: create the target, ground, ball and cannon objects
          // TODO: add init()
          function drawall() {
            ctx.clearRect(0, 0, cwidth, cheight);
            // TODO: draw each object (rotate the cannon)
          }
          function fire() {
            // TODO: convert the angle to velocity components and start the animation
            return false;
          }
          function change() {
            // TODO: apply gravity, move the ball and check the target
          }
        </script>
      </body>
      </html>
    2. [Obj 1, 2] Add the object constructors (Ball, Myrectangle, Picture) with a draw() method.
      function Ball(sx, sy, rad, style) {
        this.sx = sx; this.sy = sy; this.rad = rad; this.fillstyle = style;
        this.draw = drawball; this.moveit = moveball;
      }
      function Myrectangle(sx, sy, swidth, sheight, style) {
        this.sx = sx; this.sy = sy; this.swidth = swidth; this.sheight = sheight;
        this.fillstyle = style; this.draw = drawrects; this.moveit = moveball;
      }
      function Picture(sx, sy, swidth, sheight, file) {
        var imga = new Image(); imga.src = file;
        this.sx = sx; this.sy = sy; this.img = imga;
        this.swidth = swidth; this.sheight = sheight;
        this.draw = drawAnImage; this.moveit = moveball;
      }
      function drawball() { ctx.fillStyle = this.fillstyle; ctx.beginPath(); ctx.arc(this.sx, this.sy, this.rad, 0, Math.PI * 2, true); ctx.fill(); }
      function moveball(dx, dy) { this.sx += dx; this.sy += dy; }
      function drawrects() { ctx.fillStyle = this.fillstyle; ctx.fillRect(this.sx, this.sy, this.swidth, this.sheight); }
      function drawAnImage() { ctx.drawImage(this.img, this.sx, this.sy, this.swidth, this.sheight); }
    3. [Obj 3] Add the scene constants and objects, then put them in everything.
      var cannonx = 10, cannony = 280, cannonlength = 200, cannonht = 20;
      var ballrad = 10;
      var targetx = 500, targety = 50, targetw = 85, targeth = 280;
      var htargetx = 450, htargety = 220, htargetw = 355, htargeth = 96;
      
      var cball = new Ball(cannonx + cannonlength, cannony + cannonht * 0.5, ballrad, "rgb(250,0,0)");
      var target = new Picture(targetx, targety, targetw, targeth, "hill.jpg");
      var htarget = new Picture(htargetx, htargety, htargetw, htargeth, "plateau.jpg");
      var ground = new Myrectangle(0, 300, 600, 30, "rgb(10,250,0)");
      var cannon = new Myrectangle(cannonx, cannony, cannonlength, cannonht, "rgb(40,40,0)");
      
      var targetindex = everything.length;
      everything.push([target, false]);
      everything.push([ground, false]);
      var ballindex = everything.length;
      everything.push([cball, false]);
      var cannonindex = everything.length;   // remember this to rotate the cannon later
      everything.push([cannon, true, 0, cannonx, cannony + cannonht * 0.5]);
    4. [Obj 1, 6, 7] Add fire() to convert the angle and launch.
      function fire() {
        var angle = Number(document.f.ang.value);
        var outofcannon = Number(document.f.vo.value);
        var angleradians = angle * Math.PI / 180;
        horvelocity = outofcannon * Math.cos(angleradians);
        verticalvel1 = -outofcannon * Math.sin(angleradians);
        everything[cannonindex][2] = -angleradians;
        cball.sx = cannonx + cannonlength * Math.cos(angleradians);
        cball.sy = cannony + cannonht * 0.5 - cannonlength * Math.sin(angleradians);
        drawall();
        tid = setInterval(change, 100);
        return false;
      }
    5. [Obj 2, 3] Add drawall() to draw every object (rotating the cannon).
      function drawall() {
        ctx.clearRect(0, 0, cwidth, cheight);
        for (var i = 0; i < everything.length; i++) {
          var ob = everything[i];
          if (ob[1]) {
            ctx.save();
            ctx.translate(ob[3], ob[4]);
            ctx.rotate(ob[2]);
            ctx.translate(-ob[3], -ob[4]);
            ob[0].draw();
            ctx.restore();
          } else {
            ob[0].draw();
          }
        }
      }
    6. [Obj 7] Add change() to move the ball and swap the target when it is hit (hill → plateau).
      function change() {
        var dx = horvelocity;
        verticalvel2 = verticalvel1 + gravity;
        var dy = (verticalvel1 + verticalvel2) * 0.5;
        verticalvel1 = verticalvel2;
        cball.moveit(dx, dy);
        var bx = cball.sx, by = cball.sy;
        if (bx >= target.sx && bx <= target.sx + target.swidth && by >= target.sy && by <= target.sy + target.sheight) {
          clearInterval(tid);
          everything.splice(targetindex, 1, [htarget, false]);   // swap hill -> plateau
          everything.splice(ballindex, 1);                       // remove the ball on a hit
          drawall();
        }
        if (by >= ground.sy) { clearInterval(tid); }
        drawall();
      }
    7. [Obj 1, 2] Add init(), called by onLoad, to get the context and draw the first frame.
      function init() {
        ctx = document.getElementById("canvas").getContext("2d");
        drawall();
      }
    8. Save and refresh, then find the best angle and velocity.
  4. slingshot.html Objectives 1, 2, 3, 4, 5, 6, 7

    Week4\slingshot.html — drag the ball to launch it at the chicken.

    1. [Obj 7] Create slingshot.html and add this scaffold code.
      <!DOCTYPE html>
      <html lang="en">
      <head><title>Slingshot pulling back</title></head>
      <body onLoad="init();">
        <canvas id="canvas" width="1200" height="600">
          Your browser doesn't support the HTML5 element canvas.
        </canvas>
        <br />
        Mouse down and drag the ball. Releasing the mouse button will shoot the slingshot.
      
        <script>
          var cwidth = 1200, cheight = 600;
          var ctx, canvas1, everything = [], tid;
          var ballrad = 10, ballradsq = ballrad * ballrad;
          var inmotion = false, horvelocity, verticalvel1, verticalvel2, gravity = 2;
          var chicken = new Image();  chicken.src = "chicken.jpg";
          var feathers = new Image(); feathers.src = "feathers.gif";
      
          // TODO: create Sling, Ball and Picture objects
          function init() {
            ctx = document.getElementById("canvas").getContext("2d");
            canvas1 = document.getElementById("canvas");
            // TODO: add mousedown, mousemove and mouseup listeners
            // TODO: build the scene and draw it
          }
          // TODO: add findball(), moveit(), finish(), drawall() and change()
        </script>
      </body>
      </html>
    2. [Obj 1, 2] Add the object constructors and create the objects.
      var startrockx = 100, startrocky = 240;
      function Sling(bx, by, s1x, s1y, s2x, s2y, s3x, s3y, style) {
        this.bx = bx; this.by = by; this.s1x = s1x; this.s1y = s1y;
        this.s2x = s2x; this.s2y = s2y; this.s3x = s3x; this.s3y = s3y;
        this.strokeStyle = style; this.draw = drawsling; this.moveit = movesling;
      }
      function drawsling() {
        ctx.strokeStyle = this.strokeStyle; ctx.lineWidth = 4;
        ctx.beginPath();
        ctx.moveTo(this.bx, this.by); ctx.lineTo(this.s1x, this.s1y);
        ctx.moveTo(this.bx, this.by); ctx.lineTo(this.s2x, this.s2y);
        ctx.moveTo(this.s1x, this.s1y); ctx.lineTo(this.s2x, this.s2y); ctx.lineTo(this.s3x, this.s3y);
        ctx.stroke();
      }
      function movesling(dx, dy) { this.bx += dx; this.by += dy; this.s1x += dx; this.s1y += dy; this.s2x += dx; this.s2y += dy; this.s3x += dx; this.s3y += dy; }
      function Ball(sx, sy, rad, style) {
        this.sx = sx; this.sy = sy; this.rad = rad; this.fillstyle = style;
        this.draw = drawball; this.moveit = moveball;
      }
      function drawball() { ctx.fillStyle = this.fillstyle; ctx.beginPath(); ctx.arc(this.sx, this.sy, this.rad, 0, Math.PI * 2, true); ctx.fill(); }
      function moveball(dx, dy) { this.sx += dx; this.sy += dy; }
      function Myrectangle(sx, sy, swidth, sheight, style) {
        this.sx = sx; this.sy = sy; this.swidth = swidth; this.sheight = sheight;
        this.fillstyle = style; this.draw = drawrects;
      }
      function drawrects() { ctx.fillStyle = this.fillstyle; ctx.fillRect(this.sx, this.sy, this.swidth, this.sheight); }
      function Picture(sx, sy, swidth, sheight, imga) {
        this.sx = sx; this.sy = sy; this.img = imga; this.swidth = swidth; this.sheight = sheight; this.draw = drawAnImage;
      }
      function drawAnImage() { ctx.drawImage(this.img, this.sx, this.sy, this.swidth, this.sheight); }
      
      var mysling = new Sling(startrockx, startrocky, startrockx + 80, startrocky - 10, startrockx + 80, startrocky + 10, startrockx + 70, startrocky + 180, "rgb(120,20,10)");
      var cball = new Ball(startrockx, startrocky, ballrad, "rgb(250,0,0)");
      var target = new Picture(700, 210, 209, 179, chicken);
      var ground = new Myrectangle(0, 370, 1200, 30, "rgb(10,250,0)");
    3. [Obj 3, 4] Add init() to wire the mouse events and build the scene.
      function init() {
        ctx = document.getElementById("canvas").getContext("2d");
        canvas1 = document.getElementById("canvas");
        canvas1.addEventListener("mousedown", findball, false);
        canvas1.addEventListener("mousemove", moveit, false);
        canvas1.addEventListener("mouseup", finish, false);
        everything.push(target);
        everything.push(ground);
        everything.push(mysling);
        everything.push(cball);
        drawall();
      }
    4. [Obj 4, 5] Add findball() (hit-test) and distsq().
      function findball(ev) {
        var mx = ev.layerX, my = ev.layerY;
        if (distsq(mx, my, cball.sx, cball.sy) < ballradsq) { inmotion = true; drawall(); }
      }
      function distsq(x1, y1, x2, y2) { return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); }
    5. [Obj 4] Add moveit() to drag the ball and the sling.
      function moveit(ev) {
        if (!inmotion) { return; }
        cball.sx = ev.pageX; cball.sy = ev.pageY;
        mysling.bx = ev.pageX; mysling.by = ev.pageY;
        drawall();
      }
    6. [Obj 5, 6] Add finish() to turn the drag into a velocity (squared distance + atan2).
      function finish(ev) {
        if (!inmotion) { return; }
        inmotion = false;
        var outofcannon = distsq(mysling.bx, mysling.by, mysling.s1x, mysling.s1y) / 700;
        var angleradians = -Math.atan2(mysling.s1y - mysling.by, mysling.s1x - mysling.bx);
        horvelocity = outofcannon * Math.cos(angleradians);
        verticalvel1 = -outofcannon * Math.sin(angleradians);
        tid = setInterval(change, 100);
      }
    7. [Obj 2, 3] Add drawall() and change() (gravity + hit the chicken).
      function drawall() {
        ctx.clearRect(0, 0, 1200, 600);
        for (var i = 0; i < everything.length; i++) { everything[i].draw(); }
      }
      function change() {
        var dx = horvelocity;
        verticalvel2 = verticalvel1 + gravity;
        var dy = (verticalvel1 + verticalvel2) * 0.5;
        verticalvel1 = verticalvel2;
        cball.moveit(dx, dy);
        var bx = cball.sx, by = cball.sy;
        if (bx >= target.sx + 40 && bx <= target.sx + target.swidth - 40 && by >= target.sy + 40 && by <= target.sy + target.sheight - 40) {
          target.img = feathers;
        }
        if (by >= ground.sy) { clearInterval(tid); }
        drawall();
      }
    8. Save and refresh, then drag and release the ball.
Finished example output

In slingshot.html the mouse position comes from ev.pageX/pageY. When you release, the length of the drag sets the launch speed and the direction sets the angle.

📚 The projectile maths