Hello, I'm currently in the process of reworking my code from the ground up since I want to develop a full version of my game. In this post I'd like to talk about the "proper" ways of mantaining and refactoring your code. (Proper is in quotes since there's actually no proper way of doing it, some ways are just more manageable than others).
First of all you can find the JAM code here: https://bit.ly/3d5z4sP (Carefull, it's a mess!)
What I did wrong
The main problem of that code is the fact that it was really heavily scene dependant. You can see this mostly in the Player script where I have this mess:
void Start() {
if(!tutorial) StartCoroutine(FadeImage(true));
r = GetComponent<Renderer>();
anim = GetComponent<PlayerAnimator>();
controller = GetComponent<Controller2D>();
shoot = GetComponent<PlayerShoot>();
torch = GetComponent<PlayerTorch>();
if(!tutorial) shake = GameObject.FindGameObjectWithTag("Shake").GetComponent<CameraShake>();
}
I had to add a boolean just for the tutorial level and find all of the special cases where the components were not required. This is clearly an example of bad architecture.
Then I tried but failed to use the Chain Of Command pattern as you can see here:

The Player class has some delegations but that code is a mess. It also doesn't follow the Single Responsibility Principle, that class does too many things.
Another mistake is that my code was too sparse
```
public class PlayerAnimator : MonoBehaviour
{
private Animator anim;
private Player p;
// Start is called before the first frame update
void Start()
{
p = GameObject.FindGameObjectWithTag("Player").GetComponent();
anim = GetComponent();
}
private void Update()
{
anim.SetBool("isResting", p.isResting);
}
}
```
This was my animator but as you can see it does almost nothing, the rest of the animation logic is scattered around various scripts (mostly Animation Behaviours)
And lastly my coupling was terrible.
```
public class MainMenuScript : MonoBehaviour
{
GameObject p;
public AudioClip boop;
public AudioClip woosh;
public Button startButton;
public TextMeshProUGUI tutorial;
public void Sound()
{
AudioManager.Instance.Play(boop, transform, .1f);
}
public void StartGame()
{
Camera.main.GetComponent<CameraFollow>().player = p.transform;
p.GetComponentInChildren<Light>().intensity = 2;
p.GetComponentInChildren<Light>().range = 20;
StartCoroutine(fadeButton(startButton, false, .2f));
AudioManager.Instance.Play(woosh, transform, .1f, 2.5f);
StartCoroutine(ShowTutorial("Use WASD to move"));
}
}
```
For example my Main Menu had to know about the player, the buttons, the text, the sounds etc. That meant that if only one of those things broke, everything else (probaby) did as well
What I did well
Luckily this not being my first jam I had some experience with this sort of things so I managed to do some good stuff.
First of all everything was (at least in the beginning) neatly organized
Separate folders for Art, Scripts, SFX etc...
This might seem a small improvement but it really makes a difference when you have to find stuff
And the second thing that really helped me was the use of interfaces. For example I defined one single IInteractable interface
public interface IInteractable
{
string Name { get; set; }
void Interact(Player p);
}
And implemented it to every object the player could've interacted with. For example:
```
using UnityEngine;
public class WeaponPickup : MonoBehaviour, IInteractable
{
public string Name { get => itemName; set => throw new System.NotImplementedException(); }
public GameObject weapon;
public string itemName = "Magical Bullet";
public void Interact(Player p)
{
p.AddWeapon(weapon);
Destroy(GetComponent());
Destroy(GetComponent());
Destroy(this.gameObject, 1f);
AudioManager.Instance.Play(pickUp, transform, .05f);
}
}
```
(Ignore the destroy mess! This happens when you don't decouple classes! :P)
And the Player a really simple interact logic
```
void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.CompareTag("Usable"))
{
obj = collision.gameObject.GetComponent();
itemTextBox.GetComponent().text = obj.Name;
itemTextBox.SetActive(true);
canInteract = true;
}
}
private void OnTriggerExit2D(Collider2D collision)
{
if (collision.gameObject.CompareTag("Usable"))
{
itemTextBox.SetActive(false);
obj = null;
canInteract = false;
}
}
void Interact()
{
if(Input.GetKeyDown(KeyCode.E) && canInteract){
obj.Interact(this);
}
}
```
(They can be simplified even further!)
What Am I doing now?
I've spent the last 4 days rewriting my old code with zero progress, this is because I wanted to turn it into an easily expandable code with the same functionalities. What I did (and am still doing) was:
- Reducing coupling between classes
- Implementing Events for most of the functionalities: This correlates with the first point, if I have a class that sends an event and another one that listens to it the two classes are essentialy independant!
- Separating the Audio and Animation logic from the base classes: I'm doing this using events as well
- Splitting the Player class into smaller Components (this applies to Unity but other programs as well, check the Single Responsibility Principle!)
- Using Scriptable Objects to add a layer of abstraction between Classes: this also reduces coupling. Let's say my Player class writes to a HealthScriptObj and my HealthUI reads from it. The two classes are now decoupled but the functionality is the same!
If you came this far, thanks for reading, hope you learned something useful! If you want to check my game you can find it here: Solar