In our upcoming Knight vs Trolls game, our heroic knight will be wielding a sword which he uses to defeat foul trolls. Let’s think about how to implement this. We want the sword to be an independent GameObject, different than the knight. We need this for a few reasons. One is that we want the sword to have the freedom to move independently from the knight. We want, for example, to be able to rotate the sword to simulate a sword swing but we don’t want to rotate the knight. We might also want to allow the sword to be dropped on the ground if our knight gets hit by a troll and we want to let our knight change swords by spending coins. The sword’s position will be near the knight’s arm. As the knight moves the sword moves as well but since the two are different objects we need a way to apply the knight’s movement to the sword.
One solution is to give the sword GameObject a reference to the knight GameObject. This way the sword can read the knight’s position and update its own position accordingly.
type Sword struct{
GameObjectCommon .Sprite
Sprite sprite*Knight // the knight holding this sword
Knight }
We add a small offset to make the sword appear near the knight’s hand otherwise it would appear dead center on the knight’s center. We can still add code to rotate the sword and it won’t affect the knight’s rotation.
func (s *Sword) Update(){
:= math.Vector3[float32]{-5, 5, 0}
offset .Translation = t.Knight.GetTranslation().Add(offset)
s.Rotation = ... // some code to rotate when swung
s}
We can use the same solution to give Trolls swords (or clubs, or spears). To give our player two swords, one on each hand, we would do the same and change the offset for each sword.
When the trolls hit the knight, the knight has a chance to drop
his sword. Then the trolls rush to pick up the sword and our hero
must get it first or bad things ensue. Since both trolls and the
knight can hold the sword we must update our reference on the
sword object. Ae generic GameObject
reference covers
both. The sword’s Holder
can be either a knight or a
troll since both are GameObjects
.
type Sword struct{
GameObjectCommon .Sprite
Sprite sprite*GameObject // the one holding this sword
Holder }
We are ready code everything when suddenly inspiration strikes and we come up with a new exiting game feature: trolls with shields. No problem, we can use the same design as the sword.
type Shield struct{
GameObjectCommon .Sprite
Sprite sprite*GameObject // the one holding this shield
Holder }
But there is a twist. Our hero can attack shield-carrying trolls with the sword and disarm them. For a bit of extra fun, the shield (which is crudely made out of wood) gets stuck on the sword and drops after the knight swings the sword a few times. Very fun, but how do we code this?
We don’t really need to code much it turns out. If we make the
knight’s sword the Holder
of the shield we are good
to go. The knight holds the sword and the sword ‘holds’ the
shield. Updating the stuck shield is still done by the same code
as before:
func (s *Shield) Update() {
.Translation = t.Holder.GetTranslation().Add(s.offset)
s}
This will use the sword’s translation as basis and then add a
small cosmetic offset. This way the shield moves as the sword
moves. The sword’s update is the same but in it’s case the
Holder
is the knight. So as the knight moves, so does
the sword and in turn so does the shield. We could mount our
knight on a horse and have arrows be stuck on the shield the same
process would make everything work as expected.
In our example, the Holder
references create a
hierarchical tree relationship between objects. Lets consider a
knight wielding two swords, and one of the swords has a shield
stuck to it. The shield itself has blocked three arrows whose tips
are now buried in the shield. This is a diagram of this situation.
The white arrows represent Holder
references.
Organizing game objects in tree structures is very common in games and graphical applications in general. It’s so common and useful that many game engines support it out of the box, unlike here where we had to code it into our game objects. It makes sense for us to generalize our code to all game objects but before we do that we must address a problem with our solution: The sword knows who holds it but the knight doesn’t know what they are holding! This is problematic for many reasons. For example, we don’t have a way for our knight to swing or drop the sword, the sword must detach itself from its holder which is an counterintuitive way to code.
The solution is easy, we store a reference to the sword on the knight. And, because the knight can hold many items, lets make that a list of references.
type Knight struct{
GameObjectCommon .Sprite
Sprite sprite[]*GameObject
Holds }
Tree hierarchies are very useful in games so we will generalize
the structure we created for knight and the swords to our
GameObject
. Instead of calling our object references
Holder
and Holds
we will use the more
generic Parent
and Children
. Our
GameObject
interface will now have methods to set
these references.
type GameObject interface {
()
Update(*sprite.Renderer)
Render
() math.Vector3[float32]
GetTranslation() math.Vector3[float32]
GetRotation() math.Vector2[float32]
GetScale
(math.Vector3[float32])
SetTranslation(math.Vector3[float32])
SetRotation(math.Vector2[float32])
SetScale
// new
() GameObject
GetParent(GameObject)
SetParent() []GameObject
GetChildren(g GameObject)
AddChild}
As we did with the other GameObject
methods, we
will create default implementations for these methods in the
GameObjectCommon
struct.
type GameObjectCommon struct {
.Vector3[float32]
Translation math.Vector3[float32]
Rotation math.Vector2[float32]
Scale math
[]GameObject
Children
Parent GameObject}
func (g *GameObjectCommon) AddChild(gg GameObject) {
.SetParent(g)
gg.Children = append(g.Children, gg)
g}
func (g *GameObjectCommon) GetChildren() []GameObject {
return g.Children
}
func (g *GameObjectCommon) GetParent() GameObject {
return g.Parent
}
func (g *GameObjectCommon) SetParent(parent GameObject) {
.Parent = parent
g}
Notice that when a child game object is added the implementation also sets itself as the parent of that child.
In the previous tutorial we kept all game objects in a list.
Our game loop was to iterate over the list of game objects and
call their Update
and Render
functions.
:= []GameObject{}
gameScene = append(gameScene, Knight{})
gameScene = append(gameScene, Troll{})
gameScene = append(gameScene, Troll{})
gameScene = append(gameScene, Troll{})
gameScene
// Game loop
for {
for gameObject := range scene {
.Update()
gameObject.Render(renderer)
gameObject}
}
Let’s formalize this. We will define Scene
, an
object that holds GameObjects
. A scene can be a level
in a game or a portion of a level like a room that is loaded
independently. Only one scene is active (updating and rendering)
at any time.
type Scene struct {
[]GameObject
gameobjects }
In our tree hierarchy, a scene is the root of the tree. It has
children but no parent. Updating and rendering a scene is not as
simple as looping over the GameObjects
of the scene
because those GameObjects
might have children. We
need a way to traverse the tree structure that we have created.
Traversing a tree is a well known operation. We will use a depth
first search which we defined as:
func depthFirst(g GameObject, fn func(g GameObject)) {
(g)
fnfor _, v := range g.GetChildren() {
(v, fn)
depthFirst}
}
This function calls a user-supplied fn
function on
the game object and then loops over the object’s children and
calls depthFirst
on them 1. The fn
function
is any function we want. To Update
all game objects
we call depthFirst
and pass a function that calls the
GameObject.Update()
method.
func (s *Scene) Update() {
:= func(g GameObject) {
fn .Update()
g}
for _, v := range s.gameobjects {
(v, fn)
depthFirst}
}
If you haven’t used depth-first traversal before, I recommend
you try to go through this code, perhaps with pen and paper, using
the example diagram with the knight. Try to record the order in
which each object is updated. Then, as an exercise, try the same
with this version of depthFirst
.
func depthFirst(g GameObject, fn func(g GameObject)) {
for _, v := range g.GetChildren() {
(v, fn)
depthFirst}
(g)
fn}
To render we do the same but the fn function call
GameObject.Render()
.
func (s *Scene) Render(r *sprite.Renderer) {
:= func(g GameObject) {
fn .Render(r)
g}
for _, v := range s.gameobjects {
(v, fn)
depthFirst}
}
With Update and Render covered, our scene update simply becomes:
:= makeAScene()
scene for {
.Update()
scene.Render()
scene}
Depth first traversal ensures that parent game objects will be updated before their children. This is desirable as children often depend on parent state for their own updates. If the knight moves, the sword’s update will need the latest position of it’s parent to update itself. If we updated the child before the parent we would create a small visual bug where the sword would lag behind it’s holder. Whether this is noticeable or not depends on the frequency of our updates (framerate) and the movement speed of the knight. Fast movement with slow framerate would be the most noticeable.
The order of updates is more important for gameplay updates. For example, if the knight dies, their sword should stop swinging and fall to the ground. If not, the knight might be able to defeat trolls postmortem. Again, not a catastrophic bug, but imagine that defeating the last troll triggered a change to the next level. This sequence would change levels with our character dead! Having a consistent and known order of game objects does not automatically eliminate this type of bug but it does make it easier to track down and fix.