Add option to disable use of analog sticks for simulated tilt controls

Resolves #397.

This also makes the options help text acknowledge that the mouse can
be used for simulated tilt controls (previously only mentioned in the
README and log messages).

Change-Id: I0e3615791a4183a838528f9cb2e3f0844a330a8c
This commit is contained in:
hikari_no_yume
2025-06-14 16:26:25 +02:00
parent b448f97559
commit 2130ea8386
6 changed files with 59 additions and 17 deletions

View File

@@ -62,6 +62,7 @@ Usability:
- The app picker now has a “Quick options” feature. This provides a quicker and easier way to set some common options. (@hikari-no-yume)
- App icons in the app picker are now sorted by the display name of the app, case-insensitively. (@hikari-no-yume)
- The accelerometer (tilt controls) can now be simulated using a mouse, instead of a game controller or real accelerometer. Simply hold down the right mouse button and move the mouse cursor. (@alborrajo)
- The new `--disable-analog-stick-tilt-controls` option can be used to disable the use of the game controller's analog sticks for accelerometer simulation. This is useful on devices with both an integrated game controller and an integrated accelerometer, as touchHLE by default will only use the real accelerometer if no game controller is detected. (@hikari-no-yume)
- macOS builds and releases of touchHLE now come as an application bundle (`.app` directory) rather than as a bare “Unix executable” file. This should fix problems some users encountered with running touchHLE outside of a terminal, and allows putting touchHLE in the Applications folder like a normal graphical app. To support this, user data (apps, options, etc) is now stored in “Application Support” rather than the current directory, and the bundled files (fonts, dylibs, etc) are now part of the app bundle. If you prefer the old layout, you can still get it if you move all the files out of the bundle. (@hikari-no-yume)
- The new `--force-composition=` option has been added, which is a workaround that may solve rendering issues in some games, at the cost of performance. For some games it is applied by the default options. (@ciciplusplus)
- touchHLE now writes log messages to a file on all platforms, not just on Android. The file has been renamed from `log.txt` to `touchHLE_log.txt`. (@hikari-no-yume)

View File

@@ -40,13 +40,29 @@ Game controller options:
This is a floating-point (decimal) number between 0 and 1.
--disable-analog-stick-tilt-controls
Disables mapping of analog stick input to simulated rotation of the
device.
By default, if touchHLE detects a game controller, its analog sticks
will be used for simulated accelerometer tilt controls. When this
happens, the real accelerometer (e.g. on an Android phone), if present,
is ignored. By setting this option, the real accelerometer can be used
even if a game controller is detected. This may be useful on a device
that integrates both an accelerometer and a game controller, or if you
do not want to disconnect your game controller.
Note that touchHLE does not currently support accelerometers contained
within game controllers.
--x-tilt-range=...
--y-tilt-range=...
Set the simulated rotation range of the device axis mapped to the analog
stick X or Y axis.
stick X or Y axis, or to the mouse cursor's X or Y position within the
window (when the right mouse button is held).
Positive X on the analog stick is mapped to tilting the device rightward
and positive Y is mapped to tilting the device forward.
Positive X and Y are mapped to tilting the device rightward and forward
respectively.
By default, an analog stick's axis is mapped to a rotation range of 60°
(30° in either direction). If you wanted a range of 90° on the X axis,
@@ -61,10 +77,11 @@ Game controller options:
--x-tilt-offset=...
--y-tilt-offset=...
Offset the simulated angle of the device axis mapped to the analog stick
X or Y axis.
X or Y axis, or to the mouse cursor's X or Y position within the window
(when the right mouse button is held).
Positive X on the analog stick is mapped to tilting the device rightward
and positive Y is mapped to tilting the device forward.
Positive X and Y are mapped to tilting the device rightward and forward
respectively.
By default, the device is simulated as being level with the ground when
the stick is in the center/neutral position. This option is intended for

View File

@@ -148,6 +148,7 @@ struct AppPickerDelegateHostObject {
orientation_default: bool,
orientation_landscape_left: bool,
orientation_landscape_right: bool,
analog_stick_tilt_controls: Option<bool>,
network: Option<bool>,
fullscreen: Option<bool>,
}
@@ -213,6 +214,10 @@ pub const CLASSES: ClassExports = objc_classes! {
- (())orientationLandscapeRight {
env.objc.borrow_mut::<AppPickerDelegateHostObject>(this).orientation_landscape_right = true;
}
- (())analogStickTiltControls:(id)switch { // UISwitch*
let switch_state: bool = msg![env; switch isOn];
env.objc.borrow_mut::<AppPickerDelegateHostObject>(this).analog_stick_tilt_controls = Some(switch_state);
}
- (())network:(id)switch { // UISwitch*
let switch_state: bool = msg![env; switch isOn];
env.objc.borrow_mut::<AppPickerDelegateHostObject>(this).network = Some(switch_state);
@@ -505,6 +510,7 @@ fn show_app_picker_gui(
let mut quick_options_scale_hack: Option<NonZeroU32> = None;
let mut quick_options_fullscreen: Option<()> = None;
let mut quick_options_orientation: Option<DeviceOrientation> = None;
let mut quick_options_analog_stick_tilt_controls = true;
let mut quick_options_network = false;
fn update_quick_option_buttons(env: &mut Environment, buttons: &[id], selected_idx: usize) {
@@ -674,6 +680,8 @@ fn show_app_picker_gui(
&quick_options_stuff.orientation_buttons,
quick_options_orientation,
);
} else if let Some(enabled) = std::mem::take(&mut host_obj.analog_stick_tilt_controls) {
quick_options_analog_stick_tilt_controls = enabled;
} else if let Some(enabled) = std::mem::take(&mut host_obj.network) {
quick_options_network = enabled;
} else if let Some(fullscreen) = std::mem::take(&mut host_obj.fullscreen) {
@@ -701,6 +709,9 @@ fn show_app_picker_gui(
if let Some(()) = quick_options_fullscreen {
option_args.push("--fullscreen".to_string());
}
if !quick_options_analog_stick_tilt_controls {
option_args.push("--disable-analog-stick-tilt-controls".to_string());
}
if quick_options_network {
option_args.push("--allow-network-access".to_string());
}
@@ -1277,7 +1288,7 @@ fn setup_quick_options(
enum RowKind {
Label(&'static str),
Buttons(&'static [(&'static str, &'static str)]),
Switch(&'static str),
Switch(&'static str, bool),
}
let rows = [
RowKind::Label("Scale hack"),
@@ -1295,10 +1306,12 @@ fn setup_quick_options(
("", "orientationLandscapeRight"),
]),
RowKind::Label("Network access"),
RowKind::Switch("network:"),
RowKind::Switch("network:", false),
RowKind::Label("Use analog sticks for tilt controls"),
RowKind::Switch("analogStickTiltControls:", true),
// ---- (divider for stuff skipped below)
RowKind::Label("Fullscreen (override)"),
RowKind::Switch("fullscreen:"),
RowKind::Switch("fullscreen:", false),
];
let rows_len_full = rows.len();
let rows = if crate::window::Window::rotatable_fullscreen() {
@@ -1345,7 +1358,7 @@ fn setup_quick_options(
/* font_size: */ None,
));
}
RowKind::Switch(selector) => {
RowKind::Switch(selector, default_state) => {
let switch_frame = CGRect {
origin: CGPoint {
x: main_frame.size.width / 2.0 - 94.0 / 2.0,
@@ -1356,6 +1369,7 @@ fn setup_quick_options(
let switch: id = msg_class![env; UISwitch alloc];
let switch: id = msg![env; switch initWithFrame:switch_frame];
() = msg![env; switch setOn:default_state];
let selector = env.objc.lookup_selector(selector).unwrap();
() = msg![env; switch addTarget:delegate
action:selector

View File

@@ -73,7 +73,7 @@ pub const CLASSES: ClassExports = objc_classes! {
env.framework_state.uikit.ui_accelerometer.delegate = None;
} else {
env.framework_state.uikit.ui_accelerometer.delegate = Some(delegate);
env.window().print_accelerometer_notice();
env.window().print_accelerometer_notice(&env.options);
}
}

View File

@@ -37,6 +37,7 @@ pub struct Options {
pub initial_orientation: DeviceOrientation,
pub scale_hack: NonZeroU32,
pub deadzone: f32,
pub analog_stick_tilt_controls: bool,
pub x_tilt_range: f32,
pub y_tilt_range: f32,
pub x_tilt_offset: f32,
@@ -61,6 +62,7 @@ impl Default for Options {
fullscreen: false,
initial_orientation: DeviceOrientation::Portrait,
scale_hack: NonZeroU32::new(1).unwrap(),
analog_stick_tilt_controls: true,
deadzone: 0.1,
x_tilt_range: 60.0,
y_tilt_range: 60.0,
@@ -107,6 +109,8 @@ impl Options {
self.scale_hack = value
.parse()
.map_err(|_| "Invalid scale hack factor".to_string())?;
} else if arg == "--disable-analog-stick-tilt-controls" {
self.analog_stick_tilt_controls = false;
} else if let Some(value) = arg.strip_prefix("--deadzone=") {
self.deadzone = parse_degrees(value, "deadzone")?;
} else if let Some(value) = arg.strip_prefix("--x-tilt-range=") {

View File

@@ -712,26 +712,32 @@ impl Window {
let controller = self.controllers.remove(idx);
log!("Warning: Controller disconnected: {}", controller.name());
}
pub fn print_accelerometer_notice(&self) {
pub fn print_accelerometer_notice(&self, options: &Options) {
log!("This app uses the accelerometer.");
if !self.controllers.is_empty() {
if !self.controllers.is_empty() && options.analog_stick_tilt_controls {
log!("Your connected controller's left analog stick will be used for accelerometer simulation.");
if self.accelerometer.is_some() {
log!("Disconnect the controller if you want to use your device's accelerometer.");
}
} else if self.accelerometer.is_some() {
log!("Your device's accelerometer will be used for accelerometer simulation.");
log!("Connect a controller if you would prefer to use an analog stick.");
} else if self.controllers.is_empty() {
if options.analog_stick_tilt_controls {
log!("Connect a controller if you would prefer to use an analog stick.");
}
} else if self.controllers.is_empty() && options.analog_stick_tilt_controls {
log!("Connect a controller to get accelerometer simulation.");
}
log!("You can also hold right click and move the cursor to simulate the accelerometer.");
if self.accelerometer.is_none() {
log!("You can {}hold right click and move the cursor to simulate the accelerometer.", if options.analog_stick_tilt_controls { "also " } else { "" });
}
}
/// Get the real or simulated accelerometer output.
/// See also [crate::frameworks::uikit::ui_accelerometer].
pub fn get_acceleration(&self, options: &Options) -> (f32, f32, f32) {
if self.controllers.is_empty() {
if self.controllers.is_empty() || !options.analog_stick_tilt_controls {
if let Some(ref accelerometer) = self.accelerometer {
let data = accelerometer.get_data().unwrap();
let sdl2::sensor::SensorData::Accel(data) = data else {