Landing page and Install Aego / Your first project in English and Russian; the 2D template matches them. Platformer from scratch and Recipes with every step scene run by docs check. Builtin data and handle methods in the reference, Inspector names in field tables, Markdown copies and llms-full.txt, strict docs check in CI, engine/docs/README.md. Fixes: a body cast from a touching contact uses the face normal, so characters no longer stick after a respawn or against walls; Respawn.resetVelocity works; a touch gamepad on exported web pages.
172 lines
3.9 KiB
Go
172 lines
3.9 KiB
Go
package gameplay
|
|
|
|
import (
|
|
"aego/core/mathx"
|
|
"aego/core/rewind"
|
|
"aego/scene"
|
|
)
|
|
|
|
var _ rewind.Keyframer = (*Service)(nil)
|
|
|
|
// noKey: the service keeps no state between steps; component state rides in the scene key.
|
|
type noKey struct{}
|
|
|
|
func (noKey) Size() int { return 0 }
|
|
|
|
func (s *Service) Capture(rewind.Key) rewind.Key {
|
|
return noKey{}
|
|
}
|
|
|
|
func (s *Service) Restore(rewind.Key) {}
|
|
|
|
type hashFns struct {
|
|
motion func(scene.NodeHandle, *Motion2D)
|
|
health func(scene.NodeHandle, *Health)
|
|
hit func(scene.NodeHandle, *Hitbox2D)
|
|
kill func(scene.NodeHandle, *KillZone2D)
|
|
cp func(scene.NodeHandle, *Checkpoint2D)
|
|
pick func(scene.NodeHandle, *Pickup2D)
|
|
resp func(scene.NodeHandle, *Respawn)
|
|
life func(scene.NodeHandle, *Lifetime)
|
|
fsm func(scene.NodeHandle, *StateMachine)
|
|
}
|
|
|
|
func newHashFns(s *Service) hashFns {
|
|
return hashFns{
|
|
motion: s.hashMotion,
|
|
health: s.hashHealth,
|
|
hit: s.hashHit,
|
|
kill: func(n scene.NodeHandle, c *KillZone2D) { s.hashZone(n, &c.st) },
|
|
cp: func(n scene.NodeHandle, c *Checkpoint2D) { s.hashZone(n, &c.st) },
|
|
pick: func(n scene.NodeHandle, c *Pickup2D) { s.hashZone(n, &c.st) },
|
|
resp: s.hashRespawn,
|
|
life: s.hashLife,
|
|
fsm: s.hashFSM,
|
|
}
|
|
}
|
|
|
|
// Hash adds the unexported component state, which the scene hash cannot see.
|
|
func (s *Service) Hash(h *rewind.Hasher) {
|
|
h.String(ServiceID)
|
|
s.hh = h
|
|
f := &s.hashFns
|
|
scene.Store[Motion2D](s.scene).EachAll(f.motion)
|
|
scene.Store[Health](s.scene).EachAll(f.health)
|
|
scene.Store[Hitbox2D](s.scene).EachAll(f.hit)
|
|
scene.Store[KillZone2D](s.scene).EachAll(f.kill)
|
|
scene.Store[Checkpoint2D](s.scene).EachAll(f.cp)
|
|
scene.Store[Pickup2D](s.scene).EachAll(f.pick)
|
|
scene.Store[Respawn](s.scene).EachAll(f.resp)
|
|
scene.Store[Lifetime](s.scene).EachAll(f.life)
|
|
scene.Store[StateMachine](s.scene).EachAll(f.fsm)
|
|
s.hh = nil
|
|
}
|
|
|
|
func hashVec(h *rewind.Hasher, v mathx.Vec2) {
|
|
h.F32(v.X)
|
|
h.F32(v.Y)
|
|
}
|
|
|
|
func hashNode(h *rewind.Hasher, n scene.NodeHandle) {
|
|
h.U32(n.Index)
|
|
h.U32(n.Gen)
|
|
}
|
|
|
|
func (s *Service) hashMotion(n scene.NodeHandle, c *Motion2D) {
|
|
h, st := s.hh, &c.st
|
|
hashNode(h, n)
|
|
hashVec(h, st.move)
|
|
hashVec(h, st.dashVel)
|
|
hashVec(h, st.addVel)
|
|
hashVec(h, st.teleportTo)
|
|
hashVec(h, st.floorN)
|
|
hashVec(h, st.platVel)
|
|
hashVec(h, st.wallN)
|
|
hashVec(h, st.disp)
|
|
hashVec(h, st.climbFrom)
|
|
hashVec(h, st.climbRise)
|
|
hashVec(h, st.climbFwd)
|
|
h.F32(st.climbLeft)
|
|
hashNode(h, st.climbFloor)
|
|
h.F32(st.dashLeft)
|
|
h.F32(st.coyote)
|
|
h.F32(st.buffer)
|
|
h.F32(st.landSpeed)
|
|
hashNode(h, st.ground)
|
|
h.U32(uint32(st.events))
|
|
h.Bool(st.jumpReq)
|
|
h.Bool(st.jumpRel)
|
|
h.Bool(st.jumping)
|
|
h.Bool(st.stop)
|
|
h.Bool(st.teleport)
|
|
h.Bool(st.keepVel)
|
|
h.Bool(st.grounded)
|
|
h.Bool(c.Climbing)
|
|
}
|
|
|
|
func (s *Service) hashHealth(n scene.NodeHandle, c *Health) {
|
|
h, st := s.hh, &c.st
|
|
hashNode(h, n)
|
|
h.U32(uint32(st.dmg))
|
|
h.U32(uint32(st.healed))
|
|
hashNode(h, st.dmgSrc)
|
|
hashNode(h, st.diedSrc)
|
|
h.F32(st.invuln)
|
|
h.Bool(st.ready)
|
|
h.Bool(st.died)
|
|
h.Bool(st.revived)
|
|
h.Bool(st.arm)
|
|
}
|
|
|
|
func (s *Service) hashHit(n scene.NodeHandle, c *Hitbox2D) {
|
|
h, st := s.hh, &c.st
|
|
hashNode(h, n)
|
|
h.Bool(st.pulse)
|
|
h.Int(len(st.recs))
|
|
for i := range st.recs {
|
|
r := &st.recs[i]
|
|
hashNode(h, r.node)
|
|
h.F32(r.timer)
|
|
h.Bool(r.inside)
|
|
h.Bool(r.seen)
|
|
h.Bool(r.hit)
|
|
}
|
|
}
|
|
|
|
func (s *Service) hashZone(n scene.NodeHandle, z *zoneState) {
|
|
h := s.hh
|
|
hashNode(h, n)
|
|
h.Bool(z.used)
|
|
h.Int(len(z.inside))
|
|
for _, x := range z.inside {
|
|
hashNode(h, x)
|
|
}
|
|
}
|
|
|
|
func (s *Service) hashRespawn(n scene.NodeHandle, c *Respawn) {
|
|
h, st := s.hh, &c.st
|
|
hashNode(h, n)
|
|
hashVec(h, st.point)
|
|
h.U32(uint32(st.priority))
|
|
h.F32(st.pending)
|
|
h.Bool(st.hasPoint)
|
|
h.Bool(st.hasPri)
|
|
h.Bool(st.waiting)
|
|
}
|
|
|
|
func (s *Service) hashLife(n scene.NodeHandle, c *Lifetime) {
|
|
h := s.hh
|
|
hashNode(h, n)
|
|
h.Bool(c.st.started)
|
|
h.Bool(c.st.done)
|
|
}
|
|
|
|
func (s *Service) hashFSM(n scene.NodeHandle, c *StateMachine) {
|
|
h := s.hh
|
|
hashNode(h, n)
|
|
h.U32(uint32(c.Index))
|
|
h.F32(c.Time)
|
|
h.U32(uint32(c.st.queued))
|
|
h.Bool(c.st.started)
|
|
}
|