Game Development Tutorial: Swift and SpriteKit Part 6 - Working with Physics Part 2

29. July 2014

 

Ok I think I ended up with an overly complicated title for this series once again…  This is the next part in the ongoing Swift with SpriteKit tutorial series.  In the previous part we hooked up a very simple physics simulation.  First starting with basic gravity then with a collision off the edge of the screen.  In this part we are going to look at some slightly more complicated collision scenarios.  This tutorial section is going to be a bit more code heavy than before.

 

In this first code example, we are going to have two physics guided balls on the screen.   This example will show how to “do something” when two objects collide.  Let’s hope right in:

 

import SpriteKit

 

class GameScene: SKScene, SKPhysicsContactDelegate {

    override func didMoveToView(view: SKView) {

        

        // Define the bitmasks identifying various physics objects

        let sphereObject : UInt32 = 0x01;

        let worldObject : UInt32 = 0x02;

        

        //Create a ball shape

        var path = CGPathCreateMutable();

        CGPathAddArc(path, nil, 0, 0, 45, 0, M_PI*2, true);

        CGPathCloseSubpath(path);

 

        // Create one ball

        var shapeNode = SKShapeNode();

        shapeNode.path = path;

        shapeNode.lineWidth = 2.0;

        shapeNode.position = CGPoint(x:self.view.frame.width/2,y:self.view.frame.height);

        

        // Set the ball's physical properties

        shapeNode.physicsBody = SKPhysicsBody(circleOfRadius: shapeNode.frame.width/2);

        shapeNode.physicsBody.dynamic = true;

        shapeNode.physicsBody.mass = 5;

        shapeNode.physicsBody.friction = 0.2;

        shapeNode.physicsBody.restitution = 1;

        shapeNode.physicsBody.collisionBitMask = sphereObject | worldObject;

        shapeNode.physicsBody.categoryBitMask = sphereObject;

        shapeNode.physicsBody.contactTestBitMask = sphereObject;

 ��      

        // Now create another ball

        var shapeNode2 = SKShapeNode();

        shapeNode2.path = path;

        shapeNode2.position = CGPoint(x:self.view.frame.width/2,y:self.view.frame.height/2);

        

        shapeNode2.physicsBody = SKPhysicsBody(circleOfRadius: shapeNode.frame.width/2);

        shapeNode2.physicsBody.dynamic = true;

        shapeNode2.physicsBody.mass = 5;

        shapeNode2.physicsBody.friction = 0.2;

        shapeNode2.physicsBody.restitution = 1;

        shapeNode2.physicsBody.collisionBitMask = sphereObject | worldObject;

        shapeNode2.physicsBody.categoryBitMask = sphereObject;

        shapeNode2.physicsBody.contactTestBitMask = sphereObject;

        

        // Now make the edges of the screen a physics object as well

        scene.physicsBody = SKPhysicsBody(edgeLoopFromRect: view.frame);

        scene.physicsBody.contactTestBitMask = worldObject;

        scene.physicsBody.categoryBitMask = worldObject;

        

        // Make gravity "fall" at 1 unit per second along the y-axis

        self.physicsWorld.gravity.dy = -1;

        

        self.addChild(shapeNode);

        self.addChild(shapeNode2);

        

        // We implement SKPhysicsContactDelegate to get called back when a contact occurs

        // Register ourself as the delegate

        self.physicsWorld.contactDelegate = self;

 

    }

    

    // This function is called on contact between physics objects

    func didBeginContact(contact:SKPhysicsContact){

        let node1:SKNode = contact.bodyA.node;

        let node2:SKNode = contact.bodyB.node;

        

        // Node 1 is the object the hit another object

        // Randomly apply a force of 0 - 1000 on both x and y axis

        node1.physicsBody.applyImpulse(CGVector(

            CGFloat(arc4random() % 1000),

            CGFloat(arc4random() % 1000)));

        

        // Node 2 is the other object, the one being hit

        node2.physicsBody.applyImpulse(CGVector(

            CGFloat(arc4random() % 1000),

            CGFloat(arc4random() % 1000)));

    }

    

}

 

And when you run it:

 

Physics3

 

A lot of the code is very similar to our previous example, so I will only focus on what’s new.  The first thing you may notice is our class now implements SKPhysicsContactDelegate.  This provides a function, didBeginContact() that will be called when a contact occurs.  In this example, didBeginContact will only be called when a contact occurs between spheres, and not the edge of the screen.  I will explain why shortly.

 

The only other major change in this code is for each PhysicsObject in the scene, we now define a couple values, collisionBitMask, categoryBitmask and contactTestBitMask.  Earlier in the code you may have noticed:

        let sphereObject : UInt32 = 0x01;

        let worldObject : UInt32 = 0x02;

This is where we define our two bitmasks.  Bitmasking may be a somewhat new concept to you.  Basically its a way of packing multiple values into a single variable.  Lets use a simple 8 byte char as an example.  In memory, in terms of bits, a char is composed of 8 bits that can be either on or off.  Like this for example:

10011001

 

So using a single byte of memory, we are able to store 8 individual on/off values.  1 representing on, while 0 represents off.  Of course in variables we don’t generally deal with values in binary form, but instead we often use hexadecimal.  The value above, 10011001 as binary translates to 153 decimal or 0x99 in hex.  Now lets look at our defined values.  We are essentially saying

0001 is a sphereObject

0010 is a worldObject.

 

Now using bitwise math you can do some neat stuff. For example, using a bitwise AND, you can make an object out of both:

let worldSphere = sphereObject & worldObject; // result 0011

This allows you to pack multiple on/off values into a single variable.  Now a full discussion on bitwise math is way beyond the scope of this tutorial, you can read more about it here.  But the basics above should get you through this code example.

 

Basically in SpriteKit physics you define the “type” of an object using categoryBitmask.  So in this example we set the categoryBitmask of each of our spheres to sphereObject, and the world frame to worldObject.  Next you tell each object what it interacts with, like we did here:

        shapeNode2.physicsBody.collisionBitMask = sphereObject | worldObject;

        shapeNode2.physicsBody.categoryBitMask = sphereObject;

        shapeNode2.physicsBody.contactTestBitMask = sphereObject;

What we are saying here is this node will collide with sphereObjects OR worldObjects, but it is a sphereObject and will only contact with other sphereObjects.

Therefore, our contact delegate will only be called when two spheres contact, while nothing will happen when a sphere contacts the side of the screen.  As you notice from the balls bouncing off the side, the collision still occur.  By default, each collision and contact bit mask is set to 0xFFFFFF, which basically sets all possible bits to on, meaning everything contacts and collides with everything else.

 

The delegate function called each time a collision occurs between sphere objects is pretty straight forward:

    func didBeginContact(contact:SKPhysicsContact){

        let node1:SKNode = contact.bodyA.node;

        let node2:SKNode = contact.bodyB.node;

        

        // Node 1 is the object the hit another object

        // Randomly apply a force of 0 - 1000 on both x and y axis

        node1.physicsBody.applyImpulse(CGVector(

            CGFloat(arc4random() % 1000),

            CGFloat(arc4random() % 1000)));

        

        // Node 2 is the other object, the one being hit

        node2.physicsBody.applyImpulse(CGVector(

            CGFloat(arc4random() % 1000),

            CGFloat(arc4random() % 1000)));

    }

Basically for each node involved in the collision we apply a random impulse ( think push ) in a random direction between 0 and 1000 in both the x and y axis.

Speaking of applying force, let’s take a quick look at another example:

import SpriteKit

 

class GameScene: SKScene, SKPhysicsContactDelegate {

    

    var shapeNode:SKShapeNode;

    

    init(size:CGSize) {

 

        shapeNode = SKShapeNode();

        //Create a ball shape

        var path = CGPathCreateMutable();

        CGPathAddArc(path, nil, 0, 0, 45, 0, M_PI*2, true);

        CGPathCloseSubpath(path);

        

 

        shapeNode.path = path;

        shapeNode.lineWidth = 2.0;

 

        

        // Set the ball's physical properties

        shapeNode.physicsBody = SKPhysicsBody(circleOfRadius: shapeNode.frame.width/2);

        shapeNode.physicsBody.dynamic = true;

        shapeNode.physicsBody.mass = 5;

        shapeNode.physicsBody.friction = 0.2;

        shapeNode.physicsBody.restitution = 1;

        // this time we dont want gravity mucking things up

        shapeNode.physicsBody.affectedByGravity = false;

        

        super.init(size:size);

    }

    

    override func didMoveToView(view: SKView) {

 

        

        // Position the ball top center of the view

        shapeNode.position = CGPoint(x:view.frame.width/2,y:view.frame.height);

 

        // Now make the edges of the screen a physics object as well

        scene.physicsBody = SKPhysicsBody(edgeLoopFromRect: view.frame);

        

        self.addChild(shapeNode);

        

    }

    

    override func keyUp(theEvent: NSEvent!) {

 

        switch theEvent.keyCode{

        

        case126: // up arrow

            shapeNode.physicsBody.applyImpulse(CGVector(0,1000));

        case125: // down arrow

            shapeNode.physicsBody.applyImpulse(CGVector(0,-1000));

        case123: // left arrow

            shapeNode.physicsBody.applyForce(CGVector(-1000,0));

        case124: // right arrow

            shapeNode.physicsBody.applyForce(CGVector(1000,0));

        case49: // spacebar

            shapeNode.physicsBody.velocity = CGVector(0,0);

            shapeNode.position = CGPoint(x: self.view.frame.width/2,y:self.view.frame.height/2);

        default:

            return;

        }

    }

    

}

This sample is pretty straight forward, thus no animated gif. If the user presses up or down, an implies is applied along that direction. If the user presses left or right, a force is applied instead.  While if the user hits the spacebar, the sphere’s velocity is set to nothing and it is manually moved to the centre of the screen. One thing you might notice is implies moves a great deal more than force.  This is because force is expected to be applied per frame.  Think of it like driving a car.

An impulse is like i loaded your car into a gigantic slingshot and propelled you along at a blistering speed.

Force on the other hand is much more akin to you driving the car yourself.  If you take your foot of the gas, you rapidly decelerate.  If on the other hand you keep your foot down ( apply the same amount of force each frame ), you will move along consistently at the same speed.

As you see from the above example, you can also manipulate velocity and position directly.  Generally though this mucks with the physics simulation something severe, so generally isn’t recommended if alternatives exist.




Silo 3D modeller 2.3 released. Now available on Linux

28. July 2014

Silo 3D is a nice affordable 3D package I previously featured in the GameFromScratch 3D application list.  My comment at the time was:

 

SiloLogo

I highly recommend you check out the 30 day download, but caution you that the developer support is incredibly iffy.  When evaluating your purchase, ask yourself if the version you are evaluating is worth the price of admission WITHOUT any further patches or upgrades, as there may be none!

 

 

That comment was made over two years ago about version 2.2.  Today I received an email that version 2.3 was released.  By far an away the biggest feature is a port to Linux.  Here is the announcement of 2.3’s features:

 

 Nevercenter brings versatile Silo 3D modeler to Linux, updates codebase with new 2.3 release. 

Responding to the number one request from users, Nevercenter has brought its well-loved 3D modeling software Silo to Linux for the first time with the software’s new version 2.3 update. The update is free to registered users, and also includes bug fixes and improvements to the internal codebase which benefit the Windows and OS X versions. 

Silo's utility as a focused subdivision surfaces modeling solution will be greatly enhanced with support for Linux, the operating system of choice for many professional studios and, increasingly, individuals. "Silo is designed to be lightweight and flexible," said Nevercenter president Tom Plewe. "We want it to fit as seamlessly as possible into any workflow, and obviously this is a huge step in that direction." 

Silo's internals have also received significant updates including an updated windowing system and bug fixes across all platforms, as well as added support for .stl import. 

The free update is available now to all existing Silo 2 users. A license for Silo across all three platforms can currently be purchased for the sale price of just $109 via http://www.nevercenter.com/silo , where a trial version and more information can also be found.

 

From the forum discussion on CGSociety, the following are details of the update:

 

* Moved to more modern Qt 5.x

* Linux support (RHEL/CentOS 6, recent Fedoras, recent Ubuntus have been tested; not all distros will work)

* 64-bit support for Mac OS X and Linux

* STL import

* Bug fixes

 

So more a stability release than a feature packed one.  That said, it’s nice to see any signs of life at Silo, so hopefully we will see more in the future.  So is Silo worth checking out?  Two or three years ago I would have said most certainly.  The lack of changes coupled with the improvements we’ve seen in Blender make that a bit trickier.  That said, in a world were Modo is one of the cheapest options, and it’s over a grand!!! yes, low priced alternatives are certainly always welcome.  Of course, there is a 30 day trial available.

 

Oddly enough, Silo is also available on Steam for less money Silo is available on Steam for $79.  I’m not entirely certain if there is a difference between versions.  One license gets you all three supported platforms, which is nice.




Game Development Tutorial: Swift and SpriteKit Part 6 - Working with Physics Part 1

25. July 2014

 

Now we are going to look at using Physics in our Spritekit based game.  If you’ve worked with a physics engine before, a lot of this is going to feel very familiar.  One of the key differences from many other engines is SpriteKit handles updating the graphics as well as the physics.  It’s a fairly involved process, so I am going to split this across multiple posts.

 

The first one is going to be short and sweet.  We are going to configure the physics of a sphere, a SKShapeNode.  It is simply going to exist and let gravity take it’s course.  Code time:

 

import SpriteKit

 

class GameScene: SKScene {

    override func didMoveToView(view: SKView) {

        view.scene!.anchorPoint = CGPoint(x: 0.5,y: 0.5)

        

        var shapeNode = SKShapeNode();

        var path = CGPathCreateMutable();

        CGPathAddArc(path, nil, 0, view.bounds.height/2, 45, 0, M_PI*2, true);

        shapeNode.path = path;

        shapeNode.lineWidth = 2.0;

        

        shapeNode.physicsBody = SKPhysicsBody(circleOfRadius: 45.0);

        shapeNode.physicsBody.dynamic = true;

        

        // Make gravity "fall" at 1 unit per second along the y-axis

        self.physicsWorld.gravity.dy = -1;

        self.addChild(shapeNode);

    }

}

 

And once run:

 

 

Well, that was simple enough, let’s take a quick run through the code.

 

We start off by creating an SKShapeNode.  This shape node is defined by a CGPath.  You can think of a CGPath as a set of drawing instructions.  In this case it consists of a single arc that draws a circle.    Once our path is created we set it to be our shapeNodes path.  We set the lineWidth to 2 to make it a bit more visible.

 

Next we define the physicsBody.  Every SKNode has a SKPhysicsBody.  The SKPhysicsBody is the nodes’ representation in the physics engine.  When defining a physics body you pick the shape that most matches your underlying shape.  In this case it’s a no brainer to use a circle.  There are constructors available for rectangles, polygons, edges, etc.  Of all the options however, the circle is the least processing intensive.  So if you need just a rough physical representation, prefer the more simple types ( circle, then rectangle ) and leave the more complex types until required.  The key thing here is the dynamic property.  This is what tells SpriteKit to include your SKNode in the physics calculation.  If this isn’t set, nothing is going to happen!

 

Finally we set the gravity value dy to -1.  This means gravity moves by a delta of -1 unit per second on the y axis.  Notice the concept of “unit” here, it is very import.  When dealing with SpriteKit ( and many other Physics engines ), a unit is completely arbitrary.  So you may ask “1 what?”.  The answer is, 1 whatever… just be consistent about it.  In your code you could choose 1 to be a millimetre, an inch, a foot, a lightyear.  A lot of it comes down to the type of game you are working on.  One thing to keep in mind here though, massive ranges in value are not a good thing…  so don’t mix units.  For example, trying to represent something in millimetres and kilometres in the same simulation is a sure recipe for disaster!

 

Now let’s give our ball something to collide with… that being the edge of the window.  While we are at it, let’s add a bit of bounce to our step:

 

import SpriteKit

 

class GameScene: SKScene {

    override func didMoveToView(view: SKView) {

 

        //Create the ball

        var shapeNode = SKShapeNode();

        var path = CGPathCreateMutable();

        CGPathAddArc(path, nil, 0, 0, 45, 0, M_PI*2, true);

        CGPathCloseSubpath(path);

        shapeNode.path = path;

        shapeNode.lineWidth = 2.0;

        shapeNode.position = CGPoint(x:self.view.frame.width/2,y:self.view.frame.height);

        

        // Set the ball's physical properties

        shapeNode.physicsBody = SKPhysicsBody(circleOfRadius: shapeNode.frame.width/2);

        shapeNode.physicsBody.dynamic = true;

        shapeNode.physicsBody.mass = 1;

        shapeNode.physicsBody.friction = 0.2;

        shapeNode.physicsBody.restitution = 1;

        

 

        // Now make the edges of the screen a physics object as well

        scene.physicsBody = SKPhysicsBody(edgeLoopFromRect: view.frame);

        

        // Make gravity "fall" at 1 unit per second along the y-axis

        self.physicsWorld.gravity.dy = -1;

 

        self.addChild(shapeNode);

    }

}

 

And run this:

 

 

 

Not, the jerkiness you see isn’t from the physics, but instead the animated gif.  Getting that to encode to a reasonable size was oddly a bit of a battle.

 

The key difference here is first of all, we set the edges of the screen as a physics object for our ball to collide against.  A key thing to remember with SpriteKit, every thing is a SKNode, even the scene itself!

 

Next we set a couple key physics properties for our ball, mass, friction and restitution.  Mass is the weight of the object… going back to the age old question, what falls faster… a ton of feathers, or a ton of bricks?  Friction is how two surfaces react to each other, generally used when simulating “sliding” and is fairly irrelevant in this example.  Restitution is the key value.  For lack of a better explanation, restitution is the bounciness of the objet.  Lower value, less bounce, higher value, higher bounce.  A value higher than 1 will result in an object actually gaining momentum after a collision ( aka, going higher UP then it fell from before bouncing ).

 

Next up we will work on actual collisions.

Programming , ,




Playing around with Three.JS -- Part Two of a not quite a tutorial series

23. July 2014

 

In Part One we looked at the basics of working with the Three.js graphics library.  We got as far as creating a camera and a textured 3D object.  Now is the true test of ease of use… getting a 3D model exported from Blender and displayed in our browser.  HTML libraries face an even bigger burden, as their access to the local file system isn’t as seamless as most other games.  Simply opening up an FBX or DAE file isn’t an option.  Let’s take a look at how ThreeJS works around this issues.

 

 

First’s thing first, I needed a Blender Blend file to work with.  This actually lead me down this road, resulting in a post about taking a Blend file from the web and making it game ready.  Anyways, I did.  I started with this file, merged the geometry, UV mapped it, and baked the Blender materials to a single texture map.  I am not entirely sure if I can share the resulting file or not, so you may have to provide your own Blender file or follow the linked tutorial to generate one of your own.

 

Anyways, this is what we are starting with…

image 

 

Let’s see how close we can get with Three.js.

 

The first obvious question is… how the hell do we get this model in to Three.JS from Blender?

Well, the answer is a plugin.  Follow the installation direction, however in my case the path was wrong.  For Blender 2.71, my actual plugin directory is C:\Program Files\Blender Foundation\Blender\2.71\scripts\addons\io_mesh_threejs.

 

There is one very critical thing to be aware of here… when downloading the files from Github, be certain to download the RAW format:

image

 

This particular mistake caused me a bit of pain, don’t make the same mistake!

 

Once you’ve copied each of these three files, configure the plugin in Blender.  If Blender is running, restart it.

Now select File->User Preferences:

image

 

In the resulting dialog select Addons, then in the search box type “three”.  If it installed correctly it will show on the right.  Click the checkbox to enable the plugin.

image

 

Now if you check the File->Export menu, you should see Three.js as an option.

image

 

When exporting you can clearly see there are a ton of options:

image

 

The options I selected above is for just exporting the mesh and materials.  No animation data, lights, cameras, etc… Scaling and Flip YZ all depend on the orientation of your game engine.

 

This exporter creates a JSON js like this one:

 

{

	"metadata" :
	{
		"formatVersion" : 3.1,
		"generatedBy"   : "Blender 2.7 Exporter",
		"vertices"      : 8,
		"faces"         : 6,
		"normals"       : 2,
		"colors"        : 0,
		"uvs"           : [24],
		"materials"     : 1,
		"morphTargets"  : 0,
		"bones"         : 0
	},

	"scale" : 1.000000,

	"materials" : [	{
		"DbgColor" : 15658734,
		"DbgIndex" : 0,
		"DbgName" : "Material",
		"blending" : "NormalBlending",
		"colorAmbient" : [0.6400000190734865, 0.6400000190734865, 0.6400000190734865],
		"colorDiffuse" : [0.6400000190734865, 0.6400000190734865, 0.6400000190734865],
		"colorEmissive" : [0.0, 0.0, 0.0],
		"colorSpecular" : [0.5, 0.5, 0.5],
		"depthTest" : true,
		"depthWrite" : true,
		"mapDiffuse" : "crate.jpg",
		"mapDiffuseWrap" : ["repeat", "repeat"],
		"shading" : "Lambert",
		"specularCoef" : 50,
		"transparency" : 1.0,
		"transparent" : false,
		"vertexColors" : false
	}],

	"vertices" : [1,-1,0,1,0,1,-1,0,0,0,-1,0,1,0,0,0,1,1,-1,1,0,0,0,0],

	"morphTargets" : [],

	"normals" : [0.577349,0.577349,0.577349,0.577349,0.577349,-0.577349],

	"colors" : [],

	"uvs" : [[0.988679,0.99767,0.988677,0.016243,0.007251,0.016244,0.007252,0.
	997671,0.989755,0.017099,0.989755,0.998526,0.008328,0.998526,0.008328,0.017099,
	0.990714,0.989755,0.009287,0.989755,0.009286,0.008328,0.990713,0.008328,0.
	000516,0.993662,0.981943,0.993661,0.981942,0.012235,0.000516,0.012235,0.987766,
	0.997568,0.987766,0.016141,0.006339,0.016141,0.006339,0.997568,0.986807,0.
	986807,0.986807,0.005381,0.00538,0.00538,0.00538,0.986807]],

	"faces" : [43,0,3,2,1,0,0,1,2,3,0,0,1,1,43,4,7,6,5,0,4,5,6,7,0,0,1,1,43,0,4,5,1,
	0,8,9,10,11,0,0,1,1,43,1,2,6,5,0,12,13,14,15,1,1,1,1,43,2,3,7,6,0,16,17,18,19,1,
	0,0,1,43,3,0,4,7,0,20,21,22,23,0,0,0,0],

	"bones" : [],

	"skinIndices" : [],

	"skinWeights" : [],

  "animations" : []


}

 

In theory things should just work, but since when did gamedev give a damn about theory?  Suffice to say I ran into a bit of a problem fully documented here.  The bug actually had nothing to do with Three.js, it was actually caused by my IDE WebStorm.

 

Anyways… once I figured out the problem, the code to load a model was extremely straightforward:

 

///<reference path="./three.d.ts"/>

class ThreeJSTest {
    renderer:THREE.WebGLRenderer;
    scene:THREE.Scene;
    camera:THREE.Camera;

    constructor() {
        this.renderer = new THREE.WebGLRenderer({ alpha: true });

        this.renderer.setSize(500, 500);
        this.renderer.setClearColor(0xFFFFFF, 1);

        document.getElementById('content').appendChild(this.renderer.domElement);

        this.scene = new THREE.Scene();

        this.camera = new THREE.PerspectiveCamera(75
            , 1
            , 0.1, 1000);

        this.camera.position = new THREE.Vector3(10, 0, 10);
        this.camera.lookAt(new THREE.Vector3(0, 0, 0));


        // New code begins below
        // Create a loader to load in the JSON file
        var modelLoader = new THREE.JSONLoader();

        // Call the load method, passing in the name of our generated JSON file
        // and a callback for when loadign is complete.
        // Not fat arrow typescript call for proper thisification.  AKA, we want 
        this to be this, not that
        // or something else completely
        modelLoader.load("robot.jsm", (geometry,materials) => {
            // create a mesh using the passed in geometry and textures
            var mesh = new THREE.SkinnedMesh(geometry,new THREE.MeshFaceMaterial(
                       materials));
            mesh.position.x = 0; mesh.position.y = mesh.position.z = 0;
            // add it to the scene
            this.scene.add(mesh);
        });

        this.scene.add(new THREE.AmbientLight(new THREE.Color(0.9,0.9,0.9).
                       getHex()));
        this.renderer.render(this.scene, this.camera);
    }

    render() {
        requestAnimationFrame(() => this.render());
        this.renderer.render(this.scene, this.camera);
    }

    start() {
        this.render();
    }
}

window.onload = () => {
    var three = new ThreeJSTest();
    three.start();
};

 

And when you run it:

image

 

Mission accomplished!  The astute reader may notice the file was renamed robot.jsm.  That was to work around the problem I mentioned earlier.

 

 

 

This isn’t actually the only option for loading 3D models into Three.js, there are actually a series of loaders available as a separate download from the Github site.  It is however certainly the easiest one!  The next few steps took me two full days to fight my way through!  Some of the blame is on TypeScript, some is on me and of course, CORS reared it ugly head as well.  Oh, and to add to the fun, a recent change in Three.js introduced an error in the version of ColladaLoader.js I downloaded. This was one of those trials that Google was no help with, so hopefully this guide will help others in the future.  Anyways… on with the adventure!

 

We are actually about to run smack into two different problems with using a Three.js plugin.  The first one is TypeScript related.  You see, the ColladaLoader plugin is not a core part of Three.js.  In JavaScript, this is no big deal.  In TypeScript however, big deal.  You see, until now we have been relying on the generated .d.ts file from StrictlyTyped for defining all of the types in Three.js from JavaScript in their corresponding TypeScript form.  However, since this is a plugin and not a core part of Three.js, this means the d.ts file has no idea how ColladaLoader works.

 

Ultimately this means you have to had roll your own Typescript definition file. I had to struggle a bit to find the exact TypeScript syntax to map ColladaLoader so it will run in TypeScript within the THREE namespace with proper callback syntax.  First off, create a file called ColladaLoader.d.ts   Now enter the following code:

 

///<reference path="./three.d.ts"/>

declare module THREE {
    export class ColladaLoader{
        options:any;
        load(
            name:string,
            readyCallback:(result:any)=> void,
            progressCallback:( total:number,loaded:number)=> void);
    }
}

 

 

I should probably point out, I only implemented the barest minimum of what I required.  In your case you may have to implement more of the interface.  Also note I also took the lazy approach to defining options.  By returning any, my code will compile in TypeScript, but I do lose some of the type checking.  The alternative would have been to define the Options type and I am way too lazy for that.  The above definition enable me to call the load() method and set options, which is all I actually needed.  I don’t even want to talk about how long it took me to puzzle out those 10 lines of code so that the generated code actually matched THREE.js!

 

OK, we now have ColladaLoader.d.ts defined let’s look at code to use ColladaLoader to load a DAE (COLLADA) file:

 

///<reference path="./three.d.ts"/>
///<reference path="./ColladaLoader.d.ts"/>


class ThreeJSTest {
    renderer:THREE.WebGLRenderer;
    scene:THREE.Scene;
    camera:THREE.Camera;
    loader:THREE.ColladaLoader;
    light:THREE.PointLight;

    constructor() {
        this.renderer = new THREE.WebGLRenderer({ alpha: true });

        this.renderer.setSize(500, 500);
        this.renderer.setClearColor(0xFFFFFF, 1);

        document.getElementById('content').appendChild(this.renderer.domElement);

        this.scene = new THREE.Scene();;

        this.camera = new THREE.PerspectiveCamera(75
            , 1
            , 0.1, 1000);

        this.camera.position = new THREE.Vector3(5, 0, 5);
        this.camera.lookAt(new THREE.Vector3(0, 0, 0));

        // Create a loader
        this.loader = new THREE.ColladaLoader();

        // Add a point light to the scene to light up our model
        this.light = new THREE.PointLight();
        this.light.position.set(100,100,100);
        this.light.intensity = 0.8;
        this.scene.add(this.light);

        this.renderer.render(this.scene, this.camera);

        // Blender COLLADA models have a different up vector than Three.js, set 
        this option to flip them
        this.loader.options.convertUpAxis = true;

        // Now load the model, passing the callback finishedLoading() when done.
        this.loader.load("robot.dae",
            (result) => this.finishedLoading(result),
            (length,curLength)=>{
                // called as file is loading, if you want a progress bar
            }
        );
    }

    finishedLoading(result){
        // Model file is loaded, add it to the scene
        this.scene.add(result.scene);
    }
    render() {
        requestAnimationFrame(() => this.render());
        this.renderer.render(this.scene, this.camera);
    }

    start() {
        this.render();
    }
}

window.onload = () => {
    var three = new ThreeJSTest();
    three.start();
};

 

 

Finally of course we need to export our COLLADA model.  Using Blender, you can export using File->Export->Collada menu option.  These are the settings I used:

image

 

And when you run it:

image

 

That said, there is a really good chance this isn’t what is going to happen to you when you run your project.  Instead you are going to probably receive a 404 error that your dae file is not found.  This is because, unlike before with the JSON file being added directly to your project, this time you are loading the model using an XML HTTP Request.  This causes a number of problems.  The first and most likely problem you are going to encounter is if you are running your application locally from your file system instead of a server.  By default XHR requests do not work this way ( no idea why, seems kinda stupid to me ).  There is a switch that allows chrome to run XHR local requests ( link here ) using --allow-file-access-from-files.

 

In my case the problem was a little bit different.  I use WebStorm, which includes a built in web server to make this kind of stuff easier.  This however raises a completely different set of problems…  CORS  Cross Origin Resource Sharing.  In a very simple description, CORS is a security method for making XML HTTP Requests across different servers.  That said, how the heck do you set it when you are working with a built in stripped down development server?  Fortunately WebStorm have thought about that.

 

Assuming you are using Chrome and have the Webstorm plugin install, in the Chrome address bar, go to chrome://extensions.

image

 

Click the Options button.

image

Now simply add 127.0.0.1 to the allow list and press Apply. 

 

Now if you run from Webstorm, no more 404 errors.

 

A moment about TypeScript

 

I’ve been using TypeScript a fair bit lately and this is the first time I’ve run into major problems with it.  But the experience is enough that I can safely say…

 

TypeScript is not appropriate for new developers to use!

 

Simply put, unless you have a decent amount of experience with JavaScript, you really shouldn’t use TypeScript.  You will have to read the generated code at some point in time, and if you don’t full understand the code it generates, you are doomed.  The minute definition files arent available to you the experience becomes a hell of a lot less fun.  The process of mapping TypeScript types to existing JavaScript libraries is not trivial and requires you to have a pretty good understanding of both languages.  Especially when the library you are trying to define uses a number of clever JavaScript tricks, which basically… is all of them.  The fact there isnt a reliable tool out there for generating at least boilerplate .d.ts files from .js files is a bit of a puzzle to me.

 

Next, I also have to say TypeScript’s handling of this is just as mind bogglingly stupid as JavaScript’s.  That fat arrow ( => ) functions are used to capture local context, until used as an anonymous method, at which point they capture global (window) context, forcing you to resort to function() is downright perplexing.  I simply couldn’t get anonymous callback functions to have the proper this context no matter what syntax combination I tried.  Infuriatingly, the _this value Typescript automatically contains was set to the right value.

 

One other major downside I noticed about the language with my recent struggles is the newness of the language is very much an annoyance.  When researching bugs or workarounds you quite often find things that are reported as bugs, reported as changed,  or reported and never responded to.  This isn’t a bash on the language as it’s really only a problem that time can solve.  However, for a new developer, dealing with a language where a great deal of the material out there is potentially wrong because of language changes, that is certainly a challenge.  All languages change over time of course, but young languages change more and more dramatically.

 

Don’t get me wrong, I am not off Typescript, even though it spent a good part of the last two days pissing me off.  At least until ECMAScript 6 is the norm I can see a great deal of value in using TypeScript for large projects.

 

For beginners though, with little JavaScript experience… forget about it.  It’s going to cause more headaches than it’s worth.

Programming , ,




Unreal Engine 4.3 released

18. July 2014

I just received the following email from Unreal:

 

TranslusentFogDemo

Unreal Engine 4.3 Released!

 

More than 500 updates ship in this release! Unreal Engine 4.3 includes greatly improved mobile support, awesome new rendering features, improved Blueprint workflows, and strides toward an excellent experience on Mac and laptops.

 

Check out the new World Composition tools, spline features, and the preview of Paper2D, our 2D toolset! You also get SpeedTree 7 support, our work on Metal API for iOS 8 to date, and new Oculus Rift features such as time warping.

 

There’s no limit to what you can do with Unreal Engine 4 for only $19 per month.

 

Paper2D Side Scroller Template

Have fun with the new side-scroller template game as you become acquainted with Paper2D.

Read More

 

VR Couch Knights

We love VR, and Unreal Engine 4.3 supports the new Oculus DK2 out of the box! Dive into Epic’s popular “Couch Knights” demo which has been making the rounds at GDC and other shows.

Read More

SpeedTree 7

SpeedTree 7 support is here, and UE4 trees are 33% off in the SpeedTree store throughJuly 26!

Read More

Rendering Goodies

Rendering goodies include distance field ambient occlusion, skylight global illumination and shadowed translucency.

Read More

Behavior Trees

Better AI tools! Switch to the new Blackboard mode inside the Behavior Tree Editor to edit and debug Blackboard entries.

Read More

Large World Support!

Large world support! Check out the new World Composition tools. Create sub-levels and position them anywhere.

Read More

Customize Your Static Mesh Collision!

Customize your static mesh collision!

Read More

Spline Editing

Edit splines directly within your levels!

Read More

 

Build games and apps for Windows, Mac, iOS, Android, PlayStation 4, Xbox One, Linux, SteamOS, HTML5 and VR platforms.

Get Unreal for $19/Month

Mobile Developers!

Zen Gardens

Google recently demonstrated the graphics power of L, the upcoming release of Android, using Epic's Rivalry demo running on Tegra K1 at Google I/O. Mobile is a huge focus for UE4, and we hope you’ll enjoy all the latest improvements!

Read More

 

 

The UE4 Roadmap continues to evolve, and we encourage you to vote for features that want to use.

 

To ask questions and share feedback, please visit the forums or join our live broadcasts at Twitch.tv every Thursday at 2pm ET, which you can always go back and view atYouTube.com.

 

Hats off to the developers who contributed to this great release! These who helped are forever immortalized in the Credits section under the editor’s Help menu.

 

Thank you for being a part of this adventure. We can’t wait to see what you build next.

 

We are one step closer to Paper2D support, which is their support for 2D engines. Occulus Rift support is no doubt cool for those developing for the Rift. Not quite as impressive as the last release, but still a good amount of progress from Unreal.

News ,