From 7b477b742a5c9cdf3a6815395692296856e64814 Mon Sep 17 00:00:00 2001 From: Joel Silva Schutz Date: Tue, 27 Feb 2024 12:02:26 -0300 Subject: [PATCH 01/19] Initial Transition Awareness --- scene.go | 28 +++++++++++++++++----------- transition.go | 27 ++++++++++++++++++--------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/scene.go b/scene.go index a2bdf1c..10cce26 100644 --- a/scene.go +++ b/scene.go @@ -4,14 +4,23 @@ import ( ebiten "github.com/hajimehoshi/ebiten/v2" ) -type Scene[T any] interface { +type ProtoScene[T any] interface { ebiten.Game - Load(T, *SceneManager[T]) - Unload() T +} + +type Scene[T any] interface { + ProtoScene[T] + Load(T, *SceneManager[T]) // Runs when scene is first started, must keep state and SceneManager + Unload() T // Runs when scene is discarted, must return last state +} +type TransitionAwareScene[T any] interface { + Scene[T] + PreTransition(Scene[T]) T // Runs before Load, must return last state + PostTransition(T, Scene[T]) // Runs when scene is fully loaded } type SceneManager[T any] struct { - current Scene[T] + current ProtoScene[T] } func NewSceneManager[T any](scene Scene[T], state T) *SceneManager[T] { @@ -22,13 +31,10 @@ func NewSceneManager[T any](scene Scene[T], state T) *SceneManager[T] { // Scene Switching func (s *SceneManager[T]) SwitchTo(scene Scene[T]) { - scene.Load(s.current.Unload(), s) - s.current = scene -} - -func (s *SceneManager[T]) SwitchWithTransition(scene Scene[T], transition SceneTransition[T]) { - transition.Start(s.current, scene) - s.SwitchTo(transition) + if c, ok := s.current.(Scene[T]); ok { + scene.Load(c.Unload(), s) + s.current = scene + } } // Ebiten Interface diff --git a/transition.go b/transition.go index 8d1d9a9..a69a755 100644 --- a/transition.go +++ b/transition.go @@ -7,7 +7,7 @@ import ( ) type SceneTransition[T any] interface { - Scene[T] + ProtoScene[T] Start(fromScene, toScene Scene[T]) End() } @@ -47,20 +47,29 @@ func (t *BaseTransition[T]) Layout(outsideWidth, outsideHeight int) (int, int) { return MaxInt(sw, tw), MaxInt(sh, th) } -// Loads the next scene -func (t *BaseTransition[T]) Load(state T, manager *SceneManager[T]) { - t.sm = manager - t.toScene.Load(state, manager) +func (s *SceneManager[T]) returnFromTransition(scene, orgin Scene[T]) { + if c, ok := scene.(TransitionAwareScene[T]); ok { + c.PostTransition(orgin.Unload(), orgin) + } else { + scene.Load(orgin.Unload(), s) + } + s.current = scene } -// Unloads the last scene -func (t *BaseTransition[T]) Unload() T { - return t.fromScene.Unload() +func (s *SceneManager[T]) SwitchWithTransition(scene Scene[T], transition SceneTransition[T]) { + sc := s.current.(Scene[T]) + transition.Start(sc, scene) + if c, ok := sc.(TransitionAwareScene[T]); ok { + scene.Load(c.PreTransition(scene), s) + } else { + scene.Load(sc.Unload(), s) + } + s.current = transition } // Ends transition to the next scene func (t *BaseTransition[T]) End() { - t.sm.SwitchTo(t.toScene) + t.sm.returnFromTransition(t.toScene, t.fromScene) } type FadeTransition[T any] struct { From 227b6fe806142ac73c71aed24689b7cde47f00b7 Mon Sep 17 00:00:00 2001 From: Joel Silva Schutz <49206635+joelschutz@users.noreply.github.com> Date: Wed, 28 Feb 2024 22:19:08 -0300 Subject: [PATCH 02/19] [DRAFT]Atualizar o README.md --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 58787e3..6226b9f 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,22 @@ func (t *MyTransition) Draw(screen *ebiten.Image) { ``` +### Transition Awareness + +When a scene is transitioned, the `Load` and `Unload` methods are called twice for the destination and original scenes respectively. Once at the start and again at the end of the transition. This behavior can be changed for additional control by implementing the `TranditionAwareScene` interface. + +```go +func (s *MyScene) PreTransition(state T, origin stagehand.Scene[T]) { + // code to run before load scene +} + +func (s *MyScene) PostTransition(origin stagehand.Scene[T]) { + // code to run after unload scene +} +``` + +With this you can insure that those methods are only called once on transitions and it signals that the scene is being unloaded or has fully transitioned. + ## Contribution Contributions are welcome! If you find a bug or have a feature request, please open an issue on GitHub. If you would like to contribute code, please fork the repository and submit a pull request. From 87138b6213bf044a0dd574855ddde2b69578f7e1 Mon Sep 17 00:00:00 2001 From: Joel Silva Schutz Date: Thu, 29 Feb 2024 14:22:58 -0300 Subject: [PATCH 03/19] [WIP]Updates tests --- scene_test.go | 14 ++++++++------ transition_test.go | 46 ++++++++++++---------------------------------- 2 files changed, 20 insertions(+), 40 deletions(-) diff --git a/scene_test.go b/scene_test.go index 233572e..1386c7a 100644 --- a/scene_test.go +++ b/scene_test.go @@ -113,10 +113,12 @@ func TestSceneManager_Layout(t *testing.T) { } func TestSceneManager_Load_Unload(t *testing.T) { - sm := NewSceneManager[int](&MockScene{}, 42) - mockScene := &MockScene{} - sm.SwitchTo(mockScene) - unloaded := sm.current.Unload() - assert.True(t, mockScene.unloadCalled) - assert.Equal(t, 42, unloaded) + from := &MockScene{} + to := &MockScene{} + sm := NewSceneManager[int](from, 42) + sm.SwitchTo(to) + + assert.True(t, to.loadCalled) + assert.True(t, from.unloadCalled) + assert.Equal(t, 42, sm.current.(Scene[int]).Unload()) } diff --git a/transition_test.go b/transition_test.go index 81810ef..2f6b174 100644 --- a/transition_test.go +++ b/transition_test.go @@ -1,6 +1,7 @@ package stagehand import ( + "fmt" "testing" "time" @@ -49,37 +50,14 @@ func TestBaseTransition_Layout(t *testing.T) { assert.True(t, to.layoutCalled) } -func TestBaseTransition_Load(t *testing.T) { - from := &MockScene{} - to := &MockScene{} - trans := &baseTransitionImplementation{} - trans.Start(from, to) - trans.Load(42, &SceneManager[int]{}) - - assert.True(t, to.loadCalled) - assert.False(t, from.loadCalled) - -} - -func TestBaseTransition_Unload(t *testing.T) { - from := &MockScene{} - to := &MockScene{} - trans := &baseTransitionImplementation{} - trans.Start(from, to) - - trans.Unload() - assert.True(t, from.unloadCalled) - assert.False(t, to.unloadCalled) -} - func TestBaseTransition_End(t *testing.T) { from := &MockScene{} to := &MockScene{} trans := &baseTransitionImplementation{} - trans.Start(from, to) - sm := NewSceneManager[int](trans, 0) + sm := NewSceneManager[int](from, 0) + sm.SwitchWithTransition(to, trans) - trans.End() + fmt.Println(sm.current.(Scene[int]), to) assert.Equal(t, to, sm.current) } @@ -114,8 +92,8 @@ func TestFadeTransition_Update(t *testing.T) { from := &MockScene{} to := &MockScene{} trans := NewFadeTransition[int](.5) - trans.Start(from, to) - sm := NewSceneManager[int](trans, 0) + sm := NewSceneManager[int](from, 0) + sm.SwitchWithTransition(to, trans) err := sm.Update() assert.NoError(t, err) @@ -198,8 +176,8 @@ func TestSlideTransition_Update(t *testing.T) { for _, direction := range variations { trans := NewSlideTransition[int](direction, .5) - trans.Start(from, to) - sm := NewSceneManager[int](trans, 0) + sm := NewSceneManager[int](from, 0) + sm.SwitchWithTransition(to, trans) err := sm.Update() assert.NoError(t, err) @@ -255,8 +233,8 @@ func TestTimedFadeTransition_Update(t *testing.T) { from := &MockScene{} to := &MockScene{} trans := NewDurationTimedFadeTransition[int](time.Second) - trans.Start(from, to) - sm := NewSceneManager[int](trans, 0) + sm := NewSceneManager[int](from, 0) + sm.SwitchWithTransition(to, trans) // Should not update if no time passed err := sm.Update() @@ -322,8 +300,8 @@ func TestTimedSlideTransition_Update(t *testing.T) { for _, direction := range variations { Clock = &MockClock{currentTime: time.Now()} trans := NewDurationTimedSlideTransition[int](direction, time.Second) - trans.Start(from, to) - sm := NewSceneManager[int](trans, 0) + sm := NewSceneManager[int](from, 0) + sm.SwitchWithTransition(to, trans) // Should not update if no time passed err := sm.Update() From 43333e980dd45867ac5860ca3df672eb5876c505 Mon Sep 17 00:00:00 2001 From: Joel Schutz Date: Thu, 29 Feb 2024 21:44:15 -0300 Subject: [PATCH 04/19] Corrects missing SceneManager reference --- transition.go | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/transition.go b/transition.go index a69a755..a10ff15 100644 --- a/transition.go +++ b/transition.go @@ -8,7 +8,7 @@ import ( type SceneTransition[T any] interface { ProtoScene[T] - Start(fromScene, toScene Scene[T]) + Start(fromScene, toScene Scene[T], sm *SceneManager[T]) End() } @@ -18,9 +18,10 @@ type BaseTransition[T any] struct { sm *SceneManager[T] } -func (t *BaseTransition[T]) Start(fromScene, toScene Scene[T]) { +func (t *BaseTransition[T]) Start(fromScene, toScene Scene[T], sm *SceneManager[T]) { t.fromScene = fromScene t.toScene = toScene + t.sm = sm } // Update updates the transition state @@ -58,7 +59,7 @@ func (s *SceneManager[T]) returnFromTransition(scene, orgin Scene[T]) { func (s *SceneManager[T]) SwitchWithTransition(scene Scene[T], transition SceneTransition[T]) { sc := s.current.(Scene[T]) - transition.Start(sc, scene) + transition.Start(sc, scene, s) if c, ok := sc.(TransitionAwareScene[T]); ok { scene.Load(c.PreTransition(scene), s) } else { @@ -87,8 +88,8 @@ func NewFadeTransition[T any](factor float32) *FadeTransition[T] { } // Start starts the transition from the given "from" scene to the given "to" scene -func (t *FadeTransition[T]) Start(fromScene, toScene Scene[T]) { - t.BaseTransition.Start(fromScene, toScene) +func (t *FadeTransition[T]) Start(fromScene, toScene Scene[T], sm *SceneManager[T]) { + t.BaseTransition.Start(fromScene, toScene, sm) t.alpha = 0 t.isFadingIn = true } @@ -164,8 +165,8 @@ func NewSlideTransition[T any](direction SlideDirection, factor float64) *SlideT } // Start starts the transition from the given "from" scene to the given "to" scene -func (t *SlideTransition[T]) Start(fromScene Scene[T], toScene Scene[T]) { - t.BaseTransition.Start(fromScene, toScene) +func (t *SlideTransition[T]) Start(fromScene Scene[T], toScene Scene[T], sm *SceneManager[T]) { + t.BaseTransition.Start(fromScene, toScene, sm) t.offset = 0 } @@ -237,8 +238,8 @@ func NewDurationTimedFadeTransition[T any](duration time.Duration) *TimedFadeTra } } -func (t *TimedFadeTransition[T]) Start(fromScene, toScene Scene[T]) { - t.FadeTransition.Start(fromScene, toScene) +func (t *TimedFadeTransition[T]) Start(fromScene, toScene Scene[T], sm *SceneManager[T]) { + t.FadeTransition.Start(fromScene, toScene, sm) t.initialTime = Clock.Now() } @@ -283,8 +284,8 @@ func NewDurationTimedSlideTransition[T any](direction SlideDirection, duration t } } -func (t *TimedSlideTransition[T]) Start(fromScene, toScene Scene[T]) { - t.SlideTransition.Start(fromScene, toScene) +func (t *TimedSlideTransition[T]) Start(fromScene, toScene Scene[T], sm *SceneManager[T]) { + t.SlideTransition.Start(fromScene, toScene, sm) t.initialTime = Clock.Now() } From 58c0132e4f96f8f469de64fc14511a0bf31ad3c7 Mon Sep 17 00:00:00 2001 From: Joel Schutz Date: Thu, 29 Feb 2024 21:45:42 -0300 Subject: [PATCH 05/19] Uptades Tests --- scene_test.go | 7 +------ transition_test.go | 23 ++++++++++++----------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/scene_test.go b/scene_test.go index 1386c7a..c3f34f0 100644 --- a/scene_test.go +++ b/scene_test.go @@ -44,12 +44,11 @@ type MockTransition[T any] struct { fromScene Scene[T] toScene Scene[T] startCalled bool - state T } func NewMockTransition[T any]() *MockTransition[T] { return &MockTransition[T]{} } -func (t *MockTransition[T]) Start(fromScene, toScene Scene[T]) { +func (t *MockTransition[T]) Start(fromScene, toScene Scene[T], sm *SceneManager[T]) { t.fromScene = fromScene t.toScene = toScene t.startCalled = true @@ -61,10 +60,6 @@ func (t *MockTransition[T]) Update() error { return nil } func (t *MockTransition[T]) Draw(screen *ebiten.Image) {} -func (t *MockTransition[T]) Load(state T, sm *SceneManager[T]) { t.state = state } - -func (t *MockTransition[T]) Unload() T { return t.state } - func (t *MockTransition[T]) Layout(w, h int) (int, int) { return w, h } func TestSceneManager_SwitchTo(t *testing.T) { diff --git a/transition_test.go b/transition_test.go index 2f6b174..a03d3bc 100644 --- a/transition_test.go +++ b/transition_test.go @@ -28,7 +28,7 @@ func TestBaseTransition_Update(t *testing.T) { from := &MockScene{} to := &MockScene{} trans := &baseTransitionImplementation{} - trans.Start(from, to) + trans.Start(from, to, nil) err := trans.Update() assert.NoError(t, err) @@ -40,7 +40,7 @@ func TestBaseTransition_Layout(t *testing.T) { from := &MockScene{} to := &MockScene{} trans := &baseTransitionImplementation{} - trans.Start(from, to) + trans.Start(from, to, nil) sw, sh := trans.Layout(100, 100) assert.Equal(t, 100, sw) @@ -56,6 +56,7 @@ func TestBaseTransition_End(t *testing.T) { trans := &baseTransitionImplementation{} sm := NewSceneManager[int](from, 0) sm.SwitchWithTransition(to, trans) + trans.End() fmt.Println(sm.current.(Scene[int]), to) assert.Equal(t, to, sm.current) @@ -65,7 +66,7 @@ func TestBaseTransition_Start(t *testing.T) { from := &MockScene{} to := &MockScene{} trans := &baseTransitionImplementation{} - trans.Start(from, to) + trans.Start(from, to, nil) assert.Equal(t, from, trans.fromScene) assert.Equal(t, to, trans.toScene) @@ -76,7 +77,7 @@ func TestFadeTransition_UpdateOncePerFrame(t *testing.T) { from := &MockScene{} to := &MockScene{} trans := NewFadeTransition[int](value) - trans.Start(from, to) + trans.Start(from, to, nil) err := trans.Update() assert.NoError(t, err) @@ -127,7 +128,7 @@ func TestFadeTransition_Start(t *testing.T) { from := &MockScene{} to := &MockScene{} trans := NewFadeTransition[int](.5) - trans.Start(from, to) + trans.Start(from, to, nil) assert.Equal(t, from, trans.fromScene) assert.Equal(t, to, trans.toScene) @@ -139,7 +140,7 @@ func TestFadeTransition_Draw(t *testing.T) { from := &MockScene{} to := &MockScene{} trans := NewFadeTransition[int](.5) - trans.Start(from, to) + trans.Start(from, to, nil) trans.Update() trans.Draw(ebiten.NewImage(100, 100)) @@ -155,7 +156,7 @@ func TestSlideTransition_UpdateOncePerFrame(t *testing.T) { from := &MockScene{} to := &MockScene{} trans := NewSlideTransition[int](RightToLeft, value) - trans.Start(from, to) + trans.Start(from, to, nil) err := trans.Update() assert.NoError(t, err) @@ -202,7 +203,7 @@ func TestSlideTransition_Start(t *testing.T) { from := &MockScene{} to := &MockScene{} trans := NewSlideTransition[int](TopToBottom, .5) - trans.Start(from, to) + trans.Start(from, to, nil) assert.Equal(t, from, trans.fromScene) assert.Equal(t, to, trans.toScene) @@ -219,7 +220,7 @@ func TestSlideTransition_Draw(t *testing.T) { for _, direction := range variations { trans := NewSlideTransition[int](direction, .5) - trans.Start(from, to) + trans.Start(from, to, nil) trans.Update() trans.Draw(ebiten.NewImage(100, 100)) @@ -282,7 +283,7 @@ func TestTimedFadeTransition_Start(t *testing.T) { from := &MockScene{} to := &MockScene{} trans := NewDurationTimedFadeTransition[int](time.Second) - trans.Start(from, to) + trans.Start(from, to, nil) assert.Equal(t, from, trans.fromScene) assert.Equal(t, to, trans.toScene) @@ -338,7 +339,7 @@ func TestTimedSlideTransition_Start(t *testing.T) { from := &MockScene{} to := &MockScene{} trans := NewDurationTimedSlideTransition[int](TopToBottom, time.Second) - trans.Start(from, to) + trans.Start(from, to, nil) assert.Equal(t, from, trans.fromScene) assert.Equal(t, to, trans.toScene) From c69298d1900329b93c75d792c78e8792d4854c5b Mon Sep 17 00:00:00 2001 From: Joel Schutz Date: Thu, 29 Feb 2024 22:32:24 -0300 Subject: [PATCH 06/19] Transition awareness tests --- transition_test.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/transition_test.go b/transition_test.go index a03d3bc..3c32e71 100644 --- a/transition_test.go +++ b/transition_test.go @@ -24,6 +24,21 @@ type baseTransitionImplementation struct { func (b *baseTransitionImplementation) Draw(screen *ebiten.Image) {} +type MockTransitionAwareScene struct { + MockScene + preTransitionCalled bool + postTransitionCalled bool +} + +func (m *MockTransitionAwareScene) PreTransition(fromScene Scene[int]) int { + m.preTransitionCalled = true + return 0 +} + +func (m *MockTransitionAwareScene) PostTransition(state int, toScene Scene[int]) { + m.postTransitionCalled = true +} + func TestBaseTransition_Update(t *testing.T) { from := &MockScene{} to := &MockScene{} @@ -72,6 +87,23 @@ func TestBaseTransition_Start(t *testing.T) { assert.Equal(t, to, trans.toScene) } +func TestBaseTransition_Awareness(t *testing.T) { + from := &MockTransitionAwareScene{} + to := &MockTransitionAwareScene{} + sm := NewSceneManager[int](from, 0) + trans := &baseTransitionImplementation{} + sm.SwitchWithTransition(to, trans) + + assert.True(t, from.preTransitionCalled) + assert.True(t, to.loadCalled) + assert.False(t, from.unloadCalled) + assert.False(t, to.postTransitionCalled) + + trans.End() + assert.True(t, from.unloadCalled) + assert.True(t, to.postTransitionCalled) +} + func TestFadeTransition_UpdateOncePerFrame(t *testing.T) { var value float32 = .6 from := &MockScene{} From 1260333d435a34d6499d857225482b5e589e06f0 Mon Sep 17 00:00:00 2001 From: Joel Schutz Date: Fri, 1 Mar 2024 15:23:34 -0300 Subject: [PATCH 07/19] Adds Transition Awareness example --- examples/aware/main.go | 114 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 examples/aware/main.go diff --git a/examples/aware/main.go b/examples/aware/main.go new file mode 100644 index 0000000..9e3e4b1 --- /dev/null +++ b/examples/aware/main.go @@ -0,0 +1,114 @@ +package main + +import ( + "fmt" + "image" + "image/color" + "log" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/ebitenutil" + "github.com/hajimehoshi/ebiten/v2/inpututil" + "github.com/joelschutz/stagehand" +) + +const ( + screenWidth = 640 + screenHeight = 480 +) + +type State struct { + Count int + OnTransition bool +} + +type BaseScene struct { + bounds image.Rectangle + count State + sm *stagehand.SceneManager[State] +} + +func (s *BaseScene) Layout(w, h int) (int, int) { + s.bounds = image.Rect(0, 0, w, h) + return w, h +} + +func (s *BaseScene) Load(st State, sm *stagehand.SceneManager[State]) { + s.count = st + s.sm = sm +} + +func (s *BaseScene) Unload() State { + return s.count +} + +func (s *BaseScene) PreTransition(toScene stagehand.Scene[State]) State { + s.count.OnTransition = true + return s.count +} + +func (s *BaseScene) PostTransition(state State, fromScene stagehand.Scene[State]) { + s.count.OnTransition = false +} + +type FirstScene struct { + BaseScene +} + +func (s *FirstScene) Update() error { + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { + s.count.Count++ + } + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { + s.sm.SwitchWithTransition(&SecondScene{}, stagehand.NewSlideTransition[State](stagehand.TopToBottom, .05)) + } + return nil +} + +func (s *FirstScene) Draw(screen *ebiten.Image) { + if s.count.OnTransition { + screen.Fill(color.RGBA{0, 0, 0, 255}) // Fill Black + } else { + screen.Fill(color.RGBA{255, 0, 0, 255}) // Fill Red + } + ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Count: %v, WindowSize: %s", s.count.Count, s.bounds.Max), s.bounds.Dx()/2, s.bounds.Dy()/2) +} + +type SecondScene struct { + BaseScene +} + +func (s *SecondScene) Update() error { + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { + s.count.Count-- + } + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { + s.sm.SwitchWithTransition(&FirstScene{}, stagehand.NewSlideTransition[State](stagehand.BottomToTop, .05)) + } + return nil +} + +func (s *SecondScene) Draw(screen *ebiten.Image) { + if s.count.OnTransition { + screen.Fill(color.RGBA{255, 255, 255, 255}) // Fill White + } else { + screen.Fill(color.RGBA{0, 0, 255, 255}) // Fill Blue + } + + ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Count: %v, WindowSize: %s", s.count.Count, s.bounds.Max), s.bounds.Dx()/2, s.bounds.Dy()/2) +} + +func main() { + ebiten.SetWindowSize(screenWidth, screenHeight) + ebiten.SetWindowTitle("My Game") + ebiten.SetWindowResizable(true) + + state := State{Count: 10} + + s := &FirstScene{} + sm := stagehand.NewSceneManager[State](s, state) + + if err := ebiten.RunGame(sm); err != nil { + log.Fatal(err) + } +} From 820fb507aca1d5ef7a973ca6a9b038b4e09b74c8 Mon Sep 17 00:00:00 2001 From: Joel Schutz Date: Fri, 1 Mar 2024 22:47:36 -0300 Subject: [PATCH 08/19] Updates documentation --- README.md | 42 +++++++++++++++++++++++++++++++----------- scene.go | 4 ++-- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 6226b9f..d79556c 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,14 @@ func main() { } ``` +### Examples + +We provide some example code so you can start fast: + +- [Simple Example](https://github.com/stagehand/blob/master/examples/simple/main.go) +- [Timed Transition Example](https://github.com/stagehand/blob/master/examples/timed/main.go) +- [Transition Awareness Example](https://github.com/stagehand/blob/master/examples/aware/main.go) + ## Transitions You can switch scenes by calling `SwitchTo` method on the `SceneManager` giving the scene instance you wanna switch to. @@ -98,7 +106,7 @@ func (s *MyScene) Update() error { } ``` -In this example, the `FadeTransition` will fade 5% every frame. +In this example, the `FadeTransition` will fade 5% every frame. There is also the option for a timed transition using `NewTicksTimedFadeTransition`(for a ticks based timming) or `NewDurationTimedFadeTransition`(for a real-time based timming). ### Slide Transition @@ -114,7 +122,7 @@ func (s *MyScene) Update() error { } ``` -In this example, the `SlideTransition` will slide in the new scene from the left 5% every frame. +In this example, the `SlideTransition` will slide in the new scene from the left 5% every frame. There is also the option for a timed transition using `NewTicksTimedSlideTransition`(for a ticks based timming) or `NewDurationTimedSlideTransition`(for a real-time based timming). ### Custom Transitions @@ -126,9 +134,9 @@ type MyTransition struct { progress float64 // An example factor } -func (t *MyTransition) Start(from, to stagehand.Scene[T]) { +func (t *MyTransition) Start(from, to stagehand.Scene[MyState], sm *SceneManager[MyState]) { // Start the transition from the "from" scene to the "to" scene here - t.BaseTransition.Start(fromScene, toScene) + t.BaseTransition.Start(fromScene, toScene, sm) t.progress = 0 } @@ -149,19 +157,31 @@ func (t *MyTransition) Draw(screen *ebiten.Image) { ### Transition Awareness -When a scene is transitioned, the `Load` and `Unload` methods are called twice for the destination and original scenes respectively. Once at the start and again at the end of the transition. This behavior can be changed for additional control by implementing the `TranditionAwareScene` interface. +When a scene is transitioned, the `Load` and `Unload` methods are called **twice** for the destination and original scenes respectively. Once at the start and again at the end of the transition. This behavior can be changed for additional control by implementing the `TransitionAwareScene` interface. ```go -func (s *MyScene) PreTransition(state T, origin stagehand.Scene[T]) { - // code to run before load scene +func (s *MyScene) PreTransition(destination Scene[MyState]) MyState { + // Runs before new scene is loaded } -func (s *MyScene) PostTransition(origin stagehand.Scene[T]) { - // code to run after unload scene +func (s *MyScene) PostTransition(lastState MyState, original Scene[MyState]) { + // Runs when old scene is unloaded } ``` -With this you can insure that those methods are only called once on transitions and it signals that the scene is being unloaded or has fully transitioned. +With this you can insure that those methods are only called once on transitions and can control your scenes at each point of the transition. The execution order will be: + +```shell +PreTransition Called on old scene +Load Called on new scene +Updated old scene +Updated new scene +... +Updated old scene +Updated new scene +Unload Called on old scene +PostTransition Called on new scene +``` ## Contribution @@ -175,4 +195,4 @@ go test ./... ## License -Stagehand is released under the [MIT License](https://github.com/example/stagehand/blob/master/LICENSE). +Stagehand is released under the [MIT License](https://github.com/stagehand/blob/master/LICENSE). diff --git a/scene.go b/scene.go index 10cce26..1e26651 100644 --- a/scene.go +++ b/scene.go @@ -15,8 +15,8 @@ type Scene[T any] interface { } type TransitionAwareScene[T any] interface { Scene[T] - PreTransition(Scene[T]) T // Runs before Load, must return last state - PostTransition(T, Scene[T]) // Runs when scene is fully loaded + PreTransition(Scene[T]) T // Runs before new scene is loaded, must return last state + PostTransition(T, Scene[T]) // Runs when old scene is unloaded } type SceneManager[T any] struct { From 4030e65edfcc7e61b1f9dd88e5e39a093b5cb932 Mon Sep 17 00:00:00 2001 From: Joel Schutz Date: Fri, 1 Mar 2024 22:49:57 -0300 Subject: [PATCH 09/19] Corrects references --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d79556c..2760171 100644 --- a/README.md +++ b/README.md @@ -72,9 +72,9 @@ func main() { We provide some example code so you can start fast: -- [Simple Example](https://github.com/stagehand/blob/master/examples/simple/main.go) -- [Timed Transition Example](https://github.com/stagehand/blob/master/examples/timed/main.go) -- [Transition Awareness Example](https://github.com/stagehand/blob/master/examples/aware/main.go) +- [Simple Example](https://github.com/joelschutz/stagehand/blob/master/examples/simple/main.go) +- [Timed Transition Example](https://github.com/joelschutz/stagehand/blob/master/examples/timed/main.go) +- [Transition Awareness Example](https://github.com/joelschutz/stagehand/blob/master/examples/aware/main.go) ## Transitions @@ -195,4 +195,4 @@ go test ./... ## License -Stagehand is released under the [MIT License](https://github.com/stagehand/blob/master/LICENSE). +Stagehand is released under the [MIT License](https://github.com/joelschutz/stagehand/blob/master/LICENSE). From f9070ae9054aad7a95e930eb4254fa529036ee9d Mon Sep 17 00:00:00 2001 From: Joel Schutz Date: Fri, 1 Mar 2024 22:55:04 -0300 Subject: [PATCH 10/19] Corrects documentation indentation --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2760171..d57952a 100644 --- a/README.md +++ b/README.md @@ -63,8 +63,8 @@ func main() { manager := stagehand.NewSceneManager[MyState](scene1, state) if err := ebiten.RunGame(sm); err != nil { - log.Fatal(err) - } + log.Fatal(err) + } } ``` @@ -131,7 +131,7 @@ You can also define your own transition, simply implement the `SceneTransition` ```go type MyTransition struct { stagehand.BaseTransition - progress float64 // An example factor + progress float64 // An example factor } func (t *MyTransition) Start(from, to stagehand.Scene[MyState], sm *SceneManager[MyState]) { @@ -141,14 +141,14 @@ func (t *MyTransition) Start(from, to stagehand.Scene[MyState], sm *SceneManager } func (t *MyTransition) Update() error { - // Update the progress of the transition + // Update the progress of the transition t.progress += 0.01 - return t.BaseTransition.Update() + return t.BaseTransition.Update() } func (t *MyTransition) Draw(screen *ebiten.Image) { - // Optionally you can use a helper function to render each scene frame - toImg, fromImg := stagehand.PreDraw(screen.Bounds(), t.fromScene, t.toScene) + // Optionally you can use a helper function to render each scene frame + toImg, fromImg := stagehand.PreDraw(screen.Bounds(), t.fromScene, t.toScene) // Draw transition effect here } From 5544cf73647be46cb88dfb4776e3535a7cd7a1f2 Mon Sep 17 00:00:00 2001 From: Joel Schutz Date: Fri, 8 Mar 2024 20:46:16 -0300 Subject: [PATCH 11/19] Initial Director Implementation --- director.go | 51 ++++++++++++++++++++++++++++ helpers.go | 2 +- manager.go | 34 +++++++++++++++++++ scene.go | 46 ++++++------------------- transition.go | 94 +++++++++++++++++++++++++-------------------------- 5 files changed, 143 insertions(+), 84 deletions(-) create mode 100644 director.go create mode 100644 manager.go diff --git a/director.go b/director.go new file mode 100644 index 0000000..bc42c5e --- /dev/null +++ b/director.go @@ -0,0 +1,51 @@ +package stagehand + +type SceneTransitionTrigger int + +// A Directive is a struct that represents how a scene should be transitioned +type Directive[T any] struct { + Dest Scene[T, *SceneDirector[T]] + Transition SceneTransition[T, *SceneDirector[T]] + Trigger SceneTransitionTrigger +} + +// A SceneDirector is a struct that manages the transitions between scenes +type SceneDirector[T any] struct { + SceneManager[T] + RuleSet map[Scene[T, *SceneDirector[T]]][]Directive[T] +} + +func NewSceneDirector[T any](scene Scene[T, *SceneDirector[T]], state T, RuleSet map[Scene[T, *SceneDirector[T]]][]Directive[T]) *SceneDirector[T] { + s := &SceneDirector[T]{RuleSet: RuleSet} + s.current = scene + scene.Load(state, s) + return s +} + +// ProcessTrigger finds if a transition should be triggered +func (d *SceneDirector[T]) ProcessTrigger(trigger SceneTransitionTrigger) { + for _, directive := range d.RuleSet[d.current.(Scene[T, *SceneDirector[T]])] { + if directive.Trigger == trigger { + if directive.Transition != nil { + // With transition + // Equivalent to SwitchWithTransition + sc := d.current.(Scene[T, *SceneDirector[T]]) + directive.Transition.Start(sc, directive.Dest, &d.SceneManager) + if c, ok := sc.(TransitionAwareScene[T, *SceneDirector[T]]); ok { + directive.Dest.Load(c.PreTransition(directive.Dest), d) + } else { + directive.Dest.Load(sc.Unload(), d) + } + d.current = directive.Transition + } else { + // No transition + // Equivalent to SwitchTo + if c, ok := d.current.(Scene[T, *SceneDirector[T]]); ok { + directive.Dest.Load(c.Unload(), d) + d.current = directive.Dest + } + } + + } + } +} diff --git a/helpers.go b/helpers.go index b904a4b..1bab10b 100644 --- a/helpers.go +++ b/helpers.go @@ -33,7 +33,7 @@ func MaxInt(a, b int) int { } // Pre-draw scenes -func PreDraw[T any](bounds image.Rectangle, fromScene, toScene Scene[T]) (*ebiten.Image, *ebiten.Image) { +func PreDraw[T any, M SceneController[T]](bounds image.Rectangle, fromScene, toScene Scene[T, M]) (*ebiten.Image, *ebiten.Image) { fromImg := ebiten.NewImage(bounds.Dx(), bounds.Dy()) fromScene.Draw(fromImg) diff --git a/manager.go b/manager.go new file mode 100644 index 0000000..640518e --- /dev/null +++ b/manager.go @@ -0,0 +1,34 @@ +package stagehand + +import ebiten "github.com/hajimehoshi/ebiten/v2" + +type SceneManager[T any] struct { + current ProtoScene[T] +} + +func NewSceneManager[T any](scene Scene[T, *SceneManager[T]], state T) *SceneManager[T] { + s := &SceneManager[T]{current: scene} + scene.Load(state, s) + return s +} + +// Scene Switching +func (s *SceneManager[T]) SwitchTo(scene Scene[T, *SceneManager[T]]) { + if c, ok := s.current.(Scene[T, *SceneManager[T]]); ok { + scene.Load(c.Unload(), s) + s.current = scene + } +} + +// Ebiten Interface +func (s *SceneManager[T]) Update() error { + return s.current.Update() +} + +func (s *SceneManager[T]) Draw(screen *ebiten.Image) { + s.current.Draw(screen) +} + +func (s *SceneManager[T]) Layout(w, h int) (int, int) { + return s.current.Layout(w, h) +} diff --git a/scene.go b/scene.go index 1e26651..2124cb5 100644 --- a/scene.go +++ b/scene.go @@ -8,44 +8,18 @@ type ProtoScene[T any] interface { ebiten.Game } -type Scene[T any] interface { - ProtoScene[T] - Load(T, *SceneManager[T]) // Runs when scene is first started, must keep state and SceneManager - Unload() T // Runs when scene is discarted, must return last state -} -type TransitionAwareScene[T any] interface { - Scene[T] - PreTransition(Scene[T]) T // Runs before new scene is loaded, must return last state - PostTransition(T, Scene[T]) // Runs when old scene is unloaded -} - -type SceneManager[T any] struct { - current ProtoScene[T] -} - -func NewSceneManager[T any](scene Scene[T], state T) *SceneManager[T] { - s := &SceneManager[T]{current: scene} - scene.Load(state, s) - return s +type SceneController[T any] interface { + *SceneManager[T] | *SceneDirector[T] } -// Scene Switching -func (s *SceneManager[T]) SwitchTo(scene Scene[T]) { - if c, ok := s.current.(Scene[T]); ok { - scene.Load(c.Unload(), s) - s.current = scene - } -} - -// Ebiten Interface -func (s *SceneManager[T]) Update() error { - return s.current.Update() -} - -func (s *SceneManager[T]) Draw(screen *ebiten.Image) { - s.current.Draw(screen) +type Scene[T any, M SceneController[T]] interface { + ProtoScene[T] + Load(T, M) // Runs when scene is first started, must keep state and SceneManager + Unload() T // Runs when scene is discarted, must return last state } -func (s *SceneManager[T]) Layout(w, h int) (int, int) { - return s.current.Layout(w, h) +type TransitionAwareScene[T any, M SceneController[T]] interface { + Scene[T, M] + PreTransition(Scene[T, M]) T // Runs before new scene is loaded, must return last state + PostTransition(T, Scene[T, M]) // Runs when old scene is unloaded } diff --git a/transition.go b/transition.go index a10ff15..d9d3268 100644 --- a/transition.go +++ b/transition.go @@ -6,26 +6,26 @@ import ( "github.com/hajimehoshi/ebiten/v2" ) -type SceneTransition[T any] interface { +type SceneTransition[T any, M SceneController[T]] interface { ProtoScene[T] - Start(fromScene, toScene Scene[T], sm *SceneManager[T]) + Start(fromScene, toScene Scene[T, M], sm *SceneManager[T]) End() } -type BaseTransition[T any] struct { - fromScene Scene[T] - toScene Scene[T] +type BaseTransition[T any, M SceneController[T]] struct { + fromScene Scene[T, M] + toScene Scene[T, M] sm *SceneManager[T] } -func (t *BaseTransition[T]) Start(fromScene, toScene Scene[T], sm *SceneManager[T]) { +func (t *BaseTransition[T, M]) Start(fromScene, toScene Scene[T, M], sm *SceneManager[T]) { t.fromScene = fromScene t.toScene = toScene t.sm = sm } // Update updates the transition state -func (t *BaseTransition[T]) Update() error { +func (t *BaseTransition[T, M]) Update() error { // Update the scenes err := t.fromScene.Update() if err != nil { @@ -41,15 +41,15 @@ func (t *BaseTransition[T]) Update() error { } // Layout updates the layout of the scenes -func (t *BaseTransition[T]) Layout(outsideWidth, outsideHeight int) (int, int) { +func (t *BaseTransition[T, M]) Layout(outsideWidth, outsideHeight int) (int, int) { sw, sh := t.fromScene.Layout(outsideWidth, outsideHeight) tw, th := t.toScene.Layout(outsideWidth, outsideHeight) return MaxInt(sw, tw), MaxInt(sh, th) } -func (s *SceneManager[T]) returnFromTransition(scene, orgin Scene[T]) { - if c, ok := scene.(TransitionAwareScene[T]); ok { +func (s *SceneManager[T]) ReturnFromTransition(scene, orgin Scene[T, *SceneManager[T]]) { + if c, ok := scene.(TransitionAwareScene[T, *SceneManager[T]]); ok { c.PostTransition(orgin.Unload(), orgin) } else { scene.Load(orgin.Unload(), s) @@ -57,10 +57,10 @@ func (s *SceneManager[T]) returnFromTransition(scene, orgin Scene[T]) { s.current = scene } -func (s *SceneManager[T]) SwitchWithTransition(scene Scene[T], transition SceneTransition[T]) { - sc := s.current.(Scene[T]) +func (s *SceneManager[T]) SwitchWithTransition(scene Scene[T, *SceneManager[T]], transition SceneTransition[T, *SceneManager[T]]) { + sc := s.current.(Scene[T, *SceneManager[T]]) transition.Start(sc, scene, s) - if c, ok := sc.(TransitionAwareScene[T]); ok { + if c, ok := sc.(TransitionAwareScene[T, *SceneManager[T]]); ok { scene.Load(c.PreTransition(scene), s) } else { scene.Load(sc.Unload(), s) @@ -69,33 +69,33 @@ func (s *SceneManager[T]) SwitchWithTransition(scene Scene[T], transition SceneT } // Ends transition to the next scene -func (t *BaseTransition[T]) End() { - t.sm.returnFromTransition(t.toScene, t.fromScene) +func (t *BaseTransition[T, M]) End() { + t.sm.ReturnFromTransition(t.toScene.(Scene[T, *SceneManager[T]]), t.fromScene.(Scene[T, *SceneManager[T]])) } -type FadeTransition[T any] struct { - BaseTransition[T] +type FadeTransition[T any, M SceneController[T]] struct { + BaseTransition[T, M] factor float32 // factor used for the fade-in/fade-out effect alpha float32 // alpha value used for the fade-in/fade-out effect isFadingIn bool // whether the transition is currently fading in or out frameUpdated bool } -func NewFadeTransition[T any](factor float32) *FadeTransition[T] { - return &FadeTransition[T]{ +func NewFadeTransition[T any, M SceneController[T]](factor float32) *FadeTransition[T, M] { + return &FadeTransition[T, M]{ factor: factor, } } // Start starts the transition from the given "from" scene to the given "to" scene -func (t *FadeTransition[T]) Start(fromScene, toScene Scene[T], sm *SceneManager[T]) { +func (t *FadeTransition[T, M]) Start(fromScene, toScene Scene[T, M], sm *SceneManager[T]) { t.BaseTransition.Start(fromScene, toScene, sm) t.alpha = 0 t.isFadingIn = true } // Update updates the transition state -func (t *FadeTransition[T]) Update() error { +func (t *FadeTransition[T, M]) Update() error { if !t.frameUpdated { // Update the alpha value based on the current state of the transition if t.isFadingIn { @@ -119,7 +119,7 @@ func (t *FadeTransition[T]) Update() error { } // Draw draws the transition effect -func (t *FadeTransition[T]) Draw(screen *ebiten.Image) { +func (t *FadeTransition[T, M]) Draw(screen *ebiten.Image) { toImg, fromImg := PreDraw(screen.Bounds(), t.fromScene, t.toScene) toOp, fromOp := &ebiten.DrawImageOptions{}, &ebiten.DrawImageOptions{} @@ -140,8 +140,8 @@ func (t *FadeTransition[T]) Draw(screen *ebiten.Image) { t.frameUpdated = false } -type SlideTransition[T any] struct { - BaseTransition[T] +type SlideTransition[T any, M SceneController[T]] struct { + BaseTransition[T, M] factor float64 // factor used for the slide-in/slide-out effect direction SlideDirection offset float64 @@ -157,21 +157,21 @@ const ( BottomToTop ) -func NewSlideTransition[T any](direction SlideDirection, factor float64) *SlideTransition[T] { - return &SlideTransition[T]{ +func NewSlideTransition[T any, M SceneController[T]](direction SlideDirection, factor float64) *SlideTransition[T, M] { + return &SlideTransition[T, M]{ direction: direction, factor: factor, } } // Start starts the transition from the given "from" scene to the given "to" scene -func (t *SlideTransition[T]) Start(fromScene Scene[T], toScene Scene[T], sm *SceneManager[T]) { +func (t *SlideTransition[T, M]) Start(fromScene Scene[T, M], toScene Scene[T, M], sm *SceneManager[T]) { t.BaseTransition.Start(fromScene, toScene, sm) t.offset = 0 } // Update updates the transition state -func (t *SlideTransition[T]) Update() error { +func (t *SlideTransition[T, M]) Update() error { if !t.frameUpdated { // Update the offset value based on the current state of the transition if t.offset >= 1.0 { @@ -188,7 +188,7 @@ func (t *SlideTransition[T]) Update() error { } // Draw draws the transition effect -func (t *SlideTransition[T]) Draw(screen *ebiten.Image) { +func (t *SlideTransition[T, M]) Draw(screen *ebiten.Image) { toImg, fromImg := PreDraw(screen.Bounds(), t.fromScene, t.toScene) toOp, fromOp := &ebiten.DrawImageOptions{}, &ebiten.DrawImageOptions{} @@ -221,29 +221,29 @@ func (t *SlideTransition[T]) Draw(screen *ebiten.Image) { // Timed Variants of the transition -func NewTicksTimedFadeTransition[T any](duration time.Duration) *FadeTransition[T] { - return NewFadeTransition[T](float32(DurationToFactor(float64(ebiten.TPS()), duration))) +func NewTicksTimedFadeTransition[T any, M SceneController[T]](duration time.Duration) *FadeTransition[T, M] { + return NewFadeTransition[T, M](float32(DurationToFactor(float64(ebiten.TPS()), duration))) } -type TimedFadeTransition[T any] struct { - FadeTransition[T] +type TimedFadeTransition[T any, M SceneController[T]] struct { + FadeTransition[T, M] initialTime time.Time duration time.Duration } -func NewDurationTimedFadeTransition[T any](duration time.Duration) *TimedFadeTransition[T] { - return &TimedFadeTransition[T]{ +func NewDurationTimedFadeTransition[T any, M SceneController[T]](duration time.Duration) *TimedFadeTransition[T, M] { + return &TimedFadeTransition[T, M]{ duration: duration, - FadeTransition: *NewFadeTransition[T](0.), + FadeTransition: *NewFadeTransition[T, M](0.), } } -func (t *TimedFadeTransition[T]) Start(fromScene, toScene Scene[T], sm *SceneManager[T]) { +func (t *TimedFadeTransition[T, M]) Start(fromScene, toScene Scene[T, M], sm *SceneManager[T]) { t.FadeTransition.Start(fromScene, toScene, sm) t.initialTime = Clock.Now() } -func (t *TimedFadeTransition[T]) Update() error { +func (t *TimedFadeTransition[T, M]) Update() error { if !t.frameUpdated { // Update the alpha value based on the current state of the transition if t.isFadingIn { @@ -267,29 +267,29 @@ func (t *TimedFadeTransition[T]) Update() error { } -func NewTicksTimedSlideTransition[T any](direction SlideDirection, duration time.Duration) *SlideTransition[T] { - return NewSlideTransition[T](direction, DurationToFactor(float64(ebiten.TPS()), duration)) +func NewTicksTimedSlideTransition[T any, M SceneController[T]](direction SlideDirection, duration time.Duration) *SlideTransition[T, M] { + return NewSlideTransition[T, M](direction, DurationToFactor(float64(ebiten.TPS()), duration)) } -type TimedSlideTransition[T any] struct { - SlideTransition[T] +type TimedSlideTransition[T any, M SceneController[T]] struct { + SlideTransition[T, M] initialTime time.Time duration time.Duration } -func NewDurationTimedSlideTransition[T any](direction SlideDirection, duration time.Duration) *TimedSlideTransition[T] { - return &TimedSlideTransition[T]{ +func NewDurationTimedSlideTransition[T any, M SceneController[T]](direction SlideDirection, duration time.Duration) *TimedSlideTransition[T, M] { + return &TimedSlideTransition[T, M]{ duration: duration, - SlideTransition: *NewSlideTransition[T](direction, 0.), + SlideTransition: *NewSlideTransition[T, M](direction, 0.), } } -func (t *TimedSlideTransition[T]) Start(fromScene, toScene Scene[T], sm *SceneManager[T]) { +func (t *TimedSlideTransition[T, M]) Start(fromScene, toScene Scene[T, M], sm *SceneManager[T]) { t.SlideTransition.Start(fromScene, toScene, sm) t.initialTime = Clock.Now() } -func (t *TimedSlideTransition[T]) Update() error { +func (t *TimedSlideTransition[T, M]) Update() error { if !t.frameUpdated { // Update the offset value based on the current state of the transition if t.offset >= 1.0 { From f7c9f896472b8037621dcc73e393442d4a383964 Mon Sep 17 00:00:00 2001 From: Joel Schutz Date: Fri, 8 Mar 2024 20:47:05 -0300 Subject: [PATCH 12/19] Updates Tests --- helpers_test.go | 2 +- scene_test.go | 24 +++++++++++++----------- transition_test.go | 32 ++++++++++++++++---------------- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/helpers_test.go b/helpers_test.go index ba2cbbe..5f5a2d9 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -36,7 +36,7 @@ func TestPreDraw(t *testing.T) { from := &MockScene{} to := &MockScene{} - toImg, fromImg := PreDraw[int](image.Rect(0, 0, 10, 10), from, to) + toImg, fromImg := PreDraw[int, *SceneManager[int]](image.Rect(0, 0, 10, 10), from, to) assert.True(t, from.drawCalled) assert.True(t, to.drawCalled) diff --git a/scene_test.go b/scene_test.go index c3f34f0..7d8927e 100644 --- a/scene_test.go +++ b/scene_test.go @@ -40,27 +40,29 @@ func (m *MockScene) Layout(w, h int) (int, int) { return w, h } -type MockTransition[T any] struct { - fromScene Scene[T] - toScene Scene[T] +type MockTransition[T any, M SceneController[T]] struct { + fromScene Scene[T, M] + toScene Scene[T, M] startCalled bool } -func NewMockTransition[T any]() *MockTransition[T] { return &MockTransition[T]{} } +func NewMockTransition[T any, M SceneController[T]]() *MockTransition[T, M] { + return &MockTransition[T, M]{} +} -func (t *MockTransition[T]) Start(fromScene, toScene Scene[T], sm *SceneManager[T]) { +func (t *MockTransition[T, M]) Start(fromScene, toScene Scene[T, M], sm *SceneManager[T]) { t.fromScene = fromScene t.toScene = toScene t.startCalled = true } -func (t *MockTransition[T]) End() {} +func (t *MockTransition[T, M]) End() {} -func (t *MockTransition[T]) Update() error { return nil } +func (t *MockTransition[T, M]) Update() error { return nil } -func (t *MockTransition[T]) Draw(screen *ebiten.Image) {} +func (t *MockTransition[T, M]) Draw(screen *ebiten.Image) {} -func (t *MockTransition[T]) Layout(w, h int) (int, int) { return w, h } +func (t *MockTransition[T, M]) Layout(w, h int) (int, int) { return w, h } func TestSceneManager_SwitchTo(t *testing.T) { sm := NewSceneManager[int](&MockScene{}, 0) @@ -73,7 +75,7 @@ func TestSceneManager_SwitchTo(t *testing.T) { func TestSceneManager_SwitchWithTransition(t *testing.T) { sm := NewSceneManager[int](&MockScene{}, 0) mockScene := &MockScene{} - mockTransition := &MockTransition[int]{} + mockTransition := &MockTransition[int, *SceneManager[int]]{} sm.SwitchWithTransition(mockScene, mockTransition) assert.True(t, mockTransition.startCalled) assert.True(t, sm.current == mockTransition) @@ -115,5 +117,5 @@ func TestSceneManager_Load_Unload(t *testing.T) { assert.True(t, to.loadCalled) assert.True(t, from.unloadCalled) - assert.Equal(t, 42, sm.current.(Scene[int]).Unload()) + assert.Equal(t, 42, sm.current.(Scene[int, *SceneManager[int]]).Unload()) } diff --git a/transition_test.go b/transition_test.go index 3c32e71..3624f1f 100644 --- a/transition_test.go +++ b/transition_test.go @@ -19,7 +19,7 @@ func (m *MockClock) Since(t time.Time) time.Duration { return m.currentTime.Sub( func (m *MockClock) Until(t time.Time) time.Duration { return t.Sub(m.currentTime) } type baseTransitionImplementation struct { - BaseTransition[int] + BaseTransition[int, *SceneManager[int]] } func (b *baseTransitionImplementation) Draw(screen *ebiten.Image) {} @@ -30,12 +30,12 @@ type MockTransitionAwareScene struct { postTransitionCalled bool } -func (m *MockTransitionAwareScene) PreTransition(fromScene Scene[int]) int { +func (m *MockTransitionAwareScene) PreTransition(fromScene Scene[int, *SceneManager[int]]) int { m.preTransitionCalled = true return 0 } -func (m *MockTransitionAwareScene) PostTransition(state int, toScene Scene[int]) { +func (m *MockTransitionAwareScene) PostTransition(state int, toScene Scene[int, *SceneManager[int]]) { m.postTransitionCalled = true } @@ -73,7 +73,7 @@ func TestBaseTransition_End(t *testing.T) { sm.SwitchWithTransition(to, trans) trans.End() - fmt.Println(sm.current.(Scene[int]), to) + fmt.Println(sm.current.(Scene[int, *SceneManager[int]]), to) assert.Equal(t, to, sm.current) } @@ -108,7 +108,7 @@ func TestFadeTransition_UpdateOncePerFrame(t *testing.T) { var value float32 = .6 from := &MockScene{} to := &MockScene{} - trans := NewFadeTransition[int](value) + trans := NewFadeTransition[int, *SceneManager[int]](value) trans.Start(from, to, nil) err := trans.Update() @@ -124,7 +124,7 @@ func TestFadeTransition_UpdateOncePerFrame(t *testing.T) { func TestFadeTransition_Update(t *testing.T) { from := &MockScene{} to := &MockScene{} - trans := NewFadeTransition[int](.5) + trans := NewFadeTransition[int, *SceneManager[int]](.5) sm := NewSceneManager[int](from, 0) sm.SwitchWithTransition(to, trans) @@ -159,7 +159,7 @@ func TestFadeTransition_Update(t *testing.T) { func TestFadeTransition_Start(t *testing.T) { from := &MockScene{} to := &MockScene{} - trans := NewFadeTransition[int](.5) + trans := NewFadeTransition[int, *SceneManager[int]](.5) trans.Start(from, to, nil) assert.Equal(t, from, trans.fromScene) @@ -171,7 +171,7 @@ func TestFadeTransition_Start(t *testing.T) { func TestFadeTransition_Draw(t *testing.T) { from := &MockScene{} to := &MockScene{} - trans := NewFadeTransition[int](.5) + trans := NewFadeTransition[int, *SceneManager[int]](.5) trans.Start(from, to, nil) trans.Update() @@ -187,7 +187,7 @@ func TestSlideTransition_UpdateOncePerFrame(t *testing.T) { var value float64 = .6 from := &MockScene{} to := &MockScene{} - trans := NewSlideTransition[int](RightToLeft, value) + trans := NewSlideTransition[int, *SceneManager[int]](RightToLeft, value) trans.Start(from, to, nil) err := trans.Update() @@ -208,7 +208,7 @@ func TestSlideTransition_Update(t *testing.T) { } for _, direction := range variations { - trans := NewSlideTransition[int](direction, .5) + trans := NewSlideTransition[int, *SceneManager[int]](direction, .5) sm := NewSceneManager[int](from, 0) sm.SwitchWithTransition(to, trans) @@ -234,7 +234,7 @@ func TestSlideTransition_Update(t *testing.T) { func TestSlideTransition_Start(t *testing.T) { from := &MockScene{} to := &MockScene{} - trans := NewSlideTransition[int](TopToBottom, .5) + trans := NewSlideTransition[int, *SceneManager[int]](TopToBottom, .5) trans.Start(from, to, nil) assert.Equal(t, from, trans.fromScene) @@ -251,7 +251,7 @@ func TestSlideTransition_Draw(t *testing.T) { } for _, direction := range variations { - trans := NewSlideTransition[int](direction, .5) + trans := NewSlideTransition[int, *SceneManager[int]](direction, .5) trans.Start(from, to, nil) trans.Update() @@ -265,7 +265,7 @@ func TestTimedFadeTransition_Update(t *testing.T) { Clock = &MockClock{currentTime: now} from := &MockScene{} to := &MockScene{} - trans := NewDurationTimedFadeTransition[int](time.Second) + trans := NewDurationTimedFadeTransition[int, *SceneManager[int]](time.Second) sm := NewSceneManager[int](from, 0) sm.SwitchWithTransition(to, trans) @@ -314,7 +314,7 @@ func TestTimedFadeTransition_Start(t *testing.T) { Clock = &MockClock{currentTime: now} from := &MockScene{} to := &MockScene{} - trans := NewDurationTimedFadeTransition[int](time.Second) + trans := NewDurationTimedFadeTransition[int, *SceneManager[int]](time.Second) trans.Start(from, to, nil) assert.Equal(t, from, trans.fromScene) @@ -332,7 +332,7 @@ func TestTimedSlideTransition_Update(t *testing.T) { for _, direction := range variations { Clock = &MockClock{currentTime: time.Now()} - trans := NewDurationTimedSlideTransition[int](direction, time.Second) + trans := NewDurationTimedSlideTransition[int, *SceneManager[int]](direction, time.Second) sm := NewSceneManager[int](from, 0) sm.SwitchWithTransition(to, trans) @@ -370,7 +370,7 @@ func TestTimedSlideTransition_Start(t *testing.T) { Clock = &MockClock{currentTime: now} from := &MockScene{} to := &MockScene{} - trans := NewDurationTimedSlideTransition[int](TopToBottom, time.Second) + trans := NewDurationTimedSlideTransition[int, *SceneManager[int]](TopToBottom, time.Second) trans.Start(from, to, nil) assert.Equal(t, from, trans.fromScene) From 0651a50a36acf492cf4f54627a3154a9529a1fbe Mon Sep 17 00:00:00 2001 From: Joel Schutz Date: Fri, 8 Mar 2024 20:48:34 -0300 Subject: [PATCH 13/19] Updates examples --- examples/aware/main.go | 8 +-- examples/director/main.go | 106 ++++++++++++++++++++++++++++++++++++++ examples/simple/main.go | 4 +- examples/timed/main.go | 4 +- 4 files changed, 114 insertions(+), 8 deletions(-) create mode 100644 examples/director/main.go diff --git a/examples/aware/main.go b/examples/aware/main.go index 9e3e4b1..1124f38 100644 --- a/examples/aware/main.go +++ b/examples/aware/main.go @@ -42,12 +42,12 @@ func (s *BaseScene) Unload() State { return s.count } -func (s *BaseScene) PreTransition(toScene stagehand.Scene[State]) State { +func (s *BaseScene) PreTransition(toScene stagehand.Scene[State, *stagehand.SceneManager[State]]) State { s.count.OnTransition = true return s.count } -func (s *BaseScene) PostTransition(state State, fromScene stagehand.Scene[State]) { +func (s *BaseScene) PostTransition(state State, fromScene stagehand.Scene[State, *stagehand.SceneManager[State]]) { s.count.OnTransition = false } @@ -60,7 +60,7 @@ func (s *FirstScene) Update() error { s.count.Count++ } if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { - s.sm.SwitchWithTransition(&SecondScene{}, stagehand.NewSlideTransition[State](stagehand.TopToBottom, .05)) + s.sm.SwitchWithTransition(&SecondScene{}, stagehand.NewSlideTransition[State, *stagehand.SceneManager[State]](stagehand.TopToBottom, .05)) } return nil } @@ -83,7 +83,7 @@ func (s *SecondScene) Update() error { s.count.Count-- } if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { - s.sm.SwitchWithTransition(&FirstScene{}, stagehand.NewSlideTransition[State](stagehand.BottomToTop, .05)) + s.sm.SwitchWithTransition(&FirstScene{}, stagehand.NewSlideTransition[State, *stagehand.SceneManager[State]](stagehand.BottomToTop, .05)) } return nil } diff --git a/examples/director/main.go b/examples/director/main.go new file mode 100644 index 0000000..5ac0ac8 --- /dev/null +++ b/examples/director/main.go @@ -0,0 +1,106 @@ +package main + +import ( + "fmt" + "image" + "image/color" + "log" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/ebitenutil" + "github.com/hajimehoshi/ebiten/v2/inpututil" + "github.com/joelschutz/stagehand" +) + +const ( + screenWidth = 640 + screenHeight = 480 +) + +type State int + +var ( + Trigger stagehand.SceneTransitionTrigger = 1 +) + +type BaseScene struct { + bounds image.Rectangle + count State + sm *stagehand.SceneDirector[State] +} + +func (s *BaseScene) Layout(w, h int) (int, int) { + s.bounds = image.Rect(0, 0, w, h) + return w, h +} + +func (s *BaseScene) Load(st State, sm *stagehand.SceneDirector[State]) { + s.count = st + s.sm = sm +} + +func (s *BaseScene) Unload() State { + return s.count +} + +type FirstScene struct { + BaseScene +} + +func (s *FirstScene) Update() error { + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { + s.count++ + } + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { + s.sm.ProcessTrigger(Trigger) + } + return nil +} + +func (s *FirstScene) Draw(screen *ebiten.Image) { + screen.Fill(color.RGBA{255, 0, 0, 255}) // Fill Red + ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Count: %v, WindowSize: %s", s.count, s.bounds.Max), s.bounds.Dx()/2, s.bounds.Dy()/2) +} + +type SecondScene struct { + BaseScene +} + +func (s *SecondScene) Update() error { + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { + s.count-- + } + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { + s.sm.ProcessTrigger(Trigger) + } + return nil +} + +func (s *SecondScene) Draw(screen *ebiten.Image) { + screen.Fill(color.RGBA{0, 0, 255, 255}) // Fill Blue + ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Count: %v, WindowSize: %s", s.count, s.bounds.Max), s.bounds.Dx()/2, s.bounds.Dy()/2) +} + +func main() { + ebiten.SetWindowSize(screenWidth, screenHeight) + ebiten.SetWindowTitle("My Game") + ebiten.SetWindowResizable(true) + + state := State(10) + + s1 := &FirstScene{} + s2 := &SecondScene{} + rs := map[stagehand.Scene[State, *stagehand.SceneDirector[State]]][]stagehand.Directive[State]{ + s1: []stagehand.Directive[State]{ + stagehand.Directive[State]{Dest: s2, Trigger: Trigger}, + }, + s2: []stagehand.Directive[State]{ + stagehand.Directive[State]{Dest: s1, Trigger: Trigger}, + }, + } + sm := stagehand.NewSceneDirector[State](s1, state, rs) + + if err := ebiten.RunGame(sm); err != nil { + log.Fatal(err) + } +} diff --git a/examples/simple/main.go b/examples/simple/main.go index 6a0f93d..7f3baf5 100644 --- a/examples/simple/main.go +++ b/examples/simple/main.go @@ -67,7 +67,7 @@ func (s *SecondScene) Update() error { s.count-- } if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { - s.sm.SwitchWithTransition(&ThirdScene{}, stagehand.NewFadeTransition[State](.05)) + s.sm.SwitchWithTransition(&ThirdScene{}, stagehand.NewFadeTransition[State, *stagehand.SceneManager[State]](.05)) } return nil } @@ -86,7 +86,7 @@ func (s *ThirdScene) Update() error { s.count *= 2 } if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { - s.sm.SwitchWithTransition(&FirstScene{}, stagehand.NewSlideTransition[State](stagehand.RightToLeft, .05)) + s.sm.SwitchWithTransition(&FirstScene{}, stagehand.NewSlideTransition[State, *stagehand.SceneManager[State]](stagehand.RightToLeft, .05)) } return nil } diff --git a/examples/timed/main.go b/examples/timed/main.go index 3b40e36..b43610f 100644 --- a/examples/timed/main.go +++ b/examples/timed/main.go @@ -49,7 +49,7 @@ func (s *FirstScene) Update() error { s.count++ } if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { - s.sm.SwitchWithTransition(&SecondScene{}, stagehand.NewTicksTimedSlideTransition[State](stagehand.LeftToRight, time.Second*time.Duration(s.count))) + s.sm.SwitchWithTransition(&SecondScene{}, stagehand.NewTicksTimedSlideTransition[State, *stagehand.SceneManager[State]](stagehand.LeftToRight, time.Second*time.Duration(s.count))) } return nil } @@ -68,7 +68,7 @@ func (s *SecondScene) Update() error { s.count-- } if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { - s.sm.SwitchWithTransition(&FirstScene{}, stagehand.NewDurationTimedSlideTransition[State](stagehand.RightToLeft, time.Second*time.Duration(s.count))) + s.sm.SwitchWithTransition(&FirstScene{}, stagehand.NewDurationTimedSlideTransition[State, *stagehand.SceneManager[State]](stagehand.RightToLeft, time.Second*time.Duration(s.count))) } return nil } From 7921b5bb55efe6d1d1a05a660c66fa75672b1884 Mon Sep 17 00:00:00 2001 From: Joel Schutz Date: Fri, 8 Mar 2024 21:26:10 -0300 Subject: [PATCH 14/19] Adds missing tests for director --- director_test.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 director_test.go diff --git a/director_test.go b/director_test.go new file mode 100644 index 0000000..51c55de --- /dev/null +++ b/director_test.go @@ -0,0 +1,45 @@ +package stagehand + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type DirectidScene struct { + MockScene +} + +func (m *DirectidScene) Load(state int, sm *SceneDirector[int]) { + m.loadCalled = true + m.unloadReturns = state +} + +func TestSceneDirector_NewSceneDirector(t *testing.T) { + mockScene := &DirectidScene{} + ruleSet := make(map[Scene[int, *SceneDirector[int]]][]Directive[int]) + + director := NewSceneDirector[int](mockScene, 1, ruleSet) + + assert.NotNil(t, director) + assert.Equal(t, mockScene, director.current) +} + +func TestSceneDirector_ProcessTrigger(t *testing.T) { + mockScene := &DirectidScene{} + mockScene2 := &DirectidScene{} + ruleSet := make(map[Scene[int, *SceneDirector[int]]][]Directive[int]) + + director := NewSceneDirector[int](mockScene, 1, ruleSet) + + rule := Directive[int]{Dest: mockScene2, Trigger: 2} + ruleSet[mockScene] = []Directive[int]{rule} + + // Call the ProcessTrigger method with wrong trigger + director.ProcessTrigger(1) + assert.NotEqual(t, rule.Dest, director.current) + + // Call the ProcessTrigger method with correct trigger + director.ProcessTrigger(2) + assert.Equal(t, rule.Dest, director.current) +} From db9e258c6c10b6fcb6166eab4fddef15ba24d054 Mon Sep 17 00:00:00 2001 From: Joel Schutz Date: Sat, 9 Mar 2024 18:38:18 -0300 Subject: [PATCH 15/19] Reimplements `director` with simplified interface --- director.go | 27 ++++++++----- helpers.go | 2 +- manager.go | 26 +++++++++++-- scene.go | 17 ++++---- transition.go | 106 ++++++++++++++++++++------------------------------ 5 files changed, 94 insertions(+), 84 deletions(-) diff --git a/director.go b/director.go index bc42c5e..1113868 100644 --- a/director.go +++ b/director.go @@ -4,18 +4,18 @@ type SceneTransitionTrigger int // A Directive is a struct that represents how a scene should be transitioned type Directive[T any] struct { - Dest Scene[T, *SceneDirector[T]] - Transition SceneTransition[T, *SceneDirector[T]] + Dest Scene[T] + Transition SceneTransition[T] Trigger SceneTransitionTrigger } // A SceneDirector is a struct that manages the transitions between scenes type SceneDirector[T any] struct { SceneManager[T] - RuleSet map[Scene[T, *SceneDirector[T]]][]Directive[T] + RuleSet map[Scene[T]][]Directive[T] } -func NewSceneDirector[T any](scene Scene[T, *SceneDirector[T]], state T, RuleSet map[Scene[T, *SceneDirector[T]]][]Directive[T]) *SceneDirector[T] { +func NewSceneDirector[T any](scene Scene[T], state T, RuleSet map[Scene[T]][]Directive[T]) *SceneDirector[T] { s := &SceneDirector[T]{RuleSet: RuleSet} s.current = scene scene.Load(state, s) @@ -24,14 +24,14 @@ func NewSceneDirector[T any](scene Scene[T, *SceneDirector[T]], state T, RuleSet // ProcessTrigger finds if a transition should be triggered func (d *SceneDirector[T]) ProcessTrigger(trigger SceneTransitionTrigger) { - for _, directive := range d.RuleSet[d.current.(Scene[T, *SceneDirector[T]])] { + for _, directive := range d.RuleSet[d.current.(Scene[T])] { if directive.Trigger == trigger { if directive.Transition != nil { // With transition // Equivalent to SwitchWithTransition - sc := d.current.(Scene[T, *SceneDirector[T]]) - directive.Transition.Start(sc, directive.Dest, &d.SceneManager) - if c, ok := sc.(TransitionAwareScene[T, *SceneDirector[T]]); ok { + sc := d.current.(Scene[T]) + directive.Transition.Start(sc, directive.Dest, d) + if c, ok := sc.(TransitionAwareScene[T]); ok { directive.Dest.Load(c.PreTransition(directive.Dest), d) } else { directive.Dest.Load(sc.Unload(), d) @@ -40,7 +40,7 @@ func (d *SceneDirector[T]) ProcessTrigger(trigger SceneTransitionTrigger) { } else { // No transition // Equivalent to SwitchTo - if c, ok := d.current.(Scene[T, *SceneDirector[T]]); ok { + if c, ok := d.current.(Scene[T]); ok { directive.Dest.Load(c.Unload(), d) d.current = directive.Dest } @@ -49,3 +49,12 @@ func (d *SceneDirector[T]) ProcessTrigger(trigger SceneTransitionTrigger) { } } } + +func (d *SceneDirector[T]) ReturnFromTransition(scene, orgin Scene[T]) { + if c, ok := scene.(TransitionAwareScene[T]); ok { + c.PostTransition(orgin.Unload(), orgin) + } else { + scene.Load(orgin.Unload(), d) + } + d.current = scene +} diff --git a/helpers.go b/helpers.go index 1bab10b..b904a4b 100644 --- a/helpers.go +++ b/helpers.go @@ -33,7 +33,7 @@ func MaxInt(a, b int) int { } // Pre-draw scenes -func PreDraw[T any, M SceneController[T]](bounds image.Rectangle, fromScene, toScene Scene[T, M]) (*ebiten.Image, *ebiten.Image) { +func PreDraw[T any](bounds image.Rectangle, fromScene, toScene Scene[T]) (*ebiten.Image, *ebiten.Image) { fromImg := ebiten.NewImage(bounds.Dx(), bounds.Dy()) fromScene.Draw(fromImg) diff --git a/manager.go b/manager.go index 640518e..b2f9bea 100644 --- a/manager.go +++ b/manager.go @@ -6,20 +6,40 @@ type SceneManager[T any] struct { current ProtoScene[T] } -func NewSceneManager[T any](scene Scene[T, *SceneManager[T]], state T) *SceneManager[T] { +func NewSceneManager[T any](scene Scene[T], state T) *SceneManager[T] { s := &SceneManager[T]{current: scene} scene.Load(state, s) return s } // Scene Switching -func (s *SceneManager[T]) SwitchTo(scene Scene[T, *SceneManager[T]]) { - if c, ok := s.current.(Scene[T, *SceneManager[T]]); ok { +func (s *SceneManager[T]) SwitchTo(scene Scene[T]) { + if c, ok := s.current.(Scene[T]); ok { scene.Load(c.Unload(), s) s.current = scene } } +func (s *SceneManager[T]) SwitchWithTransition(scene Scene[T], transition SceneTransition[T]) { + sc := s.current.(Scene[T]) + transition.Start(sc, scene, s) + if c, ok := sc.(TransitionAwareScene[T]); ok { + scene.Load(c.PreTransition(scene), s) + } else { + scene.Load(sc.Unload(), s) + } + s.current = transition +} + +func (s *SceneManager[T]) ReturnFromTransition(scene, orgin Scene[T]) { + if c, ok := scene.(TransitionAwareScene[T]); ok { + c.PostTransition(orgin.Unload(), orgin) + } else { + scene.Load(orgin.Unload(), s) + } + s.current = scene +} + // Ebiten Interface func (s *SceneManager[T]) Update() error { return s.current.Update() diff --git a/scene.go b/scene.go index 2124cb5..3c617f4 100644 --- a/scene.go +++ b/scene.go @@ -9,17 +9,18 @@ type ProtoScene[T any] interface { } type SceneController[T any] interface { - *SceneManager[T] | *SceneDirector[T] + // *SceneManager[T] | *SceneDirector[T] + ReturnFromTransition(scene, orgin Scene[T]) } -type Scene[T any, M SceneController[T]] interface { +type Scene[T any] interface { ProtoScene[T] - Load(T, M) // Runs when scene is first started, must keep state and SceneManager - Unload() T // Runs when scene is discarted, must return last state + Load(T, SceneController[T]) // Runs when scene is first started, must keep state and SceneManager + Unload() T // Runs when scene is discarted, must return last state } -type TransitionAwareScene[T any, M SceneController[T]] interface { - Scene[T, M] - PreTransition(Scene[T, M]) T // Runs before new scene is loaded, must return last state - PostTransition(T, Scene[T, M]) // Runs when old scene is unloaded +type TransitionAwareScene[T any] interface { + Scene[T] + PreTransition(Scene[T]) T // Runs before new scene is loaded, must return last state + PostTransition(T, Scene[T]) // Runs when old scene is unloaded } diff --git a/transition.go b/transition.go index d9d3268..01181ff 100644 --- a/transition.go +++ b/transition.go @@ -6,26 +6,26 @@ import ( "github.com/hajimehoshi/ebiten/v2" ) -type SceneTransition[T any, M SceneController[T]] interface { +type SceneTransition[T any] interface { ProtoScene[T] - Start(fromScene, toScene Scene[T, M], sm *SceneManager[T]) + Start(fromScene, toScene Scene[T], sm SceneController[T]) End() } -type BaseTransition[T any, M SceneController[T]] struct { - fromScene Scene[T, M] - toScene Scene[T, M] - sm *SceneManager[T] +type BaseTransition[T any] struct { + fromScene Scene[T] + toScene Scene[T] + sm SceneController[T] } -func (t *BaseTransition[T, M]) Start(fromScene, toScene Scene[T, M], sm *SceneManager[T]) { +func (t *BaseTransition[T]) Start(fromScene, toScene Scene[T], sm SceneController[T]) { t.fromScene = fromScene t.toScene = toScene t.sm = sm } // Update updates the transition state -func (t *BaseTransition[T, M]) Update() error { +func (t *BaseTransition[T]) Update() error { // Update the scenes err := t.fromScene.Update() if err != nil { @@ -41,61 +41,41 @@ func (t *BaseTransition[T, M]) Update() error { } // Layout updates the layout of the scenes -func (t *BaseTransition[T, M]) Layout(outsideWidth, outsideHeight int) (int, int) { +func (t *BaseTransition[T]) Layout(outsideWidth, outsideHeight int) (int, int) { sw, sh := t.fromScene.Layout(outsideWidth, outsideHeight) tw, th := t.toScene.Layout(outsideWidth, outsideHeight) return MaxInt(sw, tw), MaxInt(sh, th) } -func (s *SceneManager[T]) ReturnFromTransition(scene, orgin Scene[T, *SceneManager[T]]) { - if c, ok := scene.(TransitionAwareScene[T, *SceneManager[T]]); ok { - c.PostTransition(orgin.Unload(), orgin) - } else { - scene.Load(orgin.Unload(), s) - } - s.current = scene -} - -func (s *SceneManager[T]) SwitchWithTransition(scene Scene[T, *SceneManager[T]], transition SceneTransition[T, *SceneManager[T]]) { - sc := s.current.(Scene[T, *SceneManager[T]]) - transition.Start(sc, scene, s) - if c, ok := sc.(TransitionAwareScene[T, *SceneManager[T]]); ok { - scene.Load(c.PreTransition(scene), s) - } else { - scene.Load(sc.Unload(), s) - } - s.current = transition -} - // Ends transition to the next scene -func (t *BaseTransition[T, M]) End() { - t.sm.ReturnFromTransition(t.toScene.(Scene[T, *SceneManager[T]]), t.fromScene.(Scene[T, *SceneManager[T]])) +func (t *BaseTransition[T]) End() { + t.sm.ReturnFromTransition(t.toScene.(Scene[T]), t.fromScene.(Scene[T])) } -type FadeTransition[T any, M SceneController[T]] struct { - BaseTransition[T, M] +type FadeTransition[T any] struct { + BaseTransition[T] factor float32 // factor used for the fade-in/fade-out effect alpha float32 // alpha value used for the fade-in/fade-out effect isFadingIn bool // whether the transition is currently fading in or out frameUpdated bool } -func NewFadeTransition[T any, M SceneController[T]](factor float32) *FadeTransition[T, M] { - return &FadeTransition[T, M]{ +func NewFadeTransition[T any](factor float32) *FadeTransition[T] { + return &FadeTransition[T]{ factor: factor, } } // Start starts the transition from the given "from" scene to the given "to" scene -func (t *FadeTransition[T, M]) Start(fromScene, toScene Scene[T, M], sm *SceneManager[T]) { +func (t *FadeTransition[T]) Start(fromScene, toScene Scene[T], sm SceneController[T]) { t.BaseTransition.Start(fromScene, toScene, sm) t.alpha = 0 t.isFadingIn = true } // Update updates the transition state -func (t *FadeTransition[T, M]) Update() error { +func (t *FadeTransition[T]) Update() error { if !t.frameUpdated { // Update the alpha value based on the current state of the transition if t.isFadingIn { @@ -119,7 +99,7 @@ func (t *FadeTransition[T, M]) Update() error { } // Draw draws the transition effect -func (t *FadeTransition[T, M]) Draw(screen *ebiten.Image) { +func (t *FadeTransition[T]) Draw(screen *ebiten.Image) { toImg, fromImg := PreDraw(screen.Bounds(), t.fromScene, t.toScene) toOp, fromOp := &ebiten.DrawImageOptions{}, &ebiten.DrawImageOptions{} @@ -140,8 +120,8 @@ func (t *FadeTransition[T, M]) Draw(screen *ebiten.Image) { t.frameUpdated = false } -type SlideTransition[T any, M SceneController[T]] struct { - BaseTransition[T, M] +type SlideTransition[T any] struct { + BaseTransition[T] factor float64 // factor used for the slide-in/slide-out effect direction SlideDirection offset float64 @@ -157,21 +137,21 @@ const ( BottomToTop ) -func NewSlideTransition[T any, M SceneController[T]](direction SlideDirection, factor float64) *SlideTransition[T, M] { - return &SlideTransition[T, M]{ +func NewSlideTransition[T any](direction SlideDirection, factor float64) *SlideTransition[T] { + return &SlideTransition[T]{ direction: direction, factor: factor, } } // Start starts the transition from the given "from" scene to the given "to" scene -func (t *SlideTransition[T, M]) Start(fromScene Scene[T, M], toScene Scene[T, M], sm *SceneManager[T]) { +func (t *SlideTransition[T]) Start(fromScene Scene[T], toScene Scene[T], sm SceneController[T]) { t.BaseTransition.Start(fromScene, toScene, sm) t.offset = 0 } // Update updates the transition state -func (t *SlideTransition[T, M]) Update() error { +func (t *SlideTransition[T]) Update() error { if !t.frameUpdated { // Update the offset value based on the current state of the transition if t.offset >= 1.0 { @@ -188,7 +168,7 @@ func (t *SlideTransition[T, M]) Update() error { } // Draw draws the transition effect -func (t *SlideTransition[T, M]) Draw(screen *ebiten.Image) { +func (t *SlideTransition[T]) Draw(screen *ebiten.Image) { toImg, fromImg := PreDraw(screen.Bounds(), t.fromScene, t.toScene) toOp, fromOp := &ebiten.DrawImageOptions{}, &ebiten.DrawImageOptions{} @@ -221,29 +201,29 @@ func (t *SlideTransition[T, M]) Draw(screen *ebiten.Image) { // Timed Variants of the transition -func NewTicksTimedFadeTransition[T any, M SceneController[T]](duration time.Duration) *FadeTransition[T, M] { - return NewFadeTransition[T, M](float32(DurationToFactor(float64(ebiten.TPS()), duration))) +func NewTicksTimedFadeTransition[T any](duration time.Duration) *FadeTransition[T] { + return NewFadeTransition[T](float32(DurationToFactor(float64(ebiten.TPS()), duration))) } -type TimedFadeTransition[T any, M SceneController[T]] struct { - FadeTransition[T, M] +type TimedFadeTransition[T any] struct { + FadeTransition[T] initialTime time.Time duration time.Duration } -func NewDurationTimedFadeTransition[T any, M SceneController[T]](duration time.Duration) *TimedFadeTransition[T, M] { - return &TimedFadeTransition[T, M]{ +func NewDurationTimedFadeTransition[T any](duration time.Duration) *TimedFadeTransition[T] { + return &TimedFadeTransition[T]{ duration: duration, - FadeTransition: *NewFadeTransition[T, M](0.), + FadeTransition: *NewFadeTransition[T](0.), } } -func (t *TimedFadeTransition[T, M]) Start(fromScene, toScene Scene[T, M], sm *SceneManager[T]) { +func (t *TimedFadeTransition[T]) Start(fromScene, toScene Scene[T], sm SceneController[T]) { t.FadeTransition.Start(fromScene, toScene, sm) t.initialTime = Clock.Now() } -func (t *TimedFadeTransition[T, M]) Update() error { +func (t *TimedFadeTransition[T]) Update() error { if !t.frameUpdated { // Update the alpha value based on the current state of the transition if t.isFadingIn { @@ -267,29 +247,29 @@ func (t *TimedFadeTransition[T, M]) Update() error { } -func NewTicksTimedSlideTransition[T any, M SceneController[T]](direction SlideDirection, duration time.Duration) *SlideTransition[T, M] { - return NewSlideTransition[T, M](direction, DurationToFactor(float64(ebiten.TPS()), duration)) +func NewTicksTimedSlideTransition[T any](direction SlideDirection, duration time.Duration) *SlideTransition[T] { + return NewSlideTransition[T](direction, DurationToFactor(float64(ebiten.TPS()), duration)) } -type TimedSlideTransition[T any, M SceneController[T]] struct { - SlideTransition[T, M] +type TimedSlideTransition[T any] struct { + SlideTransition[T] initialTime time.Time duration time.Duration } -func NewDurationTimedSlideTransition[T any, M SceneController[T]](direction SlideDirection, duration time.Duration) *TimedSlideTransition[T, M] { - return &TimedSlideTransition[T, M]{ +func NewDurationTimedSlideTransition[T any](direction SlideDirection, duration time.Duration) *TimedSlideTransition[T] { + return &TimedSlideTransition[T]{ duration: duration, - SlideTransition: *NewSlideTransition[T, M](direction, 0.), + SlideTransition: *NewSlideTransition[T](direction, 0.), } } -func (t *TimedSlideTransition[T, M]) Start(fromScene, toScene Scene[T, M], sm *SceneManager[T]) { +func (t *TimedSlideTransition[T]) Start(fromScene, toScene Scene[T], sm SceneController[T]) { t.SlideTransition.Start(fromScene, toScene, sm) t.initialTime = Clock.Now() } -func (t *TimedSlideTransition[T, M]) Update() error { +func (t *TimedSlideTransition[T]) Update() error { if !t.frameUpdated { // Update the offset value based on the current state of the transition if t.offset >= 1.0 { From 3a0d8471bbf24863a67d1235c6cd75633d572d2c Mon Sep 17 00:00:00 2001 From: Joel Schutz Date: Sat, 9 Mar 2024 18:38:47 -0300 Subject: [PATCH 16/19] Updates tests --- director_test.go | 28 +++++++++++++++++++++++++--- helpers_test.go | 2 +- scene_test.go | 26 +++++++++++++------------- transition_test.go | 32 ++++++++++++++++---------------- 4 files changed, 55 insertions(+), 33 deletions(-) diff --git a/director_test.go b/director_test.go index 51c55de..dcf6d8d 100644 --- a/director_test.go +++ b/director_test.go @@ -10,14 +10,14 @@ type DirectidScene struct { MockScene } -func (m *DirectidScene) Load(state int, sm *SceneDirector[int]) { +func (m *DirectidScene) Load(state int, sm SceneController[int]) { m.loadCalled = true m.unloadReturns = state } func TestSceneDirector_NewSceneDirector(t *testing.T) { mockScene := &DirectidScene{} - ruleSet := make(map[Scene[int, *SceneDirector[int]]][]Directive[int]) + ruleSet := make(map[Scene[int]][]Directive[int]) director := NewSceneDirector[int](mockScene, 1, ruleSet) @@ -28,7 +28,7 @@ func TestSceneDirector_NewSceneDirector(t *testing.T) { func TestSceneDirector_ProcessTrigger(t *testing.T) { mockScene := &DirectidScene{} mockScene2 := &DirectidScene{} - ruleSet := make(map[Scene[int, *SceneDirector[int]]][]Directive[int]) + ruleSet := make(map[Scene[int]][]Directive[int]) director := NewSceneDirector[int](mockScene, 1, ruleSet) @@ -43,3 +43,25 @@ func TestSceneDirector_ProcessTrigger(t *testing.T) { director.ProcessTrigger(2) assert.Equal(t, rule.Dest, director.current) } + +func TestSceneDirector_ProcessTriggerWiithTransition(t *testing.T) { + mockScene := &DirectidScene{} + mockTransition := &baseTransitionImplementation{} + ruleSet := make(map[Scene[int]][]Directive[int]) + + director := NewSceneDirector[int](mockScene, 1, ruleSet) + + rule := Directive[int]{Dest: &DirectidScene{}, Trigger: 2, Transition: mockTransition} + ruleSet[mockScene] = []Directive[int]{rule} + + // Call the ProcessTrigger method with wrong trigger + director.ProcessTrigger(1) + assert.NotEqual(t, rule.Transition, director.current) + + // Call the ProcessTrigger method with correct trigger + director.ProcessTrigger(2) + assert.Equal(t, rule.Transition, director.current) + + rule.Transition.End() + assert.Equal(t, rule.Dest, director.current) +} diff --git a/helpers_test.go b/helpers_test.go index 5f5a2d9..ba2cbbe 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -36,7 +36,7 @@ func TestPreDraw(t *testing.T) { from := &MockScene{} to := &MockScene{} - toImg, fromImg := PreDraw[int, *SceneManager[int]](image.Rect(0, 0, 10, 10), from, to) + toImg, fromImg := PreDraw[int](image.Rect(0, 0, 10, 10), from, to) assert.True(t, from.drawCalled) assert.True(t, to.drawCalled) diff --git a/scene_test.go b/scene_test.go index 7d8927e..4f95abc 100644 --- a/scene_test.go +++ b/scene_test.go @@ -16,7 +16,7 @@ type MockScene struct { unloadReturns int } -func (m *MockScene) Load(state int, sm *SceneManager[int]) { +func (m *MockScene) Load(state int, sm SceneController[int]) { m.loadCalled = true m.unloadReturns = state } @@ -40,29 +40,29 @@ func (m *MockScene) Layout(w, h int) (int, int) { return w, h } -type MockTransition[T any, M SceneController[T]] struct { - fromScene Scene[T, M] - toScene Scene[T, M] +type MockTransition[T any] struct { + fromScene Scene[T] + toScene Scene[T] startCalled bool } -func NewMockTransition[T any, M SceneController[T]]() *MockTransition[T, M] { - return &MockTransition[T, M]{} +func NewMockTransition[T any]() *MockTransition[T] { + return &MockTransition[T]{} } -func (t *MockTransition[T, M]) Start(fromScene, toScene Scene[T, M], sm *SceneManager[T]) { +func (t *MockTransition[T]) Start(fromScene, toScene Scene[T], sm SceneController[T]) { t.fromScene = fromScene t.toScene = toScene t.startCalled = true } -func (t *MockTransition[T, M]) End() {} +func (t *MockTransition[T]) End() {} -func (t *MockTransition[T, M]) Update() error { return nil } +func (t *MockTransition[T]) Update() error { return nil } -func (t *MockTransition[T, M]) Draw(screen *ebiten.Image) {} +func (t *MockTransition[T]) Draw(screen *ebiten.Image) {} -func (t *MockTransition[T, M]) Layout(w, h int) (int, int) { return w, h } +func (t *MockTransition[T]) Layout(w, h int) (int, int) { return w, h } func TestSceneManager_SwitchTo(t *testing.T) { sm := NewSceneManager[int](&MockScene{}, 0) @@ -75,7 +75,7 @@ func TestSceneManager_SwitchTo(t *testing.T) { func TestSceneManager_SwitchWithTransition(t *testing.T) { sm := NewSceneManager[int](&MockScene{}, 0) mockScene := &MockScene{} - mockTransition := &MockTransition[int, *SceneManager[int]]{} + mockTransition := &MockTransition[int]{} sm.SwitchWithTransition(mockScene, mockTransition) assert.True(t, mockTransition.startCalled) assert.True(t, sm.current == mockTransition) @@ -117,5 +117,5 @@ func TestSceneManager_Load_Unload(t *testing.T) { assert.True(t, to.loadCalled) assert.True(t, from.unloadCalled) - assert.Equal(t, 42, sm.current.(Scene[int, *SceneManager[int]]).Unload()) + assert.Equal(t, 42, sm.current.(Scene[int]).Unload()) } diff --git a/transition_test.go b/transition_test.go index 3624f1f..3c32e71 100644 --- a/transition_test.go +++ b/transition_test.go @@ -19,7 +19,7 @@ func (m *MockClock) Since(t time.Time) time.Duration { return m.currentTime.Sub( func (m *MockClock) Until(t time.Time) time.Duration { return t.Sub(m.currentTime) } type baseTransitionImplementation struct { - BaseTransition[int, *SceneManager[int]] + BaseTransition[int] } func (b *baseTransitionImplementation) Draw(screen *ebiten.Image) {} @@ -30,12 +30,12 @@ type MockTransitionAwareScene struct { postTransitionCalled bool } -func (m *MockTransitionAwareScene) PreTransition(fromScene Scene[int, *SceneManager[int]]) int { +func (m *MockTransitionAwareScene) PreTransition(fromScene Scene[int]) int { m.preTransitionCalled = true return 0 } -func (m *MockTransitionAwareScene) PostTransition(state int, toScene Scene[int, *SceneManager[int]]) { +func (m *MockTransitionAwareScene) PostTransition(state int, toScene Scene[int]) { m.postTransitionCalled = true } @@ -73,7 +73,7 @@ func TestBaseTransition_End(t *testing.T) { sm.SwitchWithTransition(to, trans) trans.End() - fmt.Println(sm.current.(Scene[int, *SceneManager[int]]), to) + fmt.Println(sm.current.(Scene[int]), to) assert.Equal(t, to, sm.current) } @@ -108,7 +108,7 @@ func TestFadeTransition_UpdateOncePerFrame(t *testing.T) { var value float32 = .6 from := &MockScene{} to := &MockScene{} - trans := NewFadeTransition[int, *SceneManager[int]](value) + trans := NewFadeTransition[int](value) trans.Start(from, to, nil) err := trans.Update() @@ -124,7 +124,7 @@ func TestFadeTransition_UpdateOncePerFrame(t *testing.T) { func TestFadeTransition_Update(t *testing.T) { from := &MockScene{} to := &MockScene{} - trans := NewFadeTransition[int, *SceneManager[int]](.5) + trans := NewFadeTransition[int](.5) sm := NewSceneManager[int](from, 0) sm.SwitchWithTransition(to, trans) @@ -159,7 +159,7 @@ func TestFadeTransition_Update(t *testing.T) { func TestFadeTransition_Start(t *testing.T) { from := &MockScene{} to := &MockScene{} - trans := NewFadeTransition[int, *SceneManager[int]](.5) + trans := NewFadeTransition[int](.5) trans.Start(from, to, nil) assert.Equal(t, from, trans.fromScene) @@ -171,7 +171,7 @@ func TestFadeTransition_Start(t *testing.T) { func TestFadeTransition_Draw(t *testing.T) { from := &MockScene{} to := &MockScene{} - trans := NewFadeTransition[int, *SceneManager[int]](.5) + trans := NewFadeTransition[int](.5) trans.Start(from, to, nil) trans.Update() @@ -187,7 +187,7 @@ func TestSlideTransition_UpdateOncePerFrame(t *testing.T) { var value float64 = .6 from := &MockScene{} to := &MockScene{} - trans := NewSlideTransition[int, *SceneManager[int]](RightToLeft, value) + trans := NewSlideTransition[int](RightToLeft, value) trans.Start(from, to, nil) err := trans.Update() @@ -208,7 +208,7 @@ func TestSlideTransition_Update(t *testing.T) { } for _, direction := range variations { - trans := NewSlideTransition[int, *SceneManager[int]](direction, .5) + trans := NewSlideTransition[int](direction, .5) sm := NewSceneManager[int](from, 0) sm.SwitchWithTransition(to, trans) @@ -234,7 +234,7 @@ func TestSlideTransition_Update(t *testing.T) { func TestSlideTransition_Start(t *testing.T) { from := &MockScene{} to := &MockScene{} - trans := NewSlideTransition[int, *SceneManager[int]](TopToBottom, .5) + trans := NewSlideTransition[int](TopToBottom, .5) trans.Start(from, to, nil) assert.Equal(t, from, trans.fromScene) @@ -251,7 +251,7 @@ func TestSlideTransition_Draw(t *testing.T) { } for _, direction := range variations { - trans := NewSlideTransition[int, *SceneManager[int]](direction, .5) + trans := NewSlideTransition[int](direction, .5) trans.Start(from, to, nil) trans.Update() @@ -265,7 +265,7 @@ func TestTimedFadeTransition_Update(t *testing.T) { Clock = &MockClock{currentTime: now} from := &MockScene{} to := &MockScene{} - trans := NewDurationTimedFadeTransition[int, *SceneManager[int]](time.Second) + trans := NewDurationTimedFadeTransition[int](time.Second) sm := NewSceneManager[int](from, 0) sm.SwitchWithTransition(to, trans) @@ -314,7 +314,7 @@ func TestTimedFadeTransition_Start(t *testing.T) { Clock = &MockClock{currentTime: now} from := &MockScene{} to := &MockScene{} - trans := NewDurationTimedFadeTransition[int, *SceneManager[int]](time.Second) + trans := NewDurationTimedFadeTransition[int](time.Second) trans.Start(from, to, nil) assert.Equal(t, from, trans.fromScene) @@ -332,7 +332,7 @@ func TestTimedSlideTransition_Update(t *testing.T) { for _, direction := range variations { Clock = &MockClock{currentTime: time.Now()} - trans := NewDurationTimedSlideTransition[int, *SceneManager[int]](direction, time.Second) + trans := NewDurationTimedSlideTransition[int](direction, time.Second) sm := NewSceneManager[int](from, 0) sm.SwitchWithTransition(to, trans) @@ -370,7 +370,7 @@ func TestTimedSlideTransition_Start(t *testing.T) { Clock = &MockClock{currentTime: now} from := &MockScene{} to := &MockScene{} - trans := NewDurationTimedSlideTransition[int, *SceneManager[int]](TopToBottom, time.Second) + trans := NewDurationTimedSlideTransition[int](TopToBottom, time.Second) trans.Start(from, to, nil) assert.Equal(t, from, trans.fromScene) From 7eb5b21858126a938826a18edda657828b9acb0f Mon Sep 17 00:00:00 2001 From: Joel Schutz Date: Sat, 9 Mar 2024 18:38:59 -0300 Subject: [PATCH 17/19] Updates examples --- examples/aware/main.go | 12 ++++++------ examples/director/main.go | 13 +++++++++---- examples/simple/main.go | 8 ++++---- examples/timed/main.go | 8 ++++---- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/examples/aware/main.go b/examples/aware/main.go index 1124f38..01021cf 100644 --- a/examples/aware/main.go +++ b/examples/aware/main.go @@ -33,21 +33,21 @@ func (s *BaseScene) Layout(w, h int) (int, int) { return w, h } -func (s *BaseScene) Load(st State, sm *stagehand.SceneManager[State]) { +func (s *BaseScene) Load(st State, sm stagehand.SceneController[State]) { s.count = st - s.sm = sm + s.sm = sm.(*stagehand.SceneManager[State]) } func (s *BaseScene) Unload() State { return s.count } -func (s *BaseScene) PreTransition(toScene stagehand.Scene[State, *stagehand.SceneManager[State]]) State { +func (s *BaseScene) PreTransition(toScene stagehand.Scene[State]) State { s.count.OnTransition = true return s.count } -func (s *BaseScene) PostTransition(state State, fromScene stagehand.Scene[State, *stagehand.SceneManager[State]]) { +func (s *BaseScene) PostTransition(state State, fromScene stagehand.Scene[State]) { s.count.OnTransition = false } @@ -60,7 +60,7 @@ func (s *FirstScene) Update() error { s.count.Count++ } if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { - s.sm.SwitchWithTransition(&SecondScene{}, stagehand.NewSlideTransition[State, *stagehand.SceneManager[State]](stagehand.TopToBottom, .05)) + s.sm.SwitchWithTransition(&SecondScene{}, stagehand.NewSlideTransition[State](stagehand.TopToBottom, .05)) } return nil } @@ -83,7 +83,7 @@ func (s *SecondScene) Update() error { s.count.Count-- } if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { - s.sm.SwitchWithTransition(&FirstScene{}, stagehand.NewSlideTransition[State, *stagehand.SceneManager[State]](stagehand.BottomToTop, .05)) + s.sm.SwitchWithTransition(&FirstScene{}, stagehand.NewSlideTransition[State](stagehand.BottomToTop, .05)) } return nil } diff --git a/examples/director/main.go b/examples/director/main.go index 5ac0ac8..704ed7c 100644 --- a/examples/director/main.go +++ b/examples/director/main.go @@ -34,9 +34,9 @@ func (s *BaseScene) Layout(w, h int) (int, int) { return w, h } -func (s *BaseScene) Load(st State, sm *stagehand.SceneDirector[State]) { +func (s *BaseScene) Load(st State, sm stagehand.SceneController[State]) { s.count = st - s.sm = sm + s.sm = sm.(*stagehand.SceneDirector[State]) } func (s *BaseScene) Unload() State { @@ -90,12 +90,17 @@ func main() { s1 := &FirstScene{} s2 := &SecondScene{} - rs := map[stagehand.Scene[State, *stagehand.SceneDirector[State]]][]stagehand.Directive[State]{ + trans := stagehand.NewSlideTransition[State](stagehand.BottomToTop, 0.05) + rs := map[stagehand.Scene[State]][]stagehand.Directive[State]{ s1: []stagehand.Directive[State]{ stagehand.Directive[State]{Dest: s2, Trigger: Trigger}, }, s2: []stagehand.Directive[State]{ - stagehand.Directive[State]{Dest: s1, Trigger: Trigger}, + stagehand.Directive[State]{ + Dest: s1, + Trigger: Trigger, + Transition: trans, + }, }, } sm := stagehand.NewSceneDirector[State](s1, state, rs) diff --git a/examples/simple/main.go b/examples/simple/main.go index 7f3baf5..3ba9645 100644 --- a/examples/simple/main.go +++ b/examples/simple/main.go @@ -30,9 +30,9 @@ func (s *BaseScene) Layout(w, h int) (int, int) { return w, h } -func (s *BaseScene) Load(st State, sm *stagehand.SceneManager[State]) { +func (s *BaseScene) Load(st State, sm stagehand.SceneController[State]) { s.count = st - s.sm = sm + s.sm = sm.(*stagehand.SceneManager[State]) } func (s *BaseScene) Unload() State { @@ -67,7 +67,7 @@ func (s *SecondScene) Update() error { s.count-- } if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { - s.sm.SwitchWithTransition(&ThirdScene{}, stagehand.NewFadeTransition[State, *stagehand.SceneManager[State]](.05)) + s.sm.SwitchWithTransition(&ThirdScene{}, stagehand.NewFadeTransition[State](.05)) } return nil } @@ -86,7 +86,7 @@ func (s *ThirdScene) Update() error { s.count *= 2 } if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { - s.sm.SwitchWithTransition(&FirstScene{}, stagehand.NewSlideTransition[State, *stagehand.SceneManager[State]](stagehand.RightToLeft, .05)) + s.sm.SwitchWithTransition(&FirstScene{}, stagehand.NewSlideTransition[State](stagehand.RightToLeft, .05)) } return nil } diff --git a/examples/timed/main.go b/examples/timed/main.go index b43610f..41919fc 100644 --- a/examples/timed/main.go +++ b/examples/timed/main.go @@ -31,9 +31,9 @@ func (s *BaseScene) Layout(w, h int) (int, int) { return w, h } -func (s *BaseScene) Load(st State, sm *stagehand.SceneManager[State]) { +func (s *BaseScene) Load(st State, sm stagehand.SceneController[State]) { s.count = st - s.sm = sm + s.sm = sm.(*stagehand.SceneManager[State]) } func (s *BaseScene) Unload() State { @@ -49,7 +49,7 @@ func (s *FirstScene) Update() error { s.count++ } if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { - s.sm.SwitchWithTransition(&SecondScene{}, stagehand.NewTicksTimedSlideTransition[State, *stagehand.SceneManager[State]](stagehand.LeftToRight, time.Second*time.Duration(s.count))) + s.sm.SwitchWithTransition(&SecondScene{}, stagehand.NewTicksTimedSlideTransition[State](stagehand.LeftToRight, time.Second*time.Duration(s.count))) } return nil } @@ -68,7 +68,7 @@ func (s *SecondScene) Update() error { s.count-- } if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { - s.sm.SwitchWithTransition(&FirstScene{}, stagehand.NewDurationTimedSlideTransition[State, *stagehand.SceneManager[State]](stagehand.RightToLeft, time.Second*time.Duration(s.count))) + s.sm.SwitchWithTransition(&FirstScene{}, stagehand.NewDurationTimedSlideTransition[State](stagehand.RightToLeft, time.Second*time.Duration(s.count))) } return nil } From 5b108e9fbab4f7eb8539282d09e606de807a2a14 Mon Sep 17 00:00:00 2001 From: Joel Schutz Date: Sat, 9 Mar 2024 22:59:18 -0300 Subject: [PATCH 18/19] Updates and clean `director` tests --- director_test.go | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/director_test.go b/director_test.go index dcf6d8d..f91d3bb 100644 --- a/director_test.go +++ b/director_test.go @@ -6,17 +6,8 @@ import ( "github.com/stretchr/testify/assert" ) -type DirectidScene struct { - MockScene -} - -func (m *DirectidScene) Load(state int, sm SceneController[int]) { - m.loadCalled = true - m.unloadReturns = state -} - func TestSceneDirector_NewSceneDirector(t *testing.T) { - mockScene := &DirectidScene{} + mockScene := &MockScene{} ruleSet := make(map[Scene[int]][]Directive[int]) director := NewSceneDirector[int](mockScene, 1, ruleSet) @@ -26,8 +17,8 @@ func TestSceneDirector_NewSceneDirector(t *testing.T) { } func TestSceneDirector_ProcessTrigger(t *testing.T) { - mockScene := &DirectidScene{} - mockScene2 := &DirectidScene{} + mockScene := &MockScene{} + mockScene2 := &MockScene{} ruleSet := make(map[Scene[int]][]Directive[int]) director := NewSceneDirector[int](mockScene, 1, ruleSet) @@ -44,14 +35,36 @@ func TestSceneDirector_ProcessTrigger(t *testing.T) { assert.Equal(t, rule.Dest, director.current) } -func TestSceneDirector_ProcessTriggerWiithTransition(t *testing.T) { - mockScene := &DirectidScene{} +func TestSceneDirector_ProcessTriggerWithTransition(t *testing.T) { + mockScene := &MockScene{} + mockTransition := &baseTransitionImplementation{} + ruleSet := make(map[Scene[int]][]Directive[int]) + + director := NewSceneDirector[int](mockScene, 1, ruleSet) + + rule := Directive[int]{Dest: &MockScene{}, Trigger: 2, Transition: mockTransition} + ruleSet[mockScene] = []Directive[int]{rule} + + // Call the ProcessTrigger method with wrong trigger + director.ProcessTrigger(1) + assert.NotEqual(t, rule.Transition, director.current) + + // Call the ProcessTrigger method with correct trigger + director.ProcessTrigger(2) + assert.Equal(t, rule.Transition, director.current) + + rule.Transition.End() + assert.Equal(t, rule.Dest, director.current) +} + +func TestSceneDirector_ProcessTriggerWithTransitionAwareness(t *testing.T) { + mockScene := &MockTransitionAwareScene{} mockTransition := &baseTransitionImplementation{} ruleSet := make(map[Scene[int]][]Directive[int]) director := NewSceneDirector[int](mockScene, 1, ruleSet) - rule := Directive[int]{Dest: &DirectidScene{}, Trigger: 2, Transition: mockTransition} + rule := Directive[int]{Dest: &MockTransitionAwareScene{}, Trigger: 2, Transition: mockTransition} ruleSet[mockScene] = []Directive[int]{rule} // Call the ProcessTrigger method with wrong trigger From 299fe79cc5986a9911a537e942d42f283e4a0ee9 Mon Sep 17 00:00:00 2001 From: Joel Schutz Date: Sun, 10 Mar 2024 08:59:57 -0300 Subject: [PATCH 19/19] Updates documentation --- README.md | 68 +++++++++++++++++++++++++++++++++++++-- examples/director/main.go | 4 +-- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d57952a..1479f39 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ type MyState struct { type MyScene struct { // your scene fields + sm *stagehand.SceneManager[MyState] } func (s *MyScene) Update() error { @@ -44,8 +45,9 @@ func (s *MyScene) Draw(screen *ebiten.Image) { // your draw code } -func (s *MyScene) Load(state MyState ,manager *stagehand.SceneManager) { +func (s *MyScene) Load(state MyState ,manager stagehand.SceneController[MyState]) { // your load code + s.sm = manager.(*stagehand.SceneManager[MyState]) // This type assertion is important } func (s *MyScene) Unload() MyState { @@ -75,6 +77,7 @@ We provide some example code so you can start fast: - [Simple Example](https://github.com/joelschutz/stagehand/blob/master/examples/simple/main.go) - [Timed Transition Example](https://github.com/joelschutz/stagehand/blob/master/examples/timed/main.go) - [Transition Awareness Example](https://github.com/joelschutz/stagehand/blob/master/examples/aware/main.go) +- [Scene Director Example](https://github.com/joelschutz/stagehand/blob/master/examples/director/main.go) ## Transitions @@ -100,7 +103,7 @@ The `FadeTransition` will fade out the current scene while fading in the new sce func (s *MyScene) Update() error { // ... scene2 := &OtherScene{} - s.manager.SwitchWithTransition(scene2. stagehand.NewFadeTransition(.05)) + s.manager.SwitchWithTransition(scene2, stagehand.NewFadeTransition(.05)) // ... } @@ -116,7 +119,7 @@ The `SlideTransition` will slide out the current scene and slide in the new scen func (s *MyScene) Update() error { // ... scene2 := &OtherScene{} - s.manager.SwitchWithTransition(scene2. stagehand.NewSlideTransition(stagehand.LeftToRight, .05)) + s.manager.SwitchWithTransition(scene2, stagehand.NewSlideTransition(stagehand.LeftToRight, .05)) // ... } @@ -183,6 +186,65 @@ Unload Called on old scene PostTransition Called on new scene ``` +## SceneDirector + +The `SceneDirector` is an alternative way to manage the transitions between scenes. It provides transitioning between scenes based on a set of rules just like a FSM. The `Scene` implementation is the same, with only a feel differences, first you need to assert the `SceneDirector` instead of the `SceneManager`: + +```go +type MyScene struct { + // your scene fields + director *stagehand.SceneDirector[MyState] +} + +func (s *MyScene) Load(state MyState ,director stagehand.SceneController[MyState]) { + // your load code + s.director = director.(*stagehand.SceneDirector[MyState]) // This type assertion is important +} +``` + +Then define a ruleSet of `Directive` and `SceneTransitionTrigger` for the game. + +```go +// Triggers are int type underneath +const ( + Trigger1 stagehand.SceneTransitionTrigger = iota + Trigger2 +) + +func main() { + // ... + scene1 := &MyScene{} + scene2 := &OtherScene{} + + // Create a rule set for transitioning between scenes based on Triggers + ruleSet := make(map[stagehand.Scene[MyState]][]Directive[MyState]) + directive1 := Directive[MyState]{Dest: scene2, Trigger: Trigger1} + directive2 := Directive[MyState]{Dest: scene1, Trigger: Trigger2, Transition: stagehand.NewFadeTransition(.05)} // Add transitions inside the directive + + // Directives are mapped to each Scene pointer and can be shared + ruleSet[scene1] = []Directive[MyState]{directive1, directive2} + ruleSet[scene2] = []Directive[MyState]{directive2} + + state := MyState{} + manager := stagehand.NewSceneDirector[MyState](scene1, state, ruleSet) + + if err := ebiten.RunGame(sm); err != nil { + log.Fatal(err) + } +} +``` + +Now you can now notify the `SceneDirector` about activated `SceneTransitionTrigger`, if no `Directive` match, the code will still run without errors. + +```go +func (s *MyScene) Update() error { + // ... + s.manager.ProcessTrigger(Trigger) + + // ... +} +``` + ## Contribution Contributions are welcome! If you find a bug or have a feature request, please open an issue on GitHub. If you would like to contribute code, please fork the repository and submit a pull request. diff --git a/examples/director/main.go b/examples/director/main.go index 704ed7c..294086d 100644 --- a/examples/director/main.go +++ b/examples/director/main.go @@ -19,8 +19,8 @@ const ( type State int -var ( - Trigger stagehand.SceneTransitionTrigger = 1 +const ( + Trigger stagehand.SceneTransitionTrigger = iota ) type BaseScene struct {