mirror of
https://github.com/izzy2lost/Super3.git
synced 2026-07-05 15:18:38 -07:00
added virtual joystick and buttons for games that didn't have controls
This commit is contained in:
@@ -165,6 +165,16 @@ void AndroidInputSystem::ApplyConfig(const Util::Config::Node& config)
|
||||
m_touchShift3.a = keySc(get("InputGearShift3", "KEY_9"), SDL_SCANCODE_9);
|
||||
m_touchShift4.a = keySc(get("InputGearShift4", "KEY_0"), SDL_SCANCODE_0);
|
||||
m_touchShiftN.a = keySc(get("InputGearShiftN", "KEY_6"), SDL_SCANCODE_6);
|
||||
|
||||
m_touchPunch.a = keySc(get("InputPunch", "KEY_A"), SDL_SCANCODE_A);
|
||||
m_touchKick.a = keySc(get("InputKick", "KEY_S"), SDL_SCANCODE_S);
|
||||
m_touchGuard.a = keySc(get("InputGuard", "KEY_D"), SDL_SCANCODE_D);
|
||||
m_touchEscape.a = keySc(get("InputEscape", "KEY_F"), SDL_SCANCODE_F);
|
||||
|
||||
m_touchSpikeShift.a = keySc(get("InputShift", "KEY_A"), SDL_SCANCODE_A);
|
||||
m_touchSpikeBeat.a = keySc(get("InputBeat", "KEY_S"), SDL_SCANCODE_S);
|
||||
m_touchSpikeCharge.a = keySc(get("InputCharge", "KEY_D"), SDL_SCANCODE_D);
|
||||
m_touchSpikeJump.a = keySc(get("InputJump", "KEY_F"), SDL_SCANCODE_F);
|
||||
}
|
||||
|
||||
bool AndroidInputSystem::InitializeSystem()
|
||||
@@ -319,6 +329,88 @@ void AndroidInputSystem::HandleTouch(const SDL_TouchFingerEvent& tf, bool down)
|
||||
const float x = tf.x;
|
||||
const float y = tf.y;
|
||||
|
||||
// Fighting game action buttons (fingerId encoded from Java).
|
||||
switch (tf.fingerId)
|
||||
{
|
||||
case 1110: SetKeys(m_touchPunch, down); return;
|
||||
case 1111: SetKeys(m_touchKick, down); return;
|
||||
case 1112: SetKeys(m_touchGuard, down); return;
|
||||
case 1113: SetKeys(m_touchEscape, down); return;
|
||||
case 1115: SetKeys(m_touchSpikeShift, down); return;
|
||||
case 1116: SetKeys(m_touchSpikeBeat, down); return;
|
||||
case 1117: SetKeys(m_touchSpikeCharge, down); return;
|
||||
case 1118: SetKeys(m_touchSpikeJump, down); return;
|
||||
default: break;
|
||||
}
|
||||
|
||||
// Virtual fighting stick: 8-way directional based on encoded x/y in [0..1], keyed by a fixed fingerId.
|
||||
if (tf.fingerId == 1114)
|
||||
{
|
||||
auto releaseHeld = [&]() {
|
||||
auto it = m_fingerHeldDir.find(tf.fingerId);
|
||||
if (it != m_fingerHeldDir.end())
|
||||
{
|
||||
SetKeys(it->second.primary, false);
|
||||
SetKeys(it->second.secondary, false);
|
||||
m_fingerHeldDir.erase(it);
|
||||
}
|
||||
};
|
||||
|
||||
if (!down)
|
||||
{
|
||||
releaseHeld();
|
||||
return;
|
||||
}
|
||||
|
||||
const float sx = std::clamp((x - 0.5f) * 2.0f, -1.0f, 1.0f);
|
||||
const float sy = std::clamp((y - 0.5f) * 2.0f, -1.0f, 1.0f);
|
||||
constexpr float deadzone = 0.25f;
|
||||
if (std::abs(sx) < deadzone && std::abs(sy) < deadzone)
|
||||
{
|
||||
releaseHeld();
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr float kPi = 3.14159265358979323846f;
|
||||
float angle = std::atan2(-sy, sx); // y is down in normalized coords; invert for math-y up.
|
||||
if (angle < 0.0f)
|
||||
angle += 2.0f * kPi;
|
||||
|
||||
const int oct = (int)std::floor((angle + (kPi / 8.0f)) / (kPi / 4.0f)) & 7;
|
||||
DualScancode h{};
|
||||
DualScancode v{};
|
||||
switch (oct)
|
||||
{
|
||||
case 0: h = m_touchJoyRight; break;
|
||||
case 1: h = m_touchJoyRight; v = m_touchJoyUp; break;
|
||||
case 2: v = m_touchJoyUp; break;
|
||||
case 3: h = m_touchJoyLeft; v = m_touchJoyUp; break;
|
||||
case 4: h = m_touchJoyLeft; break;
|
||||
case 5: h = m_touchJoyLeft; v = m_touchJoyDown; break;
|
||||
case 6: v = m_touchJoyDown; break;
|
||||
case 7: h = m_touchJoyRight; v = m_touchJoyDown; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
releaseHeld();
|
||||
HeldDirKeys held{};
|
||||
if (h.a != SDL_SCANCODE_UNKNOWN || h.b != SDL_SCANCODE_UNKNOWN)
|
||||
held.primary = h;
|
||||
if (v.a != SDL_SCANCODE_UNKNOWN || v.b != SDL_SCANCODE_UNKNOWN)
|
||||
{
|
||||
if (held.primary.a == SDL_SCANCODE_UNKNOWN && held.primary.b == SDL_SCANCODE_UNKNOWN)
|
||||
held.primary = v;
|
||||
else
|
||||
held.secondary = v;
|
||||
}
|
||||
if (held.primary.a != SDL_SCANCODE_UNKNOWN || held.primary.b != SDL_SCANCODE_UNKNOWN)
|
||||
SetKeys(held.primary, true);
|
||||
if (held.secondary.a != SDL_SCANCODE_UNKNOWN || held.secondary.b != SDL_SCANCODE_UNKNOWN)
|
||||
SetKeys(held.secondary, true);
|
||||
m_fingerHeldDir[tf.fingerId] = held;
|
||||
return;
|
||||
}
|
||||
|
||||
// Virtual manual shifter (racing games): encoded in tf.x/tf.y from Java and keyed by a fixed fingerId.
|
||||
if ((m_virtualShifterShift4 || m_virtualShifterUpDown) && tf.fingerId == 1108)
|
||||
{
|
||||
@@ -487,7 +579,8 @@ void AndroidInputSystem::HandleTouch(const SDL_TouchFingerEvent& tf, bool down)
|
||||
auto it = m_fingerHeldDir.find(tf.fingerId);
|
||||
if (it != m_fingerHeldDir.end())
|
||||
{
|
||||
SetKeys(it->second, false);
|
||||
SetKeys(it->second.primary, false);
|
||||
SetKeys(it->second.secondary, false);
|
||||
m_fingerHeldDir.erase(it);
|
||||
}
|
||||
return;
|
||||
@@ -507,13 +600,21 @@ void AndroidInputSystem::HandleTouch(const SDL_TouchFingerEvent& tf, bool down)
|
||||
|
||||
if (dir.a != SDL_SCANCODE_UNKNOWN || dir.b != SDL_SCANCODE_UNKNOWN)
|
||||
{
|
||||
m_fingerHeldDir[tf.fingerId] = dir;
|
||||
SetKeys(dir, true);
|
||||
HeldDirKeys held{};
|
||||
held.primary = dir;
|
||||
m_fingerHeldDir[tf.fingerId] = held;
|
||||
SetKeys(held.primary, true);
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidInputSystem::HandleTouchMotion(const SDL_TouchFingerEvent& tf)
|
||||
{
|
||||
if (tf.fingerId == 1114)
|
||||
{
|
||||
HandleTouch(tf, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_virtualShifterShift4 && tf.fingerId == 1108)
|
||||
{
|
||||
// Avoid accidental neutral when passing through center during motion.
|
||||
@@ -582,7 +683,8 @@ void AndroidInputSystem::HandleTouchMotion(const SDL_TouchFingerEvent& tf)
|
||||
}
|
||||
|
||||
// Release previous direction and compute a new one.
|
||||
SetKeys(it->second, false);
|
||||
SetKeys(it->second.primary, false);
|
||||
SetKeys(it->second.secondary, false);
|
||||
m_fingerHeldDir.erase(it);
|
||||
HandleTouch(tf, true);
|
||||
}
|
||||
|
||||
@@ -73,6 +73,11 @@ private:
|
||||
SDL_Scancode b = SDL_SCANCODE_UNKNOWN;
|
||||
};
|
||||
|
||||
struct HeldDirKeys {
|
||||
DualScancode primary{};
|
||||
DualScancode secondary{};
|
||||
};
|
||||
|
||||
void RefreshControllers();
|
||||
void CloseControllers();
|
||||
|
||||
@@ -126,6 +131,16 @@ private:
|
||||
DualScancode m_touchShift4{SDL_SCANCODE_0, SDL_SCANCODE_UNKNOWN};
|
||||
DualScancode m_touchShiftN{SDL_SCANCODE_6, SDL_SCANCODE_UNKNOWN};
|
||||
|
||||
DualScancode m_touchPunch{SDL_SCANCODE_A, SDL_SCANCODE_UNKNOWN};
|
||||
DualScancode m_touchKick{SDL_SCANCODE_S, SDL_SCANCODE_UNKNOWN};
|
||||
DualScancode m_touchGuard{SDL_SCANCODE_D, SDL_SCANCODE_UNKNOWN};
|
||||
DualScancode m_touchEscape{SDL_SCANCODE_F, SDL_SCANCODE_UNKNOWN};
|
||||
|
||||
DualScancode m_touchSpikeShift{SDL_SCANCODE_A, SDL_SCANCODE_UNKNOWN};
|
||||
DualScancode m_touchSpikeBeat{SDL_SCANCODE_S, SDL_SCANCODE_UNKNOWN};
|
||||
DualScancode m_touchSpikeCharge{SDL_SCANCODE_D, SDL_SCANCODE_UNKNOWN};
|
||||
DualScancode m_touchSpikeJump{SDL_SCANCODE_F, SDL_SCANCODE_UNKNOWN};
|
||||
|
||||
bool m_gunTouchEnabled = false;
|
||||
SDL_FingerID m_gunFinger = 0;
|
||||
bool m_gunFingerActive = false;
|
||||
@@ -152,7 +167,7 @@ private:
|
||||
|
||||
std::vector<ControllerState> m_controllers;
|
||||
std::vector<uint8_t> m_keys; // indexed by SDL_Scancode
|
||||
std::unordered_map<SDL_FingerID, DualScancode> m_fingerHeldDir;
|
||||
std::unordered_map<SDL_FingerID, HeldDirKeys> m_fingerHeldDir;
|
||||
std::unordered_map<SDL_FingerID, DualScancode> m_fingerHeldKey;
|
||||
std::unordered_map<SDL_Scancode, uint32_t> m_pulseUntilMs;
|
||||
};
|
||||
|
||||
@@ -267,6 +267,14 @@ struct Super3Host {
|
||||
ensureKeyboardFallback("InputSteeringRight", "KEY_RIGHT");
|
||||
ensureKeyboardFallback("InputAccelerator", "KEY_W");
|
||||
ensureKeyboardFallback("InputBrake", "KEY_S");
|
||||
ensureKeyboardFallback("InputPunch", "KEY_A");
|
||||
ensureKeyboardFallback("InputKick", "KEY_S");
|
||||
ensureKeyboardFallback("InputGuard", "KEY_D");
|
||||
ensureKeyboardFallback("InputEscape", "KEY_F");
|
||||
ensureKeyboardFallback("InputShift", "KEY_A");
|
||||
ensureKeyboardFallback("InputBeat", "KEY_S");
|
||||
ensureKeyboardFallback("InputCharge", "KEY_D");
|
||||
ensureKeyboardFallback("InputJump", "KEY_F");
|
||||
|
||||
inputSystem.ApplyConfig(config);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageButton
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.RelativeLayout
|
||||
@@ -139,6 +140,16 @@ class Super3Activity : SDLActivity() {
|
||||
gamesXml.isNotBlank() &&
|
||||
GameInputsIndex.hasAnyInputType(gamesXml, game, setOf("gun1", "gun2", "analog_gun1", "analog_gun2"))
|
||||
|
||||
val isFighting =
|
||||
game.isNotBlank() &&
|
||||
gamesXml.isNotBlank() &&
|
||||
GameInputsIndex.hasAnyInputType(gamesXml, game, setOf("fighting"))
|
||||
|
||||
val isSpikeout =
|
||||
game.isNotBlank() &&
|
||||
gamesXml.isNotBlank() &&
|
||||
GameInputsIndex.hasAnyInputType(gamesXml, game, setOf("spikeout"))
|
||||
|
||||
val shifterEnabled = getSharedPreferences("super3_prefs", MODE_PRIVATE)
|
||||
.getBoolean("overlay_shifter_enabled", false)
|
||||
val showShifter = shifterEnabled && (hasShift4 || hasShiftUpDown)
|
||||
@@ -155,6 +166,12 @@ class Super3Activity : SDLActivity() {
|
||||
overlay.findViewById<MaterialButton>(R.id.overlay_reload)?.visibility =
|
||||
if (isGunGame) View.VISIBLE else View.GONE
|
||||
|
||||
overlay.findViewById<View>(R.id.overlay_fight_stick)?.visibility =
|
||||
if (isFighting || isSpikeout) View.VISIBLE else View.GONE
|
||||
|
||||
overlay.findViewById<View>(R.id.overlay_fight_buttons)?.visibility =
|
||||
if (isFighting || isSpikeout) View.VISIBLE else View.GONE
|
||||
|
||||
fun nativeTouch(action: Int, fingerId: Int, x: Float, y: Float, p: Float = 1.0f) {
|
||||
SDLActivity.onNativeTouch(0, fingerId, action, x, y, p)
|
||||
}
|
||||
@@ -216,6 +233,67 @@ class Super3Activity : SDLActivity() {
|
||||
bindMomentary(R.id.overlay_reload, fingerId = 1109, x = 0.90f, y = 0.90f)
|
||||
}
|
||||
|
||||
if (isFighting || isSpikeout) {
|
||||
val baseId =
|
||||
if (isSpikeout) {
|
||||
1115
|
||||
} else {
|
||||
1110
|
||||
}
|
||||
|
||||
bindHeld(R.id.overlay_fight_punch, fingerId = baseId + 0, x = 0.90f, y = 0.55f)
|
||||
bindHeld(R.id.overlay_fight_kick, fingerId = baseId + 1, x = 0.82f, y = 0.65f)
|
||||
bindHeld(R.id.overlay_fight_guard, fingerId = baseId + 2, x = 0.90f, y = 0.45f)
|
||||
bindHeld(R.id.overlay_fight_escape, fingerId = baseId + 3, x = 0.82f, y = 0.35f)
|
||||
|
||||
val stick = overlay.findViewById<FrameLayout>(R.id.overlay_fight_stick)
|
||||
val knob = overlay.findViewById<View>(R.id.overlay_fight_stick_knob)
|
||||
stick?.setOnTouchListener { v, ev ->
|
||||
val w = v.width.toFloat().coerceAtLeast(1f)
|
||||
val h = v.height.toFloat().coerceAtLeast(1f)
|
||||
val cx = w / 2f
|
||||
val cy = h / 2f
|
||||
val dx = ((ev.x - cx) / cx).coerceIn(-1f, 1f)
|
||||
val dy = ((ev.y - cy) / cy).coerceIn(-1f, 1f)
|
||||
|
||||
val radiusPx = (w.coerceAtMost(h) * 0.32f).coerceAtLeast(1f)
|
||||
knob?.translationX = dx * radiusPx
|
||||
knob?.translationY = dy * radiusPx
|
||||
|
||||
val encodedX = ((dx + 1f) / 2f).coerceIn(0f, 1f)
|
||||
val encodedY = ((dy + 1f) / 2f).coerceIn(0f, 1f)
|
||||
|
||||
when (ev.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
v.alpha = 0.90f
|
||||
nativeTouch(MotionEvent.ACTION_DOWN, 1114, encodedX, encodedY)
|
||||
true
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
nativeTouch(MotionEvent.ACTION_MOVE, 1114, encodedX, encodedY)
|
||||
true
|
||||
}
|
||||
MotionEvent.ACTION_UP -> {
|
||||
v.alpha = 1.0f
|
||||
knob?.translationX = 0f
|
||||
knob?.translationY = 0f
|
||||
nativeTouch(MotionEvent.ACTION_UP, 1114, 0.5f, 0.5f)
|
||||
true
|
||||
}
|
||||
MotionEvent.ACTION_CANCEL -> {
|
||||
v.alpha = 1.0f
|
||||
knob?.translationX = 0f
|
||||
knob?.translationY = 0f
|
||||
nativeTouch(MotionEvent.ACTION_UP, 1114, 0.5f, 0.5f)
|
||||
true
|
||||
}
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
overlay.findViewById<View>(R.id.overlay_fight_stick)?.setOnTouchListener(null)
|
||||
}
|
||||
|
||||
if (isRacing) {
|
||||
// Match the native pedal zone (right-middle), independent of UI placement.
|
||||
bindHeld(R.id.overlay_gas, fingerId = 1103, x = 0.85f, y = 0.35f)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||
<solid android:color="#22000000" />
|
||||
<stroke android:width="2dp" android:color="@color/overlay_ripple" />
|
||||
</shape>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||
<solid android:color="#33FFFFFF" />
|
||||
<stroke android:width="2dp" android:color="#66FFFFFF" />
|
||||
</shape>
|
||||
@@ -197,4 +197,138 @@
|
||||
android:src="@drawable/gaspedal" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/overlay_fight_stick"
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="160dp"
|
||||
android:layout_gravity="bottom|start"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginBottom="104dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<View
|
||||
android:id="@+id/overlay_fight_stick_base"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/overlay_joystick_base" />
|
||||
|
||||
<View
|
||||
android:id="@+id/overlay_fight_stick_knob"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/overlay_joystick_knob" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/overlay_fight_buttons"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:layout_marginBottom="64dp"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="end">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/overlay_fight_escape"
|
||||
style="?attr/materialButtonOutlinedStyle"
|
||||
android:layout_width="62dp"
|
||||
android:layout_height="62dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"
|
||||
android:text="E"
|
||||
android:textAllCaps="true"
|
||||
android:textSize="16sp"
|
||||
app:cornerRadius="31dp"
|
||||
app:rippleColor="@color/overlay_ripple"
|
||||
app:strokeWidth="2dp" />
|
||||
|
||||
<Space
|
||||
android:layout_width="10dp"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/overlay_fight_guard"
|
||||
style="?attr/materialButtonOutlinedStyle"
|
||||
android:layout_width="62dp"
|
||||
android:layout_height="62dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"
|
||||
android:text="G"
|
||||
android:textAllCaps="true"
|
||||
android:textSize="16sp"
|
||||
app:cornerRadius="31dp"
|
||||
app:rippleColor="@color/overlay_ripple"
|
||||
app:strokeWidth="2dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="10dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="end">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/overlay_fight_kick"
|
||||
style="?attr/materialButtonOutlinedStyle"
|
||||
android:layout_width="62dp"
|
||||
android:layout_height="62dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"
|
||||
android:text="K"
|
||||
android:textAllCaps="true"
|
||||
android:textSize="16sp"
|
||||
app:cornerRadius="31dp"
|
||||
app:rippleColor="@color/overlay_ripple"
|
||||
app:strokeWidth="2dp" />
|
||||
|
||||
<Space
|
||||
android:layout_width="10dp"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/overlay_fight_punch"
|
||||
style="?attr/materialButtonOutlinedStyle"
|
||||
android:layout_width="62dp"
|
||||
android:layout_height="62dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"
|
||||
android:text="P"
|
||||
android:textAllCaps="true"
|
||||
android:textSize="16sp"
|
||||
app:cornerRadius="31dp"
|
||||
app:rippleColor="@color/overlay_ripple"
|
||||
app:strokeWidth="2dp" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
Reference in New Issue
Block a user