Build for Necessity, not Architecture

Complex, Fractal, Chaos, Grid, Clock, Lock, Science
(source: Pixabay.com)

Build for Necessity, not Architecture,

... does not describe the current Codavore code base. In fact, I'm about to throw out my last 6 weeks of work, not because its broken, but because this beautifully crafted solution turns out to add a great deal of pain it was designed to avoid. And when coming to terms with this, the same solution kept popping up in my notes: "Build for necessity, not architecture."


This is about what I did wrong, and how I plan not do it again.
  • If a class doesn't talk to any other class, then it can be written any way it feels like. 
    • Its bugs will be isolated, and so can be fixed without risk in the future.
  • When multiple points in code need to talk to the same resource, or understand multiple classes, then an architecture needs to be applied to standardize their connection.
  • Always question if a new architecture could/should be solved by an existing one. Sometimes you need to shift the problem.
Those are all rules I have had in the past, and for the most part, still applied while getting myself into this code mess. As these didn't resolve my issue of over-architecting, I need to theorize an improvement and apply it.


But it might be the way I think.

So I'm also considering that this is just the way I work. I.e. start following a path I'm excited by and then expand and add all these "what if's" to my code. I'm fairly fast, so getting a bunch of things in quickly works great. But I often shape the structures in my head by working with them. After all, the devil is in the details.

So this poses the challenge, that I have lots of sub code features I want be default. For instance, I want strongly named types. I want to call LoadAs<Message>(content), not (Message)LoadAs(content). I want i18n to be easy to apply, not mixed in code, scriptable and scenes. But details like that are hard to consider early on in the architecture designs.


So what did I try to build?

I wanted dialogue to be able to trigger events, set data, and even wait on prereqs. I knew this meant I needed an observable system for variables and events that could be called by a name. I wanted this name to be human understandable, like "Level1/ToasterFight/DuckyDialog13" instead of a guid. I also wanted to be able to just load and onload all things rooted at Level1. I.e. turn off Level1 when complete.

So I did it. I build a node based observable system, that when you ask for a path that doesn't exist, it would generate all the children in the path for you. You could check if it had ever been set, or listen for changes. But I decided there was fault in this path.


Why did you get rid of the working node system?

It turned out, dynamically splitting and constructing the nodes was expensive. Not in a major way, but enough that I didn't like it. I.e. when the game is executing, it doesn't care about this organizational stuff at all. That is only a design time helper. So why waste valuable loading time in order to manage this?

Around the end, I decided that switching to a path string system, where the full path and name is the key in a dictionary worked much better. The loading time was faster, the request time was faster, the only thing that took longer, was individual node structure based calls and saving.

Well, saving happens in the background, and can be ignored. While node based calls were only used at design time to one element at a time by a human. The 30ish extra milliseconds didn't matter there.

And so I built it again. Finished. tested valid and then decided to kill it again.


Why is this killed again? What was wrong with the new system. It was faster wasn't it?

The problem wasn't performance, and many games actually use systems like this, where game play is controlled heavily by some configuration for the level. But that is overkill for this game. This game will have so much going on in variations and changes, that it will deal best from not running off a master script, but letting level designers work in Unity's IDE.

The current system requires level designers to work in Unity's IDE, but would also have to work in a JSON file, adding new events, dialogue and data changes to an ever growing script. Then they have to keep them tied together. I.e. this is the toaster that will be jumping. Id: Level1/ToasterFight/ToasterObject


So whats next?

Next is that I will be setting up smaller interactions as monobehaviours that can be tied into scenes. IF and only IF I decide that the MonoBehaviour work in the designer is becoming difficult, will I start architecting new code or features to manage it.

In this case, I will still keep the observable path system, but will get rid of the JSON scripts driving the game play. I am planning on creating little reference URLs to allow a designer to find certain areas of the level design, but I need to consider that after I start depending on jumping around in the level design.


Finally, How will I change my perspective to help prevent this sooner. 

Obviously, I need a new rule to consider to catch myself from this trap. I have to consider what was the clear problem, and how can I identify this early? And this is just a theory, which I will need to test. Though it is difficult to design a test to validate a catch all statement...

  • If a complication arises in the architecture, either by use or by design, define exactly what cases it will solve, and then define pseudo code that expresses how it will be used. Make sure all end points are covered. 
    • DO keep open to the removal of the architecture for something simpler. The simpler the better.
This is a reaction rule. I.e. it is only aware of an architecture if I realize I didn't have a solution to a particular problem that bubbled up. I ran into this multiple times during my development, and each time was taken by the idea of just adding another layer to the onion. 

The key difficulty is in knowing what you know not. Even if I define user stories, it will be difficult to validate them. So, on I go to hopefully have good results from this during my next retrospectives.

Comments

Popular posts from this blog

C# Parser and Code View

A Momentary Lapse of Passion

How to Setup a Multi-Scene Unity Project for Startup