Jump to content

TypeScript vs JavaScript?


ScottyFillups
 Share

Recommended Posts

Hi all,

I'm a complete beginner to Phaser. Recently I've been teaching myself by look at examples and experimenting using the phaser sandbox. I obviously plan to ditch the phaser sandbox when I begin start developing big projects, but I'm unsure whether I should stick with JavaScript or learn TypeScript.

The two tutorials for "Getting Started" differ, so I need to reach a decision before I can really move on.

http://phaser.io/tutorials/getting-started/index

http://phaser.io/tutorials/how-to-use-phaser-with-typescript

 

I'm somewhat familiar with JavaScript, but I'm not too fond of how it's a prototype-based language. TypeScript's syntax looks like a cleaner version of Java with "superior" (class-based) OOP. I have some experience with Java, so learning a new class-based language wouldn't be very difficult.

Link to comment
Share on other sites

2 hours ago, ScottyFillups said:

Hi all,

I'm a complete beginner to Phaser. Recently I've been teaching myself by look at examples and experimenting using the phaser sandbox. I obviously plan to ditch the phaser sandbox when I begin start developing big projects, but I'm unsure whether I should stick with JavaScript or learn TypeScript.

The two tutorials for "Getting Started" differ, so I need to reach a decision before I can really move on.

http://phaser.io/tutorials/getting-started/index

http://phaser.io/tutorials/how-to-use-phaser-with-typescript

 

I'm somewhat familiar with JavaScript, but I'm not too fond of how it's a prototype-based language. TypeScript's syntax looks like a cleaner version of Java with "superior" (class-based) OOP. I have some experience with Java, so learning a new class-based language wouldn't be very difficult.

Class based OO is supported on ES6. Not many browsers support it yet so we rely on tools like Babel to transpile it down. Instead of prototypal inheritence, you can code JS like "class X extends Y" like you used to in native Javascript nowadays.

There are certain things that is not supported in ES6 yet. That's where Typescript comes in. Typescript is a superset of JS that basically transpiled into native JS code.

For example, there are no interfaces in JS so you cannot do the following unless you use Typescript.

interface MonsterInterface {
   ...
}


class Monster implements MonterInterface {

}

or, return type declarations

someFunction(parameter: string): string[] {
    return ["a", "b", "c"];
}

or public/protected/private properties

class X {
    public something: number = 10;
}

In my opinion, Typescript is pretty solid, but as said, class based OO is supported in native JS if you want to give it a try. I would learn what ES6 is capable of and what kind of benefits Typescript brings on top of it.

 

 

Link to comment
Share on other sites

2 hours ago, ScottyFillups said:

Thanks Theo and Aristy for your replies. 

I'm leaning toward using Babel and ES6; I'll start reading the "Getting Started" tutorial for JS. 

What editors do you guys use? I would appreciate it if you guys described all the tools you use to make your games.

I recomend you sublime text, i've used a lot of text editors but this is my favorite one.

https://www.sublimetext.com/

Link to comment
Share on other sites

Hi, it is always good to know pure JS, but with TS you get type checking, which prevents errors and increases productivity. TS is not separate language, but it is superset of JS. If you have Java (or C#) experience, then OOP is similar in it (as well as in ES6, but there it is without type checking). If you are on Windows, there is also very luxury IDE - Microsoft Visual Studio Community 2015, which is free for individual developers. It has great intellisense support.

In my book I made small comparsion of TS / ES6 / JS:

 Just for example, consider this simple TypeScript class:

    class MyClass {

        private _position: Phaser.Point = new Phaser.Point();

        public constructor(position: Phaser.Point) {
            this._position.copyFrom(position);
        }

        public moveBy(x: number, y: number): void {
            this._position.add(x, y);
        }
    }

 It is compiled into this JavaScript code:

    var MyClass = (function () {

        function MyClass(position) {
            this._position = new Phaser.Point();
            this._position.copyFrom(position);
        }

        MyClass.prototype.moveBy = function (x, y) {
            this._position.add(x, y);
        };

        return MyClass;
    } ());

 Or into this ES6 code:

 

    class MyClass {

        constructor(position) {
            this._position = new Phaser.Point();
            this._position.copyFrom(position);
        }

        moveBy(x, y) {
            this._position.add(x, y);
        }
    }

 

Link to comment
Share on other sites

Type correctness is a fallacy, and can not be implemented into a dynamic language without injecting into the runtime, which typescript does not do (although alternatives like flowcheck and some other libs for implementing flow do, it still does not make type checking a silver bullet). Much more info in this article.

Having worked with it for an Angular 2 build (which was a horrific experience coincidentally) I can say that using TS creates so many headaches. Importing simple modules becomes an issue and often breaks your build, do you really want to spend half and hour importing a module you want?? Totally bonkers, it should just be a few keystrokes, a bit of network, and boom, get coding. I continually found inaccuracies in documentation as implementations changed which was a pain. I was worried about how TS performs some of its transforms without producing a horrible tangle of inefficient code, turns out that does happen, although a lot of people do work on making these transforms as efficient as possible, its just that some patterns are either not needed or don't work well in a dynamic language. There is also a barrier for every other JS dev, you force them to use TS and not everyone will want to do that (this depends on your project though).

The additional killer is that TS is not spec, whilst a lot of what it started considered spec, and certainly its implementation helped the spec to evolve, its a worry. Every time you move away from spec (like Webpack does with modules) you are at risk of moving further away from your chosen language, which is crazy, if your language doesn't support the stuff you want to do then choose another, don't mutate your language beyond recognition, as a web dev you are in a position of luxury here because if the web is your chosen platform then its JS so everything has a pipeline to output JS these days.

Typing is useful for large teams as it can make the code easier to understand, but I could dispute this by saying that it introduces a load of clutter and types can be gleaned by a bit of inline documentation, although you wouldn't get static checking (but, see the earlier link, static checking won't secure your program, to the point where it could be pointless). On that, the number of times in TS code I've seen the type specified as <any> because devs haven't put enough effort in (or ran out of time, or, sometimes, been forced in to it because typing is square peg round hole for JS) is shocking and clearly unhelpful.

Having said all this, it is gaining popularity so its certainly worth checking out, particularly if you're fond of strongly typed languages (just remember JS is not, and without killing your performance nothing will make it so).

On 19/11/2016 at 7:05 PM, ScottyFillups said:

I'm somewhat familiar with JavaScript, but I'm not too fond of how it's a prototype-based language.

Nothing can get you away from this, despite the newer ES2015 class syntax, JS is NOT a classical language. The class syntax is simply sugar around creating a class-like abstraction in a prototypal world.

On 19/11/2016 at 7:05 PM, ScottyFillups said:

TypeScript's syntax looks like a cleaner version of Java with "superior" (class-based) OOP.

There is nothing superior about OOP or classes, it is simply a different style/flavour/design choice.

The reason I bring it up is that if you keep up with a mindset that one thing is superior over another you will overlook anything you deem inferior which means you won't learn its pros/cons and you'll get stuck in your superior world even when you start working on something where that does not make sense. Feel free to ignore, but I'd be wary of pigeon-holing yourself or your tech choices. Learn it all and learn when each different thing makes sense and when it does not. Particularly in the fast-paced JS world, something only remains superior for 6 months, so unless you want to become a dinosaur, keep moving! :) 

Link to comment
Share on other sites

Wow, thanks for that very informative post mattstyles.

On 2016-11-21 at 2:04 AM, mattstyles said:

Nothing can get you away from this, despite the newer ES2015 class syntax, JS is NOT a classical language. The class syntax is simply sugar around creating a class-like abstraction in a prototypal world.

So, even though ES2015 has class syntax, it's still a prototype-based language and merely "fakes" being class-based? I didn't know that :P

What do you use when developing games with Phaser? ES2015 and Babel? I must stress I'm a complete beginner, so any sort of advice regarding what I should start with would be appreciated.

Link to comment
Share on other sites

Just popping in to +1 pretty much everything matt said: ES2015 is still prototypal inheritance, it just has "class" and "extends" as keywords to front it instead of messing around with the function, the prototype, and overriding the constructor.

I have zero experience with TS specifically, but lots of experience with strongly-typed, static languages. There's been research on whether or not strong typing actually helps with productivity and the jury's still out.

If you are a total beginner then you can start with one JS file. Get your game working first! Once that one file becomes a thousand line monster ;) then you can break it up into multiple files, and include those files in the right order in your HTML file. If that's still working for you, great.

However, if you find that you want to learn browserify or webpack (and you should -- professionally, the industry is moving that way) then you can experiment with converting your multiple JS files into a single bundle.

Depending on what browsers you're targeting, you can probably use a lot of ES2015 features now, without transpiling from TypeScript or using babel. let and const, arrow functions, classes... those all work in the browser natively now (I'm using Chrome 54).

In the end: start small. Make a game. Don't worry too much about the "right" way of doing things. The right thing is to make a game. Shipping is a feature.

Good luck!

Link to comment
Share on other sites

Shipping puts you ahead of 90% of other devs who have great ideas but never finish them! I remember a great talk by one of the guys from valve, who was asked in the audience how to best bet into the industry. And he said just make stuff.... But make you finish what you start! The fresh ideas in your head, the initial enthusiasm, that's the easy part. But following it through and finishing it? That's hard. Especially when you're new, learning, and your realise half way through that what you're doing sucks. You might wanna start from scratch, or move to a new cooler idea with new skills you've gained. Very tempting.... but finishing an idea off is a skill in itself that you learn a lot from, and that looks good on a cv/portfolio

Link to comment
Share on other sites

20 hours ago, ScottyFillups said:

What do you use when developing games with Phaser? ES2015 and Babel? I must stress I'm a complete beginner, so any sort of advice regarding what I should start with would be appreciated.

Yep, I use Babel for almost everything I write, only moving away from it when I'm only targeting a specific browser that I know supports everything I want, Node, Chrome, FF, Edge and Safari all now completely support all ES2016 syntax so its only legacy you have to be concerned with, if you're writing demos and stuff then you probably don't need it at all (I tend to lean on some ES2017 stuff so I'll usually include it, I have a number of scaffolds and programs to quickly get going, although its not too time-consuming to start from scratch with a build pipeline once you've done it a few times).

This next bit can get a bit technical if you're a beginner but there are only a few steps you have to do to get going, you've probably done most already, the hardest part is getting used to using a build pipeline with JS:

* Get used to using the command line if you aren't already, it isn't that scary and it will give you much more power. It's not too difficult to get going (honest) and you won't be doing anything too crazy anyway, this lesson will stay with you whatever you end up coding. If you're on Windows get yourself a bash shell, most tutorials you find will target bash as the language and most servers run linux, which will run bash (or a similar variant), so its worth doing.

* Install node, use the latest 6.x LTS, this will additionally install npm, the node package manager to give you access to just about any module/dependency you're going to need (Phaser doesn't work with a module bundler although can still be installed via npm, but I'll get to that later).

* If any of the above scares you try to get someone to sit with you and help you if possible, either at work, college or maybe even your local computer shop employees could help. If none of these are available to you then there are numerous tutorials on the web with very detailed step-by-step instructions, it really isn't that hard but, like everything, can be intimidating at first. Failing all this ask here for help, or trawl Stack Overflow.

* We're nearly ready to go, if you've any experience then those first steps will take only minutes, and the next ones won't take much longer. I'm running with the beginner theme here so sorry if I'm being long-winded!

* Now create a new directory and lets get going installing stuff we'll need to create a very simple build pipeline, use the command line:

mkdir my-awesome-project
cd my-awesome-project

* Simple right? Make a directory (mkdir) and then navigate (cd) into the directory (note that if you're using the Windows command prompt your commands will differ, but, seriously, get yourself that bash shell!).

* Now lets install a couple of bits and pieces to turn super-new-super-trendy-super-modern JS into the boring old regular JS that browsers can understand (yes, I'm being flippant, never use the new and shiny solely because its new and shiny and there is nothing boring about old versions of languages!). Again, command line, this time we'll ask npm to install stuff for us:

npm init
npm install --save-dev browserify babelify babel-preset-es2015

* Use the defaults for the `npm init` prompt, or change, at the moment we don't care. Then install 3 things, browserify creates a bundle so that the browser can understand a module system (in this case, commonJS modules, as used by Node, the module spec is basically the same with a few syntactic differences, but, it gets complex so lets just ignore it for now). The babelify transform runs babel, which only turns newer JS code into 'regular' JS code and the babel-preset simply tells babel what to transform.

* Now lets scaffold out some source code

mkdir src
touch src/main.js

* Just creates a source directory and then creates (touch) a new file called main.js. Time to fire up your editor of choice and add something super simple to main.js, lets use a template string to make sure the transforms are working

let name = 'Dave'
console.log(`Hello ${name}`)

* Note that this code will work in all modern browsers as is, but, lets transform it into something else, and lets immediately add it to npm scripts, this is generally a good idea as an initial first place to keep where your scripts are (lots of people use grunt and gulp to handle script running, I'd argue that npm scripts are more than enough and there are a lot of advantages, but ignore it for now, its something you might want to consider later on). So, open up your package.json file (this was created when you ran `npm init`) and add a field to the scripts object, don't worry about the test script that is in there now:

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "build": "browserify -t babelify src/main.js > bundle.js"
}

* This creates a build script that runs browserify with the babelify transform, so, browserify turns all your modules into a bundle that the browser can run and whilst it does that it runs the babel transform over your code to transpile your code from new stuff into regular stuff, it then outputs the result to `bundle.js`. There is one more thing you need to do before this will work, so, add the following field to the package.json to tell babel how to run:

"babel": {
  "presets": [
    "es2015"
  ]
}

* This is fairly simple, you have already installed the `babel-preset-es2015`, which contains a number of transforms to run, this field simply tells babel to use the es2015 transforms when transpiling your code. Babel will look inside your package.json for this information.

* Now, finally, you can run this script and build some JS ready for the browser:

npm run build

* This will, if all goes well, create a `bundle.js` file, just create an index.html page to serve that code and fire it in to a browser:

<script src="bundle.js"></script>

Aaaaaaand, you're done. You now have a build pipeline that will produce a `bundle.js` ready for the browser. Make a change to your source file/s, re-run the script et voila, you're in the game with babel and es2015. You can automate this rebuilding later on, I won't go over it here, but if you understood all that is happening then you now have the skills to work this out for yourself.

There are basically 2 steps:

* Create a bundle for the browser

* Transpile source code ready for the browser

A quick note: just add Phaser as a script tag to the html and accept that it is global, it won't work well with bundlers.

Additional note, the process for using typescript is identical, just swap out `babelify` for `tsify` and there might be some additional TS config and the process is the same. Transpile and bundle. You could easily swap out browserify (the bundler) for webpack (or whatever) and/or swap babel for TS (or whatever).

Sorry for being long-winded, this really isn't very difficult but it can be a paradigm shift if you're just used to writing code and firing it into a browser. Hopefully it has been helpful. 

Link to comment
Share on other sites

7 hours ago, themoonrat said:

 The fresh ideas in your head, the initial enthusiasm, that's the easy part. But following it through and finishing it? That's hard. Especially when you're new, learning, and your realise half way through that what you're doing sucks. You might wanna start from scratch, or move to a new cooler idea with new skills you've gained. Very tempting.... but finishing an idea off is a skill in itself that you learn a lot from, and that looks good on a cv/portfolio

Thanks for the advice. Looking back, I had a tendency to give up projects simply because my approach wasn't "the best". I'll keep your post in mind when I work on future projects :P

 

Thanks so much mattstyles for your post! Don't worry about being long-winded, as pretty much everything you mentioned was new to me. I followed your instructions but the console log wasn't outputting "Hello Dave". I added an alert(), typed "npm run build" and opened index.html again, but nothing happened. Here's a screenshot of browser and the code:

http://i.prntscr.com/31a67b16917e4026ae3db285e002c34b.png

Thanks so much everyone for time, I really appreciate it :)

Link to comment
Share on other sites

Ok, thats not picking up an entry point at all, i.e. running `browserify -t babelify` without specifying an entry point (src/main.js) you'll get that. If you'd specified the wrong entry point it would moan at you, so its not that.

Re-check your script in the package.json, make sure it specifies the entry point `"build": "browserify -t babelify src/main.js > bundle.js"`

Link to comment
Share on other sites

  • 2 weeks later...

Hi, i have been using Typescript quite a bit for an Ionic2 project sort of like working in Visual Studio Code with Typescript as it assists me quite a bit with autocompletion.

But when I tried using Typescript with Phaser I soon realized my understanding of javascript is somewhat lacking with how the whole context thing work. Especially what "this" means.

For example all the examples at the phaser.io site will not work very well due to the fact that you have to put this. in front of all object references you put in your class "scope" which can feel a bit odd for those used to C# and Java coding.

I have not yet figured out how I can make callback functions work when I pass them as a reference like most Phaser examples use. Here is an example from the Phaser examples:

game.time.events.loop(1000, createEnemy, this); // spawn an enemy every second by calling the createEnemy method that adds it to the game

So in order to get this working in Typescript I have to write something like this:

this.game.time.events.loop(2000, function() {
 // create and add enemy sprite here
}, this);

So the game object is part of the class so it has to be explicitly referenced by this. everywhere. Well that isnt so bad (gotten used to that). The problem is that I cannot pass a function as a parameter just by referencing it. I then get all kinds of runtime errors where it fails to find some "apply" function on the context passed. So this does not work in typescript:

class MySuperGame
{
  game : Phaser.Game;

  constructor() {
    this.game = new Phaser.Game( 800, 600, Phaser.AUTO, 'content', { preload:this.preload, create:this.create });
  }

  preload() {}

  create() {
    this.game.time.events.loop(1000, this.createEnemy, this);
  }  

  createEnemy() {
    // create and add enemy sprite here
  }  

}

If anyone has any tips on making this work I would be very happy. It seems that passing this.createEnemy and this as context does not work very well in Typescript. I'd prefer to have separate functions instead of inline functions (which does work).

Obviously I need to learn some more about javascript and how the context things work which is where this fails.

Link to comment
Share on other sites

On 03/12/2016 at 2:24 PM, johncl said:

It seems that passing this.createEnemy and this as context does not work very well in Typescript

It's little things like this, that @johncl has pointed out, which is why I bash TS, it encourages situations like this by being square-peg-round-hole.

I have no idea what TS is doing under the hood but you'd have thought that, as a superset, all standard JS would work in TS, but, alas, it does not (and we haven't even got on to typing, where surely TS would shine right?).

TS might be encouraging you to use the .bind pattern, which is a good fit here (so long as you don't support old browsers), although I'm not clear what it would do with the context variable, TS has no way of knowing that it will be used later to bind the context to the called function (although it could maybe work this out) but it obviously does something, maybe it doesn't like `this` being passed around?

In any case, functions have mutable scope in JS and ordinarily functions won't be scoped to their parent even when they are an object method, some libraries tried to change this behaviour when they were mucking around with their own class implementations but most dropped it (quite rightly) in favour of following language spec.

Try using .bind instead and see how that goes:

create() {
  this.game.time.events.loop(1000, this.createEnemy.bind(this));
}  

createEnemy() {
  // create and add enemy sprite here
}  

You can actually go one step further and bind your functions only once, in a class constructor:

class Foo {
  constructor () {
    this.createEnemy = this.createEnemy.bind(this)
  }


  create() {
    this.game.time.events.loop(1000, this.createEnemy);
  }  

  createEnemy() {
    // create and add enemy sprite here
  }  
}

If you're using the right transforms then part of draft (I forget which stage) are class properties which would allow you to do this:

class Foo {
  create() {
    this.game.time.events.loop(1000, this.createEnemy);
  }  

  createEnemy = () => {
    // create and add enemy sprite here
  }  
}

This sets a new method on the object (same as the other syntax) but assigns it to a lambda/fat arrow function (can't do that with the other syntax, not without stuffing it into the constructor) which has no scope at all so it inherits from parent scope. Scope can not be assigned to lambdas, which makes them even more curious little constructs.

---

At the risk of sounding all pompous about a language, if you're not absolutely clear on the basics like scoping (which, admittedly, can be tricky in JS) then you probably shouldn't be mucking around with TS, Babel etc etc, they add extra abstractions to a language you aren't clear with so you're always going to hit issues further down the line. In your case it sounds like you can't be clear what is coding error or what is possible TS error and thats must be very frustrating.

In your defence, `this` acts weirdly in JS anyway, best to steer clear of it :)

Link to comment
Share on other sites

Hi, this is not TS problem. I will explain: with new Phaser.Game, you are pasing it as "state" parameter new anonymous object { preload: this.preload, create: this.create }. Here, you can pass instance of class, that extends Phaser.State or class name thet will be instantiated by StateManager or instance of any object. You are doing the last. You are composing it from two functions - you say, that preload will be the same as preload defined in MySuperGame (where it is in fact useless as it will never be called there) and the same is for create. Your state object does not contain any createEnemy function!

 When game runs it looks into your state object and because it finds there create function, it calls it - your code is in context of your anonymous state object not in context of MySperGame instance.

 How to solve it? Simply add createEnemy function into your state, when creating it. Now your anonymous state object has this function and it will be called:

class MySuperGame {
    game: Phaser.Game;

    constructor() {
        this.game = new Phaser.Game(800, 600, Phaser.AUTO, 'content', { preload: this.preload, create: this.create, createEnemy: this.createEnemy });
    }

    preload() { }

    create() {
        this.game.time.events.loop(1000, this.createEnemy, this);
    }

    createEnemy() {
        // create and add enemy sprite here
        console.log("ok, ok, ... creating...");
    }

}

 There are also other solutions (some pretty wild), like making createEnemy static or saving contexts in MySuperGame and using it from anonymous state object, etc. But it would just bring mess into explanation. But ... ok, this also works and it is very close to Phaser examples. Your createEnemy is not class method, but non-class function. Anybody can call it from anywhere. In Phaser examples, functions are not closed in classes or namespaces.

function createEnemy() {
    // create and add enemy sprite here
    console.log("ok, ok, ... creating...");
}

class Game {
    game: Phaser.Game;

    constructor() {
        this.game = new Phaser.Game(800, 600, Phaser.AUTO, 'content', { preload: this.preload, create: this.create });
    }

    preload() { }

    create() {
        this.game.time.events.loop(1000, createEnemy, this);
    }
}

BTW - this also works with TS compiler. But this time, no class usage at all - this is in fact the same way as Phaser examples are written. TS is superset of JS. Everything, that works in JS works in TS plus you get additional features.

window.onload = () => {
    var game = new Phaser.Game(800, 600, Phaser.AUTO, 'content', { preload: preload, create: create });

    function preload() {}

    function create() {
        game.time.events.loop(1000, createEnemy, this);
    }

    function createEnemy() {
        console.log("ok, ok, ... creating...");
        // create and add enemy sprite here
    }  
};

 

 In the end, If making larger game, not just example, I recommand creating your main Game class and states like this:

// -------------------------------------------------------------------------
class Game extends Phaser.Game {

    constructor() {
        // init game
        super(640, 400, Phaser.CANVAS, "content", State);
    }
}

// -------------------------------------------------------------------------
class State extends Phaser.State {

    // automatically contains public this.game variable and many others like this.time, this.add, ...
    // Phaser.StateManager added it all for you - see doc ("link" function)

    private static MY_STATIC_NUMBER_VARIABLE = 2;
    private _myPrivateSpriteVariable: Phaser.Sprite;

    public preload(): void {

    }

    public create(): void {

    }

    public update(): void {

    }
}

 Some time ago I wrote simple 3 part tutorial: http://sbcgamesdev.blogspot.cz/2015/05/phaser-tutorial-dronshooter-simple-game.html While old, it is still valid.

 

Link to comment
Share on other sites

window.onload = () => {
    var game = new Phaser.Game(800, 600, Phaser.AUTO, 'content', { preload: preload, create: create });

    function preload() {}

    function create() {
        game.time.events.loop(1000, createEnemy, this);
    }

    function createEnemy() {
        console.log("ok, ok, ... creating...");
        // create and add enemy sprite here
    }  
};

It is examples like this that make life really confusing, if you are a dev with good knowledge of JS but little knowledge of Phaser you'll be struggling to understand the line `game.time.events.loop(1000, createEnemy, this)` because its not clear that Phaser will invoke the create function with the context of `game`. I can understand why Phaser makes this decision but it must be very difficult if you're additionally trying to get your head around scoping in JS.

The original problem of `this.createEnemy` not existing is because Phaser mutates the scope of state functions, which is unexpected, although I suspect well documented. This fiddle, which logs `this`, just shows how Phaser changes the scope for state functions. Hence in the original problem context is not `MySuperGame`, as you might expect, but actually the Phaser.Game object.

Link to comment
Share on other sites

1 hour ago, mattstyles said:

because its not clear that Phaser will invoke the create function with the context of `game`

@mattstyles - I think this is not right... context in above example is not game, but that anonymous state object that is passed into game constructor. Change your fiddle example to this:

window.onload = function () {
    var game = new Phaser.Game(800, 600, Phaser.AUTO, 'content', {myVal:"I am state!", preload: preload, create: create });

    function preload() {}

    function create() {
        console.log('-->', this.myVal, 'not what you would expect!')
        game.time.events.add(1000, createEnemy, this);
    }

    function createEnemy() {
        console.log("ok, ok, ... creating...");
        console.log(this.myVal);
        // create and add enemy sprite here
    }  
};

 In your example, the console output was not Game, but State. Here it is more obvious: through "this" you have access to myVal added into anonymous state.

 To test further, add 2 new lines just below game:

window.onload = function () {
    var game = new Phaser.Game(800, 600, Phaser.AUTO, 'content', {myVal:"I am state!", preload: preload, create: create });

    createEnemy();
    createEnemy.call(game);
  
         :
         :

 For both this calls you get output;
  ok, ok, ... creating...
  undefined

 undefined is for printing this.myVal. You must be inside this object: {myVal:"I am state!", preload: preload, create: create } to print its value.

 

Link to comment
Share on other sites

Oh you might be right about it being State and not Game, I don't know, but its definitely not the object passed in (which would also be unexpected by the time the function gets invoked), nor is it window as you might expect from just reading the code and understanding how scoping would work in JS unobstructed (or unchanged), and thats the point, its unexpected from reading the code alone. The log shows

{game: b.Game, add: b.GameObjectFactory, make: b.GameObjectCreator, camera: b.Camera, cache: b.Cache…}

which isn't naturally what you should assume from only reading the code.

Either way Phaser mutates the scope, which is unexpected and requires knowledge of Phaser and how it works to become expected behaviour. I think it is this ambiguity that caused the initial confusion, which I think is an understandable confusion.

I'm guessing that underneath Phaser creates a state object, using the argument (passed in object) as config, and then invokes each state function scoped to that state class (object). This all makes perfect sense of course, and I don't think its a bad design choice at all given the use-case for a state, just that its not obvious that this will happen from reading the code.

Link to comment
Share on other sites

2 hours ago, mattstyles said:

but its definitely not the object passed

It should be... reason, why you get output like this:

{game: b.Game, add: b.GameObjectFactory, make: b.GameObjectCreator, camera: b.Camera, cache: b.Cache…}

is hidden inside Phaser.StateManager. What it does is, that it calls link function on any passed "state" object. I put state into quotes, as you can pass instance of state, class name or any object. It is first processed and if you passed for example class name, then new instance is created on behalf of you (see add function of StateManager: https://github.com/photonstorm/phaser/blob/master/v2/src/core/StateManager.js#L193)

 Then, when your state object (created in any way) is set as current, StateManager calls link function on it (https://github.com/photonstorm/phaser/blob/master/v2/src/core/StateManager.js#L459). It adds lot of properties to it - all the shortcuts, that enable you later to write simply this.add, this.tweens, this.rnd, ... Btw, first few added properties are those in this listing {game: b.Game, add: b.GameObjectFactory, make: b.GameObjectCreator, camera: b.Camera, cache: b.Cache…}.

 Minimal link would be only game for referencing parent game. But then, you would loose all shortcuts and would have to write this.game.add, this.game.camera. Some people do it like this, but writing this.add or this.camera is shorter and more convenient.

Link to comment
Share on other sites

 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...