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.
This commit is contained in:
Noah 2021-06-02 20:41:53 -07:00
parent fcb5d27290
commit d14eaf7df2
11 changed files with 64 additions and 22 deletions

View File

@ -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"]);

View File

@ -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"]);

3
go.mod
View File

@ -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

View File

@ -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
}

View File

@ -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.

View File

@ -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() {

View File

@ -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.

View File

@ -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)

View File

@ -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

View File

@ -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{}

View File

@ -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