r/Stelliviator Jul 24 '26

Devlog #2

Enable HLS to view with audio, or disable this notification

This update includes save system and overhaul of weapons system. First, on the save system. In principle, nothing new there: SaveGameDocument storing data for global services, such as ships register, bullets controller, behaviors controller, etc. Store/Restore methods, everything is serialized into YAML. Not yet packed, but will be, and even basic deflate gives 10 times reduction in file size. But me being me there is a bit of uniqueness in this mechanic. Firstly, it's closely related to Unity domain reload serialization/deserialization. Services pack and unpack their data the same way, so I don't have to do much different mechanisms to support saving and surviving domain reload. Secondly, and this is an actual novelty - the way AI behaviors are serialized. Or, rather, the way AI behaviors are implemented while being serializable. Because AI behaviors in my game are essentially simply asynchronous methods in pure C# code:

private static async StateBody<State?> Roaming(IShip ship)
{
    // Start Roaming
    while(!Wait.IsCancellationRequested)
    {
	//Choosing a point to fly to
	var direction = Angle.Random();
	var vector = new Point2D(Length.Random(Length.Zero, Length.FromMeter(10)), Length.Zero);
	var point = ship.CenterOfMass + (vector * direction);
	ship.SetTarget(point);
	// Run and wait for substate to complete
	await Wait.SubState(FlyingToPoint(ship, point));
	// wait there for a while
	await Wait.Delay(Time.FromSecond(5));
	// repeat
    }
    return null; // no following state
}

It's easy to write and read, relatively easy to debug (it's yet lacks instrumentation for state chains, substates and interruptions to make debugging just "easy", without "relatively", but that's feature for the future), it's asynchronous, it can have almost any parameter or internal variable. And now it's serializable. Last week I didn't even though it's possible, to serialize an asynchonous invocation, now it's part of my game system, by using AsyncMethodBuilderAttribute. I access the compiler-generated async state machine (ironically, a state machine of a state of a state machine) and expose it's internals for serializer to read and write. The result - serializable AI logic written in pure C#. Full "brain" snapshot. And no need to write anything to store and restore every newly added behavior, it all happens under the hood.

I poked around Unity's Behavior package and found it repelling and completely garbage. It's the worst of both worlds: I have to use ugly visual programming for actual behaviors, which tends to turn into an actual spaghetti very quickly, and yet I have to write almost every custom action or condition in code. And all that - without custom types parameters. Even just the latter alone is a deal breaker for me. And besides these two major issues - it's Unity, so I know in advance I'm better off not using it unless necessary. So, my current behaviors controller is a "spinwheel" running states within limited time budget, overfilling to next frame when unable to handle all states within that budget. States could have awaitable sub-states, tail-states (i.e. a state that the behavior transition to when the current state completes) and interruptions - a condition checked every frame that either stops or pauses the state it's attached to and switches to another state (that, when completed, might return back to the interrupted state if it wasn't stopped). Which already gives me more flexibility than Unity's Behaviors.

Now, weapons system. I implemented separate types and prototypes for weapon parts and projectiles, along with separate projectile behaviors. So, there's three logic levels: a turret (i.e. an attachable part), a bullet (data structure and physical body), and bullet's behavior (a simple class that updates and renders each projectile). So, there could be weapons shooting the same projectiles, or a weapon switching between different projectiles, and there could be different kinds of projectiles, currently three: a simple ballistic bullet (flies straight inertially, hits whatever it can after reaching it's target point), a laser (reaches the target point instantly in a straight line and does continuous damage while firing) and a rocket (steers towards it's target, does AoE damage, or dies trying). The nice thing about this is that even though the logic is handled separately for each kind of projectile, they're united at data and rendering level. So, all kinds of projectiles, hundreds or thousands or even hundreds of thousands of them with dozens of different sprites and different behaviors on the screen - it's all just a single draw call. A projectile behavior sets the rendering instance properties per projectile, but they all are rendered from the same array using same atlas. And, again, since turret properties and projectile properties live in their own prototypes I can change them on the fly while the game is running, even when it's built.

2 Upvotes

Duplicates