ichigo/engine/actor.go

125 lines
2.7 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-09-08 20:08:57 +10:00
"drjosh.dev/gurgle/geom"
2021-08-03 14:56:53 +10:00
)
2021-08-18 16:34:51 +10:00
// Ensure Actor satisfies interfaces.
2021-09-02 16:55:12 +10:00
var _ 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-09-08 20:08:57 +10:00
CollisionDomain string // id of component to look for colliders inside of
2021-09-09 17:16:00 +10:00
Pos geom.Int3 // in voxels; multiply by game.VoxelScale for regular Euclidean space
Bounds geom.Box // in voxels; relative to Pos
2021-08-02 15:16:58 +10:00
2021-09-08 20:08:57 +10:00
rem geom.Float3
2021-09-02 16:55:12 +10:00
game *Game
2021-08-02 15:16:58 +10:00
}
2021-09-09 17:16:00 +10:00
func (a *Actor) BoundingBox() geom.Box {
return a.Bounds.Add(a.Pos)
}
2021-09-07 14:00:50 +10:00
// CollidesAt runs a collision test of the actor, supposing the actor is at a
// given position (not necessarily a.Pos).
2021-09-08 20:08:57 +10:00
func (a *Actor) CollidesAt(p geom.Int3) bool {
2021-09-09 17:16:00 +10:00
bounds := a.Bounds.Add(p)
2021-08-30 15:03:22 +10:00
for c := range a.game.Query(a.CollisionDomain, ColliderType) {
if c.(Collider).CollidesWith(bounds) {
2021-08-27 15:39:10 +10:00
return true
}
}
return false
2021-08-03 14:56:53 +10:00
}
2021-09-07 14:00:50 +10:00
// MoveX moves the actor x units in world space. It takes Game.VoxelScale into
// account (so MoveX(x) moves the actor x/VoxelScale.X voxel units). onCollide
// is called if a collision occurs, and the actor wil be in the colliding
// position during the call.
2021-09-04 12:51:51 +10:00
func (a *Actor) MoveX(x float64, onCollide func()) {
2021-09-07 14:00:50 +10:00
a.rem.X += x / a.game.VoxelScale.X
move := int(a.rem.X + 0.5) // Note: math.Round can lead to vibration
2021-08-02 15:16:58 +10:00
if move == 0 {
return
}
2021-09-07 14:00:50 +10:00
a.rem.X -= float64(move)
2021-09-08 20:08:57 +10:00
sign := geom.Sign(move)
2021-08-02 15:16:58 +10:00
for move != 0 {
2021-08-05 12:26:41 +10:00
a.Pos.X += sign
2021-08-02 15:16:58 +10:00
move -= sign
2021-09-04 16:28:33 +10:00
if !a.CollidesAt(a.Pos) {
continue
}
if onCollide != nil {
onCollide()
}
a.Pos.X -= sign
2021-09-07 14:00:50 +10:00
a.rem.X = 0
2021-09-04 16:28:33 +10:00
return
2021-08-02 15:16:58 +10:00
}
}
2021-09-07 14:00:50 +10:00
// MoveY is like MoveX but in the Y dimension. See MoveX for more information.
2021-09-04 12:51:51 +10:00
func (a *Actor) MoveY(y float64, onCollide func()) {
2021-09-07 14:00:50 +10:00
a.rem.Y += y / a.game.VoxelScale.Y
move := int(a.rem.Y + 0.5)
2021-08-02 15:16:58 +10:00
if move == 0 {
return
}
2021-09-07 14:00:50 +10:00
a.rem.Y -= float64(move)
2021-09-08 20:08:57 +10:00
sign := geom.Sign(move)
2021-08-02 15:16:58 +10:00
for move != 0 {
2021-08-05 12:26:41 +10:00
a.Pos.Y += sign
2021-08-02 15:16:58 +10:00
move -= sign
2021-09-04 16:28:33 +10:00
if !a.CollidesAt(a.Pos) {
continue
}
if onCollide != nil {
onCollide()
}
a.Pos.Y -= sign
2021-09-07 14:00:50 +10:00
a.rem.Y = 0
2021-09-04 16:28:33 +10:00
return
2021-08-02 15:16:58 +10:00
}
}
2021-09-07 14:00:50 +10:00
// MoveZ is like MoveX but in the Y dimension. See MoveX for more information.
2021-09-04 12:51:51 +10:00
func (a *Actor) MoveZ(z float64, onCollide func()) {
2021-09-07 14:00:50 +10:00
a.rem.Z += z / a.game.VoxelScale.Z
move := int(a.rem.Z + 0.5)
2021-09-02 16:55:12 +10:00
if move == 0 {
return
}
2021-09-07 14:00:50 +10:00
a.rem.Z -= float64(move)
2021-09-08 20:08:57 +10:00
sign := geom.Sign(move)
2021-09-02 16:55:12 +10:00
for move != 0 {
a.Pos.Z += sign
move -= sign
2021-09-04 16:28:33 +10:00
if !a.CollidesAt(a.Pos) {
continue
}
if onCollide != nil {
onCollide()
}
a.Pos.Z -= sign
2021-09-07 14:00:50 +10:00
a.rem.Z = 0
2021-09-04 16:28:33 +10:00
return
2021-09-02 16:55:12 +10:00
}
}
2021-09-07 14:00:50 +10:00
// Prepare stores a reference to the game.
2021-08-27 14:52:24 +10:00
func (a *Actor) Prepare(g *Game) error {
2021-08-30 15:03:22 +10:00
a.game = g
2021-08-27 14:52:24 +10:00
return nil
2021-08-02 15:16:58 +10:00
}