It’s fun to run character simulations and generate results, it’s even more fun when you can actually see the characters. These past two weeks I’ve been writing the sprite rendering system, that doesn’t utilize gameobjects/monobehaviours, or unity spritesheets/texture atlases etc, it’s all homemade. So, how is it done?
For sprite rendering, there’s no alternative, plain and simple. We have a very simple geometry (a quad), and we want to render N instances of it, each with its own parameters. The main parameters, for sprites, are:
Rect location in texture atlas
Number of animation frames (stored sequentially in atlas)
Current location in the world
Previous location in the world (moving sprites only)
Time at previous location in the world (moving sprites only)
Movement speed (moving sprites only)
From the above one can see that for moving sprites we need more data, actually it turns out to be twice as much. We can also reason that the list with the moving sprites needs to be updated far more often, due to the changing positions. Therefore, it makes sense to have two separated entity sprite lists, the static and the dynamic, each of which contains instance data that will be rendered with Unity’s DrawMeshInstancedIndirect function.
Entity sprite list maintenance
Entity enters map: add its default/idle animation to the static list
Entity exits map: remove animation from static and dynamic lists
Entity teleports: remove animation from dynamic list, add it’ default/idle animation to the static list
Entity moves to adjacent location: remove animation from static list, add its moving animation to the dynamic list
Every several frames, we go through the dynamic list and check if an animation has finished. If it has, we remove it from the list and add the entity’s default/idle animation to the static list
For the above, when we add an animation, we also set the instance data to a CPU buffer. When we render, the CPU buffer contents are copied to a GPU ComputeBuffer (if anything has changed), which is sampled in the shader
Here’s an example where every second, we schedule a movement to 10,000 entities. Runs pretty well except the last bullet point above, which I’ve disabled for this video. The video also shows an additional GUI sprite rendering layer with highlighted tiles (yellow squares) and the hovered tile is shown via a greenish sprite.
This week I ported another bulk of data, namely RPG-system related stuff such as attributes, skills, skill categories, skill mastery levels and level-up strategies. The data all became scriptable objects (as they’re constant assets), and all except the level-up strategies were enum-ified as it is quite likely that in the code I’ll be referring to particular skills/attributes/etc (e.g. if skills[dexterity] > 10, do something)
I’ve added a much-needed global object with all the game configuration parameters (e.g. max character levels, skill points per level, etc), while in C++ it was pretty much a dictionary loaded from json to avoid recompiles, here I have it as a scriptable object with fixed fields, so access is faster, compiles fast and data are interchangeable.
Another addition is a very simplified form of date/time. I opt out of using C#’s DateTime or TimeSpan as they are for real-world time and therefore do not allow flexibility. What I’m using instead is a simple long value wrapped in a struct (called TimeUnit), representing microseconds. The struct stores loads of constants and helpers for printing and manipulating, so that in the end a TimeUnit is just an integral type with benefits. 64 bits support quite a bit of a range, so it should be enough for the game’s time system resolution.
Since last time, I’ve tackled (or not) a few issues.
NO gameobjects/monobehaviours in the game database / ECS.
First and more importantly, I’ve made the decision to represent entities with a custom, very lightweight class, that does not derive from any Unity stuff. Reason being that I don’t want to interact too much with Unity, especially derive from these classes that carry a lot of baggage. I’m pretty happy to manage entity lifetimes and bite the bullet in terms of inspector “niceties”, rather than start using GameObjects and then, when these are tangled deep in the game code, I realize that it’s actually a terrible idea and it gets too slow. So that’s that.
Entities are represented now with custom classes, and to avoid potential infinite serialization depth that Unity is not happy about, I use weak references. So, instead of:
city entity -> city component -> owned mines (entities) -> mine entity -> membership component -> owner entity -> city entity -> …
… which causes an infinite loop, I use the following:
city entity -> city component -> owned mines (entity references)
so the chain is broken there. The references are just IDs that look up in a pool. So that’s that. In the meantime, I’m trying to make the inspector show in place of the entity references the entity contents, but currently I’m not doing a good job. Revisit later.
This is simply the danger map calculation (what level of encounters one might find at a certain point on the overworld map), which involves distance field calculations to cities and routes and some poisson sampling, both of which I’m calculating in C++ via a native plugin. So, performance remains nice, which is nice. Here it is, alongside the biome/routes map. Red is dangerous, blue is safe
Implementing visualizers in Unity is a breeze, and I love being able to see what I hover over and see data, data, data. In terms of pixels or values in the inspector. I captured a video to show how this looks like, I love it 🙂
Last but not least, I ported the RPG-related datafiles (skills, attributes, skill masteries, etc). So, next work is going to be adventure location generation, and maybe character generation and levelling.