Skip to content

Commit bf89620

Browse files
committed
implement node based approach
Signed-off-by: Fabian Kammel <fabian@kammel.dev>
1 parent aa9ce35 commit bf89620

28 files changed

+913
-6
lines changed

aabb_collider.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package engine
2+
3+
import (
4+
"image/color"
5+
6+
"engine/vec2"
7+
8+
"github.com/hajimehoshi/ebiten/v2"
9+
"github.com/hajimehoshi/ebiten/v2/vector"
10+
)
11+
12+
type AABBCollider struct {
13+
BaseNode
14+
Position *vec2.T
15+
Size *vec2.T
16+
OnCollision *Signal[Node]
17+
DebugMode bool
18+
}
19+
20+
func NewAABBCollider(name string, position *vec2.T, size *vec2.T) *AABBCollider {
21+
collider := &AABBCollider{
22+
BaseNode: *NewNode(name),
23+
Position: position,
24+
Size: size,
25+
OnCollision: NewSignal[Node](),
26+
}
27+
collider.AddTag("Collider")
28+
return collider
29+
}
30+
31+
func (c *AABBCollider) Update() error {
32+
if err := c.BaseNode.Update(); err != nil {
33+
return err
34+
}
35+
36+
if ebiten.IsKeyPressed(ebiten.KeyF10) {
37+
c.DebugMode = true
38+
}
39+
if ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyF10) {
40+
c.DebugMode = false
41+
}
42+
43+
return nil
44+
}
45+
46+
func (c *AABBCollider) Draw(screen *ebiten.Image) {
47+
c.BaseNode.Draw(screen)
48+
49+
if c.DebugMode {
50+
vector.StrokeRect(
51+
screen,
52+
float32(c.Position.X),
53+
float32(c.Position.Y),
54+
float32(c.Size.X),
55+
float32(c.Size.Y),
56+
1,
57+
color.White,
58+
false,
59+
)
60+
}
61+
}
62+
63+
func (c *AABBCollider) CollidesWithAABB(other *AABBCollider) bool {
64+
return c.Position.X < other.Position.X+other.Size.X &&
65+
c.Position.X+c.Size.X > other.Position.X &&
66+
c.Position.Y < other.Position.Y+other.Size.Y &&
67+
c.Position.Y+c.Size.Y > other.Position.Y
68+
}
69+
70+
func (c *AABBCollider) CollidesWithCircle(other *CircleCollider) bool {
71+
// Check if the circle is inside the AABB
72+
if other.Position.X >= c.Position.X &&
73+
other.Position.X+other.Radius <= c.Position.X+c.Size.X &&
74+
other.Position.Y >= c.Position.Y &&
75+
other.Position.Y+other.Radius <= c.Position.Y+c.Size.Y {
76+
return true
77+
}
78+
79+
// Check if the AABB is inside the circle
80+
if c.Position.X >= other.Position.X &&
81+
c.Position.X+c.Size.X <= other.Position.X+other.Radius &&
82+
c.Position.Y >= other.Position.Y &&
83+
c.Position.Y+c.Size.Y <= other.Position.Y+other.Radius {
84+
return true
85+
}
86+
87+
// Check if the AABB is outside the circle
88+
if c.Position.X > other.Position.X+other.Radius ||
89+
c.Position.X+c.Size.X < other.Position.X ||
90+
c.Position.Y > other.Position.Y+other.Radius ||
91+
c.Position.Y+c.Size.Y < other.Position.Y {
92+
return false
93+
}
94+
95+
return true
96+
}

circle_collider.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package engine
2+
3+
import (
4+
"image/color"
5+
"math"
6+
7+
"engine/vec2"
8+
9+
"github.com/hajimehoshi/ebiten/v2"
10+
"github.com/hajimehoshi/ebiten/v2/vector"
11+
)
12+
13+
type CircleCollider struct {
14+
BaseNode
15+
Position *vec2.T
16+
Radius float64
17+
OnCollision *Signal[Node]
18+
DebugMode bool
19+
}
20+
21+
func NewCircleCollider(name string, position *vec2.T, radius float64) *CircleCollider {
22+
collider := &CircleCollider{
23+
BaseNode: *NewNode(name),
24+
Position: position,
25+
Radius: radius,
26+
OnCollision: NewSignal[Node](),
27+
}
28+
collider.AddTag("Collider")
29+
return collider
30+
}
31+
32+
func (c *CircleCollider) Update() error {
33+
if err := c.BaseNode.Update(); err != nil {
34+
return err
35+
}
36+
37+
if ebiten.IsKeyPressed(ebiten.KeyF10) {
38+
c.DebugMode = true
39+
}
40+
if ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyF10) {
41+
c.DebugMode = false
42+
}
43+
44+
return nil
45+
}
46+
47+
func (c *CircleCollider) Draw(screen *ebiten.Image) {
48+
c.BaseNode.Draw(screen)
49+
50+
if c.DebugMode {
51+
vector.StrokeCircle(
52+
screen,
53+
float32(c.Position.X+c.Radius),
54+
float32(c.Position.Y+c.Radius),
55+
float32(c.Radius),
56+
1,
57+
color.White,
58+
false,
59+
)
60+
}
61+
}
62+
63+
func (c *CircleCollider) CollidesWithCircle(other *CircleCollider) bool {
64+
// Calculate distance between centers
65+
dx := c.Position.X + c.Radius - (other.Position.X + other.Radius)
66+
dy := c.Position.Y + c.Radius - (other.Position.Y + other.Radius)
67+
distance := math.Sqrt(dx*dx + dy*dy)
68+
69+
// If distance is less than sum of radii, they are colliding
70+
return distance < (c.Radius + other.Radius)
71+
}
72+
73+
func (c *CircleCollider) CollidesWithAABB(other *AABBCollider) bool {
74+
return other.CollidesWithCircle(c)
75+
}

cmd/adventure/README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Adventure
2+
3+
## Overview
4+
5+
6+
7+
## Mechanics
8+
9+
### Enemies
10+
11+
#### Rare
12+
13+
Glow - itself is strong, should be focus target.
14+
Name should reflect their additional properties.
15+
16+
#### Elite
17+
18+
Aura - buffs nearby enemies, should focus minions first.
19+
Name should reflect their aura properties.
20+
21+
### Exploration
22+
23+
Best items and discoveries should be off the main path.
24+
25+
Teach player early that exploration is optional, but provides huge upsides.
26+
27+
#### Light
28+
29+
No light, no visibility.
30+
This is something a character has to figure out before entering a dungeon.
31+
Ring with light, or a torch, or a lamp, or a spell, ...
32+
33+
### Auto Loot Feature
34+
35+
Consider characters attributes when picking up items.
36+
In multi-character party do a priority consideration of:
37+
+ attributes
38+
+ distance to item
39+
+ inventory space
40+
41+
### Inventory
42+
43+
Space-based vs. weight-based.
44+
45+
Space-based inventory, where backpacks and pouches are different containers with
46+
requirements such as strength, or a mage pouch that requires intelligence.
47+
48+
Interesting item sizes, e.g., 1x6 staff, 3x3 shield, ... could reflect the
49+
characteristics of the specific containers: backpacks, pouches, etc.
444 Bytes
Loading

cmd/gestrandet/enemy.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package main
2+
3+
import (
4+
"engine"
5+
"engine/vec2"
6+
7+
"github.com/hajimehoshi/ebiten/v2"
8+
)
9+
10+
type Enemy struct {
11+
*engine.Entity
12+
}
13+
14+
func NewEnemy() *Enemy {
15+
enemy := &Enemy{Entity: engine.NewEntity()}
16+
17+
image := assetLoader.LoadImage("assets/img/enemy.png")
18+
enemy.Image = image
19+
enemy.ImageScale = 0.5
20+
enemy.Position = &vec2.T{X: 900, Y: 700}
21+
enemy.HitboxSize = &vec2.T{X: 64, Y: 64}
22+
enemy.HitboxOffset = &vec2.T{X: 0, Y: 0}
23+
24+
return enemy
25+
}
26+
27+
func (e *Enemy) Update() error {
28+
return nil
29+
}
30+
31+
func (e *Enemy) Draw(screen *ebiten.Image) {
32+
e.Entity.Draw(screen)
33+
}

cmd/gestrandet/gestrandet.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type Gestrandet struct {
1515
m *Map
1616
gameOver bool
1717
player *Player
18+
enemy *Enemy
1819
world *ebiten.Image
1920
camera *engine.Camera
2021
}
@@ -24,6 +25,7 @@ func (si *Gestrandet) Update() error {
2425
return fmt.Errorf("Game Over")
2526
}
2627
si.player.Update()
28+
si.enemy.Update()
2729
si.camera.FocusOn(si.player.Center())
2830

2931
if ebiten.IsKeyPressed(ebiten.KeyL) {
@@ -50,9 +52,10 @@ func (si *Gestrandet) Update() error {
5052
func (si *Gestrandet) Draw(screen *ebiten.Image) {
5153
si.m.Draw(si.world)
5254
si.player.Draw(si.world)
55+
si.enemy.Draw(si.world)
5356
si.camera.Render(si.world, screen)
5457

55-
ebitenutil.DebugPrint(screen, fmt.Sprintf("TPS: %v, FPS: %v", ebiten.CurrentTPS(), ebiten.CurrentFPS()))
58+
ebitenutil.DebugPrint(screen, fmt.Sprintf("TPS: %v, FPS: %v", ebiten.ActualTPS(), ebiten.ActualFPS()))
5659
ebitenutil.DebugPrintAt(screen, si.camera.String(), 0, 20)
5760
mouseInfo := fmt.Sprintf(
5861
"OnScreen: %v, World: %v",
@@ -72,6 +75,7 @@ func NewGestrandet() *Gestrandet {
7275
m: NewMap(),
7376
gameOver: false,
7477
player: NewPlayer(),
78+
enemy: NewEnemy(),
7579
}
7680
gestrandet.camera = engine.NewCamera(vec2.NewI(gestrandet.Layout(0, 0)).AsT())
7781
gestrandet.world = gestrandet.m.mapLoader.Generate()

cmd/goinvaders/enemy.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func newEnemy1Animation() *engine.Animation {
2929
assetLoader.LoadImage("assets/img/spritemap.png"),
3030
vec2.I{X: 64, Y: 48},
3131
[]vec2.I{{X: 0, Y: 0}, {X: 1, Y: 0}},
32-
[]time.Duration{time.Millisecond * 500, time.Millisecond * 500},
32+
engine.UniformDuration(time.Millisecond*500, 2),
3333
)
3434
}
3535

@@ -38,7 +38,7 @@ func newEnemy2Animation() *engine.Animation {
3838
assetLoader.LoadImage("assets/img/spritemap.png"),
3939
vec2.I{X: 64, Y: 48},
4040
[]vec2.I{{X: 3, Y: 0}, {X: 2, Y: 0}},
41-
[]time.Duration{time.Millisecond * 500, time.Millisecond * 500},
41+
engine.UniformDuration(time.Millisecond*500, 2),
4242
)
4343
}
4444

@@ -60,7 +60,11 @@ func NewEnemy(position *vec2.T, enemyType EnemyType) *Enemy {
6060

6161
enemy.Image = enemy.animation.CurrentImage()
6262
enemy.Position = position
63-
enemy.HitboxSize = vec2.NewI(enemy.Image.Size()).AsT()
63+
enemy.HitboxSize = vec2.NewI(
64+
enemy.Image.Bounds().Size().X,
65+
enemy.Image.Bounds().Size().Y,
66+
).AsT()
67+
6468
return enemy
6569
}
6670

cmd/node/assets/img/green.png

897 Bytes
Loading

cmd/node/assets/img/red.png

444 Bytes
Loading

cmd/node/assets/img/square.png

158 Bytes
Loading

0 commit comments

Comments
 (0)