From e6d418fdb2369617ef483f5b2d2e962d505d207b Mon Sep 17 00:00:00 2001 From: gravestench Date: Sat, 28 Nov 2020 15:49:18 -0800 Subject: [PATCH] Adding input system, mouse cursor scene, interactive component * added `d2common/d2input`, copied input vector logic from hellspawner * added an `InteractiveComponent` which contains input vector, enable flag, and callback function * Added an InputSystem which handles input logic and iterates over entities with interactive components * added a mouse cursor scene for rendering the mouse cursor * made the trademark sprite disappear when left mouse is clicked * various other small bugfixes in scene systems --- d2common/d2input/input_vector.go | 93 +++++++++++++ d2common/d2input/keys.go | 105 +++++++++++++++ d2common/d2input/modifiers.go | 11 ++ d2common/d2input/mouse_buttons.go | 11 ++ d2core/d2components/interactive.go | 51 ++++++++ d2core/d2systems/game_client_bootstrap.go | 6 + d2core/d2systems/input_system.go | 151 ++++++++++++++++++++++ d2core/d2systems/scene_base.go | 46 +++++-- d2core/d2systems/scene_loading_screen.go | 1 + d2core/d2systems/scene_main_menu.go | 38 +++++- d2core/d2systems/scene_mouse_cursor.go | 83 ++++++++++++ go.sum | 4 - 12 files changed, 582 insertions(+), 18 deletions(-) create mode 100644 d2common/d2input/input_vector.go create mode 100644 d2common/d2input/keys.go create mode 100644 d2common/d2input/modifiers.go create mode 100644 d2common/d2input/mouse_buttons.go create mode 100644 d2core/d2components/interactive.go create mode 100644 d2core/d2systems/input_system.go create mode 100644 d2core/d2systems/scene_mouse_cursor.go diff --git a/d2common/d2input/input_vector.go b/d2common/d2input/input_vector.go new file mode 100644 index 00000000..a3d98616 --- /dev/null +++ b/d2common/d2input/input_vector.go @@ -0,0 +1,93 @@ +package d2input + +import "github.com/gravestench/akara" + +func NewInputVector() *InputVector { + v := &InputVector{ + KeyVector: akara.NewBitSet(), + ModifierVector: akara.NewBitSet(), + MouseButtonVector: akara.NewBitSet(), + } + + return v.Clear() +} + +type InputVector struct { + KeyVector *akara.BitSet + ModifierVector *akara.BitSet + MouseButtonVector *akara.BitSet +} + +func (iv *InputVector) SetKey(key Key) *InputVector { + return iv.SetKeys([]Key{key}) +} + +func (iv *InputVector) SetKeys(keys []Key) *InputVector { + if len(keys) == 0 { + return iv + } + + for _, key := range keys { + iv.KeyVector.Set(int(key), true) + } + + return iv +} + +func (iv *InputVector) SetModifier(mod Modifier) *InputVector { + return iv.SetModifiers([]Modifier{mod}) +} + +func (iv *InputVector) SetModifiers(mods []Modifier) *InputVector { + if len(mods) == 0 { + return iv + } + + for _, key := range mods { + iv.ModifierVector.Set(int(key), true) + } + + return iv +} + +func (iv *InputVector) SetMouseButton(button MouseButton) *InputVector { + return iv.SetMouseButtons([]MouseButton{button}) +} + +func (iv *InputVector) SetMouseButtons(buttons []MouseButton) *InputVector { + if len(buttons) == 0 { + return iv + } + + for _, key := range buttons { + iv.MouseButtonVector.Set(int(key), true) + } + + return iv +} + +func (iv *InputVector) Contains(other *InputVector) bool { + keys := iv.KeyVector.ContainsAll(other.KeyVector) + buttons := iv.MouseButtonVector.ContainsAll(other.MouseButtonVector) + + // We do Equals here, because we dont want CTRL+X and CTRL+ALT+X to fire at the same time + mods := iv.ModifierVector.Equals(other.ModifierVector) + + return keys && mods && buttons +} + +func (iv *InputVector) Intersects(other *InputVector) bool { + keys := iv.KeyVector.Intersects(other.KeyVector) + mods := iv.ModifierVector.Intersects(other.ModifierVector) + buttons := iv.MouseButtonVector.Intersects(other.MouseButtonVector) + + return keys || mods || buttons +} + +func (iv *InputVector) Clear() *InputVector { + iv.KeyVector.Clear() + iv.ModifierVector.Clear() + iv.MouseButtonVector.Clear() + + return iv +} diff --git a/d2common/d2input/keys.go b/d2common/d2input/keys.go new file mode 100644 index 00000000..a41ebe47 --- /dev/null +++ b/d2common/d2input/keys.go @@ -0,0 +1,105 @@ +package d2input + +import "github.com/hajimehoshi/ebiten/v2" + +type Key = int + +const ( + Key0 = Key(ebiten.Key0) + Key1 = Key(ebiten.Key1) + Key2 = Key(ebiten.Key2) + Key3 = Key(ebiten.Key3) + Key4 = Key(ebiten.Key4) + Key5 = Key(ebiten.Key5) + Key6 = Key(ebiten.Key6) + Key7 = Key(ebiten.Key7) + Key8 = Key(ebiten.Key8) + Key9 = Key(ebiten.Key9) + KeyA = Key(ebiten.KeyA) + KeyB = Key(ebiten.KeyB) + KeyC = Key(ebiten.KeyC) + KeyD = Key(ebiten.KeyD) + KeyE = Key(ebiten.KeyE) + KeyF = Key(ebiten.KeyF) + KeyG = Key(ebiten.KeyG) + KeyH = Key(ebiten.KeyH) + KeyI = Key(ebiten.KeyI) + KeyJ = Key(ebiten.KeyJ) + KeyK = Key(ebiten.KeyK) + KeyL = Key(ebiten.KeyL) + KeyM = Key(ebiten.KeyM) + KeyN = Key(ebiten.KeyN) + KeyO = Key(ebiten.KeyO) + KeyP = Key(ebiten.KeyP) + KeyQ = Key(ebiten.KeyQ) + KeyR = Key(ebiten.KeyR) + KeyS = Key(ebiten.KeyS) + KeyT = Key(ebiten.KeyT) + KeyU = Key(ebiten.KeyU) + KeyV = Key(ebiten.KeyV) + KeyW = Key(ebiten.KeyW) + KeyX = Key(ebiten.KeyX) + KeyY = Key(ebiten.KeyY) + KeyZ = Key(ebiten.KeyZ) + KeyApostrophe = Key(ebiten.KeyApostrophe) + KeyBackslash = Key(ebiten.KeyBackslash) + KeyBackspace = Key(ebiten.KeyBackspace) + KeyCapsLock = Key(ebiten.KeyCapsLock) + KeyComma = Key(ebiten.KeyComma) + KeyDelete = Key(ebiten.KeyDelete) + KeyDown = Key(ebiten.KeyDown) + KeyEnd = Key(ebiten.KeyEnd) + KeyEnter = Key(ebiten.KeyEnter) + KeyEqual = Key(ebiten.KeyEqual) + KeyEscape = Key(ebiten.KeyEscape) + KeyF1 = Key(ebiten.KeyF1) + KeyF2 = Key(ebiten.KeyF2) + KeyF3 = Key(ebiten.KeyF3) + KeyF4 = Key(ebiten.KeyF4) + KeyF5 = Key(ebiten.KeyF5) + KeyF6 = Key(ebiten.KeyF6) + KeyF7 = Key(ebiten.KeyF7) + KeyF8 = Key(ebiten.KeyF8) + KeyF9 = Key(ebiten.KeyF9) + KeyF10 = Key(ebiten.KeyF10) + KeyF11 = Key(ebiten.KeyF11) + KeyF12 = Key(ebiten.KeyF12) + KeyGraveAccent = Key(ebiten.KeyGraveAccent) + KeyHome = Key(ebiten.KeyHome) + KeyInsert = Key(ebiten.KeyInsert) + KeyKP0 = Key(ebiten.KeyKP0) + KeyKP1 = Key(ebiten.KeyKP1) + KeyKP2 = Key(ebiten.KeyKP2) + KeyKP3 = Key(ebiten.KeyKP3) + KeyKP4 = Key(ebiten.KeyKP4) + KeyKP5 = Key(ebiten.KeyKP5) + KeyKP6 = Key(ebiten.KeyKP6) + KeyKP7 = Key(ebiten.KeyKP7) + KeyKP8 = Key(ebiten.KeyKP8) + KeyKP9 = Key(ebiten.KeyKP9) + KeyKPAdd = Key(ebiten.KeyKPAdd) + KeyKPDecimal = Key(ebiten.KeyKPDecimal) + KeyKPDivide = Key(ebiten.KeyKPDivide) + KeyKPEnter = Key(ebiten.KeyKPEnter) + KeyKPEqual = Key(ebiten.KeyKPEqual) + KeyKPMultiply = Key(ebiten.KeyKPMultiply) + KeyKPSubtract = Key(ebiten.KeyKPSubtract) + KeyLeft = Key(ebiten.KeyLeft) + KeyLeftBracket = Key(ebiten.KeyLeftBracket) + KeyMenu = Key(ebiten.KeyMenu) + KeyMinus = Key(ebiten.KeyMinus) + KeyNumLock = Key(ebiten.KeyNumLock) + KeyPageDown = Key(ebiten.KeyPageDown) + KeyPageUp = Key(ebiten.KeyPageUp) + KeyPause = Key(ebiten.KeyPause) + KeyPeriod = Key(ebiten.KeyPeriod) + KeyPrintScreen = Key(ebiten.KeyPrintScreen) + KeyRight = Key(ebiten.KeyRight) + KeyRightBracket = Key(ebiten.KeyRightBracket) + KeyScrollLock = Key(ebiten.KeyScrollLock) + KeySemicolon = Key(ebiten.KeySemicolon) + KeySlash = Key(ebiten.KeySlash) + KeySpace = Key(ebiten.KeySpace) + KeyTab = Key(ebiten.KeyTab) + KeyUp = Key(ebiten.KeyUp) +) diff --git a/d2common/d2input/modifiers.go b/d2common/d2input/modifiers.go new file mode 100644 index 00000000..8b893059 --- /dev/null +++ b/d2common/d2input/modifiers.go @@ -0,0 +1,11 @@ +package d2input + +import "github.com/hajimehoshi/ebiten/v2" + +type Modifier = int + +const ( + ModAlt = Modifier(ebiten.KeyAlt) + ModControl = Modifier(ebiten.KeyControl) + ModShift = Modifier(ebiten.KeyShift) +) diff --git a/d2common/d2input/mouse_buttons.go b/d2common/d2input/mouse_buttons.go new file mode 100644 index 00000000..e9ee53d7 --- /dev/null +++ b/d2common/d2input/mouse_buttons.go @@ -0,0 +1,11 @@ +package d2input + +import "github.com/hajimehoshi/ebiten/v2" + +type MouseButton = int + +const ( + MouseButtonLeft = MouseButton(ebiten.MouseButtonLeft) + MouseButtonRight = MouseButton(ebiten.MouseButtonRight) + MouseButtonMiddle = MouseButton(ebiten.MouseButtonMiddle) +) diff --git a/d2core/d2components/interactive.go b/d2core/d2components/interactive.go new file mode 100644 index 00000000..a86cc229 --- /dev/null +++ b/d2core/d2components/interactive.go @@ -0,0 +1,51 @@ +package d2components + +import ( + "github.com/gravestench/akara" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2input" +) + +// static check that Interactive implements Component +var _ akara.Component = &Interactive{} + +func noop() bool { + return false +} + +// Interactive is used to flag file entities with a file type +type Interactive struct { + Enabled bool + *d2input.InputVector + Callback func() (preventPropagation bool) +} + +// New returns a Interactive component. By default, it contains a nil instance. +func (*Interactive) New() akara.Component { + return &Interactive{ + Enabled: true, + InputVector: d2input.NewInputVector(), + Callback: noop, + } +} + +// InteractiveFactory is a wrapper for the generic component factory that returns Interactive component instances. +// This can be embedded inside of a system to give them the methods for adding, retrieving, and removing a Interactive. +type InteractiveFactory struct { + Interactive *akara.ComponentFactory +} + +// AddInteractive adds a Interactive component to the given entity and returns it +func (m *InteractiveFactory) AddInteractive(id akara.EID) *Interactive { + return m.Interactive.Add(id).(*Interactive) +} + +// GetInteractive returns the Interactive component for the given entity, and a bool for whether or not it exists +func (m *InteractiveFactory) GetInteractive(id akara.EID) (*Interactive, bool) { + component, found := m.Interactive.Get(id) + if !found { + return nil, found + } + + return component.(*Interactive), found +} diff --git a/d2core/d2systems/game_client_bootstrap.go b/d2core/d2systems/game_client_bootstrap.go index e9d9c27b..5d4544ba 100644 --- a/d2core/d2systems/game_client_bootstrap.go +++ b/d2core/d2systems/game_client_bootstrap.go @@ -46,6 +46,9 @@ func (m *GameClientBootstrapSystem) injectSystems() { m.Info("injecting render system") m.AddSystem(&RenderSystem{}) + m.Info("injecting input system") + m.AddSystem(&InputSystem{}) + m.Info("injecting update counter system") m.AddSystem(&UpdateCounter{}) @@ -54,6 +57,9 @@ func (m *GameClientBootstrapSystem) injectSystems() { m.Info("injecting main menu scene") m.AddSystem(NewMainMenuScene()) + + m.Info("injecting mouse cursor scene") + m.AddSystem(NewMouseCursorScene()) } // Update does nothing, but exists to satisfy the `akara.System` interface diff --git a/d2core/d2systems/input_system.go b/d2core/d2systems/input_system.go new file mode 100644 index 00000000..9968a90d --- /dev/null +++ b/d2core/d2systems/input_system.go @@ -0,0 +1,151 @@ +package d2systems + +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2input" + "github.com/hajimehoshi/ebiten/v2" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2components" + "github.com/gravestench/akara" +) + +const ( + logPrefixInputSystem = "Input System" +) + +// static check that InputSystem implements the System interface +var _ akara.System = &InputSystem{} + +// InputSystem is responsible for handling interactive entities +type InputSystem struct { + akara.BaseSubscriberSystem + *d2util.Logger + renderer d2interface.Renderer + configs *akara.Subscription + interactives *akara.Subscription + d2components.GameConfigFactory + d2components.InteractiveFactory + inputState *d2input.InputVector +} + +// Init initializes the system with the given world, injecting the necessary components +func (m *InputSystem) Init(world *akara.World) { + m.World = world + + m.setupLogger() + + m.Info("initializing ...") + + m.setupFactories() + m.setupSubscriptions() + + m.inputState = d2input.NewInputVector() +} + +func (m *InputSystem) setupLogger() { + m.Logger = d2util.NewLogger() + m.SetPrefix(logPrefixInputSystem) +} + +func (m *InputSystem) setupFactories() { + m.Info("setting up component factories") + + gameConfigID := m.RegisterComponent(&d2components.GameConfig{}) + interactiveID := m.RegisterComponent(&d2components.Interactive{}) + + m.GameConfig = m.GetComponentFactory(gameConfigID) + m.Interactive = m.GetComponentFactory(interactiveID) +} + +func (m *InputSystem) setupSubscriptions() { + m.Info("setting up component subscriptions") + + interactives := m.NewComponentFilter(). + Require(&d2components.Interactive{}). + Build() + + gameConfigs := m.NewComponentFilter(). + Require(&d2components.GameConfig{}). + Build() + + m.interactives = m.AddSubscription(interactives) + m.configs = m.AddSubscription(gameConfigs) +} + +// Update will iterate over interactive entities +func (m *InputSystem) Update() { + m.updateInputState() + + for _, id := range m.interactives.GetEntities() { + preventPropagation := m.applyInputState(id) + if preventPropagation { + break + } + } +} + +func (m *InputSystem) updateInputState() { + var keysToCheck = []d2input.Key{ + d2input.Key0, d2input.Key1, d2input.Key2, d2input.Key3, d2input.Key4, d2input.Key5, d2input.Key6, + d2input.Key7, d2input.Key8, d2input.Key9, d2input.KeyA, d2input.KeyB, d2input.KeyC, d2input.KeyD, + d2input.KeyE, d2input.KeyF, d2input.KeyG, d2input.KeyH, d2input.KeyI, d2input.KeyJ, d2input.KeyK, + d2input.KeyL, d2input.KeyM, d2input.KeyN, d2input.KeyO, d2input.KeyP, d2input.KeyQ, d2input.KeyR, + d2input.KeyS, d2input.KeyT, d2input.KeyU, d2input.KeyV, d2input.KeyW, d2input.KeyX, d2input.KeyY, + d2input.KeyZ, d2input.KeyApostrophe, d2input.KeyBackslash, d2input.KeyBackspace, + d2input.KeyCapsLock, d2input.KeyComma, d2input.KeyDelete, d2input.KeyDown, + d2input.KeyEnd, d2input.KeyEnter, d2input.KeyEqual, d2input.KeyEscape, + d2input.KeyF1, d2input.KeyF2, d2input.KeyF3, d2input.KeyF4, d2input.KeyF5, d2input.KeyF6, + d2input.KeyF7, d2input.KeyF8, d2input.KeyF9, d2input.KeyF10, d2input.KeyF11, d2input.KeyF12, + d2input.KeyGraveAccent, d2input.KeyHome, d2input.KeyInsert, d2input.KeyKP0, + d2input.KeyKP1, d2input.KeyKP2, d2input.KeyKP3, d2input.KeyKP4, d2input.KeyKP5, + d2input.KeyKP6, d2input.KeyKP7, d2input.KeyKP8, d2input.KeyKP9, + d2input.KeyKPAdd, d2input.KeyKPDecimal, d2input.KeyKPDivide, d2input.KeyKPEnter, + d2input.KeyKPEqual, d2input.KeyKPMultiply, d2input.KeyKPSubtract, d2input.KeyLeft, + d2input.KeyLeftBracket, d2input.KeyMenu, d2input.KeyMinus, d2input.KeyNumLock, + d2input.KeyPageDown, d2input.KeyPageUp, d2input.KeyPause, d2input.KeyPeriod, + d2input.KeyPrintScreen, d2input.KeyRight, d2input.KeyRightBracket, + d2input.KeyScrollLock, d2input.KeySemicolon, d2input.KeySlash, + d2input.KeySpace, d2input.KeyTab, d2input.KeyUp, + } + + var modifiersToCheck = []d2input.Modifier{ + d2input.ModAlt, d2input.ModControl, d2input.ModShift, + } + + var buttonsToCheck = []d2input.MouseButton{ + d2input.MouseButtonLeft, d2input.MouseButtonMiddle, d2input.MouseButtonRight, + } + + for _, key := range keysToCheck { + truth := ebiten.IsKeyPressed(ebiten.Key(key)) + m.inputState.KeyVector.Set(key, truth) + } + + for _, mod := range modifiersToCheck { + truth := ebiten.IsKeyPressed(ebiten.Key(mod)) + m.inputState.ModifierVector.Set(mod, truth) + } + + for _, btn := range buttonsToCheck { + truth := ebiten.IsMouseButtonPressed(ebiten.MouseButton(btn)) + m.inputState.MouseButtonVector.Set(btn, truth) + } +} + +func (m *InputSystem) applyInputState(id akara.EID) (preventPropagation bool) { + v, found := m.GetInteractive(id) + if !found { + return false + } + + if !v.Enabled { + return + } + + if m.inputState.Contains(v.InputVector) { + return v.Callback() + } + + return false +} diff --git a/d2core/d2systems/scene_base.go b/d2core/d2systems/scene_base.go index 4c27c932..67d0cb30 100644 --- a/d2core/d2systems/scene_base.go +++ b/d2core/d2systems/scene_base.go @@ -37,6 +37,7 @@ var _ akara.System = &BaseScene{} type baseSystems struct { *RenderSystem + *InputSystem *GameObjectFactory } @@ -58,8 +59,10 @@ type BaseScene struct { d2components.ViewportFactory d2components.MainViewportFactory d2components.ViewportFilterFactory + d2components.PriorityFactory d2components.CameraFactory d2components.RenderableFactory + d2components.InteractiveFactory d2components.PositionFactory d2components.ScaleFactory d2components.AnimationFactory @@ -82,6 +85,7 @@ func (s *BaseScene) Init(world *akara.World) { s.World = world if s.World == nil { + s.SetActive(false) return } } @@ -99,12 +103,17 @@ func (s *BaseScene) boot() { s.Add.SetPrefix(fmt.Sprintf("%s -> %s", s.key, "Object Factory")) for idx := range s.Systems { - if rendersys, ok := s.Systems[idx].(*RenderSystem); ok { + if rendersys, ok := s.Systems[idx].(*RenderSystem); ok && s.systems.RenderSystem == nil { s.systems.RenderSystem = rendersys continue } - if objFactory, ok := s.Systems[idx].(*GameObjectFactory); ok { + if inputSys, ok := s.Systems[idx].(*InputSystem); ok && s.systems.InputSystem == nil { + s.systems.InputSystem = inputSys + continue + } + + if objFactory, ok := s.Systems[idx].(*GameObjectFactory); ok && s.systems.GameObjectFactory == nil { s.systems.GameObjectFactory = objFactory continue } @@ -120,6 +129,13 @@ func (s *BaseScene) boot() { return } + if s.systems.InputSystem == nil { + s.Info("waiting for input system") + return + } + + s.systems.InputSystem.renderer = s.systems.RenderSystem.renderer + if s.systems.GameObjectFactory == nil { s.Info("waiting for game object factory ...") return @@ -140,7 +156,9 @@ func (s *BaseScene) setupFactories() { viewportID := s.RegisterComponent(&d2components.Viewport{}) viewportFilterID := s.RegisterComponent(&d2components.ViewportFilter{}) cameraID := s.RegisterComponent(&d2components.Camera{}) + priorityID := s.RegisterComponent(&d2components.Priority{}) renderableID := s.RegisterComponent(&d2components.Renderable{}) + interactiveID := s.RegisterComponent(&d2components.Interactive{}) positionID := s.RegisterComponent(&d2components.Position{}) scaleID := s.RegisterComponent(&d2components.Scale{}) animationID := s.RegisterComponent(&d2components.Animation{}) @@ -151,7 +169,9 @@ func (s *BaseScene) setupFactories() { s.Viewport = s.GetComponentFactory(viewportID) s.ViewportFilter = s.GetComponentFactory(viewportFilterID) s.Camera = s.GetComponentFactory(cameraID) + s.Priority = s.GetComponentFactory(priorityID) s.Renderable = s.GetComponentFactory(renderableID) + s.Interactive = s.GetComponentFactory(interactiveID) s.Position = s.GetComponentFactory(positionID) s.Scale = s.GetComponentFactory(scaleID) s.Animation = s.GetComponentFactory(animationID) @@ -163,13 +183,11 @@ func (s *BaseScene) createDefaultViewport() { s.Info("creating default viewport") viewportID := s.NewEntity() s.AddViewport(viewportID) + s.AddPriority(viewportID) camera := s.AddCamera(viewportID) - camera.Width = 800 - camera.Height = 600 - camera.Zoom = 1 - sfc := s.systems.renderer.NewSurface(camera.Width, camera.Height) + sfc := s.systems.RenderSystem.renderer.NewSurface(camera.Width, camera.Height) sfc.Clear(color.Transparent) @@ -266,9 +284,11 @@ func (s *BaseScene) renderViewport(idx int, objects []akara.EID) { } if sfc.Surface == nil { - sfc.Surface = s.systems.renderer.NewSurface(camera.Width, camera.Height) + sfc.Surface = s.systems.RenderSystem.renderer.NewSurface(camera.Width, camera.Height) } + sfc.Clear(color.Transparent) + cx, cy := int(camera.X()), int(camera.Y()) sfc.Surface.PushTranslation(cx, cy) // negative because we're offsetting everything that gets rendered @@ -298,6 +318,11 @@ func (s *BaseScene) renderObject(target d2interface.Surface, id akara.EID) { scale = s.AddScale(id) } + alpha, found := s.GetAlpha(id) + if !found { + alpha = s.AddAlpha(id) + } + x, y := int(position.X()), int(position.Y()) target.PushTranslation(x, y) @@ -306,6 +331,9 @@ func (s *BaseScene) renderObject(target d2interface.Surface, id akara.EID) { target.PushScale(scale.X(), scale.Y()) defer target.Pop() + target.PushColor(color.Alpha{A: uint8(alpha.Alpha * 255)}) + defer target.Pop() + segment, found := s.systems.SpriteFactory.GetSegmentedSprite(id) if found { animation, found := s.GetAnimation(id) @@ -352,10 +380,10 @@ type sceneObjectFactory struct { } func (s *sceneObjectFactory) addBasicComponenets(id akara.EID) { - _ = s.AddScale(id) - _ = s.AddOrigin(id) _ = s.AddPosition(id) + _ = s.AddScale(id) _ = s.AddAlpha(id) + _ = s.AddOrigin(id) } func (s *sceneObjectFactory) Sprite(x, y float64, imgPath, palPath string) akara.EID { diff --git a/d2core/d2systems/scene_loading_screen.go b/d2core/d2systems/scene_loading_screen.go index 149c315f..a1e20f82 100644 --- a/d2core/d2systems/scene_loading_screen.go +++ b/d2core/d2systems/scene_loading_screen.go @@ -72,6 +72,7 @@ func (s *LoadingScene) Init(world *akara.World) { func (s *LoadingScene) boot() { if !s.BaseScene.booted { + s.BaseScene.boot() return } diff --git a/d2core/d2systems/scene_main_menu.go b/d2core/d2systems/scene_main_menu.go index 6db69140..d49770de 100644 --- a/d2core/d2systems/scene_main_menu.go +++ b/d2core/d2systems/scene_main_menu.go @@ -1,6 +1,7 @@ package d2systems import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2input" "github.com/gravestench/akara" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" @@ -28,7 +29,11 @@ var _ d2interface.Scene = &MainMenuScene{} // or start the map engine test. type MainMenuScene struct { *BaseScene - booted bool + booted bool + sprites struct { + trademark akara.EID + mainBackground akara.EID + } } // Init the main menu scene @@ -43,16 +48,20 @@ func (s *MainMenuScene) boot() { return } - s.createTrademarkScreen() - s.createButtons() s.createBackground() + s.createButtons() + s.createTrademarkScreen() s.booted = true } func (s *MainMenuScene) createBackground() { s.Info("creating background") - s.Add.SegmentedSprite(0, 0, d2resource.GameSelectScreen, d2resource.PaletteSky, 4, 3, 0) + + imgPath := d2resource.GameSelectScreen + palPath := d2resource.PaletteSky + + s.sprites.mainBackground = s.Add.SegmentedSprite(0, 0, imgPath, palPath, 4, 3, 0) } func (s *MainMenuScene) createButtons() { @@ -61,7 +70,26 @@ func (s *MainMenuScene) createButtons() { func (s *MainMenuScene) createTrademarkScreen() { s.Info("creating trademark screen") - s.Add.SegmentedSprite(0, 0, d2resource.TrademarkScreen, d2resource.PaletteSky, 4, 3, 0) + + imgPath := d2resource.TrademarkScreen + palPath := d2resource.PaletteSky + + s.sprites.trademark = s.Add.SegmentedSprite(0, 0, imgPath, palPath, 4, 3, 0) + + interactive := s.AddInteractive(s.sprites.trademark) + + interactive.InputVector.SetMouseButton(d2input.MouseButtonLeft) + + interactive.Callback = func() bool { + s.Info("hiding trademark sprite") + + alpha, _ := s.GetAlpha(s.sprites.trademark) + alpha.Alpha = 0 + + interactive.Enabled = false + + return true // prevent propagation + } } // Update the main menu scene diff --git a/d2core/d2systems/scene_mouse_cursor.go b/d2core/d2systems/scene_mouse_cursor.go new file mode 100644 index 00000000..918b6b3a --- /dev/null +++ b/d2core/d2systems/scene_mouse_cursor.go @@ -0,0 +1,83 @@ +package d2systems + +import ( + "github.com/gravestench/akara" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" +) + +const ( + sceneKeyMouseCursor = "Mouse Cursor" +) + +// NewMouseCursorScene creates a new main menu scene. This is the first screen that the user +// will see when launching the game. +func NewMouseCursorScene() *MouseCursorScene { + scene := &MouseCursorScene{ + BaseScene: NewBaseScene(sceneKeyMouseCursor), + } + + return scene +} + +// static check that MouseCursorScene implements the scene interface +var _ d2interface.Scene = &MouseCursorScene{} + +// MouseCursorScene represents the game's main menu, where users can select single or multi player, +// or start the map engine test. +type MouseCursorScene struct { + *BaseScene + booted bool + cursor akara.EID +} + +// Init the main menu scene +func (s *MouseCursorScene) Init(world *akara.World) { + s.World = world + + s.Info("initializing ...") +} + +func (s *MouseCursorScene) boot() { + if !s.BaseScene.booted { + s.BaseScene.boot() + return + } + + s.createMouseCursor() + + s.booted = true +} + +func (s *MouseCursorScene) createMouseCursor() { + s.Info("creating mouse cursor") + s.cursor = s.Add.Sprite(0, 0, d2resource.CursorDefault, d2resource.PaletteUnits) +} + +// Update the main menu scene +func (s *MouseCursorScene) Update() { + if s.Paused() { + return + } + + if !s.booted { + s.boot() + return + } + + s.updateCursorPosition() + + s.BaseScene.Update() +} + +func (s *MouseCursorScene) updateCursorPosition() { + spritePosition, found := s.GetPosition(s.cursor) + if !found { + return + } + + cx, cy := s.systems.InputSystem.renderer.GetCursorPos() + + spritePosition.Set(float64(cx), float64(cy)) +} diff --git a/go.sum b/go.sum index c738e101..968b9147 100644 --- a/go.sum +++ b/go.sum @@ -19,10 +19,6 @@ github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY= github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gravestench/akara v0.0.0-20201119221449-924b47999403 h1:hoCEhoSD+4Hvg3xdbfbVleJXhyHqP/1jUa1QqexE1UQ= -github.com/gravestench/akara v0.0.0-20201119221449-924b47999403/go.mod h1:fTeda1SogMg5Lkd4lXMEd/Pk/a5/gQuLGaAI2rn1PBQ= -github.com/gravestench/akara v0.0.0-20201122210148-a1ee8ea83994 h1:Wp+4kZ0Pkap2ueAkTrE22rk++3VZE8TsU1bewpnzmsM= -github.com/gravestench/akara v0.0.0-20201122210148-a1ee8ea83994/go.mod h1:fTeda1SogMg5Lkd4lXMEd/Pk/a5/gQuLGaAI2rn1PBQ= github.com/gravestench/akara v0.0.0-20201128054238-892de9d70d6b h1:Ngfdn7O3wXQBzbOLsL6vQ9G4F7utUiKjQqKnwHbY5uI= github.com/gravestench/akara v0.0.0-20201128054238-892de9d70d6b/go.mod h1:fTeda1SogMg5Lkd4lXMEd/Pk/a5/gQuLGaAI2rn1PBQ= github.com/hajimehoshi/bitmapfont/v2 v2.1.0/go.mod h1:2BnYrkTQGThpr/CY6LorYtt/zEPNzvE/ND69CRTaHMs=