mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
Fix initial Android support (#778)
* Add force touches, fix ui focus system and touch screen system * Fix examples README. Update rodio with Android support. Add Android build CI * Alter android metadata in root Cargo.toml
This commit is contained in:
parent
562190f518
commit
7efb1b1887
9 changed files with 204 additions and 106 deletions
11
.github/workflows/ci.yml
vendored
11
.github/workflows/ci.yml
vendored
|
@ -85,6 +85,17 @@ jobs:
|
|||
command: check
|
||||
args: --target wasm32-unknown-unknown --no-default-features --features bevy_winit,x11,hdr,bevy_gltf
|
||||
|
||||
build-android:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Android targets
|
||||
run: rustup target add aarch64-linux-android armv7-linux-androideabi
|
||||
- name: Install Cargo APK
|
||||
run: cargo install cargo-apk
|
||||
- name: Build APK
|
||||
run: cargo apk build --example bevy_android
|
||||
|
||||
clean:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
|
25
Cargo.toml
25
Cargo.toml
|
@ -30,21 +30,6 @@ default = [
|
|||
"x11",
|
||||
]
|
||||
|
||||
supported_android_features = [
|
||||
# cpal is not supported yet
|
||||
# "bevy_audio",
|
||||
"bevy_dynamic_plugin",
|
||||
"bevy_gilrs",
|
||||
"bevy_gltf",
|
||||
"bevy_wgpu",
|
||||
"bevy_winit",
|
||||
"render",
|
||||
"png",
|
||||
"hdr",
|
||||
# "mp3",
|
||||
"x11",
|
||||
]
|
||||
|
||||
profiler = ["bevy_ecs/profiler", "bevy_diagnostic/profiler"]
|
||||
wgpu_trace = ["bevy_wgpu/trace"]
|
||||
|
||||
|
@ -341,10 +326,6 @@ path = "examples/android/bevy_android.rs"
|
|||
crate-type = ["cdylib"]
|
||||
|
||||
[package.metadata.android]
|
||||
build_targets = [ "aarch64-linux-android", "armv7-linux-androideabi" ]
|
||||
target_sdk_version = 28
|
||||
min_sdk_version = 28
|
||||
|
||||
[[package.metadata.android.feature]]
|
||||
name = "android.hardware.vulkan.level"
|
||||
version = "1"
|
||||
build_targets = ["aarch64-linux-android", "armv7-linux-androideabi"]
|
||||
target_sdk_version = 29
|
||||
min_sdk_version = 16
|
||||
|
|
|
@ -22,7 +22,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.2.1" }
|
|||
|
||||
# other
|
||||
anyhow = "1.0"
|
||||
rodio = { version = "0.12", default-features = false }
|
||||
rodio = { version = "0.13", default-features = false }
|
||||
parking_lot = "0.11.0"
|
||||
|
||||
[features]
|
||||
|
|
|
@ -17,6 +17,7 @@ pub mod prelude {
|
|||
},
|
||||
keyboard::KeyCode,
|
||||
mouse::MouseButton,
|
||||
touch::{TouchInput, Touches},
|
||||
Axis, Input,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,20 +1,73 @@
|
|||
use bevy_app::{EventReader, Events};
|
||||
use bevy_ecs::{Local, Res, ResMut};
|
||||
use bevy_math::Vec2;
|
||||
use bevy_utils::{HashMap, HashSet};
|
||||
use bevy_utils::HashMap;
|
||||
use core::ops::DerefMut;
|
||||
|
||||
/// A touch input event
|
||||
#[derive(Debug, Clone)]
|
||||
/// Represents a touch event
|
||||
///
|
||||
/// Every time the user touches the screen, a new `Start` event with an unique
|
||||
/// identifier for the finger is generated. When the finger is lifted, an `End`
|
||||
/// event is generated with the same finger id.
|
||||
///
|
||||
/// After a `Start` event has been emitted, there may be zero or more `Move`
|
||||
/// events when the finger is moved or the touch pressure changes.
|
||||
///
|
||||
/// The finger id may be reused by the system after an `End` event. The user
|
||||
/// should assume that a new `Start` event received with the same id has nothing
|
||||
/// to do with the old finger and is a new finger.
|
||||
///
|
||||
/// A `Cancelled` event is emitted when the system has canceled tracking this
|
||||
/// touch, such as when the window loses focus, or on iOS if the user moves the
|
||||
/// device against their face.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct TouchInput {
|
||||
pub phase: TouchPhase,
|
||||
pub position: Vec2,
|
||||
/// Describes how hard the screen was pressed. May be `None` if the platform
|
||||
/// does not support pressure sensitivity.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - Only available on **iOS** 9.0+ and **Windows** 8+.
|
||||
pub force: Option<ForceTouch>,
|
||||
/// Unique identifier of a finger.
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
/// Describes the force of a touch event
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum ForceTouch {
|
||||
/// On iOS, the force is calibrated so that the same number corresponds to
|
||||
/// roughly the same amount of pressure on the screen regardless of the
|
||||
/// device.
|
||||
Calibrated {
|
||||
/// The force of the touch, where a value of 1.0 represents the force of
|
||||
/// an average touch (predetermined by the system, not user-specific).
|
||||
///
|
||||
/// The force reported by Apple Pencil is measured along the axis of the
|
||||
/// pencil. If you want a force perpendicular to the device, you need to
|
||||
/// calculate this value using the `altitude_angle` value.
|
||||
force: f64,
|
||||
/// The maximum possible force for a touch.
|
||||
///
|
||||
/// The value of this field is sufficiently high to provide a wide
|
||||
/// dynamic range for values of the `force` field.
|
||||
max_possible_force: f64,
|
||||
/// The altitude (in radians) of the stylus.
|
||||
///
|
||||
/// A value of 0 radians indicates that the stylus is parallel to the
|
||||
/// surface. The value of this property is Pi/2 when the stylus is
|
||||
/// perpendicular to the surface.
|
||||
altitude_angle: Option<f64>,
|
||||
},
|
||||
/// If the platform reports the force as normalized, we have no way of
|
||||
/// knowing how much pressure 1.0 corresponds to – we know it's the maximum
|
||||
/// amount of force, but as to how much force, you might either have to
|
||||
/// press really really hard, or not hard at all, depending on the device.
|
||||
Normalized(f64),
|
||||
}
|
||||
|
||||
/// Describes touch-screen input state.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
|
@ -30,12 +83,15 @@ pub struct TouchSystemState {
|
|||
touch_event_reader: EventReader<TouchInput>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Touch {
|
||||
pub id: u64,
|
||||
pub start_position: Vec2,
|
||||
pub start_force: Option<ForceTouch>,
|
||||
pub previous_position: Vec2,
|
||||
pub previous_force: Option<ForceTouch>,
|
||||
pub position: Vec2,
|
||||
pub force: Option<ForceTouch>,
|
||||
}
|
||||
|
||||
impl Touch {
|
||||
|
@ -48,47 +104,69 @@ impl Touch {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
impl From<&TouchInput> for Touch {
|
||||
fn from(input: &TouchInput) -> Touch {
|
||||
Touch {
|
||||
id: input.id,
|
||||
start_position: input.position,
|
||||
start_force: input.force,
|
||||
previous_position: input.position,
|
||||
previous_force: input.force,
|
||||
position: input.position,
|
||||
force: input.force,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Touches {
|
||||
active_touches: HashMap<u64, Touch>,
|
||||
just_pressed: HashSet<u64>,
|
||||
just_released: HashSet<u64>,
|
||||
just_cancelled: HashSet<u64>,
|
||||
pressed: HashMap<u64, Touch>,
|
||||
just_pressed: HashMap<u64, Touch>,
|
||||
just_released: HashMap<u64, Touch>,
|
||||
just_cancelled: HashMap<u64, Touch>,
|
||||
}
|
||||
|
||||
impl Touches {
|
||||
pub fn iter(&self) -> impl Iterator<Item = &Touch> + '_ {
|
||||
self.active_touches.values()
|
||||
self.pressed.values()
|
||||
}
|
||||
|
||||
pub fn get_pressed(&self, id: u64) -> Option<&Touch> {
|
||||
self.pressed.get(&id)
|
||||
}
|
||||
|
||||
pub fn just_pressed(&self, id: u64) -> bool {
|
||||
self.just_pressed.contains(&id)
|
||||
self.just_pressed.contains_key(&id)
|
||||
}
|
||||
|
||||
pub fn iter_just_pressed(&self) -> impl Iterator<Item = &Touch> + '_ {
|
||||
self.just_pressed
|
||||
.iter()
|
||||
.map(move |id| self.active_touches.get(id).unwrap())
|
||||
.map(move |(id, _)| self.pressed.get(id).unwrap())
|
||||
}
|
||||
|
||||
pub fn get_released(&self, id: u64) -> Option<&Touch> {
|
||||
self.just_released.get(&id)
|
||||
}
|
||||
|
||||
pub fn just_released(&self, id: u64) -> bool {
|
||||
self.just_released.contains(&id)
|
||||
self.just_released.contains_key(&id)
|
||||
}
|
||||
|
||||
pub fn iter_just_released(&self) -> impl Iterator<Item = &Touch> + '_ {
|
||||
self.just_released
|
||||
.iter()
|
||||
.map(move |id| self.active_touches.get(id).unwrap())
|
||||
.map(move |(id, _)| self.pressed.get(id).unwrap())
|
||||
}
|
||||
|
||||
pub fn just_cancelled(&self, id: u64) -> bool {
|
||||
self.just_cancelled.contains(&id)
|
||||
self.just_cancelled.contains_key(&id)
|
||||
}
|
||||
|
||||
pub fn iter_just_cancelled(&self) -> impl Iterator<Item = &Touch> + '_ {
|
||||
self.just_cancelled
|
||||
.iter()
|
||||
.map(move |id| self.active_touches.get(id).unwrap())
|
||||
.map(move |(id, _)| self.pressed.get(id).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,45 +176,30 @@ pub fn touch_screen_input_system(
|
|||
mut touch_state: ResMut<Touches>,
|
||||
touch_input_events: Res<Events<TouchInput>>,
|
||||
) {
|
||||
let touch_state = &mut *touch_state;
|
||||
for released_id in touch_state.just_released.iter() {
|
||||
touch_state.active_touches.remove(&released_id);
|
||||
}
|
||||
|
||||
for cancelled_id in touch_state.just_cancelled.iter() {
|
||||
touch_state.active_touches.remove(&cancelled_id);
|
||||
}
|
||||
|
||||
let touch_state = touch_state.deref_mut();
|
||||
touch_state.just_pressed.clear();
|
||||
touch_state.just_cancelled.clear();
|
||||
|
||||
touch_state.just_released.clear();
|
||||
for event in state.touch_event_reader.iter(&touch_input_events) {
|
||||
let active_touch = touch_state.active_touches.get(&event.id);
|
||||
match event.phase {
|
||||
TouchPhase::Started => {
|
||||
touch_state.active_touches.insert(
|
||||
event.id,
|
||||
Touch {
|
||||
id: event.id,
|
||||
start_position: event.position,
|
||||
previous_position: event.position,
|
||||
position: event.position,
|
||||
},
|
||||
);
|
||||
touch_state.just_pressed.insert(event.id);
|
||||
touch_state.pressed.insert(event.id, event.into());
|
||||
touch_state.just_pressed.insert(event.id, event.into());
|
||||
}
|
||||
TouchPhase::Moved => {
|
||||
let old_touch = active_touch.unwrap();
|
||||
let mut new_touch = old_touch.clone();
|
||||
let mut new_touch = touch_state.pressed.get(&event.id).cloned().unwrap();
|
||||
new_touch.previous_position = new_touch.position;
|
||||
new_touch.previous_force = new_touch.force;
|
||||
new_touch.position = event.position;
|
||||
touch_state.active_touches.insert(event.id, new_touch);
|
||||
new_touch.force = event.force;
|
||||
touch_state.pressed.insert(event.id, new_touch);
|
||||
}
|
||||
TouchPhase::Ended => {
|
||||
touch_state.just_released.insert(event.id);
|
||||
touch_state.just_released.insert(event.id, event.into());
|
||||
touch_state.pressed.remove_entry(&event.id);
|
||||
}
|
||||
TouchPhase::Cancelled => {
|
||||
touch_state.just_cancelled.insert(event.id);
|
||||
touch_state.just_cancelled.insert(event.id, event.into());
|
||||
touch_state.pressed.remove_entry(&event.id);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::Node;
|
|||
use bevy_app::{EventReader, Events};
|
||||
use bevy_core::FloatOrd;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_input::{mouse::MouseButton, Input};
|
||||
use bevy_input::{mouse::MouseButton, touch::Touches, Input};
|
||||
use bevy_math::Vec2;
|
||||
use bevy_transform::components::GlobalTransform;
|
||||
use bevy_window::CursorMoved;
|
||||
|
@ -43,6 +43,7 @@ pub fn ui_focus_system(
|
|||
mut state: Local<State>,
|
||||
mouse_button_input: Res<Input<MouseButton>>,
|
||||
cursor_moved_events: Res<Events<CursorMoved>>,
|
||||
touches_input: Res<Touches>,
|
||||
mut node_query: Query<(
|
||||
Entity,
|
||||
&Node,
|
||||
|
@ -54,8 +55,11 @@ pub fn ui_focus_system(
|
|||
if let Some(cursor_moved) = state.cursor_moved_event_reader.latest(&cursor_moved_events) {
|
||||
state.cursor_position = cursor_moved.position;
|
||||
}
|
||||
if let Some(touch) = touches_input.get_pressed(0) {
|
||||
state.cursor_position = touch.position;
|
||||
}
|
||||
|
||||
if mouse_button_input.just_released(MouseButton::Left) {
|
||||
if mouse_button_input.just_released(MouseButton::Left) || touches_input.just_released(0) {
|
||||
for (_entity, _node, _global_transform, interaction, _focus_policy) in node_query.iter_mut()
|
||||
{
|
||||
if let Some(mut interaction) = interaction {
|
||||
|
@ -66,7 +70,8 @@ pub fn ui_focus_system(
|
|||
}
|
||||
}
|
||||
|
||||
let mouse_clicked = mouse_button_input.just_pressed(MouseButton::Left);
|
||||
let mouse_clicked =
|
||||
mouse_button_input.just_pressed(MouseButton::Left) || touches_input.just_released(0);
|
||||
let mut hovered_entity = None;
|
||||
|
||||
{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use bevy_input::{
|
||||
keyboard::{KeyCode, KeyboardInput},
|
||||
mouse::MouseButton,
|
||||
touch::{TouchInput, TouchPhase},
|
||||
touch::{ForceTouch, TouchInput, TouchPhase},
|
||||
ElementState,
|
||||
};
|
||||
use bevy_math::Vec2;
|
||||
|
@ -39,6 +39,18 @@ pub fn convert_touch_input(touch_input: winit::event::Touch) -> TouchInput {
|
|||
winit::event::TouchPhase::Cancelled => TouchPhase::Cancelled,
|
||||
},
|
||||
position: Vec2::new(touch_input.location.x as f32, touch_input.location.y as f32),
|
||||
force: touch_input.force.map(|f| match f {
|
||||
winit::event::Force::Calibrated {
|
||||
force,
|
||||
max_possible_force,
|
||||
altitude_angle,
|
||||
} => ForceTouch::Calibrated {
|
||||
force,
|
||||
max_possible_force,
|
||||
altitude_angle,
|
||||
},
|
||||
winit::event::Force::Normalized(x) => ForceTouch::Normalized(x),
|
||||
}),
|
||||
id: touch_input.id,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -261,9 +261,15 @@ pub fn winit_runner(mut app: App) {
|
|||
});
|
||||
}
|
||||
},
|
||||
WindowEvent::Touch(touch) => {
|
||||
WindowEvent::Touch(mut touch) => {
|
||||
let mut touch_input_events =
|
||||
app.resources.get_mut::<Events<TouchInput>>().unwrap();
|
||||
let windows = app.resources.get_mut::<Windows>().unwrap();
|
||||
// FIXME?: On Android window start is top while on PC/Linux/OSX on bottom
|
||||
if cfg!(target_os = "android") {
|
||||
let window_height = windows.get_primary().unwrap().height();
|
||||
touch.location.y = window_height as f64 - touch.location.y;
|
||||
}
|
||||
touch_input_events.send(converters::convert_touch_input(touch));
|
||||
}
|
||||
_ => {}
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
These examples demonstrate the main features of Bevy and how to use them.
|
||||
To run an example, use the command `cargo run --example <Example>`, and add the option `--features x11` or `--features wayland` to force the example to run on a specific window compositor, e.g.
|
||||
```
|
||||
|
||||
```sh
|
||||
cargo run --features wayland --example hello_world
|
||||
```
|
||||
|
||||
|
@ -119,47 +120,61 @@ Example | File | Description
|
|||
|
||||
## WASM
|
||||
|
||||
#### pre-req
|
||||
#### Pre-requirements
|
||||
|
||||
$ rustup target add wasm32-unknown-unknown
|
||||
$ cargo install wasm-bindgen-cli
|
||||
```sh
|
||||
rustup target add wasm32-unknown-unknown
|
||||
cargo install wasm-bindgen-cli
|
||||
```
|
||||
|
||||
#### build & run
|
||||
#### Build & run
|
||||
|
||||
Following is an example for `headless_wasm`. For other examples in wasm/ directory,
|
||||
change the `headless_wasm` in the following commands **and edit** `examples/wasm/index.html`
|
||||
to point to the correct `.js` file.
|
||||
|
||||
$ cargo build --example headless_wasm --target wasm32-unknown-unknown --no-default-features
|
||||
$ wasm-bindgen --out-dir examples/wasm/target --target web target/wasm32-unknown-unknown/debug/examples/headless_wasm.wasm
|
||||
```sh
|
||||
cargo build --example headless_wasm --target wasm32-unknown-unknown --no-default-features
|
||||
wasm-bindgen --out-dir examples/wasm/target --target web target/wasm32-unknown-unknown/debug/examples/headless_wasm.wasm
|
||||
```
|
||||
|
||||
Then serve `examples/wasm` dir to browser. i.e.
|
||||
|
||||
$ basic-http-server examples/wasm
|
||||
```sh
|
||||
basic-http-server examples/wasm
|
||||
```
|
||||
|
||||
## iOS
|
||||
|
||||
#### pre-req
|
||||
#### Pre-requirements
|
||||
|
||||
$ rustup target add aarch64-apple-ios x86_64-apple-ios
|
||||
$ cargo install cargo-lipo
|
||||
```sh
|
||||
rustup target add aarch64-apple-ios x86_64-apple-ios
|
||||
cargo install cargo-lipo
|
||||
```
|
||||
|
||||
#### build & run
|
||||
#### Build & run
|
||||
|
||||
Using bash:
|
||||
|
||||
$ cd examples/ios
|
||||
$ make run
|
||||
```sh
|
||||
cd examples/ios
|
||||
make run
|
||||
```
|
||||
|
||||
In an ideal world, this will boot up, install and run the app for the first
|
||||
iOS simulator in your `xcrun simctl devices list`. If this fails, you can
|
||||
specify the simulator device UUID via:
|
||||
|
||||
$ DEVICE_ID=${YOUR_DEVICE_ID} make run
|
||||
```sh
|
||||
DEVICE_ID=${YOUR_DEVICE_ID} make run
|
||||
```
|
||||
|
||||
If you'd like to see xcode do stuff, you can run
|
||||
|
||||
$ open bevy_ios_example.xcodeproj/
|
||||
```sh
|
||||
open bevy_ios_example.xcodeproj/
|
||||
```
|
||||
|
||||
which will open xcode. You then must push the zoom zoom play button and wait
|
||||
for the magic.
|
||||
|
@ -175,42 +190,46 @@ used for the `Makefile`.
|
|||
|
||||
## Android
|
||||
|
||||
#### pre-req
|
||||
#### Pre-requirements
|
||||
|
||||
$ rustup target add aarch64-linux-android armv7-linux-androideabi
|
||||
$ cargo install cargo-apk
|
||||
```sh
|
||||
rustup target add aarch64-linux-android armv7-linux-androideabi
|
||||
cargo install cargo-apk
|
||||
```
|
||||
|
||||
The Android SDK 29 must be installed, and the environment variable `ANDROID_SDK_ROOT` set to the root Android `sdk` folder.
|
||||
The Android SDK must be installed, and the environment variable `ANDROID_SDK_ROOT` set to the root Android `sdk` folder.
|
||||
|
||||
When using `NDK (Side by side)`, the environment variable `ANDROID_NDK_ROOT` must also be set to one of the NDKs in `sdk\ndk\[NDK number]`.
|
||||
|
||||
#### build & run
|
||||
#### Build & run
|
||||
|
||||
To run on a device setup for Android development, run:
|
||||
|
||||
$ cargo apk run --example bevy_android --features="supported_android_features" --no-default-features
|
||||
```sh
|
||||
cargo apk run --example bevy_android
|
||||
```
|
||||
|
||||
:warning: At this time Bevy does not work in Android Emulator.
|
||||
|
||||
When using Bevy as a library, the following fields must be added to `Cargo.toml`:
|
||||
|
||||
[package.metadata.android]
|
||||
build_targets = [ "aarch64-linux-android", "armv7-linux-androideabi" ]
|
||||
target_sdk_version = 29
|
||||
min_sdk_version = 29
|
||||
|
||||
[[package.metadata.android.feature]]
|
||||
name = "android.hardware.vulkan.level"
|
||||
version = "1"
|
||||
```toml
|
||||
[package.metadata.android]
|
||||
build_targets = ["aarch64-linux-android", "armv7-linux-androideabi"]
|
||||
target_sdk_version = 29
|
||||
min_sdk_version = 16
|
||||
```
|
||||
|
||||
Please reference `cargo-apk` [README](https://crates.io/crates/cargo-apk) for other Android Manifest fields.
|
||||
|
||||
#### old phones
|
||||
#### Old phones
|
||||
|
||||
Bevy by default requires Android API level 29 which is the [Play Store's minimum API to upload or update apps](https://developer.android.com/distribute/best-practices/develop/target-sdk). Users of older phones may want to use an older API when testing.
|
||||
Bevy by default targets Android API level 29 in its examples which is the [Play Store's minimum API to upload or update apps](https://developer.android.com/distribute/best-practices/develop/target-sdk). Users of older phones may want to use an older API when testing.
|
||||
|
||||
To use a different API, the following fields must be updated in Cargo.toml:
|
||||
|
||||
[package.metadata.android]
|
||||
target_sdk_version = >>API<<
|
||||
min_sdk_version = >>API or less<<
|
||||
```toml
|
||||
[package.metadata.android]
|
||||
target_sdk_version = >>API<<
|
||||
min_sdk_version = >>API or less<<
|
||||
```
|
||||
|
|
Loading…
Reference in a new issue