Ok, I've been really bad about writing these.
We work on Starmancer every single day (you can join the Discord to see). We've been in such a non-visual part of development. It's difficult to share screenshots.
It's also sort of exhausting to write up a blog post. You have to come up with something interesting to talk about, and then organize your thoughts into several coherent paragraphs. Ideally you'll include some pictures too.
But this sounds like complaining, so I'll just get into it.
Lighting Changes
The way lighting worked before was sort of a mess. It worked something like:
- Floors are placed
- Floors extend the room of any adjacent floor.
- Walls and doors block rooms
- All objects placed on a floor are added to that floor's room
This becomes somewhat complicated when you place a wall in a way that it splits an existing room.
Like this:
The new wall caused an existing room to be split in half. All of the current values of the room (lighting, atmosphere, etc) have to be copied into a new room, and all of the objects have to be distributed to the correct rooms.
To do this, we just recursively find all of the connected floors, something like:
void AddFloor(Floor floor) { if(allFloors.Contains(floor)) return; allFloors.Add(floor); foreach(Floor adjacentFloor in floor.GetAllAdjacentFloors()) { AddFloor(adjacentFloor); } }
And then we assign each set of connected floors to a room.
Merging Rooms
If you were to remove the splitting wall above, you would have to merge the rooms. It would look like this:
This is a bit easier to handle. You just have to:
- Get the floor with the wall
- Find all adjacent floors
- Get their rooms
- Merge all rooms
- And add the free floor to the merged room
The code would look something like this:
void MergeRooms(Floor floor) { List<Room> allRooms = GetAllAdjacentRooms(floor); if(allRooms.Count == 0) CreateNewRoom(floor); else MergeRooms(allRooms, floor); } List<Room> GetAllAdjacentRooms(Floor floor) { List<Room> allRooms = new List<Room>(); foreach(Floor adjacentFloor in floor.GetAllAdjacentFloors()) { Room room = adjacentFloor.GetRoom(); if(allRooms.Contains(room) == false) allRooms.Add(room); } return allRooms; } void MergeRooms(List<Room> allRooms, Floor floor) { Room roomToKeep = allRooms[0]; roomToKeep.Add(floor); for(int i = 1; i < allRooms.Count; i++) { roomToKeep.MergeWith(allRooms[i]); } }
Wait...did I say it was easier. It has it's own hassles. You have to merge the two room values together. Atmosphere doesn't matter, because it's not visual. Lighting, however, is visual, so we have to immediately merge it to some correct value.
And it looks bad when you change the lighting instantly, so you have to change it over time. Oh, and when you're changing the lighting over time the room could continue to get new objects, change its own lighting, or split/merge.
Corners, and Why I Hate Them
You may have noticed that the corners in the above gif weren't lit correctly. Well here's why:
Walls have 4 sides. To determine the room that a wall side belongs to, we use whatever floor is located at that wall side.
We then light that wall side appropriately.
This used to be more of a hassle, because a wall used to be a single object. Walls and doors are unique in that they're the only objects in the game that can exist in multiple rooms.
One of the reworks we did was to change walls so that they're now 1 object that has 4 child objects. Each child object (wall side) is responsible for rendering itself. Other objects, like a floor/room/whatever don't know that the wall side is actually a child object. They just treat it how they treat every other object.
Anyway, corners are an issue because a corner belongs to a wall that isn't actually in the room. The wall side belonging to a corner is actually on a floor that is covered up by an adjacent wall.
Let me show you:
I added red square to represent the wall sides.
I painted one side of the wall. Once it becomes a corner, you can still see the paint, but it's not being lit with the room. It's because the wall side with the paint is visually shown in the corner, but the internal wall side thinks it's underneath a wall (and not considered valid to be in a room)
I was actually about to fix this issue, but I decided to write a blog post instead.
(Here's proof. We use Asana to manage everything.)
My plan is to just move the wall side whenever another wall is on top of it. It should be just simple enough to work.
The Actual Changes
I realize that I haven't actually talked about the lighting changes. I've just been talking about how the lighting currently works (after the changes).
The actual changes were sort of boring to talk about.
- Objects no longer redundantly change their color during a room split.
- Rooms now merge and split colors correctly.
- There is now a concept of an "Exterior Room". It's always dark (for now)
- Restructured how material changes work to prevent excess calls of getting and setting MaterialPropertyBlocks
- Added a concept of "Shared Modifiers" to renderers (or something like that)
- Prevented some redundant room splits and merges (they no longer split / merge if 1 single object is added/removed)
I Like Context
One reason that I have a difficult time writing blogs is because I don't like to say what I've done without explaining why it matters.
It would take me about 30 minutes (of writing) to explain to you how we modify materials on objects so that they'll light correctly. The quick thing is something like:
- Add / modify existing rendering modifier
- Apply all rendering modifiers
There didn't used to be a consistent system in place for modifying materials. It was the wild west. Objects would just do, myRenderer.AdjustProperty("_LightingColor", roomColor).
And then other objects would adjust material properties for their own reasons.
One way that we animate, for example, is to change the texture offset on models/materials. The propaganda billboard is a good example of this.
We had to retrieve all of stored properties before we could change any of them, and this had really bad performance when done frequently (like when lighting every single object in a room).
UI Mockups
We've been working on the new UI too.
The position of everything is placeholder. We've been playing the game with various layouts to find out what feel best.
Once we're happy with the results Victor will make it look really pretty.
Here's a mockup of the build menu (you should be able to click it to make it bigger).
I think that the User Interface in a game like this is especially important. Too many indie games suffer from poorly designed interfaces.
We don't want Starmancer to feel frustrating or confusing to play. We want a game where you can actually experience interesting stories first-hand (instead of reading about them on the internet).
Because of this, it's taking us a while to design the interface. We don't want the interface to feel like it was thrown together last-minute.
We've Been Quiet
Ok, we've actually been quiet for somewhat good reasons.
Reason One
Most importantly (to me) is that my wife is pregnant and will be having a baby (our first) on or around September 7th. With this ticking time clock looming over my head, I haven't wanted to waste any development time with blog posts (so far, I've spent 90 minutes writing this, btw, and I'm still not finished).
Eventually, I'll have to take some time off, and I wanted Evan to be as prepared as possible to handle things for a little bit.
By the way, I don't have a smartphone (but my phone is waterproof). I like being away from the internet. So it's not easy for me to answer any questions that he might have.
A More Interesting Reason
Most importantly (to you) is that we've rebuilt Starmancer almost from the ground-up.
We don't take your support (both financially and otherwise) for granted. We've all pre-ordered games that either never came out or were terrible when they came out.
We don't want that.
You might think, "hey, you raised $150,000. That sounds good to me".
Over 5000 people gave us their hard-earned money for a product that doesn't even exist yet. You could have spent your money on anything. You could have just waited. But you didn't. You put your faith in us, and we don't want to let you down.
If you think we're not here, head over to the Discord. Evan and I change Unity to "Starmancer Development", so you always know when we're working.
We're here every single day, except for Sunday and some Saturdays.
Redesign
I don't want to get into too much detail here. I'll write some other post about the actual redesign.
The summary is that the game is now completely component based. There's no such thing as a colonist or a floor or a ship anymore.
There are pathfinding components, rendering components, room extension components, room blocking components, etc.
Objects are simply a sum of their components.
What this means in practice is that we can add an incredible amount of complexity to Starmancer.
If we want colonists to sleep in a chair, we just have to add the "BedComponent" to a chair, and colonists will sleep in it.
If we wanted little cleaner robots, we could basically copy the colonist and remove the NeedsComponent (so that the cleaner robot doesn't eat).
If we wanted an alien egg that spawns an alien if you place it in a really hot room, we can do that now. Somewhat easily actually.
There's a component responsible for finding conversation participants. We could add a CrazyComponent to a colonist and he'll talk to walls or potted plants.
All of the objects are saved in plaintext xml too, so anyone can create or modify objects by using the existing components.
So Long, Farewell
I never know how to end these things, so bye.
(I spent 2 hours and 3 minutes on this, for the record)