mirror of
https://github.com/griffi-gh/kubi.git
synced 2024-12-24 21:08:19 -06:00
Add touch controls, switch to tinymap for keyboard input
This commit is contained in:
parent
48497c6e17
commit
bbea1659e1
BIN
.readme/touch_controls.png
Normal file
BIN
.readme/touch_controls.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -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"
|
||||
|
|
39
README.md
39
README.md
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
));
|
||||
}
|
||||
|
||||
_ => ()
|
||||
},
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue