Component System - Same Face, New Engine

It’s been a long time since the Kickstarter finished, and everyone keeps asking, “What the heck have you been doing?” (and also, “I want alpha”)

Well, this is what we’ve been doing:


New Engine

We’ve (basically) created an entirely new engine for Starmancer. We’re still using Unity for rendering, animations, input, and some other things, but the rest of back-end is completely new.

We essentially recreated the game from scratch.

Why Would You Do This

When we first started the Kickstarter, we had no idea how popular (or un-popular) it would be. All of our time frames were based around Starmancer being as unpopular as possible. Our thought was that if the Kickstarter failed, or if it just barely limped in to the $40,000 goal, we would release Starmancer without any major internal changes. It wouldn’t have been the best game ever, but it still would have been everything that we promised.

But that didn’t happen. We raised way more money than we thought we would, and Chucklefish reached out to be our publisher.

We wanted Starmancer to have the best future possible. We didn’t want to create one of those games that has severe bugs that never get fixed, even years after release. Starmancer needed to be flexible, moddable, and have incredible depth. This was the best and only time to do it.

But Why So Quiet

We’ve been fairly quiet, for 2 (maybe 3) reasons:

Time

Posting on social media takes up a lot of time.

You have to figure out what you want to post, then you have to figure out how you want to visually portray it, then you have to actually create the screenshot or gif. Then you have to come up with some caption and text to go with it.

Finally, you have to linger on social media and respond to comments (throughout the week too, not just the day that you make the post).

A conservative guess is that each social media post takes up at least 3 hours. Maybe more.

So let’s say that you work for 8 hours in a day. Well you still have to eat, use the bathroom, take breaks, whatever. So you effectively have 5-6 hours of useful working time a day. That’s also assuming that you can stay completely focused and motivated all day.

That means each week has 25-30 hours. If we made 1 post per week, we’d be using 10% of our time, minimum, on posting.

Instead, we decided to put all of our effort into making Starmancer.

Post-Kickstarter

As a side note, handling all of the Kickstarter things took way longer than we anticipated. There was so much to handle in terms of coordinating rewards and communicating with everyone who we needed information from (and who had questions for us). It takes around 5 minutes to read and respond to an email, question, criticism, whatever.

If we spent 2 minutes handling every single backer, it would take 9200 minutes. That’s 153 hours. 6 straight weeks of doing nothing but Kickstarter.

Introversion

This may sound like an excuse, and maybe it is. I didn’t become interested with programming and computers because I like to go outside and interact with a bunch of strangers. I’m introverted. I would guess that most programmers are introverted.

It’s stressful to put yourself out there all the time and open to criticism. When someone criticizes Starmancer, it’s like they’re criticizing me.

I completely understand that we have an obligation to keep all of you informed, and that’s completely fair.

But we’re humans, too

Engine Failure

The biggest reason that we’ve been quiet is because we weren’t sure how successful our engine overhaul would be. We didn’t want to post that we were overhauling the engine and then a month later post that it failed and we wasted all that time.

We didn’t even know how long it would take to overhaul the engine.

We would rather come to you with no news than bad news.


The Old Engine

In the old engine, a colonist was a colonist. A wall was a wall. If we wanted to place a colonist the same way you placed a wall…good luck. This pertained to everything.

Here’s an example:

Resource Producers

We have Resource Producers, like the Ore Refinery. The Ore Refinery requires raw ore and it produces metal. Raw ore and metal are resources. There was a system in place for requesting resources, producing resources, queuing offload of those resources, not starting production if no space was available, not starting production if required resources weren’t present, etc.

Biotanks

Everything worked great, until we wanted to produce colonists in a Biotank.

A Biotanks requires biomass (a resource) and produces colonists.

Colonists are not resources.

80% of the behavior of an Ore Refinery is matched in the Biotank. The remaining 20% is special.

In programming, treating things as special as usually bad. Writing code that does something special for just one single purpose is almost always bad. It’s a failure to abstract.

Old Implementation

For reference, here’s how we actually implemented Biotanks (in the old system).

 
public void OnProductionStarted()
{
}
 
 
 
public void OnProductionStopped()
{
}
 
 
 
public BuildableObject_GameObject GetParentObject()
{
    return this;
}
 
 
 
public void OnResourceProducerTurnedOff()
{
}
 
 
 
public void OnResourceProducerTurnedOn()
{
}
 
 
 
public void OnManualProductionIsReadyToStart()
{
}
 
 
 
public void OnProductionFinishedCycle()
{
    SpawnColonist();
}
 
 

I didn’t remove the code from those methods. This is the actual code that makes the Biotank work. It’s called ColonistSpawner.cs. It derives from the interface ResourceProducerObject.

In C#, an object can derive from multiple interfaces. Every object is required to implement every method in the interface. So anything could be a Resource Producer, as long is it derived from the correct interface. All of the appropriate methods could be called in a somewhat standardized way.

This wasn’t a terrible system, but you can see that 5 of those 7 methods are doing absolutely nothing.

If one object wants to know about a new event, you have to add a method to the interface, and all objects that derive from it are required to implement the new method (even if it does nothing). For example, The Biotank doesn’t care about OnManualProductionIsReadyToStart, but some other object does (probably the farm).


But I Want Aliens

Now pretend that we want to make a Biotank that spawns aliens instead of colonists. We would have to make an entirely new script, call it “AlienSpawner” and it would once again have 5 empty methods. Alternatively, we could inherit from ColonistSpawner, but then AlienSpawner could never inherit from anything else.

Then imagine that we want to make a fundamental change to how ResourceProducers work, we would have to ensure nothing broke in 2 locations: ColonistSpawner and AlienSpawner.

Every single time that we wanted new behavior, we would exponentially increase the development time. This was not a viable long-term approach.


Colonist Skills

Skills are another great example. Let’s say that there’s a “charisma” skill. As charisma goes up, colonists might have an easier time making friends. Maybe they even talk about different things in a conversation. Maybe a charismatic colonist has an easier time smuggling contraband as well.

So what would that code look like?

Everywhere that you wanted charisma to have an effect, you’d have to do something like:

 

(colonist.GetSkill("Charisma") as CharismaSkill).DoSomething();
 
 

How many methods would the CharismaSkill end up with? In how many places would you have to adjust the code if you wanted to change what Charisma does? How many areas of code would know that the CharismaSkill exists? Changing Charisma or any code that used Charisma would cause something to break.

This is coupling. Coupling is bad.

What if you want both Charisma and some other object to have a similar effect? Maybe you want colonists to have both a smuggling and charisma skill. Do you copy and paste the charisma smuggling code into the new smuggling skill? If you do that, you’ll have to make all smuggling adjustments in at least 2 places. And eventually, you’ll forget about the other place, and a bug is introduced.

Or what if you want an item that increases the Charisma skill while worn?

Events

Let’s go back to the Biotank. It was clear that the Biotank cared about resource production finishing. It didn’t care about production starting, turning off, or anything else. It just wanted to know when one specific thing happened.

We needed some way to tell the Biotank about the events that it cared about.

We did our best to have a poor man’s setup of this with our interfaces, but the interfaces were still very rigid. Anytime a new object needed to know about a new specific thing in ResourceProduction, we would have to add another method to the interface, and every single class that inherited from ResourceProducerObject would need a new, empty method (which is why there were so many empty methods in the Biotank’s class).

Subscribing to Events

We wanted a system where the Biotank could select exactly what it cared about, and it wouldn’t need to concern itself over anything else.

The ideal world would have code that looked something like this:

 
 
bioTank.SubscribeTo("OnProductionFinished", OnProductionFinishedCycle);
 
 


Where the Biotank could magically be informed about only the things that it cared about.

The ResourceProducer would be responsible for invoking the event with the correct data:

 
 
producer.CallEvent("OnProductionFinished", productionData);
 
 

The Biotank now barely knows about production, and the ResourceProducer knows nothing about Biotanks or ResourceProducerObjects.

What’s more, imagine that we wanted to play a sound when production was finished. All we’d need do to is create some new code that subscribed to “OnProductionFinished” and played a sound when it was invoked.

This idea has formed the basis of our Agent-Component-Event system.

Agent-Component-Event Engine

In our new engine, there are no such things as Colonists, Biotanks, or even Resources.

Everything is an Agent. Components are added to agents.

There’s a component for pathfinding, another for breathing, another for handling lighting, and for everything else in the game.

Components subscribe to events and do whatever they want with that information.

So it became: Agent have Components, which subscribe to Events.

No More Colonists

There’s no longer any code that refers to “Colonists”. What you see as a colonist is simply an agent with components for animating, rotating, changing sprites, carrying objects, tracking hitpoints, storing items, and whatever else a colonist does.

Colonists (and everything else) are a sum of their components.

Event Priority

Events are subscribed to with a priority as well. There’s an event called, “OnDamaged”. We could create an “Invulnerable Component” that subscribes to that event with a high priority. Anytime “OnDamaged” is called, the component could set the amount of damage to 0.

We could also create a “Fragile Component” that doubles all damage received.

We could create an “Iron Skin Component” that removes all damage only if the source was a projectile.

New Behavior

Here’s the really cool part.

Components can be added and removed as the game is running.

If the colonist has robotic lungs installed, we could remove the Oxygen Need Component. The colonist would no longer die if he didn’t have oxygen. Or we could subscribe to the “OnBreathed” event and adjust the amount of oxygen required or the amount of oxygen breathed in.

If we wanted to add robots, we could just copy the colonist agent and remove the sleep and hunger need components.

If we wanted to create an alien egg item that spawns an alien and emits radiation, we could do that.

Incredible Depth

It has become very simple to add lots of depth to the game.

Beds

When a colonist sleeps, they look for a bed. There’s a Claimable Component, Mountable Component, and a Sleep Restoring Component (among other components), but let’s simplify things and just say that there’s a single “Bed Component”.

If we wanted colonists to sleep in chairs, tables, toilets, spaceships, lockers, or wherever we just have to add the Bed Component. That’s it.

A priority can be assigned to each bed as well, so that a colonist will always prefer:

bed > chair > table > spaceship > toilet > floor > locker

It gets better. When a colonist sleeps on the floor we could add the “Back-pain Component”, maybe that component reduces the max carry weight and causes a morale penalty for 24 hours. The floor would just need a component that subscribes to OnMounted, OnSleptOn, or whatever event is best.

We could create a “Hates Beds Components” and that colonist will never sleep in a bed. Maybe that component is added whenever a traumatic experience happens to a colonist while they’re in a bed.

Farms

In Starmancer, you can grow crops in little crop boxes. Objects have an HP system. Mechanics repair objects with low HP.

In the old system, the crop and the box were the same object. It was impossible (or at least not very easy) for the crop and the box to have independent HP that changed for different reasons.

With the component system, crops have an HP system that goes down if atmosphere conditions aren’t ideal (and if they’re not watered). The actual box has an HP system that goes down over time, through use.

Both the crop and the box have the same HitPointComponent on them, and they can both die, but vastly different things cause their death.

Heaters

As objects become damaged, they should start to behave strangely. This has become incredibly simple.

A heater could subscribe to OnDamaged and change its target temperature whenever it’s damaged (potentially boiling or freezing your station).

This allows us to do more than just generically blowing up a damaged object. We’re introducing unique consequences for different circumstances.

Everything is an Agent

Back to the Biotank and Ore Refinery again.

Everything is now an agent. The Ore Refinery no longer produces Resources, it produces agents. Wheat is an agent, colonists are agents, floors are agents, items are agents.

This means that everything can be substituted with everything else (in general).

A Biotank and an Ore Refinery can now use almost the exact same components. They don’t know the specifics of the agent that they’re creating. All they know is that they’re supposed to produce “AgentToProduce”.

Actually, there’s a component called “ProducedItemComponent” and an event for “OnProductionFinished”. ProducedItemComponent creates an Item when OnProductionFinished is received. There’s another component called ProducedColonistComponent.

Less Bugs (Possibly)

Components operate independently. They know only enough to do whatever it is they do.

A change in one component almost never affects any other component. We can somewhat safely add, remove, and modify components without too much worry about breaking something else.

We also have way less code to read through when trying to make a change

This isn’t entirely true, of course. Components are also responsible for calling events with the correct data. If the calling component breaks, all components that relied on that event will also break. If we forget to add a component to an agent, the agent won’t work as intended. In general, though, the components are much more self-sufficient.

XML Loading

All of the agents are loaded at run-time from an external xml file.

We set up agents using the Unity inspector, and automatically generate the xml file. Once the game is running, though, it relies very little on Unity.

This is what the xml file looks like for the curtain that you can place on walls:

curtain.png

Anyone could modify this to adjust the curtain or to create an entirely new object. You (or a modder) could add the Oxygen Need, Breather and Hitpoint Component to the curtain. It would die if it was in a room without oxygen.

We’re still working on a good system for modders to create their own components in C#. We were able to make new components by building our own dll’s and loading them at runtime. We haven’t done enough testing with this system (performance, will it break easily, etc) to confidently choose it yet.

Saving

Think about what it means to save in a game.

A colonist has a name, age, friendship status, current animation, inventory, job, skills, hunger level, memories, etc. Saving something like a name is fairly easy, it’s just a string. But how do you save something like “friendship status”?

Friendship status is composed of multiple variables. At a minimum you have Other Colonist and Relationship Level. Relationship Level is probably a number, Other Colonist is a reference to some other colonist. Somehow, the colonist has to say, “hey, save this number and associate it with that other colonist”.

In the new system, components are responsible for saving whatever it is that they care about. We can save almost anything without too much of a headache.

Here’s an example of the VisualDeathComponent. It subscribes to the event, “OnDied”. When that event is received, it plays the death animation.

We use the DatabaseEntry attribute above variables to mark them for saving. We’re using reflection to find all of the marked variables.

(Sorry that it’s so wide)

 
public class VisualDeathComponent : AgentComponent
{
    [DatabaseEntry]
    public string isDeadAnimationVariable = "isDead";
     
    protected override ListOfObjects<AgentEventRegistrationData> GetAllEventsToRegister()
    {
        AgentEventRegistrationData onDied = new AgentEventRegistrationData(EventName.OnDied, OnDied);
        return new ListOfObjects<AgentEventRegistrationData>(onDied);
    }
   
    void OnDied(AgentEventParameter parameter)
    {
        // I spread out the event call so that it would be on multiple lines
        // Otherwise it would take up too much width in the blog post
 
        EventName eventName = EventName.OnAnimatorBoolChanged;
        var animatorParameter = new OnAnimatorBooleanChangedParameter(isDeadAnimationVariable, true); 
        agent_.CallEvent(eventName, animatorParameter);
    }
}
 

The component itself barely has any code in it. We don’t have to read through a bunch of unrelated code (and possibly break it) whenever we want to make a change to the VisualDeathComponent.


Better Future

We want to give Starmancer the best foundation possible. We think that this new engine will do that.

It’s way more flexible. The complexity is almost endless. Modding is way easier. Saving is way easier.

I think that we’ll more than make up for our lost time with this overhaul. Our goal is to create a game where 1 object can do 100 things instead of 100 objects doing 1 thing (eventually we’d like to have 100 objects doing 100 things).

Alpha

We’re still not sure when the Alpha will be released. Here’s a rough list of what still needs to happen.

  1. Add all objects back to the game (currently doing this)

  2. Implement build menu (about halfway done with this)

  3. Implement build mode

  4. Fix whatever bugs we might find


Streaming

I’d like to do a live stream or at least a youtube video before the Alpha goes live. I’m not sure when that will be, and we’ll post something on Kickstarter and Discord before that happens.

By the way, don’t forget to join our Discord server.

Roadmap

We have a roadmap as well. It’s possible that we’ll forget to update it or change it, but we’ll do our best to keep it current.

You can find the roadmap, here.

Music

Dirk created a compilation of some of the music that will be in Starmancer.

You can listen to it here:

https://soundcloud.com/irkluesing/starmancer-teaser-compilation



Thanks for reading and thanks so much for all of your support.

- Tyler