2024-01-29

Project Raygine #19 - testing

This is an early test of something more serious. If you click the window, you can control the ship using WSDA for movement, Q and E for rotation and mouse clicking for shooting.

It's a very early test, so it's not so impressive (it won't be 😅). I want to make a simple asteroid shooting game here - so some flying asteroids on a radar that can be shot and absorbed. The primary goal is to collect experience with the editor and seeing which steps are the most annoying. One part is also to see, how the scripting feels. Here's the projectile component for example:

local type_registry = require "raygine.shared.type.type_registry"
local T = require "raygine.shared.type.type_identifiers"

---@class scripts.projectile_component : raygine.scene_component
---@field transform raygine.transform_component
---@field speed number
---@field max_age number
local projectile_component = type_registry
    :define(T.scripts.projectile_component)
    :set_super(T.raygine.scene_component)
    :add_attribute("speed", T.number)
    :add_attribute("max_age", T.number, 5)
    :get_class()

function projectile_component:on_awake()
    local t = assert(self.entity:get_component(T.raygine.transform_component))
    self.transform = t
    self.age = 0
end

function projectile_component:on_update(dt)
    self.age = self.age + dt
    local s = self.speed * dt
    local x,y,z = unpack(self.transform.position)
    local fx,fy,fz = self.transform:get_world_forward(true)
    -- mesh forward is -z
    self.transform:set_world_position(x-fx*s, y-fy*s, z-fz*s)
    if self.age > self.max_age then
        self.entity:destroy_deferred()
        return
    end
end

return projectile_component

The projectile component is a simple component that moves the entity forward and destroys it after a certain time. The projectile is spawned by the ship when the player clicks the mouse. The annotations provide information during editing the script. I dislike the annotations a little, because they are a little redundant, but without it, the script editing is much less convenient:

A screenshot of Visual Studio Code showing the autocomplete a member of the projectile class in the update function

The autocomplete is a huge help when writing scripts. For the editor scripts, I am generating the annotations from the type registry information; the editor generates them, when the scripts are loaded. I could do the same for the game project scripts.

I was also considering to create the type information from the annotations themselves, but there are also arguments against this; Here's an excerpt from the transform component how it registers itself in the type registry:

local transform_component = type_registry
    :define(T.raygine.transform_component):set_super(T.raygine.scene_component)
    :set_display_name("Transform")
    :set_icon "resources/icons/icon_transform_component.png"
    :add_attribute("position", T.vector3, { 0, 0, 0 }):set_hidden(true)
    :add_attribute("rotation", T.vector3, { 0, 0, 0 }):set_hidden(true)
    :add_attribute("scale", T.vector3, { 1, 1, 1 }):set_hidden(true)
    :add_inspector_callback(
    ---@param self raygine.transform_component
    ---@return boolean
        function(self, path_id)
            -- dropdown menu for coordinate system selection: Local, World, Reference
            type_gui:draw_label("Coordinate system")

It shows that for each attribute in a serialized type, I can add various bits of additional behavior. Being able to inject editor inspector code makes custom component behavior handling quite easy. In general, it isn't a problem to keep having editor logic in play classes; the code isn't executed, so the missing editor scripts are not going to cause trouble.

So I'll have to see how it feels when working on an actual game. Currently, a lot of the systems are pure Lua logic, but I started thinking how to port some parts to the C/C++ realm to keep things efficient. The entities and the scene graph are good candidates since they have a lot of hot paths. I also started thinking about using an ECS approach for this; The thing is, I don't think it pays off to use an ECS for Lua scripted elements, since the advantage of memory locality is lost. But using ECS for entities, transforms and some few other components may be a good idea. It wouldn't look like an ECS from the outside, but it would be internally. But this is one of many topics that I am only keeping in the back of my head and that have a very low priority right now. It's far more important to me to get the editing experience smooth and simple as well as being able to build the project for different platforms.

My plan is still to participate in a gamejam this year using this engine to see how it feels to work with it. Maybe the Kenney Jam 2024 is a good candidate: Focus on using existing assets and creating games using them and taking place in 5 months, which gives me enough time to get the editor into a usable shape. Maybe even getting Android builds working!

🍪