From d14eaf7df2381a26593a4ebdce8163b4b052bb20 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Wed, 2 Jun 2021 20:41:53 -0700 Subject: [PATCH] Collision Box Updates * The F4 key to draw collision boxes works reliably again: it draws the player's hitbox in world-space using the canvas.DrawStrokes() function, rather than in screen-space so it follows the player reliably. * The F4 key also draws hitboxes for ALL other actors in the level: buttons, enemies, doors, etc. * The level geometry collision function is updated to respect a doodad's declared Hitbox from their script, which may result in a smaller box than their raw Canvas size. The result is tighter collision between doodads, and Boy's sprite is rather narrow for its square Canvas so collision on rightward geometry is tighter for the player character. * Collision checks between actors also respect the actor's declared hitboxes now, allowing for Boy to get even closer to a locked door before being blocked. --- dev-assets/doodads/azulian/azulian-red.js | 1 + dev-assets/doodads/boy/boy.js | 2 +- go.mod | 3 -- pkg/collision/bounding_rect.go | 10 ++++++ pkg/collision/collide_actors.go | 1 + pkg/collision/collide_level.go | 6 +++- pkg/fps.go | 38 +++++++++++++++++------ pkg/play_scene.go | 6 +++- pkg/uix/actor_collision.go | 6 ++-- pkg/uix/canvas_actors.go | 5 +++ pkg/uix/canvas_strokes.go | 8 ++--- 11 files changed, 64 insertions(+), 22 deletions(-) diff --git a/dev-assets/doodads/azulian/azulian-red.js b/dev-assets/doodads/azulian/azulian-red.js index e3a4ea0..45f5881 100644 --- a/dev-assets/doodads/azulian/azulian-red.js +++ b/dev-assets/doodads/azulian/azulian-red.js @@ -5,6 +5,7 @@ function main() { var direction = "right"; + Self.SetHitbox(0, 0, 32, 32) Self.SetMobile(true); Self.SetGravity(true); Self.AddAnimation("walk-left", 100, ["red-wl1", "red-wl2", "red-wl3", "red-wl4"]); diff --git a/dev-assets/doodads/boy/boy.js b/dev-assets/doodads/boy/boy.js index 6d48705..362a6c0 100644 --- a/dev-assets/doodads/boy/boy.js +++ b/dev-assets/doodads/boy/boy.js @@ -9,7 +9,7 @@ function main() { Self.SetMobile(true); Self.SetGravity(true); - Self.SetHitbox(0, 0, 16, 52); + Self.SetHitbox(0, 0, 32, 52); Self.AddAnimation("walk-left", 200, ["stand-left", "walk-left-1", "walk-left-2", "walk-left-3", "walk-left-2", "walk-left-1"]); Self.AddAnimation("walk-right", 200, ["stand-right", "walk-right-1", "walk-right-2", "walk-right-3", "walk-right-2", "walk-right-1"]); diff --git a/go.mod b/go.mod index 119c22c..aec88d5 100644 --- a/go.mod +++ b/go.mod @@ -43,6 +43,3 @@ require ( -replace git.kirsle.net/go/render => /home/kirsle/SketchyMaze/deps/render -replace git.kirsle.net/go/ui => /home/kirsle/SketchyMaze/deps/ui -replace git.kirsle.net/go/audio => /home/kirsle/SketchyMaze/deps/audio diff --git a/pkg/collision/bounding_rect.go b/pkg/collision/bounding_rect.go index cecc93d..ac04655 100644 --- a/pkg/collision/bounding_rect.go +++ b/pkg/collision/bounding_rect.go @@ -40,3 +40,13 @@ func GetBoundingRectHitbox(a Actor, hitbox render.Rect) render.Rect { } return rect } + +// SizePlusHitbox adjusts an actor's canvas Size() to better fit the +// declared Hitbox by the actor's script. +func SizePlusHitbox(size render.Rect, hitbox render.Rect) render.Rect { + size.X += hitbox.X + size.Y += hitbox.Y + size.W -= size.W - hitbox.W + size.H -= size.H - hitbox.H + return size +} diff --git a/pkg/collision/collide_actors.go b/pkg/collision/collide_actors.go index b38d412..4f94569 100644 --- a/pkg/collision/collide_actors.go +++ b/pkg/collision/collide_actors.go @@ -14,6 +14,7 @@ type Actor interface { Size() render.Rect Grounded() bool SetGrounded(bool) + Hitbox() render.Rect } // BoxCollision holds the result of a collision BetweenBoxes. diff --git a/pkg/collision/collide_level.go b/pkg/collision/collide_level.go index 0e50cee..f455fae 100644 --- a/pkg/collision/collide_level.go +++ b/pkg/collision/collide_level.go @@ -57,6 +57,7 @@ func CollidesWithGrid(d Actor, grid *level.Chunker, target render.Point) (*Colli var ( P = d.Position() S = d.Size() + hitbox = d.Hitbox() result = &Collide{ MoveTo: P, @@ -71,8 +72,11 @@ func CollidesWithGrid(d Actor, grid *level.Chunker, target render.Point) (*Colli hitFloor bool ) + // Adjust the actor's bounding rect by its stated Hitbox from its script. + S = SizePlusHitbox(S, hitbox) + // Test all of the bounding boxes for a collision with level geometry. - if ok := result.ScanBoundingBox(GetBoundingRect(d), grid); ok { + if ok := result.ScanBoundingBox(GetBoundingRectHitbox(d, hitbox), grid); ok { // We've already collided! Try to wiggle free. if result.Bottom { if !d.Grounded() { diff --git a/pkg/fps.go b/pkg/fps.go index b1c820b..69afd37 100644 --- a/pkg/fps.go +++ b/pkg/fps.go @@ -6,7 +6,8 @@ import ( "git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/collision" - "git.kirsle.net/apps/doodle/pkg/doodads" + "git.kirsle.net/apps/doodle/pkg/drawtool" + "git.kirsle.net/apps/doodle/pkg/uix" "git.kirsle.net/go/render" "git.kirsle.net/go/ui" ) @@ -133,10 +134,9 @@ func (d *Doodle) DrawDebugOverlay() { // DrawCollisionBox draws the collision box around a Doodad. // -// TODO: move inside the Canvas. Currently it takes an actor's World Position -// and draws the box as if it were a relative (to the window) position, so the -// hitbox drifts off when the level scrolls away from 0,0 -func (d *Doodle) DrawCollisionBox(actor doodads.Actor) { +// The canvas will be the level Canvas, and the collision box is drawn in world +// space using the canvas.DrawStrokes function. +func (d *Doodle) DrawCollisionBox(canvas *uix.Canvas, actor *uix.Actor) { if !DebugCollision { return } @@ -144,12 +144,32 @@ func (d *Doodle) DrawCollisionBox(actor doodads.Actor) { var ( rect = collision.GetBoundingRect(actor) box = collision.GetCollisionBox(rect) + hitbox = actor.Hitbox() ) - d.Engine.DrawLine(render.DarkGreen, box.Top[0], box.Top[1]) - d.Engine.DrawLine(render.DarkBlue, box.Bottom[0], box.Bottom[1]) - d.Engine.DrawLine(render.DarkYellow, box.Left[0], box.Left[1]) - d.Engine.DrawLine(render.Red, box.Right[0], box.Right[1]) + // Adjust the actor's bounding rect by its stated Hitbox from its script. + rect = collision.SizePlusHitbox(rect, hitbox) + + box = collision.GetCollisionBox(rect) + + // The stroke data for drawing the collision box "inside" the level Canvas, + // so it scrolls and works in world units not screen units. + var strokes = []struct{ + Color render.Color + PointA render.Point + PointB render.Point + }{ + {render.DarkGreen, box.Top[0], box.Top[1]}, + {render.DarkBlue, box.Bottom[0], box.Bottom[1]}, + {render.DarkYellow, box.Left[0], box.Left[1]}, + {render.Red, box.Right[0], box.Right[1]}, + } + for _, cfg := range strokes { + stroke := drawtool.NewStroke(drawtool.Line, cfg.Color) + stroke.PointA = cfg.PointA + stroke.PointB = cfg.PointB + canvas.DrawStrokes(d.Engine, []*drawtool.Stroke{stroke}) + } } // TrackFPS shows the current FPS once per second. diff --git a/pkg/play_scene.go b/pkg/play_scene.go index 5db1890..c42d724 100644 --- a/pkg/play_scene.go +++ b/pkg/play_scene.go @@ -427,7 +427,11 @@ func (s *PlayScene) Draw(d *Doodle) error { s.drawing.Present(d.Engine, s.drawing.Point()) // Draw out bounding boxes. - d.DrawCollisionBox(s.Player.Drawing) + if DebugCollision { + for _, actor := range s.drawing.Actors() { + d.DrawCollisionBox(s.drawing, actor) + } + } // Draw the UI screen and any widgets that attached to it. s.screen.Compute(d.Engine) diff --git a/pkg/uix/actor_collision.go b/pkg/uix/actor_collision.go index 3a296ea..53e3cc7 100644 --- a/pkg/uix/actor_collision.go +++ b/pkg/uix/actor_collision.go @@ -112,7 +112,7 @@ func (w *Canvas) loopActorCollision() error { w.loopContainActorsInsideLevel(a) // Store this actor's bounding box after they've moved. - boxes[i] = collision.GetBoundingRect(a) + boxes[i] = collision.SizePlusHitbox(collision.GetBoundingRect(a), a.Hitbox()) }(i, a) wg.Wait() } @@ -133,7 +133,7 @@ func (w *Canvas) loopActorCollision() error { // Call the OnCollide handler for A informing them of B's intersection. if w.scripting != nil { var ( - rect = collision.GetBoundingRect(b) + rect = collision.SizePlusHitbox(collision.GetBoundingRect(b), b.Hitbox()) lastGoodBox = render.Rect{ X: originalPositions[b.ID()].X, Y: originalPositions[b.ID()].Y, @@ -205,7 +205,7 @@ func (w *Canvas) loopActorCollision() error { // Did A protest? if err == scripting.ErrReturnFalse { // Are they on top? - aHitbox := collision.GetBoundingRectHitbox(a, a.Hitbox()) + aHitbox := collision.SizePlusHitbox(collision.GetBoundingRect(a), a.Hitbox()) if render.AbsInt(test.Y+test.H-aHitbox.Y) == 0 { onTop = true onTopY = test.Y diff --git a/pkg/uix/canvas_actors.go b/pkg/uix/canvas_actors.go index d50b956..05892e7 100644 --- a/pkg/uix/canvas_actors.go +++ b/pkg/uix/canvas_actors.go @@ -31,6 +31,11 @@ func (w *Canvas) InstallActors(actors level.ActorMap) error { return nil } +// Actors returns the list of actors currently in the Canvas. +func (w *Canvas) Actors() []*Actor { + return w.actors +} + // ClearActors removes all the actors from the Canvas. func (w *Canvas) ClearActors() { w.actors = []*Actor{} diff --git a/pkg/uix/canvas_strokes.go b/pkg/uix/canvas_strokes.go index aedc6c5..b902610 100644 --- a/pkg/uix/canvas_strokes.go +++ b/pkg/uix/canvas_strokes.go @@ -126,7 +126,7 @@ func (w *Canvas) presentStrokes(e render.Engine) { for _, stroke := range w.strokes { strokes = append(strokes, stroke) } - w.drawStrokes(e, strokes) + w.DrawStrokes(e, strokes) // Dynamic actor links visible in the ActorTool and LinkTool. if w.Tool == drawtool.ActorTool || w.Tool == drawtool.LinkTool { @@ -201,12 +201,12 @@ func (w *Canvas) presentActorLinks(e render.Engine) { } } - w.drawStrokes(e, strokes) + w.DrawStrokes(e, strokes) } -// drawStrokes is the common base function behind presentStrokes and +// DrawStrokes is the common base function behind presentStrokes and // presentActorLinks to actually draw the lines to the canvas. -func (w *Canvas) drawStrokes(e render.Engine, strokes []*drawtool.Stroke) { +func (w *Canvas) DrawStrokes(e render.Engine, strokes []*drawtool.Stroke) { var ( P = ui.AbsolutePosition(w) // Canvas point in UI VP = w.ViewportRelative() // Canvas scroll viewport