Brent
Latest Posts:
|
Force updating project's engine code One of the problems with being an engine developer is that often you want the latest changes you've done to the engine to be pulled into the project you're working on. Historically, the way that you would do this is by bumping the editor version in So I've fixed this problem by adding the ability to trigger the engine replace popup by holding Shift+CTRL when opening a project from the project select screen. This will do the exact same flow as an editor version bump without the need to waste your time messing with bumping the editor version. Brent - 2026-02-08 05:06:34.318 +0000 UTC |
|||||||||||||||||||||||||||||||||||
|
Ditching the GOB encoder (and making my own) The Problem with |
| Step | Encoder | Decoder |
|---|---|---|
| Header | Write a slice of type keys ([]string) and a slice of field names ([]string). | Read the two lookup tables. |
| Value* | For each value, write a *type id (uint8). If the value is a slice/array, write the element count first. | Read the type ID, resolve it via the lookup table, then decode accordingly. |
| Structs | Write the number of encodable fields, then for each field: (1) Write the field‑lookup index (uint16). (2) Recursively encode the field value. | Read the field count, then for each field: (1) Resolve the field name via the lookup table. (2) Locate the struct field by name (reflection). (3) Recursively decode the field value. |
| Primitives | Directly write the binary representation (or string length + bytes for strings). | Directly read the binary representation. |
The encoder/decoder are in the engine/encoding/pod folder.
- pod_encoder.go - builds the lookup tables, writes the header, and recursively encodes values.
- pod_decoder.go - reads the header, resolves types, and recursively reconstructs values.
Both files rely on a global registry (a sync.Map) that maps a qualified type name to its reflect.Type. Registration is done via pod.Register and unregistration via pod.Unregister.
This new encoder is actually very simple thanks to reflection. There is actually more test code than actual encode/decode implementation code, which is also nice.
Brent - 2026-02-06 23:44:00.132 +0000 UTC
SIMD Optimizations for Matrix Operations in Kaiju Engine
Introduction
Matrix math is at the heart of every 3‑D engine - it powers transforms, camera projections, skinning, and a host of other calculations. The original Go implementation in Kaiju used plain scalar arithmetic, which was clean but far from optimal on modern CPUs. By hand‑crafting SIMD assembly for the two platforms we ship on - AMD64* (Windows) and *ARM64 (macOS/Linux) - we have cut the cost of the most common operations by an order of magnitude.
The following post walks through the three core functions we accelerated, explains the assembly line‑by‑line, and shows the real‑world benchmark impact.
Benchmark Summary
Running the same Go test suite on an AMD Ryzen 9 7900X (amd64) and an Apple M4 (arm64) yields the numbers below. The
SIMD suffix denotes the hand‑written assembly path; the plain version is the original Go implementation.| Platform | Function | Plain (ns/op) | SIMD (ns/op) | Speed‑up |
|---|---|---|---|---|
| amd64 | Mat4Multiply | 22.62 | 3.51 | 6.4Ă— |
| amd64 | Mat4MultiplyVec4 | 44.95 | 2.98 | 15.1Ă— |
| amd64 | Vec4MultiplyMat4 | 45.27 | 2.67 | 17.0Ă— |
| arm64 | Mat4Multiply | 29.85 | 3.11 | 9.6Ă— |
| arm64 | Mat4MultiplyVec4 | 14.74 | 1.68 | 8.8Ă— |
| arm64 | Vec4MultiplyMat4 | 15.32 | 1.86 | 8.2Ă— |
These gains translate directly into higher frame rates and lower CPU budgets for physics, animation and UI rendering.
AMD64 Assembly Breakdown
The AMD64 file lives at
src/matrix/matrix.amd64.s. All three functions share a common pattern: load a row of the left matrix, broadcast each element across an XMM register with SHUFPS, multiply‑accumulate against the four rows of the right matrix, and finally store the result.1. Mat4Multiply
// func Mat4Multiply(a, b Mat4) Mat4
TEXT ·Mat4Multiply(SB),NOSPLIT,$0-192
// Load b rows (contiguous)
MOVUPS b+64(FP), X1 // b row0
MOVUPS b+80(FP), X2 // b row1
MOVUPS b+96(FP), X3 // b row2
MOVUPS b+112(FP), X4 // b row3
// Compute ret row0 = sum (a row0[k] * b row k for k=0..3)
MOVUPS a+0(FP), X0 // a row0: a00 a01 a02 a03
MOVAPS X0, X5
SHUFPS $0x00, X5, X5 // broadcast a00
MULPS X1, X5 // a00 * b row0
MOVAPS X0, X6
SHUFPS $0x55, X6, X6 // broadcast a01
MULPS X2, X6
ADDPS X6, X5
MOVAPS X0, X6
SHUFPS $0xAA, X6, X6 // broadcast a02
MULPS X3, X6
ADDPS X6, X5
MOVAPS X0, X6
SHUFPS $0xFF, X6, X6 // broadcast a03
MULPS X4, X6
ADDPS X6, X5
MOVUPS X5, ret+128(FP)
// ... rows 1‑3 repeat the same pattern ...
RET
Explanation*
MOVUPS loads an unaligned 128‑bit row of four float32 values.*
SHUFPS with the immediate masks 0x00, 0x55, 0xAA, 0xFF replicates a single element of the row across the whole register - this is the classic “broadcast” trick.*
MULPS performs four parallel single‑precision multiplies, and ADDPS accumulates the partial results.* The same sequence is repeated for rows 1‑3, only the source offset (
a+16, a+32, a+48) changes.2. Mat4MultiplyVec4
// func Mat4MultiplyVec4(a Mat4, b Vec4) Vec4
TEXT ·Mat4MultiplyVec4(SB),NOSPLIT,$0-96
MOVUPS m+0(FP), X0
MOVUPS m+16(FP), X1
MOVUPS m+32(FP), X2
MOVUPS m+48(FP), X3
MOVAPS X0, X4
UNPCKLPS X1, X4
MOVAPS X0, X5
UNPCKHPS X1, X5
MOVAPS X2, X6
UNPCKLPS X3, X6
MOVAPS X2, X7
UNPCKHPS X3, X7
MOVAPS X4, X8
MOVLHPS X6, X8
MOVAPS X4, X9
MOVHLPS X4, X9
MOVAPS X6, X12
MOVHLPS X6, X12
MOVLHPS X12, X9
MOVAPS X5, X10
MOVLHPS X7, X10
MOVAPS X5, X11
MOVHLPS X5, X11
MOVAPS X7, X13
MOVHLPS X7, X13
MOVLHPS X13, X11
DOT(b+64(FP), X8, ret+80(FP)) // x
DOT(b+64(FP), X9, ret+84(FP)) // y
DOT(b+64(FP), X10, ret+88(FP)) // z
DOT(b+64(FP), X11, ret+92(FP)) // w
RET
Explanation* The macro
DOT (defined at the top of the file) computes a dot‑product of a broadcasted scalar (b+64(FP)) with a 4‑component vector stored in an XMM register.The series of
UNPCK* and MOV instructions transpose the 4×4 matrix into column vectors (X8‑X11) so that each column can be dotted with the input vector b.* The final four
DOT calls write the resulting Vec4 back to the stack.3. Vec4MultiplyMat4
// func Vec4MultiplyMat4(a Vec4, b Mat4) Vec4
TEXT ·Vec4MultiplyMat4(SB),NOSPLIT,$0-96
MOVUPS b+16(FP), X1
MOVUPS b+32(FP), X2
MOVUPS b+48(FP), X3
MOVUPS b+64(FP), X4
DOT(a+0(FP), X1, ret+80(FP)) // x
DOT(a+0(FP), X2, ret+84(FP)) // y
DOT(a+0(FP), X3, ret+88(FP)) // z
DOT(a+0(FP), X4, ret+92(FP)) // w
RET
Explanation* Here the vector
a is broadcast once per column of the matrix b using the same DOT macro.* Because the matrix rows are already laid out contiguously, we can simply load each row (
X1‑X4) and reuse the macro.ARM64 Assembly Breakdown
The ARM64 version lives in
src/matrix/matrix.arm64.s. It uses NEON SIMD registers (V0‑V15) and the VDUP/FMOVQ intrinsics to achieve the same broadcast‑multiply‑accumulate pattern.1. Mat4Multiply
// func Mat4Multiply(a, b Mat4) Mat4
TEXT ·Mat4Multiply(SB),NOSPLIT,$0-192
FMOVQ b_0+64(FP), F0
FMOVQ b_4+80(FP), F1
FMOVQ b_8+96(FP), F2
FMOVQ b_12+112(FP), F3
MULROWWISE(0, 128)
MULROWWISE(16, 144)
MULROWWISE(32, 160)
MULROWWISE(48, 176)
RET
// macro used above
#define MULROWWISE(inOff, outOff) \
FMOVQ a_rI+inOff(FP), F4 \
VDUP V4.S[1], V5.S4 \
VDUP V4.S[2], V6.S4 \
VDUP V4.S[3], V7.S4 \
VDUP V4.S[0], V4.S4 \
MULSUM4ROWS() \
FMOVQ F14, ret_rI+outOff(FP)
#define MULSUM4ROWS() \
WORD $0x6e24dc08 \ // fmul.4s v8, v0, v4
WORD $0x6e25dc29 \ // fmul.4s v9, v1, v5
WORD $0x6e26dc4a \ // fmul.4s v10, v2, v6
WORD $0x6e27dc6b \ // fmul.4s v11, v3, v7
WORD $0x4e29d50c \ // fadd.4s v12, v8, v9
WORD $0x4e2ad56d \ // fadd.4s v13, v11, v10
WORD $0x4e2cd5ae // fadd.4s v14, v13, v12
Explanation*
FMOVQ loads a 128‑bit row of the right matrix into a NEON vector register (F0‑F3).*
VDUP replicates each scalar component of the left‑matrix row (a_rI) into four separate registers (V4‑V7).* The
MULSUM4ROWS macro performs four parallel fmul.4s operations (one per column) followed by a tree of fadd.4s to accumulate the four products into a single result (v14).* The final
FMOVQ writes the 128‑bit result back to the stack.2. Mat4MultiplyVec4
// func Mat4MultiplyVec4(a Mat4, b Vec4) Vec4
TEXT ·Mat4MultiplyVec4(SB),NOSPLIT,$0-96
FMOVQ a_0+0(FP), F0
FMOVQ a_4+16(FP), F1
FMOVQ a_8+32(FP), F2
FMOVQ a_12+48(FP), F3
FMOVQ b+64(FP), F4
VDUP V4.S[1], V5.S4
VDUP V4.S[2], V6.S4
VDUP V4.S[3], V7.S4
VDUP V4.S[0], V4.S4
MULSUM4ROWS()
FMOVQ F14, ret+80(FP)
RET
Explanation* The four rows of matrix
a are loaded into F0‑F3.* The vector
b is broadcast into four registers (V4‑V7) using VDUP.*
MULSUM4ROWS performs the same multiply‑accumulate as in the matrix‑matrix case, yielding a single Vec4 result stored in F14.3. Vec4MultiplyMat4
// func Vec4MultiplyMat4(v Vec4, m Mat4) Vec4
TEXT ·Vec4MultiplyMat4(SB),NOSPLIT,$0-96
FMOVQ m_0+16(FP), F0
FMOVQ m_4+32(FP), F1
FMOVQ m_8+48(FP), F2
FMOVQ m_12+64(FP), F3
FMOVQ v+0(FP), F4
WORD $0x6e24dc05 // fmul.4s v5, v0, v4
WORD $0x6e24dc26 // fmul.4s v6, v1, v4
WORD $0x6e24dc47 // fmul.4s v7, v2, v4
WORD $0x6e24dc68 // fmul.4s v8, v3, v4
WORD $0x6e25d4a9 // faddp.4s v9, v5, v5
WORD $0x7e30d920 // faddp.2s s0, v9
WORD $0x6e26d4ca // faddp.4s v10, v6, v6
WORD $0x7e30d941 // faddp.2s s1, v10
WORD $0x6e27d4eb // faddp.4s v11, v7, v7
WORD $0x7e30d962 // faddp.2s s2, v11
WORD $0x6e28d50c // faddp.4s v12, v8, v8
WORD $0x7e30d983 // faddp.2s s3, v12
FMOVS F0, ret_0+80(FP)
FMOVS F1, ret_1+84(FP)
FMOVS F2, ret_2+88(FP)
FMOVS F3, ret_3+92(FP)
RET
Explanation* Each row of the matrix is multiplied by the broadcast vector
v using four fmul.4s instructions.* A series of
faddp (pairwise add) instructions collapse the four products into a single scalar per component, which are then stored with FMOVS.Results and fallback
By moving the hot paths of matrix math into hand‑written SIMD, we have achieved single‑digit nanosecond execution times on both major desktop architectures. The code is deliberately low‑level - we avoid function calls, keep everything in registers, and let the compiler focus on the surrounding Go glue.
For platforms other that don't support SIMD, or that we've not yet written the assembly for, the compiler will fall back to the traditional Go variants of the matrix math (found in src/matrix/matrix.none.go). Contributors can feel free to write the SIMD assembly code for other platforms as needed, just follow the pattern already set for AMD64 and ARM64.
Credits:
- AMD64 assembly authored by Brent Farris
- ARM64 assembly authored by rhawrami
Brent - 2026-02-03 14:20:31.976 +0000 UTC
Working with VFX in Kaiju Engine
Introduction
Visual effects (VFX) are a cornerstone of modern game development, providing everything from subtle smoke trails to spectacular fireworks. Kaiju Engine's VFX subsystem is designed to be lightweight, extensible, and tightly integrated with the editor, allowing designers to craft and iterate on particle effects in real time.
In this post we'll explore the architecture of the VFX system, dive into the key data structures (Particle, Emitter, and ParticleSystem), and show how to edit emitters using the built‑in editor UI.
VFX Architecture Overview
At a high level the VFX pipeline consists of three main pieces:
1. Particle - a lightweight struct that stores transform, velocity, opacity and lifespan.
2. Emitter - owns a list of particles, spawns them according to a configuration, and updates them each frame.
3. ParticleSystem - aggregates one or more emitters and provides a single interface for the renderer.
Relevant source files include:
- src/rendering/vfx/particle.go - defines the Particle type and its update logic.
- src/rendering/vfx/emitter.go - implements spawning, path functions, and per‑particle data.
- src/rendering/vfx/emitter_path_funcs.go - registers built‑in path functions (e.g., Circle).
- src/editor/editor_workspace/vfx_workspace/vfx_workspace.go - UI glue that lets you edit emitters in the editor.
Particle Structure
type particleTransformation struct {
Position matrix.Vec3
Rotation matrix.Vec3 // TODO: This can be 1D for billboarded particle
Scale matrix.Vec3 // TODO: This can be 2D for billboarded particle
}
type Particle struct {
Transform particleTransformation
Velocity particleTransformation
OpacityVelocity float32
LifeSpan float32
}
The update method advances the particle based on its velocity and reduces its remaining lifespan:func (p *Particle) update(deltaTime float64) {
p.LifeSpan -= float32(deltaTime)
t := &p.Transform
v := &p.Velocity
t.Position.AddAssign(v.Position.Scale(matrix.Float(deltaTime)))
t.Rotation.AddAssign(v.Rotation.Scale(matrix.Float(deltaTime)))
t.Scale.AddAssign(v.Scale.Scale(matrix.Float(deltaTime)))
}
Emitters
An
Emitter holds a slice of Particle objects and a configuration struct (EmitterConfig). The config controls texture, spawn rate, particle lifespan, direction ranges, velocity ranges, color, and optional path functions.Key fields in EmitterConfig:
type EmitterConfig struct {
Texture content_id.Texture
SpawnRate float64
ParticleLifeSpan float32
LifeSpan float64
Offset matrix.Vec3
DirectionMin matrix.Vec3
DirectionMax matrix.Vec3
VelocityMinMax matrix.Vec2
OpacityMinMax matrix.Vec2
Color matrix.Color
PathFuncName string `options:"PathFuncName"`
PathFunc func(t float64) matrix.Vec3 `visible:"hidden"`
PathFuncOffset float64
PathFuncScale float32
PathFuncSpeed float32
FadeOutOverLife bool
Burst bool
Repeat bool
}
The emitter spawns particles based on SpawnRate and applies the optional path function to offset the whole system over time.Path Functions
Path functions let you move an entire emitter along a curve. The engine ships with a
Circle function, but you can register your own.func init() {
RegisterPathFunc("None", nil)
RegisterPathFunc("Circle", pathFuncCircle)
}
func pathFuncCircle(t float64) matrix.Vec3 {
// Normalise t to the range [0,1]
for t < 0 { t += 1 }
for t > 1 { t -= 1 }
angle := matrix.Float(2 math.Pi t)
var pos matrix.Vec3
pos.SetX(matrix.Cos(angle))
pos.SetZ(matrix.Sin(angle))
return pos
}
Editing VFX in the Editor
The VFX editor UI is defined in
editor/ui/workspace/vfx_workspace.go.html. It provides two panels:- Left panel - list of emitters, system name, and add/save buttons.
- Right panel - per-emitter data bindings (texture, color, direction, etc.).
Adding a New Emitter
Click the Add Emitter button to create a new emitter with a default
EmitterConfig:w.addEmitter(vfx.EmitterConfig{
Texture: "smoke.png",
SpawnRate: 0.05,
ParticleLifeSpan: 2,
Color: matrix.ColorWhite(),
DirectionMin: matrix.NewVec3(-0.3, 1, -0.3),
DirectionMax: matrix.NewVec3(0.3, 1, 0.3),
VelocityMinMax: matrix.Vec2One().Scale(1),
OpacityMinMax: matrix.NewVec2(0.3, 1.0),
FadeOutOverLife: true,
PathFuncScale: 1,
PathFuncSpeed: 1,
})
You can then edit each field in the right‑hand panel. When you're satisfied, click Save - the workspace serialises the ParticleSystemSpec to JSON and writes it back to the project's content database.Still a work in progress
Known limitations
* CPU-only simulation - At the moment particles are updated on the CPU. This works well for modest counts, but large fire-works or dense smoke quickly become a bottleneck.
* Path functions are static - The built‑in
Circle is the only non‑trivial path function. Custom functions can be registered, but there is no UI for authoring functions in-editor.* Limited editor feedback - The VFX workspace shows the raw config values, but does not visualise the spawn area, direction cones, or velocity ranges directly in the viewport.
Planned improvements
* GPU particle pipelines - Off‑load the
update and spawn logic to a compute shader.* Rich path-function editor - Expose some curve editors in the UI to set path curves.
* Live preview helpers - Visual gizmos for spawn cones, velocity vectors, and opacity envelopes to make tweaking feel immediate.
Contributing
The VFX subsystem is deliberately lightweight, but I welcome extensions. To add a new path function:
1. Implement the function in src/rendering/vfx/emitter_path_funcs.go following the pathFuncCircle example.
2. Register it with RegisterPathFunc("MyPath", myPathFunc) inside the same file.
3. Submit a pull request with tests that verify the function's output range.
If you encounter bugs or have ideas for new emitter features, open an issue on the repository or join the discussion in Discord.
Brent - 2026-01-30 14:20:34.164 +0000 UTC
A Sony conference changed my perspective
In my last post I was investigating an idea for an isometric 2.5D game inspired by final fantasy tactics. However, since then, I went to a private Sony conference and was honestly bothered seeing a bunch of people who probably never played a game in their life talking about publishing, developing, and the business of video games; and honestly it struck a nerve with me.
The Sony conference
We are all deeply aware of what is going on in the game industry, and particularly in AAA, many of us work for those same companies. In fact, my company's parent company was one of the 4 guest speakers for the "state of the industry" fireside chat. You could imagine that the main topic was money, numbers, and how to scale. Another topic was how do AAA companies do what indie developers are doing, how can they artificially create these smaller scale, small team games and take that portion of the market? There was some ideology nonsense sprinkled in there, but not as much as you would think, probably from current trends.Of course these big companies have a lawful requirement to do whatever they can for shareholders, but they also have to make money to keep alive. It's not such a crazy idea, I know many indie developers are making games to either make a "hit" game to make a lot of money, or are making indie games so that they can make it their day job and stop working for someone else. So, indie or AAA, it seems to be a lot about the money, and very little about the games themselves.
Indies? Hustler culture?
We could balk at big AAA or even AA game companies and point to indie developers as the "last bastion" for creating fun, memorable games; but is that true? How many simulator games are there, how many horror games are there, how many games are made for a YouTube audience? Really, I don't think there are many "indies" out there holding up the last vestiges of a once experimental and thriving craft. You see a bunch of "survivors games" pop up out of nowhere, but some of us are old enough to remember "Smash TV", "Robotron" and "I made a game with zombies in it". How many mine-craft voxel games came after mine-craft, even huge hit indie titles just played off of the idea, just in 2D.So many indie developers are spending so much time creating games in hopes that it'll "pop off". I can't even begin to count how many instances of indie developers I've ran into who have a "great idea" (meaning money) rather than a "fun idea". It feels like the same hustle culture that I felt at the Sony conference. And... The worst part of the whole thing is that I fell into the same trap as everyone else.
Going back to "the good old days"
I reflected on what was going on in my head for a bit and realized that I perhaps have fallen into the same trap that everyone else seems to be falling into. This results in me making games that are well beyond what I can do on my own and ideas that are just too big to tell if they are fun. Now, it is a risk saying "fun", what I should say is "fun to me"- which probably isn't fun to most, and that's okay. Fun to me sometimes means technology, sometimes gameplay, sometimes art.I have always just done what I want to do, and that's why I have so many dead projects. I find one thing I'm interested in and then try to make some big ol' game idea based off that. Then I do the stuff I want to do and abandon the project. What I need to do is what I used to always do, which was find a very small scoped game I want to make, then make it for the fun of it and not even care if I sell it or make any "return". There is no return that I want other than to do the work to make the game. I thoroughly adore "the process" and not the goal, so when my view shifts to "the goal", I completely lose interest.
More than just mere words
Since that conference and my reflection, I turned around and have nearly completed a game in just 2 weeks. I decided that I'm going to use my Go game engine (instead of my C or C++ one), simply because I want to just write code and make a game. I don't really want to deal with all the technicalities of lower level languages, though I do absolutely love doing so at times. I've done so much low level stuff that I think I'm just ready to make things these days, process and perfection be damned!The game I've been working on for the last 2 weeks is a multiplayer Sudoku game. The game will use PBR rendering, and network code I developed just for the game. I've also developed a master server registry so that players can find each other and utilize UDP NAT hole punching. The primary gameplay loop is playing sudoku, getting a token for playing easy/medium/hard/multiplayer, and unlocking name tags and special player cursors (which show in multiplayer).
I can have a more formal development log for the Sudoku game specifically in another post, but that's basically just me "putting my money where my mouth is" as it relates to these things I've been feeling since that Sony conference.
Where do we go from here?
From here, we make games for the fun of it.Brent - 2025-06-30 22:48:11.885 +0000 UTC
Designing an isometric 2.5D game like Final Fantasy Tactics Advance
One of my favorite game consoles was the Game Boy Advance, particularly the SP version because it had a light-up screen and you could fold it to fit in your pocket. However, I find that I'm enjoying the old form GBA now days, mainly due to being able to mod it to have a back-lit screen, and it's more comfortable to play with.
modded back-lit screen game boy advance, with final fantasy tactics advance
One of my favorite games on the GBA was Final Fantasy Tactics Advance, when I was a kid I picked it up because I liked the artwork on the box. Recently, I've been playing the game anytime I have some down-time and, other than being fun, it has got me thinking about developing a game. Playing through it I started to think about how cool it would be to create a game with that isometric style including using pixel art characters and animations.
Isometric, without tactics
One of the main things I'm thinking about is mostly creating a game with that visual isometric and nostalgic feel. I'm not exactly interested in making a "tactics" style game, to be honest. I thought it would be interesting to be able to freely move around an isometric world, including with the iconic sharp elevations of floor tiles (see below image). I'm thinking of just having the character automatically jump up or down to the next tile, depending on their movement vector.final fantasy tactics advance tile layout
It would also be interesting to see if I could get the world to dynamically load in as you move around, this way there would be no loading screens in the overworld. If I dynamically load the interior of buildings as they are loaded into view, it might also be possible to teleport the player to the building interior instantly upon entering the building door.
The last part of this is that I want to make the world 3D, but the characters and props are going to be 2D sprites. This will make the physical movement around a bit easier, and it will make it easier to place objects within the world. This might give it a bit of a Ragnarok Online feel, though I don't really have an intent to orbit the camera around, it'll likely be fixed but can zoom in/out. Ragnarok didn't have this tactics style isometric floor, but it did have the 2.5D artwork (see below example).
Gameplay
I haven't really worked out the gameplay mechanics, which is odd because that's typically what I start off working out. I know that I like the idea in Final Fantasy Tactics Advance where you place the nodes around on the map, build up your clan, and send them out on missions. It might be cool to anchor to that concept and work out a simple form of combat, possibly like World of Warcraft or Final Fantasy 14, where you designate a target to attack and use abilities while fighting that target. Having clan members, pets, or maybe even friends (through multiplayer) help fight monsters might be a cool way to make the fights more interesting.Game Engine
The game engine I'll be using is the one I've passively developed over many years. It's a C game engine using Vulkan for rendering, though I've ported it over to C++ for integration with any libraries I need and to simplify some things. It's in C++, but it is very much closer to C than C++ if you were to look at the engine, in fact C++ die-hards probably would be disgusted with what I've done here. I am a creature of habit and there are many reasons I primarily use C over C++ in general, but I can make a blog post all on it's own for this topic, I have a lot of opinions on it.Getting started with tiles
The engine I've developed has a lot of stuff already done in general, so really it's just about starting on the game. I've already began doing some experimentation on the floor tiles as they were what initially inspired me. For this, I'm generating each individual floor tile cube directly in code, setting it's UVs, it's height, and it's vertices. In order to make it tile correctly, I'm not using just one large cube with a texture stretched across it, this would be an issue because I wouldn't be able to use a tile-map effectively by doing this. So I've taken the hit on having more vertexes so that I can have an uber tilesheet for the world artwork (instead of many little ones). Below is an example of how I can configure/construct a floor tile in code.Mesh* m = isoland_create_cube(host, IsolandConstructCube{
.height = i+1, // Copied from a loop, thus the 'i'
.tilesetWidth = 4, // Tiles in the tileset image
.tilesetHeight = 4,
.sideTileX = 0, // Side tile image in tileset
.sideTileY = 0,
.topTileX = 0, // Top tile image in tileset
.topTileY = 0,
});
Doing this in a simple loop, we can see how the floor tiles and the tiling textures on them look. Ignore the texture artwork I'm using here, it's something I threw together quickly in Retro Sketch to make it easier to review.Resolution and camera
One of the things I have been trying to work out is if I should render at native (1080p) or if I should render at a smaller resolution and scale up to the window size (without super sampling). This might give it a nostalgic jagged edge (aliased) look that could feed into the retro feel of the artwork, however, it might also may conflict with the pixel-art style characters and props. I may do something in-between, where I don't render to a too low resolution (say just 720p HD) and see how that looks.Example of 320x240:
example of rendering at 320x240
Example of 720x480:
example of rendering at 720x480
Example of full resolution:
example of rendering at full resolution
For the camera, I'm likely going to be rendering in perspective, but with a lower FOV (field of view) to get a somewhat orthographic look without actually using an orthographic camera. I'm not entirely fond of a fully orthographic camera for rendering just because I think some perspective gives it a little more life/character than no perspective at all. As stated previously though, I'm not likely going to allow for the camera to orbit, but I will likely let it zoom in/out a bit for cinematic effect.
What to work on next
Next I'll be laying out a definition file for designing out the world. This will just be a specification that will allow me to set the heights, well, basically it's going to be a heightmap. So perhaps I'll use an image to do this, or perhaps I'll use some other form of custom binary file to specify this.Placing buildings and props should be as easy as specifying their location in the world, the height should be automatically picked up from the heightmap data. This will require a standardization of assets, all of the pivot points of each asset must be at it's base so that it will snap to the floor correctly. However, I will need to also provide the ability to offset from the floor, in case the prop is on top of something or floating in the air for effect.
Timeline and goals
For the most part I'm hoping to keep a shorter timeline, but that is going to be proportional to how much time I can spend on working on the artwork. It would be nice to have the core of the game up and running in about three months. However, artwork takes a very long time, so I'll need to come up with some methods of creating the artwork in an efficient way, and skip out on making things that aren't important to the core game itself. It'd be hard to project out right now at the very beginning of testing things out, but I feel that three months is a good goal; it's how long it took to initially create/release Retro Sketch.As for my goals, it really is just to make a game in my game engine. Retro Sketch's goal was to create some software I can actually use to create games, but also to fully test out my bespoke UI system idea in the engine. Since that was pretty successful, I'd like to use this game as a way of proving out the ability to create games without much friction from the engine. I'm not typically focused much on money/return on the things I create, though nice, it's not exactly my primary goal for creating things.
Brent - 2025-06-14 21:07:34.195 +0000 UTC
Hello everyone! It’s been a little while since we’ve had any updates on the blog, Retro Sketch, or the game engine I’ve been wrestling with. But fear not! I’m getting back into the swing of things, and thought it would be a great opportunity to share what’s been happening.
Life, as it often does, threw a few curveballs recently, which pulled me away from engine development. You know how it is – other exciting projects pop up, demanding your attention! Thankfully, I’ve tackled those life events and distractions, and I’m now laser-focused on games and the engine.
My next step is to start working on another game in the game engine. That means I probably won’t be pushing out updates to Retro Sketch unless a bug pops up, or I need it for a feature while I’ve been developing my game. Don’t worry though – I use Retro Sketch myself for my pixel art, so there will almost certainly be updates related to that!
Of course, diving back into code after a few months can be…interesting. Turns out nothing was compiling or running! I spent the last couple of days wrestling with the engine just to get it building and running again. The good news? The address sanitizer is now fixed and working! That was pretty annoying to be without for a while. Another cool feature I’ve been experimenting with is time travel debugging – a fantastic tool built into Windows when you install WinDbg. With all my cool tools back online, it’s finally time to start developing that new game!
I’ve been making games for over 15 years, and have published quite a few… though not under the “Game Goat” moniker. Previously, I ran a studio called Bearded Man Studios. But that chapter has passed, and most of the amazing people who worked with me have moved on. Those games have faded into obscurity, and they’re sadly built on older versions of Unity, making a resurrection quite the challenge. If I find the time and the desire, I might resurrect them in the new engine someday. But my focus right now is on creating something brand new.
Once I have something genuinely fun and interesting to share, I’ll be posting it here on the blog. For now, I’m overflowing with ideas – but, ideas are cheap. I’m starting to test out some of these concepts, probably sticking with pixel art for the near future. The game engine itself is a 3D engine, and it plays very well with Vulkan. However, creating 3D art and all the assets that go with it takes a lot of time and energy, especially when you’re a bit of a perfectionist (like me!). I want to make simple, fun games, and the easiest way to do that is to embrace some constraints.
Although I am completely comfortable working in 3D and working with 3D game engines, animation rigging, texturing high-poly models, re-topologizing, sculpting and the rest of things that come with 3D. Right now, it just isn’t something I can realistically focus on. Feel free to stay tuned for what I’m working on next.
Brent - 2025-05-18 15:05:00 +0000 UTC
Community game development topic rules
You are free to share the games you are working on. There are a few rules to your games though
1. Explicitly sexual content is not allowed
2. Content that can be considerered a fetish is not allowed
3. Don't spam your games, re-use threads for iterations
4. Be nice to other's games, critique's are fine
General Forum Rules
1. Search things before creating threads/posting https://shieldcrush.com/search2. Be kind
3. No adult content
4. Stay on topic
5. Don't spam
6. Don't advertise
7. Don't abuse text formatting features
Brent - 2025-04-03 20:34:19.175 +0000 UTC
Updated forums formatting, categories, embeddings, and more
We've added a lot of new updates to the forums so that posts can be formatted in more interesting ways. Tables were added so that guide threads are easier to both explain things and read things. We've added embedding of Imgur, Odysee, and YouTube links for more media interactivity as well. You can also select a category for your thread so readers know what they're getting into before clicking. You also can now click on an account image/name to view that person's profile and their latest posts.
List of additions
- Links in forum posts will now be clickable- Added Odysee and YouTube video embedding in forums
- An "Open image" button is added next to image links in forum posts
- Added embedding Imgur albums in forum posts
- Account profiles are now viewable
- Fixed some forum formatting issues on mobile
- Added latest post column to topic view
- Added forum help page
- Added forum topic to top of thread for clarity
- Added ability to search just game topics
- Added table formatting to posts
- Added category drop down when creating a new thread
Brent - 2025-04-02 19:24:57.202 +0000 UTC
You are free to chat about anything related to the Kaiju game engine, our in-house, open source game engine. Talk about your games/applications, show progress, ask for help, or help others. Before you post or reply, there are some rules that must be followed:
1. Don't post anything related to adult content
2. Don't create multiple threads about your game, a single thread will do
3. Don't spam about your game, use your game's thread to talk about it
4. Stay on topic with the thread, don't veer off on unrelated subjects
5. Test code you are about to suggest or clearly explain that it is pseudo-code
General Forum Rules
1. Search things before creating threads/posting https://shieldcrush.com/search2. Be kind
3. No adult content
4. Stay on topic
5. Don't spam
6. Don't advertise
7. Don't abuse text formatting features
Brent - 2025-03-26 21:55:03.807 +0000 UTC