doodle/pkg/play_scene.go
Noah Petherbridge 258b2eb285 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)
2019-04-18 18:15:05 -07:00

215 lines
5.3 KiB
Go

package doodle
import (
"fmt"
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render"
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/doodads/dummy"
"git.kirsle.net/apps/doodle/pkg/level"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/apps/doodle/pkg/scripting"
"git.kirsle.net/apps/doodle/pkg/uix"
)
// PlayScene manages the "Edit Level" game mode.
type PlayScene struct {
// Configuration attributes.
Filename string
Level *level.Level
// Private variables.
d *Doodle
drawing *uix.Canvas
scripting *scripting.Supervisor
// Custom debug labels.
debPosition *string
debViewport *string
debScroll *string
debWorldIndex *string
// Player character
Player *uix.Actor
}
// Name of the scene.
func (s *PlayScene) Name() string {
return "Play"
}
// Setup the play scene.
func (s *PlayScene) Setup(d *Doodle) error {
s.d = d
s.scripting = scripting.NewSupervisor()
// Initialize debug overlay values.
s.debPosition = new(string)
s.debViewport = new(string)
s.debScroll = new(string)
s.debWorldIndex = new(string)
customDebugLabels = []debugLabel{
{"Pixel:", s.debWorldIndex},
{"Player:", s.debPosition},
{"Viewport:", s.debViewport},
{"Scroll:", s.debScroll},
}
// Initialize the drawing canvas.
s.drawing = uix.NewCanvas(balance.ChunkSize, false)
s.drawing.Name = "play-canvas"
s.drawing.MoveTo(render.Origin)
s.drawing.Resize(render.NewRect(int32(d.width), int32(d.height)))
s.drawing.Compute(d.Engine)
// Given a filename or map data to play?
if s.Level != nil {
log.Debug("PlayScene.Setup: received level from scene caller")
s.drawing.LoadLevel(d.Engine, s.Level)
s.drawing.InstallActors(s.Level.Actors)
} else if s.Filename != "" {
log.Debug("PlayScene.Setup: loading map from file %s", s.Filename)
// NOTE: s.LoadLevel also calls s.drawing.InstallActors
s.LoadLevel(s.Filename)
}
if s.Level == nil {
log.Debug("PlayScene.Setup: no grid given, initializing empty grid")
s.Level = level.New()
s.drawing.LoadLevel(d.Engine, s.Level)
s.drawing.InstallActors(s.Level.Actors)
}
// Load all actor scripts.
s.drawing.SetScriptSupervisor(s.scripting)
if err := s.scripting.InstallScripts(s.Level); err != nil {
log.Error("PlayScene.Setup: failed to InstallScripts: %s", err)
}
if err := s.drawing.InstallScripts(); err != nil {
log.Error("PlayScene.Setup: failed to drawing.InstallScripts: %s", err)
}
player := dummy.NewPlayer()
s.Player = uix.NewActor(player.ID(), &level.Actor{}, player.Doodad)
s.Player.MoveTo(render.NewPoint(128, 128))
s.drawing.AddActor(s.Player)
s.drawing.FollowActor = s.Player.ID()
d.Flash("Entered Play Mode. Press 'E' to edit this map.")
return nil
}
// Loop the editor scene.
func (s *PlayScene) Loop(d *Doodle, ev *events.State) error {
// Update debug overlay values.
*s.debWorldIndex = s.drawing.WorldIndexAt(render.NewPoint(ev.CursorX.Now, ev.CursorY.Now)).String()
*s.debPosition = s.Player.Position().String() + " vel " + s.Player.Velocity().String()
*s.debViewport = s.drawing.Viewport().String()
*s.debScroll = s.drawing.Scroll.String()
// Has the window been resized?
if resized := ev.Resized.Read(); resized {
w, h := d.Engine.WindowSize()
if w != d.width || h != d.height {
d.width = w
d.height = h
s.drawing.Resize(render.NewRect(int32(d.width), int32(d.height)))
return nil
}
}
// Switching to Edit Mode?
if ev.KeyName.Read() == "e" {
log.Info("Edit Mode, Go!")
d.Goto(&EditorScene{
Filename: s.Filename,
Level: s.Level,
})
return nil
}
// 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())
}
return nil
}
// Draw the pixels on this frame.
func (s *PlayScene) Draw(d *Doodle) error {
// Clear the canvas and fill it with white.
d.Engine.Clear(render.White)
// Draw the level.
s.drawing.Present(d.Engine, s.drawing.Point())
// Draw out bounding boxes.
d.DrawCollisionBox(s.Player)
return nil
}
// movePlayer updates the player's X,Y coordinate based on key pressed.
func (s *PlayScene) movePlayer(ev *events.State) {
var playerSpeed = int32(balance.PlayerMaxVelocity)
var gravity = int32(balance.Gravity)
var velocity render.Point
if ev.Down.Now {
velocity.Y = playerSpeed
}
if ev.Left.Now {
velocity.X = -playerSpeed
}
if ev.Right.Now {
velocity.X = playerSpeed
}
if ev.Up.Now {
velocity.Y = -playerSpeed
}
// Apply gravity if not grounded.
if !s.Player.Grounded() {
// Gravity has to pipe through the collision checker, too, so it
// can't give us a cheated downward boost.
velocity.Y += gravity
}
s.Player.SetVelocity(velocity)
}
// Drawing returns the private world drawing, for debugging with the console.
func (s *PlayScene) Drawing() *uix.Canvas {
return s.drawing
}
// LoadLevel loads a level from disk.
func (s *PlayScene) LoadLevel(filename string) error {
s.Filename = filename
level, err := level.LoadJSON(filename)
if err != nil {
return fmt.Errorf("PlayScene.LoadLevel(%s): %s", filename, err)
}
s.Level = level
s.drawing.LoadLevel(s.d.Engine, s.Level)
// s.drawing.InstallActors(s.Level.Actors)
return nil
}
// Destroy the scene.
func (s *PlayScene) Destroy() error {
return nil
}