gg/engine.go

365 lines
6.5 KiB
Go
Raw Normal View History

2023-10-23 15:45:18 +03:00
package gg
2023-02-17 07:04:29 +03:00
import (
"github.com/hajimehoshi/ebiten/v2"
2023-02-18 07:46:33 +03:00
"github.com/hajimehoshi/ebiten/v2/inpututil"
2023-12-23 00:09:07 +03:00
"github.com/di4f/gods/maps"
//"fmt"
2023-02-17 23:51:40 +03:00
"time"
2023-12-24 15:05:34 +03:00
"slices"
"sync"
2023-02-17 07:04:29 +03:00
)
// The type represents order of drawing.
// Higher values are drawn later.
type Layer float64
func (l Layer) GetLayer() Layer {
return l
}
2023-05-02 17:38:34 +03:00
// Window configuration type.
2023-02-17 07:04:29 +03:00
type WindowConfig struct {
2023-12-23 00:09:07 +03:00
// The title of the window.
2023-02-17 07:04:29 +03:00
Title string
2023-05-26 18:31:04 +03:00
2023-12-23 00:09:07 +03:00
// Width and height of the window
// in pixels.
2023-05-26 18:31:04 +03:00
Width,
Height int
2023-12-23 00:09:07 +03:00
// Optional settings with
// self describing names.
2023-05-26 18:31:04 +03:00
FixedSize,
Fullscreen,
VSync bool
2023-02-17 07:04:29 +03:00
}
2023-05-02 17:38:34 +03:00
// The main structure that represents current state of [game] engine.
2023-02-17 07:04:29 +03:00
type Engine struct {
wcfg *WindowConfig
2023-12-23 00:09:07 +03:00
// The main holder for objects.
// Uses the map structure to quickly
// delete and create new objects.
Objects maps.Map[Object, struct{}]
// The main camera to display in window.
// If is set to nil then the engine will panic.
Camera *Camera
// The same delta time for all frames
// and all objects.
2023-02-17 23:51:40 +03:00
lastTime time.Time
dt Float
2023-12-23 00:09:07 +03:00
// Temporary stuff
2023-11-23 22:05:22 +03:00
keys, prevKeys []Key
2023-12-24 15:05:34 +03:00
buttons MouseButtonMap
wheel Vector
cursorPos Vector
2023-11-23 22:05:22 +03:00
outerEvents, handleEvents EventChan
2023-02-17 07:04:29 +03:00
}
type engine Engine
2023-05-02 17:38:34 +03:00
// Get currently pressed keys.
2023-02-18 07:46:33 +03:00
func (e *Engine) Keys() []Key {
return e.keys
}
2023-12-24 15:05:34 +03:00
// Returns currently pressed buttons.
func (e *Engine) MouseButtons() []MouseButton {
ret := make([]MouseButton, len(e.buttons))
i := 0
for v := range e.buttons {
ret[i] = v
i++
}
slices.Sort(ret)
return ret
}
2023-05-02 17:38:34 +03:00
// Returns new empty Engine.
func NewEngine(
2023-02-17 07:04:29 +03:00
cfg *WindowConfig,
) *Engine {
2023-12-23 00:09:07 +03:00
/*w := Float(cfg.Width)
h := Float(cfg.Height)*/
ret := &Engine{}
ret.wcfg = cfg
ret.Camera = ret.NewCamera()
ret.outerEvents = make(EventChan)
ret.handleEvents = make(EventChan)
ret.Objects = maps.NewOrdered[Object, struct{}]()
2023-12-24 15:05:34 +03:00
ret.buttons = MouseButtonMap{}
2023-12-23 00:09:07 +03:00
return ret
}
// Get the real window size in the current context.
func (c *Engine) RealWinSize() Vector {
return V(
Float(c.wcfg.Width),
Float(c.wcfg.Height),
)
}
func (c *Engine) AbsWinSize() Vector {
return c.RealWinSize().Div(c.Camera.Scale)
}
2023-11-23 22:05:22 +03:00
func (e *Engine) EventInput() EventChan {
return e.outerEvents
}
2023-02-17 23:51:40 +03:00
// Add new object considering what
// interfaces it implements.
func (e *Engine) Add(b any) error {
object, _ := b.(Object)
2023-12-23 00:09:07 +03:00
if e.Objects.Has(object) {
return ObjectExistErr
}
/*o, ok := e.makeObject(b)
if !ok {
return ObjectNotImplementedErr
}*/
2023-05-26 18:31:04 +03:00
starter, ok := b.(Starter)
if ok {
starter.Start(&Context{
Engine: e,
})
2023-05-26 18:31:04 +03:00
}
2023-02-17 23:51:40 +03:00
2023-12-23 00:09:07 +03:00
e.Objects.Set(object, struct{}{})
return nil
2023-02-17 23:51:40 +03:00
}
2023-05-26 18:31:04 +03:00
// Delete object from Engine.
func (e *Engine) Del(b any) error {
object, _ := b.(Object)
2023-12-23 00:09:07 +03:00
if !e.Objects.Has(object) {
return ObjectNotExistErr
2023-04-28 18:21:34 +03:00
}
deleter, ok := b.(Deleter)
2023-04-28 18:21:34 +03:00
if ok {
deleter.Delete(&Context{
Engine: e,
})
2023-04-28 18:21:34 +03:00
}
2023-12-23 00:09:07 +03:00
e.Objects.Del(b)
return nil
2023-02-17 23:51:40 +03:00
}
2023-12-24 15:05:34 +03:00
var (
allButtons = []MouseButton{
MouseButton0,
MouseButton1,
MouseButton2,
MouseButton3,
MouseButton4,
}
)
func (e *Engine) IsPressed(k Key) bool {
keys := e.Keys()
for _, v := range keys {
if v == k {
return true
}
}
return false
}
func (e *Engine) IsButtoned(b MouseButton) bool {
_, ok := e.buttons[b]
return ok
}
func (e *Engine) Wheel() Vector {
return e.wheel
}
func (e *Engine) cursorPosition() Vector {
x, y := ebiten.CursorPosition()
return V(Float(x), Float(y))
}
func (e *Engine) CursorPosition() Vector {
return e.cursorPos
}
func (e *Engine) AbsCursorPosition() Vector {
m := &Matrix{}
m.Concat(e.Camera.AbsMatrix())
return e.CursorPosition().Apply(m)
}
2023-02-17 07:04:29 +03:00
func (e *engine) Update() error {
var wg sync.WaitGroup
eng := (*Engine)(e)
2023-02-17 23:51:40 +03:00
2023-12-24 15:05:34 +03:00
e.dt = time.Since(e.lastTime).Seconds()
for object := range e.Objects.KeyChan() {
updater, ok := object.(Updater)
if !ok {
continue
}
wg.Add(1)
go func() {
updater.Update(&Context{
Engine: eng,
})
wg.Done()
}()
2023-12-24 15:05:34 +03:00
}
wg.Wait()
2023-11-23 22:05:22 +03:00
e.prevKeys = e.keys
2023-02-18 07:46:33 +03:00
e.keys = inpututil.
AppendPressedKeys(e.keys[:0])
2023-11-23 22:05:22 +03:00
events := []any{}
2023-12-24 15:05:34 +03:00
btns := e.buttons
for _, btn := range allButtons {
if inpututil.IsMouseButtonJustPressed(btn) {
btns[btn] = struct{}{}
events = append(events, &MouseButtonDown{
MouseButton: btn,
})
} else if inpututil.IsMouseButtonJustReleased(btn) {
delete(btns, btn)
events = append(events, &MouseButtonUp{
MouseButton: btn,
})
}
}
x, y := ebiten.Wheel()
eng.wheel = V(x, y)
if !(eng.wheel.Eq(ZV)) {
events = append(events, &WheelChange{
Offset: eng.wheel,
})
}
2023-11-23 22:05:22 +03:00
2023-12-24 15:05:34 +03:00
keyDiff := diffEm(e.prevKeys, e.keys)
for _, key := range keyDiff {
2023-11-23 22:05:22 +03:00
var event any
if eng.IsPressed(key) {
event = &KeyDown{
Key: key,
}
} else {
event = &KeyUp{
Key: key,
}
}
events = append(events, event)
}
2023-12-24 15:05:34 +03:00
realPos := eng.cursorPosition()
if !realPos.Eq(eng.cursorPos) {
absM := eng.Camera.AbsMatrix()
absPrevPos :=eng.cursorPos.Apply(&absM)
absPos := realPos.Apply(&absM)
events = append(events, &MouseMove{
Real: realPos.Sub(eng.cursorPos),
Abs: absPos.Sub(absPrevPos),
})
eng.cursorPos = realPos
}
// Providing the events to the objects.
// Maybe should think of the better way,
// but for it is simple enough.
2023-12-23 00:09:07 +03:00
for object := range e.Objects.KeyChan() {
2023-11-23 22:05:22 +03:00
eventer, ok := object.(Eventer)
if ok {
wg.Add(1)
go func() {
defer wg.Done()
for _, event := range events {
eventer.Event(&Context{
Engine: eng,
Event: event,
})
}
}()
2023-11-23 22:05:22 +03:00
}
}
wg.Wait()
2023-12-24 15:05:34 +03:00
e.lastTime = time.Now()
2023-02-17 07:04:29 +03:00
return nil
}
2023-02-17 23:51:40 +03:00
func (e *engine) Draw(i *ebiten.Image) {
eng := (*Engine)(e)
2023-11-12 13:29:38 +03:00
m := map[Layer][]Drawer{}
2023-12-23 00:09:07 +03:00
for object := range eng.Objects.KeyChan() {
drawer, ok := object.(Drawer)
if !ok {
continue
}
l := drawer.GetLayer()
2023-11-12 13:29:38 +03:00
layer, ok := m[l]
// Create new if has no the layer
if !ok {
2023-11-12 13:29:38 +03:00
m[l] = []Drawer{drawer}
continue
}
2023-11-12 13:29:38 +03:00
m[l] = append(layer, drawer)
}
2023-11-12 13:29:38 +03:00
// Drawing layers.
layers := maps.NewSparse[Layer, []Drawer](nil, m)
for layer := range layers.Chan() {
for _, drawer := range layer {
drawer.Draw(&Context{
Engine: eng,
Image: i,
})
2023-02-17 23:51:40 +03:00
}
}
2023-12-23 00:09:07 +03:00
// Empty the buff to generate it again.
eng.Camera.buf = nil
2023-02-17 07:04:29 +03:00
}
func (e *engine) Layout(ow, oh int) (int, int) {
if e.wcfg.FixedSize {
return e.wcfg.Width, e.wcfg.Height
}
return ow, oh
2023-02-17 07:04:29 +03:00
}
2023-02-17 23:51:40 +03:00
// Return the delta time duration value.
func (e *Engine) DT() Float {
return e.dt
}
2023-02-17 07:04:29 +03:00
func (e *Engine) Run() error {
ebiten.SetWindowTitle(e.wcfg.Title)
ebiten.SetWindowSize(e.wcfg.Width, e.wcfg.Height)
2023-02-18 19:35:38 +03:00
ebiten.SetWindowSizeLimits(1, 1, e.wcfg.Width, e.wcfg.Height)
ebiten.SetVsyncEnabled(e.wcfg.VSync)
2023-02-17 07:04:29 +03:00
2023-02-18 00:17:51 +03:00
e.lastTime = time.Now()
2023-12-23 00:09:07 +03:00
//fmt.Println(e.Objects)
2023-02-17 07:04:29 +03:00
return ebiten.RunGame((*engine)(e))
}