123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492 |
- package gg
- import (
- "github.com/hajimehoshi/ebiten/v2"
- "github.com/hajimehoshi/ebiten/v2/inpututil"
- "surdeus.su/core/gods/maps"
- "time"
- "slices"
- )
- import "surdeus.su/core/gg/mx"
- const (
- MaxVertices = 1 << 16
- )
- type GraphicsLibrary = ebiten.GraphicsLibrary
- type RunOptions = ebiten.RunGameOptions
- // Window configuration type.
- type WindowConfig struct {
- DebugInfo ebiten.DebugInfo
- Options *RunOptions
- // The title of the window.
- Title string
-
- // Width and height of the window
- // in pixels.
- Width,
- Height int
-
- // Optional settings with
- // self describing names.
- FixedSize,
- Fullscreen,
- VSync bool
- }
- // The main structure that represents
- // current state of [game] engine.
- type Engine struct {
- wcfg WindowConfig
- // The main holder for objects.
- // Uses the map structure to quickly
- // delete and create new objects.
- objects *Objects
- // The main camera to display in window.
- // If is set to nil then the engine will panic.
- camera Camera
- drawLastTime time.Time
- drawdt Duration
- // Frame delta time.
- dt Duration
- lastTime time.Time
- // Temporary stuff
- keys, prevKeys []Key
- buttons MouseButtonMap
- wheel mx.Vector
- cursorPos mx.Vector
- outerEvents, handleEvents EventChan
- //bufs [LayerBufSize]*Image
- vertices map[Layer] []ebiten.Vertex
- //vindices []uint16
- // Draw frame.
- dframe uint
- // Frame.
- frame uint
- runes []rune
- }
- type engine Engine
- // Get currently pressed keys.
- func (e *Engine) GetKeyboardKeys() []Key {
- return e.keys
- }
- func (e *Engine) GraphicsLibrary() GraphicsLibrary {
- return e.wcfg.DebugInfo.GraphicsLibrary
- }
- // Returns currently pressed buttons.
- func (e *Engine) GetMouseButtons() []MouseButton {
- ret := make([]MouseButton, len(e.buttons))
- i := 0
- for v := range e.buttons {
- ret[i] = v
- i++
- }
- slices.Sort(ret)
- return ret
- }
- // Returns new empty Engine.
- func NewEngine(
- cfg WindowConfig,
- ) *Engine {
- ret := &Engine{}
- ret.wcfg = cfg
- ret.outerEvents = make(EventChan)
- ret.handleEvents = make(EventChan)
- ret.objects = NewObjects()
- ret.buttons = MouseButtonMap{}
- return ret
- }
- func (e *Engine) Camera() Camera {
- return e.camera
- }
- func (e *Engine) SetCamera(c Camera) *Engine {
- e.camera = c
- return e
- }
- func (e *Engine) EventInput() EventChan {
- return e.outerEvents
- }
- func (e *Engine) Exist(object Object) bool {
- return e.objects.has(object)
- }
- // Add new objects to the Engine's view.
- func (e *Engine) Spawn(object Object) bool {
- ctx := Context{
- engine: e,
- }
- object.OnStart(ctx)
- ok := e.objects.add(object)
- return ok
- }
- // Delete object from Engine.
- func (e *Engine) Delete(object Object) bool {
- ctx := Context{
- engine: e,
- }
- object.OnDelete(ctx)
- ok := e.objects.remove(object)
- return ok
- }
- var (
- allButtons = []MouseButton{
- MouseButton0,
- MouseButton1,
- MouseButton2,
- MouseButton3,
- MouseButton4,
- }
- )
- func (e *Engine) IsPressed(k Key) bool {
- keys := e.GetKeyboardKeys()
- 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) GetMouseWheel() mx.Vector {
- return e.wheel
- }
- func (e *Engine) cursorPosition() mx.Vector {
- x, y := ebiten.CursorPosition()
- return mx.Vector{mx.Float(x), mx.Float(y)}
- }
- // Get the real cursor position.
- func (e *Engine) GetRealCursorPosition() mx.Vector {
- return e.cursorPos
- }
- // Get the absolute cursor position in the world
- // of the engine.
- func (e *Engine) GetAbsCursorPosition(
- ) mx.Vector {
- return e.GetRealCursorPosition().
- Apply(e.camera.GetAbsMatrice(Context{
- engine: e,
- }))
- }
- // Get the real window size in the current context.
- func (e *Engine) GetRealWinSize() mx.Vector {
- var w, h int
- if e.wcfg.Fullscreen {
- w, h = ebiten.ScreenSizeInFullscreen()
- } else {
- w, h = e.wcfg.Width, e.wcfg.Height
- }
- return mx.Vector{
- mx.Float(w),
- mx.Float(h),
- }
- }
- func (e *Engine) GetAbsWinSize() mx.Vector {
- return e.camera.GetAbsWinSize(Context{
- engine: e,
- })
- }
- func (e *Engine) Runes() []rune {
- return e.runes
- }
- func (e *engine) updateEvents() Events {
- eng := (*Engine)(e)
- events := Events{}
- // Mouse buttons.
- btns := e.buttons
- for _, btn := range allButtons {
- if inpututil.IsMouseButtonJustPressed(btn) {
- btns[btn] = struct{}{}
- events.Mouse.Downs = append(
- events.Mouse.Downs,
- MouseButtonDown{
- MouseButton: btn,
- },
- )
- } else if inpututil.IsMouseButtonJustReleased(btn) {
- delete(btns, btn)
- events.Mouse.Ups = append(
- events.Mouse.Ups,
- MouseButtonUp{
- MouseButton: btn,
- },
- )
- }
- }
- // Mouse wheel.
- x, y := ebiten.Wheel()
- e.wheel = mx.Vector{x, y}
- if !(e.wheel.Eq(mx.ZV)) {
- events.Mouse.Wheel = &WheelChange{
- Offset: e.wheel,
- }
- }
- // Cursor position.
- realPos := eng.cursorPosition()
- if !realPos.Eq(e.cursorPos) {
- absM := eng.camera.GetAbsMatrice(Context{
- engine: eng,
- })
- absPrevPos := e.cursorPos.Apply(absM)
- absPos := realPos.Apply(absM)
- events.Mouse.Move = &MouseMove{
- RealDelta: realPos.Sub(e.cursorPos),
- AbsDelta: absPos.Sub(absPrevPos),
- }
- e.cursorPos = realPos
- }
-
- e.prevKeys = e.keys
- //newKeys := []Key{e.keys[0]}
- e.keys = nil
- e.keys = inpututil.
- AppendPressedKeys(e.keys[:0])
- // Keyboard.
- keyDiff := diffEm(e.prevKeys, e.keys)
- for _, key := range keyDiff {
- if eng.IsPressed(key) {
- events.Keyboard.Downs = append(
- events.Keyboard.Downs,
- KeyDown{
- Key: key,
- },
- )
- } else {
- events.Keyboard.Ups = append(
- events.Keyboard.Ups,
- KeyUp{
- Key: key,
- },
- )
- }
- }
- return events
- }
- func (e *engine) Update() error {
- eng := (*Engine)(e)
- e.dt = time.Since(e.lastTime)
- e.runes = ebiten.AppendInputChars(e.runes[:0])
- //fmt.Println("runes:", e.runes)
- // Buffering the context for faster.
- // Providing the events to the objects.
- // Maybe should think of the better way,
- // but for it is simple enough.
- events := e.updateEvents()
- c := Context{
- engine: eng,
- events: events,
- }
- // Should think of the order?
- e.objects.updateTags(c)
- e.objects.updateObjects(c)
- e.lastTime = time.Now()
- e.frame++
- return nil
- }
- func (e *Engine) Objects() *Objects {
- return e.objects
- }
- var (
- fullPageIndexes = func() [MaxVertices]uint16 {
- ret := [MaxVertices]uint16{}
- for i:=0 ; i<len(ret) ; i++ {
- ret[i] = uint16(i)
- }
- return ret
- }()
- defaultPageImg = func() *Image {
- img := NewImage(1, 1)
- img.Set(0, 0, RGBA(1, 1, 1, 1))
- return img
- }()
- defaultTriOptions = &ebiten.DrawTrianglesOptions{}
- )
- func (e *engine) Draw(img *ebiten.Image) {
- e.drawdt = time.Since(e.drawLastTime)
- eng := (*Engine)(e)
- m := map[Layer][]Drawer{}
- for _, object := range eng.objects.store {
- // Skipping the ones we do not need to draw.
- if object == nil || !object.IsVisible() {
- continue
- }
- l := object.GetLayer()
- layer, ok := m[l]
- // Create new if has no the layer
- if !ok {
- m[l] = []Drawer{object}
- continue
- }
- m[l] = append(layer, object)
- }
- // Drawing layers via the sparse array.
- // First drawing via the inside function
- // and then the returned []EVertex.
- layers := maps.NewSparse[Layer, []Drawer](nil, m)
- c := Context{engine: eng, image: img}
- for layer := range layers.Chan() {
- vertices := Vertices{}
- for _, drawer := range layer {
- drawing := drawer.Draw(c)
- if drawing != nil {
- vertices = append(
- vertices,
- drawing.Vertices...,
- )
- }
- }
- pn := len(vertices) / MaxVertices
- mod := len(vertices) % MaxVertices
- for i := 0 ; i<pn ; i++ {
- cur := i*MaxVertices
- img.DrawTriangles(
- vertices[cur:cur+MaxVertices].ToAPI(),
- fullPageIndexes[:],
- defaultPageImg,
- defaultTriOptions,
- )
- }
- st := pn*MaxVertices
- img.DrawTriangles(
- vertices[st:].ToAPI(),
- fullPageIndexes[:mod],
- defaultPageImg,
- defaultTriOptions,
- )
- }
- // Empty the buff to generate it again.
- e.drawLastTime = time.Now()
- e.dframe++
- }
- func (e *engine) Layout(ow, oh int) (int, int) {
- if e.wcfg.FixedSize {
- return e.wcfg.Width, e.wcfg.Height
- }
- return ow, oh
- }
- // Return the delta time between Draw calls.
- func (e *Engine) DrawDT() Duration {
- return e.drawdt
- }
- // Current Drawing frame.
- func (e *Engine) Dframe() uint {
- return e.dframe
- }
- // Return the real delta time.
- // Please, prefer the DT().
- func (e *Engine) RealDT() Duration {
- return e.dt
- }
- // Returns the current delta time. (btw frames)
- // (By the Ebiten community convention
- // currently it is a fixed value)
- func (e *Engine) DT() Duration {
- return time.Second/60
- }
- // Current frame
- func (e *Engine) Frame() uint {
- return e.frame
- }
- // Current FPS.
- func (e *Engine) FPS() float64 {
- return ebiten.ActualFPS()
- }
- // Current TPS.
- func (e *Engine) TPS() float64 {
- return ebiten.ActualTPS()
- }
- // Run the engine.
- func (e *Engine) Run() error {
- ebiten.ReadDebugInfo(&e.wcfg.DebugInfo)
- ebiten.SetWindowTitle(e.wcfg.Title)
- ebiten.SetWindowSize(e.wcfg.Width, e.wcfg.Height)
- ebiten.SetWindowSizeLimits(1, 1, e.wcfg.Width, e.wcfg.Height)
-
- ebiten.SetFullscreen(e.wcfg.Fullscreen)
- ebiten.SetVsyncEnabled(e.wcfg.VSync)
- e.lastTime = time.Now()
- //fmt.Println(e.Objects)
- return ebiten.RunGameWithOptions((*engine)(e), e.wcfg.Options)
- }
- func (e *Engine) GetWindowConfig() WindowConfig {
- return e.wcfg
- }
|