JavaScript for Game Developers

...student performance on examinations [...] increased by 0.47 SDs under active learning (n = 158 studies), and that the odds ratio for failing was 1.95 under traditional lecturing (n = 67 studies)....
2014 Freeman et al. "Active learning increases student performance in science, engineering, and mathematics" published in PNAS

Your hammer: The text editor

  • Windows: Notepad++
  • Linux: Gedit++
  • All platforms: Sublime Text ,VIM, Emacs

Minimal Web Page

index.html

              <!DOCTYPE html>
              <html>                            
                <head>
                  <script src="test.js"></script>
                </head> 
                <body>
                </body>
              </html>
            
test.js

              (function(){
                console.log("hello world!");
              })()
            

JavaScript Console

firefox: tools > web developer > web console
chrome: view > developer > JavaScript console
JavaScript interpreter that runs in your console or on your server
http://nodejs.org/

helpful tools

print to console:
console.log(thingToPrint1, thingToPrint2, ....)
puts a breakpoint
debugger;
  • Basic control structures
  • Basic data structures
  • "Functional" programming
  • Object oriented JS
  • Common Patterns
  • The Bad Parts

Basic control structures

for loops and try-catch

var target = 9;
for (var i = 0; i < target; i++){
  ...
}

try {
  ...
} catch (e) {
  console.error(e);
}
            

Basic data structures

Strings:
"hello world"
Numbers:
4
null:
null
Booleans:
true; false

String ops:

  • + concatenates strings.
  • + with a string converts a number to string

var firstName = "Jacob";
var lastName = "Lyles";
var iceCream = 5;
var fullName = firstName + " " + lastName;
var message = fullName + " ate " + iceCream + " ice creams today";
            
Functions:

var fun = function(foo){
  console.log("hello", foo);
}
fun("NTU");  // logs "hello NTU"

function fun2(foo){
  console.log("hello", foo);
}
fun2("NTU"); // logs "hello NTU"
undefined:

var a = undefined;
var b;
a === b          => true
console.log(b);  // logs undefined

function printer(foo) {
  console.log(foo);
}
printer(); // logs undefined

if(!undefined){ console.log("hello"); }  // logs "hello"
Arrays:

var arr = [1,"a", 3, null];
arr[0];     => 1
arr[3];     => null

arr[4];     => undefined
arr[-1];    => undefined

arr.length;  => 4
arr.push("hello");
arr.length;  => 5 
console.log(arr); // prints [1, "a", 3, null, "hello"]
Array Reference:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
Objects:

var obj = {"a": 1, "b": []};
obj["a"]                => 1
obj.b                   => []
obj["c"]                => undefined
obj.hasOwnProperty("a") => true
"a" in obj              => true
Iterate over object properties with for loops

for (var key in obj){
  console.log(key, obj[key]);
}
            
typeof:
  • typeof tells type of a variable, but results can be unexpected:

typeof(2)          => "number"
typeof(true)       => "boolean"
typeof(undefined)  => "undefined"
typeof({})         => "object"


typeof(null)       => "object"
typeof([])         => "object"

Boolean warning

  • all these things are "falsy"
  • have to check for them explicitly

null
0
""
undefined

Dates


var now = new Date();
now                                     => Fri May 09 2014 05:50:40 GMT-0700 (PDT)
var yesterday = new Date();
yesterday.setDate(now.getDate() - 1)   
yesterday                               => Thu May 08 2014 05:51:48 GMT-0700 (PDT)
            

Serialization with JSON

JSON: a string format for storing JS objects

var json = '{ "name": "Nick" }';
try {
  var nick = JSON.parse(json);
  nick.name; // "Nick"
} catch (e) {
  console.error(e);
}

var dog = { name: "Winston", type: "Bulldog", };
JSON.stringify(dog); // '{ "name": "Winson", "type": "Bulldog" }'
            

Functional programming

"First class functions" -> use functions like any other variable

var doBefore = function (doAfter){
  console.log("outer function");
  doAfter();
}

doBefore(function(){
  console.log("inner function");
}); // logs "outer function" \n "inner function"

passing the function to do next as a variable is the "callback" pattern

var arr = [];
var fun = function(){ console.log("hello!"); }
arr.push(fun);
arr.length      => 1
arr[0](); // logs "hello!"
functions create new scope

var fun = function(foo){

  var secondFun = function(innerFoo){
    // variable 'foo' is available from outer scope!
    console.log(foo, innerFoo);
  }

  // using "innerFoo" here would result in an error

  secondFun("second");
}

fun("first"); // logs "first second"

Object oriented JS

Object oriented JS

  • no "classes" in JS
  • object instances are built with constructor functions
  • inheritance happens with prototypes
  • Calling a function with new makes it a constructor function
  • Inside a constructor, "this" is a new object
  • The new object is returned from the constructor

function Character(firstName, lastName, hp){
  this.name = firstName + " " + lastName;
  this.hp = hp;
}

var char1 = new Character("Zhenghao", "Chen", 100);
var char2 = new Character("Jacob", "Lyles", 200);

char1.name       => "Zhenghao Chen";
char1.hp         => 100
char2.name       => "Jacob Lyles";
char2.hp         => 200

Methods

  • methods are a property of an object that is a function
  • inside a method, this refers to the object that "owns" the function


function Character(firstName, lastName, hp) {
  this.name = firstName + " " + lastName;
  this.hp = hp;
}

var char1 = new Character("Jacob", "Lyles", 200);

char1.takeDamage = function(damage){
  this.hp - damage;
  console.log(this.hp + " is left");
}

char1.takeDamage(50); // logs "150 is left"

Inheritance

  • objects inherit properties from the constructor's prototype
  • the prototype acts as a fallback if an undefined property is called on the instance

function Character(firstName, lastName, hp) {
  this.name = firstName + " " + lastName;
  this.hp = hp;
}

Character.prototype.takeDamage = function(damage) {
  this.hp -= damage;
  console.log(this.hp + " is left");
}

var char1 = new Character("Jacob", "Lyles", 200);
char1.hasOwnProperty("takeDamage")     => false
char1.takeDamage(50);                  // logs "150 is left"

Inheritance

Object.create makes a new object with the given prototype without calling the constructor (not available in older browsers)


function FrostKnight(firstName, lastName, hp) {
  this.name = firstName + " " + lastName;
  this.hp = hp;
  this.type = "ice";
}

FrostKnight.prototype = Object.create(Character.prototype);
FrostKnight.prototype.frostArmor = function(){
  this.hp += 100;
  console.log(this.hp + " is left");
};

var iceChar = new FrostKnight("Jacob", "Lyles", 200);
iceChar.takeDamage(50); // logs "150 is left"
iceChar.frostArmor();   // logs "250 is left"

Inheritance

The prototype of an instance can be checked with Object.getPrototypeOf


function FrostKnight(firstName, lastName, hp) {
  ...
}

FrostKnight.prototype = Object.create(Character.prototype);

var iceChar = new FrostKnight("Jacob", "Lyles", 200);
var iceProto = Object.getPrototypeOf(iceChar)              
iceProto === FrostKnight.prototype                             => true

iceChar.hasOwnProperty("takeDamage")                           => false
iceProto.hasOwnProperty("takeDamage")                          => false
Object.getPrototypeOf(iceProto).hasOwnProperty("takeDamage")   => true

"takeDamage" in iceChar                                        => true

Changing this

  • bind sets the value of this in a function
  • this code erroneously logs "NaN is left"

FrostKnight.prototype.delayedMeltDamage = function(damage){
  setTimeout(this.takeDamage, 5000, damage);
};

var iceChar = new FrostKnight("jacob", "lyles", 200);
iceChar.delayedMeltDamage(50);
But this will work:

FrostKnight.prototype.delayedMeltDamage = function(damage){
  setTimeout(this.takeDamage.bind(this), 5000, damage);
};

var iceChar = new FrostKnight("jacob", "lyles", 200);
iceChar.delayedMeltDamage(50);

Common Patterns

The options object pattern


function Character(firstName, lastName, hp, type, boss, level, world, bio, background){
  ...
}  

function Character(opts){
  ...
}

The defaults pattern

In JS, foo || "default" evals to "default" if foo is false, undefined, null or another "falsy" object

function Character(opts){
  this.name = opts.name || "Jack NoName";
  this.hp = opts.hp || 200;
}

The module pattern

When you load a JS file, variables get added to the global name space. Two files that define variables of the same name will cause a collision

<script src="character.js"></script>
<script src="enemy.js"></script>

// in character.js
var DEFAULT_HP = 500;

// in enemy.js
var DEFAULT_HP = 1000;

// value from enemy.js overwrites value in character.js

The module pattern

  • Since functions create scope, wrap files in a function and only add what is needed to the global namespace
  • In the browser, the global namespace is called window. All values available by default are properties of window


(function(global){
  var DEFAULT_HP = 1000;

  var Enemy = function (){
    ...
  }

  window.Enemy = Enemy;

})(window);

window.DEFAULT_HP   => undefined
window.Enemy        => function

The Bad Parts(or: rules to keep you sane)

(many thanks to Doug Crockford's book "JavaScript: the Good Parts")

Rule 1:

  • use === by default instead of ==
  • == does type coercion, and may not give you what you want

[] == ""      => true
5 == "5"      => true
" " == 0      => true

[] === ""     => false
5 === "5"     => false
" " === 0     => false
Also, !== instead of !=

Rule 2:

  • always use var to declare a variable
  • declaring variable without "var" makes it global

for(i = 0; i < 3; i++){    // WARNING - this loop will only run once!
  console.log("first i is " + i);
  second();
}

function second(){
  for (i = 0; i < 3; i++){
    console.log("second i is " + i);
  }
}

Rule 3:

  • Math that requires precision should use whole numbers
  • All numbers are IEEE 754 floating point, which can give unexpected results
  • the operation 100.543 | 0 truncates the decimal places

(0.1 + 0.2) === 0.3    => false
10.5 | 0               => 10

Rule 4:

  • Always break at the end of switch-case statements
  • JS switch blocks "fall through", which can lead to unexpected results
  • buffing an "ice" character results in 250 point buff

function buff(character){
  switch(character.type) {
    case "ice":
      character.hp += 100;

    case "fire":
      character.hp += 50;

    default: 
      character.hp += 100;
  }
}

The End