Dev Blog

Hi, this is the Stranded III development blog (see also Forum Thread, Comment Thread).

Overview (118 Entries)

Entry 118 - Lua Defs & Billboards - April 27, 2025

It's all Lua now
In Dev Blog 116 I said that definitions will now be managed in the Godot editor directly. Well... I changed my mind (again).
It was way easier to implement but it turned out that it is very annoying in practice. Especially with nesting of data structures which requires a lot of unfolding in the editor UI. It's just not a lot of fun to work with that.
Therefore I switched to another approach. All definitions are now written in Lua! This has several advantages over the Godot approach and also some over the old definition file approach:

√ Easy modding without Godot
You'll be able to modify stuff without having to download Godot. You just have to edit Lua files which works with any text editor.

√ Seamless integration of Lua scripts
The game also uses Lua scripts to add functionality. These scripts now blend in seamlessly.
With the definition approach I had a weird mix of definition files and Lua script in one file. Now it's all Lua which also makes it easier to edit the files with proper Lua editors.

√ You can write scripts to create definitions
This one is pretty insane. You can, for instance, write a for loop which creates defintions. E.g. when you want to have 10 rock variations which basically work the same, you don't have to write 10 definitions yourself anymore. Just create them dynamically with Lua.

What does it look like and how does it work? Let me give you an example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
palmTreeSpawn = {
     steepnessRange = {0, 0.5},
     block = 1,
     highBlock = 2,
     scaleRange = {1, 1.3},
     surfaceAlign = 0.25
}

def.object.palmtree01a = {
     name = "object_palmtree",
     category = "palmtrees",
     model = "res://models/palmtree01a.glb",
     icon = {
          brightness = 1,
          rotation = {0, 30, 45}
     },
     
     spawn = palmTreeSpawn,
     
     attributes = {
          health_max = 4000,
          effectiveness_wood = 10,
     },
     
     findRate = 1000,
     finds = {
          { entity = "palmfrond" }
     },
     
     onDestroy = function(id)
          spawnLogs(id, 2)
          entity.destructionFx(id, "palmfrond")
     end
}

This sample creates 2 new Lua tables: "palmTreeSpawn" which defines random map generation spawn settings (which can be reused by multiple definitions) and an object definition for the object "palmtree01a".
Game definitions need to be in the "def"-table and then there are sub-tables for the type of definition (in this sample "object"). The last part, "palmtree01" is the internal ID for the thing you want to define.
Inside the table is a list of properties. These can be simple data types, like the "name" key which links to the loca key "object_palmtree", but also nested tables. The "icon" sub-table for instance defines how the game should render a preview icon of that object.
"spawn" uses the data of the "palmTreeSpawn" table which was defined earlier. This approach makes it super easy to reuse data. "onDestroy" at the bottom is an actual Lua function which is executed by the game when the object is destroyed.

To make Lua definitions works with the game code, I could work directly with the Lua data structures. This is not what I do though because Lua data lookup is string based which would be relatively slow.
Therefore I read all the Lua values and put them into C# objects at startup via reflection. While doing so (and also afterwards) I do sanity checks and show errors/warnings when something is wrong. Attributes on fields in the C# classes allow me to define allowed data types, value ranges and to run additional sanity checks.

GLB instead of FBX
I also changed all model files from FBX to GLB. GLB is the recommended model format for Godot. One great thing about it: Godot can even load external GLB files at runtime. So if you want to add custom models to the game you can do so without having to install Godot.
This is why the definition above says "res://models/palmtree01a.glb". "res://" stands for built-in resources which are bundled with the game when building it in Godot. Stranded III however would also allow using external file paths which are relative to the folder of the mod you're currently running.

RenderingServer and PhysicsServer3D
To improve render performance, I'm trying to avoid using nodes for static objects like trees. Instead I'm using the RenderingServer and PhysicsServer3D. Objects are rendered together using Godot's MultiMeshes (which are also created via the RenderingServer). This is combined with a chunk logic. The world is split into a lot of little squares and for each model type in a square there's a MultiMesh which renders all instances of that model in that chunk.

Billboards
To get even more performance and to support a bigger viewing distance, I now also introduced my own billboard system. It was important to me that this system doesn't add any extra effort when adding or changing game assets. Therefore the game automatically builds a billboard texture atlas for all objects which need a billboard.

Currently this needs to be specified manually in the definition but I also plan to add an automatic mode where the game checks the bounding boxes of meshes and decides itself if a billboard is required (big objects) or not (smaller objects) and how large the billboards should be.

Even though the atlas generation is quite quick, I don't want to do it at every startup.
The atlas texture and all meta data is stored in a cache file and loaded at startup. Only when billboards are missing the game will rebuild the atlas. This has a little drawback: When you're modding the game and replacing a model asset (without renaming the file) the billboard doesn't update automatically. You need to delete the cache file manually. I think this is acceptable though.
I could add a dev mode which always rebuilds the billboard atlas or store checksums or file change dates to fix this but that's just an idea and I'm not sure if I'll do it.

The system works quite ok but scale and brightness of the billboards isn't perfect yet. I need to fine tune that. Also it only creates a billboards from one angle to save GPU memory and to simplify the system.
Of course this doesn't give perfect results but it's acceptable as billboards are only used when pretty far away. I may add rendering from multiple angles later. Maybe optionally for specific meshes only.

Here are some "fun" screenshots of things that went wrong while trying to implement that stuff:
IMG:https://stuff.unrealsoftware.de/pics/s3dev/bb_broken1_pre.jpg

> bad atlas UVs


IMG:https://stuff.unrealsoftware.de/pics/s3dev/bb_broken2_pre.jpg

> vertically flipped


And here's what it looks like to only show billboards, even for close objects.
Maybe that could be an option to make the game perform better on super low end systems?
IMG:https://stuff.unrealsoftware.de/pics/s3dev/bb_close_pre.jpg

> Billboards


> Unrelated bonus pic: Ooops, bad transform calculation. I think it looks pretty cool in some way.

Disqus

blog comments powered by Disqus