Skip to content

Commit

Permalink
Support property anim, based on timeline
Browse files Browse the repository at this point in the history
  • Loading branch information
qlli committed Oct 30, 2024
1 parent 845f2c0 commit 25b7e8f
Show file tree
Hide file tree
Showing 7 changed files with 902 additions and 10 deletions.
31 changes: 21 additions & 10 deletions game.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,24 @@ const (
DbgFlagInstr
DbgFlagEvent
DbgFlagPerf
DbgFlagAll = DbgFlagLoad | DbgFlagInstr | DbgFlagEvent | DbgFlagPerf
DbgFlagTimeline
DbgFlagAll = DbgFlagLoad | DbgFlagInstr | DbgFlagEvent | DbgFlagPerf | DbgFlagTimeline
)

var (
debugInstr bool
debugLoad bool
debugEvent bool
debugPerf bool
debugInstr bool
debugLoad bool
debugEvent bool
debugPerf bool
debugTimeline bool
)

func SetDebug(flags dbgFlags) {
debugLoad = (flags & DbgFlagLoad) != 0
debugInstr = (flags & DbgFlagInstr) != 0
debugEvent = (flags & DbgFlagEvent) != 0
debugPerf = (flags & DbgFlagPerf) != 0
debugTimeline = (flags & DbgFlagTimeline) != 0
}

// -------------------------------------------------------------------------------------
Expand All @@ -81,11 +84,12 @@ type Game struct {
fs spxfs.Dir
shared *sharedImages

sounds soundMgr
turtle turtleCanvas
typs map[string]reflect.Type // map: name => sprite type, for all sprites
sprs map[string]Sprite // map: name => sprite prototype, for loaded sprites
items []Shape // shapes on stage (in Zorder), not only sprites
sounds soundMgr
turtle turtleCanvas
typs map[string]reflect.Type // map: name => sprite type, for all sprites
sprs map[string]Sprite // map: name => sprite prototype, for loaded sprites
items []Shape // shapes on stage (in Zorder), not only sprites
timelines TimelineMgr

tickMgr tickMgr
input inputMgr
Expand Down Expand Up @@ -625,16 +629,23 @@ func (p *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeigh
return p.windowSize_()
}

var ts int64

func (p *Game) Update() error {
if !p.isLoaded {
return nil
}

old := ts
ts = time.Now().UnixMilli()
p.updateColliders()
p.input.update()
p.updateMousePos()
p.sounds.update()
p.tickMgr.update()
if old != 0 {
p.timelines.Update((float64(ts-old) / 1000.0))
}
return nil
}

Expand Down
31 changes: 31 additions & 0 deletions internal/timeline/interval.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package timeline

import "fmt"

type Interval struct {
Offset float64
Duration float64
}

func (i Interval) End() float64 {
return i.Offset + i.Duration
}

func (i *Interval) Step(time float64) {
i.Offset -= time
}

func (i Interval) Scale(scale float32) Interval {
return Interval{
Offset: float64(float64(i.Offset) * float64(scale)),
Duration: float64(float64(i.Duration) * float64(scale)),
}
}

func (i Interval) Contains(time float64) bool {
return i.Offset <= time && time <= i.End()
}

func (i Interval) String() string {
return fmt.Sprintf("Interval{offset:%.3f, duration:%.3f}", i.Offset, i.Duration)
}
192 changes: 192 additions & 0 deletions internal/timeline/timeline.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package timeline

import (
"fmt"
"log"
"sync"
)

const EPSILON float64 = 0.0001
const DEFAULT_TRANSITION float64 = 0.5

var debugTimeline bool = false
var idSeed int64
var idSeedM sync.Mutex

type ITimeline interface {
GetTimeline() *Timeline
Step(time *float64) ITimeline
SetActive(bool)
}

type Timeline struct {
ID int64
active bool
offset float64
speed float64
fadeIn *Interval
fadeOut *Interval
freezingTime float64
next ITimeline
group *TimelineGroup
onStep func(*float64) ITimeline
onActive func(bool)
}

func (t *Timeline) Init() *Timeline {
idSeedM.Lock()
defer idSeedM.Unlock()
t.ID = idSeed
idSeed++
if debugTimeline {
log.Printf("Timeline %X", t.ID)
}
t.speed = 1.0
return t
}

func (t *Timeline) GetTimeline() *Timeline {
return t
}

func (t *Timeline) SetActive(on bool) {
if t.active == on {
return
}
if debugTimeline {
log.Println("SetActive", on, t.ID)
}
t.active = on
if t.onActive != nil {
t.onActive(on)
}
}

func (t *Timeline) Step(time *float64) ITimeline {
if *time < 0.0 {
*time = 0.0
}

var running ITimeline = t
var loopLimit int = 10000
for running != nil && *time > EPSILON && loopLimit > 0 {
loopLimit--
var realStep, scaledTime, oldScaledTime float64
var oldRunning ITimeline
r := running.GetTimeline()

// step until time <= 0 || offset <= 0
var min float64 = *time // minimum step in consideration of time, offset, fadeout.end, freezingTime
if r.offset > EPSILON {
if min > r.offset {
min = r.offset
}
*time -= min
r.offset -= min
continue
}

r.SetActive(true)

if r.fadeOut != nil {
end := r.fadeOut.End()
if end < 0.0 {
end = 0.0
}
if min > end {
min = end
}
}

// step until time <= 0 || fadeOut.end <= 0 || freezingTime <= 0
if r.freezingTime > EPSILON {
if min > r.freezingTime {
min = r.freezingTime
}
*time -= min
r.freezingTime -= min
if r.fadeIn != nil {
r.fadeIn.Step(min)
}
if r.fadeOut != nil {
r.fadeOut.Step(min)
}

goto CHECK_FADE_OUT
}

// step until time <= 0 || fadeOut.end <= 0 || runOut.end <= 0
scaledTime = float64(min) * r.speed
oldScaledTime = scaledTime
oldRunning = running
running = r.onStep(&scaledTime)
if r.speed > float64(EPSILON) {
realStep = (oldScaledTime - scaledTime) / r.speed
} else {
realStep = min
}
*time -= realStep
if r.fadeIn != nil {
r.fadeIn.Step(realStep)
}
if r.fadeOut != nil {
r.fadeOut.Step(realStep)
}

if running != oldRunning {
if running != nil {
continue
}

// running == null, means the timeline has run out, should check whether fade out
if r.fadeOut == nil || r.fadeOut.End() <= EPSILON {
oldRunning.SetActive(false)
running = r.next
continue
}

// step until time <= 0 || fadeOut.end <= 0
end2 := r.fadeOut.End()
if end2 < 0.0 {
end2 = 0.0
}
min2 := *time
if min2 > end2 {
min2 = end2
}
*time -= min2
if r.fadeIn != nil {
r.fadeIn.Step(min2)
}
if r.fadeOut != nil {
r.fadeOut.Step(min2)
}
running = oldRunning
}

CHECK_FADE_OUT:
t2 := running.GetTimeline()
if t2.fadeOut != nil && t2.fadeOut.End() <= EPSILON {
t2.SetActive(false)
running = t2.next
}
}

if loopLimit <= 0 {
panic("LOOP LIMIT")
}
return running
}

func (this *Timeline) String() string {
var nID int64 = 0
if this.next != nil {
nID = this.next.GetTimeline().ID
}
var gID int64 = 0
if this.group != nil {
gID = this.group.ID
}
return fmt.Sprintf("{id:%x,active:%t,offset:%.3f,speed:%.3f,in:%s,out:%s,frz:%.3f,next:%x,group:%x}",
this.ID, this.active, this.offset, this.speed, this.fadeIn, this.fadeOut, this.freezingTime, nID, gID)
}
16 changes: 16 additions & 0 deletions internal/timeline/timelineGroup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package timeline

type TimelineGroup struct {
Timeline
Tracks []ITimeline
}

func (tg *TimelineGroup) Step(time *float64) ITimeline {
tg.Timeline.Step(time)
return nil
}

// AddTrack 方法
func (tg *TimelineGroup) AddTrack(track ITimeline) {
tg.Tracks = append(tg.Tracks, track)
}
Loading

0 comments on commit 25b7e8f

Please sign in to comment.