ichigo/engine/actor.go

108 lines
2 KiB
Go
Raw Normal View History

2021-08-02 15:16:58 +10:00
package engine
2021-08-03 14:56:53 +10:00
import (
"encoding/gob"
2021-08-27 13:56:50 +10:00
"fmt"
2021-08-03 14:56:53 +10:00
"image"
)
2021-08-18 16:34:51 +10:00
// Ensure Actor satisfies interfaces.
2021-08-27 13:56:50 +10:00
var _ interface {
Bounder
Prepper
} = &Actor{}
2021-08-20 15:01:31 +10:00
2021-08-03 14:56:53 +10:00
func init() {
2021-08-25 15:04:38 +10:00
gob.Register(&Actor{})
2021-08-03 14:56:53 +10:00
}
2021-08-02 15:16:58 +10:00
// Thorson-style movement:
// https://maddythorson.medium.com/celeste-and-towerfall-physics-d24bd2ae0fc5
2021-08-04 15:07:57 +10:00
// Actor handles basic movement.
2021-08-02 15:16:58 +10:00
type Actor struct {
2021-08-05 12:26:41 +10:00
CollisionDomain string
Pos image.Point
Size image.Point
2021-08-02 15:16:58 +10:00
2021-08-05 12:26:41 +10:00
collisionDomain interface{}
xRem, yRem float64
2021-08-02 15:16:58 +10:00
}
2021-08-27 13:56:50 +10:00
func (a *Actor) BoundingRect() image.Rectangle { return image.Rectangle{a.Pos, a.Pos.Add(a.Size)} }
2021-08-04 15:07:57 +10:00
func (a *Actor) CollidesAt(p image.Point) bool {
2021-08-27 13:56:50 +10:00
bounds := image.Rectangle{Min: p, Max: p.Add(a.Size)}
return nil != Walk(a.collisionDomain, func(c interface{}, _ []interface{}) error {
2021-08-20 15:01:31 +10:00
coll, ok := c.(Collider)
if !ok {
return nil
}
2021-08-27 13:56:50 +10:00
if coll.CollidesWith(bounds) {
return Collision{With: coll}
2021-08-03 14:56:53 +10:00
}
2021-08-20 15:01:31 +10:00
return nil
2021-08-03 14:56:53 +10:00
})
}
2021-08-02 15:16:58 +10:00
func (a *Actor) MoveX(dx float64, onCollide func()) {
a.xRem += dx
2021-08-05 14:26:23 +10:00
move := int(a.xRem + 0.5) // Note: math.Round can lead to vibration
2021-08-02 15:16:58 +10:00
if move == 0 {
return
}
a.xRem -= float64(move)
sign := sign(move)
for move != 0 {
2021-08-05 12:26:41 +10:00
if a.CollidesAt(a.Pos.Add(image.Pt(sign, 0))) {
2021-08-02 15:16:58 +10:00
if onCollide != nil {
onCollide()
}
return
}
2021-08-05 12:26:41 +10:00
a.Pos.X += sign
2021-08-02 15:16:58 +10:00
move -= sign
}
}
func (a *Actor) MoveY(dy float64, onCollide func()) {
a.yRem += dy
2021-08-05 14:26:23 +10:00
move := int(a.yRem + 0.5)
2021-08-02 15:16:58 +10:00
if move == 0 {
return
}
a.yRem -= float64(move)
sign := sign(move)
for move != 0 {
2021-08-05 12:26:41 +10:00
if a.CollidesAt(a.Pos.Add(image.Pt(0, sign))) {
2021-08-02 15:16:58 +10:00
if onCollide != nil {
onCollide()
}
return
}
2021-08-05 12:26:41 +10:00
a.Pos.Y += sign
2021-08-02 15:16:58 +10:00
move -= sign
}
}
2021-08-05 12:26:41 +10:00
func (a *Actor) Prepare(g *Game) {
a.collisionDomain = g.Component(a.CollisionDomain)
2021-08-02 15:16:58 +10:00
}
func sign(m int) int {
if m < 0 {
return -1
}
return 1
}
2021-08-27 13:56:50 +10:00
// Collision reports a collision occurred.
type Collision struct {
With Collider
}
// Error is really only to implement the error interface.
func (c Collision) Error() string {
return fmt.Sprintf("collision with %v", c.With)
}