2019-05-29 04:43:30 +00:00
|
|
|
package uix
|
|
|
|
|
|
|
|
import (
|
2019-07-05 22:02:22 +00:00
|
|
|
"errors"
|
2019-05-29 04:43:30 +00:00
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"git.kirsle.net/apps/doodle/lib/render"
|
|
|
|
"git.kirsle.net/apps/doodle/pkg/balance"
|
|
|
|
"git.kirsle.net/apps/doodle/pkg/collision"
|
|
|
|
"git.kirsle.net/apps/doodle/pkg/doodads"
|
2019-06-25 21:57:11 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/log"
|
2019-05-29 04:43:30 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/scripting"
|
|
|
|
"github.com/robertkrimen/otto"
|
|
|
|
)
|
|
|
|
|
|
|
|
// loopActorCollision is the Loop function that checks if pairs of
|
|
|
|
// actors are colliding with each other, and handles their scripting
|
|
|
|
// responses to such collisions.
|
|
|
|
func (w *Canvas) loopActorCollision() error {
|
2019-07-05 22:02:22 +00:00
|
|
|
if w.scripting == nil {
|
|
|
|
return errors.New("Canvas.loopActorCollision: scripting engine not attached to Canvas")
|
|
|
|
}
|
|
|
|
|
2019-05-29 04:43:30 +00:00
|
|
|
var (
|
|
|
|
// Current time of this tick so we can advance animations.
|
|
|
|
now = time.Now()
|
|
|
|
|
|
|
|
// As we iterate over all actors below to process their movement, track
|
|
|
|
// their bounding rectangles so we can later see if any pair of actors
|
|
|
|
// intersect each other. Also, in case of actor scripts protesting a
|
|
|
|
// collision later, store each actor's original position before the move.
|
|
|
|
boxes = make([]render.Rect, len(w.actors))
|
|
|
|
originalPositions = map[string]render.Point{}
|
|
|
|
)
|
|
|
|
|
|
|
|
// Loop over all the actors in parallel, processing their movement and
|
|
|
|
// checking collision data against the level geometry.
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
for i, a := range w.actors {
|
|
|
|
wg.Add(1)
|
|
|
|
go func(i int, a *Actor) {
|
|
|
|
defer wg.Done()
|
|
|
|
originalPositions[a.ID()] = a.Position()
|
|
|
|
|
|
|
|
// Advance any animations for this actor.
|
|
|
|
if a.activeAnimation != nil && a.activeAnimation.nextFrameAt.Before(now) {
|
|
|
|
if done := a.TickAnimation(a.activeAnimation); done {
|
|
|
|
// Animation has finished, run the callback script.
|
|
|
|
if a.animationCallback.IsFunction() {
|
|
|
|
a.animationCallback.Call(otto.NullValue())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clean up the animation state.
|
|
|
|
a.StopAnimation()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the actor's velocity to see if it's moving this tick.
|
|
|
|
v := a.Velocity()
|
|
|
|
if a.hasGravity {
|
|
|
|
v.Y += int32(balance.Gravity)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If not moving, grab the bounding box right now.
|
|
|
|
if v == render.Origin {
|
|
|
|
boxes[i] = doodads.GetBoundingRect(a)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a delta point from their current location to where they
|
|
|
|
// want to move to this tick.
|
|
|
|
delta := a.Position()
|
|
|
|
delta.Add(v)
|
|
|
|
|
|
|
|
// Check collision with level geometry.
|
|
|
|
info, ok := collision.CollidesWithGrid(a, w.chunks, delta)
|
|
|
|
if ok {
|
|
|
|
// Collision happened with world.
|
Add Switches, Fire/Water Collision and Play Menu
* New doodads: Switches.
* They come in four varieties: wall switch (background element, with
"ON/OFF" text) and three side-profile switches for the floor, left
or right walls.
* On collision with the player, they flip their state from "OFF" to
"ON" or vice versa. If the player walks away and then collides
again, the switch flips again.
* Can be used to open/close Electric Doors when turned on/off. Their
default state is "off"
* If a switch receives a power signal from another linked switch, it
sets its own state to match. So, two "on/off" switches that are
connected to a door AND to each other will both flip on/off when one
of them flips.
* Update the Level Collision logic to support Decoration, Fire and Water
pixel collisions.
* Previously, ALL pixels in the level were acting as though solid.
* Non-solid pixels don't count for collision detection, but their
attributes (fire and water) are collected and returned.
* Updated the MenuScene to support loading a map file in Play Mode
instead of Edit Mode. Updated the title screen menu to add a button
for playing levels instead of editing them.
* Wrote some documentation.
2019-07-07 01:30:03 +00:00
|
|
|
if w.OnLevelCollision != nil {
|
|
|
|
w.OnLevelCollision(a, info)
|
|
|
|
}
|
2019-05-29 04:43:30 +00:00
|
|
|
}
|
|
|
|
delta = info.MoveTo // Move us back where the collision check put us
|
|
|
|
|
|
|
|
// Move the actor's World Position to the new location.
|
|
|
|
a.MoveTo(delta)
|
|
|
|
|
|
|
|
// Keep the actor from leaving the world borders of bounded maps.
|
|
|
|
w.loopContainActorsInsideLevel(a)
|
|
|
|
|
|
|
|
// Store this actor's bounding box after they've moved.
|
|
|
|
boxes[i] = doodads.GetBoundingRect(a)
|
|
|
|
}(i, a)
|
|
|
|
wg.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
var collidingActors = map[string]string{}
|
|
|
|
for tuple := range collision.BetweenBoxes(boxes) {
|
|
|
|
a, b := w.actors[tuple.A], w.actors[tuple.B]
|
|
|
|
collidingActors[a.ID()] = b.ID()
|
|
|
|
|
|
|
|
// Call the OnCollide handler.
|
|
|
|
if w.scripting != nil {
|
|
|
|
// Tell actor A about the collision with B.
|
|
|
|
if err := w.scripting.To(a.ID()).Events.RunCollide(&CollideEvent{
|
|
|
|
Actor: b,
|
|
|
|
Overlap: tuple.Overlap,
|
|
|
|
InHitbox: tuple.Overlap.Intersects(a.Hitbox()),
|
|
|
|
}); err != nil {
|
|
|
|
if err == scripting.ErrReturnFalse {
|
|
|
|
if origPoint, ok := originalPositions[b.ID()]; ok {
|
|
|
|
// Trace a vector back from the actor's current position
|
|
|
|
// to where they originated from and find the earliest
|
|
|
|
// point where they are not violating the hitbox.
|
|
|
|
var (
|
|
|
|
rect = doodads.GetBoundingRect(b)
|
|
|
|
hitbox = a.Hitbox()
|
|
|
|
)
|
|
|
|
for point := range render.IterLine2(
|
|
|
|
b.Position(),
|
|
|
|
origPoint,
|
|
|
|
) {
|
|
|
|
test := render.Rect{
|
|
|
|
X: point.X,
|
|
|
|
Y: point.Y,
|
|
|
|
W: rect.W,
|
|
|
|
H: rect.H,
|
|
|
|
}
|
|
|
|
info, err := collision.CompareBoxes(
|
|
|
|
boxes[tuple.A],
|
|
|
|
test,
|
|
|
|
)
|
|
|
|
if err != nil || !info.Overlap.Intersects(hitbox) {
|
|
|
|
b.MoveTo(point)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log.Error(
|
|
|
|
"ERROR: Actors %s and %s overlap and the script returned false,"+
|
|
|
|
"but I didn't store %s original position earlier??",
|
|
|
|
a.Doodad.Title, b.Doodad.Title, b.Doodad.Title,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log.Error(err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for lacks of collisions since last frame.
|
|
|
|
for sourceID, targetID := range w.collidingActors {
|
|
|
|
if _, ok := collidingActors[sourceID]; !ok {
|
|
|
|
w.scripting.To(sourceID).Events.RunLeave(targetID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store this frame's colliding actors for next frame.
|
|
|
|
w.collidingActors = collidingActors
|
|
|
|
return nil
|
|
|
|
}
|