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

View file

@ -26,49 +26,64 @@
<h2>download</h2>
<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
cargo build --bin kubi
cargo run --bin kubi
```
build in release mode, with nightly optimizations
**build in release mode, with nightly optimizations**
```bash
cargo +nightly build --bin kubi --features nightly --release
```
build for android
<h2>build for android</h2>
please note that android support is purely experimental!
gamepad, keyboard and mouse input is currently borked, and touch controls are not available.
please note that android support is highly experimental!\
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
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
cargo install cargo-apk
cargo target add aarch64-linux-android
```
Build:
`--no-default-features` is required for keyboard input!
**Build:**
`--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
cargo apk build -p kubi --no-default-features
```
Run:
**Run:**
```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>
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 }
lz4_flex = { version = "0.10", default-features = false, features = ["std", "checked-decode"] }
static_assertions = "1.1"
tinyset = "0.4"
[target.'cfg(target_os = "android")'.dependencies]
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 };
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);
return
}

View file

@ -9,7 +9,7 @@ pub fn exit_on_esc(
raw_inputs: UniqueView<RawKbmInputState>,
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);
}
}

View file

@ -1,6 +1,6 @@
use glam::UVec2;
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;
pub mod player_actions;
@ -17,6 +17,10 @@ pub struct InputDeviceEvent{
pub event: DeviceEvent
}
#[derive(Component, Clone, Copy, Debug)]
#[repr(transparent)]
pub struct TouchEvent(pub Touch);
#[derive(Component, Clone, Copy, Debug, Default)]
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)]
match event {
Event::WindowEvent { window_id: _, event } => match event {
WindowEvent::Resized(size) => {
world.add_entity((
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 glam::{Vec2, DVec2, vec2};
use glium::glutin::event::{DeviceEvent, VirtualKeyCode, ElementState};
use hashbrown::HashSet;
use glam::{Vec2, DVec2, vec2, dvec2};
use glium::glutin::event::{DeviceEvent, DeviceId, VirtualKeyCode, ElementState, TouchPhase};
use hashbrown::HashMap;
use tinyset::{SetU32, SetU64};
use nohash_hasher::BuildNoHashHasher;
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)]
pub struct Inputs {
@ -19,11 +23,62 @@ pub struct PrevInputs(pub Inputs);
#[derive(Unique, Clone, Default, Debug)]
pub struct RawKbmInputState {
pub keyboard_state: HashSet<VirtualKeyCode, BuildNoHashHasher<u32>>,
pub keyboard_state: SetU32,
pub button_state: [bool; 32],
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)]
pub struct GilrsWrapper(Option<Gilrs>);
@ -46,8 +101,8 @@ fn process_events(
DeviceEvent::Key(input) => {
if let Some(keycode) = input.virtual_keycode {
match input.state {
ElementState::Pressed => input_state.keyboard_state.insert(keycode),
ElementState::Released => input_state.keyboard_state.remove(&keycode),
ElementState::Pressed => input_state.keyboard_state.insert(keycode as u32),
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(
mut gilrs: NonSendSync<UniqueViewMut<GilrsWrapper>>,
mut active_gamepad: UniqueViewMut<ActiveGamepad>
@ -85,10 +173,10 @@ fn update_input_state (
mut inputs: UniqueViewMut<Inputs>,
) {
inputs.movement += Vec2::new(
raw_inputs.keyboard_state.contains(&VirtualKeyCode::D) as u32 as f32 -
raw_inputs.keyboard_state.contains(&VirtualKeyCode::A) as u32 as f32,
raw_inputs.keyboard_state.contains(&VirtualKeyCode::W) as u32 as f32 -
raw_inputs.keyboard_state.contains(&VirtualKeyCode::S) 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 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 u32 as f32
);
inputs.look += raw_inputs.mouse_delta.as_vec2();
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(
mut inputs: UniqueViewMut<Inputs>,
) {
@ -133,14 +275,17 @@ pub fn init_input (
storages.add_unique(Inputs::default());
storages.add_unique(PrevInputs::default());
storages.add_unique(RawKbmInputState::default());
storages.add_unique(RawTouchState::default());
}
pub fn process_inputs() -> Workload {
(
process_events,
process_touch_events,
process_gilrs_events,
input_start,
update_input_state,
update_input_state_touch,
update_input_state_gamepad,
input_end,
).into_sequential_workload()

View file

@ -76,7 +76,7 @@ fn override_loading(
kbm_state: UniqueView<RawKbmInputState>,
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);
}
}