game design, code, writing, art and music

English По-русски

How I make games in my own 3D game engine

I originally started out making small Flash games on Newgrounds many years ago. Now I'm making much bigger indie games for PC. So far I've released 3 commercial titles on Steam and plan to release my 4th game Pilie Pals in October.

Pilie Pals is a puzzle game where the player controls a group of cute creatures that can stack and carry each other
Pilie Pals is a puzzle game where the player controls a group of cute creatures that can stack and carry each other

I do all the programming, art, writing, sound design and music by myself. I enjoy it, and this way I can switch between different kinds of tasks and not get bored of just doing one thing for a long period of time.

I've written my own custom 3D game engine using Haxe, C++ and OpenGL, which I've been using for 3 of my projects so far. If you'd like to know why, I've listed some of my reasons in this post. So far I'm pretty happy with my workflow and don't plan to change it.

The development of Pilie Pals

I've been working on my latest puzzle game Pilie Pals for about 6 months now, in the evenings after work and on the weekends. In this post I'll try to give a breakdown of my development process for this game.

But first, a trailer:

There's a playable demo available, and the game now has a Steam page. Go add it to your wishlist if you're into puzzles!

The first thing I did when creating Pilie Pals (even before I had any idea what the game is going to be) was clone the project folder of my previous game Phantom Path and remove all the game related assets and code. All that was left was a sandbox that I could play around in, prototyping new ideas and experimenting with the results.

Before going through the development timeline, I'll explain some basic concepts of my engine.

Storing game data in text files

My engine is mostly data-driven, which means that I create different text files to describe game objects, and the engine reads and processes them at runtime.

I try to avoid hardcoding stuff in the game's source code itself. I store as much as I can in external files - game entities, particle emitters, soundscape descriptions, level progression maps, text translations, and even some game logic (written in the engine's own scripting language). Most of the data is stored in JSON format, because there's no need to over-complicate things.

The best thing about this approach is that I can make and tweak the game as I'm playing it. Whenever I change one of these data files in a text editor, the engine detects that and reloads it immediately, without me having to recompile or even restart the game. It also does the same thing for other assets, like textures, models and sounds.

Another advantage of this approach would be potential mod support - not something that's relevant for Pilie Pals specifically, but it's an area I'd like to explore in the future.

Entities

Two main components of a game scene in my engine are entities and maps.

An entity is a game object that I describe in a JSON file (called an entity descriptor) and can later create many instances of in the game. For example, the player's character is an entity. An entity descriptor contains information like:

  • What 3D models the entity has and how they should be rendered
  • Which animations the models can perform and how they interpolate
  • What particle emitters the entity can trigger
  • What sounds the entity can trigger
  • What collision boxes the entity has
  • What states the entity has, and how it behaves in each of those states
  • Other data that can be used in the game logic - special tags, render groups, etc.
Every character, tree, crate, gameplay element and decoration prop is a separate entity
Every character, tree, crate, gameplay element and decoration prop is a separate entity

It's worth noting that each spawned entity can have its own state machine, and each state has a linear sequence of performance actions that the entity can play.

For example, I can define a state "walk" for my player character entity, and the performance sequence would contain commands that trigger the walking animation of the model, play footstep sounds at a given 3D position and spawn dust particles under the character's feet. All of this is defined in a text file, which I can edit and preview immediately, and this greatly speeds up the development process.

Maps

A map is a data file that contains instances of entities. It also has a multi-level 3D tilemap, which I use for blocking out the terrain. I do not create map files by hand - the engine has a map editor for that.

The internal map editor is used for editing all of the game's levels
The internal map editor is used for editing all of the game's levels

Because the engine knows a lot about the entities it needs to display, there are some optimizations that can be done automatically. For example, if an entity is static and never moves (e.g. a tree or the ground), the engine automatically groups it together with all other static entities that use the same texture, and merges them all into one big static model. This significantly reduces draw calls and improves the performance of the game.

Game logic

Some of the game logic can be scripted in text files, but it's only used for creating game scenarios and not the actual core functionality. Things like collision detection, AI behavior, gameplay rules, etc. are programmed in game's source code in Haxe, which is turned into C++ at compilation.

I have two main categories of logic-related code files:

  • Entity logic processors - these can be attached to entities, and process entity-specific logic, like AI behavior. Not every entity needs a logic processor.
  • Logic ticker - a single class that defines gameplay rules that are applied on every frame.

First month: The prototype

I had an idea about a turn-based puzzle game, where the player controls multiple characters that can carry objects around. The game had to be turn-based internally, because I wanted to record a history of the game, so that the player would be able to undo their previous steps.

I started by creating a new logic ticker and implementing a step-based state machine. Each dynamic entity that is part of the puzzle gameplay is marked as "puzzle element", and the ticker assigns a state to each of them.

The player can take control of a character and perform an action (e.g. walk around, or pick an object up) to change the world's state. The game then checks if the new world state is valid (e.g. the player hasn't walked into an obstacle), then checks if any reactions are needed from other elements of the world (e.g. if a button needs to change its state to "pressed" if an object is placed on top of it). If everything's fine - the state changes are committed to history.

The player can then undo steps by simply reverting the recent state changes.

Each puzzle element has a state
Each puzzle element has a state

That's the basic functionality of the gameplay engine. It's actually a bit more complicated than that, because I need to account for smooth transitions between states, allow elements to be carried by other elements, and do other interesting things - but all these features are added on top of the existing generic foundation.

Implementing this kind of system and all its edge cases took me about a week. The rest of the time was spent figuring out what kind of game I wanted to make exactly. This is when I got the idea to let characters carry other characters and be able to create infinite stacks of each other.

I made 4 levels of the game using my existing map editor to see if the gameplay was fun. Fortunately, it was, so I continued.

Second month: The visuals

Now that I had a playable prototype, I started experimenting with different art directions. I settled on a colorful "toy" art style, and spent the month creating 3D models, animations, particle effects, UI, transitions, etc. The game is split into 5 worlds, each with its own theme.

A stage from World 4 of Pilie Pals
A stage from World 4 of Pilie Pals

I use Blender for creating 3D models and animating them, and GIMP for texturing.

I've achieved the plastic look by writing a shader that applies pre-rendered lighting data to models. It works by taking an image of a pre-rendered sphere with lighting data, and then applying it to some parts of a model by mixing it with the diffuse data based on the model's normals in screen space.

I got the idea for this concept after seeing how 3D modeling/sculpting software render untextured models in MatCap mode. My approach is to combine this technique with traditional real time rendering methods. It's really fast to render and produces a good result.

Before and after applying pre-baked lighting data to models
Before and after applying pre-baked lighting data to models

Third month: Polishing

An entire month was spent working on improving the user experience - making sure all transitions are smooth, controls are responsive, graphics are clean, and the UI is easy to navigate.

I've implemented a pause menu, stage select screen, progression system and save files - most of this functionality I had already made in my previous games, so I could re-use some of the code and just tweak the visuals.

A world map, where the player selects which stage to play
A world map, where the player selects which stage to play

Working on interfaces is pretty tedious, but bad UX is the first thing that can potentially annoy players, so it's important to get it right from the start.

My approach here was to make a polished vertical slice as soon as I could. This way I would see what the final project was going to look and feel like, and still be able to tweak it relatively painlessly, because there wasn't much content in the game yet.

Fourth month: Music, sounds, and the demo

I make music and sound effects in a virtual modular synth called SunVox. I'm self-taught and it always takes me a long time to make a track.

A music track from Pilie Pals in SunVox
A music track from Pilie Pals in SunVox

The game also uses the engine's soundscape feature, which procedurally generates ambience by combining pre-defined sounds, pitch-shifting them, and playing them back in random positions and patterns. The pattern types, source sounds and other information is described in an external JSON file.

At this point I had a fully polished game with 4 levels. I've designed 6 more levels and released a public demo of the game, which looked and felt like the final product.

This is when I started gathering feedback from players and improving the user experience of the game.

At around this time I also added a hint system to the game. I've written about it in detail here.

Fifth and sixth months: Game content

The next 2 months were spent adding more levels to the game with new graphics and game elements, accomplished entirely using the entity system I've described earlier. There were almost no changes to the source code at this point, because all of the game systems had already been done by then.

I was mostly working in the game's map editor during this time.

A game stage in the map editor
A game stage in the map editor

Finishing touches

The game is mostly finished now!

I'm going to spend the remaining time adding more music tracks to the game, integrating Steam achievements, and putting the finishing touches here and there.

Lessons learned

In general, I'm quite happy with how Pilie Pals is turning out.

I had a pretty clear idea of what kind of game I wanted to make very early in the development, which helped the process go much smoother and faster than it usually does. This made me realize the importance of having a clear goal from the start.

It also turned out to be a really good idea to get a polished vertical slice done as soon as possible. This way I could start making screenshots, videos and even provide a good quality public demo after only 4 months of work. I've got some useful feedback from the players before most of the content was done, so it was easier to make changes without breaking things.

One of the early levels of Pilie Pals
One of the early levels of Pilie Pals

I spent barely any time at all changing any of the core engine code, focusing almost entirely on game content this time around. I'll continue using my own custom game engine in my future projects too.

Please add Pilie Pals to your wishlist!

Pilie Pals is planned to come out on Steam in October. It would help me out a great deal if you added it to your wishlist!

Update: Pilie Pals is now available!

Next Article

Pilie Pals is now available!

Subscribe

Receive a notification on your device whenever there's a new blog post available, in order to:

Follow the development process of my next game.

Get notified of articles about the art and tech of designing games and engines.

Receive updates about changes to my games.

Subscription is free and takes just one click!