Not directly related to implementing component based weapon systems, but since you asked.
The most used guideline is (or should at least be) KISS: Keep It Simple, Stupid. Meaning you make the simplest possible solution that works for the current needs as opposed by implementing a fancy design pattern straight away. There are two main reasons why KISS is good. 1) You get things done. 2) Your code is only as complex as it needs to be.
The second important would be SOLID, which stands for
- Single Responsibility principle (SRP)
- Open Closed principle (OCP)
- Liskov Substitution principle (LSP)
- Interface Segregation (IS)
- Dependency Inversion (DI)
There’re wikipedia pages on all of these.
SRP means that a class should have one thing it’s responsible for, and one thing only. This helps in tracking down and preventing bugs because you only have one logic to implement per class. Additionally If something does go wrong during gameplay, it’s easier to figure out what logic was wrong and then zone in on the correct class. You’ll know if you’re following this rule when you ask yourself “What is the responsibility of this class?” If the answer contains even one AND, you better have a good explanation (to yourself) why.
OCP means that a class should be open for extension, but closed for modification. This basically means, if you want more functionality in a class, you extend it instead of go poke around in the code, potentially creating bugs where there were none before. Unity3D breaks this rule by having most of their central classes sealed.
Liskov substitution means that an extended class should do at least as much as the base class. Let’s say you have a math class that computes the square root of positive numbers to 5 decimal points. Following the OCP, people are free to extend the class, but LSP dictates, that because the base class computes square roots of positive numbers to 5 decimals, any class extending our base class must do the same. Our own class may compute square roots of negative numbers, or up to 10 decimal precision, but it is not allowed to compute only numbers 0-100 or only 4 decimal precision. Because any end user may be using our class as the base class, the same restrictions must apply.
Interface Segregation is the SRP for interfaces. Instead of a massive interface with lots of behaviors, it’s better to have lots of interfaces with very segregated, or specific, function. That way you can combine the needed interfaces to create just the object you need, instead of having lots of functionality the object shouldn’t logically have.
Dependency Inversion is the notion of having a class always depend on a higher abstraction. The abstraction hierarchy is
- Interfaces
- Abstract Classes
- Concrete Classes
The idea here is that the more abstract a class is, the less pressure there is for it to change. We all know that changing implementation always causes bugs. So, we’re trying to keep the bugs in the concrete classes, where the implementation is, instead of the abstract classes, which are reserved for the program logic flow.
“Program to interfaces, not implementation.” is a mantra I find myself saying at times, and it is Depencendy Inversion at its core. It allows us to change the implementation of any less abstract class during runtime, because any dependency is not to the actual implementation, but merely to what role a class has promised to implement.
Not following these guidelines can, and often do, lead to code smells (a.k.a design smells)
As for the actual design patterns by the Gang of Four you asked for. I often find need for the Strategy pattern. I’ve once used the Facade when working with a database.
The strategy pattern (which I often call Command and I shouldn’t) is when you have a class that acts as the context for another class. In the link in my previous post, my suggestion #3 is in fact the Strategy pattern. We have the class Enemy that acts as the context, as it has the abstract Mover and other attributes of that class as fields. Those abstract classes can be changed strategically, according to the situation. An example:
Let’s consider a GameObject with the following Components:
-public class Enemy : MonoBehaviour
-public class SineMover : AbstractMover (extends MB)
-public class EdgeBouncer : AbstractEdgeReacter (extends MB)
Now, we could have our Enemy class
public class Enemy : MonoBehaviour{
public AbstractMover mover;
public AbstractEdgeReacter edgeReacter;
void Update(){
mover.Move(direction);
}
//possibly called by the collision event on the Edge.
public bool ReactToEdge(Edge edge){
edgeReacter.React(edge);
}
//Possibly called on the second hit to an edge.
public void ModifyMovingBehavior(AbstractMover newMover){
Destroy(mover);
mover = gameObject.AddComponent(newMover);
}
}
The variables are uninitialized because the components are supposed to be dragged and dropped to the appropriate fields in Enemy. Then, whenever appropriate, you can change the moving behavior and the Enemy class doesn’t care. It’s programmed to the interface of AbstractMover so it doesn’t need to care about the implementation and just keeps calling Move() every Update. It’s worth noting that any outside class should only know of the existance of the Enemy class. There isn’t (AFAIK) any way to make private Components on a GameObject. (It’s possible to hide Components, which isn’t what I mean.)
The Facade is simply a class that provides a front for actions the class should do, but shouldn’t really have access to the needed resource. Like databases. Any other class shouldn’t know how the data is stored; it should just know how to access it. So instead of directly accessing the database from each class that needs to have access, you provide a singular point of entry (possibly a static class). It’s easier to maintain too. Especially if the database is changed to another storage system, say XML files, you’ll be glad you only need to rewrite one class instead of search the codebase for all accesses to the old database.
Phew, this post turned out to be a doozie. Seems like I just reiterated half of the Designing Object Oriented Software course.