Getting Started - Spinning 3D Cube.

In this tutorial you will code an Ajaxna Class that inherits from ajax.xna.framework.Game, and overrides a couple of methods to get a spinning cube onto your web page.

This short program will render this in your web browser
Basic3DCube.png
  1. Create a new web site that uses Ajaxna. Here is your guide for doing that: Getting Started - Create a site that uses Ajaxna

You should now have an aspx page with an instance of the AjaxnaControl on it, and the Ajaxna javascript references stored in a file in the root of your site called, "_namespace.js".
  1. Open the "_namespace.js" file and add this at the bottom:
$addNamespace("myApp");

You do this because you want to create your own namespace to work in this time.

Next, right click your Project in the Solution explorer window and click "Add New Item...". Scroll to the bottom of the "Add New Item" dialog box to get to the "My Templates" section. In here, select the "AjaxnaGame" template. This template is similar to the Xna Game template. Rename to "Game1.js" and click "Add".

This file will now open in Visual Studio. At the top of the file you will see a "TODO" list. Lets cover those now, the first one is:

//TODO: Ensure _namespace.js exists and contains our namespace and all library references.


Well, we DO have a file called "_namespace.js" and it DOES contain our namespace and all library references. So place the cursor on this line and press SHIFT & DELETE to delete that line.

Next we have this TODO item:

//TODO: Change (global replace) "myAjaxApp" Application namespace to your preference.


It's telling us we should rename the default namespace in the template to be the one we are using. To do this, Right Click press CTRL + F to bring up the "Find" dialog, click "Quick Replace" and enter "myAjaxApp" for "Find what" and "myApp" in the "Replace with". Remember "myApp" is the namespace you want to work in. Ensure "Look in" is set to "Current Document and click the "Replace All" button. You will then see a dialog saying "19 occurance(s) replaced". Click OK.

Now place your cursor on the TODO line and press SHIFT + DELETE to remove that line (we just did that TODO item).

What you are now left with is a class that inherits from "ajax.xna.framework.Game", contains a GraphicsDeviceManager property and defines several method overides for you:
  1. initialize
  2. update
  3. draw
#dispose

Each of these overrides calls the base implementation by default before it exits.

Just like in the real Xna, what we are going to do now is to make use of some of these overrides in order to render our cube on screen.

The first thing we want to do is to define the Mesh object we want to use as our cube. So scroll down until you see the section

// Public members


And just ABOVE that (it doesn't matter where it goes but we try to follow a convention) type this in:

// Private Members
myApp.Game1.prototype._mesh = ajax.geometry.Mesh.prototype;


What you are doing here is adding a private member (_mesh) to the prototype for your class and saying that it's of type "ajax.geometry.Mesh". However, due to the "lazy loading" of the framework, we cannot depend that "ajax.geometry.Mesh" is loaded to the client yet, so we should ensure that it is. Scroll to the top of the file, and under the addNamespace line you should see this:

$imports("ajax.xna.framework.GraphicsDeviceManager");


This line is ensuring that the GraphicsDeviceManager class is loaded for use before this class is defined, so we are free to use it. We should do the same thing for the mesh class. Add a new line under that $imports statement. In order to have intellisense help us, I suggest typing this:

$imports(ajax.geometry.Mesh);


Now, you MUST add the quote marks around the class name, giving you this:

$imports("ajax.geometry.Mesh");

NOTE: You could have added this class name as a second parameter to the first $imports statement instead of defing a new $imports statement as we've done here.

OK, now we want to do some initialization when our game starts up. Scroll down to the "initialize" override and place the cursor under the line:

// TODO: Add your one time initialization code here
 


We are going to load our mesh here, we are also going to define some (3) Matrices we need in order to render 3d geometry. Add you code so that the initialize method override looks like this:

myApp.Game1.prototype.initialize = function()
{/// <summary>Allows the game to perform any initialization it needs to before starting to run.</summary>

    // TODO: Add your one time initialization code here
    this._mesh = ajax.geometry.Mesh.createBox(this.getDocument(), 1, 1, 1, ajax.drawing.Colour.green());

    // Shortcuts to save some typing.
    var Matrix = ajax.math.Matrix;
    var Vector3 = ajax.math.Vector3;
    var transform = this.graphicsDevice.transform;
    var viewport = this.graphicsDevice.viewport;
    var MathHelper = ajax.geometry.Math;


    transform.set_world(Matrix.identity()); // Our object is placed at world origin with no transforms.
    transform.set_view(
        Matrix.createLookAt(new ajax.math.Vector3(0, 0, 5), Vector3.zero(), Vector3.up())
    ); // Place our "camera" 5 units "back" on the Z axis.
    transform.set_projection(
        Matrix.createPerspectiveFieldOfView(MathHelper.piOver4, viewport.width / viewport.height, 0.1, 100)
    ); // A standard Projection Matrix.

    myApp.Game1.superClass.initialize.call(this);
}


Now we want to have our cube spin. So in the "update" method override, add these lines of code after // TODO: Add your Update code here:

var Matrix = ajax.math.Matrix;
var rotSpeed = gameTime.elapsedTime * 0.001;
this.graphicsDevice.transform.get_world().multiply(Matrix.createRotationY(rotSpeed));


The entire "update" method should now look like this:

myApp.Game1.prototype.update = function(gameTime)
{
    gameTime = ajax.drawing.Timer.cast(gameTime);
    // TODO: Add your Update code here
    var Matrix = ajax.math.Matrix;
    var rotSpeed = gameTime.elapsedTime * 0.001;
    this.graphicsDevice.transform.get_world().multiply(Matrix.createRotationY(rotSpeed));

    myApp.Game1.superClass.update.call(this, gameTime);
}


Nearly there, we just have to draw the cube. How do we do that? You guessed it, in the "draw" method. Scroll there now and add this line of code so it looks like this:

// TODO: Add your drawing code here
this._mesh.draw(this.graphicsDevice);



Our game class is now complete. All we need to do now is to actually USE an instance of it. We can do this in loads of ways but we will follow the XNA program methodology and define a "Program". Finally we will link our Program to our web page.

Start by right clicking on our Project in the Solution explorer window and select "Add New Item...". Again scroll down to the "My Templates" section. Select the "AjaxnaProgram" template, rename it to simply "Program.js" and click the "Add" button.

Delete the "_namespace" TODO line, as we did that before.
Do the Global replacement indicated by the second TODO item, renaming "myAjaxApp" to "myApp". When that is done you can remove that TODO line as well.

Before we start coding, in order to get intellisense on our Game1 class, we want to add a reference to it. The best way to do this is to DRAG the "Game1.js" file from the Solution explorer window into the top of our Program.js file.


Your program listing should now look like this:

/// <reference path="_namespace.js" />
/// <reference path="Game1.js" />

/*****************************************************************************
Ajaxna program definition.
Author: Kevin 8/17/2009 3:13:22 PM.
File:   Program
*****************************************************************************/

$addApplication("myApp", "");
//-----------------------------------------------------------------------------

// program - main entry point
function main()
{        
    //TODO: Replace this code with your own program logic.
    var win = $new("ajax.windows.Console", document);
    win = ajax.windows.Console.cast(win);
    win.showCentered();        
}

// use the window load event to start us off.
$getEvent(window, window, "onload").addListener(main);


As you can see, it defines a "main" function. This "main" method is then added as a listener to the window "onload" event. This means when the web page window is loaded, this function will be called. To all intents and purposes, this "main" function is now the equilvilent to a similar one in a WinForms or XNA program.

So all we need to do now is to replace the boilerplate example code in there with our own. Replace the "TODO" section with the following code:

var game = $new("myApp.Game1", document);
game = myApp.Game1.cast(game);
game.run();


Note: We are using dynamic class loading here with the $new() method. We don't have to do that, we could have used $import to import the class first and then created an instance of it directly, without having to "cast" it.

Right, so the last thing we need to do is to include our "Program" as a startup script for our web page. Open up Default.aspx and select the "Design" tab.

Click on the Smart Tag of the Ajaxna control and click on "Startup Scripts..."

Click the "Add" button and type "Program.js" as the Path and click OK.

The Ajaxna control will now display "You have added 1 scripts".

You Are Done

Save everything and Press F5 to run your application. You may be prompted to have web.config modified for debugging. After some moments you should now see a Spinning wirefame cube, with its backfaces being culled (not drawn as they are not needed).

Note: Running scripts in the debugger is much slower that starting without debugging.

That's fantastic but the cube is a little hard to see. Lets change the background to Black. We do that by modifying the device clear() call, like this (in the "draw" method of the Game1. class):

this.graphicsDevice.clear(ajax.drawing.Colour.black());


Run that again. Aha - that looks better.
You should now see a Spinning wirefame cube, with its backfaces being culled (not drawn as they are not needed).

Extending this Sample

Rendering a solid object. To do this, lets create a new override for the "LoadContent" method,

So after the "initialize" override, type this in:

myApp.Game1.prototype.loadContent(

Now look at the intellisense. It's telling you about this method, that there are no parameters and that there is no need to call base. So lets complete the override like this:

myApp.Game1.prototype.loadContent = function()
{
    // TODO: Place our code here in a minute.
}


Next, add this code in. This code sets the fillmode on the graphics device renderstate to be Solid instead of the default Wireframe.

this.graphicsDevice.renderState.fillMode = ajax.drawing.FillModes.Solid;


If you run this now you will see that all sides of the cube are now filled with the same green colour. That's good but it would be nice to see the sides shaded based on a light source.

Note: Strictly speaking, we should have our mesh creation code in the LoadContent override as well but to keep things simple I didn't do that.

Next we'll add an Omni-directional light source as the first (and only) light in our scene and set the lighting (enable lighting) renderstate to "true". Before we do this, we should ensure that the "Light" type is loaded and ready for use (otherwise we'd need to use $new to create the instance). So add this $imports statement under the previous ones at the top of the class file:

$imports("ajax.drawing.Light");


With this done, we can modify the "LoadContent" method to look like this:

myApp.Game1.prototype.loadContent = function()
{
    this.graphicsDevice.renderState.fillMode = ajax.drawing.FillModes.Solid;

    var light = new ajax.drawing.Light(
        ajax.drawing.LightTypes.Omni,
        new ajax.math.Vector3(-5, 5, 5),
        ajax.drawing.Colour.white(),
        new ajax.drawing.Colour(20, 20, 20, 1),
        true
    );
    this.graphicsDevice.lights[0] = light;
    this.graphicsDevice.renderState.lighting = true;
}


Save and Run you application now.

Now your cube is drawn as a solid, and is lit by a white Omnidirectional light source, eminating from world coordinate -5, 5, 5. Not too shabby :-)

Just to highlight the dynamic class loading, lets say we didn't WANT to import the Light type. Maybe "lighting" might only be invoked by the user, something they may never do - meaning we just imported a class we didn't actually use, which it's never a good idea to do as that is wasting memory and bandwidth.

So lets REMOVE the $imports line:
$imports("ajax.drawing.Light");


If you ran the application now you would get an error during runtime, "ajax,drawing.LightTypes.Omni is null or not an object". That's of course because it's not loaded yet. So we need to control that ourselves. As it happens, to create a Light instance, we need to pass in what TYPE of light it is. That LightType is an Enum. Therefore we don't create instances of it, so are unable to use "$new". There is another method called "$call" for loading classes and calling static methods of that class but we are not doing that either. Enums are really a special case.

So what we'll do is, in our LoadContent method, add an "$imports" statement for the LightTypes enum there, as $import is the only option for Enums. Then we'll use $new to create the Light Class instance itself. Modify the Light creation code as follows:

// Import the enum we want to use here.
$imports("ajax.drawing.LightTypes");
// Use $new to create the Light instance. This is just for example purposes.
var light = $new("ajax.drawing.Light",
    ajax.drawing.LightTypes.Omni,
    new ajax.math.Vector3(-5, 5, 5),
    ajax.drawing.Colour.white(),
    new ajax.drawing.Colour(20, 20, 20, 1),
    true
);
this.graphicsDevice.lights[0] = light;
this.graphicsDevice.renderState.lighting = true;

Additional Information (Code Explained)

The Game1 class shows how you can write classes for Ajaxna. In this case we inherited from ajax.xna.framework.Game. The code template helped us with this. Because of this we called the superConstructor of the class, using our context (this) and passed through the "doc" variable. We knew what variables to pass because we can type "ajax.xna.framework.Game(" and intellisense will tell us what params are needed.

The line:
if ($getDesignMode()) myApp.Game1.prototype = ajax.xna.framework.Game.prototype;

is there to enable us to get intellisense on most things, as we write our class - including the base class API.

We then declared the "mesh"_ variable and set it to the prototype of the Mesh class. we could do this because we previously $import'ed that class. If we had not then we should have set that member to null, and relied on casting later on to get intellisense.

We then overrode some of the ajax.xna.framework.Game class methods: initialize, update and draw. Again we knew if we should call base, and what params to pass if we did because if we type in something like:
myApp.Game1.prototype.initialize(

intellisense tells us what we need to know.

In "initialize" we setup our 3 matrices (World, View and Projection), and loaded our mesh.

In "update" we just multiplied our World matrix by a rotation around the Y axis, based on the elapsed time since the last update.

In "draw" we just cleared the screen to our background color and rendered our mesh, using our graphics device.

Each class in ajaxna should also supply a static "cast" method, as shown:
myApp.Game1.cast = function(obj)
{///<returns type="myApp.Game1" />
    return obj;
}



"Program.js" just mimicks the class of the same name created in C# to get our game running. It also contains the statement::
$addApplication("myApp", ""); 

This tells Ajaxna to add your namespace as a top level application, thus enabling you to dynamically load your own classes.

Default.aspx just loads in Program.js during the initial rendering of the page, and then "Program.js" dynamically (by way of the $new method) loads in your "myApp.Game1" class and creates an instance of it, which it then shows and calls "run()" on, to start the game loop.

Reference (Complete Source Code)

Game1.js code listing:
/// <reference path="_namespace.js" />

/*****************************************************************************
Ajaxna library class definition. Inheriting from ajax.xna.framework.Game.
Author: Kevin 8/17/2009 2:27:23 PM.
Class:  myApp.Game1
*****************************************************************************/

$addNamespace("myApp");
$imports("ajax.xna.framework.GraphicsDeviceManager");
$imports("ajax.geometry.Mesh");


//-----------------------------------------------------------------------------

myApp.Game1 = function(doc)
{///<summary>Creates a new instance of the myApp.Game1 game class.</summary>
    myApp.Game1.superConstructor.call(this, doc);
    
    this.graphics = new ajax.xna.framework.GraphicsDeviceManager(this);    
}
//-----------------------------------------------------------------------------
$extend("myApp.Game1", "ajax.xna.framework.Game");    // Inherits from ajax.xna.Game
if ($getDesignMode()) myApp.Game1.prototype = ajax.xna.framework.Game.prototype; // Enable local intellisense.
//-----------------------------------------------------------------------------

// Private Members
myApp.Game1.prototype._mesh = ajax.geometry.Mesh.prototype;

// Public members
//-----------------------------------------------------------------------------
myApp.Game1.prototype.graphics = ajax.xna.framework.GraphicsDeviceManager.prototype;


// Public Methods & overrides 
//-----------------------------------------------------------------------------
myApp.Game1.prototype.initialize = function()
{/// <summary>Allows the game to perform any initialization it needs to before starting to run.</summary>

    // TODO: Add your one time initialization code here
    this._mesh = ajax.geometry.Mesh.createBox(this.getDocument(), 1, 1, 1, ajax.drawing.Colour.green());

    // Shortcuts to save some typing.
    var Matrix = ajax.math.Matrix;
    var Vector3 = ajax.math.Vector3;
    var transform = this.graphicsDevice.transform;
    var viewport = this.graphicsDevice.viewport;
    var MathHelper = ajax.geometry.Math;


    transform.set_world(Matrix.identity()); // Our object is placed at world origin with no transforms.
    transform.set_view(
        Matrix.createLookAt(new ajax.math.Vector3(0, 0, 5), Vector3.zero(), Vector3.up())
    ); // Place our "camera" 5 units "back" on the Z axis.
    transform.set_projection(
        Matrix.createPerspectiveFieldOfView(MathHelper.piOver4, viewport.width / viewport.height, 0.1, 100)
    ); // A standard Projection Matrix.

    

    myApp.Game1.superClass.initialize.call(this);
}
//-----------------------------------------------------------------------------
myApp.Game1.prototype.loadContent = function()
{
    this.graphicsDevice.renderState.fillMode = ajax.drawing.FillModes.Solid;

    // Import the enum we want to use here.
    $imports("ajax.drawing.LightTypes");
    // Use $new to create the Light instance. This is just for example purposes.
    var light = $new("ajax.drawing.Light",
        ajax.drawing.LightTypes.Omni,
        new ajax.math.Vector3(-5, 5, 5),
        ajax.drawing.Colour.white(),
        new ajax.drawing.Colour(20, 20, 20, 1),
        true
    );
    this.graphicsDevice.lights[0] = light;
    this.graphicsDevice.renderState.lighting = true;
}

//-----------------------------------------------------------------------------
myApp.Game1.prototype.update = function(gameTime)
{
    gameTime = ajax.drawing.Timer.cast(gameTime);
    // TODO: Add your Update code here
    var Matrix = ajax.math.Matrix;
    var rotSpeed = gameTime.elapsedTime * 0.001;
    this.graphicsDevice.transform.get_world().multiply(Matrix.createRotationY(rotSpeed));

    myApp.Game1.superClass.update.call(this, gameTime);
}
//-----------------------------------------------------------------------------

myApp.Game1.prototype.draw = function(gameTime)
{
    gameTime = ajax.drawing.Timer.cast(gameTime);

    this.graphicsDevice.clear(ajax.drawing.Colour.black());

    // TODO: Add your drawing code here
    this._mesh.draw(this.graphicsDevice);
    
    myApp.Game1.superClass.draw.call(this, gameTime);
}
//-----------------------------------------------------------------------------

myApp.Game1.prototype.dispose = function()
{
    // TODO: Cleanup your NON GAME COMPONENT objects here (eg Sounds!). Note: All items in the Components array are cleaned for you.
    myApp.Game1.superClass.dispose.call(this);
}

//-----------------------------------------------------------------------------
// Always provide a static cast method to your classes
myApp.Game1.cast = function(obj)
{///<returns type="myApp.Game1"/>
    return obj;
}


Program.js listing:
/// <reference path="_namespace.js" />
/// <reference path="Game1.js" />

/*****************************************************************************
Ajaxna program definition.
Author: Kevin 8/17/2009 3:13:22 PM.
File:   Program
*****************************************************************************/

$addApplication("myApp", "");
//-----------------------------------------------------------------------------

// program - main entry point
function main()
{
    var game = $new("myApp.Game1", document);
    game = myApp.Game1.cast(game);
    game.run();
}

// use the window load event to start us off.
$getEvent(window, window, "onload").addListener(main);


Default.aspx Template listing:
<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>

<%@ Register assembly="Ajaxna" namespace="Ajaxna" tagprefix="Ajaxna" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <Ajaxna:AjaxnaControl ID="AjaxnaControl1" runat="server">
        <Scripts>
            <Ajaxna:AjaxnaScriptReference Path="Program.js" />
        </Scripts>
    </Ajaxna:AjaxnaControl>
    <div>
    
    </div>
    </form>
</body>
</html>

Last edited Aug 17, 2009 at 4:44 PM by VR2, version 11

Comments

VR2 Jan 11, 2009 at 1:11 AM 
Hi there

So sorry for the delay in response - I was expecting to be emailed comments from this site and had not spotted yours (which is a big shame since you are the only one who leaves comments!!).

Firstly, thanks so much for the comment - you are actually USING the framework!!

So, I've added some work items I will look into but I think the big issue here is that I'm in the process of deprecating ajax.xna.Game (this is why render has become draw)!!

I would advise that everytime you download a new release, that you update your javascript references via the smart tag (so intellisense would tell you about the signature change without you needing to dig), and also update your code templates via the installer (you *may* need to manually remove the previous versions to avoid ending up with multiple versions of each).

Your items in turn:
1) nameOfColor - yes I added a work Item but now closed it - color names are now methods.
2) createBox - not sure about this ATM - so the work item stands.
3) mesh.render() has now changed to draw to bring it into line of the new API.

I will post a news item about the replacement for ajax.xna.Game :)

ghaladen Nov 25, 2008 at 4:16 PM 
Hmm, seems with the new release there are some breaking changes:

> ajax.drawing.Colour.nameOfColor throws errors now. Had to use new ajax.drawing.Colour(rValue, gValue, bValue) instead.

> this._mesh = ajax.geometry.Mesh.createBox(1, 1, 1, ajax.drawing.Colour.Green); renders a white square instead of a green cube. Further digging into the assembly shows me that the createBox signature has changed to createBox(doc, x, y, z, Color). But passing in the document or null for the doc parameter throws an error. I could only get this to work with:

ajax.geometry.Mesh.createBox(undefined, 1, 1, 1, new ajax.drawing.Colour(0, 255, 0)); // Not sure if this is the intended way.

> this._mesh.render(this.graphicsDevice) doesn't seem to exist anymore. Had to replace with this._mesh.draw(this.graphicsDevice);

Once I changed the above, everything works as it should.