Add touch controls, switch to tinymap for keyboard input

This commit is contained in:
griffi-gh 2023-06-04 18:39:55 +02:00
parent 3a9a452fda
commit b3be350047
9 changed files with 210 additions and 29 deletions

BIN
.readme/touch_controls.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

10
Cargo.lock generated
View file

@ -976,6 +976,7 @@ dependencies = [
"shipyard", "shipyard",
"static_assertions", "static_assertions",
"strum", "strum",
"tinyset",
"uflow", "uflow",
"winapi", "winapi",
] ]
@ -1989,6 +1990,15 @@ dependencies = [
"bytemuck", "bytemuck",
] ]
[[package]]
name = "tinyset"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2417e47ddd3809ad40222777ac754ee881b3a6401e38cbeeeb3ee1ca5f30aa0"
dependencies = [
"rand",
]
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.7.3" version = "0.7.3"

View file

@ -26,49 +26,64 @@
<h2>download</h2> <h2>download</h2>
<a href="https://github.com/griffi-gh/kubi/releases/tag/nightly">Latest nightly release</a> <a href="https://github.com/griffi-gh/kubi/releases/tag/nightly">Latest nightly release</a>
<h2>building</h2> <h2>build for windows/linux</h2>
build/run **build/run**
```bash ```bash
cargo build --bin kubi cargo build --bin kubi
cargo run --bin kubi cargo run --bin kubi
``` ```
build in release mode, with nightly optimizations **build in release mode, with nightly optimizations**
```bash ```bash
cargo +nightly build --bin kubi --features nightly --release cargo +nightly build --bin kubi --features nightly --release
``` ```
build for android <h2>build for android</h2>
please note that android support is purely experimental! please note that android support is highly experimental!\
gamepad, keyboard and mouse input is currently borked, and touch controls are not available. gamepad, mouse input is currently borked, and proper touch controls are not available.\
srgb and blending are broken too, which leads to many rendering issues srgb and blending are broken too, which leads to many rendering issues
prerequisites: Android SDK, NDK, platform-tools, latest JDK (all should be in $PATH) prerequisites: Android SDK, command line tools, NDK, platform-tools, latest JDK\
(make sure that your $PATH variable is configured properly)
Setup: **Setup:**
```bash ```bash
cargo install cargo-apk cargo install cargo-apk
cargo target add aarch64-linux-android cargo target add aarch64-linux-android
``` ```
Build: **Build:**
`--no-default-features` is required for keyboard input!
`--no-default-features` is required for keyboard input!\
(`prefer-raw-events` feature *must* be disabled on android)
Mouse input is not implemented, touch only!
```bash ```bash
cargo apk build -p kubi --no-default-features cargo apk build -p kubi --no-default-features
``` ```
Run: **Run:**
```bash ```bash
cargo apk run -p kubi --features nightly cargo apk run -p kubi --no-default-features
``` ```
<h2>android controls</h2>
<img src=".readme/touch_controls.png" alt="touch control scheme" width="300">
- Left side: **Movement**
- Rigth side: **Camera controls**
- Bottom right corner:
- **B** (e.g. place blocks)
- **A** (e.g. break, attack)
<h2>mutiplayer</h2> <h2>mutiplayer</h2>
to join a multiplayer server, just pass the ip address as an argument to join a multiplayer server, just pass the ip address as an argument

View file

@ -27,6 +27,7 @@ postcard = { version = "1.0", features = ["alloc"] }
serde_json = { version = "1.0", optional = true } serde_json = { version = "1.0", optional = true }
lz4_flex = { version = "0.10", default-features = false, features = ["std", "checked-decode"] } lz4_flex = { version = "0.10", default-features = false, features = ["std", "checked-decode"] }
static_assertions = "1.1" static_assertions = "1.1"
tinyset = "0.4"
[target.'cfg(target_os = "android")'.dependencies] [target.'cfg(target_os = "android")'.dependencies]
ndk = "0.7" ndk = "0.7"

View file

@ -30,7 +30,7 @@ fn pick_block_with_number_keys(
) { ) {
let Some((_, mut holding)) = (&main_player, &mut holding).iter().next() else { return }; let Some((_, mut holding)) = (&main_player, &mut holding).iter().next() else { return };
for &(key, block) in BLOCK_KEY_MAP { for &(key, block) in BLOCK_KEY_MAP {
if input.keyboard_state.contains(&key) { if input.keyboard_state.contains(key as u32) {
holding.0 = Some(block); holding.0 = Some(block);
return return
} }

View file

@ -9,7 +9,7 @@ pub fn exit_on_esc(
raw_inputs: UniqueView<RawKbmInputState>, raw_inputs: UniqueView<RawKbmInputState>,
mut control_flow: UniqueViewMut<SetControlFlow> mut control_flow: UniqueViewMut<SetControlFlow>
) { ) {
if raw_inputs.keyboard_state.contains(&VirtualKeyCode::Escape) { if raw_inputs.keyboard_state.contains(VirtualKeyCode::Escape as u32) {
control_flow.0 = Some(ControlFlow::Exit); control_flow.0 = Some(ControlFlow::Exit);
} }
} }

View file

@ -1,6 +1,6 @@
use glam::UVec2; use glam::UVec2;
use shipyard::{World, Component, AllStoragesViewMut, SparseSet, NonSendSync, UniqueView}; use shipyard::{World, Component, AllStoragesViewMut, SparseSet, NonSendSync, UniqueView};
use glium::glutin::event::{Event, DeviceEvent, DeviceId, WindowEvent}; use glium::glutin::event::{Event, DeviceEvent, DeviceId, WindowEvent, Touch};
use crate::rendering::Renderer; use crate::rendering::Renderer;
pub mod player_actions; pub mod player_actions;
@ -17,6 +17,10 @@ pub struct InputDeviceEvent{
pub event: DeviceEvent pub event: DeviceEvent
} }
#[derive(Component, Clone, Copy, Debug)]
#[repr(transparent)]
pub struct TouchEvent(pub Touch);
#[derive(Component, Clone, Copy, Debug, Default)] #[derive(Component, Clone, Copy, Debug, Default)]
pub struct WindowResizedEvent(pub UVec2); pub struct WindowResizedEvent(pub UVec2);
@ -24,7 +28,6 @@ pub fn process_glutin_events(world: &mut World, event: &Event<'_, ()>) {
#[allow(clippy::collapsible_match, clippy::single_match)] #[allow(clippy::collapsible_match, clippy::single_match)]
match event { match event {
Event::WindowEvent { window_id: _, event } => match event { Event::WindowEvent { window_id: _, event } => match event {
WindowEvent::Resized(size) => { WindowEvent::Resized(size) => {
world.add_entity(( world.add_entity((
EventComponent, EventComponent,
@ -43,6 +46,13 @@ pub fn process_glutin_events(world: &mut World, event: &Event<'_, ()>) {
)); ));
} }
WindowEvent::Touch(touch) => {
world.add_entity((
EventComponent,
TouchEvent(*touch)
));
}
_ => () _ => ()
}, },

View file

@ -1,10 +1,14 @@
use gilrs::{Gilrs, GamepadId, Button, Event, Axis}; use gilrs::{Gilrs, GamepadId, Button, Event, Axis};
use glam::{Vec2, DVec2, vec2}; use glam::{Vec2, DVec2, vec2, dvec2};
use glium::glutin::event::{DeviceEvent, VirtualKeyCode, ElementState}; use glium::glutin::event::{DeviceEvent, DeviceId, VirtualKeyCode, ElementState, TouchPhase};
use hashbrown::HashSet; use hashbrown::HashMap;
use tinyset::{SetU32, SetU64};
use nohash_hasher::BuildNoHashHasher; use nohash_hasher::BuildNoHashHasher;
use shipyard::{AllStoragesView, Unique, View, IntoIter, UniqueViewMut, Workload, IntoWorkload, UniqueView, NonSendSync}; use shipyard::{AllStoragesView, Unique, View, IntoIter, UniqueViewMut, Workload, IntoWorkload, UniqueView, NonSendSync};
use crate::events::InputDeviceEvent; use crate::{
events::{InputDeviceEvent, TouchEvent},
rendering::WindowSize
};
#[derive(Unique, Clone, Copy, Default, Debug)] #[derive(Unique, Clone, Copy, Default, Debug)]
pub struct Inputs { pub struct Inputs {
@ -19,11 +23,62 @@ pub struct PrevInputs(pub Inputs);
#[derive(Unique, Clone, Default, Debug)] #[derive(Unique, Clone, Default, Debug)]
pub struct RawKbmInputState { pub struct RawKbmInputState {
pub keyboard_state: HashSet<VirtualKeyCode, BuildNoHashHasher<u32>>, pub keyboard_state: SetU32,
pub button_state: [bool; 32], pub button_state: [bool; 32],
pub mouse_delta: DVec2 pub mouse_delta: DVec2
} }
#[derive(Clone, Copy, Debug, Default)]
pub enum FingerCheck {
#[default]
Start,
Current,
StartOrCurrent,
StartAndCurrent,
NotMoved,
}
#[derive(Clone, Copy, Debug)]
pub struct Finger {
pub id: u64,
pub device_id: DeviceId,
pub prev_position: DVec2,
pub start_position: DVec2,
pub current_position: DVec2,
pub has_moved: bool,
}
impl Finger {
pub fn within_area(&self, area_pos: DVec2, area_size: DVec2, check: FingerCheck) -> bool {
let within_area = |pos: DVec2| -> bool {
((pos - area_pos).min_element() >= 0.) &&
((pos - (area_pos + area_size)).max_element() <= 0.)
};
let start = within_area(self.start_position);
let current = within_area(self.current_position);
match check {
FingerCheck::Start => start,
FingerCheck::Current => current,
FingerCheck::StartOrCurrent => start || current,
FingerCheck::StartAndCurrent => start && current,
FingerCheck::NotMoved => current && !self.has_moved,
}
}
}
#[derive(Unique, Clone, Default, Debug)]
pub struct RawTouchState {
//TODO: handle multiple touch devices somehow
pub fingers: HashMap<u64, Finger, BuildNoHashHasher<u64>>
}
impl RawTouchState {
pub fn query_area(&self, area_pos: DVec2, area_size: DVec2, check: FingerCheck) -> impl Iterator<Item = Finger> + '_ {
self.fingers.iter().filter_map(move |(_, &finger)| {
finger.within_area(area_pos, area_size, check).then_some(finger)
})
}
}
#[derive(Unique)] #[derive(Unique)]
pub struct GilrsWrapper(Option<Gilrs>); pub struct GilrsWrapper(Option<Gilrs>);
@ -46,8 +101,8 @@ fn process_events(
DeviceEvent::Key(input) => { DeviceEvent::Key(input) => {
if let Some(keycode) = input.virtual_keycode { if let Some(keycode) = input.virtual_keycode {
match input.state { match input.state {
ElementState::Pressed => input_state.keyboard_state.insert(keycode), ElementState::Pressed => input_state.keyboard_state.insert(keycode as u32),
ElementState::Released => input_state.keyboard_state.remove(&keycode), ElementState::Released => input_state.keyboard_state.remove(keycode as u32),
}; };
} }
}, },
@ -61,6 +116,39 @@ fn process_events(
} }
} }
fn process_touch_events(
touch_events: View<TouchEvent>,
mut touch_state: UniqueViewMut<RawTouchState>,
) {
for (_, finger) in &mut touch_state.fingers {
finger.prev_position = finger.current_position;
}
for event in touch_events.iter() {
let position = dvec2(event.0.location.x, event.0.location.y);
match event.0.phase {
TouchPhase::Started => {
touch_state.fingers.insert(event.0.id, Finger {
id: event.0.id,
device_id: event.0.device_id,
start_position: position,
current_position: position,
prev_position: position,
has_moved: false
});
},
TouchPhase::Moved => {
if let Some(finger) = touch_state.fingers.get_mut(&event.0.id) {
finger.has_moved = true;
finger.current_position = position;
}
},
TouchPhase::Ended | TouchPhase::Cancelled => {
touch_state.fingers.remove(&event.0.id);
},
}
}
}
fn process_gilrs_events( fn process_gilrs_events(
mut gilrs: NonSendSync<UniqueViewMut<GilrsWrapper>>, mut gilrs: NonSendSync<UniqueViewMut<GilrsWrapper>>,
mut active_gamepad: UniqueViewMut<ActiveGamepad> mut active_gamepad: UniqueViewMut<ActiveGamepad>
@ -85,10 +173,10 @@ fn update_input_state (
mut inputs: UniqueViewMut<Inputs>, mut inputs: UniqueViewMut<Inputs>,
) { ) {
inputs.movement += Vec2::new( inputs.movement += Vec2::new(
raw_inputs.keyboard_state.contains(&VirtualKeyCode::D) as u32 as f32 - raw_inputs.keyboard_state.contains(VirtualKeyCode::D as u32) as u32 as f32 -
raw_inputs.keyboard_state.contains(&VirtualKeyCode::A) as u32 as f32, raw_inputs.keyboard_state.contains(VirtualKeyCode::A as u32) as u32 as f32,
raw_inputs.keyboard_state.contains(&VirtualKeyCode::W) as u32 as f32 - raw_inputs.keyboard_state.contains(VirtualKeyCode::W as u32) as u32 as f32 -
raw_inputs.keyboard_state.contains(&VirtualKeyCode::S) as u32 as f32 raw_inputs.keyboard_state.contains(VirtualKeyCode::S as u32) as u32 as f32
); );
inputs.look += raw_inputs.mouse_delta.as_vec2(); inputs.look += raw_inputs.mouse_delta.as_vec2();
inputs.action_a |= raw_inputs.button_state[1]; inputs.action_a |= raw_inputs.button_state[1];
@ -112,6 +200,60 @@ fn update_input_state_gamepad (
} }
} }
fn update_input_state_touch (
touch_state: UniqueView<RawTouchState>,
win_size: UniqueView<WindowSize>,
mut inputs: UniqueViewMut<Inputs>,
) {
let w = win_size.0.as_dvec2();
//Movement
if let Some(finger) = touch_state.query_area(
dvec2(0., 0.),
dvec2(w.x / 2., w.y),
FingerCheck::Start
).next() {
inputs.movement += (((finger.current_position - finger.start_position) / (w.x / 4.)) * dvec2(1., -1.)).as_vec2();
}
//Action buttons
let action_button_fingers = {
let mut action_button_fingers = SetU64::new();
//Creates iterator of fingers that started within action button area
let action_finger_iter = || touch_state.query_area(
dvec2(w.x * 0.75, w.y * 0.666),
dvec2(w.x * 0.25, w.y * 0.333),
FingerCheck::Start
);
//Action button A
inputs.action_a |= action_finger_iter().filter(|finger| finger.within_area(
dvec2(w.x * (0.75 + 0.125), w.y * 0.666),
dvec2(w.x * 0.125, w.y * 0.333),
FingerCheck::StartOrCurrent
)).map(|x| action_button_fingers.insert(x.id)).next().is_some();
//Action button B
inputs.action_b |= action_finger_iter().filter(|finger| finger.within_area(
dvec2(w.x * 0.75, w.y * 0.666),
dvec2(w.x * 0.125, w.y * 0.333),
FingerCheck::StartOrCurrent
)).map(|x| action_button_fingers.insert(x.id)).next().is_some();
action_button_fingers
};
//Camera controls
if let Some(finger) = touch_state.query_area(
dvec2(w.x / 2., 0.),
dvec2(w.x / 2., w.y),
FingerCheck::Start
).find(|x| !action_button_fingers.contains(x.id)) {
inputs.look += (((finger.current_position - finger.prev_position) / (w.x / 4.)) * 300.).as_vec2();
}
}
fn input_end( fn input_end(
mut inputs: UniqueViewMut<Inputs>, mut inputs: UniqueViewMut<Inputs>,
) { ) {
@ -133,14 +275,17 @@ pub fn init_input (
storages.add_unique(Inputs::default()); storages.add_unique(Inputs::default());
storages.add_unique(PrevInputs::default()); storages.add_unique(PrevInputs::default());
storages.add_unique(RawKbmInputState::default()); storages.add_unique(RawKbmInputState::default());
storages.add_unique(RawTouchState::default());
} }
pub fn process_inputs() -> Workload { pub fn process_inputs() -> Workload {
( (
process_events, process_events,
process_touch_events,
process_gilrs_events, process_gilrs_events,
input_start, input_start,
update_input_state, update_input_state,
update_input_state_touch,
update_input_state_gamepad, update_input_state_gamepad,
input_end, input_end,
).into_sequential_workload() ).into_sequential_workload()

View file

@ -76,7 +76,7 @@ fn override_loading(
kbm_state: UniqueView<RawKbmInputState>, kbm_state: UniqueView<RawKbmInputState>,
mut state: UniqueViewMut<NextState> mut state: UniqueViewMut<NextState>
) { ) {
if kbm_state.keyboard_state.contains(&VirtualKeyCode::F) { if kbm_state.keyboard_state.contains(VirtualKeyCode::F as u32) {
state.0 = Some(GameState::InGame); state.0 = Some(GameState::InGame);
} }
} }