DevBlog — Object Oriented Structure
In this blog post, I’ll write a bit about how I grabbed the concept idea and deconstructed it into smaller blocks I could tackle, leveraging the good old Object-Oriented Programming concepts.
As I stated in the previous DevBlog , the goal here is to recreate the Connor’s Game of Life, with the added aspect of puzzle elements. So, if I look at the original game as-is, what kind of elements I can identify ?
- A Board where the simulation happens
- A Cell that can be in one of two states : Alive or Dead
Simple enough. Two “classes” (scenes, in Godot terms). One Board is composed of multiple Cells and is responsible for the simulation logic and each Cell knows its own state.
For the Cell scene, it looks something like this :
A simple StaticBody2D plus CollisionShape2D (with a square shape) combo to visually represent the Cell.
As for color, I added two Sprite2D that I toggle between to represent the different states. The sprites themselves are basic GradientTexture2D with a single color, because I didn’t want to loose too much time with the visuals of this prototype.
For the toggle itself, the code was pretty straightforward too:
extends StaticBody2D
var _state: bool
func _ready() -> void:
toggle(false);
func toggle(state: bool) -> void:
_state = state
$LightSprite.visible = !state;
$DarkSprite.visible = state;With this snippet, each cell I create starts in the “Dead” state and shows the Light color sprite. Any any point, I can use the toggle function to swap between the two states.
As for the Board entity, I decided to try a different approach.
Instead of instancing each cell individually (which is bothersome), I decided to search how to instantiate other Godot scenes in runtime. Apparently … not that hard.
extends Node2D
@export var CellTemplate: PackedSceneThis makes it so I can set up in Godot’s editor a Scene that I want to be able to instantiate in code. Which I then pick up the Cell scene.
Now, I need two things : A way of creating multiple Cells and place them side by side, to “fill” the square board I want and a way of updating them every second or so to their new state.
Get Apprentice Codex’s stories in your inbox
Join Medium for free to get updates from this writer.
To “fill”/populate the board, I will need a function that creates rows/columns of cells evenly spaced visually. To simplify a bit, I’ll assume only square configurations (same width/height), but the function should be easy enough to adapt should be needed.
var boardMatrix = []
var cellLookup = []
func init_board(length: int):
var startingPos = get_starting_pos()
for x in range(length):
boardMatrix.append([])
boardMatrix[x]=[]
for y in range(length):
boardMatrix[x].append([])
boardMatrix[x][y]=create_cell(startingPos)
cellLookup.push_back([boardMatrix[x][y], [x, y]])
startingPos = startingPos + Vector2(0, cellSize)
startingPos = startingPos + Vector2(cellSize, -cellSize*length)
func get_starting_pos() -> Vector2:
var offset = floor(boardSize / 2.0 * cellSize)
return Vector(0, 0) - Vector2(offset, offset)
func create_cell(pos: Vector2):
var cell = CellTemplate.instantiate()
cell.position = pos
cell.scale *= int(cellSize / floor(16)) #shrink/grow the cell size. default 16
add_child(cell)
return cellIn terms of approach, nothing too fancy here.
- Two basic for cycles to create the row / column logic.
- The first cycle is responsible to create new rows, while the second one creates the columns. All stored in the
boardMatrixvariable. - The
create_cellfunction just uses the Cell template to create a new Cell instance, sets it's position and adds it to Godot's scene tree using theadd_childmethod. - Every for cycle updates the
startingPosvariable so the spawning position of each new Cell gets correctly adjusted
The “trick” here is in the startingPos variable. In each column I can easily increment (as in, move right) by summing a new Vector with only the y portion set (in this case, set to the cell size). At the end of each row, however, I can increment the vector with a positive value in the x portion (making it move down) and negatively set the y portion so it "magically" moves left, creating the board effect. Good thing that, in GDScript (Godot's own language), playing with Vectors is very easy.
As for the simulation portion, I’ll include the relevant snippets below. It’s nothing more than just following the Connor’s Game of Life rules and implement 1 to 1 (or just search online for a basic implementation and toy with it a bit)
var _refreshTimeS:float = 1 #change this value to speed or slow down the simulation
var _refreshDeltaS:float = 0
# typical milis implementation to achieve a "every T seconds, do this" logic
func _process(delta: float) -> void:
_refreshDeltaS += delta
if (_refreshDeltaS < _refreshTimeS):
return
_refreshDeltaS = 0
refresh_board(stateMachine.board.boardSize)
# a function that, for a given cell's current state and it's neighbors, returns the next state
func get_next_state(_current_state:bool, _neighbors: Array[bool]) -> bool:
var aliveN = _neighbors.count(true)
if (_current_state == true && aliveN >= 4): return false
if (_current_state == true && aliveN <= 1): return false
if (_current_state == false && aliveN == 3): return true
return _current_state
# the function that navigates through the board matrix to calculate the next simulation state
# using a temporary matrix to ensure I dont override any cell's value too early, as to not mess up the simulation results
func refresh_board(length: int):
var nextBoardMatrix = []
for x in range(length):
nextBoardMatrix.append([])
nextBoardMatrix[x]=[]
for y in range(length):
nextBoardMatrix[x].append([])
var currentCell =boardMatrix[x][y]
var neightbors = get_neighbors(x, y, 1)
nextBoardMatrix[x][y] = get_next_state(currentCell._state, neightbors)
for x in range(length):
for y in range(length):
boardMatrix[x][y].toggle(nextBoardMatrix[x][y])There is a lot of room for improvement (and I will tackle some of those later on in this mini series), but it gets the job done.
If all is wired up properly, we should now have a simple board (I set mine to 5 in size, but you can play around with this value plus the cell’s size)
As for the “starting” positions, I added the following bits on the Board logic
func get_cell_at(x: int, y: int) -> Node2D:
return boardMatrix[x][y]
func _ready() -> void:
init_board(5)
# ....
# use this on any valid position and try out different patterns
get_cell_at(2, 2).toggle(true)
get_cell_at(1, 2).toggle(true)
get_cell_at(0, 2).toggle(true)
get_cell_at(4, 1).toggle(true)
get_cell_at(4, 2).toggle(true)
get_cell_at(4, 3).toggle(true)However, to have a “proper” (in the scope of this prototype) game, we might need a few more elements. As a puzzle inspired game, I will want to have different “levels” and a way to pick which one I want to try and solve, a few buttons to interact with the level itself and a way of navigating back and forth.
With all this in consideration, how many elements do I need exactly ?
- A Level that contains the Board, responsible to load the initial state and the “win condition”
- A Level selector, a screen that allows the player to pick which level to tackle
- A Main entity that, as the name suggests, I will be using as the game’s “entry point”
Luckily, I don’t need to implement all this in one go. I can focus on implementing one of these and keep on improving, both incrementally (by adding more elements) and iteratively (by going back and expand existing elements as I need)
On a future DevBlog, I will write about how I created each new entity and wired everything together, taking the opportunity to explore Godot’s events (signals) and one of my favorite concepts : State Machines









