diff --git a/.readme/touch_controls.png b/.readme/touch_controls.png new file mode 100644 index 0000000..e7db529 Binary files /dev/null and b/.readme/touch_controls.png differ diff --git a/Cargo.lock b/Cargo.lock index 28f1ccc..8219cbe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/README.md b/README.md index 3672f7c..9c5df8d 100644 --- a/README.md +++ b/README.md @@ -26,49 +26,64 @@

download

Latest nightly release -

building

+

build for windows/linux

-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 +

build for android

-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 ``` +

android controls

+ +touch control scheme + +- Left side: **Movement** +- Rigth side: **Camera controls** +- Bottom right corner: + - **B** (e.g. place blocks) + - **A** (e.g. break, attack) +

mutiplayer

to join a multiplayer server, just pass the ip address as an argument diff --git a/kubi/Cargo.toml b/kubi/Cargo.toml index 2307e82..6528890 100644 --- a/kubi/Cargo.toml +++ b/kubi/Cargo.toml @@ -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" diff --git a/kubi/src/block_placement.rs b/kubi/src/block_placement.rs index cc0a747..18eb313 100644 --- a/kubi/src/block_placement.rs +++ b/kubi/src/block_placement.rs @@ -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 } diff --git a/kubi/src/control_flow.rs b/kubi/src/control_flow.rs index 9dad6e1..4a4b0c3 100644 --- a/kubi/src/control_flow.rs +++ b/kubi/src/control_flow.rs @@ -9,7 +9,7 @@ pub fn exit_on_esc( raw_inputs: UniqueView, mut control_flow: UniqueViewMut ) { - if raw_inputs.keyboard_state.contains(&VirtualKeyCode::Escape) { + if raw_inputs.keyboard_state.contains(VirtualKeyCode::Escape as u32) { control_flow.0 = Some(ControlFlow::Exit); } } diff --git a/kubi/src/events.rs b/kubi/src/events.rs index c9132c2..ba919f8 100644 --- a/kubi/src/events.rs +++ b/kubi/src/events.rs @@ -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,10 +28,9 @@ 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, + EventComponent, WindowResizedEvent(UVec2::new(size.width as _, size.height as _)) )); }, @@ -43,6 +46,13 @@ pub fn process_glutin_events(world: &mut World, event: &Event<'_, ()>) { )); } + WindowEvent::Touch(touch) => { + world.add_entity(( + EventComponent, + TouchEvent(*touch) + )); + } + _ => () }, diff --git a/kubi/src/input.rs b/kubi/src/input.rs index 37eb0fb..d1e65b5 100644 --- a/kubi/src/input.rs +++ b/kubi/src/input.rs @@ -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>, + 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> +} + +impl RawTouchState { + pub fn query_area(&self, area_pos: DVec2, area_size: DVec2, check: FingerCheck) -> impl Iterator + '_ { + self.fingers.iter().filter_map(move |(_, &finger)| { + finger.within_area(area_pos, area_size, check).then_some(finger) + }) + } +} + #[derive(Unique)] pub struct GilrsWrapper(Option); @@ -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, + mut touch_state: UniqueViewMut, +) { + 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>, mut active_gamepad: UniqueViewMut @@ -85,10 +173,10 @@ fn update_input_state ( mut inputs: UniqueViewMut, ) { 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, + win_size: UniqueView, + mut inputs: UniqueViewMut, +) { + 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, ) { @@ -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() diff --git a/kubi/src/loading_screen.rs b/kubi/src/loading_screen.rs index 210acc2..e3e3e99 100644 --- a/kubi/src/loading_screen.rs +++ b/kubi/src/loading_screen.rs @@ -76,7 +76,7 @@ fn override_loading( kbm_state: UniqueView, mut state: UniqueViewMut ) { - if kbm_state.keyboard_state.contains(&VirtualKeyCode::F) { + if kbm_state.keyboard_state.contains(VirtualKeyCode::F as u32) { state.0 = Some(GameState::InGame); } }