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!