From 94d0da78e74726e5df745b03808b6d57c4a2b461 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Thu, 5 May 2022 21:35:32 -0700 Subject: [PATCH] Swimming Physics and Bubble Pattern Water pixels finally do something other than turn your character blue! * When the player character is "wet" (touching water pixels, and so appearing in a blue mask), water physics apply: gravity is slower, your jump height is halved, but you get infinite jumps to swim higher in the water. * Holding the jump key under water will incur a short delay between jumps, so that you don't just fly straight up to the surface. Tap the jump button to move up quicker, you can spam it all you want. Azulians are also able to handle being under water: * They'll sink to the bottom and keep walking back and forth normally. * If you are above them and noticed, they'll jump (swim) up towards you, aware of the water and it jumps like you do. * The Blue Azulian has the poorest vertical aggro range so it isn't a very good swimmer. The White Azulian is very good at navigating water as it can pursue the player from the furthest distance of them all. Changes to the editor: * New brush pattern added: bubbles.png * It's the default pattern now for the "water" color of all of the built-in palettes instead of ink.png * A repeating pattern of bubbles carved out showing the level wallpaper. * The old "Bubbles (circles.png)" is renamed "Circles" * The last scroll position is saved with the Level file, so when you reload the level later it's scrolled at where you left it. --- assets/pattern/bubbles.png | Bin 0 -> 4102 bytes dev-assets/doodads/azulian/azulian.js | 28 +++++- pkg/balance/numbers.go | 5 +- pkg/collision/collide_level.go | 11 ++- pkg/editor_scene.go | 9 +- pkg/level/palette_defaults.go | 8 +- pkg/level/types.go | 3 + pkg/pattern/pattern.go | 4 + pkg/play_scene.go | 111 +----------------------- pkg/player_physics.go | 119 ++++++++++++++++++++++++++ pkg/uix/actor.go | 11 +++ pkg/uix/actor_collision.go | 19 ++-- pkg/uix/scripting.go | 1 + 13 files changed, 200 insertions(+), 129 deletions(-) create mode 100644 assets/pattern/bubbles.png create mode 100644 pkg/player_physics.go diff --git a/assets/pattern/bubbles.png b/assets/pattern/bubbles.png new file mode 100644 index 0000000000000000000000000000000000000000..647fe0a43c1f8a1432a4758c2676d2e3d8f47e75 GIT binary patch literal 4102 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 swimJumpCooldownTick) { + canJump = true; + swimJumpCooldownTick = tick + swimJumpCooldown; + } + } + + // How speedy would our movement and jump be? + let xspeed = playerSpeed, yspeed = jumpSpeed; + if (Self.IsWet()) { + xspeed = swimSpeed; + yspeed = swimJumpSpeed; + } + + let Vx = parseFloat(xspeed * (direction === "left" ? -1 : 1)), + Vy = jump && canJump ? parseFloat(-yspeed) : Self.GetVelocity().Y; Self.SetVelocity(Vector(Vx, Vy)); // If we changed directions, stop animating now so we can diff --git a/pkg/balance/numbers.go b/pkg/balance/numbers.go index 15bb536..3407103 100644 --- a/pkg/balance/numbers.go +++ b/pkg/balance/numbers.go @@ -41,7 +41,10 @@ var ( PlayerAcceleration float64 = 0.12 Gravity float64 = 7 GravityAcceleration float64 = 0.1 - SlopeMaxHeight = 8 // max pixel height for player to walk up a slope + SwimGravity float64 = 3 + SwimJumpVelocity float64 = -12 + SwimJumpCooldown uint64 = 24 // number of frames of cooldown between swim-jumps + SlopeMaxHeight = 8 // max pixel height for player to walk up a slope // Default chunk size for canvases. ChunkSize = 128 diff --git a/pkg/collision/collide_level.go b/pkg/collision/collide_level.go index 33d7ae0..3350f51 100644 --- a/pkg/collision/collide_level.go +++ b/pkg/collision/collide_level.go @@ -35,6 +35,7 @@ func (c *Collide) Reset() { c.Left = false c.Right = false c.Bottom = false + c.InWater = false } // Side of the collision box (top, bottom, left, right) @@ -85,8 +86,6 @@ func CollidesWithGrid(d Actor, grid *level.Chunker, target render.Point) (*Colli if result.Bottom { if !d.Grounded() { d.SetGrounded(true) - } else { - // result.Bottom = false } } else { d.SetGrounded(false) @@ -250,10 +249,10 @@ func (c *Collide) ScanBoundingBox(box render.Rect, grid *level.Chunker) bool { side Side } jobs := []jobSide{ // We'll scan each side of the bounding box in parallel - jobSide{col.Top[0], col.Top[1], Top}, - jobSide{col.Bottom[0], col.Bottom[1], Bottom}, - jobSide{col.Left[0], col.Left[1], Left}, - jobSide{col.Right[0], col.Right[1], Right}, + {col.Top[0], col.Top[1], Top}, + {col.Bottom[0], col.Bottom[1], Bottom}, + {col.Left[0], col.Left[1], Left}, + {col.Right[0], col.Right[1], Right}, } var wg sync.WaitGroup diff --git a/pkg/editor_scene.go b/pkg/editor_scene.go index 80daa11..4e86b49 100644 --- a/pkg/editor_scene.go +++ b/pkg/editor_scene.go @@ -225,7 +225,11 @@ func (s *EditorScene) setupAsync(d *Doodle) error { // Scroll the level to the remembered position from when we went // to Play Mode and back. If no remembered position, this is zero // anyway. - s.UI.Canvas.ScrollTo(s.RememberScrollPosition) + if s.RememberScrollPosition.IsZero() && s.Level != nil { + s.UI.Canvas.ScrollTo(s.Level.ScrollPosition) + } else { + s.UI.Canvas.ScrollTo(s.RememberScrollPosition) + } d.Flash("Editor Mode.") if s.DrawingType == enum.LevelDrawing { @@ -514,6 +518,9 @@ func (s *EditorScene) SaveLevel(filename string) error { m.Palette = s.UI.Canvas.Palette m.Chunker = s.UI.Canvas.Chunker() + // Store the scroll position. + m.ScrollPosition = s.UI.Canvas.Scroll + // Clear the modified flag on the level. s.UI.Canvas.SetModified(false) diff --git a/pkg/level/palette_defaults.go b/pkg/level/palette_defaults.go index f913501..aa2bce0 100644 --- a/pkg/level/palette_defaults.go +++ b/pkg/level/palette_defaults.go @@ -37,7 +37,7 @@ var ( Name: "water", Color: render.MustHexColor("#09F"), Water: true, - Pattern: "ink.png", + Pattern: "bubbles.png", }, { Name: "hint", @@ -89,7 +89,7 @@ var ( Name: "water", Color: render.RGBA(0, 153, 255, 255), Water: true, - Pattern: "ink.png", + Pattern: "bubbles.png", }, { Name: "hint", @@ -126,7 +126,7 @@ var ( { Name: "water", Color: render.MustHexColor("#09F"), - Pattern: "ink.png", + Pattern: "bubbles.png", }, { Name: "hint", @@ -159,7 +159,7 @@ var ( Name: "water", Color: render.RGBA(0, 153, 255, 255), Water: true, - Pattern: "ink.png", + Pattern: "bubbles.png", }, { Name: "electric", diff --git a/pkg/level/types.go b/pkg/level/types.go index a0de0f4..7ff8639 100644 --- a/pkg/level/types.go +++ b/pkg/level/types.go @@ -57,6 +57,9 @@ type Level struct { MaxHeight int64 `json:"boundedHeight"` Wallpaper string `json:"wallpaper"` + // The last scrolled position in the editor. + ScrollPosition render.Point `json:"scroll"` + // Actors keep a list of the doodad instances in this map. Actors ActorMap `json:"actors"` diff --git a/pkg/pattern/pattern.go b/pkg/pattern/pattern.go index c8cadbc..b33c284 100644 --- a/pkg/pattern/pattern.go +++ b/pkg/pattern/pattern.go @@ -42,6 +42,10 @@ var Builtins = []Pattern{ }, { Name: "Bubbles", + Filename: "bubbles.png", + }, + { + Name: "Circles", Filename: "circles.png", }, { diff --git a/pkg/play_scene.go b/pkg/play_scene.go index 58ff74b..5d531f3 100644 --- a/pkg/play_scene.go +++ b/pkg/play_scene.go @@ -74,6 +74,7 @@ type PlayScene struct { godMode bool // Cheat: player can't die godModeUntil time.Time // Invulnerability timer at respawn. playerJumpCounter int // limit jump length + jumpCooldownUntil uint64 // future game tick for jump cooldown (swimming esp.) // Inventory HUD. Impl. in play_inventory.go invenFrame *ui.Frame @@ -211,6 +212,8 @@ func (s *PlayScene) setupAsync(d *Doodle) error { // Handler when an actor touches water or fire. s.drawing.OnLevelCollision = func(a *uix.Actor, col *collision.Collide) { + a.SetWet(col.InWater) + if col.InFire != "" { a.Canvas.MaskColor = render.Black if a.ID() == "PLAYER" { // only the player dies in fire. @@ -472,8 +475,7 @@ func (s *PlayScene) installPlayerDoodad(filename string, spawn render.Point, cen // Set up the movement physics for the player. s.playerPhysics = &physics.Mover{ - MaxSpeed: physics.NewVector(balance.PlayerMaxVelocity, balance.PlayerMaxVelocity), - // Gravity: physics.NewVector(balance.Gravity, balance.Gravity), + MaxSpeed: physics.NewVector(balance.PlayerMaxVelocity, balance.PlayerMaxVelocity), Acceleration: 0.025, Friction: 0.1, } @@ -804,111 +806,6 @@ func (s *PlayScene) Draw(d *Doodle) error { return nil } -// movePlayer updates the player's X,Y coordinate based on key pressed. -func (s *PlayScene) movePlayer(ev *event.State) { - var ( - playerSpeed = float64(balance.PlayerMaxVelocity) - velocity = s.Player.Velocity() - direction float64 - jumping bool - ) - - // Antigravity: player can move anywhere with arrow keys. - if s.antigravity || !s.Player.HasGravity() { - velocity.X = 0 - velocity.Y = 0 - - // Shift to slow your roll to 1 pixel per tick. - if keybind.Shift(ev) { - playerSpeed = 1 - } - - if keybind.Left(ev) { - velocity.X = -playerSpeed - } else if keybind.Right(ev) { - velocity.X = playerSpeed - } - if keybind.Up(ev) { - velocity.Y = -playerSpeed - } else if keybind.Down(ev) { - velocity.Y = playerSpeed - } - } else { - // Moving left or right. - if keybind.Left(ev) { - direction = -1 - } else if keybind.Right(ev) { - direction = 1 - } - - // Up button to signal they want to jump. - if keybind.Up(ev) { - if s.Player.Grounded() { - velocity.Y = balance.PlayerJumpVelocity - } - } else if velocity.Y < 0 { - velocity.Y = 0 - } - // if keybind.Up(ev) && (s.Player.Grounded() || s.playerJumpCounter >= 0) { - // jumping = true - - // if s.Player.Grounded() { - // // Allow them to sustain the jump this many ticks. - // s.playerJumpCounter = 32 - // } - // } - - // Moving left or right? Interpolate their velocity by acceleration. - if direction != 0 { - if s.playerLastDirection != direction { - velocity.X = 0 - } - - // TODO: fast turn-around if they change directions so they don't - // slip and slide while their velocity updates. - velocity.X = physics.Lerp( - velocity.X, - direction*s.playerPhysics.MaxSpeed.X, - s.playerPhysics.Acceleration, - ) - } else { - // Slow them back to zero using friction. - velocity.X = physics.Lerp( - velocity.X, - 0, - s.playerPhysics.Friction, - ) - } - - // Moving upwards (jumping): give them full acceleration upwards. - if jumping { - velocity.Y = -playerSpeed - } - - // While in the air, count down their jump counter; when zero they - // cannot jump again until they touch ground. - if !s.Player.Grounded() { - s.playerJumpCounter-- - } - } - - s.playerLastDirection = direction - - // Move the player unless frozen. - // TODO: if Y=0 then gravity fails, but not doing this allows the - // player to jump while frozen. Not a HUGE deal right now as only Warp Doors - // freeze the player currently but do address this later. - if s.Player.IsFrozen() { - velocity.X = 0 - } - s.Player.SetVelocity(velocity) - - // If the "Use" key is pressed, set an actor flag on the player. - s.Player.SetUsing(keybind.Use(ev)) - - s.scripting.To(s.Player.ID()).Events.RunKeypress(keybind.FromEvent(ev)) -} - // Drawing returns the private world drawing, for debugging with the console. func (s *PlayScene) Drawing() *uix.Canvas { return s.drawing diff --git a/pkg/player_physics.go b/pkg/player_physics.go new file mode 100644 index 0000000..561d375 --- /dev/null +++ b/pkg/player_physics.go @@ -0,0 +1,119 @@ +package doodle + +// Subset of the PlayScene that is responsible for movement of the player character. + +import ( + "git.kirsle.net/apps/doodle/pkg/balance" + "git.kirsle.net/apps/doodle/pkg/keybind" + "git.kirsle.net/apps/doodle/pkg/physics" + "git.kirsle.net/apps/doodle/pkg/shmem" + "git.kirsle.net/go/render/event" +) + +// movePlayer updates the player's X,Y coordinate based on key pressed. +func (s *PlayScene) movePlayer(ev *event.State) { + var ( + playerSpeed = float64(balance.PlayerMaxVelocity) + velocity = s.Player.Velocity() + direction float64 + jumping bool + // holdingJump bool // holding down the jump button vs. tapping it + ) + + // Antigravity: player can move anywhere with arrow keys. + if s.antigravity || !s.Player.HasGravity() { + velocity.X = 0 + velocity.Y = 0 + + // Shift to slow your roll to 1 pixel per tick. + if keybind.Shift(ev) { + playerSpeed = 1 + } + + if keybind.Left(ev) { + velocity.X = -playerSpeed + } else if keybind.Right(ev) { + velocity.X = playerSpeed + } + if keybind.Up(ev) { + velocity.Y = -playerSpeed + } else if keybind.Down(ev) { + velocity.Y = playerSpeed + } + } else { + // Moving left or right. + if keybind.Left(ev) { + direction = -1 + } else if keybind.Right(ev) { + direction = 1 + } + + // Up button to signal they want to jump. + if keybind.Up(ev) { + if s.Player.IsWet() { + // If they are holding Up put a cooldown in how fast they can swim + // to the surface. Tapping the Jump button allows a faster ascent. + if shmem.Tick > s.jumpCooldownUntil { + s.jumpCooldownUntil = shmem.Tick + balance.SwimJumpCooldown + velocity.Y = balance.SwimJumpVelocity + } + } else if s.Player.Grounded() { + velocity.Y = balance.PlayerJumpVelocity + } + } else { + s.jumpCooldownUntil = 0 + if velocity.Y < 0 { + velocity.Y = 0 + } + } + + // Moving left or right? Interpolate their velocity by acceleration. + if direction != 0 { + if s.playerLastDirection != direction { + velocity.X = 0 + } + + // TODO: fast turn-around if they change directions so they don't + // slip and slide while their velocity updates. + velocity.X = physics.Lerp( + velocity.X, + direction*s.playerPhysics.MaxSpeed.X, + s.playerPhysics.Acceleration, + ) + } else { + // Slow them back to zero using friction. + velocity.X = physics.Lerp( + velocity.X, + 0, + s.playerPhysics.Friction, + ) + } + + // Moving upwards (jumping): give them full acceleration upwards. + if jumping { + velocity.Y = -playerSpeed + } + + // While in the air, count down their jump counter; when zero they + // cannot jump again until they touch ground. + if !s.Player.Grounded() { + s.playerJumpCounter-- + } + } + + s.playerLastDirection = direction + + // Move the player unless frozen. + // TODO: if Y=0 then gravity fails, but not doing this allows the + // player to jump while frozen. Not a HUGE deal right now as only Warp Doors + // freeze the player currently but do address this later. + if s.Player.IsFrozen() { + velocity.X = 0 + } + s.Player.SetVelocity(velocity) + + // If the "Use" key is pressed, set an actor flag on the player. + s.Player.SetUsing(keybind.Use(ev)) + + s.scripting.To(s.Player.ID()).Events.RunKeypress(keybind.FromEvent(ev)) +} diff --git a/pkg/uix/actor.go b/pkg/uix/actor.go index 6c13ecc..8d49af9 100644 --- a/pkg/uix/actor.go +++ b/pkg/uix/actor.go @@ -36,6 +36,7 @@ type Actor struct { // Actor runtime variables. hasGravity bool hasInventory bool + wet bool isMobile bool // Mobile character, such as the player or an enemy noclip bool // Disable collision detection hidden bool // invisible, via Hide() and Show() @@ -152,6 +153,16 @@ func (a *Actor) SetInvulnerable(v bool) { a.immortal = v } +// Wet returns whether the actor is in contact with water pixels in a level. +func (a *Actor) IsWet() bool { + return a.wet +} + +// SetWet updates the state of the actor's wet-ness. +func (a *Actor) SetWet(v bool) { + a.wet = v +} + // Size returns the size of the actor, from the underlying doodads.Drawing. func (a *Actor) Size() render.Rect { return a.Drawing.Size() diff --git a/pkg/uix/actor_collision.go b/pkg/uix/actor_collision.go index 5348f67..22c1ca8 100644 --- a/pkg/uix/actor_collision.go +++ b/pkg/uix/actor_collision.go @@ -73,9 +73,13 @@ func (w *Canvas) loopActorCollision() error { // Apply gravity to the actor's velocity. if a.hasGravity && !a.Grounded() { //v.Y >= 0 { if !a.Grounded() { + var gravity = balance.Gravity + if a.IsWet() { + gravity = balance.SwimGravity + } v.Y = physics.Lerp( - v.Y, // current speed - balance.Gravity, // target max gravity falling downwards + v.Y, // current speed + gravity, // target max gravity falling downwards balance.GravityAcceleration, ) } else { @@ -98,12 +102,11 @@ func (w *Canvas) loopActorCollision() error { // Check collision with level geometry. chkPoint := delta.ToPoint() - info, ok := collision.CollidesWithGrid(a, w.chunks, chkPoint) - if ok { - // Collision happened with world. - if w.OnLevelCollision != nil { - w.OnLevelCollision(a, info) - } + info, _ := collision.CollidesWithGrid(a, w.chunks, chkPoint) + + // Inform the caller about the collision state every tick + if w.OnLevelCollision != nil { + w.OnLevelCollision(a, info) } // Move us back where the collision check put us diff --git a/pkg/uix/scripting.go b/pkg/uix/scripting.go index 1ecddb7..2b33a2d 100644 --- a/pkg/uix/scripting.go +++ b/pkg/uix/scripting.go @@ -122,6 +122,7 @@ func (w *Canvas) MakeSelfAPI(actor *Actor) map[string]interface{} { "Destroy": actor.Destroy, "Freeze": actor.Freeze, "Unfreeze": actor.Unfreeze, + "IsWet": actor.IsWet, "Hide": actor.Hide, "Show": actor.Show, "GetLinks": func() []map[string]interface{} {