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
+
+
+
+- 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);
}
}