DEADSOFTWARE

Add joystick touch controls
authorfredboy <fredboy@protonmail.com>
Wed, 8 May 2024 17:28:16 +0000 (00:28 +0700)
committerfredboy <fredboy@protonmail.com>
Wed, 8 May 2024 17:28:16 +0000 (00:28 +0700)
22 files changed:
android/assets/joy_background.png [new file with mode: 0644]
android/assets/joy_stick.png [new file with mode: 0644]
android/assets/json/touch_buttons.json
core/src/ru/deadsoftware/cavedroid/MainConfig.java
core/src/ru/deadsoftware/cavedroid/game/GameRenderer.java
core/src/ru/deadsoftware/cavedroid/game/input/Joystick.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/MouseInputHandlersModule.kt
core/src/ru/deadsoftware/cavedroid/game/input/action/keys/MouseInputActionKey.kt
core/src/ru/deadsoftware/cavedroid/game/input/handler/keyboard/MoveCursorControlsModeKeyboardInputHandler.kt
core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/CloseGameWindowMouseInputHandler.kt
core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/CreativeInventoryScrollMouseInputHandler.kt
core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/CursorMouseInputHandler.kt
core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/HotbarMouseInputHandler.kt
core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectCraftingInventoryItemMouseInputHandler.kt
core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectCreativeInventoryItemMouseInputHandler.kt
core/src/ru/deadsoftware/cavedroid/game/input/handler/mouse/SelectSurvivalInventoryItemMouseInputHandler.kt
core/src/ru/deadsoftware/cavedroid/game/input/handler/touch/JoystickInputHandler.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/input/mapper/MouseInputActionMapper.kt
core/src/ru/deadsoftware/cavedroid/game/mobs/Mob.java
core/src/ru/deadsoftware/cavedroid/game/mobs/player/Player.java
core/src/ru/deadsoftware/cavedroid/game/render/TouchControlsRenderer.kt
core/src/ru/deadsoftware/cavedroid/misc/Assets.java

diff --git a/android/assets/joy_background.png b/android/assets/joy_background.png
new file mode 100644 (file)
index 0000000..b1ea80b
Binary files /dev/null and b/android/assets/joy_background.png differ
diff --git a/android/assets/joy_stick.png b/android/assets/joy_stick.png
new file mode 100644 (file)
index 0000000..0d75f67
Binary files /dev/null and b/android/assets/joy_stick.png differ
index cddaf534397ea531931a299d935742d52e4b3b82..4d8330104f285aa557d4d7c5065d287688e19bd7 100644 (file)
@@ -1,53 +1,9 @@
 {
-  "up": {
-    "x": 26,
-    "y": -52,
-    "w": 26,
-    "h": 26,
-    "key": "W"
-  },
-  "down": {
-    "x": 26,
-    "y": -26,
-    "w": 26,
-    "h": 26,
-    "key": "S"
-  },
-  "left": {
-    "x": 0,
-    "y": -26,
-    "w": 26,
-    "h": 26,
-    "key": "A"
-  },
-  "right": {
-    "x": 52,
-    "y": -26,
-    "w": 26,
-    "h": 26,
-    "key": "D"
-  },
   "alt": {
-    "x": 78,
-    "y": -26,
-    "w": 26,
-    "h": 26,
+    "x": -32,
+    "y": -32,
+    "w": 32,
+    "h": 32,
     "key": "L-Alt"
-  },
-  "lmb": {
-    "x": -52,
-    "y": -26,
-    "w": 26,
-    "h": 26,
-    "mouse": true,
-    "key": "Left"
-  },
-  "rmb": {
-    "x": -26,
-    "y": -26,
-    "w": 26,
-    "h": 26,
-    "mouse": true,
-    "key": "Right"
   }
 }
\ No newline at end of file
index 6577f297254f82928b2e18d28f29911d32764750..0219655c9c83f109ec6f862b542517b2ff6d9b50 100644 (file)
@@ -1,6 +1,7 @@
 package ru.deadsoftware.cavedroid;
 
 import ru.deadsoftware.cavedroid.game.GameUiWindow;
+import ru.deadsoftware.cavedroid.game.input.Joystick;
 
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
@@ -15,6 +16,9 @@ public class MainConfig {
     @CheckForNull
     private MainComponent mMainComponent;
 
+    @CheckForNull
+    private Joystick mJoystick;
+
     private GameUiWindow mGameUiWindow;
     private String mGameFolder;
 
@@ -113,4 +117,13 @@ public class MainConfig {
     public void setAssetsPackPath(@Nullable String assetsPackPath) {
         mAssetsPackPath = assetsPackPath;
     }
+
+    @CheckForNull
+    public Joystick getJoystick() {
+        return mJoystick;
+    }
+
+    public void setJoystick(@CheckForNull Joystick joystick) {
+        mJoystick = joystick;
+    }
 }
index 4b418a7b566ac26b03c72a926c8b382ecc3c69db..423469372609e4c3f53321bf2b00d0f37974519a 100644 (file)
@@ -1,15 +1,16 @@
 package ru.deadsoftware.cavedroid.game;
 
 import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.Input;
 import com.badlogic.gdx.graphics.Color;
 import com.badlogic.gdx.graphics.GL20;
 import com.badlogic.gdx.math.Rectangle;
 import com.badlogic.gdx.scenes.scene2d.ui.Label;
 import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
-import com.badlogic.gdx.utils.Align;
 import com.badlogic.gdx.utils.ObjectMap;
 import ru.deadsoftware.cavedroid.MainConfig;
 import ru.deadsoftware.cavedroid.game.input.IGameInputHandler;
+import ru.deadsoftware.cavedroid.game.input.Joystick;
 import ru.deadsoftware.cavedroid.game.input.action.KeyboardInputAction;
 import ru.deadsoftware.cavedroid.game.input.action.MouseInputAction;
 import ru.deadsoftware.cavedroid.game.input.action.keys.MouseInputActionKey;
@@ -24,6 +25,8 @@ import ru.deadsoftware.cavedroid.game.ui.TooltipManager;
 import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager;
 import ru.deadsoftware.cavedroid.misc.Assets;
 import ru.deadsoftware.cavedroid.misc.Renderer;
+import ru.deadsoftware.cavedroid.misc.utils.RenderingUtilsKt;
+import ru.deadsoftware.cavedroid.misc.utils.SpriteUtilsKt;
 
 import javax.annotation.CheckForNull;
 import javax.inject.Inject;
@@ -35,6 +38,7 @@ import java.util.Set;
 @GameScope
 public class GameRenderer extends Renderer {
 
+    private static final float DRAG_THRESHOLD = 1f;
     private static final TouchButton nullButton = new TouchButton(null, -1, true);
 
     private final MainConfig mMainConfig;
@@ -48,6 +52,8 @@ public class GameRenderer extends Renderer {
     private final GameWindowsManager mGameWindowsManager;
     private final TooltipManager mTooltipManager;
 
+    private final TouchButton mouseLeftTouchButton, mouseRightTouchButton;
+
     @Inject
     GameRenderer(MainConfig mainConfig,
                  MobsController mobsController,
@@ -73,6 +79,11 @@ public class GameRenderer extends Renderer {
         mGameWindowsManager = gameWindowsManager;
         mTooltipManager = tooltipManager;
 
+        mouseLeftTouchButton = new TouchButton(new Rectangle(getWidth() / 2, 0f, getWidth() / 2, getHeight() / 2), Input.Buttons.LEFT, true);
+        mouseRightTouchButton = new TouchButton(new Rectangle(getWidth() / 2, getHeight() / 2, getWidth() / 2, getHeight() / 2), Input.Buttons.RIGHT, true);
+
+        mMainConfig.setJoystick(new Joystick(mMobsController.getPlayer().getSpeed()));
+
         Gdx.gl.glClearColor(0f, .6f, .6f, 1f);
     }
 
@@ -107,14 +118,8 @@ public class GameRenderer extends Renderer {
         mCursorMouseInputHandler.handle(action);
 
         if (!mTooltipManager.getCurrentMouseTooltip().isEmpty()) {
-            final Label.LabelStyle style = new Label.LabelStyle(Assets.minecraftFont, Color.WHITE);
-            style.background = new TextureRegionDrawable(Assets.textureRegions.get("background"));
-            final Label label = new Label(mTooltipManager.getCurrentMouseTooltip(), style);
-            label.setX(screenX);
-            label.setY(screenY);
-//            label.setHeight(10f);
-//            label.setAlignment(Align.left, Align.top);
-            label.draw(spriter, 1f);
+            RenderingUtilsKt.drawString(spriter, mTooltipManager.getCurrentMouseTooltip(), screenX + 1, screenY + 1, Color.BLACK);
+            RenderingUtilsKt.drawString(spriter, mTooltipManager.getCurrentMouseTooltip(), screenX, screenY, Color.WHITE);
         }
     }
 
@@ -137,9 +142,9 @@ public class GameRenderer extends Renderer {
         return anyProcessed;
     }
 
-    private boolean onMouseActionEvent(int mouseX, int mouseY, int button, boolean touchUp) {
+    private boolean onMouseActionEvent(int mouseX, int mouseY, int button, boolean touchUp, int pointer) {
         @CheckForNull MouseInputAction action = mMouseInputActionMapper
-                .map((float) mouseX, (float) mouseY, getCameraViewport(), button, touchUp);
+                .map((float) mouseX, (float) mouseY, getCameraViewport(), button, touchUp, pointer);
         return handleMouseAction(action);
     }
 
@@ -151,13 +156,13 @@ public class GameRenderer extends Renderer {
         if (mMainConfig.isTouch()) {
             TouchButton touchedKey = getTouchedKey(touchX, touchY);
             if (touchedKey.isMouse()) {
-                return onMouseActionEvent(screenX, screenY, touchedKey.getCode(), true);
+                return onMouseActionEvent(screenX, screenY, touchedKey.getCode(), true, pointer);
             } else {
                 return keyUp(touchedKey.getCode());
             }
         }
 
-        return onMouseActionEvent(screenX, screenY, button, true);
+        return onMouseActionEvent(screenX, screenY, button, true, pointer);
     }
 
     private TouchButton getTouchedKey(float touchX, float touchY) {
@@ -170,6 +175,15 @@ public class GameRenderer extends Renderer {
                 return button;
             }
         }
+
+        if (mouseLeftTouchButton.getRect().contains(touchX, touchY)) {
+            return mouseLeftTouchButton;
+        }
+
+        if (mouseRightTouchButton.getRect().contains(touchX, touchY)) {
+            return mouseRightTouchButton;
+        }
+
         return nullButton;
     }
 
@@ -184,13 +198,13 @@ public class GameRenderer extends Renderer {
         if (mMainConfig.isTouch()) {
             TouchButton touchedKey = getTouchedKey(touchX, touchY);
             if (touchedKey.isMouse()) {
-                return onMouseActionEvent(screenX, screenY, touchedKey.getCode(), false);
+                return onMouseActionEvent(screenX, screenY, touchedKey.getCode(), false, pointer);
             } else {
                 return keyDown(touchedKey.getCode());
             }
         }
 
-        return onMouseActionEvent(screenX, screenY, button, false);
+        return onMouseActionEvent(screenX, screenY, button, false, pointer);
     }
 
     @Override
@@ -198,12 +212,12 @@ public class GameRenderer extends Renderer {
         float touchX = transformScreenX(screenX);
         float touchY = transformScreenY(screenY);
 
-        if (Math.abs(touchX - mTouchDownX) < 16 && Math.abs(touchY - mTouchDownY) < 16) {
+        if (Math.abs(touchX - mTouchDownX) < 16 && Math.abs(touchY - mTouchDownY) < DRAG_THRESHOLD) {
             return false;
         }
 
         @CheckForNull MouseInputAction action =
-                mMouseInputActionMapper.mapDragged(screenX, screenY, getCameraViewport());
+                mMouseInputActionMapper.mapDragged(screenX, screenY, getCameraViewport(), pointer);
         return handleMouseAction(action);
     }
 
@@ -250,6 +264,13 @@ public class GameRenderer extends Renderer {
     public void render(float delta) {
         updateCameraPosition();
 
+        if (mMainConfig.getJoystick() != null && mMainConfig.getJoystick().getActive()) {
+            mMainConfig.getJoystick().updateState(
+                    transformScreenX(Gdx.input.getX(mMainConfig.getJoystick().getPointer())),
+                    transformScreenY(Gdx.input.getY(mMainConfig.getJoystick().getPointer()))
+            );
+        }
+
         Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
 
         spriter.begin();
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/Joystick.kt b/core/src/ru/deadsoftware/cavedroid/game/input/Joystick.kt
new file mode 100644 (file)
index 0000000..ef4ead1
--- /dev/null
@@ -0,0 +1,74 @@
+package ru.deadsoftware.cavedroid.game.input
+
+import com.badlogic.gdx.math.Vector2
+import com.badlogic.gdx.utils.TimeUtils
+
+class Joystick(
+    private val value: Float,
+) {
+
+    var active = false
+        private set
+    var centerX = 0f
+        private set
+    var centerY = 0f
+        private set
+
+    var activeX = 0f
+        private set
+    var activeY = 0f
+        private set
+
+    var pointer = 0
+        private set
+
+    private val stickVector = Vector2()
+
+    private var activateTimeMs = 0L
+
+    fun activate(touchX: Float, touchY: Float, pointer: Int) {
+        active = true
+        centerX = touchX
+        centerY = touchY
+        activateTimeMs = TimeUtils.millis()
+        this.pointer = pointer
+    }
+
+    fun deactivate() {
+        active = false
+    }
+
+    fun getVelocityVector(): Vector2 {
+        if (!active) {
+            return Vector2.Zero
+        }
+        println(stickVector)
+        return Vector2(
+            stickVector.x * value,
+            stickVector.y * value
+        )
+    }
+
+    fun updateState(touchX: Float, touchY: Float) {
+        if (!active) {
+            return
+        }
+
+        stickVector.x = touchX - centerX
+        stickVector.y = touchY - centerY
+        stickVector.clamp(0f, RADIUS)
+
+        activeX = centerX + stickVector.x
+        activeY = centerY + stickVector.y
+
+        stickVector.x /= RADIUS
+        stickVector.y /= RADIUS
+    }
+
+    companion object {
+        const val RADIUS = 24f
+        const val SIZE = RADIUS * 2
+        const val STICK_SIZE = 16f
+    }
+
+}
\ No newline at end of file
index 937b3e68d921e6d4dfd831c23dd7ca27439e26b3..3d5f32ac0c08b5632c2ce93677c2c1b6060110a0 100644 (file)
@@ -5,6 +5,7 @@ import dagger.Module
 import dagger.multibindings.IntoSet
 import ru.deadsoftware.cavedroid.game.GameScope
 import ru.deadsoftware.cavedroid.game.input.action.MouseInputAction
+import ru.deadsoftware.cavedroid.game.input.handler.touch.JoystickInputHandler
 import ru.deadsoftware.cavedroid.game.input.handler.mouse.*
 
 @Module
@@ -72,4 +73,11 @@ object MouseInputHandlersModule {
     fun bindSelectCraftingInventoryItemMouseInputHandler(handler: SelectCraftingInventoryItemMouseInputHandler): IGameInputHandler<MouseInputAction> {
         return handler
     }
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindJoystickInputHandler(handler: JoystickInputHandler): IGameInputHandler<MouseInputAction> {
+        return handler
+    }
 }
\ No newline at end of file
index 58227603ae8b08f2b295fbf14f89b0e8adc05ffa..3b2744f7da431bea7f8a254782ec4cf4fc47dbac 100644 (file)
@@ -4,12 +4,18 @@ sealed interface MouseInputActionKey {
 
     val touchUp: Boolean
 
+    sealed interface Touch : MouseInputActionKey {
+        val pointer: Int
+    }
+
     data object None : MouseInputActionKey {
         override val touchUp: Boolean
             get() = throw IllegalAccessException("not applicable for mouse move action")
     }
 
-    data object Dragged : MouseInputActionKey {
+    data class Dragged(
+        override val pointer: Int
+    ) : Touch {
         override val touchUp: Boolean
             get() = throw IllegalAccessException("not applicable for mouse dragged action")
     }
@@ -26,9 +32,10 @@ sealed interface MouseInputActionKey {
         override val touchUp: Boolean
     ) : MouseInputActionKey
 
-    data class Touch(
-        override val touchUp: Boolean
-    ) : MouseInputActionKey
+    data class Screen(
+        override val touchUp: Boolean,
+        override val pointer: Int,
+    ) : Touch
 
     data class Scroll(
         val amountX: Float,
index e6d00536239b1d7d03d369dbbc7967399a6becdf..0cb74d7d0ff20bd0d01ceafa873b46f5bdea863c 100644 (file)
@@ -1,6 +1,5 @@
 package ru.deadsoftware.cavedroid.game.input.handler.keyboard
 
-import com.badlogic.gdx.math.MathUtils
 import ru.deadsoftware.cavedroid.MainConfig
 import ru.deadsoftware.cavedroid.game.GameScope
 import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
@@ -27,21 +26,6 @@ class MoveCursorControlsModeKeyboardInputHandler @Inject constructor(
                         action.actionKey is KeyboardInputActionKey.Down)
     }
 
-    private fun checkCursorBounds() {
-        val player = mobsController.player
-        if (player.gameMode == 0) {
-            val minCursorX = player.mapX - SURVIVAL_CURSOR_RANGE
-            val maxCursorX = player.mapX + SURVIVAL_CURSOR_RANGE
-            val minCursorY = player.middleMapY - SURVIVAL_CURSOR_RANGE
-            val maxCursorY = player.middleMapY + SURVIVAL_CURSOR_RANGE
-
-            player.cursorX = MathUtils.clamp(player.cursorX, minCursorX, maxCursorX)
-            player.cursorY = MathUtils.clamp(player.cursorY, minCursorY, maxCursorY)
-        }
-
-        player.cursorY = MathUtils.clamp(player.cursorY, 0, gameWorld.height - 1)
-    }
-
     override fun handle(action: KeyboardInputAction) {
         val player = mobsController.player
 
@@ -53,7 +37,7 @@ class MoveCursorControlsModeKeyboardInputHandler @Inject constructor(
             else -> return
         }
 
-        checkCursorBounds()
+        player.checkCursorBounds(gameWorld);
     }
 
     companion object {
index bee22dc621d77b89b2029dbfec40eb6b05935cdc..8a7fdb46ff4559af473fb7dc1a40bcbc287c7d6d 100644 (file)
@@ -26,7 +26,7 @@ class CloseGameWindowMouseInputHandler @Inject constructor(
 
     override fun checkConditions(action: MouseInputAction): Boolean {
         return gameWindowsManager.getCurrentWindow() != GameUiWindow.NONE &&
-                (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Touch) &&
+                (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Screen) &&
                 !action.actionKey.touchUp &&
                 !isInsideWindow(action, getCurrentWindowTexture())
     }
index 352d04bcb5fba0e98d439af2da68edcc5e4f0a0c..11f76e9a691fd8a34c2086fdc21c4023ab592481 100644 (file)
@@ -34,12 +34,12 @@ class CreativeInventoryScrollMouseInputHandler @Inject constructor(
     }
 
     private fun checkStartDragConditions(action: MouseInputAction): Boolean {
-        return (action.actionKey is MouseInputActionKey.Touch) &&
+        return (action.actionKey is MouseInputActionKey.Screen) &&
                 !action.actionKey.touchUp && !gameWindowsManager.isDragging
     }
 
     private fun checkEndDragConditions(action: MouseInputAction): Boolean {
-        return action.actionKey is MouseInputActionKey.Touch &&
+        return action.actionKey is MouseInputActionKey.Screen &&
                 action.actionKey.touchUp && gameWindowsManager.isDragging
     }
 
@@ -75,7 +75,7 @@ class CreativeInventoryScrollMouseInputHandler @Inject constructor(
 
     override fun handle(action: MouseInputAction) {
         when (action.actionKey) {
-            is MouseInputActionKey.Touch -> handleStartOrEndDrag(action)
+            is MouseInputActionKey.Screen -> handleStartOrEndDrag(action)
             is MouseInputActionKey.Dragged -> handleDrag(action)
             is MouseInputActionKey.Scroll -> handleScroll(action)
             else -> return
index 6a4b5a4eb968101d134eeeefa7aa9b9e290b51bd..9093680c299d7f46d68acf79c8236eba49a0b843 100644 (file)
@@ -41,20 +41,6 @@ class CursorMouseInputHandler @Inject constructor(
     private fun GameWorld.isCurrentBlockAutoselectable() =
         getForeMap(player.cursorX, player.cursorY).isAutoselectable
 
-    private fun checkCursorBounds() {
-        if (player.gameMode == 0) {
-            val minCursorX = player.mapX - SURVIVAL_CURSOR_RANGE
-            val maxCursorX = player.mapX + SURVIVAL_CURSOR_RANGE
-            val minCursorY = player.middleMapY - SURVIVAL_CURSOR_RANGE
-            val maxCursorY = player.middleMapY + SURVIVAL_CURSOR_RANGE
-
-            player.cursorX = MathUtils.clamp(player.cursorX, minCursorX, maxCursorX)
-            player.cursorY = MathUtils.clamp(player.cursorY, minCursorY, maxCursorY)
-        }
-
-        player.cursorY = MathUtils.clamp(player.cursorY, 0, gameWorld.height - 1)
-    }
-
     private fun setPlayerDirectionToCursor() {
         if (player.controlMode != Player.ControlMode.CURSOR) {
             return
@@ -137,7 +123,7 @@ class CursorMouseInputHandler @Inject constructor(
             !mainConfig.isTouch -> handleMouse(action)
         }
 
-        checkCursorBounds()
+        player.checkCursorBounds(gameWorld)
         setPlayerDirectionToCursor()
 
         if (player.cursorX != pastCursorX || player.cursorY != pastCursorY) {
index 0725abee6fc190dbbc230a82d1e7f1407790abc4..92f6a5a1a8ed4368393809cad2999a9688162363 100644 (file)
@@ -25,7 +25,7 @@ class HotbarMouseInputHandler @Inject constructor(
 
     override fun checkConditions(action: MouseInputAction): Boolean {
         return buttonHoldTask?.isScheduled == true ||
-                ((action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Touch)
+                ((action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Screen)
                         && isInsideHotbar(action)
                         || action.actionKey is MouseInputActionKey.Scroll) &&
                 gameWindowsManager.getCurrentWindow() == GameUiWindow.NONE
@@ -75,7 +75,7 @@ class HotbarMouseInputHandler @Inject constructor(
             cancelHold()
         }
 
-        if (action.actionKey !is MouseInputActionKey.Left && action.actionKey !is MouseInputActionKey.Touch ) {
+        if (action.actionKey !is MouseInputActionKey.Left && action.actionKey !is MouseInputActionKey.Screen ) {
             if (action.actionKey is MouseInputActionKey.Scroll) {
                 handleScroll(action)
             }
index 89c0ce1061910b1378a8cf1a0573b647a7816682..489815029f5d72da169c5a62c52a256dca6e7990 100644 (file)
@@ -28,7 +28,7 @@ class SelectCraftingInventoryItemMouseInputHandler @Inject constructor(
     override fun checkConditions(action: MouseInputAction): Boolean {
         return gameWindowsManager.getCurrentWindow() == GameUiWindow.CRAFTING_TABLE &&
                 isInsideWindow(action, survivalWindowTexture) &&
-                (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Right || action.actionKey is MouseInputActionKey.Touch)
+                (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Right || action.actionKey is MouseInputActionKey.Screen)
                 && action.actionKey.touchUp
     }
 
@@ -73,7 +73,7 @@ class SelectCraftingInventoryItemMouseInputHandler @Inject constructor(
             itemIndex -= 36
         }
 
-        if (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Touch) {
+        if (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Screen) {
             onLeftCLick(mobsController.player.inventory.items as MutableList<InventoryItem?>, window, itemIndex)
         } else {
             onRightClick(mobsController.player.inventory.items as MutableList<InventoryItem?>, window, itemIndex)
@@ -89,7 +89,7 @@ class SelectCraftingInventoryItemMouseInputHandler @Inject constructor(
         val window = gameWindowsManager.currentWindow as CraftingInventoryWindow
         val index = xOnCraft + yOnCraft * GameWindowsConfigs.Crafting.craftGridSize
 
-        if (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Touch) {
+        if (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Screen) {
             onLeftCLick(window.craftingItems, window, index)
         } else {
             onRightClick(window.craftingItems, window, index)
index 9ccbf6eac8fde6cddf301ee9e09aefec3918ea07..272a17874b69897af5f4b3376131cdc61cfca5ea 100644 (file)
@@ -25,7 +25,7 @@ class SelectCreativeInventoryItemMouseInputHandler @Inject constructor(
     override fun checkConditions(action: MouseInputAction): Boolean {
         return gameWindowsManager.getCurrentWindow() == GameUiWindow.CREATIVE_INVENTORY &&
                 !gameWindowsManager.isDragging &&
-                (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Touch) &&
+                (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Screen) &&
                 action.actionKey.touchUp && isInsideWindow(action, creativeInventoryTexture)
     }
 
index ab65fb8c05d888d644bb63fcc618b46b453588d6..0d0a64771f6cf6f7097d2cd54316ff850075b448 100644 (file)
@@ -28,7 +28,7 @@ class SelectSurvivalInventoryItemMouseInputHandler @Inject constructor(
     override fun checkConditions(action: MouseInputAction): Boolean {
         return gameWindowsManager.getCurrentWindow() == GameUiWindow.SURVIVAL_INVENTORY &&
                 isInsideWindow(action, survivalWindowTexture) &&
-                (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Right || action.actionKey is MouseInputActionKey.Touch)
+                (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Right || action.actionKey is MouseInputActionKey.Screen)
                 && action.actionKey.touchUp
     }
 
@@ -73,7 +73,7 @@ class SelectSurvivalInventoryItemMouseInputHandler @Inject constructor(
             itemIndex -= 36
         }
 
-        if (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Touch) {
+        if (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Screen) {
             onLeftCLick(mobsController.player.inventory.items as MutableList<InventoryItem?>, window, itemIndex)
         } else {
             onRightClick(mobsController.player.inventory.items as MutableList<InventoryItem?>, window, itemIndex)
@@ -89,7 +89,7 @@ class SelectSurvivalInventoryItemMouseInputHandler @Inject constructor(
         val window = gameWindowsManager.currentWindow as SurvivalInventoryWindow
         val index = xOnCraft + yOnCraft * GameWindowsConfigs.Crafting.craftGridSize // this is crafting on purpose!!
 
-        if (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Touch) {
+        if (action.actionKey is MouseInputActionKey.Left || action.actionKey is MouseInputActionKey.Screen) {
             onLeftCLick(window.craftingItems, window, index)
         } else {
             onRightClick(window.craftingItems, window, index)
diff --git a/core/src/ru/deadsoftware/cavedroid/game/input/handler/touch/JoystickInputHandler.kt b/core/src/ru/deadsoftware/cavedroid/game/input/handler/touch/JoystickInputHandler.kt
new file mode 100644 (file)
index 0000000..c175f31
--- /dev/null
@@ -0,0 +1,134 @@
+package ru.deadsoftware.cavedroid.game.input.handler.touch
+
+import com.badlogic.gdx.math.Vector2
+import com.badlogic.gdx.utils.TimeUtils
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.input.IGameInputHandler
+import ru.deadsoftware.cavedroid.game.input.Joystick
+import ru.deadsoftware.cavedroid.game.input.action.MouseInputAction
+import ru.deadsoftware.cavedroid.game.input.action.keys.MouseInputActionKey
+import ru.deadsoftware.cavedroid.game.input.isInsideHotbar
+import ru.deadsoftware.cavedroid.game.mobs.Mob
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.mobs.player.Player
+import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import javax.inject.Inject
+
+@GameScope
+class JoystickInputHandler @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val mobsController: MobsController,
+    private val gameWindowsManager: GameWindowsManager,
+    private val gameWorld: GameWorld,
+) : IGameInputHandler<MouseInputAction> {
+
+    private var activateTimeMs = 0L
+    private var cursorTimeoutMs = 100L
+
+    private var active = false
+        set(value) {
+            if (!value) {
+                resetVelocity()
+                if (TimeUtils.timeSinceMillis(activateTimeMs) < 100L &&
+                    mobsController.player.controlMode != Player.ControlMode.CURSOR) {
+                    mobsController.player.jump()
+                }
+            } else {
+                activateTimeMs = TimeUtils.millis()
+            }
+            field = value
+        }
+
+    private fun resetVelocity() {
+        mobsController.player.velocity.x = 0f
+
+        if (mobsController.player.isFlyMode) {
+            mobsController.player.velocity.y = 0f
+        }
+    }
+
+    override fun checkConditions(action: MouseInputAction): Boolean {
+        return gameWindowsManager.getCurrentWindow() == GameUiWindow.NONE &&
+                mainConfig.isTouch &&
+//                mobsController.player.controlMode == Player.ControlMode.WALK &&
+                mainConfig.joystick != null &&
+                (action.actionKey is MouseInputActionKey.Touch) &&
+                (action.actionKey.pointer == mainConfig.joystick?.pointer || !active) &&
+                ((action.actionKey is MouseInputActionKey.Dragged) ||
+                        (action.screenX < action.cameraViewport.width / 2 && !action.actionKey.touchUp || active)) &&
+                !(action.actionKey is MouseInputActionKey.Screen && isInsideHotbar(action))
+
+    }
+
+    private fun handleTouchDown(action: MouseInputAction) {
+        val key = action.actionKey as MouseInputActionKey.Screen
+        mainConfig.joystick?.activate(action.screenX, action.screenY, key.pointer) ?: return
+        active = true
+    }
+
+    private fun handleTouchUp(action: MouseInputAction) {
+        mainConfig.joystick?.deactivate()
+        active = false
+    }
+
+    private fun handleCursor() {
+        val joystick = mainConfig.joystick ?: return
+
+        if (TimeUtils.timeSinceMillis(cursorTimeoutMs) < 200L) {
+            return
+        }
+
+        if (Math.abs(joystick.activeX - joystick.centerX) >= Joystick.RADIUS / 2) {
+            mobsController.player.cursorX += if (joystick.activeX > joystick.centerX) 1 else -1
+            cursorTimeoutMs = TimeUtils.millis()
+        }
+
+        if (Math.abs(joystick.activeY - joystick.centerY) >= Joystick.RADIUS / 2) {
+            mobsController.player.cursorY += if (joystick.activeY > joystick.centerY) 1 else -1
+            cursorTimeoutMs = TimeUtils.millis()
+        }
+
+        mobsController.player.checkCursorBounds(gameWorld)
+    }
+
+    private fun handleDragged() {
+        if (mobsController.player.controlMode == Player.ControlMode.CURSOR) {
+            handleCursor()
+            return
+        }
+
+        val joystick = mainConfig.joystick ?: return
+        val joyVector = joystick.getVelocityVector()
+
+        mobsController.player.velocity.x = joyVector.x
+
+        mobsController.player.setDir(
+            if (joyVector.x < 0) {
+                Mob.Direction.LEFT
+            } else {
+                Mob.Direction.RIGHT
+            }
+        )
+
+        if (mobsController.player.isFlyMode) {
+            mobsController.player.velocity.y = joyVector.y
+        }
+    }
+
+    override fun handle(action: MouseInputAction) {
+        when (action.actionKey) {
+            is MouseInputActionKey.Dragged -> handleDragged()
+            else -> {
+                if (action.actionKey.touchUp) {
+                    handleTouchUp(action)
+                } else {
+                    handleTouchDown(action)
+                }
+            }
+        }
+    }
+
+}
\ No newline at end of file
index 254cee2f49f2cda6ccdc5d48ee34264ed00556e0..5ef450a3b700502f4b3a0b16cd7e95251c3bc53d 100644 (file)
@@ -19,9 +19,10 @@ class MouseInputActionMapper @Inject constructor(
         mouseY: Float,
         cameraViewport: Rectangle,
         button: Int,
-        touchUp: Boolean
+        touchUp: Boolean,
+        pointer: Int,
     ): MouseInputAction? {
-        val actionKey = mapActionKey(button, touchUp) ?: return null
+        val actionKey = mapActionKey(button, touchUp, pointer) ?: return null
 
         return MouseInputAction(
             screenX = getScreenX(mouseX),
@@ -35,11 +36,12 @@ class MouseInputActionMapper @Inject constructor(
         mouseX: Float,
         mouseY: Float,
         cameraViewport: Rectangle,
+        pointer: Int,
     ): MouseInputAction {
         return MouseInputAction(
             screenX = getScreenX(mouseX),
             screenY = getScreenY(mouseY),
-            actionKey = MouseInputActionKey.Dragged,
+            actionKey = MouseInputActionKey.Dragged(pointer),
             cameraViewport = cameraViewport,
         )
     }
@@ -59,12 +61,12 @@ class MouseInputActionMapper @Inject constructor(
         )
     }
 
-    private fun mapActionKey(button: Int, touchUp: Boolean): MouseInputActionKey? {
+    private fun mapActionKey(button: Int, touchUp: Boolean, pointer: Int): MouseInputActionKey? {
         return when (button) {
             Input.Buttons.LEFT -> MouseInputActionKey.Left(touchUp)
             Input.Buttons.RIGHT -> MouseInputActionKey.Right(touchUp)
             Input.Buttons.MIDDLE -> MouseInputActionKey.Middle(touchUp)
-            -1 -> MouseInputActionKey.Touch(touchUp)
+            -1 -> MouseInputActionKey.Screen(touchUp, pointer)
             else -> null
         }
     }
index 415ef30376503a491a3eefd9eb8c127d53089b77..fc9d61bbe778e37f1c1186fa1387f0be207fb8d6 100644 (file)
@@ -99,17 +99,21 @@ public abstract class Mob extends Rectangle implements Serializable {
     }
 
     protected final void updateAnimation(float delta) {
-        if (mVelocity.x != 0f || Math.abs(mAnim) > mAnimDelta * delta) {
-            mAnim += mAnimDelta * delta;
+        final float velocityMultiplier = (Math.abs(getVelocity().x) / getSpeed());
+        final float animMultiplier = (velocityMultiplier == 0f ? 1f : velocityMultiplier) * delta;
+        final float maxAnim = 60f * (velocityMultiplier == 0f ? 1f : velocityMultiplier);
+
+        if (mVelocity.x != 0f || Math.abs(mAnim) > mAnimDelta * animMultiplier) {
+            mAnim += mAnimDelta * animMultiplier;
         } else {
             mAnim = 0;
         }
 
-        if (mAnim > 60f) {
-            mAnim = 60f;
+        if (mAnim > maxAnim) {
+            mAnim = maxAnim;
             mAnimDelta = -ANIMATION_SPEED;
-        } else if (mAnim < -60f) {
-            mAnim = -60f;
+        } else if (mAnim < -maxAnim) {
+            mAnim = -maxAnim;
             mAnimDelta = ANIMATION_SPEED;
         }
 
index 241714a3579cc8bfb94e5d0fd39017cc0169ee4c..58ddc025c849feaa37d91a71a5607d5e4b1c04ad 100644 (file)
@@ -22,6 +22,7 @@ public class Player extends Mob {
 
     private static final float SPEED = 69.072f;
     private static final float JUMP_VELOCITY = -133.332f;
+    private static final int SURVIVAL_CURSOR_RANGE = 4;
 
     public static final int MAX_HEALTH = 20;
     public static final int INVENTORY_SIZE = 36;
@@ -126,6 +127,17 @@ public class Player extends Mob {
 
     @Override
     public void jump() {
+        if (!canJump()) {
+            if (gameMode == 1) {
+                if (isFlyMode()) {
+                    setFlyMode(false);
+                } else {
+                    getVelocity().y = 0f;
+                    setFlyMode(true);
+                }
+            }
+            return;
+        }
         mVelocity.y = JUMP_VELOCITY;
     }
 
@@ -234,6 +246,20 @@ public class Player extends Mob {
         super.heal(heal);
     }
 
+    public void checkCursorBounds(GameWorld gameWorld) {
+        if (gameMode == 0) {
+            int minCursorX = getMapX() - SURVIVAL_CURSOR_RANGE;
+            int maxCursorX = getMapX() + SURVIVAL_CURSOR_RANGE;
+            int minCursorY = getMiddleMapY() - SURVIVAL_CURSOR_RANGE;
+            int maxCursorY = getMiddleMapY() + SURVIVAL_CURSOR_RANGE;
+
+            cursorX = MathUtils.clamp(cursorX, minCursorX, maxCursorX);
+            cursorY = MathUtils.clamp(cursorY, minCursorY, maxCursorY);
+        }
+
+        cursorY = MathUtils.clamp(cursorY, 0, gameWorld.getHeight() - 1);
+    }
+
     private void drawItem(SpriteBatch spriteBatch, float x, float y, float anim) {
         final Item item = inventory.getActiveItem().getItem();
 
index ad68bce795a4721413fb373000a09ec1215243ce..63aa09fd74fce303e6d45b7d976ee6edab53c8ce 100644 (file)
@@ -6,12 +6,14 @@ import com.badlogic.gdx.math.Rectangle
 import ru.deadsoftware.cavedroid.MainConfig
 import ru.deadsoftware.cavedroid.game.GameScope
 import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.input.Joystick
 import ru.deadsoftware.cavedroid.game.mobs.MobsController
 import ru.deadsoftware.cavedroid.game.mobs.player.Player.ControlMode
 import ru.deadsoftware.cavedroid.game.ui.windows.GameWindowsManager
 import ru.deadsoftware.cavedroid.misc.Assets
 import ru.deadsoftware.cavedroid.misc.utils.ArrayMapExtensions.component1
 import ru.deadsoftware.cavedroid.misc.utils.ArrayMapExtensions.component2
+import ru.deadsoftware.cavedroid.misc.utils.drawSprite
 import javax.inject.Inject
 
 @GameScope
@@ -25,6 +27,26 @@ class TouchControlsRenderer @Inject constructor(
 
     private val shadeTexture get() = Assets.textureRegions[SHADE_KEY]
 
+    private fun drawJoystick(spriteBatch: SpriteBatch) {
+        val joystick = mainConfig.joystick?.takeIf { it.active } ?: return
+
+        spriteBatch.drawSprite(
+            sprite = Assets.joyBackground,
+            x = joystick.centerX - Joystick.RADIUS,
+            y = joystick.centerY - Joystick.RADIUS,
+            width = Joystick.SIZE,
+            height = Joystick.SIZE
+        )
+
+        spriteBatch.drawSprite(
+            sprite = Assets.joyStick,
+            x = joystick.activeX - Joystick.STICK_SIZE / 2,
+            y = joystick.activeY - Joystick.STICK_SIZE / 2,
+            width = Joystick.STICK_SIZE,
+            height = Joystick.STICK_SIZE
+        )
+    }
+
     override fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle, delta: Float) {
         if (!mainConfig.isTouch || gameWindowsManager.getCurrentWindow() != GameUiWindow.NONE) {
             return
@@ -48,6 +70,8 @@ class TouchControlsRenderer @Inject constructor(
             val altKeyRect = touchControlsMap.get("alt").rect
             spriteBatch.draw(shadeTexture, altKeyRect.x, altKeyRect.y, altKeyRect.width, altKeyRect.height)
         }
+
+        drawJoystick(spriteBatch)
     }
 
     companion object {
index 029c59f362407e74cdd0c138a9ac03ca2880fc10..329207f677299a79d8430eb68e1aab9bc63a0a1c 100644 (file)
@@ -41,6 +41,9 @@ public class Assets {
     public static Map<String, Texture> blockTextures = new HashMap<>();
     public static Map<String, Texture> itemTextures = new HashMap<>();
 
+    public static Sprite joyBackground;
+    public static Sprite joyStick;
+
     public static void dispose() {
         minecraftFont.dispose();
         loadedTextures.forEach(Texture::dispose);
@@ -188,6 +191,11 @@ public class Assets {
         loadAllPngsFromDirInto(blocksDir, blockTextures);
     }
 
+    private static void loadJoystick(AssetLoader assetLoader) {
+        joyStick = new Sprite(loadTexture(assetLoader.getAssetHandle("joy_stick.png")));
+        joyBackground = new Sprite(loadTexture(assetLoader.getAssetHandle("joy_background.png")));
+    }
+
     public static void load(final AssetLoader assetLoader) {
         loadMob(assetLoader, playerSprite, "char");
         loadMob(assetLoader, pigSprite, "pig");
@@ -196,6 +204,7 @@ public class Assets {
         loadBlocks(assetLoader);
         loadItems(assetLoader);
         loadTouchButtonsFromJSON(assetLoader);
+        loadJoystick(assetLoader);
         setPlayerHeadOrigin();
         minecraftFont = new BitmapFont(assetLoader.getAssetHandle("font.fnt"), true);
         minecraftFont.getData().setScale(.375f);