Return False: Solid Collision Between Actors
* Implement the handler code for `return false` when actors are colliding with each other and wish to act like solid walls. * The locked doors will `return false` when they're closed and the colliding actor does not have the matching key. * Add arbitrary key/value storage to Actors. The colored keys will set an actor value "key:%TITLE%" on the one who touched the key before destroying itself. The colored doors check that key when touched to decide whether to open. * The trapdoor now only opens if you're touching it from the top (your overlap box Y value is 0), but if you touch it from below and the door is closed, it acts like a solid object.
This commit is contained in:
parent
a2e1bd1ccb
commit
1523deeb9c
|
@ -10,6 +10,7 @@ function main() {
|
||||||
var animFrame = animStart;
|
var animFrame = animStart;
|
||||||
|
|
||||||
Self.SetGravity(true);
|
Self.SetGravity(true);
|
||||||
|
Self.SetHitbox(7, 4, 17, 28);
|
||||||
Self.AddAnimation("walk-left", 100, ["blu-wl1", "blu-wl2", "blu-wl3", "blu-wl4"]);
|
Self.AddAnimation("walk-left", 100, ["blu-wl1", "blu-wl2", "blu-wl3", "blu-wl4"]);
|
||||||
Self.AddAnimation("walk-right", 100, ["blu-wr1", "blu-wr2", "blu-wr3", "blu-wr4"]);
|
Self.AddAnimation("walk-right", 100, ["blu-wr1", "blu-wr2", "blu-wr3", "blu-wr4"]);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
function main() {
|
function main() {
|
||||||
Events.OnCollide(function(e) {
|
Events.OnCollide(function(e) {
|
||||||
|
console.log("%s picked up by %s", Self.Doodad.Title, e.Actor.Title);
|
||||||
|
e.Actor.SetData("key:" + Self.Doodad.Title, "true");
|
||||||
Self.Destroy();
|
Self.Destroy();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,25 +2,33 @@ function main() {
|
||||||
Self.AddAnimation("open", 0, [1]);
|
Self.AddAnimation("open", 0, [1]);
|
||||||
var unlocked = false;
|
var unlocked = false;
|
||||||
|
|
||||||
Events.OnCollide(function(e) {
|
// Map our door names to key names.
|
||||||
console.log("%s was touched by %s!", Self.Doodad.Title, e.Actor.ID());
|
var KeyMap = {
|
||||||
console.log("my box: %+v and theirs: %+v", Self.GetBoundingRect(), e.Actor.GetBoundingRect());
|
"Blue Door": "Blue Key",
|
||||||
console.warn("But the overlap is: %+v", e.Overlap);
|
"Red Door": "Red Key",
|
||||||
console.log(Object.keys(e));
|
"Green Door": "Green Key",
|
||||||
|
"Yellow Door": "Yellow Key"
|
||||||
if (e.Overlap.X + e.Overlap.W >= 16 && e.Overlap.X < 48) {
|
|
||||||
Self.Canvas.SetBackground(RGBA(255, 0, 0, 153));
|
|
||||||
} else {
|
|
||||||
Self.Canvas.SetBackground(RGBA(0, 255, 0, 153));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Warn("%s loaded!", Self.Doodad.Title);
|
||||||
|
console.log("%s Setting hitbox", Self.Doodad.Title);
|
||||||
|
Self.SetHitbox(16, 0, 32, 64);
|
||||||
|
|
||||||
|
Events.OnCollide(function(e) {
|
||||||
if (unlocked) {
|
if (unlocked) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (e.InHitbox) {
|
||||||
|
var data = e.Actor.GetData("key:" + KeyMap[Self.Doodad.Title]);
|
||||||
|
if (data === "") {
|
||||||
|
// Door is locked.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
unlocked = true;
|
unlocked = true;
|
||||||
Self.PlayAnimation("open", null);
|
Self.PlayAnimation("open", null);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
Events.OnLeave(function(e) {
|
Events.OnLeave(function(e) {
|
||||||
console.log("%s has stopped touching %s", e, Self.Doodad.Title)
|
console.log("%s has stopped touching %s", e, Self.Doodad.Title)
|
||||||
|
|
|
@ -3,6 +3,8 @@ function main() {
|
||||||
|
|
||||||
var timer = 0;
|
var timer = 0;
|
||||||
|
|
||||||
|
Self.SetHitbox(0, 0, 72, 9);
|
||||||
|
|
||||||
var animationSpeed = 100;
|
var animationSpeed = 100;
|
||||||
var opened = false;
|
var opened = false;
|
||||||
Self.AddAnimation("open", animationSpeed, ["down1", "down2", "down3", "down4"]);
|
Self.AddAnimation("open", animationSpeed, ["down1", "down2", "down3", "down4"]);
|
||||||
|
@ -13,18 +15,24 @@ function main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not touching the top of the door means door doesn't open.
|
// Is the actor colliding our solid part?
|
||||||
if (e.Overlap.Y > 9) {
|
if (e.InHitbox) {
|
||||||
return;
|
// Touching the top or the bottom?
|
||||||
}
|
if (e.Overlap.Y > 0) {
|
||||||
|
return false; // solid wall when touched from below
|
||||||
|
} else {
|
||||||
opened = true;
|
opened = true;
|
||||||
Self.PlayAnimation("open", function() {
|
Self.PlayAnimation("open", function() {
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Events.OnLeave(function() {
|
Events.OnLeave(function() {
|
||||||
|
if (opened) {
|
||||||
Self.PlayAnimation("close", function() {
|
Self.PlayAnimation("close", function() {
|
||||||
opened = false;
|
opened = false;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,3 +102,10 @@ func PrintCallers() {
|
||||||
fmt.Printf("%d: %s\n", i, caller)
|
fmt.Printf("%d: %s\n", i, caller)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pause until the user hits enter in the console.
|
||||||
|
func Pause() {
|
||||||
|
var x string
|
||||||
|
fmt.Print("Press enter to continue . . .")
|
||||||
|
fmt.Scanf("%s", &x)
|
||||||
|
}
|
||||||
|
|
|
@ -54,6 +54,16 @@ func TestIntersection(t *testing.T) {
|
||||||
B: newRect(0, -240, 874, 490),
|
B: newRect(0, -240, 874, 490),
|
||||||
Expect: false, // XXX: must be true
|
Expect: false, // XXX: must be true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
A: newRect(0, 30, 9, 62),
|
||||||
|
B: newRect(16, 0, 32, 64),
|
||||||
|
Expect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
A: newRect(0, 30, 11, 62),
|
||||||
|
B: newRect(7, 4, 17, 28),
|
||||||
|
Expect: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package collision
|
package collision
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/lib/render"
|
"git.kirsle.net/apps/doodle/lib/render"
|
||||||
|
@ -32,22 +33,11 @@ func BetweenBoxes(boxes []render.Rect) chan BoxCollision {
|
||||||
for i, box := range boxes {
|
for i, box := range boxes {
|
||||||
for j := i + 1; j < len(boxes); j++ {
|
for j := i + 1; j < len(boxes); j++ {
|
||||||
other := boxes[j]
|
other := boxes[j]
|
||||||
if box.Intersects(other) {
|
collision, err := CompareBoxes(box, other)
|
||||||
var (
|
if err == nil {
|
||||||
overlap = OverlapRelative(box, other)
|
collision.A = i
|
||||||
topLeft = overlap.TopLeft()
|
collision.B = j
|
||||||
bottomRight = overlap.BottomRight()
|
generator <- collision
|
||||||
)
|
|
||||||
generator <- BoxCollision{
|
|
||||||
A: i,
|
|
||||||
B: j,
|
|
||||||
Overlap: render.Rect{
|
|
||||||
X: topLeft.X,
|
|
||||||
Y: topLeft.Y,
|
|
||||||
W: bottomRight.X,
|
|
||||||
H: bottomRight.Y,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,6 +48,28 @@ func BetweenBoxes(boxes []render.Rect) chan BoxCollision {
|
||||||
return generator
|
return generator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CompareBoxes checks if two boxes overlaps and returns information about
|
||||||
|
// the overlap. The boxes are bounding rectangles like those given to
|
||||||
|
// BetweenBoxes().
|
||||||
|
func CompareBoxes(box, other render.Rect) (BoxCollision, error) {
|
||||||
|
if box.Intersects(other) {
|
||||||
|
var (
|
||||||
|
overlap = OverlapRelative(box, other)
|
||||||
|
topLeft = overlap.TopLeft()
|
||||||
|
bottomRight = overlap.BottomRight()
|
||||||
|
)
|
||||||
|
return BoxCollision{
|
||||||
|
Overlap: render.Rect{
|
||||||
|
X: topLeft.X,
|
||||||
|
Y: topLeft.Y,
|
||||||
|
W: bottomRight.X,
|
||||||
|
H: bottomRight.Y,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return BoxCollision{}, errors.New("boxes do not intersect")
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
OverlapRelative returns the Overlap box using coordinates relative
|
OverlapRelative returns the Overlap box using coordinates relative
|
||||||
to the source rect instead of absolute coordinates.
|
to the source rect instead of absolute coordinates.
|
||||||
|
|
|
@ -16,6 +16,10 @@ type Actor interface {
|
||||||
Grounded() bool
|
Grounded() bool
|
||||||
SetGrounded(bool)
|
SetGrounded(bool)
|
||||||
|
|
||||||
|
// Actor's elected hitbox set by their script.
|
||||||
|
SetHitbox(x, y, w, h int)
|
||||||
|
Hitbox() render.Rect
|
||||||
|
|
||||||
// Movement commands.
|
// Movement commands.
|
||||||
MoveBy(render.Point) // Add {X,Y} to current Position.
|
MoveBy(render.Point) // Add {X,Y} to current Position.
|
||||||
MoveTo(render.Point) // Set current Position to {X,Y}.
|
MoveTo(render.Point) // Set current Position to {X,Y}.
|
||||||
|
|
|
@ -13,6 +13,7 @@ type Doodad struct {
|
||||||
Palette *level.Palette `json:"palette"`
|
Palette *level.Palette `json:"palette"`
|
||||||
Script string `json:"script"`
|
Script string `json:"script"`
|
||||||
Layers []Layer `json:"layers"`
|
Layers []Layer `json:"layers"`
|
||||||
|
Tags map[string]string `json:"data"` // arbitrary key/value data storage
|
||||||
}
|
}
|
||||||
|
|
||||||
// Layer holds a layer of drawing data for a Doodad.
|
// Layer holds a layer of drawing data for a Doodad.
|
||||||
|
@ -38,6 +39,7 @@ func New(size int) *Doodad {
|
||||||
Chunker: level.NewChunker(size),
|
Chunker: level.NewChunker(size),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Tags: map[string]string{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ type Drawing struct {
|
||||||
velocity render.Point
|
velocity render.Point
|
||||||
accel int
|
accel int
|
||||||
size render.Rect
|
size render.Rect
|
||||||
|
hitbox render.Rect
|
||||||
grounded bool
|
grounded bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +76,21 @@ func (d *Drawing) SetGrounded(v bool) {
|
||||||
d.grounded = v
|
d.grounded = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetHitbox sets the actor's elected hitbox.
|
||||||
|
func (d *Drawing) SetHitbox(x, y, w, h int) {
|
||||||
|
d.hitbox = render.Rect{
|
||||||
|
X: int32(x),
|
||||||
|
Y: int32(y),
|
||||||
|
W: int32(w),
|
||||||
|
H: int32(h),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hitbox returns the actor's elected hitbox.
|
||||||
|
func (d *Drawing) Hitbox() render.Rect {
|
||||||
|
return d.hitbox
|
||||||
|
}
|
||||||
|
|
||||||
// MoveBy a relative value.
|
// MoveBy a relative value.
|
||||||
func (d *Drawing) MoveBy(by render.Point) {
|
func (d *Drawing) MoveBy(by render.Point) {
|
||||||
d.point.Add(by)
|
d.point.Add(by)
|
||||||
|
|
|
@ -29,6 +29,8 @@ type Actor struct {
|
||||||
|
|
||||||
// Actor runtime variables.
|
// Actor runtime variables.
|
||||||
hasGravity bool
|
hasGravity bool
|
||||||
|
hitbox render.Rect
|
||||||
|
data map[string]string
|
||||||
|
|
||||||
// Animation variables.
|
// Animation variables.
|
||||||
animations map[string]*Animation
|
animations map[string]*Animation
|
||||||
|
@ -78,6 +80,41 @@ func (a *Actor) GetBoundingRect() render.Rect {
|
||||||
return doodads.GetBoundingRect(a)
|
return doodads.GetBoundingRect(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetHitbox sets the actor's elected hitbox.
|
||||||
|
func (a *Actor) SetHitbox(x, y, w, h int) {
|
||||||
|
a.hitbox = render.Rect{
|
||||||
|
X: int32(x),
|
||||||
|
Y: int32(y),
|
||||||
|
W: int32(w),
|
||||||
|
H: int32(h),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hitbox returns the actor's elected hitbox.
|
||||||
|
func (a *Actor) Hitbox() render.Rect {
|
||||||
|
return a.hitbox
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetData sets an arbitrary field in the actor's K/V storage.
|
||||||
|
func (a *Actor) SetData(key, value string) {
|
||||||
|
if a.data == nil {
|
||||||
|
a.data = map[string]string{}
|
||||||
|
}
|
||||||
|
a.data[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetData gets an arbitrary field from the actor's K/V storage.
|
||||||
|
// Missing keys just return a blank string (friendly to the JavaScript
|
||||||
|
// environment).
|
||||||
|
func (a *Actor) GetData(key string) string {
|
||||||
|
if a.data == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
v, _ := a.data[key]
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
// LayerCount returns the number of layers in this actor's drawing.
|
// LayerCount returns the number of layers in this actor's drawing.
|
||||||
func (a *Actor) LayerCount() int {
|
func (a *Actor) LayerCount() int {
|
||||||
return len(a.Doodad.Layers)
|
return len(a.Doodad.Layers)
|
||||||
|
|
157
pkg/uix/actor_collision.go
Normal file
157
pkg/uix/actor_collision.go
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
package uix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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"
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/scripting"
|
||||||
|
"github.com/kirsle/blog/src/log"
|
||||||
|
"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.
|
||||||
|
//
|
||||||
|
// boxes: array of Actor bounding box rects.
|
||||||
|
func (w *Canvas) loopActorCollision() error {
|
||||||
|
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.
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
|
@ -6,4 +6,5 @@ import "git.kirsle.net/apps/doodle/lib/render"
|
||||||
type CollideEvent struct {
|
type CollideEvent struct {
|
||||||
Actor *Actor
|
Actor *Actor
|
||||||
Overlap render.Rect
|
Overlap render.Rect
|
||||||
|
InHitbox bool // If the two elected hitboxes are overlapping
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,20 +4,16 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/lib/events"
|
"git.kirsle.net/apps/doodle/lib/events"
|
||||||
"git.kirsle.net/apps/doodle/lib/render"
|
"git.kirsle.net/apps/doodle/lib/render"
|
||||||
"git.kirsle.net/apps/doodle/lib/ui"
|
"git.kirsle.net/apps/doodle/lib/ui"
|
||||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
"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/doodads"
|
||||||
"git.kirsle.net/apps/doodle/pkg/level"
|
"git.kirsle.net/apps/doodle/pkg/level"
|
||||||
"git.kirsle.net/apps/doodle/pkg/log"
|
"git.kirsle.net/apps/doodle/pkg/log"
|
||||||
"git.kirsle.net/apps/doodle/pkg/scripting"
|
"git.kirsle.net/apps/doodle/pkg/scripting"
|
||||||
"git.kirsle.net/apps/doodle/pkg/wallpaper"
|
"git.kirsle.net/apps/doodle/pkg/wallpaper"
|
||||||
"github.com/robertkrimen/otto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Canvas is a custom ui.Widget that manages a single drawing.
|
// Canvas is a custom ui.Widget that manages a single drawing.
|
||||||
|
@ -178,7 +174,7 @@ func (w *Canvas) Loop(ev *events.State) error {
|
||||||
_ = w.loopConstrainScroll()
|
_ = w.loopConstrainScroll()
|
||||||
|
|
||||||
// Current time of this loop so we can advance animations.
|
// Current time of this loop so we can advance animations.
|
||||||
now := time.Now()
|
// now := time.Now()
|
||||||
|
|
||||||
// Remove any actors that were destroyed the previous tick.
|
// Remove any actors that were destroyed the previous tick.
|
||||||
var newActors []*Actor
|
var newActors []*Actor
|
||||||
|
@ -192,92 +188,10 @@ func (w *Canvas) Loop(ev *events.State) error {
|
||||||
w.actors = newActors
|
w.actors = newActors
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move any actors. As we iterate over all actors, track their bounding
|
|
||||||
// rectangles so we can later see if any pair of actors intersect each other.
|
|
||||||
boxes := make([]render.Rect, len(w.actors))
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for i, a := range w.actors {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(i int, a *Actor) {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
}
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check collisions between actors.
|
// Check collisions between actors.
|
||||||
var collidingActors = map[string]string{}
|
if err := w.loopActorCollision(); err != nil {
|
||||||
for tuple := range collision.BetweenBoxes(boxes) {
|
log.Error("loopActorCollision: %s", err)
|
||||||
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,
|
|
||||||
}); err != nil {
|
|
||||||
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
|
|
||||||
|
|
||||||
// If the canvas is editable, only care if it's over our space.
|
// If the canvas is editable, only care if it's over our space.
|
||||||
if w.Editable {
|
if w.Editable {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user