I have some Questions

Hey,
I started one week ago with JavaScript. I use a Laptop of my parents, and on this Laptop is no Chrome. I tried to at an Eventlistener to move the avatar, The normal Internet Explorer done nothing.
I tried to hit every Key, and nothing passed. My Code was absolutely correct , I tried it many times, but I came to nothing. Works this only with Chrome?
Thx for ur answer and help, and sorry for my bad English.

You’ll definitely have more luck if you could convince your parents to install either: Chrome or Microsoft Edge (the latest browser from Microsoft).

If you really have to work with Internet Explorer, make sure it’s at least version 11. If it’s 10 or less, I can still have a look, but I can’t actually try to run it since Microsoft no longer supports those versions.

If it is 11, then can you please copy your code here as described in the welcome post? Be sure to follow those instructions, otherwise the forum software may “eat” the code you post.

Your English is quite good! I’ll wait for your code and have a look as quickly as I can :slight_smile:

-Chris

I will try to convince my parents,but here´s my code(It works not with the newest Internet Explorer Version):

<body></body>
<script src="/three.js"></script>
<script>
  // The "scene" is where stuff in our game will happen:
  var scene = new THREE.Scene();
  var flat = {flatShading: true};
  var light = new THREE.AmbientLight('white', 0.8);
  scene.add(light);

  // The "camera" is what sees the stuff:
  var aspectRatio = window.innerWidth / window.innerHeight;
  var camera = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);
  camera.position.z = 500;
  scene.add(camera);

  // The "renderer" draws what the camera sees onto the screen:
  var renderer = new THREE.WebGLRenderer({antialias: true});
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);

  // ******** START CODING ON THE NEXT LINE ********
  var body = new THREE.SphereGeometry(100);
  var cover = new THREE.MeshNormalMaterial();
  var avatar = new THREE.Mesh(body,cover);
  scene.add(avatar);
  
  var hand = new THREE.SphereGeometry(50);
  
  var lhand = new THREE.Mesh(hand,cover);
  lhand.position.set(150,0,0);
  avatar.add(lhand);
  
  var rhand = new THREE.Mesh(hand,cover);
  rhand.position.set(-150,0,0);
  avatar.add(rhand);
  
  var foot = new THREE.SphereGeometry(50);
  
  var rfoot = new THREE.Mesh(foot,cover);
rfoot.position.set(-75,-125,0);
avatar.add(rfoot);

var lfoot = new THREE.Mesh(foot,cover);
lfoot.position.set(75,-125,0);
avatar.add(lfoot);

  // Now, animate what the camera sees on the screen:
  var isCartwheeling = false;
  var isFlipping = false;
  function animate() {
    requestAnimationFrame(animate);
    if (isCartwheeling) {
  avatar.rotation.z = avatar.rotation.z + 0.05;
    } 
    if (isFlipping) {
      avatar.rotation.x = avatar.rotation.x + 0.05;
    }
  renderer.render(scene,camera);
  }
  animate();
  document.addEventListener('keydown', sendKeyDown);
  function sendKeyDown(event) {
  var code = event.code;
  if (code == 'ArrowLeft') avatar.position.x = avatar.position.x - 5;
  if (code == 'ArrowRight') avatar.position.x = avatar.position.x + 5;
  if (code == 'ArrowUp') avatar.position.z = avatar.position.z - 5;
  if (code == 'ArrowDown') avatar.position.z = avatar.position.z + 5;
  }
</script>

Thanks. You definitely did code that 100% correctly. It works perfectly on Google Chrome and the latest Microsoft Edge browser.

I can help you get this code working, but you will have difficulty with the rest of the book – especially debugging – unless you switch to one of those browsers.

To get this code working on Internet Explorer, you’ll have to change the sendKeyDown() function definition. The old Internet Explorer does not use event.code – instead it uses event.key and its values are slightly different: 'Left' instead of 'ArrowLeft', 'Right' instead of 'ArrowRight', etc.

If you change sendKeyDown() as follows, it should work for you:

function sendKeyDown(event) {
  var code = event.key;
  if (code == 'Left') avatar.position.x = avatar.position.x - 5;
  if (code == 'Right') avatar.position.x = avatar.position.x + 5;
  if (code == 'Up') avatar.position.z = avatar.position.z - 5;
  if (code == 'Down') avatar.position.z = avatar.position.z + 5;
}

Later in the book, you’ll have to use 'c' and 'f' for pressing those keys and 'Spacebar' for pressing the spacebar.

But again, things will go much smoother if you can convince your parents to let you use a newer (more secure) browser.

-Chris

Thanks for the help, but I had now convinced them to download chrome. But Thanks for your help!:wink:

Great! Your programming will be much easier with Chrome. If you do run into any other issues, don’t hesitate to ask here :slight_smile:

-Chris

Hey, i have a new Problem.
In Chapter 10, i think I typed nothing wrong, but my avatar walk still throught tree’s.
Can you check my code?

<body></body>
<script src="/three.js"></script>
<script src="/tween.js"></script>
<script>
  // The "scene" is where stuff in our game will happen:
  var scene = new THREE.Scene();
  var flat = {flatShading: true};
  var light = new THREE.AmbientLight('white', 0.8);
  scene.add(light);

  // The "camera" is what sees the stuff:
  var aspectRatio = window.innerWidth / window.innerHeight;
  var camera = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);
  camera.position.z = 500;  scene.add(camera);

  // The "renderer" draws what the camera sees onto the screen:
  var renderer = new THREE.WebGLRenderer({antialias: true});
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);

  // ******** START CODING ON THE NEXT LINE ********

  var marker = new THREE.Object3D();
  scene.add(marker);

  var body = new THREE.SphereGeometry(100);
  var cover = new THREE.MeshNormalMaterial();
  var avatar = new THREE.Mesh(body,cover);
  marker.add(avatar);

  var hand = new THREE.SphereGeometry(50);

  var lhand = new THREE.Mesh(hand,cover);
  lhand.position.set(150,0,0);
  avatar.add(lhand);
  var rhand = new THREE.Mesh(hand,cover);
  rhand.position.set(-150,0,0);
  avatar.add(rhand);
  var foot = new THREE.SphereGeometry(50);

  var rfoot = new THREE.Mesh(foot,cover);
  rfoot.position.set(-75,-125,0);
  avatar.add(rfoot);

  var lfoot = new THREE.Mesh(foot,cover);
  lfoot.position.set(75,-125,0);
  avatar.add(lfoot);
  marker.add(camera);

  var notAllowed = [];
  function makeTreeAt (x,z) {
    var stamm = new THREE.CylinderGeometry(50,50,200);
    var rinde = new THREE.MeshBasicMaterial({color: 'sienna'});
    var krone = new THREE.SphereGeometry(150);
    var blatt = new THREE.MeshBasicMaterial({color: 'forestgreen'});

    var shape = new THREE.CircleGeometry(300);
    var cover = new THREE.MeshNormalMaterial();

    var trunk = new THREE.Mesh(stamm,rinde);
    var top = new THREE.Mesh(krone, blatt);

    top.position.y = 175;
    trunk.add(top);

    trunk.position.set(x, -75 , z);
    scene.add(trunk);

    var boundary = new THREE.Mesh(shape,cover);
    boundary.position.y = -100;
    boundary.rotation.x = -Math.PI/2;
    trunk.add(boundary);

    notAllowed.push(boundary);
  }

  makeTreeAt( 500, 0);
  makeTreeAt(-500, 0);
  makeTreeAt(750, -1000);
  makeTreeAt(-750,-1000);

  // Now, animate what the camera sees on the screen:
  var clock = new THREE.Clock();
  var isCartwheeling = false;
  var isFlipping = false;
  var isMovingRight = false;
  var isMovingLeft = false;
  var isMovingForward = false;
  var isMovingBack = false;
  var direction;
  var lastDirection;

  function animate() {
    requestAnimationFrame(animate);
    TWEEN.update();
    turn();
    walk();
    acrobatics();
    renderer.render(scene,camera);
  }
  animate();

  function turn() {
    if (isMovingRight) direction = Math.PI/2;
    if (isMovingLeft) direction = -Math.PI/2;
    if (isMovingForward) direction = Math.PI;
    if (isMovingBack) direction = 0;
    if (!isWalking()) direction = 0;

    if (direction == lastDirection) return;
    lastDirection = direction;
    var tween = new TWEEN.Tween(avatar.rotation);
    tween.to({y: direction}, 1000);
    tween.start();
  }

  function acrobatics() {
    if (isCartwheeling) {
     avatar.rotation.z = avatar.rotation.z + 0.05;
    } 
    if (isFlipping) {
      avatar.rotation.x = avatar.rotation.x + 0.05;
    }
  }

  function walk() {
    if (!isWalking()) return;
    var speed = 10;
    var size = 100;
    var time = clock.getElapsedTime();
    var position = Math.sin(speed*time)*size;
 
    rhand.position.z = position;
    lhand.position.z = -position;
    rfoot.position.z = -position;
    lfoot.position.z = position;
  }

  function isWalking() {
    if (isMovingRight) return true;
    if (isMovingLeft) return true;
    if (isMovingForward) return true;
    if (isMovingBack) return true;
    return false;
  }

  function isColliding() {
    var vector = new THREE.Vector3(0,-1,0);
    var raycaster = new THREE.Raycaster(marker.position, vector);

    var intersects = raycaster.intertersectObjects(notAllowed);
    if (intersects.length > 0) return true;

    return false;
  }

  document.addEventListener('keydown', sendKeyDown);
  function sendKeyDown(event) {
    var code = event.code;
    if (code == 'ArrowLeft') { 
      marker.position.x = marker.position.x - 5;
      isMovingLeft = true;
    }
    if (code == 'ArrowRight') {
      marker.position.x = marker.position.x + 5;
      isMovingRight = true; 
    }
    if (code == 'ArrowUp') {
      marker.position.z = marker.position.z - 5;
      isMovingForward = true;
    }
    if (code == 'ArrowDown')  {
      marker.position.z = marker.position.z + 5;
      isMovingBack = true; 
    }
    if (code == 'KeyC') isCartwheeling = !isCartwheeling;
    if (code == 'KeyF') isFlipping = !isFlipping;

    if (isColliding()) {
      if (isMovingLeft) marker.position.x = marker.position.x + 5;
      if (isMovingRight) marker.position.x = marker.postion.x - 5;
      if (isMovingForward) marker.position.z = marker.position.z + 5;
      if (isMovingBack) marker.position.z = marker.position.z - 5;
    }
  };

  document.addEventListener('keyup',sendKeyUp);
  function sendKeyUp(event) {
    var code = event.code;
    if (code == 'ArrowLeft') isMovingLeft = false;
    if (code == 'ArrowRight') isMovingRight = false;
    if (code == 'ArrowUp') isMovingForward = false;
    if (code == 'ArrowDown') isMovingBack = false;
  }
</script>

In the console is no mistake…
Thx for you help :wink:

There was a problem with posting the code in the forum. I’ve tried to edit it and hopefully I’ve got it exactly as you have it in 3DE. If you post again, be sure to follow the instructions in this post.

There were two minor typos that I found. Both showed up in the JavaScript console, but to see it in the console you had to:

  1. Hide the code
  2. Move the avatar into the tree boundary
  3. Then check the JavaScript console

It is not easy to figure out how to debug like that, but maybe give it a try – debugging code is fun :slight_smile:

The first typo is in the isColliding() function. The code has intertersectObjects() but it should be intersectObjects() instead:

  function isColliding() {
    var vector = new THREE.Vector3(0,-1,0);
    var raycaster = new THREE.Raycaster(marker.position, vector);

    var intersects = raycaster.intertersectObjects(notAllowed);
    if (intersects.length > 0) return true;

    return false;
  }

The second typo is near the bottom of sendKeyDown(). There is a postion instead of position in:

    if (isColliding()) {
      if (isMovingLeft) marker.position.x = marker.position.x + 5;
      if (isMovingRight) marker.position.x = marker.postion.x - 5;
      if (isMovingForward) marker.position.z = marker.position.z + 5;
      if (isMovingBack) marker.position.z = marker.position.z - 5;
    }

Hope that helps!

-Chris

Hey, thanks for your help!
But now i have another issue with a tween code :sob:
I programmed the avatar game and i´ve added some little things. But when I jump very often in a short time, so I hit the space key so often as I can, the game crashes. Idk what the console like to say. Please have a look! Look pls for my Function animateMinus.
My code:

<body></body>
<script src="/three.js"></script>
<script src="/tween.js"></script>
<script src="/scoreboard.js"></script>
<script src="/sounds.js"></script>
<script>
  // The "scene" is where stuff in our game will happen:
  var scene = new THREE.Scene();
  var flat = {flatShading: true};
  var light = new THREE.AmbientLight('white', 0.8);
  scene.add(light);

  // The "camera" is what sees the stuff:
  var aspectRatio = window.innerWidth / window.innerHeight;
  var camera = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);
  camera.position.z = 500;
  scene.add(camera);

  // The "renderer" draws what the camera sees onto the screen:
  var renderer = new THREE.WebGLRenderer({antialias: true});
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);

  // ******** START CODING ON THE NEXT LINE ********
  var marker = new THREE.Object3D();
  scene.add(marker);
  
  var body = new THREE.SphereGeometry(100);
  var cover = new THREE.MeshNormalMaterial();
  var avatar = new THREE.Mesh(body,cover);
  marker.add(avatar);
  
  var hand = new THREE.SphereGeometry(50);
  
  var lhand = new THREE.Mesh(hand,cover);
  lhand.position.set(150,0,0);
  avatar.add(lhand);
  
  var rhand = new THREE.Mesh(hand,cover);
  rhand.position.set(-150,0,0);
  avatar.add(rhand);
  
  var foot = new THREE.SphereGeometry(50);
  
  var rfoot = new THREE.Mesh(foot,cover);
rfoot.position.set(-75,-125,0);
avatar.add(rfoot);

var lfoot = new THREE.Mesh(foot,cover);
lfoot.position.set(75,-125,0);
avatar.add(lfoot);
marker.add(camera);

var neck = new THREE.CylinderGeometry(50,50,50)

/*var avatarhead = new THREE.SphereGeometry(75);
var head = new THREE.Mesh(avatarhead, cover);
avatar.add(head);
head.position.set(0,150,0);*/

var scoreboard = new Scoreboard();
scoreboard.countdown(45);
scoreboard.score();
scoreboard.help(
  'Pfeiltasten zum Bewegen. '+
  'Leertaste zum Springen nach dem Obst. '+
  'Achte auf wackelnde Bäume mit Obst. '+
  'Gehe zum Baum und springe, bevor das Obst verschwunden ist!'
  );
  
  scoreboard.onTimeExpired(timeExpired);
  function timeExpired() {
    scoreboard.message('Das Spiel ist aus!');
    removeAvatar();
    timeExpiredsuper();
  }
  
 
  function timeExpiredsuper() {
    if (scoreboard.score > 300) scoreboard.message('Das Spiel ist aus!'+
    '    Du bist klasse!');
  }

var notAllowed = [];
var TreeTops = [];

function makeTreeAt (x,z) {
  var stamm = new THREE.CylinderGeometry(50,50,200);
  var rinde = new THREE.MeshBasicMaterial({color: 'sienna'});
  
  var krone = new THREE.SphereGeometry(150);
  var blatt = new THREE.MeshBasicMaterial({color: 'forestgreen'});
  
  var shape = new THREE.CircleGeometry(300);
  var cover = new THREE.MeshNormalMaterial();
    
  var trunk = new THREE.Mesh(stamm,rinde);
  
    var top = new THREE.Mesh(krone, blatt);
      top.position.y = 175;
      trunk.add(top);
      
      trunk.position.set(x, -75 , z);
      scene.add(trunk);
      
      var boundary = new THREE.Mesh(shape,cover);
    boundary.position.y = -100;
    boundary.rotation.x = -Math.PI/2;
      trunk.add(boundary);
      
      notAllowed.push(boundary);
      TreeTops.push(top);
}
makeTreeAt( 500, 0);
makeTreeAt(-500, 0);
makeTreeAt(750, -1000);
makeTreeAt(-750,-1000);
makeTreeAt(1000, -2000);
makeTreeAt(-1000, -2000);

var treasureTreeNumber;
function updateTreasureTreeNumber() {
  var rand = Math.random() * TreeTops.length;
  treasureTreeNumber = Math.floor(rand);
}

function shakeTreasureTree() {
  updateTreasureTreeNumber();
  if (scoreboard.getTimeRemaining() == 0) return;
  
  var tween = new TWEEN.Tween({shake: 0});
  tween.to({shake: 20 * 2 * Math.PI}, 8*1000);
  tween.onUpdate(shakeTreeUpdate);
  tween.onComplete(shakeTreeComplete);
  tween.start();
}

function shakeTreeUpdate(update) {
  var top = TreeTops[treasureTreeNumber];
  top.position.x = 50 * Math.sin(update.shake);
}

function shakeTreeComplete() {
  var top = TreeTops[treasureTreeNumber];
  top.position.x = 0;
  setTimeout(shakeTreasureTree, 2*1000);
}

shakeTreasureTree();
  // Now, animate what the camera sees on the screen:
  var clock = new THREE.Clock();
  var isCartwheeling = false;
  var isFlipping = false;
  var isMovingRight = false;
  var isMovingLeft = false;
  var isMovingForward = false;
  var isMovingBack = false;
  var direction;
  var lastDirection;
  
  function animate() {
    requestAnimationFrame(animate);
    TWEEN.update();
    turn();
    walk();
    acrobatics();
  renderer.render(scene,camera);
  }
  animate();
  
  function turn() {
    if (isMovingRight) direction = Math.PI/2;
    if (isMovingLeft) direction = -Math.PI/2;
    if (isMovingForward) direction = Math.PI;
    if (isMovingBack) direction = 0;
    if (!isWalking()) direction = 0;
    
   if (direction == lastDirection) return;
   lastDirection = direction;
   var tween = new TWEEN.Tween(avatar.rotation);
   tween.to({y: direction}, 1000);
   tween.start();
  }
  
  function acrobatics() {
    if (isCartwheeling) {
  avatar.rotation.z = avatar.rotation.z + 0.05;
    } 
    if (isFlipping) {
      avatar.rotation.x = avatar.rotation.x + 0.05;
    }
  }
  
  function walk() {
    if (!isWalking()) return;
    
    var speed = 10;
    var size = 100;
    var time = clock.getElapsedTime();
    var position = Math.sin(speed*time)*size;
    rhand.position.z = position;
    lhand.position.z = -position;
    rfoot.position.z = -position;
    lfoot.position.z = position;
  }
  
  function isWalking() {
    if (isMovingRight) return true;
    if (isMovingLeft) return true;
    if (isMovingForward) return true;
    if (isMovingBack) return true;
    return false;
  }
  function isColliding() {
    var vector = new THREE.Vector3(0,-1,0);
    var raycaster = new THREE.Raycaster(marker.position, vector);
    
    var intersects = raycaster.intersectObjects(notAllowed);
    if (intersects.length > 0) return true;
    
    return false;
  }
  
  function jump() {
  if (avatar.position.y > 0) return;
  checkForTreasure();
  animateJump();
  }
  
  function checkForTreasure() {
    var top = TreeTops[treasureTreeNumber];
    var tree = top.parent;
    var p1 = tree.position;
    var p2  = marker.position;
    var xDiff = p1.x - p2.x;
    var zDiff = p1.z - p2.z;
    var distance = Math.sqrt(xDiff*xDiff + zDiff*zDiff);
    if (distance < 500) scorePoints();
    if (distance > 1000) subtractPoints();
  }
  
  function scorePoints() {
    if (scoreboard.getTimeRemaining() == 0) return;
    scoreboard.addPoints(10);
    Sounds.bubble.play();
    animateFruit();
  }
  
  function subtractPoints() {
    if (scoreboard.getTimeRemaining() == 0) return;
    scoreboard.subtractPoints(5);
    Sounds.scratch.play();
    animateMinus();
  }
  
  var fruit;
  function animateFruit() {
    if (fruit) return;
    
    fruit = new THREE.Mesh(
      new THREE.CylinderGeometry(25,25,5,25),
      new THREE.MeshBasicMaterial({color: 'gold'})
      );
      marker.add(fruit);
      
      var tween = new TWEEN.Tween({height: 200, spin: 0});
      tween.to({height: 350, spin: 2 * Math.PI}, 500);
      tween.onUpdate(animateFruitUpdate);
      tween.onComplete(animateFruitComplete);
      tween.start();
  }
  
  function animateFruitUpdate(update) {
    fruit.position.y = update.height;
    fruit.rotation.x = update.spin;
  }
  
  function animateFruitComplete() {
    marker.remove(fruit);
    fruit = undefined;
  }
  
  var Minus;
   function animateMinus() {
     Minus = new THREE.Mesh(
       new THREE.CubeGeometry(30,10,10), 
       new THREE.MeshBasicMaterial({color: 'red'}));
       marker.add(Minus);
       
       var tween = new TWEEN.Tween({height: 200});
       tween.to({height: 350}, 500);
       tween.onUpdate(animateMinusUpdate);
       tween.onComplete(animateMinusComplete);
       tween.start();
   }
   
   function animateMinusUpdate(update) {
     Minus.position.y = update.height;
   }
   
   function animateMinusComplete() {
     marker.remove(Minus);
     Minus = undefined;
   }
  
  function animateJump() {
    var tween = new TWEEN.Tween({jump: 0});
    tween.to({jump: Math.PI}, 400);
    tween.onUpdate(animateJumpUpdate);
    tween.onComplete(animateJumpComplete);
    tween.start();
  }
  
  function animateJumpUpdate(update) {
    avatar.position.y = 100 * Math.sin(update.jump);
  }
  
  function animateJumpComplete() {
    avatar.position.y = 0;
  }  
  
  function removeAvatar() {
    marker.remove(avatar);
  }
  
  document.addEventListener('keydown', sendKeyDown);
  function sendKeyDown(event) {
  var code = event.code;
  if (code == 'ArrowLeft') { 
  marker.position.x = marker.position.x - 10;
  isMovingLeft = true;
  }
  if (code == 'ArrowRight') {
   marker.position.x = marker.position.x + 10;
  isMovingRight = true; 
  }
  if (code == 'ArrowUp') {
  marker.position.z = marker.position.z - 10;
  isMovingForward = true;
  }
  if (code == 'ArrowDown')  {
  marker.position.z = marker.position.z + 10;
  isMovingBack = true;
  }
  
  
  if (code == 'KeyC') isCartwheeling = !isCartwheeling;
  if (code == 'KeyF') isFlipping = !isFlipping;
  if (code == 'Space') jump();
  
  if (isColliding()) {
  if (isMovingLeft) marker.position.x = marker.position.x + 10;
  if (isMovingRight) marker.position.x = marker.position.x - 10;
  if (isMovingForward) marker.position.z = marker.position.z + 10;
  if (isMovingBack) marker.position.z = marker.position.z - 10;
  }
  };
  
  document.addEventListener('keyup',sendKeyUp);
  function sendKeyUp(event) {
    var code = event.code;
    if (code == 'ArrowLeft') isMovingLeft = false;
    if (code == 'ArrowRight') isMovingRight = false;
    if (code == 'ArrowUp') isMovingForward = false;
    if (code == 'ArrowDown') isMovingBack = false;
  }
</script>

Thank You!

First, it’s fantastic that you’re experimenting with your own code like this. Keep it up!

You have stumbled across a difficult bug that causes problems for experienced programmers. It’s so difficult that it gets its own name: “race condition.” It’s probably easiest to show you how to fix it first. Add the if statement to the first line of the animateMinus() function as shown:

  var Minus;
  function animateMinus() {
    if (Minus) return;
     
    Minus = new THREE.Mesh(
      new THREE.CubeGeometry(30,10,10), 
      new THREE.MeshBasicMaterial({color: 'red'}));
    marker.add(Minus);
       
    var tween = new TWEEN.Tween({height: 200});
    tween.to({height: 350}, 500);
    tween.onUpdate(animateMinusUpdate);
    tween.onComplete(animateMinusComplete);
    tween.start();
  }

What this says is that if Minus is already defined, then return immediately from animateMinus(). That is, if Minus is already defined, then don’t animate the minus sign.

The reason that you want to do this is that animateMinusUpdate() and animateMinusComplete() both try to do something to Minus. They “race” to update it. And, if you hit the spacebar quickly enough – as you found – the winner of that “race” causes bugs.

The “race” is this:

  1. Hit the spacebar, which creates a Minus
  2. Hit the spacebar again before the minus is done being animated
  3. The first minus animation completes, which sets Minus to undefined
  4. The second minus animation continues, but when it tries to set the position of Minus, it finds that Minus is undefined!

Hopefully that makes sense, but do not worry if you find it confusing. Race conditions are really tricky bugs – even for experienced programmers. The good news is that programmers get experience by finding bugs like this and finding solutions. So keep trying to write cool code like this :slight_smile:

If you have any other questions about this or anything else, keep asking your very good questions!

-Chris

Thanks for your help!
I have another issue…

My code:

<body></body>
<script src="/three.js"></script>
<script src="/tween.js"></script>
<script src="/scoreboard.js"></script>
<script src="/sounds.js"></script>
<script>
  // The "scene" is where stuff in our game will happen:
  var scene = new THREE.Scene();
  var flat = {flatShading: true};
  var light = new THREE.AmbientLight('white', 0.8);
  scene.add(light);

  // The "camera" is what sees the stuff:
  var aspectRatio = window.innerWidth / window.innerHeight;
  var camera = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);
  camera.position.z = 500;
  scene.add(camera);

  // The "renderer" draws what the camera sees onto the screen:
  var renderer = new THREE.WebGLRenderer({antialias: true});
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);

  // ******** START CODING ON THE NEXT LINE ********
  var marker = new THREE.Object3D();
  scene.add(marker);
  
  var body = new THREE.SphereGeometry(100);
  var cover = new THREE.MeshNormalMaterial();
  var avatar = new THREE.Mesh(body,cover);
  marker.add(avatar);
  
  var hand = new THREE.SphereGeometry(50);
  
  var lhand = new THREE.Mesh(hand,cover);
  lhand.position.set(150,0,0);
  avatar.add(lhand);
  
  var rhand = new THREE.Mesh(hand,cover);
  rhand.position.set(-150,0,0);
  avatar.add(rhand);
  
  var foot = new THREE.SphereGeometry(50);
  
  var rfoot = new THREE.Mesh(foot,cover);
rfoot.position.set(-75,-125,0);
avatar.add(rfoot);

var lfoot = new THREE.Mesh(foot,cover);
lfoot.position.set(75,-125,0);
avatar.add(lfoot);
marker.add(camera);

var scoreboard = new Scoreboard();
scoreboard.countdown(45);
scoreboard.score();
scoreboard.help(
  'Pfeiltasten zum Bewegen. '+
  'Leertaste zum Springen nach dem Obst. '+
  'Achte auf wackelnde Bäume mit Obst. '+
  'Gehe zum Baum und springe, bevor das Obst verschwunden ist!'
  );
  
  scoreboard.onTimeExpired(timeExpired);
  function timeExpired() {
   scoreboard.message('Das Spiel ist aus!');
    removeAvatar();
    timeExpiredSuper();
  }
  

  function timeExpiredsuper() {
    if (scoreboard.score() > 100) scoreboard.message('Das Spiel ist aus!'+
    '    Du bist klasse!');
  }

var notAllowed = [];
var TreeTops = [];

function makeTreeAt (x,z) {
  var stamm = new THREE.CylinderGeometry(50,50,200);
  var rinde = new THREE.MeshBasicMaterial({color: 'sienna'});
  
  var krone = new THREE.SphereGeometry(150);
  var blatt = new THREE.MeshBasicMaterial({color: 'forestgreen'});
  
  var shape = new THREE.CircleGeometry(300);
  var cover = new THREE.MeshNormalMaterial();
    
  var trunk = new THREE.Mesh(stamm,rinde);
  
    var top = new THREE.Mesh(krone, blatt);
      top.position.y = 175;
      trunk.add(top);
      
      trunk.position.set(x, -75 , z);
      scene.add(trunk);
      
      var boundary = new THREE.Mesh(shape,cover);
    boundary.position.y = -100;
    boundary.rotation.x = -Math.PI/2;
      trunk.add(boundary);
      
      notAllowed.push(boundary);
      TreeTops.push(top);
}
makeTreeAt( 500, 0);
makeTreeAt(-500, 0);
makeTreeAt(750, -1000);
makeTreeAt(-750,-1000);
makeTreeAt(1000, -2000);
makeTreeAt(-1000, -2000);

var treasureTreeNumber;
function updateTreasureTreeNumber() {
  var rand = Math.random() * TreeTops.length;
  treasureTreeNumber = Math.floor(rand);
}

function shakeTreasureTree() {
  updateTreasureTreeNumber();
  if (scoreboard.getTimeRemaining() == 0) return;
  
  var tween = new TWEEN.Tween({shake: 0});
  tween.to({shake: 20 * 2 * Math.PI}, 8*1000);
  tween.onUpdate(shakeTreeUpdate);
  tween.onComplete(shakeTreeComplete);
  tween.start();
}

function shakeTreeUpdate(update) {
  var top = TreeTops[treasureTreeNumber];
  top.position.x = 50 * Math.sin(update.shake);
}

function shakeTreeComplete() {
  var top = TreeTops[treasureTreeNumber];
  top.position.x = 0;
  setTimeout(shakeTreasureTree, 2*1000);
}

shakeTreasureTree();
  // Now, animate what the camera sees on the screen:
  var clock = new THREE.Clock();
  var isCartwheeling = false;
  var isFlipping = false;
  var isMovingRight = false;
  var isMovingLeft = false;
  var isMovingForward = false;
  var isMovingBack = false;
  var direction;
  var lastDirection;
  
  function animate() {
    requestAnimationFrame(animate);
    TWEEN.update();
    turn();
    walk();
    acrobatics();
  renderer.render(scene,camera);
  }
  animate();
  
  function turn() {
    if (isMovingRight) direction = Math.PI/2;
    if (isMovingLeft) direction = -Math.PI/2;
    if (isMovingForward) direction = Math.PI;
    if (isMovingBack) direction = 0;
    if (!isWalking()) direction = 0;
    
   if (direction == lastDirection) return;
   lastDirection = direction;
   var tween = new TWEEN.Tween(avatar.rotation);
   tween.to({y: direction}, 1000);
   tween.start();
  }
  
  function acrobatics() {
    if (isCartwheeling) {
  avatar.rotation.z = avatar.rotation.z + 0.05;
    } 
    if (isFlipping) {
      avatar.rotation.x = avatar.rotation.x + 0.05;
    }
  }
  
  function walk() {
    if (!isWalking()) return;
    
    var speed = 10;
    var size = 100;
    var time = clock.getElapsedTime();
    var position = Math.sin(speed*time)*size;
    rhand.position.z = position;
    lhand.position.z = -position;
    rfoot.position.z = -position;
    lfoot.position.z = position;
  }
  
  function isWalking() {
    if (isMovingRight) return true;
    if (isMovingLeft) return true;
    if (isMovingForward) return true;
    if (isMovingBack) return true;
    return false;
  }
  function isColliding() {
    var vector = new THREE.Vector3(0,-1,0);
    var raycaster = new THREE.Raycaster(marker.position, vector);
    
    var intersects = raycaster.intersectObjects(notAllowed);
    if (intersects.length > 0) return true;
    
    return false;
  }
  
  function jump() {
  if (avatar.position.y > 0) return;
  checkForTreasure();
  animateJump();
  }
  
  function checkForTreasure() {
    var top = TreeTops[treasureTreeNumber];
    var tree = top.parent;
    var p1 = tree.position;
    var p2  = marker.position;
    var xDiff = p1.x - p2.x;
    var zDiff = p1.z - p2.z;
    var distance = Math.sqrt(xDiff*xDiff + zDiff*zDiff);
    if (distance < 500) scorePoints();
    if (distance > 1000) subtractPoints();
  }
  
  function scorePoints() {
    if (scoreboard.getTimeRemaining() == 0) return;
    scoreboard.addPoints(10);
    Sounds.bubble.play();
    animateFruit();
  }
  
  function subtractPoints() {
    if (scoreboard.getTimeRemaining() == 0) return;
    scoreboard.subtractPoints(5);
    Sounds.scratch.play();
    animateMinus();
  }
  
  var fruit;
  function animateFruit() {
    if (fruit) return;
    
    fruit = new THREE.Mesh(
      new THREE.CylinderGeometry(25,25,5,25),
      new THREE.MeshBasicMaterial({color: 'gold'})
      );
      marker.add(fruit);
      
      var tween = new TWEEN.Tween({height: 200, spin: 0});
      tween.to({height: 350, spin: 2 * Math.PI}, 500);
      tween.onUpdate(animateFruitUpdate);
      tween.onComplete(animateFruitComplete);
      tween.start();
  }
  
  function animateFruitUpdate(update) {
    fruit.position.y = update.height;
    fruit.rotation.x = update.spin;
  }
  
  function animateFruitComplete() {
    marker.remove(fruit);
    fruit = undefined;
  }
  
  var Minus;
  function animateMinus() {
    if (Minus) return;
     
    Minus = new THREE.Mesh(
      new THREE.CubeGeometry(30,10,10), 
      new THREE.MeshBasicMaterial({color: 'red'}));
    marker.add(Minus);
       
    var tween = new TWEEN.Tween({height: 200});
    tween.to({height: 350}, 500);
    tween.onUpdate(animateMinusUpdate);
    tween.onComplete(animateMinusComplete);
    tween.start();
  }
       
   function animateMinusUpdate(update) {
     Minus.position.y = update.height;
   }
   
   function animateMinusComplete() {
     marker.remove(Minus);
     Minus = undefined;
   }
  
  function animateJump() {
    var tween = new TWEEN.Tween({jump: 0});
    tween.to({jump: Math.PI}, 400);
    tween.onUpdate(animateJumpUpdate);
    tween.onComplete(animateJumpComplete);
    tween.start();
  }
  
  function animateJumpUpdate(update) {
    avatar.position.y = 100 * Math.sin(update.jump);
  }
  
  function animateJumpComplete() {
    avatar.position.y = 0;
  }  
  
  function removeAvatar() {
    marker.remove(avatar);
  }
  
  document.addEventListener('keydown', sendKeyDown);
  function sendKeyDown(event) {
  var code = event.code;
  if (code == 'ArrowLeft') { 
  marker.position.x = marker.position.x - 10;
  isMovingLeft = true;
  }
  if (code == 'ArrowRight') {
   marker.position.x = marker.position.x + 10;
  isMovingRight = true; 
  }
  if (code == 'ArrowUp') {
  marker.position.z = marker.position.z - 10;
  isMovingForward = true;
  }
  if (code == 'ArrowDown')  {
  marker.position.z = marker.position.z + 10;
  isMovingBack = true;
  }
  
  
  if (code == 'KeyC') isCartwheeling = !isCartwheeling;
  if (code == 'KeyF') isFlipping = !isFlipping;
  if (code == 'Space') jump();
  
  if (isColliding()) {
  if (isMovingLeft) marker.position.x = marker.position.x + 10;
  if (isMovingRight) marker.position.x = marker.position.x - 10;
  if (isMovingForward) marker.position.z = marker.position.z + 10;
  if (isMovingBack) marker.position.z = marker.position.z - 10;
  }
  };
  
  document.addEventListener('keyup',sendKeyUp);
  function sendKeyUp(event) {
    var code = event.code;
    if (code == 'ArrowLeft') isMovingLeft = false;
    if (code == 'ArrowRight') isMovingRight = false;
    if (code == 'ArrowUp') isMovingForward = false;
    if (code == 'ArrowDown') isMovingBack = false;
  }
</script>

So the problem is the function TimeExpiredsuper,
I would like to say if you have over 300 points (in this code I used 100 for testing the code) “du bist Klasse” ( that´s german) and if you havn´t about 300 Points it should say only “Das Spiel ist aus”. Hopefully you understud my problem;

Thanks for your help!

Oof sry i forgot the three ```, now I understud the issue with the posting of the code;
Sorry

Again, you are doing some nice advanced work! We will do very similar things to this later in the book. I think what you are looking for is:

  function timeExpiredsuper() {
    var message = 'Das Spiel ist aus!';
    if (scoreboard.score() > 300) message = message + '  Du bist klasse!';

    scoreboard.message(message);
  }

This way, the timeExpiredsuper() function will always set the message to the message variable. The message variable will always include 'Das Spiel ist aus!'. If the score is over 300, then the message variable will also include 'Du bist klasse!'.

Let me know if you wanted something different.

-Chris

Hey,
I´ve a problem in Chapter 12.
I can´t see a shadow… I don´t know what´s wrong.

My Code:

//<body></body>
<script src="/three.js"></script>
<script>
  // The "scene" is where stuff in our game will happen:
  var scene = new THREE.Scene();
  var flat = {flatShading: true};
  var light = new THREE.AmbientLight('white', 0.1);
  scene.add(light);

  // The "camera" is what sees the stuff:
  var aspectRatio = window.innerWidth / window.innerHeight;
  var camera = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);
  camera.position.z = 500;
  camera.position.z = 500;
  camera.lookAt(new THREE.Vector3(0,0,0));
  scene.add(camera);

  // The "renderer" draws what the camera sees onto the screen:
  var renderer = new THREE.WebGLRenderer({antialias: true});
  renderer.shadowMap.enabled = true;
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);

  // ******** START CODING ON THE NEXT LINE ********
  var shape = new THREE.TorusGeometry(50,20,8,20);
  var cover = new THREE.MeshPhongMaterial({color: 'red'});
  cover.specular.setRGB(0.9,0.9,0.9);
  var donut = new THREE.Mesh(shape, cover);
  donut.position.set(0, 150,0);
  donut.castShadow = true;
  scene.add(donut);
  
  var shape = new THREE.PlaneGeometry(1000,1000,10,10);
  var cover = new THREE.MeshPhongMaterial();
  var ground = new THREE.Mesh(shape,cover);
  ground.rotation.x = -Math.PI/2;
  ground.receiveShadow = true;
  scene.add(ground);
  
  var point = new THREE.PointLight('white',0.8);
  point.position.set(0,300, -100);
  point.castShadow = true;
  scene.add(point);
  
  var shape = new THREE.SphereGeometry(10);
  var cover = new THREE.MeshPhongMaterial({emissive: 'white'});
  var phonyLight = new THREE.Mesh(shape,cover);
  scene.add(phonyLight);
  //phonyLight.position.set(0,300,-100);
  
  




  // Start Animation

  var clock = new THREE.Clock();
  function animate() {
    requestAnimationFrame(animate);
    var t = clock.getElapsedTime();

    // Animation code goes here...
    donut.rotation.set(t, 2*t, 0);
    
    renderer.render(scene, camera);
  }

  animate();
</script>

Thanks for your help

And another Thing: I want to add an boost to the game of chapter 11, I need another Scoreboard (or can I add another score to the scoreboard(with a custom Name)?) to show how many boosts left.

Thanks

Your code is actually working correctly and drawing the shadows – you just cannot see them. The problem is on line 14. Line 13 and 14 are identical:

  camera.position.z = 500;
  camera.position.z = 500;

That pushes the camera back on the Z axis to the 500 marker – twice. The second line should move the camera up 500 along the Y axis:

  camera.position.z = 500;
  camera.position.y = 500;

With that, the camera is back from and above the center of the scene, looking down on the scene and the ground. When the camera is not above the ground, we look right at the edge of the plane, which is too small for the computer to draw. It is as if the ground is not even in the scene. And if you cannot see the ground, you cannot see the shadows on the ground.

-Chris

You can add another scoreboard, but I am unsure if it will work the way you want. To add another scoreboard, you would do something like:

var scoreboard2 = new Scoreboard({location: 'bottomright'});
scoreboard2.message('Hi there!');
scoreboard2.score();

That would create a second scoreboard on the bottom-right of the screen with a message and a place to keep score.

This probably will not work for what you are describing because the score is always shown as “Score:” – there is no way to label it “Boosts:”, for example. Still, it may be worth giving it a try.

-Chris

Hey i’ve another thing. Now the avatar can’t boost anymore into tree’s, so I would like to write a Tween what “teleports” the into the sky(so like the golden Fruit) and the avatar should appear at the new position. How can I do that?

Code:

//<body></body>
<script src="/three.js"></script>
<script src="/tween.js"></script>
<script src="/scoreboard.js"></script>
<script src="/sounds.js"></script>
<script>
  // The "scene" is where stuff in our game will happen:
  var scene = new THREE.Scene();
  var flat = {flatShading: true};
  var light = new THREE.AmbientLight('white', 0.8);
  scene.add(light);

  // The "camera" is what sees the stuff:
  var aspectRatio = window.innerWidth / window.innerHeight;
  var camera = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);
  camera.position.z = 500;
  scene.add(camera);

  // The "renderer" draws what the camera sees onto the screen:
  var renderer = new THREE.WebGLRenderer({antialias: true});
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);

  // ******** START CODING ON THE NEXT LINE ********
  var marker = new THREE.Object3D();
  scene.add(marker);
  
  var body = new THREE.SphereGeometry(100);
  var cover = new THREE.MeshNormalMaterial();
  var avatar = new THREE.Mesh(body,cover);
  marker.add(avatar);
  
  var hand = new THREE.SphereGeometry(50);
  
  var lhand = new THREE.Mesh(hand,cover);
  lhand.position.set(150,0,0);
  avatar.add(lhand);
  
  var rhand = new THREE.Mesh(hand,cover);
  rhand.position.set(-150,0,0);
  avatar.add(rhand);
  
  var foot = new THREE.SphereGeometry(50);
  
  var rfoot = new THREE.Mesh(foot,cover);
rfoot.position.set(-75,-125,0);
avatar.add(rfoot);

var lfoot = new THREE.Mesh(foot,cover);
lfoot.position.set(75,-125,0);
avatar.add(lfoot);
marker.add(camera);

var scoreboard = new Scoreboard();
scoreboard.countdown(45);
scoreboard.score();
scoreboard.help(
  'Pfeiltasten zum Bewegen. '+
  'Leertaste zum Springen nach dem Obst. '+
  'Achte auf wackelnde Bäume mit Obst. '+
  'Gehe zum Baum und springe, bevor das Obst verschwunden ist!'
  );
  
  scoreboard.onTimeExpired(timeExpired);
  function timeExpired() {
    scoreboard.message('Das Spiel ist aus!');
    removeAvatar();
    timeExpiredsuper();
  }
  
 scoreboard.onTimeExpired(timeExpired);
  function timeExpiredsuper() {
    if (scoreboard.score < 300) return; 
    if (scoreboard.score > 300) scoreboard.message('Das Spiel ist aus!'+
    '    Du bist klasse!');
  }

 var scoreboard2 = new Scoreboard({location: 'bottomright'});
scoreboard2.score(3);
scoreboard2.countdown(1);
scoreboard2.message('Drücke B um in die Richtung zu boosten, in die du gehst zu boosten.');
scoreboard2.help(
  'Dies ist der Prototyp 1.1 der Boost Funktion! Booste mit B in die Richtung in die du gehst. Dies kannst du 3 mal machen wärend eines Spiels. Es gibt einen 5-Sekündigen Cooldown.');

function boost() {
  if (scoreboard2.getTimeRemaining() > 0) return;
  if (scoreboard2.getScore() == 0) return;
  if (!isWalking()) return;
  scoreboard2.countdown(5);
  if (isMovingRight) marker.position.x = marker.position.x + 300;
  if (isMovingLeft) marker.position.x = marker.position.x - 300;
  if (isMovingForward) marker.position.z = marker.position.z - 300;
  if (isMovingBack) marker.position.z = marker.position.z + 300;
  scoreboard2.subtractPoints(1);
  
  
}

var notAllowed = [];
var TreeTops = [];

function makeTreeAt (x,z) {
  var stamm = new THREE.CylinderGeometry(50,50,200);
  var rinde = new THREE.MeshBasicMaterial({color: 'sienna'});
  
  var krone = new THREE.SphereGeometry(150);
  var blatt = new THREE.MeshBasicMaterial({color: 'forestgreen'});
  
  var shape = new THREE.CircleGeometry(300);
  var cover = new THREE.MeshNormalMaterial();
    
  var trunk = new THREE.Mesh(stamm,rinde);
  
    var top = new THREE.Mesh(krone, blatt);
      top.position.y = 175;
      trunk.add(top);
      
      trunk.position.set(x, -75 , z);
      scene.add(trunk);
      
      var boundary = new THREE.Mesh(shape,cover);
    boundary.position.y = -100;
    boundary.rotation.x = -Math.PI/2;
      trunk.add(boundary);
      
      notAllowed.push(boundary);
      TreeTops.push(top);
}
makeTreeAt( 500, 0);
makeTreeAt(-500, 0);
makeTreeAt(750, -1000);
makeTreeAt(-750,-1000);
makeTreeAt(1000, -2000);
makeTreeAt(-1000, -2000);

var treasureTreeNumber;
function updateTreasureTreeNumber() {
  var rand = Math.random() * TreeTops.length;
  treasureTreeNumber = Math.floor(rand);
}

function shakeTreasureTree() {
  updateTreasureTreeNumber();
  if (scoreboard.getTimeRemaining() == 0) return;
  
  var tween = new TWEEN.Tween({shake: 0});
  tween.to({shake: 20 * 2 * Math.PI}, 8*1000);
  tween.onUpdate(shakeTreeUpdate);
  tween.onComplete(shakeTreeComplete);
  tween.start();
}

function shakeTreeUpdate(update) {
  var top = TreeTops[treasureTreeNumber];
  top.position.x = 50 * Math.sin(update.shake);
}

function shakeTreeComplete() {
  var top = TreeTops[treasureTreeNumber];
  top.position.x = 0;
  setTimeout(shakeTreasureTree, 2*1000);
}

shakeTreasureTree();
  // Now, animate what the camera sees on the screen:
  var clock = new THREE.Clock();
  var isCartwheeling = false;
  var isFlipping = false;
  var isMovingRight = false;
  var isMovingLeft = false;
  var isMovingForward = false;
  var isMovingBack = false;
  var direction;
  var lastDirection;
  
  function animate() {
    requestAnimationFrame(animate);
    TWEEN.update();
    turn();
    walk();
    acrobatics();
  renderer.render(scene,camera);
  }
  animate();
  
  function turn() {
    if (isMovingRight) direction = Math.PI/2;
    if (isMovingLeft) direction = -Math.PI/2;
    if (isMovingForward) direction = Math.PI;
    if (isMovingBack) direction = 0;
    if (!isWalking()) direction = 0;
    
   if (direction == lastDirection) return;
   lastDirection = direction;
   var tween = new TWEEN.Tween(avatar.rotation);
   tween.to({y: direction}, 1000);
   tween.start();
  }
  
  function acrobatics() {
    if (isCartwheeling) {
  avatar.rotation.z = avatar.rotation.z + 0.05;
    } 
    if (isFlipping) {
      avatar.rotation.x = avatar.rotation.x + 0.05;
    }
  }
  
  function walk() {
    if (!isWalking()) return;
    
    var speed = 10;
    var size = 100;
    var time = clock.getElapsedTime();
    var position = Math.sin(speed*time)*size;
    rhand.position.z = position;
    lhand.position.z = -position;
    rfoot.position.z = -position;
    lfoot.position.z = position;
  }
  
  function isWalking() {
    if (isMovingRight) return true;
    if (isMovingLeft) return true;
    if (isMovingForward) return true;
    if (isMovingBack) return true;
    return false;
  }
  function isColliding() {
    var vector = new THREE.Vector3(0,-1,0);
    var raycaster = new THREE.Raycaster(marker.position, vector);
    
    var intersects = raycaster.intersectObjects(notAllowed);
    if (intersects.length > 0) return true;
    
    return false;
  }
  
  function isColliding2() {
    var vector = new THREE.Vector3(0,-1,0);
    var raycaster = new THREE.Raycaster(marker.position, vector);
    
    var intersects = raycaster.intersectObjects(notAllowed);
    if (intersects.length > 0) return true;
    
    return false;
  }
  
  function jump() {
  if (avatar.position.y > 0) return;
  checkForTreasure();
  animateJump();
  }
  
  function checkForTreasure() {
    var top = TreeTops[treasureTreeNumber];
    var tree = top.parent;
    var p1 = tree.position;
    var p2  = marker.position;
    var xDiff = p1.x - p2.x;
    var zDiff = p1.z - p2.z;
    var distance = Math.sqrt(xDiff*xDiff + zDiff*zDiff);
    if (distance < 500) scorePoints();
    if (distance > 1000) subtractPoints();
  }
  
  function scorePoints() {
    if (scoreboard.getTimeRemaining() == 0) return;
    scoreboard.addPoints(10);
    Sounds.bubble.play();
    animateFruit();
  }
  
  function subtractPoints() {
    if (scoreboard.getTimeRemaining() == 0) return;
    scoreboard.subtractPoints(5);
    Sounds.scratch.play();
    animateMinus();
  }
  
  var fruit;
  function animateFruit() {
    if (fruit) return;
    
    fruit = new THREE.Mesh(
      new THREE.CylinderGeometry(25,25,5,25),
      new THREE.MeshBasicMaterial({color: 'gold'})
      );
      marker.add(fruit);
      
      var tween = new TWEEN.Tween({height: 200, spin: 0});
      tween.to({height: 350, spin: 2 * Math.PI}, 500);
      tween.onUpdate(animateFruitUpdate);
      tween.onComplete(animateFruitComplete);
      tween.start();
  }
  
  function animateFruitUpdate(update) {
    fruit.position.y = update.height;
    fruit.rotation.x = update.spin;
  }
  
  function animateFruitComplete() {
    marker.remove(fruit);
    fruit = undefined;
  }
  
    var Minus;
   function animateMinus() {
     Minus = new THREE.Mesh(
       new THREE.CubeGeometry(30,10,10), 
       new THREE.MeshBasicMaterial({color: 'red'}));
       marker.add(Minus);
       
       var tween = new TWEEN.Tween({height: 200});
       tween.to({height: 350}, 500);
       tween.onUpdate(animateMinusUpdate);
       tween.onComplete(animateMinusComplete);
       tween.start();
   } 
   
   function animateMinusUpdate(update) {
     Minus.position.y = update.height;
   }
   
   function animateMinusComplete() {
     marker.remove(Minus);
     Minus = undefined;
   }
  
  function animateJump() {
    var tween = new TWEEN.Tween({jump: 0});
    tween.to({jump: Math.PI}, 400);
    tween.onUpdate(animateJumpUpdate);
    tween.onComplete(animateJumpComplete);
    tween.start();
  }
  
  function animateJumpUpdate(update) {
    avatar.position.y = 100 * Math.sin(update.jump);
  }
  
  function animateJumpComplete() {
    avatar.position.y = 0;
  }  
  
  function removeAvatar() {
    marker.remove(avatar);
  }
  
  document.addEventListener('keydown', sendKeyDown);
  function sendKeyDown(event) {
  var code = event.code;
  if (code == 'ArrowLeft') { 
  marker.position.x = marker.position.x - 10;
  isMovingLeft = true;
  }
  if (code == 'ArrowRight') {
   marker.position.x = marker.position.x + 10;
  isMovingRight = true; 
  }
  if (code == 'ArrowUp') {
  marker.position.z = marker.position.z - 10;
  isMovingForward = true;
  }
  if (code == 'ArrowDown')  {
  marker.position.z = marker.position.z + 10;
  isMovingBack = true;
  }
  if (code == 'KeyB') {
    boost();
  }
  
  if (code == 'KeyC') isCartwheeling = !isCartwheeling;
  if (code == 'KeyF') isFlipping = !isFlipping;
  if (code == 'Space') jump();
  
  if (isColliding()) {
  if (isMovingLeft) marker.position.x = marker.position.x + 10;
  if (isMovingRight) marker.position.x = marker.position.x - 10;
  if (isMovingForward) marker.position.z = marker.position.z + 10;
  if (isMovingBack) marker.position.z = marker.position.z - 10;
  }
  
  if (isColliding2()) {
  if (isMovingLeft) {
  marker.position.x = marker.position.x + 300;
  scoreboard2.addPoints(1);
  scoreboard2.countdown(1);
  }
  if (isMovingRight) {
  marker.position.x = marker.position.x - 300;
  scoreboard2.addPoints(1);
  scoreboard2.countdown(1);
  }
  if (isMovingForward) { marker.position.z = marker.position.z + 300;
  scoreboard2.addPoints(1);
  scoreboard2.countdown(1);
  }
  if (isMovingBack) { marker.position.z = marker.position.z - 300;
  scoreboard2.addPoints(1);
  scoreboard2.countdown(1);
  }
  }
  };
  
  document.addEventListener('keyup',sendKeyUp);
  function sendKeyUp(event) {
    var code = event.code;
    if (code == 'ArrowLeft') isMovingLeft = false;
    if (code == 'ArrowRight') isMovingRight = false;
    if (code == 'ArrowUp') isMovingForward = false;
    if (code == 'ArrowDown') isMovingBack = false;
  }
</script>

Thanks…

I have a problem in chapter 13 Bonus 2.

Code:

//<body></body>
<script src="/three.js"></script>
<script src="/controls/FlyControls.js"></script>
<script>
  // The "scene" is where stuff in our game will happen:
  var scene = new THREE.Scene();
  var flat = {flatShading: true};
  var light = new THREE.AmbientLight('white', 0.1);
  scene.add(light);

  // The "camera" is what sees the stuff:
  var aspectRatio = window.innerWidth / window.innerHeight;
 // var camera = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);
 var w = window.innerWidth / 2;
 var h = window.innerHeight / 2;
  var camera = new THREE.OrthographicCamera(-w, w, h, -h, 1, 10000);
  camera.position.y = 500;
  camera.rotation.x = -Math.PI/2;
  scene.add(camera);
  var aboveCam = camera;

  // The "renderer" draws what the camera sees onto the screen:
  var renderer = new THREE.WebGLRenderer({antialias: true});
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);

  // ******** START CODING ON THE NEXT LINE ********
  var cover = new THREE.MeshPhongMaterial({emissive: 'yellow'});
  var shape = new THREE.SphereGeometry(50,32, 16);
  var sun = new THREE.Mesh(shape, cover);
  scene.add(sun);
  
  var sunlight = new THREE.PointLight('white',1.7);
  sun.add(sunlight);

  var earthLocal = new THREE.Object3D();
  earthLocal.position.x = 300;
  scene.add(earthLocal);
  
  var texture = new THREE.TextureLoader().load("/textures/earth.png");
  var cover = new THREE.MeshPhongMaterial({map: texture});
  var shape = new THREE.SphereGeometry(20,32,16);
  var earth = new THREE.Mesh(shape, cover);
  earthLocal.add(earth);
  
  var moonOrbit = new THREE.Object3D();
  earthLocal.add(moonOrbit);
  
  var texture = new THREE.TextureLoader().load('/textures/moon.png');
  var cover = new THREE.MeshPhongMaterial({map: texture, specular: 'black'});
  var shape = new THREE.SphereGeometry(15,32,16);
  var moon = new THREE.Mesh(shape,cover);
  moon.position.set(0,0,100);
  moon.rotation.set(0, Math.PI/2, 0);
  moonOrbit.add(moon);
  
  var moonCam = new THREE.PerspectiveCamera(70, aspectRatio, 1, 10000);
  moonCam.position.z = 25;
  moonCam.rotation.y = -Math.PI;
  moonOrbit.add(moonCam);
  
  camera = moonCam;
  
  var shipCam = new THREE.FlyControls(shipCam, renderer.domElement);
  shipCam.position.set(0,0,500);
  scene.add(shipCam);
  
  var controls = new THREE.FlyControls(shipCam, renderer.comElement);
  controls.movementSpeed = 42;
  controls.rollSpeed = 0.15;
  controls.dragToLook = true;
  controls.autoForward = false;
  
  var cover = new THREE.PointsMaterial({color: 'white', size: 15});
  var shape = new THREE.Geometry();
  
  var distance = 4000;
  for (var i = 0; i < 500; i++) {
  var ra = 2 * Math.PI * Math.random();
  var dec = 2 * Math.PI * Math.random();
  
  var point = new THREE.Vector3();
  point.x = distance * Math.cos(dec) * Math.cos(ra);
  point.y = distance * Math.sin(dec);
  point.z = distance * Math.cos(dec) * Math.sin(ra);
  
  shape.vertices.push(point);
  }
  
  var stars = new THREE.Points(shape,cover);
  scene.add(stars);

  // Start Animation

  var clock = new THREE.Clock();
  function animate() {
    requestAnimationFrame(animate);
    var t = clock.getElapsedTime();

    // Animation code goes here...

    renderer.render(scene, camera);
  }

  animate();
  
  var speed = 10;
  var pause = false;
  var days = 0;
  var clock2 = new THREE.Clock();
  
  function gameStep() {
    setTimeout(gameStep, 1000/30);
    
    if (pause) return;
    
    days = days+ speed * clock2.getDelta();
    
    earth.rotation.y = days;
    
    var years = days / 365.25;
    earthLocal.position.x = 300 * Math.cos(years);
    earthLocal.position.z = -300 * Math.sin(years);
    moonOrbit.rotation.y = days / 29.5;
  }
  
  gameStep();
  
  document.addEventListener('keydown', sendKeyDown);
  function sendKeyDown(event) {
  var code = event.code;
  if (code == 'Digit1') speed = 1;
  if (code == 'Digit2') speed = 10;
  if (code == 'Digit3') speed = 100;
  if (code == 'Digit4') speed = 1000;
  if (code == 'KeyP') pauseUnpause();
  if (code == 'KeyC') switchCam();
  }
  
  function pauseUnpause() {
  pause = !pause;
  clock2.running = false;
  }
  
  function switchCam() {
  if (camera == moonCam) camera = aboveCam;
  else camera = moonCam;
  }
</script>

Idk what the console would like to say me…

Thanks

That error is… not helpful. At least not right away.

Sometimes – like this time – an error only shows you where to start looking. There is obviously a problem with shipCam when line 68 tries to use it. So the next step is to look back to see what might be going wrong with shipCam before then.

In this case, the problem is on 64. On that line, we are trying to create a ship camera. But instead, the code there is creating some fly-controls:

  var shipCam = new THREE.FlyControls(shipCam, renderer.domElement);

You should be able to fix the problem by changing that line to:

  var shipCam = new THREE.PerspectiveCamera(75, aspectRatio, 1, 10000);

I’ll try to have a look at your boost question later today.

-Chris