...

Source file src/gitlab.com/tslocum/venture/pkg/engine/game.go

Documentation: gitlab.com/tslocum/venture/pkg/engine

     1  package engine
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sync"
     7  
     8  	"github.com/hajimehoshi/ebiten"
     9  	"github.com/hajimehoshi/ebiten/ebitenutil"
    10  	"github.com/hajimehoshi/ebiten/inpututil"
    11  	"gitlab.com/tslocum/venture/pkg/world"
    12  )
    13  
    14  // ErrExit is the error returned when exiting normally.
    15  var ErrExit = errors.New("exit")
    16  
    17  // Game is a venture game.
    18  type Game struct {
    19  	UpdateFunc func(screen *ebiten.Image) error
    20  
    21  	rootNode world.Node
    22  	sync.Mutex
    23  }
    24  
    25  // NewGame returns a new venture game.
    26  func NewGame() *Game {
    27  	doNothing := func(screen *ebiten.Image) error {
    28  		return nil
    29  	}
    30  
    31  	g := &Game{UpdateFunc: doNothing}
    32  	return g
    33  }
    34  
    35  // SetRoot sets the root node of the game.
    36  func (g *Game) SetRoot(node world.Node) {
    37  	g.Lock()
    38  	defer g.Unlock()
    39  
    40  	g.rootNode = node
    41  }
    42  
    43  // Update updates the game.
    44  func (g *Game) Update(screen *ebiten.Image) error {
    45  	g.Lock()
    46  	defer g.Unlock()
    47  
    48  	if g.rootNode != nil {
    49  		g.fireMouseEvents()
    50  	}
    51  
    52  	err := g.UpdateFunc(screen)
    53  	if err != nil {
    54  		return err
    55  	}
    56  
    57  	if g.rootNode == nil {
    58  		return nil
    59  	}
    60  
    61  	err = g.updateNode(screen, g.rootNode)
    62  	if err != nil {
    63  		return err
    64  	}
    65  
    66  	return nil
    67  }
    68  
    69  func (g *Game) fireMouseEvents() {
    70  	x, y := ebiten.CursorPosition()
    71  	hitNodes := g.nodesAt(float64(x), float64(y), g.rootNode)
    72  	if hitNodes == nil {
    73  		return
    74  	}
    75  
    76  	leftDown := inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft)
    77  	middleDown := inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonMiddle)
    78  	rightDown := inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight)
    79  
    80  	leftUp := inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonLeft)
    81  	middleUp := inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonMiddle)
    82  	rightUp := inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonRight)
    83  
    84  	for i := len(hitNodes) - 1; i >= 0; i-- {
    85  		if leftDown || middleDown || rightDown {
    86  			newNode, ok := hitNodes[i].(world.HandlesMouseDown)
    87  			if ok && ((leftDown && !newNode.MouseDown(float64(x), float64(y), ebiten.MouseButtonLeft)) ||
    88  				(middleDown && !newNode.MouseDown(float64(x), float64(y), ebiten.MouseButtonMiddle)) ||
    89  				(rightDown && !newNode.MouseDown(float64(x), float64(y), ebiten.MouseButtonRight))) {
    90  				return
    91  			}
    92  		}
    93  
    94  		if leftUp || middleUp || rightUp {
    95  			newNode, ok := hitNodes[i].(world.HandlesMouseUp)
    96  			if ok && ((leftUp && !newNode.MouseUp(float64(x), float64(y), ebiten.MouseButtonLeft)) ||
    97  				(middleUp && !newNode.MouseUp(float64(x), float64(y), ebiten.MouseButtonMiddle)) ||
    98  				(rightUp && !newNode.MouseUp(float64(x), float64(y), ebiten.MouseButtonRight))) {
    99  				return
   100  			}
   101  		}
   102  
   103  		newNode, ok := hitNodes[i].(world.HandlesHover)
   104  		if ok && !newNode.Hover(float64(x), float64(y)) {
   105  			return
   106  		}
   107  	}
   108  }
   109  
   110  func (g *Game) updateNode(screen *ebiten.Image, node world.Node) error {
   111  	childNodes := node.Nodes()
   112  	for i := 0; i < len(childNodes); i++ {
   113  		err := g.updateNode(screen, childNodes[i])
   114  		if err != nil {
   115  			return err
   116  		}
   117  	}
   118  
   119  	err := node.Update(screen)
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	return nil
   125  }
   126  
   127  // Draw draws the game.
   128  func (g *Game) Draw(screen *ebiten.Image) {
   129  	g.Lock()
   130  	defer g.Unlock()
   131  
   132  	var nodes int
   133  	if g.rootNode != nil {
   134  		nodes = g.drawNode(screen, g.rootNode)
   135  	}
   136  
   137  	ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS    %0.0f\nTPS    %0.0f\nNodes  %d", ebiten.CurrentFPS(), ebiten.CurrentTPS(), nodes))
   138  }
   139  
   140  func (g *Game) drawNode(screen *ebiten.Image, node world.Node) int {
   141  	nodes := 1
   142  	node.Draw(screen)
   143  
   144  	childNodes := node.Nodes()
   145  	for i := 0; i < len(childNodes); i++ {
   146  		nodes += g.drawNode(screen, childNodes[i])
   147  	}
   148  
   149  	return nodes
   150  }
   151  
   152  func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
   153  	s := ebiten.DeviceScaleFactor()
   154  	return int(float64(outsideWidth) * s), int(float64(outsideHeight) * s)
   155  }
   156  
   157  func (g *Game) Run() error {
   158  	err := ebiten.RunGame(g)
   159  	if err != nil && err != ErrExit {
   160  		return err
   161  	}
   162  	return nil
   163  }
   164  
   165  func (g *Game) NodeAt(x float64, y float64) []world.Node {
   166  	g.Lock()
   167  	defer g.Unlock()
   168  
   169  	return g.nodesAt(x, y, g.rootNode)
   170  }
   171  
   172  func (g *Game) nodesAt(x float64, y float64, node world.Node) []world.Node {
   173  	var n []world.Node
   174  
   175  	if node.HitTest(x, y) {
   176  		n = append(n, node)
   177  	}
   178  
   179  	for _, child := range node.Nodes() {
   180  		n = append(n, g.nodesAt(x, y, child)...)
   181  	}
   182  
   183  	return n
   184  }
   185  

View as plain text