Use Chipmunk2D to handle physics

This commit is contained in:
Trevor Slocum 2022-06-18 14:08:53 -07:00
parent 4d6d2e9c5e
commit 1214cb3aa6
13 changed files with 371 additions and 170 deletions

View File

@ -11,8 +11,8 @@
65,67,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,65,67,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
65,67,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,65,67,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
65,67,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,65,67,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
65,67,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,65,67,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
65,67,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,65,67,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
65,67,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,33,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,65,67,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
65,67,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,99,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,65,67,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
65,67,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,33,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,65,67,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
65,67,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,98,98,98,98,98,98,98,98,98,98,98,98,98,98,98,98,98,98,98,98,98,99,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,65,67,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
65,67,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,65,67,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@ -106,9 +106,6 @@
</data>
</layer>
<objectgroup id="5" name="HAZARDS">
<object id="6" gid="260" x="336" y="160" width="16" height="16"/>
<object id="7" gid="260" x="336" y="144" width="16" height="16"/>
<object id="8" gid="260" x="336" y="128" width="16" height="16"/>
<object id="9" gid="257" x="560" y="160" width="16" height="16"/>
<object id="10" gid="257" x="560" y="144" width="16" height="16"/>
<object id="11" gid="257" x="560" y="128" width="16" height="16"/>

View File

@ -1,5 +1,9 @@
package component
import "github.com/jakecoffman/cp"
type Position struct {
X, Y float64
Body *cp.Body
}

View File

@ -3,26 +3,118 @@ package entity
import (
"time"
"github.com/hajimehoshi/ebiten/v2"
"github.com/jakecoffman/cp"
"code.rocketnine.space/tslocum/doctorlectro/world"
"code.rocketnine.space/tslocum/doctorlectro/asset"
"code.rocketnine.space/tslocum/doctorlectro/component"
"code.rocketnine.space/tslocum/gohan"
)
const (
playerVelocity = 40.0
playerGroundAccelTime = 0.1
playerGroundAccel = playerVelocity / playerGroundAccelTime
playerAirAccelTime = 0.25
playerAirAccel = playerVelocity / playerAirAccelTime
fallVelocity = 900.0
)
func NewPlayer() gohan.Entity {
playerBody := world.Space.AddBody(cp.NewBody(4, cp.INFINITY))
playerBody.SetPosition(cp.Vector{0, 0})
playerBody.SetVelocityUpdateFunc(playerUpdateVelocity)
x1, y1 := 1.0, 1.0
x2, y2 := 15.0, 15.0
playerShape := world.Space.AddShape(cp.NewBox2(playerBody, cp.BB{x1, y2, x2, y1}, 0))
playerShape.SetElasticity(0)
playerShape.SetFriction(0)
world.PlayerBody = playerBody
world.PlayerShape = playerShape
player := gohan.NewEntity()
player.AddComponent(&component.Position{})
player.AddComponent(&component.Position{
Body: world.PlayerBody,
})
player.AddComponent(&component.Velocity{})
player.AddComponent(&component.Sprite{
Frames: asset.PlayerIdleFrames,
FrameTime: 200 * time.Millisecond,
FrameTime: 150 * time.Millisecond,
NumFrames: len(asset.PlayerIdleFrames),
OffsetY: -10,
})
player.AddComponent(&component.Player{})
return player
}
func playerUpdateVelocity(body *cp.Body, gravity cp.Vector, damping, dt float64) {
jumpState := false
// Grab the grounding normal from last frame
groundNormal := cp.Vector{}
/*world.PlayerBody.EachArbiter(func(arb *cp.Arbiter) {
n := arb.Normal().Neg()
if n.Y > groundNormal.Y {
groundNormal = n
}
})*/
grounded := groundNormal.Y > 0
if groundNormal.Y < 0 {
//remainingBoost = 0
}
// Do a normal-ish update
boost := jumpState // && remainingBoost > 0
var g cp.Vector
if !boost {
g = gravity
}
body.UpdateVelocity(g, damping, dt)
xDir := 0.0
if ebiten.IsKeyPressed(ebiten.KeyA) {
xDir -= 1
world.LastWalkDirL = false
}
if ebiten.IsKeyPressed(ebiten.KeyD) { // TODO
xDir += 1
world.LastWalkDirL = true
}
// Target horizontal speed for air/ground control
targetVx := playerVelocity * xDir
// Update the surface velocity and friction
// Note that the "feet" move in the opposite direction of the player.
surfaceV := cp.Vector{-targetVx, 0}
world.PlayerShape.SetSurfaceV(surfaceV)
if grounded {
world.PlayerShape.SetFriction(playerGroundAccel / world.Gravity)
} else {
world.PlayerShape.SetFriction(0)
}
// Apply air control if not grounded
if !grounded {
v := world.PlayerBody.Velocity()
world.PlayerBody.SetVelocity(cp.LerpConst(v.X, targetVx, playerAirAccel*dt), v.Y)
}
v := body.Velocity()
body.SetVelocity(v.X, cp.Clamp(v.Y, -fallVelocity, cp.INFINITY))
}

View File

@ -1,9 +1,10 @@
package game
import (
"log"
"os"
"github.com/jakecoffman/cp"
"code.rocketnine.space/tslocum/doctorlectro/entity"
"code.rocketnine.space/tslocum/doctorlectro/level"
@ -18,6 +19,7 @@ type Game struct {
func NewGame() (*Game, error) {
g := &Game{}
g.reset()
g.addSystems()
@ -25,8 +27,9 @@ func NewGame() (*Game, error) {
}
func (g *Game) addSystems() {
gohan.AddSystem(system.NewMovementSystem())
gohan.AddSystem(system.NewPlayerMoveSystem())
gohan.AddSystem(system.NewPhysicsSystem())
gohan.AddSystem(system.NewMovementSystem())
gohan.AddSystem(system.NewRenderSystem())
gohan.AddSystem(system.NewRenderMessageSystem())
gohan.AddSystem(system.NewRenderDebugTextSystem())
@ -44,8 +47,12 @@ func (g *Game) reset() {
world.Clinging = false
world.Space = cp.NewSpace()
world.Space.Iterations = 1
world.Space.SetGravity(cp.Vector{0, world.Gravity})
world.Player = entity.NewPlayer()
log.Println("new player", world.Player)
level.LoadMap()
}

1
go.mod
View File

@ -10,6 +10,7 @@ require (
require (
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220516021902-eb3e265c7661 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/jakecoffman/cp v1.2.0 // indirect
github.com/jezek/xgb v1.0.1 // indirect
github.com/lafriks/go-tiled v0.7.0 // indirect
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect

2
go.sum
View File

@ -16,6 +16,8 @@ github.com/hajimehoshi/go-mp3 v0.3.3/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0Ubt
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
github.com/hajimehoshi/oto/v2 v2.1.0/go.mod h1:9i0oYbpJ8BhVGkXDKdXKfFthX1JUNfXjeTp944W8TGM=
github.com/jakecoffman/cp v1.1.0/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg=
github.com/jakecoffman/cp v1.2.0 h1:ZdCFqHglNYJibiIeRvpAktJ7ZuWUnh0cnBsZNKVbY3A=
github.com/jakecoffman/cp v1.2.0/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg=
github.com/jezek/xgb v1.0.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
github.com/jezek/xgb v1.0.1 h1:YUGhxps0aR7J2Xplbs23OHnV1mWaxFVcOl9b+1RQkt8=
github.com/jezek/xgb v1.0.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=

View File

@ -6,6 +6,8 @@ import (
"path/filepath"
"time"
"github.com/jakecoffman/cp"
"code.rocketnine.space/tslocum/doctorlectro/asset"
"code.rocketnine.space/tslocum/doctorlectro/component"
"code.rocketnine.space/tslocum/doctorlectro/world"
@ -104,8 +106,6 @@ func LoadMap() {
}
sprite.NumFrames = len(sprite.Frames)
sprite.FrameTime = 100 * time.Millisecond
log.Println(sprite)
break
}
}
@ -183,15 +183,32 @@ func LoadMap() {
}
world.Player.With(func(position *component.Position) {
position.X, position.Y = world.SpawnX, world.SpawnY
position.Body.SetPosition(cp.Vector{world.SpawnX, world.SpawnY})
})
addCollisionBox := func(tx, ty int) {
x, y := float64(tx*world.TileSize), float64(ty*world.TileSize)
segments := [][4]float64{
{x, y, x + 16, y},
{x, y + 16, x + 16, y + 16},
{x, y, x, y + 16},
{x + 16, y, x + 16, y + 16},
}
for _, segment := range segments {
shape := world.Space.AddShape(cp.NewSegment(world.Space.StaticBody, cp.Vector{segment[0], segment[1]}, cp.Vector{segment[2], segment[3]}, 1))
shape.SetElasticity(0)
shape.SetFriction(0)
}
}
// Add metal collision rects.
for y := range metalTiles {
for x, filled := range metalTiles[y] {
if filled {
r := image.Rect(x*world.TileSize, y*world.TileSize, (x+1)*world.TileSize-1, (y+1)*world.TileSize-1)
world.MetalRects = append(world.MetalRects, r)
addCollisionBox(x, y)
}
}
}

View File

@ -15,7 +15,7 @@ func main() {
ebiten.SetWindowTitle("Doctor Lectro")
ebiten.SetWindowResizable(true)
ebiten.SetWindowSize(world.ScreenWidth, world.ScreenHeight)
ebiten.SetMaxTPS(144)
ebiten.SetMaxTPS(world.TPS)
ebiten.SetRunnableOnUnfocused(true) // Note - this currently does nothing in ebiten
ebiten.SetWindowClosingHandled(true)
ebiten.SetFPSMode(ebiten.FPSModeVsyncOn)

View File

@ -67,6 +67,8 @@ func (s *playerMoveSystem) Update(e gohan.Entity) error {
return nil
}
return nil // TODO
pressLeft := ebiten.IsKeyPressed(ebiten.KeyLeft) || ebiten.IsKeyPressed(ebiten.KeyA)
pressRight := ebiten.IsKeyPressed(ebiten.KeyRight) || ebiten.IsKeyPressed(ebiten.KeyD)
pressUp := ebiten.IsKeyPressed(ebiten.KeyUp) || ebiten.IsKeyPressed(ebiten.KeyW)

View File

@ -1,11 +1,7 @@
package system
import (
"image"
"log"
"code.rocketnine.space/tslocum/doctorlectro/asset"
"code.rocketnine.space/tslocum/doctorlectro/component"
"code.rocketnine.space/tslocum/doctorlectro/world"
"code.rocketnine.space/tslocum/gohan"
@ -14,7 +10,6 @@ import (
type MovementSystem struct {
Position *component.Position
Velocity *component.Velocity
Sprite *component.Sprite `gohan:"?"`
}
@ -32,6 +27,31 @@ func (s *MovementSystem) Update(e gohan.Entity) error {
return nil
}
position := s.Position
if position.Body != nil {
pos := position.Body.Position()
position.X, position.Y = pos.X, pos.Y
if s.Sprite != nil {
vel := position.Body.Velocity()
s.Sprite.HorizontalFlip = !world.LastWalkDirL
idleFrames := asset.PlayerIdleFrames
walkFrames := asset.PlayerWalkFrames
const walkThreshold = 4.00
walking := vel.X < -walkThreshold || vel.X > walkThreshold
if walking {
s.Sprite.Frames = walkFrames
} else {
s.Sprite.Frames = idleFrames
}
}
}
return nil // TODO
s.handleLevelCollisions(e)
s.handleScreenCollisions(e)
return nil
@ -47,176 +67,196 @@ func (_ *MovementSystem) Draw(_ gohan.Entity, _ *ebiten.Image) error {
// handleLevelCollisions handles collisions between the player and the level.
func (s *MovementSystem) handleLevelCollisions(e gohan.Entity) {
position := s.Position
velocity := s.Velocity
/*
vx, vy := velocity.X, velocity.Y
if e == world.Player && world.Debug != 0 && ebiten.IsKeyPressed(ebiten.KeyShift) {
vx, vy = vx*2, vy*2
}
vx, vy := velocity.X, velocity.Y
if e == world.Player && world.Debug != 0 && ebiten.IsKeyPressed(ebiten.KeyShift) {
vx, vy = vx*2, vy*2
}
// Check hazard collisions.
playerRect := image.Rect(int(position.X), int(position.Y), int(position.X+world.PlayerWidth), int(position.Y+world.PlayerHeight))
for i := range world.HazardRects {
if playerRect.Overlaps(world.HazardRects[i]) {
world.SetGameOver(false)
return
}
}
// Check velocity collisions.
const gravityCollisionCheck = 0.1
newX, newY := position.X+vx, position.Y+vy
playerRectX := image.Rect(int(newX), int(position.Y), int(newX+world.PlayerWidth), int(position.Y+world.PlayerHeight))
playerRectY := image.Rect(int(position.X), int(newY), int(position.X+world.PlayerWidth), int(newY+world.PlayerHeight))
playerRectXY := image.Rect(int(newX), int(newY), int(newX+world.PlayerWidth), int(newY+world.PlayerHeight))
playerRectG := image.Rect(int(position.X), int(position.Y+gravityCollisionCheck), int(position.X+world.PlayerWidth), int(position.Y+gravityCollisionCheck+world.PlayerHeight))
var (
collideX bool
collideY bool
collideXY bool
collideG bool
)
for i := range world.MetalRects {
if !collideX && world.MetalRects[i].Overlaps(playerRectX) {
collideX = true
}
if !collideY && world.MetalRects[i].Overlaps(playerRectY) {
collideY = true
}
if !collideXY && world.MetalRects[i].Overlaps(playerRectXY) {
collideXY = true
}
if !collideG && world.MetalRects[i].Overlaps(playerRectG) {
collideG = true
}
}
if collideXY || collideX || collideY {
if collideY {
if !collideX {
position.X = position.X + vx
} else {
s.Velocity.X = 0
// Check hazard collisions.
playerRect := image.Rect(int(position.X), int(position.Y), int(position.X+world.PlayerWidth), int(position.Y+world.PlayerHeight))
for i := range world.HazardRects {
if playerRect.Overlaps(world.HazardRects[i]) {
world.SetGameOver(false)
return
}
s.Velocity.Y = 0
} else if collideX {
if !collideY {
position.Y = position.Y + vy
} else {
s.Velocity.Y = 0
}
s.Velocity.X = 0
} else {
s.Velocity.X = 0
s.Velocity.Y = 0
}
} else {
position.X, position.Y = position.X+vx, position.Y+vy
}
world.MagnetActive = ebiten.IsKeyPressed(ebiten.KeySpace)
gravityX, gravityY := 0.0, 0.01
// Magnetize against nearest wall.
var clinging bool
if world.MagnetActive && !collideG {
wallThreshold := 16.0
playerRectL := image.Rect(int(position.X-wallThreshold), int(position.Y), int(position.X-wallThreshold+world.PlayerWidth), int(position.Y+world.PlayerHeight))
playerRectR := image.Rect(int(position.X+wallThreshold), int(position.Y), int(position.X+wallThreshold+world.PlayerWidth), int(position.Y+world.PlayerHeight))
// Check velocity collisions.
const gravityCollisionCheck = 0.1
newX, newY := position.X+vx, position.Y+vy
playerRectX := image.Rect(int(newX), int(position.Y), int(newX+world.PlayerWidth), int(position.Y+world.PlayerHeight))
playerRectY := image.Rect(int(position.X), int(newY), int(position.X+world.PlayerWidth), int(newY+world.PlayerHeight))
playerRectXY := image.Rect(int(newX), int(newY), int(newX+world.PlayerWidth), int(newY+world.PlayerHeight))
playerRectG := image.Rect(int(position.X), int(position.Y+gravityCollisionCheck), int(position.X+world.PlayerWidth), int(position.Y+gravityCollisionCheck+world.PlayerHeight))
var (
collideL bool
collideR bool
collideX bool
collideY bool
collideXY bool
collideG bool
)
for i := range world.MetalRects {
if !collideL && world.MetalRects[i].Overlaps(playerRectL) {
collideL = true
if !collideX && world.MetalRects[i].Overlaps(playerRectX) {
collideX = true
}
if !collideR && world.MetalRects[i].Overlaps(playerRectR) {
collideR = true
if !collideY && world.MetalRects[i].Overlaps(playerRectY) {
collideY = true
}
if !collideXY && world.MetalRects[i].Overlaps(playerRectXY) {
collideXY = true
}
if !collideG && world.MetalRects[i].Overlaps(playerRectG) {
collideG = true
}
}
if collideL || collideR {
log.Println("ground collision", collideG)
log.Println("cling", velocity.Y)
clinging = true
if collideL {
gravityX, gravityY = -0.01, 0.00
if collideXY || collideX || collideY {
if collideY {
if !collideX {
position.X = position.X + vx
} else {
s.Velocity.X = 0
}
s.Velocity.Y = 0
} else if collideX {
if !collideY {
position.Y = position.Y + vy
} else {
s.Velocity.Y = 0
}
s.Velocity.X = 0
} else {
gravityX, gravityY = 0.01, 0.00
s.Velocity.X = 0
s.Velocity.Y = 0
}
} else {
position.X, position.Y = position.X+vx, position.Y+vy
}
world.MagnetActive = ebiten.IsKeyPressed(ebiten.KeySpace)
const gravitySize = 0.02
gravityX, gravityY := 0.0, gravitySize
// Magnetize against nearest wall.
var clinging bool
if world.MagnetActive && !collideG {
wallThreshold := 16.0 // TODO cling when added space is used
playerRectL := image.Rect(int(position.X-wallThreshold), int(position.Y), int(position.X-wallThreshold+world.PlayerWidth), int(position.Y+world.PlayerHeight))
playerRectR := image.Rect(int(position.X+wallThreshold), int(position.Y), int(position.X+wallThreshold+world.PlayerWidth), int(position.Y+world.PlayerHeight))
playerRectU := image.Rect(int(position.X), int(position.Y-wallThreshold), int(position.X+world.PlayerWidth), int(position.Y-wallThreshold+world.PlayerHeight))
var (
collideL bool
collideR bool
collideU bool
)
for i := range world.MetalRects {
if !collideL && world.MetalRects[i].Overlaps(playerRectL) {
collideL = true
}
if !collideR && world.MetalRects[i].Overlaps(playerRectR) {
collideR = true
}
if !collideU && world.MetalRects[i].Overlaps(playerRectU) {
collideU = true
}
}
if collideL || collideR || collideU {
log.Println("ground collision", collideG)
log.Println("cling", velocity.Y)
clinging = true
if collideL {
gravityX, gravityY = -gravitySize, 0.00
} else if collideR {
gravityX, gravityY = gravitySize, 0.00
} else if collideU {
gravityX, gravityY = 0.00, -gravitySize
}
}
}
}
world.Clinging = clinging
const dampen = 40
world.Clinging = clinging
if clinging || true {
if s.Velocity.Y < 0 {
s.Velocity.Y -= s.Velocity.Y / dampen
} else if s.Velocity.Y > 0 {
s.Velocity.Y -= s.Velocity.Y / dampen
dampenMultiplierX, dampenMultiplierY := 3.0, 1.0
if gravityX != 0 {
dampenMultiplierX, dampenMultiplierY = 1, 3
}
}
if s.Velocity.X < 0 {
s.Velocity.X -= s.Velocity.X / dampen
} else if s.Velocity.X > 0 {
s.Velocity.X -= s.Velocity.X / dampen
}
const dampen = 100
if s.Velocity.Y < 0 {
s.Velocity.Y -= s.Velocity.Y / (dampen / dampenMultiplierY)
} else if s.Velocity.Y > 0 {
s.Velocity.Y -= s.Velocity.Y / (dampen / dampenMultiplierY)
}
if s.Velocity.X < 0 {
s.Velocity.X -= s.Velocity.X / (dampen / dampenMultiplierX)
} else if s.Velocity.X > 0 {
s.Velocity.X -= s.Velocity.X / (dampen / dampenMultiplierX)
}
// Apply gravity.
const maxSpeed = 8.0
s.Velocity.X, s.Velocity.Y = s.Velocity.X+gravityX, s.Velocity.Y+gravityY
if s.Velocity.X < -maxSpeed {
s.Velocity.X = -maxSpeed
} else if s.Velocity.X > maxSpeed {
s.Velocity.X = maxSpeed
}
if s.Velocity.Y < -maxSpeed {
s.Velocity.Y = -maxSpeed
} else if s.Velocity.Y > maxSpeed {
s.Velocity.Y = maxSpeed
}
if s.Velocity.X > -0.01 && s.Velocity.X < 0.01 {
s.Velocity.X = 0
}
if s.Velocity.Y > -0.01 && s.Velocity.Y < 0.01 {
s.Velocity.Y = 0
}
// Update player sprite.
s.Sprite.HorizontalFlip = world.LastWalkDirL
s.Sprite.VerticalFlip = false
// Apply gravity.
const maxSpeed = 8.0
s.Velocity.X, s.Velocity.Y = s.Velocity.X+gravityX, s.Velocity.Y+gravityY
if s.Velocity.X < -maxSpeed {
s.Velocity.X = -maxSpeed
} else if s.Velocity.X > maxSpeed {
s.Velocity.X = maxSpeed
}
if s.Velocity.Y < -maxSpeed {
s.Velocity.Y = -maxSpeed
} else if s.Velocity.Y > maxSpeed {
s.Velocity.Y = maxSpeed
}
offsetY := -12.0
// Update player sprite.
s.Sprite.HorizontalFlip = world.LastWalkDirL
s.Sprite.VerticalFlip = false
idleFrames := asset.PlayerIdleFrames
walkFrames := asset.PlayerWalkFrames
if gravityX < 0 {
idleFrames = asset.PlayerIdleFramesRot90
walkFrames = asset.PlayerWalkFramesRot90
s.Sprite.VerticalFlip = world.LastWalkDirU
s.Sprite.HorizontalFlip = false
offsetY := -12.0
offsetY = 0
} else if gravityX > 0 {
idleFrames = asset.PlayerIdleFramesRot90
walkFrames = asset.PlayerWalkFramesRot90
s.Sprite.VerticalFlip = world.LastWalkDirU
s.Sprite.HorizontalFlip = true
idleFrames := asset.PlayerIdleFrames
walkFrames := asset.PlayerWalkFrames
if gravityX < 0 {
idleFrames = asset.PlayerIdleFramesRot90
walkFrames = asset.PlayerWalkFramesRot90
s.Sprite.VerticalFlip = world.LastWalkDirU
s.Sprite.HorizontalFlip = false
offsetY = 0
}
offsetY = 0
} else if gravityX > 0 {
idleFrames = asset.PlayerIdleFramesRot90
walkFrames = asset.PlayerWalkFramesRot90
s.Sprite.VerticalFlip = world.LastWalkDirU
s.Sprite.HorizontalFlip = true
s.Sprite.OffsetY = offsetY
offsetY = 0
} else if gravityY < 0 {
s.Sprite.VerticalFlip = true
const walkThreshold = 0.04
walking := s.Velocity.X < -walkThreshold || s.Velocity.X > walkThreshold || s.Velocity.Y < -walkThreshold || s.Velocity.Y > walkThreshold
if walking {
s.Sprite.Frames = walkFrames
} else {
s.Sprite.Frames = idleFrames
}
offsetY = 11
}
s.Sprite.OffsetY = offsetY
const walkThreshold = 0.04
walking := (gravityY != 0 && (s.Velocity.X < -walkThreshold || s.Velocity.X > walkThreshold)) || (gravityX != 0 && (s.Velocity.Y < -walkThreshold || s.Velocity.Y > walkThreshold))
if walking {
s.Sprite.Frames = walkFrames
} else {
s.Sprite.Frames = idleFrames
}
*/
}
// handleScreenCollisions forces the player to remain within the screen bounds.

30
system/physics.go Normal file
View File

@ -0,0 +1,30 @@
package system
import (
_ "image/png"
"code.rocketnine.space/tslocum/doctorlectro/world"
"code.rocketnine.space/tslocum/doctorlectro/component"
"code.rocketnine.space/tslocum/gohan"
"github.com/hajimehoshi/ebiten/v2"
)
type PhysicsSystem struct {
Player *component.Player
}
func NewPhysicsSystem() *PhysicsSystem {
s := &PhysicsSystem{}
return s
}
func (s *PhysicsSystem) Update(_ gohan.Entity) error {
world.Space.Step(1.0 / world.TPS)
return nil
}
func (s *PhysicsSystem) Draw(e gohan.Entity, screen *ebiten.Image) error {
return gohan.ErrUnregister
}

View File

@ -40,10 +40,11 @@ func (s *RenderDebugTextSystem) Draw(e gohan.Entity, screen *ebiten.Image) error
}
position := s.Position
velocity := s.Velocity
vel := world.PlayerBody.Velocity()
s.debugImg.Fill(color.RGBA{0, 0, 0, 80})
ebitenutil.DebugPrint(s.debugImg, fmt.Sprintf("POS %.0f,%.0f\nVEL %.2f,%.2f\nENT %d\nUPD %d\nDRA %d\nTPS %0.0f\nFPS %0.0f", position.X, position.Y, velocity.X, velocity.Y, gohan.CurrentEntities(), gohan.CurrentUpdates(), gohan.CurrentDraws(), ebiten.CurrentTPS(), ebiten.CurrentFPS()))
ebitenutil.DebugPrint(s.debugImg, fmt.Sprintf("POS %.0f,%.0f\nVEL %.2f,%.2f\nENT %d\nUPD %d\nDRA %d\nTPS %0.0f\nFPS %0.0f", position.X, position.Y, vel.X, vel.Y, gohan.CurrentEntities(), gohan.CurrentUpdates(), gohan.CurrentDraws(), ebiten.CurrentTPS(), ebiten.CurrentFPS()))
screen.DrawImage(s.debugImg, nil)
return nil
}

View File

@ -2,7 +2,8 @@ package world
import (
"image"
"log"
"github.com/jakecoffman/cp"
"code.rocketnine.space/tslocum/doctorlectro/component"
@ -16,13 +17,17 @@ const (
ScreenHeight = 540
)
const TPS = 144
const TileSize = 16
const (
PlayerWidth = 15.0
PlayerHeight = 15.0
PlayerHeight = 23.0
)
const Gravity = 500.0
var (
Debug int
@ -63,6 +68,11 @@ var (
LastWalkDirL, LastWalkDirU bool
HazardRects []image.Rectangle
Space *cp.Space
PlayerShape *cp.Shape
PlayerBody *cp.Body
)
func StartGame() {
@ -72,8 +82,6 @@ func StartGame() {
GameStarted = true
log.Println(SpawnX, SpawnY)
// TODO skip intro cutscenes
}