What are some good scripting habits?

What are some good scripting habits to form? For example, is it a good idea to expose variables in your code, such as the speed of your car or the damage done by an attack? What are some more good ideas to keep in mind while writing script? Is it a good idea make lots of files or to have most of your script in a few well-written files?

Here's a good basic one that crops up a lot for beginner programmers:

Try to achieve a good "separation of concerns"

Try to separate and organise the various responsibilities and actions so that - as much as possible - the various entities within your game don't have to concern themselves with the inner workings of any other entity.

For example - a fairly typical player/enemy interaction - when a particular kind of roaming enemy touches your player object, you might think along these lines:

  • The enemy should deduct health from the player.

  • A "damage" sound should be played,

  • The player should become invincible for 5 seconds (or...)

  • The player should be destroyed if their health reaches zero

A beginner might start scripting it like this:

// -- enemy script --
var player : Player;

function OnCollisionEnter(c : Collision) {
    if (c.transform == player) {
        player.health -= 5;
        if (player.health < 0) { Destroy(player); }
    }
}

Which is fair enough, and will probably work. However there are problems here:

  • What if there are other types of enemy, or other things which aren't even enemies - like lava, spikes, etc - which should also be able to damage the player? Should they all have their own code which checks the health of the player and destroys it?

  • What about invincibility, are they each going to have to perform their own checks as to whether the player is invincible?

  • Where should we put the code for generating the players "damage" sound effect?

  • What about a graphical health bar? if many objects can independently update this "health" variable, there's no central point which we can tap into as a "notification" that the health value has changed.

All these issues can be resolved by separating each object's concerns - the enemy should not have to be aware of any of the process involved after simply "attacking" the player.

In fact, if you generalise it right out, your enemies ought to be able to simply "attack" anything that is marked as attackable, and let the thing which was attacked deal with the consequences!

You can implement this by moving the code which deals with the consequences of the attack onto the object which was attacked - i.e. the player. For example, your enemy script would now look like this - simply calling an "TakeDamage" function on the player:

// -- enemy script --
function OnCollisionEnter(c : Collision) {
    if (c.gameObject == player) {
        player.TakeDamage(5);
    }
}    

Your player would then respond accordingly in its own function:

// -- player script --
var healthBar : HealthBar;
private var health = 10;
private var invincibleTime = 0;

function TakeDamage(amount : int) {

    if (Time.time < invincibleTime) {
        // ha! I am invincible!
    } else {
        // reduce health
        health -= amount;

        // update health bar
        healthBar.Show(health);

        if (health <= 0) {
            // gah! we died!
            audio.clip = dieSound;
            audio.Play();
            Destroy(gameObject);
        } else {
            // make invincible!
            invincibleTime = Time.time + 5;
        }
    }
}

Hopefully this illustrates how you could now have various other types of hazards - both enemies and scenery, and all any of them need to do is call the player's "ApplyDamage" function when appropriate.

The techniques described here fall roughly into the formal programming principles of "Separation of Concerns", and "Encapsulation (a.k.a. Information Hiding)", and can be applied throughout your whole game, not just between player and enemy interactions.

Something that is relevant, but not described here, are the principles of Inheritance and Interfaces, which - in the context of the example in this question - would give you a formal way of defining that an object or that a certain class of objects are "attackable", and are guaranteed to have an "ApplyDamage" function.

In real-life programming there are often ambiguities, where it appears as though perhaps two or more objects share a concern. In this case, there may be no "right" answer, but as long as you are bearing these ideas in mind, your game systems should benefit!

Here's another scripting habit I recommend:

Choose a coding style and stick to it.

There have been ongoing coding "style wars" for decades. The most important thing is that you choose a consistent readable style, and if you're working with others, make sure your style is consistent with theirs. Often, established software companies will have their own "house style" which usually closely matches industry standard style guidelines for their particular language

These are some of the basic styles I stick to:


Use proper indentation and add whitespace for readability.

Many decent code editors will auto-indent as you type, and offer a retro-active autoformat tool too. I don't care if the braces come on the same line, or on the next line, as long as the indentation is correct! Use brackets even if your 'if' or loop has only one statement inside. Read about Indent Styles on Wikipedia.

Bad:

function FeedAnimals () {
for (var animal in Animals) {
if (animal.diet == Diet.Carnivore)
{
animal.Feed(new Meat());}
else
{
animal.Feed(new Fruit()); }
}
}

Good:

function FeedAnimals () {

    for (var animal in Animals) {

        if (animal.diet == Diet.Carnivore) {
            animal.Feed(new Meat());
        } else {
            animal.Feed(new Fruit());
        }

    }
}


use comments, but use them sparingly

Write your code as if comments didn't exist, and only then add comments if extra clarity is required, eg, to give a "title" to a block of code, or to explain something particularly unusual or obscure.

For example:

// feed each animal its appropriate food
for (var animal in Animals) {
    if (animal.diet == Diet.Carnivore) {
        animal.Feed(new Meat());
    } else {
        animal.Feed(new Fruit());
    }
}

I don't think any other comments are necessary in this block. The names chosen for the variables, and functions lead to comprehensible code. Which leads me to my next point...


Name your classes, functions and variables sensibly

These are all guidelines, and there will be exceptions, but try to bear these in mind. Use PascalCase and camelCase appropriately.

Function/Method names should usually:

  • use PascalCase
  • be verbs, or verb phrases (eg "Fire()", "ApplyDamage()", "CollectPowerup()")

Class names should usually:

  • use PascalCase
  • be nouns or noun phrases (eg, "Enemy", "Bullet", "Player" )
  • not have redundant suffixes like "Control", "Script", or "Object"
  • be singular, even if you're going to instantiate lots of them!

Variable names should usually:

  • use camelCase
  • be nouns (but with exceptions, eg booleans often need verb phrases)
  • be plural if the value is an array/collection (eg, enemies, bullets, people)
  • not contain information about the type of data (unless it's an index/counter)
  • not contain abbreviations (unless generally well understood like MPH, Max)

Bad:

function bullets() {...   
// (...not a verb or verb phrase, and not PascalCase)

var spcRmn;   
// (...you don't need to abbreviate!)

for (var i=0; i<j; i++) {...      
// (...you should describe what you're counting!
// except for the most 'trivial' of loops)

var powerupArray : Powerup[];
// (...you don't need to use "array" in the name!)

class Robots extends Enemy {...
// (...don't use plural, think of each instance - it's a "Robot")

Good:

function FireBullet() {    
// (...describes what the function does)

var spaceRemaining;
// (...no need for abbreviations)

for (var sheepNum=0; sheepNum < sheep.Length; sheepNum++) {
// (...ah, we're counting sheep! and sheepNum is the current index)

var powerups : Powerup[];
// (...instead of using the word "array", just pluralise it)

class Robot extends Enemy { ...
// (...classes should almost always be a singular noun)

There are many other areas of programming to which particular styles can be applied and argued over, and this answer should be considered a very basic introduction to the idea. For more information see "Programming Style" on Wikipedia and you might also be interested to read some of the more detailed C# programming style guides out there.

Well, for starters, changing variables directly in your code will cause it to recompile after you save, so it's generally a good habit to expose all the variables that you are likely to change a lot, just because it saves time. However, if you're simply getting a reference to a gameobject at runtime, you should make that kind of variable private. Nearly much all variables changed or created at runtime should be kept private. Of course, that depends on what you're trying to do.


The pros and cons of if statements in collisions:

Here are two scripts, kindly provided by `unknown(google)` to debate the pros and cons of sending messages to every gameobject touched:

The first script uses if statements to detect what it collided with and send messages accordingly:

function OnCollisionEnter(c : Collision) { 
    if (c.gameObject == player) { 
        player.TakeDamage(5); 
    } 
} 

This second script doesn't use if statements. It merely sends damage to all collisions:

function OnCollisionEnter(c : Collision) {
    SendMessage("TakeDamage", c.gameObject, damageAmount); 
}

The first script checks if the gameobject collided with the player. The second damages all gameobjects that are collided with. There several are reasons why the first script is more desirable. First of all, the second script calls a damage message on everything it touches. This may not be a good idea, because if the enemy is walking towards you, it'll constantly be sending messages to the floor about damage. The first script checks if the collider is a player, so if the enemy is walking around, it won't be sending damage messages to the floor, which, with multiple enemies, will end up taking up extra memory.