Thursday, December 10, 2009

Unity: Making the Switch from JS to C# (Part 2)

3.a) Functions

Basic functions are simple enough in Javascript, but might seem weird in C# at first. You just need remember this: [scope] [return type] [FunctionName] (varType varName).

JS
function WriteSomething (str : String) {
print(str);
}

C#
public void WriteSomething (string str) { //"public" means it can be accessed from everywhere (scope), "void" is the return type (void means it doesn't return a value)
Debug.Log(str);
}


3.b) Return

In Javascript you just do it, in C# you need to think ahead and put the return type in the declaration of the function...

JS
function ReturnSomething () {
return "Something";
}

C#
public string ReturnSomething () { // In C# you have to define the type of the value that will be returned - in this case it will return a "string"
return "Something";
}


3.c) Yielding

In Javascript yielding is easy... Just use yield. In C# you have to set the return type to IEnumerator if you want to yield inside a function. (This should also make you understand why you can't yield and return inside one and the same function...)

JS
function Start () {
yield new WaitForSeconds(2.0);
yield DoIt();
print("Done!");
}

function DoIt() {
print("Doing it...");
yield new WaitForSeconds(0.5);
print("Almost done doing it...");
}

//Output:
//Doing it...
//Almost done doing it...
//Done!

C#
IEnumerator Start() { //if you want to yield inside start you have to change "void" to "IEnumerator"
yield return new WaitForSeconds(2f); //notice the f after the 2! - float
yield return StartCoroutine(DoIt()); //Note: without the "yield return" it would simply start the coroutine and continue, thus printing the "Done!" before the "Almost done..."
Debug.Log("Done!");
}

public IEnummerator DoIt() { // "public" isn't really needed in this example, but you need to use it if you want to call this function from outside this script!
Debug.Log("Doing it...");
yield return new WaitForSeconds(0.5f);
Debug.Log("Almost done doing it...");
}

//Output:
//Doing it...
//Almost done doing it...
//Done!

Unity: Making the Switch from Javascript to C#

First of all: It was a lot easier than I expected. And if you're planning to switch as well, I hope I can make it even easier for you with this series of posts.

First I bought C# in a Nutshell, a very straight-forward book that teaches you almost everything you need to know in as few sentences as possible. Cheap and good - you really don't need much else. It is, however, not Unity-specific and that is where I come in!


1) Variables and Types

JS
var size : float = 0.5;
var keyword : String = "Skylight";
var isOn : boolean = true;

private var hiddenVar = 0; //in JS variables are shown in the inspector by default and you have to mark them as private to hide them
var visibleVar = 1;

C#

float size = 0.5f; //you have to add an f to make it a float-value! (without the f it would be a double and wouldn't fit into a float variable)
string keyword = "Skylight"; //notice the lowercase s!
bool isOn = true; //notice: bool instead of boolean in JS!

int hiddenVar = 0; //in C# variables are private by default...
public int visibleVar = 1; //...and you have to mark them as "public" if you want to see them in the inspector or access them from another script

//Add the f even if you're not using a variable - if a function is expecting a float value, it won't take anything but a float!
GiveMeAFloat(0.25f);


2) Type Conversions (Casting)

You want to convert one type to another? Here's how:

JS

var f : float = 0.08; // create a float
var i : int = f; //assign it to an int
print(i); // Done! - Result: 0


C#

float f = 0.08f; // create a float
int i = (int) f; // in c# you have to use explicit casts (the target type set in braces) if any information is in danger of getting lost during the cast
Debug.Log(i); //Result: 0

The other way around works without explicit cast though (as no information is in danger of getting lost)

int i = 5;
float f = i;
Debug.Log(f); //Result: 5

Wednesday, November 25, 2009

Unity: How to relay messages sent by AnimationEvents

Here's a reusable script for preloading AnimationClips with AnimationEvents: (in Unity 2.6 you can do this directly in the new Animation Editor, but in Unity iPhone this is a nice way to do it)

//PreloadAnimClipWithEvent.js

var targetClipName : String = "idle"; //Where should the Event be added? (the name of the AnimationClip)
var throwTime : float = 1.3; //When should the function be called?
var throwFunctionName : String = "MyFunction"; //What's the name of the function?

function Start () {

var throwEvent : AnimationEvent = new AnimationEvent();
throwEvent.time = throwTime;
throwEvent.functionName = throwFunctionName;

animation[targetClipName].clip.AddEvent(throwEvent);

}


And since my scripts are never in the right place to catch messages sent by AnimationEvents, here's a script to relay a message. If this script catches a message called RelayMessage it sends message to target. So just set the AnimationEvent to send a message named "RelayMessage" and then enter real target and real functionName here.

//RelayMessage.js

var target : GameObject;
var message : String = "FunctionName";

function RelayMessage () {

target.SendMessage(message);

}

To sum it up: With the first scripts you can add an AnimationEvent to an AnimationClip and with the second script you can easily relay that message from the gameObject that holds the animation to some other gameObject that is the intended recipient.

Tuesday, November 24, 2009

Unity: Non Power of 2 Textures

Ever tried changing the texture import settings of 5+ images in Unity iPhone? - Yes, it's painful. Luckily someone wrote an Editor Script that can automate the task and put it up on the nice Unify Wiki. (There's a version for regular Unity available as well here)

I just added a new menu to the Unity iPhone version of this script that lets you automate the changing of the Non Power of 2 setting as well. And while I'm at it... What's that setting and why is it important for iPhone development?

First the basics: The iPhone can use PVRTC-compressed textures and you might want to use this compression for 2 good reasons:
  1. It helps you keep your app install size down (a 512x512px image in RGB24 has about 768KB - in PVRTC 4bit it has 128KB)
  2. It will be harder for your app to blow up the texture memory. - If your game has a complex menu with 20 full 480x320 screens, it will most likely run out of memory and crash.
Now this is where the Non Power of 2 setting comes in. PVRTC can only be applied to textures that are square and to a power of 2 (128x128, 256x256, 512x512, 1024x1024 or 2048x2048). - Your 480x320 screens can thus not be compressed unless they are rescaled to a power of two. The non power of 2 setting gives you 4 options:
  • None - Keep the original dimensions
  • Scale to nearest
  • Scale to larger
  • Scale to smaller
Even if you choose to rescale your image you can of course still display it in it's original size.

Yes image quality will suffer, but if I have to choose between a slightly blurry main menu and a crash, I'll go with blurry...

It doesn't always make sense to do this as changing a 320x20px strip from RGB24 to a 512x512px with PVRTC will most likely result in a larger filesize. I'm sure you get the general idea; same as everywhere: Thinking never hurts...

Oh, yeah, also don't forget to turn off Mip-Maps for all your gui-images! Saves a few KB as well...

Thursday, November 19, 2009

OpenFeint Unity Integration

Integrating OpenFeint in a Unity iPhone project is pretty straightforward, just sign up, get the sdk and follow these steps. A few additional notes though...

Unity:

I didn't really manage to access the OpenFeint singleton from Javascript, it kept giving me 'singleton' is not a member of 'callable() as void' . So I wrote a simple C# script just for calling OpenFeint and call that from Javascript:

using UnityEngine;
using System.Collections;
//Notice how no "using OpenFeint;" or something like that is needed!

public class OpenFeintStuff : MonoBehaviour {

public void LaunchOpenFeint () {
OpenFeint.singleton.launchDashboard();
}

}

SDK Integration (XCode):

ad 5: If it says Multiple Values next to "Other Linker Flags", select only the active config, then edit the line. This is what it should read: -ObjC,-Wl,-S,-x



ad 6: The place to check for this is under Targets > Unity iPhone > Get Info > General - then look at the bottom and add the missing frameworks with the [+] button on the bottom left.



ad 11: Your product key and secret key need to look like this: @"x238476d-etc" (this is what strings look like in Objective-C)

Thursday, November 12, 2009

Edge of Curling

Read more here, and also here, here and here if you got the time...

Wednesday, October 28, 2009

Unity: Random Hint of the Day

Ever wanted to scratch (right-click-and-drag-to-change) transform values in Unity iPhone?
Ever wondered what the real names of the color-fields of a shader are? (If you want to do something like renderer.material.GetColor("_SpecColor"))
Ever wanted to see your private variables in the inspector?

Ever noticed the little Debug Tab in Unity iPhone or the little Debug option in the Inspector Menu in Unity 2.5?

Thursday, October 22, 2009

Unity: How to blow up a mesh like a balloon

Say you wanted to blow up a mesh like a ballon - simply scaling it up isn't good enough. There's an example in the documentation that teaches you how to move vertices along normals, but it doesn't quite do what I want it to, since the mesh falls apart in the process:

video

Here's the slightly modified code of the example, as seen in the video above:
function Update () {
var meF : MeshFilter = GetComponent(MeshFilter);
if(meF) mesh = meF.mesh;
var vertices = mesh.vertices;
var normals = mesh.normals;
for (var i=0; i<vertices.length; i++)
{
vertices[i] += normals[i] * 2.0 * Time.deltaTime;
}
mesh.vertices = vertices;
}

To prevent this disintegration from happening we need to find all vertices that share identical positions and find one average normal per group of identicals. First the result, then the code:

video


private var mesh : Mesh;
private var vertices = new Vector3[0];
private var normals = new Vector3[0];
var finalNormals = new Vector3[0];

function Start () {
var meF : MeshFilter = GetComponent(MeshFilter);
mesh = meF.mesh;

vertices = mesh.vertices;
normals = mesh.normals;

//Find identical vertices
var vertexIDs = new int[vertices.length]; //this will hold an ID for each vertex, vertices at the same position will share the same ID!
var counter : int = 0;
for (var i = 0; i < vertices.length; i++) {
for (var j = 0; j < vertices.length; j++) {
if(vertexIDs[i] == 0) {
counter++;
vertexIDs[i] = counter;
}
if(i != j) {
if(vertices[i] == vertices[j] && vertices[i] != 0) {
vertexIDs[j] = vertexIDs[i];
}
print(i + ", " + j + ":" + vertices[j] + " (" + vertexIDs[j] + ")");
}
}
}

finalNormals = normals;

//Calcualte average normals
for(k = 1; k <= counter; k++) { //counter is the highest vertexID, now go through all the groups and collect normal data
var curAvgNormal : Vector3 = Vector3.zero;
for(l = 0; l < vertexIDs.length; l++) {
if(vertexIDs[l] == k) {
curAvgNormal += normals[l]; //Add up all the normals of the vertices with identical positions
}
}
curAvgNormal.Normalize(); //Normalize the result
for(m = 0; m < vertexIDs.length; m++) {
if(vertexIDs[m] == k) finalNormals[m] = curAvgNormal;
}
}
}



function Update () {
for (var i = 0; i < vertices.length; i++) {
vertices[i] += finalNormals[i] * 2.0 * Time.deltaTime;
}
mesh.vertices = vertices;
}

This code is horribly inefficient though and calculating the average normals will take forever. (Unity will hang and the little colorful wheel will spin until it's done) So don't try this on any object with more than 200 or so triangles unless you have a lot of time... (please add your optimization tips in the comments below :])

Since the mesh is known from the start we could just as well pre-calculate the normals though, so that they don't have to be calculated each time at startup.

A Simple and crude way of doing this: Run the game in the Editor >> copy the gameObject once the finalNormals have been calculated >> stop the Editor >> paste gameObject >> replace script with a version that lacks the normal-calculations and is only good for use with a script that already has the finalNormals set:
private var mesh : Mesh;
private var vertices = new Vector3[0];
private var normals = new Vector3[0];
var finalNormals = new Vector3[0];

function Start () {
var meF : MeshFilter = GetComponent(MeshFilter);
mesh = meF.mesh;

vertices = mesh.vertices;
normals = mesh.normals;
}

function Update () {
for (var i = 0; i < vertices.length; i++) {
vertices[i] += finalNormals[i] * 2.0 * Time.deltaTime;
}
mesh.vertices = vertices;
}

Okay, not bad. There's still room for improvement though: It will look even more realistic (well, that is if you would classify "blowing up a wooden table" as "realistic") if you also slowly scale up the gameObject with transform.localScale:

video

Wednesday, October 21, 2009

Unity: Animation-Events

AnimationEvents are a way to make sure that something happens at a specific point during an animation. You boss-character stomping and shaking the ground at frame 65? No problem... Frame 65 / 30 fps = 2.166 sec. Add an AnimationEvent to the AnimationClip and a function will be called every time this AnimationClip is played at the specified time!
var boss : GameObject;

//create and set up the AnimationEvent
var myEvent : AnimationEvent = new AnimationEvent();
myEvent.time = 2.166;
myEvent.functionName = "DoSomething";

//Add the event to an AnimationClip
boss.animation.clip.AddEvent(myEvent);

//Play the animation
boss.animation.Play();

Now simply add all the code you need to the function DoSomething in a script on the gameObject that contains the animation.

From what I can tell the function really has to be on a script on that gameObject and nowhere else. myEvent.functionName = "transform.parent.DoSomething"; didn't seem to want to work...

The documentation points out that what actually happens is gameObject.SendMessage(animationEvent.functionName, animationEvent); - so I guess that explains why.

Update: Have a look at this new post for how to make it call a function on a different object (spoiler: I relay the message...)

Wednesday, October 14, 2009

gamegui.net

I started gamegui.net in my sparse spare-time while working on the interface of the MMO Age of Conan. Having a good and easily searchable database full of reference material saved me countless hours of searching the web when trying to find out how other games have approached similar tasks.

I haven't gotten around to updating it much since I quit Funcom and started doing my own indie games, but it's still there and maybe the world should finally learn of its existence...

If you find it useful and want to contribute - let me know! (At this point I want to thank my friend Simon at Creative Assembly for being the only other contributor so far :])

PS: And if you can help me with one tricky SQL problem, please let me know as well! :)

Tuesday, October 13, 2009

Software that's making my life easier

Backup: Time Machine
File/Folder-Synchronization/Backup: Synk Standard (I hate to admit it, but this is a lot more comfortable than my home-made python-tool)
HD-Cleanup: DaisyDisk
Time-Tracking: Jobs (iPhone)
Finance: iFinance
Screencapturing: Screenium
Webserver: XAMPP (on localhost, for testing stuff...)

Not all of these are free but those that are not are worth the money.

If you've got any recommendations - I'm looking for a simple and cheap/free Wave-Editor for Mac to replace Audacity... (I don't want to have to start up Logic Express for simple wave-editing...)

PS: I like Schwarzkopf hair shampoo

Wednesday, October 07, 2009

SPEEDLAP RED - Behind the scenes

A screenshot from inside Unity, showing one of the tracks of our upcoming futuristic iPhone racing game "SPEEDLAP RED"

Speedlap RED - Track 125

Age of Curling: Thoughts about Online Multiplayer

Lots of people have been asking for online multiplayer for our iPhone Curling Game "Age of Curling".

Here's why it's might not happen:

It's a niche game with a fairly small number of players. Don't get me wrong, while it made less in half a year than the few top apps make in a single day, it made enough to pay the rent for a few months, which makes it a success to me!
And I'm proud to say that it seems to be doing quite well on replay value - the numbers suggest that each day there are a few hundred people playing the game. But 500 people / 24 hours / 30 mins (let's pretend each of them would spend 2 minutes in the multiplayer lobby) = 0.69 - So, on average you'd have a little over half a person in the multiplayer-lobby waiting for a game... Not very "multi"...

But here's why it still might happen eventually:

It's only the beginning of autumn, the number of regular (and overall) players will most likely rise quite a bit during Curling season.
And anyway, it's all a matter of doing it right... With a scheduling system, push notifications and a weekly timeslot for tournaments it might well be possible to get together a whole lot of players on a regular basis. And I'm sure it would get some attention for that!

And now here's why it still probably won't happen:

It's a lot of work. - I am very committed to expanding the game (and I'll continue to do so as long as people are playing it), but I'm not sure if the goal can be to make it the be-all-and-end-all Curling game and if it kills me.
Age of Curling is a mobile curling game that's fun to play with a friend while on a train. It's a fun game to play while waiting for the bus. It's not a 100% accurate curling simulation, it's not an e-sport-league game.
Each time I sit down to add something to the game I should really ask myself: "What can I do improve the strengths of the game?". You know... Do one thing and do it well... And there you go... I just told you the secret philosophy behind BLACKISH and how we're planning to survive as indie game developers.

(this post was somewhat inspired by this talk, mostly from min 46 onwards)

Sunday, October 04, 2009

Creating a bootable backup of the entire HD

Spent a bit of time upgrading an Intel MacBook Pro from OS X 10.4 (Tiger) to 10.6 (Snow Leopard).
We wanted to do a clean install and in order to not forget about anything we wanted to create a bootable backup of the entire internal HD onto a 2.5'' external HD.

We formated the external HD to HFS+ (Mac OS Extended (Journaled)) (Partitioning Schema set to GUID) and tried 2 free cloning-tools: Carbon Copy Cloner and SuperDuper! - Both produced backups that did not show up in the boot menu (the one you get to when you press ALT while booting). (It did show up as bootable on the iMac with OS X 10.5 (Leopard) though for some reason, but that wasn't good enough)

So this is what did work in the end:
We connected the MacBook Pro to the iMac with a Firewire cable and booted the MacBook into Firewire Target Mode (press T while booting until the Firewire Logo shows up on the screen), which allowed the iMac to use the MacBook like an external HD. Then we used Carbon Copy Cloner on the iMac to create a full backup from the MacBook HD to the external HD (connected to the iMac of course) - The result was bootable on the MacBook Pro!

The actual upgrade was easy then. Insert DVD, start Installation, right after it reboots open the Disk Utility from the menu on the top, format the internal HD and install...

Unity Web-Player HTML Problem

Just found out that people who didn't have the unity web-player installed already, saw this on the page of our new futuristic racing game:


Not nice... So I uninstalled the unity web-player plugin by deleting it from /Library/Internet Plug-Ins/ (browser-restart required) and went bug-hunting...

Turns out the default unity html code opens an iFrame and never closes it again. So if you change line 141 (at least that's the line number for the html that comes out of unity 2.5) and let it just close the iFrame again like this, you should be fine:

document.write('<iframe name="InstallerFrame" height="0" width="0" frameborder="0"></iframe>\n');

et voilá: