Centralized Tick Counter, Fix Actor Dragging Bug

* The game's tick counter was moved from Doodle.ticks to shmem.Tick
  where it is more easily available from every corner of the code.
* Fix a bug in the Level Editor where dragging an already-existing actor
  from one part of your map to another, would cause it to lose all its
  data (especially its UUID), breaking links to other doodads. Now the
  existing Actor catches a ride on the drag object to be reinserted
  later.
* Animate the Link Line visualizers between actors. They now animate a
  blinking color between magenta and grey-ish.
This commit is contained in:
Noah 2019-07-05 16:04:36 -07:00
parent dc2695cfc9
commit a504658055
12 changed files with 107 additions and 32 deletions

11
TODO.md
View File

@ -3,8 +3,7 @@
## Alpha Launch Minimum Checklist ## Alpha Launch Minimum Checklist
- [ ] Open Source Licenses - [ ] Open Source Licenses
- [ ] Doodad Scripts: an "end level" function for a level goalpost. - [x] Doodad Scripts: an "end level" function for a level goalpost.
**Blocker Bugs:** **Blocker Bugs:**
@ -16,7 +15,7 @@
- Doodads Palette: - Doodads Palette:
- [ ] Hide some doodads like the player character. - [ ] Hide some doodads like the player character.
- [ ] Pagination or scrolling UI for long lists of doodads. - [x] Pagination or scrolling UI for long lists of doodads.
**Nice to haves:** **Nice to haves:**
@ -27,8 +26,8 @@
- [ ] Single-player "campaign mode" of built-in levels. - [ ] Single-player "campaign mode" of built-in levels.
- campaign.json file format configuring the level order - campaign.json file format configuring the level order
- [ ] Level Editor Improvements - [ ] Level Editor Improvements
- [ ] Undo/Redo Function - [x] Undo/Redo Function
- [ ] Lines and Boxes - [x] Lines and Boxes
- [ ] Eraser Tool - [ ] Eraser Tool
- [ ] Brush size and/or shape - [ ] Brush size and/or shape
- [ ] Doodad CLI Tool Features - [ ] Doodad CLI Tool Features
@ -52,7 +51,7 @@
- [ ] Doors - [ ] Doors
- [x] Locked Doors and Keys - [x] Locked Doors and Keys
- [x] Electric Doors - [x] Electric Doors
- [ ] Trapdoors (1 of 4) - [x] Trapdoors (all 4 directions)
## Doodad Ideas ## Doodad Ideas

View File

@ -189,6 +189,7 @@ var (
Cyan = RGBA(0, 255, 255, 255) Cyan = RGBA(0, 255, 255, 255)
DarkCyan = RGBA(0, 153, 153, 255) DarkCyan = RGBA(0, 153, 153, 255)
Yellow = RGBA(255, 255, 0, 255) Yellow = RGBA(255, 255, 0, 255)
Orange = RGBA(255, 153, 0, 255)
DarkYellow = RGBA(153, 153, 0, 255) DarkYellow = RGBA(153, 153, 0, 255)
Magenta = RGBA(255, 0, 255, 255) Magenta = RGBA(255, 0, 255, 255)
Purple = RGBA(153, 0, 153, 255) Purple = RGBA(153, 0, 153, 255)

View File

@ -55,6 +55,11 @@ var (
// Color for draggable doodad. // Color for draggable doodad.
DragColor = render.MustHexColor("#0099FF") DragColor = render.MustHexColor("#0099FF")
// Link lines drawn between connected doodads.
LinkLineColor = render.Magenta
LinkLighten = 128
LinkAnimSpeed uint64 = 30 // ticks
PlayButtonFont = render.Text{ PlayButtonFont = render.Text{
FontFilename: "DejaVuSans-Bold.ttf", FontFilename: "DejaVuSans-Bold.ttf",
Size: 16, Size: 16,

View File

@ -36,7 +36,6 @@ type Doodle struct {
startTime time.Time startTime time.Time
running bool running bool
ticks uint64
width int width int
height int height int
@ -110,7 +109,7 @@ func (d *Doodle) Run() error {
d.Engine.Clear(render.White) d.Engine.Clear(render.White)
start := time.Now() // Record how long this frame took. start := time.Now() // Record how long this frame took.
d.ticks++ shmem.Tick++
// Poll for events. // Poll for events.
ev, err := d.Engine.Poll() ev, err := d.Engine.Poll()

View File

@ -45,6 +45,19 @@ func NewStroke(shape Shape, color render.Color) *Stroke {
} }
} }
// Copy returns a duplicate of the Stroke reference.
func (s *Stroke) Copy() *Stroke {
nextStrokeID++
return &Stroke{
ID: nextStrokeID,
Shape: s.Shape,
Color: s.Color,
Points: []render.Point{},
uniqPoint: map[render.Point]interface{}{},
}
}
// IterPoints returns an iterator of points represented by the stroke. // IterPoints returns an iterator of points represented by the stroke.
// //
// For a Line, returns all of the points between PointA and PointB. For freehand, // For a Line, returns all of the points between PointA and PointB. For freehand,

View File

@ -10,7 +10,6 @@ import (
"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/branding" "git.kirsle.net/apps/doodle/pkg/branding"
"git.kirsle.net/apps/doodle/pkg/doodads"
"git.kirsle.net/apps/doodle/pkg/drawtool" "git.kirsle.net/apps/doodle/pkg/drawtool"
"git.kirsle.net/apps/doodle/pkg/enum" "git.kirsle.net/apps/doodle/pkg/enum"
"git.kirsle.net/apps/doodle/pkg/level" "git.kirsle.net/apps/doodle/pkg/level"
@ -333,12 +332,8 @@ func (u *EditorUI) SetupCanvas(d *Doodle) *uix.Canvas {
// A drag event initiated inside the Canvas. This happens in the ActorTool // A drag event initiated inside the Canvas. This happens in the ActorTool
// mode when you click an existing Doodad and it "pops" out of the canvas // mode when you click an existing Doodad and it "pops" out of the canvas
// and onto the cursor to be repositioned. // and onto the cursor to be repositioned.
drawing.OnDragStart = func(filename string) { drawing.OnDragStart = func(actor *level.Actor) {
doodad, err := doodads.LoadFile(filename) u.startDragActor(nil, actor)
if err != nil {
log.Error("drawing.OnDragStart: %s", err.Error())
}
u.startDragActor(doodad)
} }
// A link event to connect two actors together. // A link event to connect two actors together.
@ -350,7 +345,6 @@ func (u *EditorUI) SetupCanvas(d *Doodle) *uix.Canvas {
b.Actor.AddLink(idA) b.Actor.AddLink(idA)
// Reset the Link tool. // Reset the Link tool.
drawing.Tool = drawtool.ActorTool
d.Flash("Linked '%s' and '%s' together", a.Doodad.Title, b.Doodad.Title) d.Flash("Linked '%s' and '%s' together", a.Doodad.Title, b.Doodad.Title)
} }
@ -369,15 +363,25 @@ func (u *EditorUI) SetupCanvas(d *Doodle) *uix.Canvas {
return return
} }
size := actor.canvas.Size() var (
u.Scene.Level.Actors.Add(&level.Actor{
// Uncenter the drawing from the cursor. // Uncenter the drawing from the cursor.
Point: render.Point{ size = actor.canvas.Size()
position = render.Point{
X: (u.cursor.X - drawing.Scroll.X - (size.W / 2)) - P.X, X: (u.cursor.X - drawing.Scroll.X - (size.W / 2)) - P.X,
Y: (u.cursor.Y - drawing.Scroll.Y - (size.H / 2)) - P.Y, Y: (u.cursor.Y - drawing.Scroll.Y - (size.H / 2)) - P.Y,
}, }
)
// Was it an already existing actor to re-add to the map?
if actor.actor != nil {
actor.actor.Point = position
u.Scene.Level.Actors.Add(actor.actor)
} else {
u.Scene.Level.Actors.Add(&level.Actor{
Point: position,
Filename: actor.doodad.Filename, Filename: actor.doodad.Filename,
}) })
}
err := drawing.InstallActors(u.Scene.Level.Actors) err := drawing.InstallActors(u.Scene.Level.Actors)
if err != nil { if err != nil {

View File

@ -11,6 +11,7 @@ import (
"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/doodads" "git.kirsle.net/apps/doodle/pkg/doodads"
"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/uix" "git.kirsle.net/apps/doodle/pkg/uix"
) )
@ -18,13 +19,29 @@ import (
// DraggableActor is a Doodad being dragged from the Doodad palette. // DraggableActor is a Doodad being dragged from the Doodad palette.
type DraggableActor struct { type DraggableActor struct {
canvas *uix.Canvas canvas *uix.Canvas
doodad *doodads.Doodad doodad *doodads.Doodad // if a new one from the palette
actor *level.Actor // if a level actor
} }
// startDragActor begins the drag event for a Doodad onto a level. // startDragActor begins the drag event for a Doodad onto a level.
func (u *EditorUI) startDragActor(doodad *doodads.Doodad) { // actor may be nil (if you drag a new doodad from the palette) or otherwise
// is an existing actor from the level.
func (u *EditorUI) startDragActor(doodad *doodads.Doodad, actor *level.Actor) {
u.Supervisor.DragStart() u.Supervisor.DragStart()
if doodad == nil {
if actor != nil {
obj, err := doodads.LoadFile(actor.Filename)
if err != nil {
log.Error("startDragExistingActor: actor doodad name %s not found: %s", actor.Filename, err)
return
}
doodad = obj
} else {
panic("EditorUI.startDragActor: doodad AND/OR actor is required, but neither were given")
}
}
// Create the canvas to render on the mouse cursor. // Create the canvas to render on the mouse cursor.
drawing := uix.NewCanvas(doodad.Layers[0].Chunker.Size, false) drawing := uix.NewCanvas(doodad.Layers[0].Chunker.Size, false)
drawing.LoadDoodad(doodad) drawing.LoadDoodad(doodad)
@ -34,6 +51,7 @@ func (u *EditorUI) startDragActor(doodad *doodads.Doodad) {
u.DraggableActor = &DraggableActor{ u.DraggableActor = &DraggableActor{
canvas: drawing, canvas: drawing,
doodad: doodad, doodad: doodad,
actor: actor,
} }
} }
@ -153,7 +171,7 @@ func (u *EditorUI) setupDoodadFrame(e render.Engine, window *ui.Window) (*ui.Fra
// editor_ui.go#SetupCanvas() // editor_ui.go#SetupCanvas()
btn.Handle(ui.MouseDown, func(e render.Point) { btn.Handle(ui.MouseDown, func(e render.Point) {
log.Warn("MouseDown on doodad %s (%s)", doodad.Filename, doodad.Title) log.Warn("MouseDown on doodad %s (%s)", doodad.Filename, doodad.Title)
u.startDragActor(doodad) u.startDragActor(doodad, nil)
}) })
u.Supervisor.Add(btn) u.Supervisor.Add(btn)

View File

@ -10,6 +10,7 @@ import (
"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/log" "git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/apps/doodle/pkg/shmem"
"github.com/robertkrimen/otto" "github.com/robertkrimen/otto"
) )
@ -146,7 +147,7 @@ func (s *Shell) Write(line string) {
s.Output = append(s.Output, line) s.Output = append(s.Output, line)
s.Flashes = append(s.Flashes, Flash{ s.Flashes = append(s.Flashes, Flash{
Text: line, Text: line,
Expires: s.parent.ticks + balance.FlashTTL, Expires: shmem.Tick + balance.FlashTTL,
}) })
} }
@ -253,8 +254,8 @@ func (s *Shell) Draw(d *Doodle, ev *events.State) error {
} }
// Cursor flip? // Cursor flip?
if d.ticks > s.cursorFlip { if shmem.Tick > s.cursorFlip {
s.cursorFlip = d.ticks + s.cursorRate s.cursorFlip = shmem.Tick + s.cursorRate
if s.cursor == ' ' { if s.cursor == ' ' {
s.cursor = '_' s.cursor = '_'
} else { } else {
@ -329,7 +330,7 @@ func (s *Shell) Draw(d *Doodle, ev *events.State) error {
outputY := int32(d.height - (lineHeight * 2) - 16) outputY := int32(d.height - (lineHeight * 2) - 16)
for i := len(s.Flashes); i > 0; i-- { for i := len(s.Flashes); i > 0; i-- {
flash := s.Flashes[i-1] flash := s.Flashes[i-1]
if d.ticks >= flash.Expires { if shmem.Tick >= flash.Expires {
continue continue
} }

View File

@ -9,6 +9,9 @@ import (
// Shared globals for easy access throughout the app. // Shared globals for easy access throughout the app.
// Not an ideal place to keep things but *shrug* // Not an ideal place to keep things but *shrug*
var ( var (
// Tick is incremented by the main game loop each frame.
Tick uint64
// Current render engine (i.e. SDL2 or HTML5 Canvas) // Current render engine (i.e. SDL2 or HTML5 Canvas)
// The level.Chunk.ToBitmap() uses this to cache a texture image. // The level.Chunk.ToBitmap() uses this to cache a texture image.
CurrentRenderEngine render.Engine CurrentRenderEngine render.Engine

View File

@ -67,7 +67,7 @@ type Canvas struct {
// that controls the actors. Upstream should delete them and then reinstall // that controls the actors. Upstream should delete them and then reinstall
// the actor list from scratch. // the actor list from scratch.
OnDeleteActors func([]*level.Actor) OnDeleteActors func([]*level.Actor)
OnDragStart func(filename string) OnDragStart func(*level.Actor)
// -- WHEN Canvas.Tool is "Link" -- // -- WHEN Canvas.Tool is "Link" --
// When the Canvas wants to link two actors together. Arguments are the IDs // When the Canvas wants to link two actors together. Arguments are the IDs

View File

@ -183,7 +183,7 @@ func (w *Canvas) loopEditable(ev *events.State) error {
// Pop this canvas out for the drag/drop. // Pop this canvas out for the drag/drop.
if w.OnDragStart != nil { if w.OnDragStart != nil {
deleteActors = append(deleteActors, actor.Actor) deleteActors = append(deleteActors, actor.Actor)
w.OnDragStart(actor.Actor.Filename) w.OnDragStart(actor.Actor)
} }
break break
} else if ev.Button2.Read() { } else if ev.Button2.Read() {

View File

@ -7,6 +7,7 @@ import (
"git.kirsle.net/apps/doodle/pkg/drawtool" "git.kirsle.net/apps/doodle/pkg/drawtool"
"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/shmem"
) )
// canvas_strokes.go: functions related to drawtool.Stroke and the Canvas. // canvas_strokes.go: functions related to drawtool.Stroke and the Canvas.
@ -115,6 +116,27 @@ func (w *Canvas) presentActorLinks(e render.Engine) {
} }
} }
// If no links, stop.
if len(actorMap) == 0 {
return
}
// The glow colored line. Huge hacky block of code but makes for some
// basic visualization for now.
var color = balance.LinkLineColor
var lightenStep = float64(balance.LinkLighten) / 16
var step = shmem.Tick % balance.LinkAnimSpeed
if step < 32 {
for i := uint64(0); i < step; i++ {
color = color.Lighten(int(lightenStep))
}
if step > 16 {
for i := uint64(0); i < step-16; i++ {
color = color.Darken(int(lightenStep))
}
}
}
// Loop over the linked actors and draw stroke lines. // Loop over the linked actors and draw stroke lines.
for _, actor := range actorMap { for _, actor := range actorMap {
for _, linkID := range actor.Actor.Links { for _, linkID := range actor.Actor.Links {
@ -130,7 +152,7 @@ func (w *Canvas) presentActorLinks(e render.Engine) {
) )
// Draw a line connecting the centers of each actor together. // Draw a line connecting the centers of each actor together.
stroke := drawtool.NewStroke(drawtool.Line, render.Magenta) stroke := drawtool.NewStroke(drawtool.Line, color)
stroke.PointA = render.Point{ stroke.PointA = render.Point{
X: aP.X + (aS.W / 2), X: aP.X + (aS.W / 2),
Y: aP.Y + (aS.H / 2), Y: aP.Y + (aS.H / 2),
@ -141,6 +163,16 @@ func (w *Canvas) presentActorLinks(e render.Engine) {
} }
strokes = append(strokes, stroke) strokes = append(strokes, stroke)
// Make it double thick.
double := stroke.Copy()
double.PointA = render.NewPoint(stroke.PointA.X, stroke.PointA.Y+1)
double.PointB = render.NewPoint(stroke.PointB.X, stroke.PointB.Y+1)
strokes = append(strokes, double)
double = stroke.Copy()
double.PointA = render.NewPoint(stroke.PointA.X+1, stroke.PointA.Y)
double.PointB = render.NewPoint(stroke.PointB.X+1, stroke.PointB.Y)
strokes = append(strokes, double)
} }
} }