Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon

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 , ,

18. July 2014

 

So, as you may be able to tell from the title, I’ve run into a bit of a bug moment.  I am in the process of getting Blender exported models to work with the Three.JS library, as a follow up to this post.  As with all things programming related you are going to run into your share of problems.  This post actually walks through the process of identifying and fixing a bug.

 

The first lesson of bug hunting is always simplify.  Try to replicate the problem in as little code as possible.  What follows below is a blow by blow of  the debugging process.

 

First off, it’s a Three.JS project using TypeScript authored in WebStorm.  All of these points are important to this story.  Not to ruin an upcoming post with too much code, I’ll just give the applicable code.  The problem is in this code… if it’s in code at all that is.  Yeah, that’s a bit of a hint.

 

        var modelLoader = new THREE.JSONLoader();

        modelLoader.load("dice.jsm", function(geometry,materials){
            var mesh = new THREE.SkinnedMesh(geometry,new THREE.MeshFaceMaterial(
                       materials));
            mesh.position. x = mesh.position.y = mesh.position.z = 0;
            this.scene.add(mesh);
        });

 

Anyways, I started off by trying to load this model:

image

Exported from Blender to Three.JS JSON format.

 

When I run the code however I get the following in WebStorm:

image

 

Unexpected token /?

 

Hmmm, looks like something went wrong in the export process.  This is never a fun thing to debug, as the output of the above model is a JSON file 1.5MB in size.

 

So, it’s time to simplify.  I need a model with a texture, nothing more.  Let’s make something about as basic as possible.  So I hack together a quick textured model in Blender and export it.  This is the new model:

image

 

Ok, that is definitely simpler.  Now when I run it I get the exact same error.  Ok, this file should be a hell of a lot easier to debug.  Let’s take a look at the generated JSON file.  Oh, top type… right click the js file and tell Webstorm to treat it as plain text, otherwise it will clobber your CPU trying to parse the javascript!

 

 

{

	"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" : []


}

 

Well, first obvious thing is to look for an offending / in this code.

Hmmm… there is none.  Well we wouldn’t make the big bucks if this was easy now would we?

 

Let’s go back to our error for a second:

image

 

Well, other than the fact we know we have a / where we shouldn’t, we also have the line of code that is going all explodey.  Let’s start there.  This is one of those things that makes WebStorm so freaking cool.  Just click the link “three.js:11960” and it will automatically download that script file and go to that position.  Let’s take a look at the resulting code:

image

 

Ok, that’s some pretty straight forward code.  Basically it’s a XML response function handler.  As we can tell from the above code, the callback executed all nice and dandy.  As you can see on line 11960 I’ve set a breakpoint to pause execution before our exception, let’s see if that gives us any insight.  If you don’t know about breakpoints and debugging, drop everything and learn.  You will literally become a better programmer overnight.

 

So… to the debugger!  Let’s see what the value of responseText is:

 

By hovering over it, everything looks fine, seems to match the file we are expecting:

image

 

That said, let’s take a look at the whole thing.  Now we are going to use a feature called “Evaluate Expression”.  Again, if you don’t know what this is, drop everything and go learn.  I really need to write a debugging tutorial…. 

 

image

 

Copy the value and paste it into your editor of choice.  Then scroll to the very bottom:

image

 

Oh son of a bi….

 

That my friend, is our bug.  See, Webstorm has the ability to generate something called a SourceMap, which helps the debugger translate your running code to the code you created, especially useful if you, like me, are working in a JavaScript generating language like TypeScript.  As you can see, sometimes this is not ideal however.  Basically when run, Webstorm was appending a source map to the bottom of our js file, rendering into invalid JSON and causing the parser to puke.

 

There are two immediate solutions to this problem.  First, we can disable source map generation.  This unfortunately is a project wide setting as far as I can tell, and I rather like the ability to debug.  The easier solution is to change it from a .js file to something different.  However, once deployed to a server this can have unintended side effects.  For example, IIS will not, by default, serve files without a registered mime type.

 

Oh, and for the astute, once I got past the problem be renaming the file extension, I did in fact discover two errors in my code.  The proper TypeScript code is;

 

        var modelLoader = new THREE.JSONLoader();

        modelLoader.load("dice.jsm", (geometry,materials) => {
            var mesh = new THREE.SkinnedMesh(geometry,new THREE.MeshFaceMaterial(
                       materials));
            mesh.position.x = 0; mesh.position.y = mesh.position.z = 0;
            this.scene.add(mesh);
        });

 

Why is an exercise for the reader. :)

Programming , , ,

15. July 2014

 

There was recently a flood of Three.js books on Safari lately including Essential Three.js and Game Development with Three.jsThree.js is a JavaScript based 3D library using WebGL ( and not, if not available ).  More importantly, it’s just really fun to play with!  Something about working in full 3D in a scripting language is just really satisfying.  I’ve only just been playing and really don’t have a clue what I’m doing, but I figured I would share my results.  As I have been on a TypeScript kick lately, I’ve been writing in TypeScript instead of plain JavaScript, but frankly the differences are fairly minimal.  You can get the TypeScript definitions on DefinatelyTyped.

 

I think I should make something perfectly clear… I have NO idea what I am doing, I am simply playing around.  This isn’t a WebGL tutorial by any definition of the word, just me having skim read a couple of books and played around with a new technology, nothing more.  So if you look at some code and thing “damn that looks hacky” or “isn’t that a really stupid thing to do?” the answer is probably yes! :)

 

So, disclaimer given, let’s jump right in. 

 

Since this is a web app, we need a host HTML page.  So, here is ours:

<!DOCTYPE html>

<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>ThreeJS Test</title>
    <script src="http://cdnjs.cloudflare.com/ajax/libs/three.js/r67/three.js"></script>
    <script src="app.js"></script>
</head>
<body>
<h1>ThreeJS Test</h1>

<div id="content" style="width:500px;height:500px"></div>
</body>
</html>

 

Nothing really shocking here.  We include three.js using the cloudflare content delivery network.  If you wanted of course you could download the library locally and deploy it from your own servers.  I assume you don’t have servers situated around the world, so a CDN will generally thrash your own servers performance.  Next we include app.js, the generated output from our typescript application.  In the actual HTML we create a 500x500 DIV named content, for predictably enough, our content!

 

Now lets take a look at a super simple example app, app.ts:

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

class ThreeJSTest {
    renderer: THREE.WebGLRenderer;
    constructor(){
        this.renderer = new THREE.WebGLRenderer({ alpha: true });
        this.renderer.setSize(500,500);
        this.renderer.setClearColor(0xFF0000,1);
        document.getElementById('content').appendChild(this.renderer.domElement);
    }

    start() {
        this.renderer.clear();
    }
}

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

 

Here in the constructor we create a WebGLRenderer, size it, set the background color to red ( using HTML format hex color coding ) then wire the renderer to the content div.

 

When you run it you should see:

image

 

Cool, our first Three.js application.  Now let’s do something 3D!  Let’s start by creating a camera and rendering a built in 3D object in wireframe. It's commented heavily, so I wont be explaining what is going on. If you are curious why I did something, leave a comment.

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

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

    constructor(){
        // Create the renderer, in this case using WebGL, we want an alpha channel
        this.renderer = new THREE.WebGLRenderer({ alpha: true });

        // Set dimensions to 500x500 and background color to white
        this.renderer.setSize(500,500);
        this.renderer.setClearColor(0xFFFFFF,1);

        // Bind the renderer to the HTML, parenting it to our 'content' DIV
        document.getElementById('content').appendChild(this.renderer.domElement);

        // Create a Scene
        this.scene = new THREE.Scene();

        // And a camera.  Set Field of View, Near and Far clipping planes
        this.camera = new THREE.PerspectiveCamera(45
            , 1
            , 0.1, 1000);

        // Position is -20 along the Z axis and look at the origin
        this.camera.position = new THREE.Vector3(0,0,-20);
        this.camera.lookAt(new THREE.Vector3(0,0,0));

        // Createa the geometry for a sphere with a radius of 5
        var sphereGeometry = new THREE.SphereGeometry(5);

        // Create a wireframe material that's blueish
        var sphereMaterial = new THREE.MeshBasicMaterial(
            {color: 0x7777ff, wireframe: true});

        // Now make a THREE.Mesh using the geometry and a shader
        var sphere = new THREE.Mesh(sphereGeometry,sphereMaterial);

        // And put it at the origin
        sphere.position = new THREE.Vector3(0,0,0);

        // Add it to the scene and render the scene using the Scene and Camera objects
        this.scene.add(sphere);
        this.renderer.render(this.scene,this.camera);
    }

    start() {
        // Well, arent I a bit pointless?
    }
}

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

 

And when run it we get:

 

image

 

Cool!  Now time for some texturing ( and as a result, lighting ).

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

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

    constructor() {
        // Create the renderer, in this case using WebGL, we want an alpha channel
        this.renderer = new THREE.WebGLRenderer({ alpha: true });

        // Set dimensions to 500x500 and background color to white
        this.renderer.setSize(500, 500);
        this.renderer.setClearColor(0xFFFFFF, 1);

        // Bind the renderer to the HTML, parenting it to our 'content' DIV
        document.getElementById('content').appendChild(this.renderer.domElement);

        // Create a Scene
        this.scene = new THREE.Scene();

        // And a camera.  Set Field of View, Near and Far clipping planes
        this.camera = new THREE.PerspectiveCamera(45
            , 1
            , 0.1, 1000);

        // Position is -20 along the Z axis and look at the origin
        this.camera.position = new THREE.Vector3(0, 0, -20);
        this.camera.lookAt(new THREE.Vector3(0, 0, 0));

        // Createa the geometry for a sphere with a radius of 5
        // This time we cranked up the number of sections horizontal and vertical to make a 
higher resolution globe
        var sphereGeometry = new THREE.SphereGeometry(5, 20, 20);

        // This time we create a Phong shader material and provide a texture.
        var sphereMaterial = new THREE.MeshPhongMaterial(
            {
                map: THREE.ImageUtils.loadTexture("earth_sphere.jpg")
            }
        );

        // Now make a THREE.Mesh using the geometry and a shader
        var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);

        // And put it at the origin
        sphere.position = new THREE.Vector3(0, 0, 0);

        // Add it to the scene and render the scene using the Scene and Camera objects
        this.scene.add(sphere);

        // We need some light so our texture will show, ad an ambient light to the scene
        this.scene.add(new THREE.AmbientLight(new THREE.Color(0.9,0.9,0.9).getHex()));
        this.renderer.render(this.scene, this.camera);
    }

    render() {
        // Each frame we want to render the scene again
        // Use typescript Arrow notation to retain the thisocity passing render to requestAnimationFrame
        // It's possible I invented the word thisocity.
        requestAnimationFrame(() => this.render());
        this.renderer.render(this.scene, this.camera);
    }

    start() {
        // Not so pointless now!
        this.render();
    }
}

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

Bet you can't guess what texture I went with!

 

image

 

So apparently textured 3D objects are nothing difficult.

 

This is getting pretty long, so I’ll cut it off here.  Next up I’m going to look at getting a Blender object rendering in Three.JS.

Programming ,

24. June 2014

 

 

In the previous part we looked at handling graphics in Phaser, now we are going to look at handling input.  This part is going to be code heavy and fairly light on description.  Look to the code comments for more details.

As is pretty common with game frameworks, there are a number of different ways to handle input and a number of different devices, so lets get started!

 

Using the cursor keys and polling for input

 

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

// Demonstrate the use of arrow keys in a Phaser app
// This application demonstrates creation of a Cursor and polling for input
class SimpleGame {
    game: Phaser.Game;
    jetSprite: Phaser.Sprite;
    cursors: Phaser.CursorKeys;

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

    preload() {
        var loader = this.game.load.image("jet", "jet.png");
    }

    create() {
        var image = <Phaser.Image>this.game.cache.getImage("jet");
        this.jetSprite = this.game.add.sprite(
            this.game.width / 2 - image.width / 2,
            this.game.height / 2 - image.height / 2,
            "jet");

        // create the cursor key object
        this.cursors = this.game.input.keyboard.createCursorKeys();
    }

    update() {
        // Update input state
        this.game.input.update();

        // Check each of the arrow keys and move accordingly
        // If the Ctrl Key + Left or Right arrow are pressed, move at a greater rate
        if (this.cursors.down.isDown)
            this.jetSprite.position.y++;
        if (this.cursors.up.isDown)
            this.jetSprite.position.y--;
        if (this.cursors.left.isDown) {
            if (this.cursors.left.ctrlKey)
                this.jetSprite.position.x -= 5;
            else
                this.jetSprite.position.x--;
        }
        if (this.cursors.right.isDown) {
            if (this.cursors.right.ctrlKey)
                this.jetSprite.position.x += 5;
            else
                this.jetSprite.position.x++;
        }
    }
}

window.onload = () => {
    var game = new SimpleGame();
};

 

When you run this code the familiar jet sprite is rendered centered to the canvas. You can then use the arrow keys to move the fighter around.  As you can see, in the state for each key is information on modifier keys like Control and Alt.  Polling for input ( that is, checking status each call to update ) is a valid way of controlling a game, but sometimes you instead want to respond to input as it arrives.  Let’s look now at an example of event driven keyboard handling:

 

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

// Demonstrate keyboard input handling via callback
class SimpleGame {
    game: Phaser.Game;
    jetSprite: Phaser.Sprite;
    W: Phaser.Key;
    A: Phaser.Key;
    S: Phaser.Key;
    D: Phaser.Key;

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

    preload() {
        var loader = this.game.load.image("jet", "jet.png");
    }

    moveLeft() {
        this.jetSprite.position.add(-1, 0);
    }
    moveRight() {
        this.jetSprite.position.add(1, 0);
    }
    moveUp(e: KeyboardEvent) {
        // As you can see the event handler is passed an optional event KeyboardEvent
        // This contains additional information about the key, including the Control
        // key status.
        // Basically if the control key is held, we move up or down by 5 instead of 1
        if (e.ctrlKey) 
            this.jetSprite.position.add(0, -5);
        else
            this.jetSprite.position.add(0, -1);
    }
    moveDown(e: KeyboardEvent) {
        if (e.ctrlKey)
            this.jetSprite.position.add(0, 1);
        else
            this.jetSprite.position.add(0, 1);
    }

    create() {
        var image = <Phaser.Image>this.game.cache.getImage("jet");
        this.jetSprite = this.game.add.sprite(
            this.game.width / 2 - image.width / 2,
            this.game.height / 2 - image.height / 2,
            "jet");

        // Create a key for each WASD key
        this.W = this.game.input.keyboard.addKey(Phaser.Keyboard.W);
        this.A = this.game.input.keyboard.addKey(Phaser.Keyboard.A);
        this.S = this.game.input.keyboard.addKey(Phaser.Keyboard.S);
        this.D = this.game.input.keyboard.addKey(Phaser.Keyboard.D);

        // Since we are allowing the combination of CTRL+W, which is a shortcut for close window
        // we need to trap all handling of the W key and make sure it doesnt get handled by 
        // the browser.  
        // Unfortunately you can no longer capture the CTRL+W key combination in Google Chrome
        // except in "Application Mode" because apparently Google thought an unstoppable un prompted
        // key combo of death was a good idea...
        this.game.input.keyboard.addKeyCapture(Phaser.Keyboard.W);

        // Wire up an event handler for each K.  The handler is a Phaser.Signal attached to the Key Object
        this.W.onDown.add(SimpleGame.prototype.moveUp, this);
        this.A.onDown.add(SimpleGame.prototype.moveLeft, this);
        this.S.onDown.add(SimpleGame.prototype.moveDown, this);
        this.D.onDown.add(SimpleGame.prototype.moveRight, this);
    }
}

window.onload = () => {
    var game = new SimpleGame();
};

 

As you can see, you can also create Phaser.Key objects and attach onDown event handlers ( technically Signals ) to each.  Of course you can reuse the same handler for multiple keys.  A couple key things to notice here… unlike the previous example, holding down a key will not cause continuous movement.  You must press and release the key over and over.  If you want constant movement, either use a polling method, use and action instead of updating each frame, or add some logic to move until the key is released.

 

The other thing to be aware of here is the use of the CTRL+W combination and addKeyCapture().  addKeyCapture() allows you to prevent the event from bubbling up, so once you’ve handled the key combination, it’s done.  Otherwise it would keep being passed up, either to other objects in the scene, or to the browser itself.  You can also use addKeyCapture to prevent default web behavior, such as scrolling when SPACE is pressed.

 

Programming , , , ,

9. June 2014

 

 

I’ve done a number of these walk through type tutorials using many different languages/libraries and there is one common traffic trend.  People LOVE reading about graphics.  In every single example the post I do about graphics always seems to draw the most traffic.  I guess we just love drawing stuff on screen.  Now for the good part, Phaser is good at it and makes it really all quite easy.

 

Loading and adding a sprite

 

Back in the previous post I actually jumped the gun a bit and showed preloading and rendering sprites.  Since so many people jump ahead straight to the graphics post, I’ll review the process.

 

/// <reference path="phaser.d.ts"/>
class SimpleGame {
    game: Phaser.Game;
    titleScreenImage: Phaser.Sprite;

    constructor() {
        this.game = new Phaser.Game(800, 600, Phaser.AUTO, 'content', { create: this.create, preload: this.preload });
    }
    preload() {
        this.game.load.image("title", "TitleScreen.png");
    }
    create() {
        this.titleScreenImage = this.game.add.sprite(0, 0, "title");
    }
}

window.onload = () => {
    var game = new SimpleGame();
};

 

The key concepts to be aware of here is preloading assets using game.load methods, where you pass in the filename as well as a unique string key that you will use to access the asset.  Then in create you can see this in action, where we add a sprite to the game using game.add.sprite, using the key “title” to access it.  In this case our “sprite” was a full screen image.  Now let’s look at how you can work with a sprite, this time using a slightly smaller image.

Working with sprites

 

For this section I am going to work with this sprite ( created in this tutorial series ):

jet

 

Add it to your project’s root directory.  In my case I’ve called it jet.png.  Using the above code, simply replace “TitleScreen.png” with “jet.png” and “title” with “jet” and you should see:

 

image

 

 

As you can see, our sprite is drawn at the top left corner of the screen.  That is because the value (0,0) in Phaser refers to the top left corner of the screen by default.  Let’s instead center our sprite using the following code:

 

/// <reference path="phaser.d.ts"/>
class SimpleGame {
    game: Phaser.Game;
    jetSprite: Phaser.Sprite;

    constructor() {
        this.game = new Phaser.Game(800, 600, Phaser.AUTO, 'content', { create: this.create, preload: this.preload });
    }
    preload() {
        var loader = this.game.load.image("jet", "jet.png");
    }
    create() {
        var image = <Phaser.Image>this.game.cache.getImage("jet");
        
        this.jetSprite = this.game.add.sprite(
            this.game.width / 2 - image.width / 2,
            this.game.height / 2 - image.height / 2,
            "jet");
    }
}

window.onload = () => {
    var game = new SimpleGame();
};

 

Run this code and:

image

 

We are now nicely centered, both to the window and sprite.

We have one major challenge with centering the image.  Until the sprite is created, it doesn’t have a width or height.  However, when you create the sprite you can set it’s position.  Of course it would be possible to create then move the sprite but that is hackish and could have unintended graphical problems.  Instead we can get the image we loaded using this.game.cache.getImage() then access the images dimensions.  One line of code might stand out for you here:

 

var image = <Phaser.Image>this.game.cache.getImage("jet");

 

This is TypeScript’s way of typecasting.  If you’ve worked in Java, C# or C++ you’ve no doubt encountered typescasting.  If your experience was mostly in JavaScript ( a mostly typeless language ), this might be new to you.  Basically what you are saying is “we promise the value returned by getImage() is of the type <Phaser.Image>, so make image a Phaser.Image”.  If you try to access a value or method in image that doesn’t exist in Phaser.Image, TypeScript will give you an error.

 

Positioning items in Phaser

 

When using a sprite, by default, transformations happen relative to the top left corner of the sprite.  This is why we had to subtract half to the width and height of the sprite when positioning it in the center of the screen.  Otherwise the top left corner of the sprite would be centered to the screen like this:

image

 

Sometimes however you would rather transform the sprite relative to a different point, commonly the very middle or occasionally the bottom left corner.   Fortunately there is an option for this, the anchor.  The anchor tells Phaser where to draw your Sprite relative to.  Here we set the anchor to the center of the sprite then draw it at (0,0) like so:

 

    create() {
        var image = <Phaser.Image>this.game.cache.getImage("jet");
        
        this.jetSprite = this.game.add.sprite(
            this.game.width / 2 - image.width / 2,
            this.game.height / 2 - image.height / 2,
            "jet");

        this.jetSprite.anchor.set(0.5,0.0)
        this.jetSprite.position.x = this.jetSprite.position.y = 0.0;
    }

 

And the result:

image

As you can see, draw calls for the sprite now position relative to it’s center.  Positioning sprites relative to their center is incredibly handy when it comes to rotation, while anchoring at the bottom is useful for platformers where you are aligning the sprite’s feet to the ground.  What you chose is entirely up to you.  The values passed in to anchor might be a bit confusing, as they are normalized, meaning they go from 0 to 1.  The values are all relative to the sprite itself, while (0,0) is the top left corner of the sprite, while (1,1) is the bottom right corner.  (1,0) would be the bottom left, while (0,1) would be the top right.

 

There is one important thing to be aware of here.  Anchor works relative to the source image, not the sprite itself.  Therefore if you intend to scale your sprites, instead of using anchor, you are going to want to use pivot instead.  (Until recently pivot was broken, but it appears to work now).  Pivot sets the center point of the sprite, not the image that composes the sprite.  Setting the pivot looks like this:

        this.jetSprite.pivot.x = this.jetSprite.width / 2;
        this.jetSprite.pivot.y = this.jetSprite.height / 2;

 

Again, you don’t have to set the anchor at all, but it can be useful.  Unlike anchor, pivot uses relative pixel coordinates within the sprite itself.  Therefore the mid-point is at (width/2,height/2).  Once again, (0,0) is the top left corner.

 

Simple Graphics

 

Sometimes you just want to draw primitive graphics on screen… lines, circles, boxes, that kind of stuff.  Fortunately Phaser has that built in as well in the form of the Graphics object.

 

/// <reference path="phaser.d.ts"/>
class SimpleGame {
    game: Phaser.Game;
    jetSprite: Phaser.Sprite;

    constructor() {
        this.game = new Phaser.Game(800, 600, Phaser.AUTO, 'content', { create: this.create, preload: this.preload });
    }
    preload() {
        var loader = this.game.load.image("jet", "jet.png");
    }
    create() {
        // Add a graphics object to our game
        var graphics = this.game.add.graphics(0, 0);

        // Create an array to hold the points that make up our triangle
        var points: Phaser.Point[] = [];
        // Add 4 Point objects to it
        points.push(new Phaser.Point());
        points.push(new Phaser.Point());
        points.push(new Phaser.Point());

        // Position one top left, top right and botto mmiddle
        points[0].x = 0;
        points[0].y = 0;

        points[1].x = this.game.width;
        points[1].y = 0;

        points[2].x = this.game.width/2;
        points[2].y = this.game.height;

        // set fill color to red in HEX form.  The following is equal to 256 red, 0 green and 0 blue.  
        // Do at 50 % alpha, meaning half transparent
        graphics.beginFill(0xff0000, 0.5);
        
        // Finally draw the triangle, false indicates not to cull ( remove unseen values )
        graphics.drawTriangle(points, false);

        // Now change colour to green and 100% opacity/alpha
        graphics.beginFill(0x00ff00, 1.0);

        // Draw circle about screen's center, with 200 pixels radius
        graphics.drawCircle(this.game.width / 2, this.game.height / 2, 200);
        
    }
}

window.onload = () => {
    var game = new SimpleGame();
};

 

The code is pretty heavily commented so should be self explanatory.  When you run it you should see:

image

 

 

A look behind the scenes

 

Let’s take a quick look at how graphics drawing works in Phaser.  That involves going back to this line:

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

Here you are passing in a lot of important information.  First (and second) are the resolution of your game.  Next is the type of Renderer that Phaser should use.  We mentioned this briefly in the prior tutorial.  You have the option of WEBGL or Canvas rendering ( or headless, which means no rendering at all and is used for server side programming ).  Which you chose depends heavily on the device you are supporting.  For example, currently no iOS devices support WebGL and only the most recent version of Internet Explorer work.  By selecting AUTO you let Phaser decide based on the device you are running on.  Finally ‘content’ is the HTML ID of the DIV to render our game in.

 

You may notice scattered throughout Phaser’s code/documentation are references to PIXI.  Pixi.js is a popular WebGL 2D renderer that is able to fallback on Canvas rendering when WebGL is unavailable.  Pixi is the renderer that Phaser uses, so you will occasionally see Pixi classes inside Phaser code.

 

There is one final thing to cover about graphics before moving on, full screen and handling multiple resolutions.

 

Going Full Screen

 

Now let’s take a look at an application that can go full screen:

 

/// <reference path="phaser.d.ts"/>
class SimpleGame {
    game: Phaser.Game;
    jetSprite: Phaser.Sprite;

    constructor() {
        this.game = new Phaser.Game(640, 480, Phaser.AUTO, 'content', { create: this.create, preload: this.preload });
    }
    preload() {
        var loader = this.game.load.image("jet", "jet.png");
    }

    // This function is called when a full screen request comes in
    onGoFullScreen() {
        // tell Phaser how you want it to handle scaling when you go full screen
        this.game.scale.fullScreenScaleMode = Phaser.ScaleManager.EXACT_FIT;
        // and this causes it to actually do it
        this.game.scale.refresh();
    }
    goFullScreen() {

    }
    create() {
        var image = <Phaser.Image>this.game.cache.getImage("jet");

        // Draw the jet image centered to the screen
        this.jetSprite = this.game.add.sprite(
            this.game.width / 2 - image.width / 2,
            this.game.height / 2 - image.height / 2,
            "jet");

        // Set background to white to make effect clearer
        this.game.stage.backgroundColor = 0xffffff;

        // Add a function that will get called when the game goes fullscreen
        this.game.scale.enterFullScreen.add(SimpleGame.prototype.onGoFullScreen, this);

        // Now add a function that will get called when user taps screen.
        // Function declared inline using arrow (=>) function expression
        // Simply calls startFullScreen().  True specifies you want anti aliasing.
        // Unfortunately you can only make full screen requests in desktop browsers in event handlers
        this.game.input.onTap.add(
            () => { this.game.scale.startFullScreen(true); },
            this);
    }

}

window.onload = () => {
    var game = new SimpleGame();
};

 

The comments cover most of what’s going on, but I thought I would touch on a couple things in the above example.  First you cant simply request to go fullScreen in Desktop browsers for security reasons.  This means your game can’t simply start in full screen, you need to make the call to startFullScreen() inside an event handler.  Most commonly this will be in the form of a “Click here for FullScreen” button or link.

 

Next is the ()=> syntax, known in TypeScript as an arrow function expression (if you’ve used C#, this syntax is going to look awfully familiar to you!) and is something that should be coming in the next JavaScript version (ECMAScript 6).  It is simply a more compact form of a function expression ( no need for the word function ) that is automatically scoped to “this”.  You could have created a function like onGoFullScreen like we did for enterFullScreen.  ( Coincidentally we could have also handled enterFullScreen using an arrow function.

 

The last thing to look at is the scale mode.  In this example we used Phaser.ScaleManager.EXACT_FIT, which scales the scene up to match your resolution.  There are two other options, SHOW_ALL and NO_SCALE.  Here is the result of running the code above using each setting:

 

Phaser.ScaleManager.EXACT_FIT

EXACT_FIT

 

Phaser.ScaleManager.NO_SCALE

NO_SCALE

 

Phaser.ScaleManager.SHOW_ALL 

SHOW_ALL

 

If you have an HDTV, you can think about them this way.  EXACT_FIT is the same as Stretch mode.  It scales the scene to use as much of the screen as possible, but can result in some distortion.  NO_SCALE does nothing, it simply shows the scene un-altered, centered to the screen.  SHOW_ALL is about the equivalent of Letterbox.  Basically it fits as well as it can while maintaining the aspect ration of your original scene. 

 

Don’t worry, that’s not it for graphics, we have all kinds of things coming up…  spritesheets, effects, particles, animation, etc…  That’s just it for the foundations.

 

Programming , , , ,

Month List

Popular Comments

PlayN install guide… the new way
Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon


7. November 2011

 

 

Now that PlayN has been pushed into Maven Central, life and installation just got a heck of a lot easier.  Not that I appreciate their timing… couldn’t you have done this on Friday before I wrote an installer! Winking smile

 

 

I will cover two ways to create a PlayN project.

 

 

Using Eclipse to create a new PlayN project

 

 

Load Eclipse.  If you haven’t already, install the JDK and Maven m2e plugins, which are described in this thread.

 

Select File->New->Other…

 

image

 

 

 

Scroll down and select Maven Project in the Maven folder. Click Next.

 

image

 

 

Click Next again, if you don’t want to change the Workspace that is.

 

image

 

 

In the “Filter” type Playn, then select com.googlecode.playn.  Click next.

 

image

 

 

 

Now fill in your game details.  There is a small bug in Eclipse in that the Finish button will not be available until you click focus away after filling in the gameName value.  Once you fill that in, click somewhere else on the dialog and the finish button will be enabled.  Click it.

 

 

image

 

 

Your game will be created, but you might get an error like I did:  Notice the red x by the testGame-android?

 

image

 

 

If you look down in the problems section, you will see details off the error:

 

image

 

 

Fixing this is pretty straight forward.  Right click each one and select, appropriately enough… Quick Fix.  Do that for both errors.

 

image

 

 

In the following dialog, select “Permanently mark goal…” then Finish.  Another dialog will pop up, simply click OK.  You may ( I did once, and not the second time ), get another error along the lines of your project needs synchronization.  Simply Quick Fix it as well.

 

Now right click your game ( root class as in “YourGame” not “YourGame-core” ), choose Run As->Maven Install.

 

image

 

 

Sadly, I am currently getting this error still, with the Flash build being broken:

 

image

 

 

As a temp work around, open pom.xml from your root project, find Flash in the modules section and remove it.

 

image

 

 

After removing it, you get that error I mentioned earlier.  Simply right click and select Quick Fix.

 

image

 

When you do, this dialog will appear.  Select all then click Finish.

 

image

 

And you are done.  The process is actually quite a bit easier than it looks, and if it weren’t for the few incompatibilities, it would be flawless.  Regardless, it is a much nicer process than before!

 

You can now run your project, for example, right click yourproject-core and choose Run As->Java Application.  Scroll down and select your main:

 

image

 

And there is your game running in Java!

 

image

 

 

 

All told, the process takes about 3-5 minutes, a very reasonable amount of time.

 

 

 

 

Using Netbeans to create a new PlayN project

 

Now we enter the ( in my opinion ) much nicer land of using Netbeans instead of the hateful Eclipse.  As you will see in a second, this process is an absolute joy!

 

Load up Netbeans.  I am using the 7.1 beta, but other versions should be similar enough.

 

Select File… New Project:

 

image

 

 

Select Maven, then Project From Archetype, click Next.

 

image

 

 

In the New Project dialog, click Add:

 

image

 

Fill in as follows, then click OK

 

image

 

Back in the New Project Dialog, select your newly added archetype and click Next.

 

image

 

Fill the next screen in like follows.  Make sure you fill in a value for newGame and click somewhere in the dialog to make sure it takes.   Then click Finish.

 

image

 

NetBeans will churn away for a minute while it creates your project and voila!

 

image

 

 

Right click your project Core and choose Run.  In the dialog that pops up, choose your main class ( there is only one ), then Select Main Class.

 

image

 

And voila:

 

image

 

 

To fully build your project, you need to right click MyProject MetaProject and choose Build:

 

image

 

And a familiar problem arises:

 

image

 

 

Same solution too.  Under your metaproject locate pom.xml

 

image

 

 

And remove this line:

 

image

 

 

Once you edit that line and save, right click->  Build your meta project again.  This time everything should work fine.

 

 

Can you tell why I prefer NetBeans over Eclipse any day?

Programming

blog comments powered by Disqus

Month List

Popular Comments