A top down panzer game written in Go.
see 7e08f530efb0aae9367d4a122715c353ebed2f8e
We have introduced collision detection and resolution systems. The former marks entities as having collided, using AABB 2d collision detection. The latter system is responsible for resolving the collision. At present we only look a velocity and make the colliding entity bounce back.
entities := s.EntityManager.FindByComponents(components.CollisionType)
for _, e := range entities {
collision := e.GetComponent(components.CollisionType).(*components.Collision)
if collision.Target.HasComponent(components.VelocityType) {
v := collision.Target.GetComponent(components.VelocityType).(*components.Velocity)
v.Intertia = -1 // bounce back
}
e.RemoveComponent(components.CollisionType)
}
see a5bc3050ed29510dca9ceca1f3b589e18385e414
Yeah, we got some particles on the screen!
To create an emitter entity attach the emitter components
// see app/particles/main.go
redEmitter := p.EntityManager.NewEntity()
redEmitter.AddComponent(&components.ParticleEmitter{
Color: lib.ColorRed,
Lifetime_min: 10,
Lifetime_max: 100,
Velocity_min: 1,
Velocity_max: 1,
Direction_min: 0,
Direction_max: 90,
})
redEmitter.AddComponent(&components.Transform{X: ... })
see 37193f42f3c4e66f7140bc9ea7614b6e01a35808
We implemented relative positioning of entities this evening. It is basically building a tree of transform components. With each entity only having max one transform and one transform only having one parent you get a scene graph.
To demonstrate relative positioning we decided to build a planet demo. As a bonus each planet as an emitting light attached to it.
// see app/position/main.go
//...
sun := s.EntityManager.NewEntity()
sunTransform := components.Transform{}
sun.AddComponents(&sunTransform, ... )
earth := s.EntityManager.NewEntity()
earthTransform := components.Transform{OffsetX: 300, OffsetY: 0}
earthTransform.AddParent(&sunTransform)
moon := s.EntityManager.NewEntity()
moonTransform := components.Transform{OffsetX: 150, OffsetY: 150}
moonTransform.AddParent(&earthTransform)
see e1a38829ba41fd49d4c6c1dfdd1aefb79291aea4
The first action has landed. Loading sprites and rendering a list of images.
see 5836834a79dc1b6507f941bedf472cae370f60b2
We finaly have some color dynamics in the game. Basically we draw every lighting source into a texture and then use a shader to merge everything.
package main
var AmbientColor vec4
func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
// ( color of lightingmap + ambient color ) * destination color
return (imageSrc0UnsafeAt(texCoord) + AmbientColor) * (color)
}
In the there are 3 lights in the scene. Pointlights on top of the barrel and the crate. In the right bottom corner there is a circle light which is bigger and more diffuse. To demonstrate the different effects you can achieve with different ambient light colors, we cycle through some color variations ...
see de790ae4dd2528046a20e21b7da82968f84523cd
This month we managed to draw the map, introduced ZIndex to the SpriteRenderSystem and added the first AI behaviour. A Tank rotates in the direction of the player.
see 4ef22abd3572bcd0eacb827181e0e58b160c6699
Finally some action on the screen. The tank is now keyboard controllable and moves across the screen.
see 08892283bc67aabc710d610a02a6155b8704f25a
Today we implemented a basic ECS system placing a tank entity on the world, drawing a sprite and making the tank shake.
The generic ECS part can be found at /lib/ecs/*
and the actual systems and components of tankism at /game/ecs/*
.
Instead of having each game object being responsible for drawing itself and updating the game logic, there are systems that are responsible for the behavior. The data is stored in components. And the entities are a means of grouping components. This separation of concerns allows to decouple systems from other systems and entities from other entities. This is also an example of data-oriented design.
Below is the simple sprite render system:
type SpriteRenderer struct {
EntityManager ecs.EntityManager
}
func (s *SpriteRenderer) Update() error { return nil }
func (s *SpriteRenderer) Draw(screen *ebiten.Image) {
entities := s.EntityManager.FindByComponents(components.SpriteType, components.TranslateType)
for _, e := range entities {
op := &ebiten.DrawImageOptions{}
translate := e.GetComponent(components.TranslateType).(*components.Translate)
x := translate.X
y := translate.Y
scale := translate.Scale
op.GeoM.Scale(scale, scale)
op.GeoM.Translate(x, y)
sprite := e.GetComponent(components.SpriteType).(*components.Sprite)
img := sprite.Image
screen.DrawImage(img, op)
}
}
see 360d4bac97ead6067e44a4f016ca182cae33db35
Tankism is a big for-loop. In order to put some structure to the code, we decided to have individual scenes managing certain aspects of the computer game. The Start-Scene is responsible for loading assets and the Game-Scene is responsible for managing the actual game.
Our initial thought is something along the lines:
see 36813f5b94e36e06781b702731a05ff17800f7d1
Structuring go apps is a bit different than say Java packages. We tried to follow Bil Kennedy's advice of package-oriented design and came up with the following folder structure.
The main goal was to make the dependencies visible in the code utilizing the folder structure. Dependencies point downwards.
app/
├─ client/
│ ├─ gamescene/...
│ ├─ startscene/...
│ ├─ main.go
├─ server/
│ ├─ main.go
game/
├─ objects/
│ ├─ ...
│ ├─ tank.go
├─ ui/
│ ├─ ...
│ ├─ exitbutton.go
├─ colors.go
lib/
├─ ecs/...
├─ ui/...
├─ gameobject.go
media/
├─ images/...
├─ sounds/...
More about it at packagestructure
see 43bf36088d9817475df42d7d8e318ac6970776e8
After more than 3 years of being dormant, we revived the idea of writing a top-down tank shooter. This time using the ebiten library. In our first remote session we managed to create an empty window :-)
go install
go test ./...
go run cmd/tankism/main.go
Thank you https://kenney.nl for the amazing sprites and tilesheets: https://kenney.nl/assets/topdown-tanks-redux