stagehand

package module
v1.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 2, 2024 License: MIT Imports: 3 Imported by: 4

README

Stagehand

Codecov issues license

Stagehand is a scene manager library for Ebitengine that makes it easy to manage game scenes and state. With Stagehand, you can quickly create and transition between scenes, allowing you to focus on the content and gameplay of your game.

Installation

To use Stagehand, simply import it into your project:

import "github.com/joelschutz/stagehand"

Features

  • Lightweight and easy-to-use scene management for Ebitengine
  • Simple and flexible API for creating and switching between scenes
  • Managed type-safe states with the power of generics
  • Built-in support for transition effects between scenes
  • Supports custom transition effects by implementing the SceneTransition interface

Usage

To use Stagehand, you first need to create a struct that implements the Scene interface:

type MyState struct {
    // your state data
}

type MyScene struct {
    // your scene fields
    sm *stagehand.SceneManager[MyState]
}

func (s *MyScene) Update() error {
    // your update code
}

func (s *MyScene) Draw(screen *ebiten.Image) {
    // your draw code
}

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 {
    // your unload code
}

Then, create an instance of the SceneManager passing initial scene and state.

func main() {
    // ...
    scene1 := &MyScene{}
    state := MyState{}
    manager := stagehand.NewSceneManager[MyState](scene1, state)

    if err := ebiten.RunGame(sm); err != nil {
        log.Fatal(err)
    }
}
Examples

We provide some example code so you can start fast:

Transitions

You can switch scenes by calling SwitchTo method on the SceneManager giving the scene instance you wanna switch to.

func (s *MyScene) Update() error {
    // ...
    scene2 := &OtherScene{}
    s.manager.SwitchTo(scene2)

    // ...
}

You can use the SwitchWithTransition method to switch between scenes with a transition effect. Stagehand provides two built-in transition effects: FadeTransition and SlideTransition.

Fade Transition

The FadeTransition will fade out the current scene while fading in the new scene.

func (s *MyScene) Update() error {
    // ...
    scene2 := &OtherScene{}
    s.manager.SwitchWithTransition(scene2, stagehand.NewFadeTransition(.05))

    // ...
}

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

The SlideTransition will slide out the current scene and slide in the new scene.

func (s *MyScene) Update() error {
    // ...
    scene2 := &OtherScene{}
    s.manager.SwitchWithTransition(scene2, stagehand.NewSlideTransition(stagehand.LeftToRight, .05))

    // ...
}

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

You can also define your own transition, simply implement the SceneTransition interface, we provide a helper BaseTransition that you can use like this:

type MyTransition struct {
    stagehand.BaseTransition
    progress float64 // An example factor
}

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, sm)
    t.progress = 0
}

func (t *MyTransition) Update() error {
    // Update the progress of the transition
    t.progress += 0.01
    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)

    // Draw transition effect here
}

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 TransitionAwareScene interface.

func (s *MyScene) PreTransition(destination Scene[MyState]) MyState  {
    // Runs before new scene is loaded
}

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 can control your scenes at each point of the transition. The execution order will be:

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

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:

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.

// 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.

func (s *MyScene) Update() error {
    // ...
	s.manager.ProcessTrigger(Trigger)

    // ...
}

Acknowledgments

  • When switching scenes (i.e. calling SwitchTo, SwitchWithTransition or ProcessTrigger) while a transition is running it will immediately be canceled and the new switch will be started. To prevent this behavior use a TransitionAwareScene and prevent this methods to be called.

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.

Before submitting a pull request, please make sure to run the tests:

go test ./...

License

Stagehand is released under the MIT License.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CalculateProgress added in v1.1.0

func CalculateProgress(initialTime time.Time, duration time.Duration) float64

Calculates the fraction of the duration that has passed since the initial time

func DurationToFactor added in v1.1.0

func DurationToFactor(frequency float64, duration time.Duration) float64

Converts a frequency(cycle/second) to a factor(change/cycle) for a given duration

func MaxInt

func MaxInt(a, b int) int

MaxInt returns the maximum of two integers

func PreDraw

func PreDraw[T any](bounds image.Rectangle, fromScene, toScene Scene[T]) (*ebiten.Image, *ebiten.Image)

Pre-draw scenes

Types

type BaseTransition

type BaseTransition[T any] struct {
	// contains filtered or unexported fields
}

func (*BaseTransition[T]) End

func (t *BaseTransition[T]) End()

Ends transition to the next scene

func (*BaseTransition[T]) Layout

func (t *BaseTransition[T]) Layout(outsideWidth, outsideHeight int) (int, int)

Layout updates the layout of the scenes

func (*BaseTransition[T]) Start

func (t *BaseTransition[T]) Start(fromScene, toScene Scene[T], sm SceneController[T])

func (*BaseTransition[T]) Update

func (t *BaseTransition[T]) Update() error

Update updates the transition state

type ClockInterface added in v1.1.0

type ClockInterface interface {
	Since(time.Time) time.Duration
	Now() time.Time
	Until(time.Time) time.Duration
	Sleep(time.Duration)
}

Clock helpers for mocking

var Clock ClockInterface = RealClock{}

type Directive added in v1.1.0

type Directive[T any] struct {
	Dest       Scene[T]
	Transition SceneTransition[T]
	Trigger    SceneTransitionTrigger
}

A Directive is a struct that represents how a scene should be transitioned

type FadeTransition

type FadeTransition[T any] struct {
	BaseTransition[T]
	// contains filtered or unexported fields
}

func NewFadeTransition

func NewFadeTransition[T any](factor float32) *FadeTransition[T]

func NewTicksTimedFadeTransition added in v1.1.0

func NewTicksTimedFadeTransition[T any](duration time.Duration) *FadeTransition[T]

func (*FadeTransition[T]) Draw

func (t *FadeTransition[T]) Draw(screen *ebiten.Image)

Draw draws the transition effect

func (*FadeTransition[T]) Start

func (t *FadeTransition[T]) Start(fromScene, toScene Scene[T], sm SceneController[T])

Start starts the transition from the given "from" scene to the given "to" scene

func (*FadeTransition[T]) Update

func (t *FadeTransition[T]) Update() error

Update updates the transition state

type ProtoScene added in v1.1.0

type ProtoScene[T any] interface {
	ebiten.Game
}

type RealClock added in v1.1.0

type RealClock struct{}

func (RealClock) Now added in v1.1.0

func (RealClock) Now() time.Time

func (RealClock) Since added in v1.1.0

func (RealClock) Since(t time.Time) time.Duration

func (RealClock) Sleep added in v1.1.0

func (RealClock) Sleep(d time.Duration)

func (RealClock) Until added in v1.1.0

func (RealClock) Until(t time.Time) time.Duration

type Scene

type Scene[T any] interface {
	ProtoScene[T]
	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 SceneController added in v1.1.0

type SceneController[T any] interface {
	// *SceneManager[T] | *SceneDirector[T]
	ReturnFromTransition(scene, orgin Scene[T])
}

type SceneDirector added in v1.1.0

type SceneDirector[T any] struct {
	SceneManager[T]
	RuleSet map[Scene[T]][]Directive[T]
}

A SceneDirector is a struct that manages the transitions between scenes

func NewSceneDirector added in v1.1.0

func NewSceneDirector[T any](scene Scene[T], state T, RuleSet map[Scene[T]][]Directive[T]) *SceneDirector[T]

func (*SceneDirector[T]) ProcessTrigger added in v1.1.0

func (d *SceneDirector[T]) ProcessTrigger(trigger SceneTransitionTrigger)

ProcessTrigger finds if a transition should be triggered

func (*SceneDirector[T]) ReturnFromTransition added in v1.1.0

func (d *SceneDirector[T]) ReturnFromTransition(scene, origin Scene[T])

type SceneManager

type SceneManager[T any] struct {
	// contains filtered or unexported fields
}

func NewSceneManager

func NewSceneManager[T any](scene Scene[T], state T) *SceneManager[T]

func (*SceneManager[T]) Draw

func (s *SceneManager[T]) Draw(screen *ebiten.Image)

func (*SceneManager[T]) Layout

func (s *SceneManager[T]) Layout(w, h int) (int, int)

func (*SceneManager[T]) ReturnFromTransition added in v1.1.0

func (s *SceneManager[T]) ReturnFromTransition(scene, origin Scene[T])

func (*SceneManager[T]) SwitchTo

func (s *SceneManager[T]) SwitchTo(scene Scene[T])

Scene Switching

func (*SceneManager[T]) SwitchWithTransition

func (s *SceneManager[T]) SwitchWithTransition(scene Scene[T], transition SceneTransition[T])

func (*SceneManager[T]) Update

func (s *SceneManager[T]) Update() error

Ebiten Interface

type SceneTransition

type SceneTransition[T any] interface {
	ProtoScene[T]
	Start(fromScene, toScene Scene[T], sm SceneController[T])
	End()
}

type SceneTransitionTrigger added in v1.1.0

type SceneTransitionTrigger int

type SlideDirection

type SlideDirection int
const (
	LeftToRight SlideDirection = iota
	RightToLeft
	TopToBottom
	BottomToTop
)

type SlideTransition

type SlideTransition[T any] struct {
	BaseTransition[T]
	// contains filtered or unexported fields
}

func NewSlideTransition

func NewSlideTransition[T any](direction SlideDirection, factor float64) *SlideTransition[T]

func NewTicksTimedSlideTransition added in v1.1.0

func NewTicksTimedSlideTransition[T any](direction SlideDirection, duration time.Duration) *SlideTransition[T]

func (*SlideTransition[T]) Draw

func (t *SlideTransition[T]) Draw(screen *ebiten.Image)

Draw draws the transition effect

func (*SlideTransition[T]) Start

func (t *SlideTransition[T]) Start(fromScene Scene[T], toScene Scene[T], sm SceneController[T])

Start starts the transition from the given "from" scene to the given "to" scene

func (*SlideTransition[T]) Update

func (t *SlideTransition[T]) Update() error

Update updates the transition state

type TimedFadeTransition added in v1.1.0

type TimedFadeTransition[T any] struct {
	FadeTransition[T]
	// contains filtered or unexported fields
}

func NewDurationTimedFadeTransition added in v1.1.0

func NewDurationTimedFadeTransition[T any](duration time.Duration) *TimedFadeTransition[T]

func (*TimedFadeTransition[T]) Start added in v1.1.0

func (t *TimedFadeTransition[T]) Start(fromScene, toScene Scene[T], sm SceneController[T])

func (*TimedFadeTransition[T]) Update added in v1.1.0

func (t *TimedFadeTransition[T]) Update() error

type TimedSlideTransition added in v1.1.0

type TimedSlideTransition[T any] struct {
	SlideTransition[T]
	// contains filtered or unexported fields
}

func NewDurationTimedSlideTransition added in v1.1.0

func NewDurationTimedSlideTransition[T any](direction SlideDirection, duration time.Duration) *TimedSlideTransition[T]

func (*TimedSlideTransition[T]) Start added in v1.1.0

func (t *TimedSlideTransition[T]) Start(fromScene, toScene Scene[T], sm SceneController[T])

func (*TimedSlideTransition[T]) Update added in v1.1.0

func (t *TimedSlideTransition[T]) Update() error

type TransitionAwareScene added in v1.1.0

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
}

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL