Creating 2nd UV sets in Maya for Consistent and Reliable Lightmapping in Unity 3d

January 11th, 2012 by Elliott Mitchell

Lightmaps in the Unity Editor - Courtesy of Brass Monkey - Monkey Golf

Have you ever worked on a game that was beautifully lit in the Unity editor but ran like ants on molasses on your target device? Chances are you might benefit from using lightmaps. Ever worked on a game that was beautifully lit with lightmaps but looked different between your Mac and PC in the Unity editor? Chances are you might want to create your own 2nd UV sets in Maya.

Example of a lightmap

Example of a lightmap

If you didn’t know, lightmaps are 2D textures pre-generated by baking (rendering) lights onto the surfaces of 3D objects in a scene. These textures are additively blended with the 3D model’s original textures to simulate illumination and fine shadows without the use of realtime lights at runtime. The number of realtime lights rendering at any given time can make or break a 3D game when it comes to optimal performance. By reducing the number of realtime lights and shadows your games will play through more smoothly. Using fewer realtime lights also allows for more resources to be dedicated to other aspects of the game like higher poly counts and more textures. This holds true especially when developing for most 3D platforms including iOS, Android, Mac, PC, Web, XBox, PS3 and more.

Since the release of Unity 3 back in September 2010, many Unity developers have been taking advantage of Beast Lightmapping as a one-stop lightmapping solution within the Unity editor. At first glance Beast is a phenomenal time saving and performance enhancing tool. Rather quickly, Beast can automate several tedious tasks that would have needed to be preformed by a trained 3D technical artist in an application like Maya. Those tasks being mostly UV related are:

UVs in positive UV co-ordinate space

  • Generating 2nd UV sets for lightmapping 3D objects
  • Unwrapping 3D geometry into flattened 2D shells which don’t overlap in O to 1 UV co-ordinate quadrant
  • Packing UV shells (arranging the unwrapped 2D shells to optimally fit within a square quadrant with room for mipmap bleeding)
  • Atlasing lightmap textures (combining many individual baked textures into larger texture sheets for efficiency)
  • Indexing lightmaps (linking multiple 3D model’s 2nd UV set UV co-ordinate data with multiple baked texture atlases in a scene)
  • Additively applies the lightmaps to your existing model’s shaders to give 3D objects the illusion of being illuminated by realtime lights in a scene
  • Other UV properties may be tweaked in the Advanced FBX import settings influencing how the 2nd UVs are unwrapped and packed which all may drastically alter your final results and do not always transfer through version control

Why is this significant? Well your 3D object’s original UV set is typically used to align and apply textures like diffuse, specular, normal, alpha texture maps, etc, onto the 3D object’s surfaces. There are no real restrictions on laying out your UVs for texturing. UV’s may be stretched to tile a texture, they can overlap, be mirrored… Lightmap texturing requirements in Unity, on the other hand, are different and require:

  • A 2nd UV set
  • No overlapping UVs
  • UVs and must be contained in the 0 to 1, 0 to 1 UV co-ordinate space

Model with 2 UV sets for Lightmapping

Unwrapping and packing UVs so they don’t overlap and are optimally contained in 0 to 1 UV co-ordinate space is tedious and time consuming for a tech artist. Many developers without a tech artist purchase 3D models online to “save time and money”. Typically those models won’t have 2nd UV sets included. Beast can Unwrap lightmapping UVs for the developer without much effort in the Unity Inspector by:

Unity FBX import settings for Lightmapping UVs

Advanced Unity FBX import settings for Lightmapping UVs

  • Selecting the FBX to lightmap in the Unity Project Editor window
  • Set the FBX to Static in the Inspector
  • Check Generate Lightmap UVs in the FBXImporter Inspector settings
  • Change options in the Advanced Settings if needed

Atlasing multiple 3D model’s UVs and textures is extremely time consuming and not always practical especially when textures and models may change at a moment’s notice during the development process.  Frequent changes to atlased assets tend to create overwhelming amounts of tedious work. Again, Beast’s automation is truly a great time saver allowing flexibility in atlasing for iterative level design plus scene, object and texture changes in the Unity editor.

Sample atlases in Unity

Beast’s automation is truly great except for when your team is using both Mac and PC computers on the same project with version control that is. Sometimes lightmaps will appear to be totally fine on a Mac and look completely messed up on PC and vise versa. It’s daunting to remedy this and may require, among several tasks, re-baking the all the lightmaps for the scene.

Why are there differences between the Mac and PC when generating 2nd UV sets in Beast? The answer is Mac and PC computers have different floating point precisions used to calculate and generate 2nd UV sets for lightmapping upon importing in the Unity editor.  The differences between Mac and PC generated UVs are minuet but can lead to drastic visual problems. One might assume that with version control like Unity Asset Server or Git, the assets would be synced and exactly the same, but they are not. Metadata and version control issues are for another blog post down the road.

What can one to do to avoid issues with 2nd UV sets across Mac and PC computers in Unity? Well, here are four of my tips to avoid lightmap issues in Unity:

Inconsistent lightmaps on Mac and PC in the Unity Editor - Courtesy of Brass Monkey - Monkey Golf

  1. Create your own 2nd UV sets and let Beast atlas, index and apply your lightmaps in your Unity scene
  2. Avoid re-importing or re-generate 2nd UV assets if the project is being developed in Unity across Mac and PC computers when your not creating your own 2nd UV sets externally
  3. Use external version control like Git with Unity Pro with metadata set to be exposed in the Explorer or Finder to better sync changes to your assets and metadata
  4. Use 3rd party editor scripts like Lightmap Manager 2 to help speedup the lightmap baking process by empowering you to be able to just re-bake single objects without having to re-bake the entire scene

Getting Down To Business – The How To Section

If your 3D model already has a good 2nd UV set and you want to enable Unity to use it:

  • Select the FBX in the Unity Project Editor window
  • Simply uncheck Generate Lightmap UVs in the FBXImporter Inspector settings
  • Re-bake lightmaps

How to add or create a 2nd UV set in Maya to export to Unity if you don’t have a 2nd UV set already available?

Workflow 1 -> When you already have UV’s that are not overlapping and contained within the 0 to 1 co-ordinate space:

  1. Import and select your model in Maya (be sure not to include import path info in your namespaces)
  2. Go to the Polygon Menu Set
  3. Open the Window Menu -> UV Texture Editor to see your current UVs
  4. Go to Create UVs Menu -> UV Set Editor
  5. With your model selected click Copy in the UV Set Editor to create a 2nd UV set
  6. Rename your 2nd UV set to whatever you want
  7. Export your FBX with it’s new 2nd UV set
  8. Import the Asset back into Unity
  9. Select the FBX in the Unity Project Editor window
  10. Uncheck Generate Lightmap UVs in the FBXImporter Inspector settings.
  11. Re-bake Lightmaps

Workflow 2 -> When you have UV’s that are overlapping and/or not contained within the 0 to 1 co-ordinate space:

  1. Import and select your model in Maya (be sure not to include import path info in your namespaces)
  2. Go to the Polygon Menu Set
  3. Open the Window menu -> UV Texture Editor to see your current UVs
  4. Go to Create UVs menu -> UV Set Editor
  5. With your model selected click either Copy or New in the UV Set Editor to create a 2nd UV set depending on whether or not you want to try to start from scratch or to work from what you already have in your original UV set
  6. Rename your 2nd UV set to whatever you want
  7. Use the UV layout tools in Maya’s UV Texture Editor to layout and edit your new 2nd UV set being certain to have no overlapping UV’s contained in the 0 to 1 UV co-ordinate space (another tutorial on this step will be in a future blog post)
  8. Export your FBX with it’s new 2nd UV set
  9. Import the Asset back into Unity
  10. Select the FBX in the Unity Project Editor window
  11. Uncheck Generate Lightmap UVs in the FBXImporter Inspector settings.
  12. Re-bake Lightmaps

Workflow 3 -> Add a second UV set from models unwrapped in a 3rd party UV tool like Headus UV or Zbrush to your 3D model in Maya

  1. Import your original 3D model into the 3rd party application like Heads UV and layout your 2nd UV set being certain to have no overlapping UV’s contained in the 0 to 1 UV co-ordinate space (tutorials to come)
  2. Export your model with a new UV set for lightmapping as a new version of your model named something different from the original model.
  3. Import and select your original Model in Maya (be sure not to include import path info in your namespaces)
  4. Go to the Polygon Menu set
  5. Open the Window Menu -> UV Texture Editor to see your current UVs
  6. Go to Create UVs Menu -> UV Set Editor
  7. With your model selected click New in the UV Set Editor to create a 2nd UV set
  8. Select and rename your 2nd UV set to whatever you want in the UV Set Editor
  9. Import the new model with the new UV set being certain to have no overlapping UV’s all contained in the 0 to 1 UV co-ordinate space
  10. Make sure your two models are occupying the exact same space with all transform nodes like translation, rotation and scale values being the exactly the same
  11. Select the new model in Maya and be sure it’s UV is set selected in the UV Set Editor
  12. Shift select the old model in Maya (you may need to do this in the Outliner) and be sure it’s 2nd UV is set selected in the UV Set Editor
  13. In the Polygon Menu Set goto the Mesh Menu -> Transfer Attributes Options
  14. Reset the Transfer Attributes Options settings to default by File -> reset Settings within the Transfer Attributes Menus
  15. Set Attributes to Transfer all to -> Off except for UV Sets to -> Current
  16. Set Attribute Settings to -> Sample Space Topology with the rest of the options at default
  17. Click Transfer at the bottom of the Transfer Attributes Options
  18. Delete non-deformer history on the models or the UVs will break by going to the Edit menu -> Delete by Type -> Non-Deformer History
  19. Select the original 3D model’s 2nd UV set in the UV Set Editor window and look at the UV Texture Editor window to see it the UV’s are correct
  20. Export your FBX with it’s new 2nd UV set
  21. Import the Asset back into Unity
  22. Select the FBX in the Unity Project Editor window
  23. Uncheck Generate Lightmap UVs in the FBXImporter Inspector settings.
  24. Re-bake Lightmaps

Once you have added your own 2nd UV sets for Unity lightmapping there will be no lightmap differences between projects in Mac and PC Unity Editors! You will have ultimate control over how 2nd UV space is packed which is great for keeping down vertex counts from your 2nd UV sets, minimize mipmap bleeding and maintain consistent lightmap results!

Keep an eye out for more tutorials on UV and Lightmap troubleshooting in Unity coming in the near future on the Infrared5 blog! You can also play Brass Monkey’s Monkey Golf to see our bear examples in action.

-Elliott Mitchell

@mrt3d on Twitter

@infrared5 on Twitter

,

Aerial Combat in Video Games: A.K.A Dog Fighting

September 27th, 2011 by John Grden

A while back, we produced a Star Wars title for Lucas Film LTD. called “The Trench Run” which did very well on iPhone/iPod sales and later was converted to a web game hosted on StarWars.com.  Thanks to Unity3D’s ability to allow developers to create with one IDE while supporting multiple platforms, we were able to produce these 2 versions seamlessly!  Not only that, but this was one of our first releases that included the now famous Brass Monkey™ technology, which allows you to control the game experience of The Trench Run on StarWars.com with your iPhone/Android device as a remote control.  [Click here to see the video on youtube]

Now, the reason for this article is to make good on a promise I made while speaking at Unite2009 in San Francisco.  I’d said I would go over *how* we did the dog fighting scene in The Trench Run, and I have yet to do so.  So, without further delay…

Problem

The problems surrounding this issue are a few fold:

  1. How do you get the enemy to swarm “around you”?
  2. How do you get the enemy to attack?
  3. What factors into convincing AI?

When faced with a dog fight challenge for the first time, the first question you might have is how do you control the enemy to keep them flying around you (engage you), thus one of the most important ones is how to achieve the dog fight AI and playability.  The issue was more than just simple mechanics of how to deal with dog fighting, it also included issues with having an “endless” scene, performance issues on an iDevice and seamlessly introducing waves of enemies without interrupting game flow and performance.

Solution

In debug mode - way point locations shown as spheres

The solution I came up with, was to use what I would call “way points”.  Way points are just another term for GameObjects in 3D space.  I create around 10-15 or so, randomly place them within a spherical area around the player’s ship and anchor them to the player’s ship so that they’re always relatively placed around the player ( but don’t rotate with the player – position only ).  I use GameObjects and parent them to the a GameObject that follows the player, and this solves my issue of having vectors always positioned relative to the player.  The enemies each get a group of 5 way points and continually fly between them.  This solves the issue of keeping the enemies engaged with the player no matter where they fly and allows the enemy the opportunity to get a “lock” on the player to engage.   Since the way points move with the player’s position, this also creates interesting flight patterns and behavior for attacking craft, and now we’ve officially started solving our AI problem.

Check out the Demo.  Get the files

Check out the demo – the camera changes to the next enemy that gets a target lock on the player ship (orange exhaust).  Green light means it has a firing lock, red means it has a lock to follow the player.

Download the project files and follow along.

Setting up the Enemy Manager

The Enemy manager takes care of creating the original way points and providing an api that allows any object to request a range of way points.   Its functionality is basic and to the point in this area.  But it also takes care of creating the waves of enemies and keeping track of how many are in the scene at a time (this demo does not cover that topic, I leave that to you).

First, we’ll create random way points and scatter them around.  Within a loop,  you simply use Random.insideUnitSphere to place your objects at random locations and distances from you within a sphere.  Just multiply the radius of your sphere (fieldWidth) by the value returned by insideUnitSphere, and there you go – all done.

public void CreateField()
{
    targets = new List<GameObject>();

    GameObject newTarget;

    for( int i = 0; i < 20; i++ )
    {
        if( debug )
        {
            newTarget = GameObject.CreatePrimitive(PrimitiveType.Sphere);
            newTarget.transform.localScale *= 4;
        }
        else
        {
            newTarget = new GameObject();
        }

        newTarget.name = "EnemyTarget_" + i;
        newTarget.transform.position = Random.insideUnitSphere * fieldWidth;

        // push into our targets array
        targets.Add(newTarget);

        // parent it so that it follows the player
        newTarget.transform.parent = transform;
    }

}

Now, the method for handing out way points is pretty straight forward.  What we do here is give our enemy craft a random set of way points.  By doing this, we’re trying to avoid enemies having identical sets and order of way points given to each enemy.

public List<GameObject> GetTargets(int count)
{
    int r;
    int lastR = 0;
    List<GameObject> ary = new List<GameObject>();

    for( int i=0; i<count; i++)
    {
        r = Random.Range(0, targets.Count);

        while(r == lastR)
        {
            r = Random.Range(0, targets.Count);
        }

        lastR = r;
        ary.Add(targets[r]);
    }

    return ary;
}

NOTE:  You can change the scale of the GameObject that the way points are parented to and create different looking flight patterns.  In the demo files, I’ve scaled the GameController GameObject on the Y axis by setting it to 2 in the IDE.  Now, the flight patterns are more vertical and interesting, rather than flat/horizontal and somewhat boring.  Also, changing the fieldWidth to something larger will create longer flight paths and make it easier to shoot enemies.  A smaller fieldWidth means that they’ll be more evasive and drastic with their moves.  Coupled with raising the actualSensivity, you’ll see that it becomes more difficult to stay behind and get a shot on an enemy.

Setting up the enemy aircraft

The enemy needs to be able to fly one their own from way point to way point.  Once they’re in range of their target, they randomly select the next way point.   To make this look as natural as possible, we continually rotate the enemy until they’re within range of “facing” the next target and this usually looks like a nice arc/turn as if a person were flying the craft.  This is very simple to do thankfully.

First, after selecting your new target, update the rotationVector (Quaternion) property for use with the updates to rotate the ship:

protected Vector3 relativePos;
protected Quaternion rotationVector;
protected void UpdateRotationVector()
{
    relativePos =  currentTarget.transform.position - transform.position;
    rotationVector = Quaternion.LookRotation(relativePos);
}

Now, in the updateRotation method, we rotate the ship elegantly toward the new way point, and all you have to do is adjust “actualSensitivity” to achieve whatever aggressiveness you’re after:

protected Quaternion rotQ;
protected float yRot = 0;
protected float lastYRot;
protected void UpdateRotation()
{
    rotQ = Quaternion.Lerp(transform.rotation, rotationVector, Time.deltaTime*actualSensitivity);
    transform.rotation = rotQ;

    // this next bit is to get them to bank into their turns
    yRot = lastYRot - transform.localEulerAngles.y;
    yRot = Mathf.Clamp(yRot, 0, 3);
    transform.Rotate(0,0, yRot, Space.Self);

    lastYRot = transform.localEulerAngles.y;
}

As you’re flying, you’ll need to know when to change targets.  If you wait until the enemy hits the way point, it’ll likely never happen since the way point is tied to the player’s location.  So you need to set it up to see if it’s “close enough” to make the change, and you need to do this *after* you update the enemy’s position:

Enemy flying to his next way point

public float bufferDistance; //set by you - range the enemy has to be in relative to the way point to choose another target
private float distanceToTarget;

protected void UpdateMovement()
{
    transform.Translate(0,0, (Time.deltaTime * speed));

    UpdateRotationVector();

    UpdateRotation();

    UpdateTargeting();

    // if player isn't targeted, then check distance
    if( player && currentTarget != player.gameObject )
    {
        distanceToTarget = Vector3.Distance( transform.position, currentTarget.transform.position );
        if( distanceToTarget < bufferDistance ) GetNextTarget();
    }
}

You can also simply change the target for an enemy on a random timer – either way would look natural.

NOTE:  Keep the speed of the player and the enemy the same unless you’re providing acceleration controls to match speeds.  Also, keep in mind, that if your actualSensitivity is low (slow turns), and your speed is fast, you will have to make the bufferDistance larger since there is an excellent chance that the enemy craft will not be able to make a tight enough turn to get to a way point, and will continue to do donuts around it.  This issue is fixed if the player is flying around, and is also remedied by using a timer to switch way point targets.    You can also add code to make the AI more convincing that would suggest that way points are switched very often if the enemy is being targeted by the player (as well as increasing the actualSensitivity to simulate someone who is panicking).

Targeting the Player

Red means target aquired : Green means target lock to fire

The next thing we need to talk about is targeting, and that’s the 2nd part of the AI.  The first part is the enemy’s flight patterns, which we solved with way points.  The other end of it is targeting the player and engaging them.  We do this by checking the angle of the enemy to the player.  If that number falls within the predefined amount, then the currentTarget of the enemy is set to the player.

private Vector3 targetRotation;
public float enemyAngle;
private void UpdateTargeting()
{
    if( !playerTransform ) return;

    targetRotation = playerTransform.position - transform.position;
    enemyAngle = Vector3.Angle (transform.forward, targetRotation);

    if( enemyAngle <= targetPlayerAngle )
    {
        if( currentTarget != player.gameObject )
        {
            currentTarget = player.gameObject; // set to player
            isLocked = true;
            actualSensitivity = actualSensitivity + .5f; // increase to stay with target

            // for the demo only
            CameraFollow.Instance.SetTarget(this);
            targetLight.enabled = true;
        }

        if( enemyAngle <= firingAngle && !canFireCannons )
        {
            StartCoroutine( FireCannons() );

            // for demo only
            targetLight.color = Color.green;
        }
        else
        {
            canFireCannons = false;

            // for demo only
            targetLight.color = Color.red;
        }
    }
    else if( currentTarget == player.gameObject )
    {
        // if player is NOT in the targeting cone
        actualSensitivity = originalSensitivity; // put back to original
        isLocked = false;
        GetNextTarget();

        // for demo only
        targetLight.enabled = false;
    }
}

The nice part about this is that, if the player decides to fly in a straight path, then eventually (very soon actually) all of the baddies will be after him and shooting at him because of the rules above.  So, the nice caveat to all of this is that it encourages the player fly evasively.  If you become lazy, you get shot 0.o

You can also change the property “actualSensitivity” at game time to reflect an easy/medium/hard/jedi selection by the player.  If they choose easy, then you set the sensitivity so that the enemy reacts more slowly to the turns.   If it’s Jedi, then he’s a lot more aggressive and the “actualSensitivity” variable would be set to have them react very quickly to target changes.

Firing

And finally, the 3rd part to the AI problem is solved by having yet another angle variable called “firingAngle”.  “firingAngle” is the angle that has to be achieved in order to fire.  While the angle for changing targets is much wider (50), the ability to fire and hit something is a much tighter angle ( >= 15 ).  So we take the “enemyAngle” and check it against “firingAngle” and if it’s less, we fire the cannons on the player.  You could also adjust the “firingAngle” to be bigger for harder levels so that the player’s ship falls into a radar lock more frequently.

In the sample, I added an ellipsoid particle emitter/particle animator/particle renderer to the enemy ship object set the references to the “leftGun / rightGun” properties and unchecked “Emit” in the inspector. Then, via the Enemy class, I simply set emit to true on both when its time to fire:

protected bool canFireCannons = false;
protected IEnumerator FireCannons()
{
    canFireCannons = true;
    leftGun.emit = true;
    rightGun.emit = true;

    while( canFireCannons )
    {
        yield return new WaitForSeconds(.25f);
    }

    leftGun.emit = false;
    rightGun.emit = false;
}

Conclusion

So, we’ve answered all 3 questions:

  1. How do you get the enemy to swarm “around you”?
  2. How do you get the enemy to attack?
  3. What factors into convincing AI?

With the way point system, you keep the enemy engaged around you and the game play will be very even and feel like a good simulation of a dog fight.  The way points keep the enemy from getting unfair angles and provide plenty of opportunity for the player to get around on the enemy and take their own shots, as well as provide flying paths that look like someone is piloting the ship.  And adjusting values like “actualSensitivity”, “fieldWidth” and “firingAngle” can give you a great variety of game play from easy to hard.  When you start to put it all together and see it in action, you’ll see plenty of room for adjustments for difficulty as well as getting the reaction and look you want out of your enemy’s AI.

Have a Bandit Day!

Android Graphics and Animation Part III – Handling the Accelerometer

September 19th, 2011 by Keith Peters

It’s been a while, but we finally come to part 3 of this series.

In part 1 we learned the basics of setting up an Android project in Eclipse and drawing to the canvas.

In part 2 we covered animation and threading.

In this episode, we will look at handling the accelerometer, allowing you to control the animation by tilting your Android device.

Set up

We’ll continue on with the same project we created last time, which had a circle moving from left to right across the screen. Since we’ll be allowing the user to tilt the phone in any direction, we want to disable the auto-rotation feature that will change the orientation when the phone is tilted. This is done in the Android manifest xml file. We want to add the following line to the activity tag:

android:screenOrientation="portrait"

This will force the device to remain in portrait orientation no matter how it is tilted. The whole manifest will now look like this:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.infrared5"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="8" />

    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".Animation"
                  android:label="@string/app_name"
                  android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>
</manifest>

Listening for Accelerometer Events

The next thing we want to do is listen for accelerometer events, which will occur whenver the device is tilted. In reality, it’s not like you will only get events when the device is moving. You will get a steady stream of accelerometer events even if the device is sitting by itself on a table. But different types of applications may need to get these events more or less often. For example, a game may need to be very responsive and be very up to date on the events coming in, whereas in another type of application, it may not be so vital and you can opt to get the events less often to save on processing. Android allows you to listen for these events using the following values to control how often you will get them:

SENSOR_DELAY_FASTEST
SENSOR_DELAY_GAME
SENSOR_DELAY_NORMAL
SENSOR_DELAY_UI

These are all static values on the SensorManager class. We’ll see how they are used in a moment.

First we’ll go into our AnimView class which starts out like this right now:

public class AnimView extends SurfaceView implements SurfaceHolder.Callback {

In addition to implementing the SurfaceHolder.Callback interface, we now want to implement the android.hardware.SensorEventListener interface. So import that interface and add it to the class signature:

public class AnimView extends SurfaceView implements SurfaceHolder.Callback, SensorEventListener {

This, of course, will cause the compiler to complain that you have not implemented the required methods of that interface. Triggering a quick fix will add the following method stubs:

@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
    // TODO Auto-generated method stub

}

@Override
public void onSensorChanged(SensorEvent event) {
    // TODO Auto-generated method stub

}

Before we do anything with these, we need to write the code that listens for the sensor events. We’ll do that right in the constructor.

public AnimView(Context context) {
	super(context);
	holder = getHolder();
	holder.addCallback(this);

	SensorManager manager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
	if(manager.getSensorList(Sensor.TYPE_ACCELEROMETER).size() != 0) {
		Sensor accelerometer = manager.getSensorList(Sensor.TYPE_ACCELEROMETER).get(0);
		manager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_GAME);
	}
}

First we get an instance of the SensorManager class. This is done by calling the getSystemService on the context that is passed into the view’s constructor. We tell it which service we want: the Context.SENSOR_SERVICE.

Once we have the SensorManager, we need to check if there is indeed an accelerometer on this device. If so, we get the first available one. I’m not sure if any existing device has more than one accelerometer, but the api leaves that possibility open.

Finally, we register this class as a listener to the accelerometer, passing in the sensor delay you need, as covered earlier. At this point, we should start receiving events in the two methods we just added.

All we are interested in now is the onSensorChanged method, which will give us the data on how the device is currently oriented. As you can see, this method gets passed an instance of SensorEvent. This object has a property called values, which is a simple array of floats. For accelerometer events, the tilt on the x, y and z axes are represented by the first 3 elements of the array. i.e.:

event.values[0] is the degree of tilt on the x axis
event.values[1] is the degree of tilt on the y axis
event.values[2] is the degree of tilt on the z axis

All of these values will be in the range of -9.81 to +9.81. For a thorough explanation of why these values are used, see the SensorEvent class documentation here:

http://developer.android.com/reference/android/hardware/SensorEvent.html

For our purposes, we only care about the x and y axes. We’ll pass those values to the AnimThread class with a method called setTilt. This method doesn’t exist yet, but we’ll create it soon.

@Override
public void onSensorChanged(SensorEvent event) {
    if(animThread != null) {
        animThread.setTilt(event.values[0], event.values[1]);
    }
}

Note that the method will probably be called before and/or after the thread is created, so we’ll test to make sure it exists before calling any methods on it. For this simple demo, we won’t do any high or low pass filtering as described in the SensorEvent documentation, but if you wanted to do so, this would be a good place to do it.

Handling the Tilt Values

Now we need to create the setTilt method in AnimThread, but first let’s create a few properties there. We’ll need something to hold the raw tilt values, and some properties to hold the current position and velocity of the ball. The top of that class should now look like this:

public class AnimThread extends Thread {

    private SurfaceHolder holder;
    private boolean running = true;
    private float x = 100;
    private float y = 100;
    private float vx = 0;
    private float vy = 0;
    private float tiltX = 0;
    private float tiltY = 0;

Now we can create the setTilt method, which will be pretty simple:

public void setTilt(float tiltX, float tiltY) {
    this.tiltX = tiltX;
    this.tiltY = tiltY;
}

Now all we have to do is make use of those values. The strategy is to add the tilt values (or at least a part of them) to the velocity values, then add the velocity to the position values, and finally draw the circle at the final x, y point. Here’s the run method in full:

@Override
public void run() {
    while(running ) {
        Canvas canvas = null;

        try {
            canvas = holder.lockCanvas();
             synchronized (holder) {
                // update
                vx -= tiltX * 0.1;
                vy += tiltY * 0.1;
                x += vx;
                y += vy;

                // draw
                canvas.drawColor(Color.BLACK);
                Paint paint = new Paint();
                paint.setColor(Color.WHITE);
                canvas.drawCircle(x, y, 50, paint);
            }
        }
        finally {
            if (canvas != null) {
                holder.unlockCanvasAndPost(canvas);
            }
        }
    }
}

We’re now at a point where you can test the app. Hold the phone flat when you start it. The ball should “roll” in the direction you tilt the phone. Of course, it will roll out of sight if you’re not careful, so we need to fix that next.

Handling Screen Edges – Bouncing

The following stuff I’ve covered in a number of books and tutorials on my personal site, www.bit-101.com, so I’m not going to belabor the point. We’re just going to see if the ball has gone past any edge of the screen and if so, place it on the edge and reverse the velocity on that axis. Here’s the final AnimThread class in full:

package com.infrared5;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.SurfaceHolder;

public class AnimThread extends Thread {

    private SurfaceHolder holder;
    private boolean running = true;
    private float x = 100;
    private float y = 100;
    private float vx = 0;
    private float vy = 0;
    private float tiltX = 0;
    private float tiltY = 0;

    public AnimThread(SurfaceHolder holder) {
        this.holder = holder;
    }

    @Override
    public void run() {
        while(running ) {
            Canvas canvas = null;

            try {
                canvas = holder.lockCanvas();
                 synchronized (holder) {
                    // update
                    vx -= tiltX * 0.1;
                    vy += tiltY * 0.1;
                    x += vx;
                    y += vy;
                    if(x + 50 > canvas.getWidth()) {
                        x = canvas.getWidth() - 50;
                        vx *= -0.9;
                    }
                    else if(x - 50  canvas.getHeight()) {
                        y = canvas.getHeight() - 50;
                        vy *= -0.9;
                    }
                    else if(y - 50 < 0) {
                        y = 50;
                        vy *= -0.9;
                    }

                    // draw
                    canvas.drawColor(Color.BLACK);
                    Paint paint = new Paint();
                    paint.setColor(Color.WHITE);
                    canvas.drawCircle(x, y, 50, paint);
                }
            }
            finally {
                if (canvas != null) {
                    holder.unlockCanvasAndPost(canvas);
                }
            }
        }
    }

    public void setRunning(boolean b) {
        running = b;
    }

    public void setTilt(float tiltX, float tiltY) {
        this.tiltX = tiltX;
        this.tiltY = tiltY;
    }

}

Now as you tilt the device, the ball will bounce off the edges of the screen, with just a little less force than it hit.

Summary

We now have a working, accelerometer-based interactive animation. This isn’t meant to be a perfect example in terms of best practices. I’d probalby pull out a lot of hard coded values into variables, extract some of the code into separate methods, etc. I’d also get a time delta between updates so that devices running at different speeds would run the animation at the same rate. Perhaps I’ll be able to cover some of that in a future tutorial. But this gives you a good idea of the structure and what happens where and how to get started.

Red5 Vector Support

August 3rd, 2011 by Paul Gregoire

It all started for me last year, I noticed Jean-Philippe Auclair blogged about reading AS3 Vector’s in Java and provided byte-level examples. Since the blogged examples were written in Java, I saw this as an invitation to implement them in Red5. I knew of Vector’s in Java but had no idea they had been implemented in actionscript and with what looked to me like generics. In AS3 the generic notation denotes the base type for each element in the Vector, which is also how it works in Java. The main differences between the implementations are that a Vector in AS3 is basically a faster version of Array and in Java it is a synchronized List-type collection. Similarly each element must be either “null” or an instance of the base type. Java is a lot more flexible with the instances in this case, because the element merely needs to implement or extend the base type. In AS3 handling is different, a Vector of DisplayObject type will not accept a Sprite instance.

Before I get down to the byte-level stuff, I want to note that this information may not be exactly correct in terms of what or how these items are constructed by the flash player; they are however the result of reverse engineering the data. Anyone with more information or a correction is welcome to send it my way.

The following examples show how the Vector is constructed in AS3 and then its representation in bytes. The byte arrays are encoded as big endian and are not compressed.

Vector with numbers and a string

A Vector containing two Number objects and a simple repeating string:

var v:Vector.<Object> = new Vector.<Object>();
v[0] = 1.0;
v[1] = 3.0;
v[2] = "aaa";

Trace:

[1.0, 3.0, aaa]

Raw bytes:

10 07 00 01 04 01 04 03 06 07 61 61 61
0 = Vector
1..3 = Length == 3
4 = Type == Number
5 = Value == 1.0
6 = Type == Number
7 = Value == 3.0
8 = Type == String
9 = String length == 3
10..12 = "aaa"

Vector inside a Vector with other objects

A Vector containing the previous examples Vector in addition to another Vector of int type:

var v:Vector.<Object> = new Vector.<Object>();
v[0] = 1.0;
v[1] = 3.0;
v[2] = "aaa";
var v2:Vector.<int> = new Vector.<int>();
v2.push(1);
v2.push(2);
v[3] = v2;

Trace:

[1.0, 3.0, aaa, [1, 2]]

Raw bytes:

10 09 00 01 04 01 04 03 06 07 61 61 61 0d 05 00 00 00 00 01 00 00 00 02
0 = Vector
1..3 = Length == 4
4 = Type == Number
5 = Value == 1.0
6 = Type == Number
7 = Value == 3.0
8 = Type == String
9 = String length == 3
10..12 = "aaa"
13 = Type == Vector
14 = Length == 2
15 = Reference
16..19 = Value == 1
20..23 = Value == 2

Mixed Vector with class instance member

A Vector containing a simple String, a “null”, and the instance of a custom class:

var v:Vector.<Object> = new Vector.<Object>();
v[0] = "foo";
v[1] = null;
v[2] = new org.red5.test.Foo3();

Trace:

[foo,null,[Foo3=3]]

Raw bytes:

10 07 00 01 06 07 66 6f 6f 01 0a 13 25 6f 72 67 2e 72 65 64 35 2e 74 65 73 74 2e 46 6f 6f 33 00 04 03

The “Foo3″ classes

Actionscript

package org.red5.test {
	[RemoteClass(alias="org.red5.test.Foo3")]
	public class Foo3 {
		public var foo:int;

		public function Foo3() {
			this.foo = 3;
		}

		public function toString() : String {
			return "[Foo3=" + foo + "]";
		}
	}
}

Java

public class Foo3 implements IExternalizable {
	private int foo;

	public void setFoo3(int foo) {
		this.foo = foo;
	}

	public int getFoo() {
		return foo;
	}

	public void readExternal(IDataInput input) {
		this.foo = input.readInt();
	}

	public void writeExternal(IDataOutput output) {
		output.writeInt(foo);
	}
}

The original post does not contain details for writing back to Flash Player so I simply reversed the process once I had read working in the server. To test round-trip (deserialization/serialization), I used this method in my Red5 application:

	public Vector<?> echo(Vector<Object> vector) {
		log.debug("echo - vector arg: {}", vector);
		return vector;
	}

The method accepts a Vector containing any type of object and returns it back to the client as a response. On the client you’ll need a pair of methods to both hit the server and to get the response.

	public function echo() : void {
		var v:Vector.<int> = new Vector.<int>();
		v.push(42);
		nc.call("echo", new Responder(echoRecv), v);
	}

	public function echoRecv(o:Object) : void {
		var vectorIn;
		if (o is Vector.<int>) {
			vectorIn = Vector.<intt>(o);
		} else if (o is Vector.<uint>) {
			vectorIn = Vector.<uint>(o);
		} else if (o is Vector.<Number>) {
			vectorIn = Vector.<Number>(o);
		} else if (o is Vector.<Object>) {
			vectorIn = Vector.<Object>(o);
		}
		trace("Got vector:" + vectorIn);
	}

Lastly, to use Vector’s with Red5 you will need the latest trunk with a revision of 4264 or newer.

For your reference, the AS3 doc page for Vector may be found here: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/Vector.html
Note: Vector’s are available to Flash Player starting with the version 10 beta.

Clustering with Red5

July 28th, 2011 by Paul Gregoire

Once your service out grows its original space, you will most likely look at adding clustering capabilities. While clustering an application may seem daunting, it can be done in a fairly simple manner at first. Whilst at Infrared5, I have created several clustered applications using the methods which will be supplied below and now may be added to your skillset.

For this example you will need the following software (IDE’s not listed):

Herein we will go over clustering a simple chat application; both the client and server code in this example consist of only two to three source files respectively. Code blocks may not contain all of the code or comments to keep the sections short and focused.

Server-side (Java)

The first piece of code we need is the Red5 application adapter, it is used by the server to control the lifecycle of your application. In many cases, the base classes may be configured via Spring so that you don’t even need to write your own for simple applications; as an example, the SOSample demo that comes with the server doesn’t have any code for an application adapter. For this application we will override two lifecycle methods to handle the starting and stopping of the application. When the application starts, we need to start our cluster client class and join the cluster. On stop we disconnect from the cluster and perform any clean up necessary.

private ChatRegistry chatRegistry;
@Override
public boolean appStart(IScope scope) {
try {
chatRegistry = new ChatRegistry(scope);
chatRegistry.start();
} catch (Exception e) {
log.error("Exception during appStart", e);
}
return super.appStart(scope);
}
@Override
public void appStop(IScope scope) {
super.appStop(scope);
if (chatRegistry != null) {
chatRegistry.stop();
}
}

We must also add any methods which will be called by the client, which in this case would be one that accepts a sender name and some text.

public void sendChat(String sender, String text) {
ChatMessage chat = new ChatMessage();
chat.setSender(sender);
chat.setText(text);
chatRegistry.send(chat);
}

Pretty easy looking isn’t it? Being a standard Java method, it could accept these values from anywhere that has access to the method such as a servlet. Next up, on the server-side is the code to send and receive messages on the cluster. This “ChatRegistry” extends the ReceiverAdapter provided by JGroups and is implemented in the most minimal way I could think of.

public class ChatRegistry extends ReceiverAdapter {
    private JChannel channel;
    private IScope scope;
    public ChatRegistry(IScope scope) {
        this.scope = scope;
    }
// creates a channel to the cluster/group and starts the client.
public void start() throws Exception {
channel = new JChannel("./udp.xml");
channel.setReceiver(this);
channel.connect("ChatRegistry");
channel.getState(null, 10000L);
}
// closes the channel, disconnecting from the group.
public void stop() {
channel.close();
}
// sends the messages to the cluster.
public void send(ChatMessage chat) {
// serialize and send the messsage to the cluster
Message msg = new Message(null, null, Util.objectToByteBuffer(chat));
channel.send(msg);
}
// receives messages from the cluster.
public void receive(Message msg) {
// deserialize the messsage from the cluster
ChatMessage chat = (ChatMessage) Util.objectFromByteBuffer(msg.getBuffer());
// call the "receive" method on every connected client and pass
// the chat message to each one.
ServiceUtils.invokeOnAllConnections(scope, "receive", new Object[]{chat});
}
}

You may have noticed we’ll be using UDP for this example, I have found that this is the easiest way to setup JGroups and keep all the nodes communicating on a local network.

Last but not least for the server application we have our model class to hold a creation time, sender name, and the chat text itself.

public class ChatMessage implements Serializable {
private long timestamp = System.currentTimeMillis();
private String sender;
private String text;
public long getTimestamp() {
return timestamp;
}
public String getSender() {
  return sender;
}
public String getText() {
  return text;
}
}

The Actionscript twin for this model will be shown in the next section.

Client-side (ActionScript)

I used FlashBuilder to whip-up a quick chat client, so don’t bash my code too hard. For the example clients I created a Flex Project with MXML and one AS3 class file. In the MXML script block besides the connection handling, we need a method for sending and another to receive.

public function send() : void {
nc.call("sendChat", null, senderName.text, chatText.text);
}
public function receive(chat : ChatMessage) : void {
messages.appendText(chat.sender + ':' + chat.text + '\n');
}

Be aware that the NetConnection.call in the send function must match the signature on the server’s application adapter.
The final class needed on the client is our model, which in Actionscript is a lot less “wordy” than in Java.

package com.infrared5.blog.chatr {
[RemoteClass(alias="com.infrared5.blog.chatr.ChatMessage")]
public class ChatMessage {
public var timestamp:uint;
public var sender:String;
public var text:String;
public function ChatMessage() {}
}
}

I have not included the configuration files and some other things in this post to prevent confusion, but rest assured they are in the source archives at the end.

Running

The quickest way to get up-and-running is to grab the server zip and deploy it to two servers or vmware virtual machines. Everything you need to run the client and server is in the zip.  After Red5 has started up, look at your console and you should see a line indicating that your application is in the cluster.

GMS: address=MAGNUS-40196, cluster=ChatRegistry, physical address=2002:48c1:e10c:0:f83f:d256:6e23:8bdb:54305

Please note that If you are using OSX you may need to add “-Djava.net.preferIPv4Stack=true” to your start-up script due to a bug in JGroups IPv6 handling.

Open browsers on each instance and navigate to http://localhost:5080/chatr/chatr.html

Once loaded, enter a name and click connect. After you have two servers running and clients connected to them, your output should look similar to this when chatting.

So now you know how easy this can be, go forth and create some cool stuff!!

Downloads

Full application with Red5 server (ready-to-run): http://bit.ly/pIoVBi

Client source: http://dl.dropbox.com/u/7316897/red5/chatrclient-src.zip

Server source: http://dl.dropbox.com/u/7316897/red5/chatr-src.zip

, , , , ,

Meet Infrared5’s New CEO – Rebecca Allen

July 26th, 2011 by Chris Allen

As many of you who follow Infrared5’s progress know, we’ve recently created a new company around a product we developed. This company, Brass Monkey, allows a smartphone to become a game controller. We are doing some amazing work, including winning awards, landing clients like LucasFilm, and closing in on a seed round of fundraising. Needless to say, Brass Monkey has been taking more and more of my time and focus over the last year. So the partners and I have decided it is best for me to step down as Infrared5’s CEO.

I’m excited to announce that my business partner and wife, Rebecca Allen, will be taking on the role of CEO for Infrared5. Infrared5 wouldn’t be any where near where it is today without all of her expertise in art direction, customer relations, finance, company operations, project management and overall leadership. She’s actually been acting in this capacity for a year now, and the partners came to the conclusion that it made sense that she officially come on board as the CEO of the company. While Rebecca has been at the helm, the company has continued to flourish and grow even in a down economy.

We are beginning to get many more repeat customers, and we have been and are continuing to look to hire new employees. Rebecca has a knack for keeping customers happy and understanding complex problems. I can’t think of a better person to lead our service based technology business.

With all this said, I will continue to serve as Infrared5 President and be involved on strategic level decisions for the company. We are all excited this year with the growing success of all of our businesses. Please join me in welcoming Rebecca to her new position. Read the rest of this entry »

Working with Native JSON in Flash Player 11

July 21st, 2011 by Todd Anderson

With the recent public release of the next candidates for the Flash Player and AIR runtimes, I wanted to check out one of the features that I had been waiting to see as part of the player for some time. No, not Molehill. Native JSON support! Boring, right? Maybe i’ll address Stage3D (née Molehill) in another article, but initially I am actually more curious to check out the native JSON support.

If you are unfamiliar with JSON, it is lightweight, interchangeable format that is human-readable and has a familiar structure that allows for ease in parsing and generating data. You can read more about it at http://www.json.org.

History… sorta

For applications targeting the Flash Platform, there was a time when you would typically go grab the as3corelib library and use the JSON class to encode or decode JSON data – actually, the time is still now, or until these runtimes are out of candidacy. Though the JSON class from the as3corelib library works well and was a welcome addition to the community, it sits outside of the player globals and is implemented in ActionScript. That’s not to say it is necessarily slow, but – because of its implementation – it is inherently slower than having native support of JSON within the player.

Future… kinda

With the advent of native JSON support in the Flash Player 11 and AIR 3 runtimes, we now are afforded serialization and deserialization at a faster speed as well as an API that matches that of the JavaScript implementation.

JSON API

You can head over to the online docs and check out the JSON API for a deeper explanation, but the JSON class basically has two static methods:

parse( text:String, reviver:Function = null ):Object
stringify( value:Object, replacer:* = null, space:* = null ):String

If coming from working with the JSON class from as3corelib, these will most likely look different than the methods you are used to. That’s alright, they do relatively the same thing and, depending on the level of control you want, it may just be a matter of the methods being named differently. I do want to go over what has been made available in these methods in moving toward the use of the native JSON class, however, as they can be quite interesting and possibly useful depending on the design of your application.

JSON.stringify()

The JSON.stringify() method does essentially what you may be familiar with as JSON.encode() from the as3corelib – put object in, get string out. However, the global JSON class of FP11 now has a couple more parameters that can be used to your advantage when turning that object into a JSON string.

The replacer parameter can be supplied as a list of String or Number that correspond to the key-value pairs which you want to serialize to a JSON string. It can also be supplied as a Function delegate that will be invoked upon each key-value pair that allows you to handle the data directly in either modifying the value being generated or – in the case of returning undefined – removing it from serialization.

Here is a quick example of how you can assign a replacer delegate and remove a value from being serialized to the generated JSON string:

var objStr:String = JSON.stringify( {
                           name:"Todd Anderson",
                           company:"Infrared5",
                           phone:15558576309
                       }, deflate );

trace( "JSON obj>String: " + objStr ); // >
// {"name":"Todd Anderson","company":"Infrared5"}
...
protected function deflate( key:String, value:* ):*
{
    return ( key == "phone" ) ? undefined : value;
}

That gives you a little peek at the potential that a replacer has when generating JSON, but you can imagine that it could be valuable for, say, hashing passwords and such on the client side before being sent over the wire.

The other parameter in JSON.stringify() – space – can be a String or Number and is used in prettifying the generated JSON string by providing whitespace and readability. Using the previous example, if we provided the value of 4 as the argument for spacer, the trace output would no longer be on one line and look more like this:

{
    "name": "Todd Anderson",
    "company": "Infrared5"
}

Pretty!

JSON.parse()

Let’s take a look at the parse method. Again, if coming from as3corelib JSON, it will probably just be a change of calling:

[FP 11 global: JSON]

var obj:Object = JSON.parse( value );

instead of:

[as3corelib: JSON]

var obj:Object = JSON.decode( value );

However, there is a reviver parameter on the parse() method which can be of use if you wish to handle working with the key-value pairs during the parse operation of the value object. The signature of the Function supplied as the reviver should have two parameters defined – key and value – and should return the modified value in order to be stored in the object.

Here is a quick example of using the parse() method with a reviver that turns each string value to upper case:

var objStr:String = JSON.stringify({name:"Todd Anderson", company:"Infrared5"});
var obj:Object = JSON.parse( objStr, inflate );
...
protected function inflate( key:String, value:* ):*
{
    return ( (typeof value) == "string" ) ? String(value).toUpperCase() : value;
}

One thing to note here is that if you return undefined from the reviver delegate, you can actually remove the property and its value from the generated object, which is useful but also is something to look out for if things seem to be going wrong. Another thing to note here is that the last key-value pair passed through this reviver delegate is the actual object itself – keep that in mind when using a reviver and make sure to check that you are not modifying the whole object you are parsing.

Going a step further

Now this new API is well and good and we could be on our way sending and receiving JSON data and punch our time card and grab a beer, but there may come a time where you actually want to have more of a workflow in streamlining the generation and parsing of this data using real model objects in your project rather than generic Objects.

Now it is true that as long as the property values held on a custom object are those that are supported in the JSON format – string, number, boolean, array and null – that you could supply the custom object to the JSON.stringify() method and it will properly generate the JSON object for you. Going the other way – parsing – however, is not going to inherently create or write to that custom object that you may have previously serialized; it will just generate a generic object. Of course there is nothing stopping you from traversing the properties on that object and setting them on a new custom instance, but this is an opportunity in which we might want to think about how we can go about creating a design in which we can easily map back to custom objects. Things I like to think about and bore people with. Don’t fall asleep on me.

To get an idea of what i am talking about, say we have a Person class which serves as a model for a person in the context of our application. It has your basic properties on it which are standard value types – like name, company, etc from previous examples. We can certainly generate a JSON string and pass that around like so:

var person:Person = new Person();
person.name = "Todd Anderson";
person.company = "Infrared5";
var objStr:String = JSON.stringify( person );
trace( JSON.parse( objStr ) ); // [object Object]

Now, in our perfect world, that would trace [object Person], wherein the JSON parser was knowledgable to map the object to a Person. There is that reviver parameter on JSON.parse() but that won’t help us much in mapping back to a custom instance as it only handles the key-value pairs and not the object as a whole.

One solution is to serialize the classname down with the generated JSON object, so that when it is received on the client, we can inflate a custom instance based on the associated class. So, if you can imagine, the models you will use in your application all extend a base model that handles setting this classname that is accessible and writable to a JSON object, eg:

package
{
    import flash.utils.describeType;
    import flash.utils.getQualifiedClassName;

    public class Model
    {
        protected var _clazz:String;
        private static var _description:Object = {};

        public function Model()
        {
            _clazz = getQualifiedClassName( this );
        }

        public function get model():String
        {
            return _clazz;
        }

        [Transient]
        public function get description():Vector.
        {
            if( Model._description[_clazz] == null )
            {
                var desc:Vector.<String> = Model._description[_clazz] = new Vector.<String>();
                var xml:XML = describeType( this );
                var variables:XMLList = xml..variable;
                var variable:XML;
                // Run through public variable declarations.
                for each( variable in variables )
                {
                    desc[description.length] = variable.@name;
                }
                // Run through accessors.
                var accessors:XMLList = xml..accessor;
                for each( variable in accessors )
                {
                    // Only include if readwrite.
                    if( variable.@access == "readwrite" )
                        desc[description.length] = variable.@name;
                }

            }
            return Model._description[_clazz];
        }
    }
}

In this base model class, there is a model accessor which is the qualified classname of the subclass. It also exposes a description accessor which allows easy access to the list of read-write properties on the subclass. It is marked as transient to denote that it should not be considered enumerable when generating the associated JSON object.

Now that we have our base model, any subclasses will be serialized with a model property representing the qualified clas name which can be used to inflate the custom object upon parse. Modifying the Person class used in a previous example, the class now looks like this:

package
{
    public class Person extends Model
    {
        public var name:String;
        public var company:String;

        public function Person()
        {
            super();
        }
    }
}

We just have to remember to call super() in the constructor or ensure that the _clazz property value is that of the subclass. From here, all that is left is using that model property to instantiate a new instance and fill the available property values from the parsed JSON object received. Here is a quick working example:

var person:Person = new Person();
person.name = "Todd Anderson";
person.company = "Infrared5";

// To JSON.
var objStr:String = JSON.stringify( person );

// Create new Person from JSON.
var parsedPerson:Person = parseToModel( objStr ) as Person;
...

public function parseToModel( value:String ):Model
{
    var obj:Object = JSON.parse( value );
    var model:Model = new ( getDefinitionByName( obj.model ) as Class ) as Model;
    var description:Vector.<String> = model.description;

    // Fill new Model
    var property:String;
    for( property in obj )
    {
        if( description.indexOf( property ) != -1 )
        {
            model[property] = obj[property];
        }
    }
    return model;
}

In the parseToModel() method in this example, we see how the read-only model property is passed in the flash.utils.getDefinitionByName() function to inflate a new instance of the associated model – Person – that was previously serialized to a JSON object.

One thing to remember is that a reference to the class in which you want to use to inflate a new instance of must be compiled into the SWF, otherwise you will get Runtime Errors. There are a couple ways in which you can ensure that the reference is included, and I will leave that up to you to explore.

View and download the example

That is just one solution in implementing the ability to map JSON objects back to custom instances, but I am sure there are many more and I would like to hear your thoughts on them in the comments.

Conclusion

Native JSON support to the runtimes is a much welcomed addition and I am very happy to see it finally become a reality, and it is good to see – in my opinion – an API that matches that of the one on the JavaScript side. Hopefully I covered some of what can be accomplished with it and how it can benefit you in any current or future projects. Go grab the beta players, and the playerglobal SWC and have some fun!

, , ,

Android Graphics and Animation: Part II – Animation

July 19th, 2011 by Keith Peters

Android Animation

In the first part of this series, we covered basic graphics in Android – starting a new Android project, creating a custom view and displaying it, and using that view to draw custom graphics in its onDraw method. To recap, the drawing occured only when the onDraw method was called by the system when it determined that the app needed to refresh its display. This generally occurs once when the app starts and only occasionally, if ever, thereafter. For animation, we need to be able to trigger redraws on a regular basis. This is quite a bit more complex than drawing a static image, but not horribly so, so let’s dive in.

SurfaceView

In the last example, we extended View for our custom view class. That was fine for the purpose, but will not be adequate for drawing multiple times like we need to do for animation. For View, the onDraw method is triggered by the system when it knows that the Canvas is safe to draw on. It can set things up for us before calling onDraw, and then clean up when it is done executing. Since we need to do drawing on our own schedule, we need a view class that will let us do this set up and clean up ourselves. That class is called SurfaceView. So that’s what our new view will extend.

To get started, create a new Android project the same way we did last time. Call the project and activity “Animation”. Again, in the main activity class replace the call to setContentView with a custom view. We’ll call this AnimView:

package com.infrared5;

import android.app.Activity;
import android.os.Bundle;

public class Animation extends Activity {
    private AnimView view;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        view = new AnimView(this);
        setContentView(view);
    }
}

Of course, AnimView does not exist, so we’ll get an error. Trigger a quick fix, which will offer to create the AnimView class. Before accepting the defaults in the New Java Class dialog, change the superclass field to “android.view.SurfaceView”. When the class is created, trigger another quick fix to create the constructor. You should end up with the following:

package com.infrared5;

import android.content.Context;
import android.view.SurfaceView;

public class AnimView extends SurfaceView {

    public AnimView(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }
}

At this point, the app should compile and run, but naturally will show just a black screen.

SurfaceHolder

Again, since we will be a lot more in control of when things get drawn, we need to go a little more low level in what we are doing. When using onDraw, you are automatically passed a Canvas object that you are safe to draw on. When using SurfaceView though, you need to get your canvas from something called a SurfaceHolder. This can be retrieved by simply calling getHolder() from the SurfaceView instance. That’s easy enough, but there’s another bit of complexity coming up.

You can’t draw to a surface of a SurfaceView/SurfaceHolder until the surface is created. And you should not draw to it after it has been destroyed. So we need to know when these things happen. To do that, we can let the holder know that we want to handle related events. To do this, we call surfaceHolderInstance.addCallback(viewInstance). But one more catch – the object you pass to this method must implement an interface defined as SurfaceHolder.Callback. So our class definition starts out as:

public class AnimView extends SurfaceView implements SurfaceHolder.Callback {

When you do that, you’ll be informed that you are not implenting the required methods of that interface. Use a quick fix to add them. With all that done, you should have the following:

package com.infrared5;

import android.content.Context;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class AnimView extends SurfaceView implements SurfaceHolder.Callback {

    private SurfaceHolder holder;

    public AnimView(Context context) {
        super(context);
        holder = getHolder();
        holder.addCallback(this);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        // TODO Auto-generated method stub
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // TODO Auto-generated method stub
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // TODO Auto-generated method stub
        }
    }
}

Threading

Now we can start animating. In animation, you need generally have some kind of model of what you are animating, with some kind of rules on how that model changes. you need to update the model, and then render that model to the display, then update the model again, render again, and so on.

If you are used to animating in Flash you’re familiar with doing this via enterFrame, or perhaps with timers. Timers are also used in JavaScript animation. In Android though, we generally use threads.

Threads can be a bit scary as they are a bit more complex than a simple timer. If you’re not familiar with threads, the concept is just that you are starting another process that runs independently from the main process. This is useful for operations that might take a long time or will not return immediately. The new thread does its own thing in its own time frame, and the main process of your app continues to do what it needs to do, remaining responsive, etc.

The scary part of threads is that they run separately, but are able to access the same variables and objects in a non-synchronized way. Thus, one thread might be performing some procedure on a given object, and right in the middle of tht procedure, the other thread might step in and change the state of that object or even delete it. So you have to take some extra steps to guard against these types of situations.

Our view will use a separate thread to perform its animation. We will create and start the thread running in the surfaceCreated method, and we will stop the thread in the surfaceDestroyed method. There are a number of different ways to use threads. The way we’ll do it is to subclass the Thread class and put the custom functionality in that class.

Here’s the start of our custom thread class:

package com.infrared5;

import android.view.SurfaceHolder;

public class AnimThread extends Thread {

    private SurfaceHolder holder;
    private boolean running = true;

    public AnimThread(SurfaceHolder holder) {
        this.holder = holder;
    }

    @Override
    public void run() {
        // this is where the animation will occur
    }

    public void setRunning(boolean b) {
        running = b;
    }
}

In order to draw to a canvas, we’ll need the surface holder to get the canvas from, so we’ll pass that in in the constructor and save it. We’ll also need a variable that will indicate whether or not the thread is currently running and a way to set that.

When we create an instance of this thread class and call start() on it, its run method will be executed in a separate process. We’ll actually use a while loop to do our animation. This may seem odd if you’re coming from the Flash or JavaScript world, where in infinite while loop would just lock things up. But because this is in a separate thread, it works out fine.

The pseudocode for what we will do is like this:

public void run() {
    while(running) {
        // update the model
        // get a canvas
        // draw to the canvas
    }
}

This will just run forever. Well, until we set running to false anyway. As you might have guessed, we’ll create and start the thread in the surfaceCreated method and we’ll set running to false in the surfaceDestroyed method. There’s a few more details to it, but we’ll get there eventually.

Locking and Unlocking

To get a canvas from a surface holder, we actually call holder.lockCanvas(). This prevents anything from happening to the canvas while we are using it. When we are done with our drawing, we call holder.unlockCanvasAndPost(canvas), passing in the canvas instance we just drew to. This frees it up and displays what was just drawn.

Here is the final code with some actual animation going on:

package com.infrared5;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.SurfaceHolder;

public class AnimThread extends Thread {

    private SurfaceHolder holder;
    private boolean running = true;
    int i = 0;

    public AnimThread(SurfaceHolder holder) {
        this.holder = holder;
    }

    @Override
    public void run() {
        while(running ) {
            Canvas canvas = null;

            try {
                canvas = holder.lockCanvas();
                 synchronized (holder) {
                    // draw
                    canvas.drawColor(Color.BLACK);
                    Paint paint = new Paint();
                    paint.setColor(Color.WHITE);
                    canvas.drawCircle(i++, 100, 50, paint);
                }
            }
            finally {
                    if (canvas != null) {
                            holder.unlockCanvasAndPost(canvas);
                        }
            }
        }
    }

    public void setRunning(boolean b) {
        running = b;
    }
}

Here you can see we declare the canvas variable, then we enter a try block where we get the canvas and do the drawing. This allows us to unlock the canvas in a finally block, so that even if an exception is thrown while drawing, we won’t leave the canvas in locked state.

Note that the drawing is done in a synchronized block. This puts a lock on the holder so that nothing else can change it from another thread while we are using it. In this block we set the background to black and draw a white circle. The x value will be incremented on each loop, moving the circle across the screen.

Starting and stopping the thread

All we have to do now is create, start, and stop this thread. We’ve already said that we’ll do that in surfaceCreated and surfaceDestroyed methods. So let’s see what this looks like. First the created:

@Override
public void surfaceCreated(SurfaceHolder holder) {
    animThread = new AnimThread(holder);
    animThread.setRunning(true);
    animThread.start();
}

Simple enough. We create the thread, passing in the suface holder, set running to true, and start it. This will wind up executing the run method, which will run that for loop in a separate process.

The destroyed method is a bit more complex:

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    boolean retry = true;
    animThread.setRunning(false);
    while (retry) {
        try {
            animThread.join();
            retry = false;
        } catch (InterruptedException e) {
        }
    }
}

First of all, we set running to false. This will allow the while loop in the run method to exit. But since that’s happening in another thread, we don’t know exactly when that’s going to happen. So we want to make sure that it’s really fully complete before we leave here. We do that with the join method of the thread. That will cause execution to stop and wait for that thread to end. However, this will sometimes result in an InterruptedException. So we throw that whole thing in a try/catch statement and keep retrying it until the join finally successfully returns. Here’s the final AnimView class:

package com.infrared5;

import android.content.Context;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class AnimView extends SurfaceView implements SurfaceHolder.Callback {

    private SurfaceHolder holder;
    private AnimThread animThread;

    public AnimView(Context context) {
        super(context);
        holder = getHolder();
        holder.addCallback(this);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        animThread = new AnimThread(holder);
        animThread.setRunning(true);
        animThread.start();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        boolean retry = true;
        animThread.setRunning(false);
        while (retry) {
            try {
                animThread.join();
                retry = false;
            } catch (InterruptedException e) {
            }
        }
    }
}

, , , ,

Android Graphics and Animation: Part I

June 27th, 2011 by Keith Peters

This is the start of a series of tutorials on graphics and animation on the Android platform. There is plenty of information out there on how to create general form-based, controls-and-layout type of Android apps, but very little on how to do more creative drawing and animation. So this series will cover the following topics:

1. Android graphics.
2. Android animation.
3. Android input: Accelerometer.
4. Android input: Touch.

Today we’ll get started with simple graphics. There are actually a few different ways to draw graphics on the screen in Android.

First, there is the Canvas class, which gives you a nice basic drawing API to create lines, circles, rectangles, fills, strokes, deal with bitmaps, etc.

Then there’s OpenGL. If you’re going to do 3D or just need more raw graphics and animation power, you’ll probably want to use OpenGL, or more likely use one of the various 3rd party libraries that make it a bit easier to use.

And then there is something called RenderScript, which was introduced in Android 3.0 (which, at the time of this writing is supported by only a few devices).

For this set of articles, we’ll be using the simplest and most widely available option, Canvas.

Setting up an Android coding environment

Of course, before we can even get started, you’ll need to have an Android coding environment set up and a connected Android device. You could use the Android simulator, and you should use it for testing different device resolutions and capabilities, but in general day-to-day dev, you’ll probably find it faster and easier to deploy and test on a device.

I’m not going to go into very deep detail about this, only because Google has covered it in far more depth than I ever could. So I’ll just point you to the right place.

http://developer.android.com/index.html

Here you’ll find links to the SDK, Developer’s Guide, References, Resources, Videos, and a blog. Within all that, you’ll find step by step instructions on how to set up your environment. But in a nutshell, you’ll need to:
1. Install Eclipse (or another editor of your choice, but this tutorial will assume you’re using Eclipse).
2. Download the Android SDK. This is just a folder of files and tools used in developing Android apps.
3. Install the ADT Plugin, Android Development Tools. This is an Eclipse plugin that will set up your Eclipse install to build Android apps.
4. Add Android platforms and components.

These steps are all covered in more detail here:

http://developer.android.com/sdk/installing.html

Connecting a device or creating a virtual device (emulator)

Next you’ll need to have someplace to run your code. Again, I recommend using a real device as much as you can. Setting up a device for development is covered here:

http://developer.android.com/guide/developing/device.html

If you don’t have a physical device, or are at a point where you need to test some different resolutions or features your device doesn’t have, this link will walk you through setting up a virtual device on the emulator:

http://developer.android.com/guide/developing/devices/index.html

Code!

OK, let’s make an app. Assuming you have everything installed and working, and are using Eclipse as your editor, fire it up and create a new workspace. Then create a new project by using the menu File -> New -> Android Project. This will bring up the “New Android Project” dialog.

Give your project a name, “Drawing” and choose a Build Target. We’ll stick with Android 2.2 since that’s a pretty common one.

Going further down, we need an application name, package name, and activity name. The application name is what will show up on the device. For now, think of the activity name as the name of the main class of the app. The package is the class package as in any Java project. Finally we need to specify the minimum SDK version. We’ll choose 8 here to coincide with the Android 2.2 SDK. The whole numbering system for SDKs and SDK versions is a bit confusing. I’ll leave it to you to figure it out more on your own. But the above settings will work for now.

Now we can click “Finish” and our project will be created. Your package explorer view should look like this:

There you can see your src folder with your package and main activity class. Opening that class you should see the following code:

package com.infrared5;
import android.app.Activity;
import android.os.Bundle;

public class Drawing extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

Since this is the only activity in this application, this class will be instantiated when the app is run, and the onCreate method will be called. This is where you want to hook into to initialize pretty much everything.

Right now, onCreate calls super.onCreate and then setContentView, passing in something called R.layout.main. If you’re curious what this is, look in the folder res/layout and you’ll see main.xml, which will look like this:

If you’ve done any work with Flex, Silverlight, or any other XML-based layout systems (or even HTML) this will look pretty familiar. It creates a layout with a single child that is a TextView. The TextView’s text property is set to “@string/hello”. If you want to see what that is, look in res/values/strings.xml.

The Android compiler will compile all the stuff in the res folder into classes or embeddable assets as appropriate. So res/layout/main.xml becomes the R.layout.main, which is an instance of a class that extends View and can be set as the activity’s content view using setContentView.

Now, if you’ve set everything up correctly, you should be able to run or debug this project on your device and/or in the emulator and see something like the following:

If this is not working, stop here and get it debugged. This is the bare bones of project setup, and everything else depends on this.

Custom Views

OK, that’s all very interesting, but we’re not going to use much in the res folder or any of that xml-based layout stuff here. We’re going down to the metal and writing our own drawing code.

But since we aren’t relying on the compiler to create a view from xml for us, we’ll have to make our own view class. We can even use some of the ADT plugin’s shortcuts to let it do a bunch of the work for us. Change Drawing.java to look like this:

package com.infrared5;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;

public class Drawing extends Activity {

    private View drawingView;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        drawingView = new DrawingView(this);
        setContentView(drawingView);
    }
}

Here we’ve created a new class member, drawingView of type View, instantiated it as a new DrawingView, passing in this to the constructor, and set it as the content view.

Of course, Eclipse will complain because DrawingView does not exist yet. But if we click on that error it will offer to create the class for you. It will even know that it should extend View. So go ahead and let it create that class. It should look like this:

package com.infrared5;

import android.view.View;

public class DrawingView extends View {

}

Now it’s going to complain again because it wants a constructor that takes an argument. Again, use the quick fix feature to let it create the constructor it wants. Now you’ll have this:

package com.infrared5;

import android.content.Context;
import android.view.View;

public class DrawingView extends View {

    public DrawingView(Context context) {
    super(context);
        // TODO Auto-generated constructor stub
    }
}

We’re at a stable point here, so go ahead and run that on your device/emulator and make sure it launches. You shouldn’t see anything but a black screen with the app name at the top, but it should compile and deploy.

OK, now we have a view we can draw in. The View class is designed so that all the drawing will be done in an onDraw method. This method will be automatically called whenever the view needs to be redrawn. To create this method, type “onDraw”, trigger auto-complete, and accept the first choice. You should wind up with an onDraw method like you see below (or you could go all old school and actually type it by hand).

package com.infrared5;

import android.content.Context;
import android.graphics.Canvas;
import android.view.View;

public class DrawingView extends View {

    public DrawingView(Context context) {
        super(context);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }
}

You see this method has given us a Canvas to draw on. If you trigger autocomplete on canvas, you’ll see that it has all kinds of drawing methods. Let’s add a call to drawLine right after the super.onDraw call:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawLine(0, 0, 100, 100, paint);
}

As you probably guessed, the first arguments for this are the x, y values of an initial and an ending 2d point. The last argument, paint, is a Paint object that tells the system what to make this line look like (color, width, etc.). Since we haven’t defined paint yet, it will give you an error. Trigger a quick fix to create a field named paint. Then in the constructor we’ll instantiate it and give it some properties. Here’s the result:

package com.infrared5;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.view.View;

public class DrawingView extends View {

    private Paint paint;

    public DrawingView(Context context) {
        super(context);
        paint = new Paint();
        paint.setColor(Color.WHITE);
        paint.setStyle(Style.STROKE);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawLine(0, 0, 100, 100, paint);
    }
}

Don’t forget the imports for Color and Style. You can run or debug this now and you should have an utterly fascinating diagonal white line on your device’s screen. When you’ve calmed down and gotten yourself under control, we’ll move on.

Setting the Background Color

Perhaps you want to change the background color. You can do that will canvas.drawColor, passing in the color you want to use. Note that this will actually clear the screen, so you’ll want to do this before drawing anything important.

Specifying Colors

In addition to the constants on the color class, like Color.BLACK, Color.WHITE, Color.RED, etc. you can specify exact colors with Color.rgb(red, green, blue) where each parameter is an int from 0 to 255, or Color.argb(alpha, red, green, blue) if you need transparency.

So to set the background to a kind of light purple, do something like this:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawColor(Color.rgb(200, 155, 255));
    canvas.drawLine(0, 0, 100, 100, paint);
}

Other Shapes

As mentioned, there are lots of other options on Canvas for drawing various things. A few examples:

canvas.drawCircle(cx, cy, radius, paint)

Here cx and cy are the center point to draw a circle with the given radius.

canvas.drawRect(rect, paint)

Here rect is a Rect object or a RectF object (which would use floats rather than ints for its measurements).

canvas.drawPoint(x, y, paint)

Pretty obvious.

Then there are drawOval, drawArc, drawRoundRect, and many others.

Putting it all together

Just to implement a few things all at once, we’ll do something like this for a final demo:

package com.infrared5;

import java.util.Random;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Paint.Style;
import android.view.View;

public class DrawingView extends View {

    private Paint paint;

    public DrawingView(Context context) {
        super(context);
        paint = new Paint();
        paint.setColor(Color.WHITE);
        paint.setStyle(Style.FILL);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.rgb(200, 155, 255));
        int cols = 5;
        int margin = 20;
        int w = (canvas.getWidth() - (cols + 1) * margin) / cols;
        int rows = canvas.getHeight() / (w + margin);
        Random rand = new Random();
        for(int i = 0; i < cols; i++) {
            int x = margin + i * (w + margin);
            for(int j = 0; j < rows; j++) {
                int y = margin + j * (w + margin);
                Rect r = new Rect(x, y, x + w, y + w);
                paint.setColor(Color.rgb(rand.nextInt(255), rand.nextInt(255), rand.nextInt(255)));
                canvas.drawRect(r, paint);
            }
        }
    }
}

Here we’ve set the style to FILL instead of STROKE, then use some fancy math and a couple of for loops to draw a grid of squares, each with a random color. Nothing amazing, but assuming you have some previous experience with any kind of drawing API from any other language, this should set you up to create all kinds of custom graphics in your Android app or game.

Summary

Here we’ve seen how to set up a new Android project and create a custom view that we can draw into. The view class is instantiated and added as the activity’s main content view, and the onDraw method is called when it’s ready to display.

Of course, since generally speaking this is only called the one time near the start of the app, it’s just a static drawing. In the next installment of this series, we’ll dive into animation and making things move in Android.

, , ,

The Evolution of Infrared5

June 21st, 2011 by Keith Peters

I joined Infrared5 back in November 2007. Those were very different times. We were a hard core Flash shop, focusing on Red5 Server based applications and Papervision3D. The iPhone had been out for less than six months and only Apple could write apps for it. The iPod Touch was just a few weeks old. Nobody had heard of Android. Tablets were just a failed venture by Microsoft that most people had forgotten about a few years before. Nobody was particularly excited about HTML (5 or otherwise) or JavaScript. If there was any perceived threat to Flash at the time, it might have been Silverlight, but nobody was particularly worried about that.

Now, the landscape is very different. I’m not going to say Flash is dead. I don’t think it is. I don’t even think that it is dying, per se. What is happening though, is that there are so many other cool and interesting things out there now, that Flash has lost its place in the spotlight for many developers. Also, I think that Flash initially had a very low learning curve and very little barrier to entry. A lot of Flash developers grew up as Flash did, learned real programming, object orientation, design patterns, best practices, etc., and were then able to branch out to other languages and platforms.

I have to say, that Infrared5 has not only rolled with the changes very well, but has completely embraced the change. I think virtually all of our front end developers are now seasoned iOS developers. Several have embraced Android development as well. We have Windows Phone 7 knowledge (mostly me), and our 3D platform has moved from Papervision to Unity. We’re doing HTML5 stuff as well as Flash and Flex sites, iPad apps, kiosk applications. Many of our projects even span multiple platforms – a Flex 4 app with an HTML5 public facing site, Flash or Unity 3D games with a companion iPhone app via Brass Monkey.

The company’s tag line is “Yeah, we can build that.” I’d say we’ve lived up to that.

In closing, I ran across this quote the other day that I really loved. It comes from a free on line book, “Learn Python the Hard Way”, by Zed A. Shaw, which you can find here: http://learnpythonthehardway.org/ . In the last section called “Advice From An Old Programmer”, he says:

“What I discovered after this journey of learning is that the languages did not matter, it’s what you do with them. Actually, I always knew that, but I’d get distracted by the languages and forget it periodically. Now I never forget it, and neither should you.

Which programming language you learn and use does not matter. Do not get sucked into the religion surrounding programming languages as that will only blind you to their true purpose of being your tool for doing interesting things.

Programming as an intellectual activity is the only art form that allows you to create interactive art. You can create projects that other people can play with, and you can talk to them indirectly. No other art form is quite this interactive. Movies flow to the audience in one direction. Paintings do not move. Code goes both ways.”

The full quote is here: http://learnpythonthehardway.org/book/advice.html

, , , , , , ,

« Previous Entries