Script Timers, Multiple Doodad Frames
* CLI: fix the `doodad convert` command to share the same Palette when converting each frame (layer) of a doodad so subsequent layers find the correct color swatches for serialization. * Scripting: add timers and intervals to Doodad scripts to allow them to animate themselves or add delayed callbacks. The timers have the same API as a web browser: setTimeout(), setInterval(), clearTimeout(), clearInterval(). * Add support for uix.Actor to change its currently rendered layer in the level. For example a Button Doodad can set its image to Layer 1 (pressed) when touched by the player, and Trapdoors can cycle through their layers to animate opening and closing. * Usage from a Doodad script: Self.ShowLayer(1) * Default Doodads: added scripts for all Buttons, Doors, Keys and the Trapdoor to run their various animations when touched (in the case of Keys, destroy themselves when touched, because there is no player inventory yet)
This commit is contained in:
parent
81cb3bd617
commit
258b2eb285
|
@ -148,14 +148,16 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
|
|||
doodad.Author = os.Getenv("USER")
|
||||
|
||||
// Write the first layer and gather its palette.
|
||||
palette, layer0 := imageToChunker(images[0], chroma, chunkSize)
|
||||
log.Info("Converting first layer to drawing and getting the palette")
|
||||
palette, layer0 := imageToChunker(images[0], chroma, nil, chunkSize)
|
||||
doodad.Palette = palette
|
||||
doodad.Layers[0].Chunker = layer0
|
||||
|
||||
// Write any additional layers.
|
||||
if len(images) > 1 {
|
||||
for i, img := range images[1:] {
|
||||
_, chunker := imageToChunker(img, chroma, chunkSize)
|
||||
log.Info("Converting extra layer %d", i+1)
|
||||
_, chunker := imageToChunker(img, chroma, palette, chunkSize)
|
||||
doodad.Layers = append(doodad.Layers, doodads.Layer{
|
||||
Name: fmt.Sprintf("layer-%d", i+1),
|
||||
Chunker: chunker,
|
||||
|
@ -180,7 +182,7 @@ func imageToDrawing(c *cli.Context, chroma render.Color, inputFiles []string, ou
|
|||
lvl.Title = "Converted Level"
|
||||
}
|
||||
lvl.Author = os.Getenv("USER")
|
||||
palette, chunker := imageToChunker(images[0], chroma, lvl.Chunker.Size)
|
||||
palette, chunker := imageToChunker(images[0], chroma, nil, lvl.Chunker.Size)
|
||||
lvl.Palette = palette
|
||||
lvl.Chunker = chunker
|
||||
|
||||
|
@ -275,15 +277,21 @@ func drawingToImage(c *cli.Context, chroma render.Color, inputFiles []string, ou
|
|||
//
|
||||
// img: input image like a PNG
|
||||
// chroma: transparent color
|
||||
func imageToChunker(img image.Image, chroma render.Color, chunkSize int) (*level.Palette, *level.Chunker) {
|
||||
func imageToChunker(img image.Image, chroma render.Color, palette *level.Palette, chunkSize int) (*level.Palette, *level.Chunker) {
|
||||
var (
|
||||
palette = level.NewPalette()
|
||||
chunker = level.NewChunker(chunkSize)
|
||||
bounds = img.Bounds()
|
||||
)
|
||||
|
||||
if palette == nil {
|
||||
palette = level.NewPalette()
|
||||
}
|
||||
|
||||
// Cache a palette of unique colors as we go.
|
||||
var uniqueColor = map[string]*level.Swatch{}
|
||||
for _, swatch := range palette.Swatches {
|
||||
uniqueColor[swatch.Color.String()] = swatch
|
||||
}
|
||||
|
||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
||||
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
function main() {
|
||||
console.log("Sticky Button initialized!");
|
||||
console.log("%s initialized!", Self.Doodad.Title);
|
||||
|
||||
var timer = 0;
|
||||
|
||||
Events.OnCollide( function() {
|
||||
console.log("Touched!");
|
||||
Self.Canvas.SetBackground(RGBA(255, 153, 0, 153))
|
||||
if (timer > 0) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
|
||||
Self.ShowLayer(1);
|
||||
timer = setTimeout(function() {
|
||||
Self.ShowLayer(0);
|
||||
timer = 0;
|
||||
}, 200);
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
function main() {
|
||||
console.log("Sticky Button initialized!");
|
||||
console.log("%s initialized!", Self.Doodad.Title);
|
||||
|
||||
Events.OnCollide( function() {
|
||||
console.log("Touched!");
|
||||
Self.Canvas.SetBackground(RGBA(255, 153, 0, 153))
|
||||
Self.ShowLayer(1);
|
||||
})
|
||||
}
|
||||
|
|
35
dev-assets/doodads/doors/electric-door.js
Normal file
35
dev-assets/doodads/doors/electric-door.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
function main() {
|
||||
console.log("%s initialized!", Self.Doodad.Title);
|
||||
|
||||
var timer = 0;
|
||||
|
||||
// Animation frames.
|
||||
var frame = 0;
|
||||
var frames = Self.LayerCount();
|
||||
var animationDirection = 1; // forward or backward
|
||||
var animationSpeed = 100; // interval between frames when animating
|
||||
var animating = false; // true if animation is actively happening
|
||||
|
||||
console.warn("Electric Door has %d frames", frames);
|
||||
|
||||
// Animation interval function.
|
||||
setInterval(function() {
|
||||
if (!animating) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Advance the frame forwards or backwards.
|
||||
frame += animationDirection;
|
||||
if (frame >= frames) {
|
||||
// Reached the last frame, start the pause and reverse direction.
|
||||
animating = false;
|
||||
frame = frames - 1;
|
||||
}
|
||||
|
||||
Self.ShowLayer(frame);
|
||||
}, animationSpeed);
|
||||
|
||||
Events.OnCollide( function() {
|
||||
animating = true; // start the animation
|
||||
})
|
||||
}
|
5
dev-assets/doodads/doors/keys.js
Normal file
5
dev-assets/doodads/doors/keys.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
function main() {
|
||||
Events.OnCollide(function(e) {
|
||||
Self.Destroy();
|
||||
})
|
||||
}
|
5
dev-assets/doodads/doors/locked-door.js
Normal file
5
dev-assets/doodads/doors/locked-door.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
function main() {
|
||||
Events.OnCollide(function(e) {
|
||||
Self.ShowLayer(1);
|
||||
});
|
||||
}
|
55
dev-assets/doodads/trapdoors/down.js
Normal file
55
dev-assets/doodads/trapdoors/down.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
function main() {
|
||||
console.log("%s initialized!", Self.Doodad.Title);
|
||||
|
||||
var timer = 0;
|
||||
|
||||
// Animation frames.
|
||||
var frame = 0;
|
||||
var frames = Self.LayerCount();
|
||||
var animationDirection = 1; // forward or backward
|
||||
var animationSpeed = 100; // interval between frames when animating
|
||||
var animationDelay = 8; // delay ticks at the end before reversing, in
|
||||
// multiples of animationSpeed
|
||||
var delayCountdown = 0;
|
||||
var animating = false; // true if animation is actively happening
|
||||
|
||||
console.warn("Trapdoor has %d frames", frames);
|
||||
|
||||
// Animation interval function.
|
||||
setInterval(function() {
|
||||
if (!animating) {
|
||||
return;
|
||||
}
|
||||
|
||||
// At the end of the animation (door is open), delay before resuming
|
||||
// the close animation.
|
||||
if (delayCountdown > 0) {
|
||||
delayCountdown--;
|
||||
return;
|
||||
}
|
||||
|
||||
// Advance the frame forwards or backwards.
|
||||
frame += animationDirection;
|
||||
if (frame >= frames) {
|
||||
// Reached the last frame, start the pause and reverse direction.
|
||||
delayCountdown = animationDelay;
|
||||
animationDirection = -1;
|
||||
|
||||
// also bounds check it
|
||||
frame = frames - 1;
|
||||
}
|
||||
|
||||
if (frame < 0) {
|
||||
// reached the start again
|
||||
frame = 0;
|
||||
animationDirection = 1;
|
||||
animating = false;
|
||||
}
|
||||
|
||||
Self.ShowLayer(frame);
|
||||
}, animationSpeed);
|
||||
|
||||
Events.OnCollide( function() {
|
||||
animating = true; // start the animation
|
||||
})
|
||||
}
|
|
@ -2,7 +2,6 @@ package collision
|
|||
|
||||
import (
|
||||
"git.kirsle.net/apps/doodle/lib/render"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
)
|
||||
|
||||
// IndexTuple holds two integers used as array indexes.
|
||||
|
@ -21,7 +20,6 @@ func BetweenBoxes(boxes []render.Rect) chan IndexTuple {
|
|||
for i, box := range boxes {
|
||||
for j := i + 1; j < len(boxes); j++ {
|
||||
if box.Intersects(boxes[j]) {
|
||||
log.Info("Actor %d intersects %d", i, j)
|
||||
generator <- IndexTuple{i, j}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -181,14 +181,6 @@ func (s *EditorScene) LoadLevel(filename string) error {
|
|||
s.Level = level
|
||||
s.UI.Canvas.LoadLevel(s.d.Engine, s.Level)
|
||||
|
||||
// TODO: debug
|
||||
for i, actor := range level.Actors {
|
||||
log.Info("Actor %s is a %s", i, actor.ID())
|
||||
}
|
||||
for name, file := range level.Files {
|
||||
log.Info("File %s has: %s", name, file.Data)
|
||||
}
|
||||
|
||||
log.Info("Installing %d actors into the drawing", len(level.Actors))
|
||||
if err := s.UI.Canvas.InstallActors(level.Actors); err != nil {
|
||||
return fmt.Errorf("EditorScene.LoadLevel: InstallActors: %s", err)
|
||||
|
|
|
@ -18,7 +18,7 @@ const maxSamples = 100
|
|||
// like: boolProp DebugOverlay true
|
||||
var (
|
||||
DebugOverlay = true
|
||||
DebugCollision = true
|
||||
DebugCollision = false
|
||||
|
||||
DebugTextPadding int32 = 8
|
||||
DebugTextSize = 24
|
||||
|
|
|
@ -130,7 +130,11 @@ func (s *PlayScene) Loop(d *Doodle, ev *events.State) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// s.drawing.Loop(ev)
|
||||
// Loop the script supervisor so timeouts/intervals can fire in scripts.
|
||||
if err := s.scripting.Loop(); err != nil {
|
||||
log.Error("PlayScene.Loop: scripting.Loop: %s", err)
|
||||
}
|
||||
|
||||
s.movePlayer(ev)
|
||||
if err := s.drawing.Loop(ev); err != nil {
|
||||
log.Error("Drawing loop error: %s", err.Error())
|
||||
|
@ -147,16 +151,6 @@ func (s *PlayScene) Draw(d *Doodle) error {
|
|||
// Draw the level.
|
||||
s.drawing.Present(d.Engine, s.drawing.Point())
|
||||
|
||||
// Draw our hero. TODO: this draws a yellow box using the player's World
|
||||
// Position as tho it were Screen Position. The player has its own canvas
|
||||
// currently drawn in red
|
||||
d.Engine.DrawBox(render.RGBA(255, 255, 153, 64), render.Rect{
|
||||
X: s.Player.Position().X,
|
||||
Y: s.Player.Position().Y,
|
||||
W: s.Player.Size().W,
|
||||
H: s.Player.Size().H,
|
||||
})
|
||||
|
||||
// Draw out bounding boxes.
|
||||
d.DrawCollisionBox(s.Player)
|
||||
|
||||
|
|
|
@ -4,6 +4,16 @@ import (
|
|||
"github.com/robertkrimen/otto"
|
||||
)
|
||||
|
||||
// Event name constants.
|
||||
const (
|
||||
CollideEvent = "OnCollide" // another doodad collides with us
|
||||
EnterEvent = "OnEnter" // a doodad is fully inside us
|
||||
LeaveEvent = "OnLeave" // a doodad no longer collides with us
|
||||
|
||||
// Controllable (player character) doodad events
|
||||
KeypressEvent = "OnKeypress" // i.e. arrow keys
|
||||
)
|
||||
|
||||
// Events API for Doodad scripts.
|
||||
type Events struct {
|
||||
registry map[string][]otto.Value
|
||||
|
@ -18,27 +28,37 @@ func NewEvents() *Events {
|
|||
|
||||
// OnCollide fires when another actor collides with yours.
|
||||
func (e *Events) OnCollide(call otto.FunctionCall) otto.Value {
|
||||
callback := call.Argument(0)
|
||||
if !callback.IsFunction() {
|
||||
return otto.Value{} // TODO
|
||||
}
|
||||
|
||||
if _, ok := e.registry[CollideEvent]; !ok {
|
||||
e.registry[CollideEvent] = []otto.Value{}
|
||||
}
|
||||
|
||||
e.registry[CollideEvent] = append(e.registry[CollideEvent], callback)
|
||||
return otto.Value{}
|
||||
return e.register(CollideEvent, call.Argument(0))
|
||||
}
|
||||
|
||||
// RunCollide invokes the OnCollide handler function.
|
||||
func (e *Events) RunCollide() error {
|
||||
if _, ok := e.registry[CollideEvent]; !ok {
|
||||
return e.run(CollideEvent)
|
||||
}
|
||||
|
||||
// register a named event.
|
||||
func (e *Events) register(name string, callback otto.Value) otto.Value {
|
||||
if !callback.IsFunction() {
|
||||
return otto.Value{} // TODO
|
||||
}
|
||||
|
||||
if _, ok := e.registry[name]; !ok {
|
||||
e.registry[name] = []otto.Value{}
|
||||
}
|
||||
|
||||
e.registry[name] = append(e.registry[name], callback)
|
||||
return otto.Value{}
|
||||
}
|
||||
|
||||
// Run an event handler. Returns an error only if there was a JavaScript error
|
||||
// inside the function. If there are no event handlers, just returns nil.
|
||||
func (e *Events) run(name string, args ...interface{}) error {
|
||||
if _, ok := e.registry[name]; !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, callback := range e.registry[CollideEvent] {
|
||||
_, err := callback.Call(otto.Value{}, "test argument")
|
||||
for _, callback := range e.registry[name] {
|
||||
_, err := callback.Call(otto.Value{}, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -46,8 +66,3 @@ func (e *Events) RunCollide() error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Event name constants.
|
||||
const (
|
||||
CollideEvent = "collide"
|
||||
)
|
||||
|
|
|
@ -5,6 +5,7 @@ package scripting
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/level"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
|
@ -23,6 +24,15 @@ func NewSupervisor() *Supervisor {
|
|||
}
|
||||
}
|
||||
|
||||
// Loop the supervisor to invoke timer events in any running scripts.
|
||||
func (s *Supervisor) Loop() error {
|
||||
now := time.Now()
|
||||
for _, vm := range s.scripts {
|
||||
vm.TickTimer(now)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InstallScripts loads scripts for all actors in the level.
|
||||
func (s *Supervisor) InstallScripts(level *level.Level) error {
|
||||
for _, actor := range level.Actors {
|
||||
|
@ -47,9 +57,11 @@ func (s *Supervisor) To(name string) *VM {
|
|||
return vm
|
||||
}
|
||||
|
||||
log.Error("scripting.Supervisor.To(%s): no such VM but returning blank VM",
|
||||
name,
|
||||
)
|
||||
// TODO: put this log back in, but add PLAYER script so it doesn't spam
|
||||
// the console for missing PLAYER.
|
||||
// log.Error("scripting.Supervisor.To(%s): no such VM but returning blank VM",
|
||||
// name,
|
||||
// )
|
||||
return NewVM(name)
|
||||
}
|
||||
|
||||
|
|
105
pkg/scripting/timers.go
Normal file
105
pkg/scripting/timers.go
Normal file
|
@ -0,0 +1,105 @@
|
|||
package scripting
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/robertkrimen/otto"
|
||||
)
|
||||
|
||||
// Timer keeps track of delayed function calls for the scripting engine.
|
||||
type Timer struct {
|
||||
id int
|
||||
callback otto.Value
|
||||
interval time.Duration // milliseconds delay for timeout
|
||||
next time.Time // scheduled time for next invocation
|
||||
repeat bool // for setInterval
|
||||
}
|
||||
|
||||
/*
|
||||
SetTimeout registers a callback function to be run after a while.
|
||||
|
||||
This is to be called by JavaScript running in the VM and has an API similar to
|
||||
that found in web browsers.
|
||||
|
||||
The callback is a JavaScript function and the interval is in milliseconds,
|
||||
with 1000 being 'one second.'
|
||||
|
||||
Returns the ID number of the timer in case you want to clear it. The underlying
|
||||
Timer type is NOT exposed to JavaScript.
|
||||
*/
|
||||
func (vm *VM) SetTimeout(callback otto.Value, interval int) int {
|
||||
return vm.AddTimer(callback, interval, false)
|
||||
}
|
||||
|
||||
/*
|
||||
SetInterval registers a callback function to be run repeatedly.
|
||||
|
||||
Returns the ID number of the timer in case you want to clear it. The underlying
|
||||
Timer type is NOT exposed to JavaScript.
|
||||
*/
|
||||
func (vm *VM) SetInterval(callback otto.Value, interval int) int {
|
||||
return vm.AddTimer(callback, interval, true)
|
||||
}
|
||||
|
||||
/*
|
||||
AddTimer loads timeouts and intervals into the VM's memory and returns the ID.
|
||||
*/
|
||||
func (vm *VM) AddTimer(callback otto.Value, interval int, repeat bool) int {
|
||||
// Get the next timer ID. The first timer has ID 1.
|
||||
vm.timerLastID++
|
||||
id := vm.timerLastID
|
||||
|
||||
t := &Timer{
|
||||
id: id,
|
||||
callback: callback,
|
||||
interval: time.Duration(interval),
|
||||
repeat: repeat,
|
||||
}
|
||||
t.Schedule()
|
||||
vm.timers[id] = t
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
// TickTimer checks if any timers are ready and calls their functions.
|
||||
func (vm *VM) TickTimer(now time.Time) {
|
||||
if len(vm.timers) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// IDs of expired timeouts to clear.
|
||||
var clear []int
|
||||
|
||||
for id, timer := range vm.timers {
|
||||
if now.After(timer.next) {
|
||||
timer.callback.Call(otto.Value{})
|
||||
if timer.repeat {
|
||||
timer.Schedule()
|
||||
} else {
|
||||
clear = append(clear, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up expired timers.
|
||||
if len(clear) > 0 {
|
||||
for _, id := range clear {
|
||||
delete(vm.timers, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
ClearTimer will clear both timeouts and intervals.
|
||||
|
||||
In the JavaScript VM this function is bound to clearTimeout() and clearInterval()
|
||||
to expose an API like that seen in web browsers.
|
||||
*/
|
||||
func (vm *VM) ClearTimer(id int) {
|
||||
delete(vm.timers, id)
|
||||
}
|
||||
|
||||
// Schedule the callback to be run in the future.
|
||||
func (t *Timer) Schedule() {
|
||||
t.next = time.Now().Add(t.interval * time.Millisecond)
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package scripting
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.kirsle.net/apps/doodle/lib/render"
|
||||
|
@ -18,6 +17,10 @@ type VM struct {
|
|||
Self interface{}
|
||||
|
||||
vm *otto.Otto
|
||||
|
||||
// setTimeout and setInterval variables.
|
||||
timerLastID int // becomes 1 when first timer is set
|
||||
timers map[int]*Timer
|
||||
}
|
||||
|
||||
// NewVM creates a new JavaScript VM.
|
||||
|
@ -26,6 +29,7 @@ func NewVM(name string) *VM {
|
|||
Name: name,
|
||||
Events: NewEvents(),
|
||||
vm: otto.New(),
|
||||
timers: map[int]*Timer{},
|
||||
}
|
||||
return vm
|
||||
}
|
||||
|
@ -48,8 +52,14 @@ func (vm *VM) RegisterLevelHooks() error {
|
|||
"log": log.Logger,
|
||||
"RGBA": render.RGBA,
|
||||
"Point": render.NewPoint,
|
||||
"Self": vm.Self,
|
||||
"Self": vm.Self, // i.e., the uix.Actor object
|
||||
"Events": vm.Events,
|
||||
|
||||
// Timer functions with APIs similar to the web browsers.
|
||||
"setTimeout": vm.SetTimeout,
|
||||
"setInterval": vm.SetInterval,
|
||||
"clearTimeout": vm.ClearTimer,
|
||||
"clearInterval": vm.ClearTimer,
|
||||
}
|
||||
for name, v := range bindings {
|
||||
err := vm.vm.Set(name, v)
|
||||
|
@ -59,7 +69,15 @@ func (vm *VM) RegisterLevelHooks() error {
|
|||
)
|
||||
}
|
||||
}
|
||||
vm.vm.Run(`console = {}; console.log = log.Info;`)
|
||||
|
||||
// Alias the console.log functions to the logger.
|
||||
vm.vm.Run(`
|
||||
console = {};
|
||||
console.log = log.Info;
|
||||
console.debug = log.Debug;
|
||||
console.warn = log.Warn;
|
||||
console.error = log.Error;
|
||||
`)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -71,7 +89,7 @@ func (vm *VM) Main() error {
|
|||
}
|
||||
|
||||
if !function.IsFunction() {
|
||||
return errors.New("main is not a function")
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = function.Call(otto.Value{})
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package uix
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.kirsle.net/apps/doodle/lib/render"
|
||||
"git.kirsle.net/apps/doodle/pkg/doodads"
|
||||
"git.kirsle.net/apps/doodle/pkg/level"
|
||||
|
@ -19,6 +22,9 @@ type Actor struct {
|
|||
doodads.Drawing
|
||||
Actor *level.Actor
|
||||
Canvas *Canvas
|
||||
|
||||
activeLayer int // active drawing frame for display
|
||||
flagDestroy bool // flag the actor for destruction
|
||||
}
|
||||
|
||||
// NewActor sets up a uix.Actor.
|
||||
|
@ -51,3 +57,26 @@ func NewActor(id string, levelActor *level.Actor, doodad *doodads.Doodad) *Actor
|
|||
|
||||
return actor
|
||||
}
|
||||
|
||||
// LayerCount returns the number of layers in this actor's drawing.
|
||||
func (a *Actor) LayerCount() int {
|
||||
return len(a.Doodad.Layers)
|
||||
}
|
||||
|
||||
// ShowLayer sets the actor's ActiveLayer to the index given.
|
||||
func (a *Actor) ShowLayer(index int) error {
|
||||
if index < 0 {
|
||||
return errors.New("layer index must be 0 or greater")
|
||||
} else if index > len(a.Doodad.Layers) {
|
||||
return fmt.Errorf("layer %d out of range for doodad's layers", index)
|
||||
}
|
||||
|
||||
a.activeLayer = index
|
||||
a.Canvas.Load(a.Doodad.Palette, a.Doodad.Layers[index].Chunker)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy deletes the actor from the running level.
|
||||
func (a *Actor) Destroy() {
|
||||
a.flagDestroy = true
|
||||
}
|
||||
|
|
|
@ -173,6 +173,18 @@ func (w *Canvas) Loop(ev *events.State) error {
|
|||
log.Debug("loopConstrainScroll: %s", err)
|
||||
}
|
||||
|
||||
// Remove any actors that were destroyed the previous tick.
|
||||
var newActors []*Actor
|
||||
for _, a := range w.actors {
|
||||
if a.flagDestroy {
|
||||
continue
|
||||
}
|
||||
newActors = append(newActors, a)
|
||||
}
|
||||
if len(newActors) < len(w.actors) {
|
||||
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))
|
||||
|
@ -195,7 +207,6 @@ func (w *Canvas) Loop(ev *events.State) error {
|
|||
info, ok := collision.CollidesWithGrid(a, w.chunks, delta)
|
||||
if ok {
|
||||
// Collision happened with world.
|
||||
log.Error("COLLIDE %+v", info)
|
||||
}
|
||||
delta = info.MoveTo // Move us back where the collision check put us
|
||||
|
||||
|
@ -211,7 +222,7 @@ func (w *Canvas) Loop(ev *events.State) error {
|
|||
|
||||
// Check collisions between actors.
|
||||
for tuple := range collision.BetweenBoxes(boxes) {
|
||||
log.Error("Actor %s collides with %s",
|
||||
log.Debug("Actor %s collides with %s",
|
||||
w.actors[tuple[0]].ID(),
|
||||
w.actors[tuple[1]].ID(),
|
||||
)
|
||||
|
@ -219,8 +230,12 @@ func (w *Canvas) Loop(ev *events.State) error {
|
|||
|
||||
// Call the OnCollide handler.
|
||||
if w.scripting != nil {
|
||||
w.scripting.To(a.ID()).Events.RunCollide()
|
||||
w.scripting.To(b.ID()).Events.RunCollide()
|
||||
if err := w.scripting.To(a.ID()).Events.RunCollide(); err != nil {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
if err := w.scripting.To(b.ID()).Events.RunCollide(); err != nil {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ func (w *Canvas) InstallScripts() error {
|
|||
vm.Run(actor.Drawing.Doodad.Script)
|
||||
|
||||
// Call the main() function.
|
||||
log.Error("Calling Main() for %s", actor.ID())
|
||||
log.Debug("Calling Main() for %s", actor.ID())
|
||||
if err := vm.Main(); err != nil {
|
||||
log.Error("main() for actor %s errored: %s", actor.ID(), err)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user