├── .gitignore
├── Android
├── .gitignore
├── app
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── assets
│ │ ├── fonts
│ │ │ ├── FiraMono-LICENSE
│ │ │ ├── FiraMono-Medium.ttf
│ │ │ └── FiraSans-Bold.ttf
│ │ ├── shaders
│ │ │ ├── animate_shader.wgsl
│ │ │ ├── array_texture.wgsl
│ │ │ ├── cubemap_unlit.wgsl
│ │ │ ├── custom_material.frag
│ │ │ ├── custom_material.vert
│ │ │ ├── custom_material.wgsl
│ │ │ ├── custom_material_chromatic_aberration.wgsl
│ │ │ ├── custom_material_screenspace_texture.wgsl
│ │ │ ├── custom_vertex_attribute.wgsl
│ │ │ ├── game_of_life.wgsl
│ │ │ ├── instancing.wgsl
│ │ │ ├── line_material.wgsl
│ │ │ └── shader_defs.wgsl
│ │ └── sounds
│ │ │ └── breakout_collision.ogg
│ │ ├── cpp
│ │ ├── CMakeLists.txt
│ │ └── native-lib.cpp
│ │ ├── java
│ │ └── name
│ │ │ └── jinleili
│ │ │ └── bevy
│ │ │ ├── BevySurfaceView.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── RustBridge.kt
│ │ │ ├── ToggleButton.kt
│ │ │ └── ui
│ │ │ └── theme
│ │ │ ├── Color.kt
│ │ │ ├── Shape.kt
│ │ │ ├── Theme.kt
│ │ │ └── Type.kt
│ │ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
├── Cargo.lock
├── Cargo.toml
├── LICENSE.MIT
├── README.md
├── android_build.sh
├── assets
├── bevy_in_android.png
├── bevy_in_ios.png
├── branding
│ ├── bevy_bird_dark.png
│ ├── bevy_logo_dark.png
│ └── bevy_logo_light.png
├── fonts
│ ├── FiraMono-LICENSE
│ ├── FiraMono-Medium.ttf
│ └── FiraSans-Bold.ttf
└── sounds
│ ├── Windless Slopes.ogg
│ └── breakout_collision.ogg
├── iOS
├── base
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── Base.lproj
│ │ └── Main.storyboard
│ ├── MetalView.swift
│ ├── ViewController+CoreMotion.swift
│ ├── ViewController.swift
│ ├── bevy_in_iOS-Bridging-Header.h
│ └── libbevy_in_app.h
├── bevy_in_iOS.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ ├── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcuserdata
│ │ │ └── LiJinlei.xcuserdatad
│ │ │ └── UserInterfaceState.xcuserstate
│ ├── xcshareddata
│ │ └── xcschemes
│ │ │ └── bevy_in_iOS.xcscheme
│ └── xcuserdata
│ │ └── LiJinlei.xcuserdatad
│ │ ├── xcdebugger
│ │ └── Breakpoints_v2.xcbkptlist
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
└── bevy_in_iOS
│ ├── Base.lproj
│ └── LaunchScreen.storyboard
│ └── Info.plist
├── ios_build.sh
└── src
├── android_asset_io.rs
├── app_view
├── android.rs
├── app_views.rs
├── ios.rs
├── mod.rs
└── view.rs
├── breakout_game.rs
├── ffi
├── android.rs
├── ios.rs
└── mod.rs
├── lib.rs
├── lighting_demo.rs
├── main.rs
├── shapes_demo.rs
└── stepping.rs
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 |
3 | # Include the root lockfile but not the others
4 | */Cargo.lock
5 |
6 | # These are backup files generated by rustfmt
7 | **/*.rs.bk
8 |
9 | .DS_Store
10 |
11 | # IDE/Editor configuration files
12 | .vscode
13 | .idea
14 |
15 | **/*.so
16 | JetpackCompose/
17 | iOS/libs/debug/*
18 | iOS/libs/release/*
19 | iOS/libs/M1+Mac/*
20 | GPU-Frame-Caputre.xcodeproj
--------------------------------------------------------------------------------
/Android/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/Android/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/Android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | android {
7 | compileSdk 33
8 |
9 | defaultConfig {
10 | applicationId "name.jinleili.bevy"
11 | // Android 7.1.1, API 25
12 | // Android 9.0, API 28
13 | minSdk 27
14 | targetSdk 32
15 | versionCode 1
16 | versionName "1.0"
17 |
18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
19 | vectorDrawables {
20 | useSupportLibrary true
21 | }
22 | externalNativeBuild {
23 | cmake {
24 | arguments "-DANDROID_STL=c++_shared"
25 | }
26 | }
27 | }
28 | sourceSets {
29 | main {
30 | jniLibs.srcDirs = ['libs']
31 | assets {
32 | srcDirs 'src/main/assets'
33 | }
34 | }
35 | }
36 | externalNativeBuild {
37 | cmake {
38 | path file('src/main/cpp/CMakeLists.txt')
39 | version '3.18.1'
40 | }
41 | }
42 | buildTypes {
43 | release {
44 | minifyEnabled false
45 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
46 | }
47 | debug {
48 | applicationIdSuffix ".debug"
49 | debuggable true
50 | }
51 | }
52 | compileOptions {
53 | sourceCompatibility JavaVersion.VERSION_1_8
54 | targetCompatibility JavaVersion.VERSION_1_8
55 | }
56 | kotlinOptions {
57 | jvmTarget = '1.8'
58 | }
59 | buildFeatures {
60 | compose true
61 | }
62 | composeOptions {
63 | kotlinCompilerExtensionVersion compose_version
64 | }
65 | packagingOptions {
66 | resources {
67 | excludes += '/META-INF/{AL2.0,LGPL2.1}'
68 | }
69 | }
70 | namespace 'name.jinleili.bevy'
71 | }
72 |
73 | dependencies {
74 |
75 | implementation 'androidx.core:core-ktx:1.7.0'
76 | implementation "androidx.compose.ui:ui:$compose_version"
77 | implementation "androidx.compose.material:material:$compose_version"
78 | implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
79 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
80 | implementation 'androidx.activity:activity-compose:1.3.1'
81 | testImplementation 'junit:junit:4.13.2'
82 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
83 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
84 | androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
85 | debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
86 | }
--------------------------------------------------------------------------------
/Android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/Android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
17 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Android/app/src/main/assets/fonts/FiraMono-LICENSE:
--------------------------------------------------------------------------------
1 | Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A.
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | http://scripts.sil.org/OFL
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/Android/app/src/main/assets/fonts/FiraMono-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/Android/app/src/main/assets/fonts/FiraMono-Medium.ttf
--------------------------------------------------------------------------------
/Android/app/src/main/assets/fonts/FiraSans-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/Android/app/src/main/assets/fonts/FiraSans-Bold.ttf
--------------------------------------------------------------------------------
/Android/app/src/main/assets/shaders/animate_shader.wgsl:
--------------------------------------------------------------------------------
1 | #import bevy_pbr::mesh_types
2 | // The time since startup data is in the globals binding which is part of the mesh_view_bindings import
3 | #import bevy_pbr::mesh_view_bindings
4 |
5 | fn oklab_to_linear_srgb(c: vec3) -> vec3 {
6 | let L = c.x;
7 | let a = c.y;
8 | let b = c.z;
9 |
10 | let l_ = L + 0.3963377774 * a + 0.2158037573 * b;
11 | let m_ = L - 0.1055613458 * a - 0.0638541728 * b;
12 | let s_ = L - 0.0894841775 * a - 1.2914855480 * b;
13 |
14 | let l = l_ * l_ * l_;
15 | let m = m_ * m_ * m_;
16 | let s = s_ * s_ * s_;
17 |
18 | return vec3(
19 | 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
20 | -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
21 | -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s,
22 | );
23 | }
24 |
25 | struct FragmentInput {
26 | #import bevy_pbr::mesh_vertex_output
27 | }
28 |
29 | @fragment
30 | fn fragment(in: FragmentInput) -> @location(0) vec4 {
31 | let speed = 2.0;
32 | // The globals binding contains various global values like time
33 | // which is the time since startup in seconds
34 | let t_1 = sin(globals.time * speed) * 0.5 + 0.5;
35 | let t_2 = cos(globals.time * speed);
36 |
37 | let distance_to_center = distance(in.uv, vec2(0.5)) * 1.4;
38 |
39 | // blending is done in a perceptual color space: https://bottosson.github.io/posts/oklab/
40 | let red = vec3(0.627955, 0.224863, 0.125846);
41 | let green = vec3(0.86644, -0.233887, 0.179498);
42 | let blue = vec3(0.701674, 0.274566, -0.169156);
43 | let white = vec3(1.0, 0.0, 0.0);
44 | let mixed = mix(mix(red, blue, t_1), mix(green, white, t_2), distance_to_center);
45 |
46 | return vec4(oklab_to_linear_srgb(mixed), 1.0);
47 | }
48 |
--------------------------------------------------------------------------------
/Android/app/src/main/assets/shaders/array_texture.wgsl:
--------------------------------------------------------------------------------
1 | #import bevy_pbr::mesh_view_bindings
2 | #import bevy_pbr::mesh_bindings
3 |
4 | #import bevy_pbr::pbr_types
5 | #import bevy_pbr::utils
6 | #import bevy_pbr::clustered_forward
7 | #import bevy_pbr::lighting
8 | #import bevy_pbr::shadows
9 | #import bevy_pbr::pbr_functions
10 |
11 | @group(1) @binding(0)
12 | var my_array_texture: texture_2d_array;
13 | @group(1) @binding(1)
14 | var my_array_texture_sampler: sampler;
15 |
16 | struct FragmentInput {
17 | @builtin(front_facing) is_front: bool,
18 | @builtin(position) frag_coord: vec4,
19 | #import bevy_pbr::mesh_vertex_output
20 | };
21 |
22 | @fragment
23 | fn fragment(in: FragmentInput) -> @location(0) vec4 {
24 | let layer = i32(in.world_position.x) & 0x3;
25 |
26 | // Prepare a 'processed' StandardMaterial by sampling all textures to resolve
27 | // the material members
28 | var pbr_input: PbrInput = pbr_input_new();
29 |
30 | pbr_input.material.base_color = textureSample(my_array_texture, my_array_texture_sampler, in.uv, layer);
31 | #ifdef VERTEX_COLORS
32 | pbr_input.material.base_color = pbr_input.material.base_color * in.color;
33 | #endif
34 |
35 | pbr_input.frag_coord = in.frag_coord;
36 | pbr_input.world_position = in.world_position;
37 | pbr_input.world_normal = prepare_world_normal(
38 | in.world_normal,
39 | (pbr_input.material.flags & STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u,
40 | in.is_front,
41 | );
42 |
43 | pbr_input.is_orthographic = view.projection[3].w == 1.0;
44 |
45 | pbr_input.N = apply_normal_mapping(
46 | pbr_input.material.flags,
47 | pbr_input.world_normal,
48 | #ifdef VERTEX_TANGENTS
49 | #ifdef STANDARDMATERIAL_NORMAL_MAP
50 | in.world_tangent,
51 | #endif
52 | #endif
53 | in.uv,
54 | );
55 | pbr_input.V = calculate_view(in.world_position, pbr_input.is_orthographic);
56 |
57 | return tone_mapping(pbr(pbr_input));
58 | }
59 |
--------------------------------------------------------------------------------
/Android/app/src/main/assets/shaders/cubemap_unlit.wgsl:
--------------------------------------------------------------------------------
1 | #import bevy_pbr::mesh_view_bindings
2 |
3 | #ifdef CUBEMAP_ARRAY
4 | @group(1) @binding(0)
5 | var base_color_texture: texture_cube_array;
6 | #else
7 | @group(1) @binding(0)
8 | var base_color_texture: texture_cube;
9 | #endif
10 |
11 | @group(1) @binding(1)
12 | var base_color_sampler: sampler;
13 |
14 | @fragment
15 | fn fragment(
16 | #import bevy_pbr::mesh_vertex_output
17 | ) -> @location(0) vec4 {
18 | let fragment_position_view_lh = world_position.xyz * vec3(1.0, 1.0, -1.0);
19 | return textureSample(
20 | base_color_texture,
21 | base_color_sampler,
22 | fragment_position_view_lh
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/Android/app/src/main/assets/shaders/custom_material.frag:
--------------------------------------------------------------------------------
1 | #version 450
2 | layout(location = 0) in vec2 v_Uv;
3 |
4 | layout(location = 0) out vec4 o_Target;
5 |
6 | layout(set = 1, binding = 0) uniform CustomMaterial {
7 | vec4 Color;
8 | };
9 |
10 | layout(set = 1, binding = 1) uniform texture2D CustomMaterial_texture;
11 | layout(set = 1, binding = 2) uniform sampler CustomMaterial_sampler;
12 |
13 |
14 | void main() {
15 | o_Target = Color * texture(sampler2D(CustomMaterial_texture,CustomMaterial_sampler), v_Uv);
16 | }
17 |
--------------------------------------------------------------------------------
/Android/app/src/main/assets/shaders/custom_material.vert:
--------------------------------------------------------------------------------
1 | #version 450
2 |
3 | layout(location = 0) in vec3 Vertex_Position;
4 | layout(location = 1) in vec3 Vertex_Normal;
5 | layout(location = 2) in vec2 Vertex_Uv;
6 |
7 | layout(location = 0) out vec2 v_Uv;
8 |
9 | layout(set = 0, binding = 0) uniform CameraViewProj {
10 | mat4 ViewProj;
11 | mat4 View;
12 | mat4 InverseView;
13 | mat4 Projection;
14 | vec3 WorldPosition;
15 | float width;
16 | float height;
17 | };
18 |
19 | layout(set = 2, binding = 0) uniform Mesh {
20 | mat4 Model;
21 | mat4 InverseTransposeModel;
22 | uint flags;
23 | };
24 |
25 | void main() {
26 | v_Uv = Vertex_Uv;
27 | gl_Position = ViewProj * Model * vec4(Vertex_Position, 1.0);
28 | }
29 |
--------------------------------------------------------------------------------
/Android/app/src/main/assets/shaders/custom_material.wgsl:
--------------------------------------------------------------------------------
1 | struct CustomMaterial {
2 | color: vec4,
3 | };
4 |
5 | @group(1) @binding(0)
6 | var material: CustomMaterial;
7 | @group(1) @binding(1)
8 | var base_color_texture: texture_2d;
9 | @group(1) @binding(2)
10 | var base_color_sampler: sampler;
11 |
12 | @fragment
13 | fn fragment(
14 | #import bevy_pbr::mesh_vertex_output
15 | ) -> @location(0) vec4 {
16 | return material.color * textureSample(base_color_texture, base_color_sampler, uv);
17 | }
18 |
--------------------------------------------------------------------------------
/Android/app/src/main/assets/shaders/custom_material_chromatic_aberration.wgsl:
--------------------------------------------------------------------------------
1 | #import bevy_pbr::mesh_view_bindings
2 | #import bevy_pbr::utils
3 |
4 | @group(1) @binding(0)
5 | var texture: texture_2d;
6 |
7 | @group(1) @binding(1)
8 | var our_sampler: sampler;
9 |
10 | @fragment
11 | fn fragment(
12 | @builtin(position) position: vec4,
13 | #import bevy_sprite::mesh2d_vertex_output
14 | ) -> @location(0) vec4 {
15 | // Get screen position with coordinates from 0 to 1
16 | let uv = coords_to_viewport_uv(position.xy, view.viewport);
17 | let offset_strength = 0.02;
18 |
19 | // Sample each color channel with an arbitrary shift
20 | var output_color = vec4(
21 | textureSample(texture, our_sampler, uv + vec2(offset_strength, -offset_strength)).r,
22 | textureSample(texture, our_sampler, uv + vec2(-offset_strength, 0.0)).g,
23 | textureSample(texture, our_sampler, uv + vec2(0.0, offset_strength)).b,
24 | 1.0
25 | );
26 |
27 | return output_color;
28 | }
29 |
--------------------------------------------------------------------------------
/Android/app/src/main/assets/shaders/custom_material_screenspace_texture.wgsl:
--------------------------------------------------------------------------------
1 | #import bevy_pbr::mesh_view_bindings
2 | #import bevy_pbr::utils
3 |
4 | @group(1) @binding(0)
5 | var texture: texture_2d;
6 | @group(1) @binding(1)
7 | var texture_sampler: sampler;
8 |
9 | @fragment
10 | fn fragment(
11 | @builtin(position) position: vec4,
12 | #import bevy_pbr::mesh_vertex_output
13 | ) -> @location(0) vec4 {
14 | let uv = coords_to_viewport_uv(position.xy, view.viewport);
15 | let color = textureSample(texture, texture_sampler, uv);
16 | return color;
17 | }
18 |
--------------------------------------------------------------------------------
/Android/app/src/main/assets/shaders/custom_vertex_attribute.wgsl:
--------------------------------------------------------------------------------
1 | #import bevy_pbr::mesh_view_bindings
2 | #import bevy_pbr::mesh_bindings
3 |
4 | struct CustomMaterial {
5 | color: vec4,
6 | };
7 | @group(1) @binding(0)
8 | var material: CustomMaterial;
9 |
10 | // NOTE: Bindings must come before functions that use them!
11 | #import bevy_pbr::mesh_functions
12 |
13 | struct Vertex {
14 | @location(0) position: vec3,
15 | @location(1) blend_color: vec4,
16 | };
17 |
18 | struct VertexOutput {
19 | @builtin(position) clip_position: vec4,
20 | @location(0) blend_color: vec4,
21 | };
22 |
23 | @vertex
24 | fn vertex(vertex: Vertex) -> VertexOutput {
25 | var out: VertexOutput;
26 | out.clip_position = mesh_position_local_to_clip(mesh.model, vec4(vertex.position, 1.0));
27 | out.blend_color = vertex.blend_color;
28 | return out;
29 | }
30 |
31 | struct FragmentInput {
32 | @location(0) blend_color: vec4,
33 | };
34 |
35 | @fragment
36 | fn fragment(input: FragmentInput) -> @location(0) vec4 {
37 | return material.color * input.blend_color;
38 | }
39 |
--------------------------------------------------------------------------------
/Android/app/src/main/assets/shaders/game_of_life.wgsl:
--------------------------------------------------------------------------------
1 | @group(0) @binding(0)
2 | var texture: texture_storage_2d;
3 |
4 | fn hash(value: u32) -> u32 {
5 | var state = value;
6 | state = state ^ 2747636419u;
7 | state = state * 2654435769u;
8 | state = state ^ state >> 16u;
9 | state = state * 2654435769u;
10 | state = state ^ state >> 16u;
11 | state = state * 2654435769u;
12 | return state;
13 | }
14 |
15 | fn randomFloat(value: u32) -> f32 {
16 | return f32(hash(value)) / 4294967295.0;
17 | }
18 |
19 | @compute @workgroup_size(8, 8, 1)
20 | fn init(@builtin(global_invocation_id) invocation_id: vec3, @builtin(num_workgroups) num_workgroups: vec3) {
21 | let location = vec2(i32(invocation_id.x), i32(invocation_id.y));
22 |
23 | let randomNumber = randomFloat(invocation_id.y * num_workgroups.x + invocation_id.x);
24 | let alive = randomNumber > 0.9;
25 | let color = vec4(f32(alive));
26 |
27 | textureStore(texture, location, color);
28 | }
29 |
30 | fn is_alive(location: vec2, offset_x: i32, offset_y: i32) -> i32 {
31 | let value: vec4 = textureLoad(texture, location + vec2(offset_x, offset_y));
32 | return i32(value.x);
33 | }
34 |
35 | fn count_alive(location: vec2) -> i32 {
36 | return is_alive(location, -1, -1) +
37 | is_alive(location, -1, 0) +
38 | is_alive(location, -1, 1) +
39 | is_alive(location, 0, -1) +
40 | is_alive(location, 0, 1) +
41 | is_alive(location, 1, -1) +
42 | is_alive(location, 1, 0) +
43 | is_alive(location, 1, 1);
44 | }
45 |
46 | @compute @workgroup_size(8, 8, 1)
47 | fn update(@builtin(global_invocation_id) invocation_id: vec3) {
48 | let location = vec2(i32(invocation_id.x), i32(invocation_id.y));
49 |
50 | let n_alive = count_alive(location);
51 |
52 | var alive: bool;
53 | if (n_alive == 3) {
54 | alive = true;
55 | } else if (n_alive == 2) {
56 | let currently_alive = is_alive(location, 0, 0);
57 | alive = bool(currently_alive);
58 | } else {
59 | alive = false;
60 | }
61 | let color = vec4(f32(alive));
62 |
63 | storageBarrier();
64 |
65 | textureStore(texture, location, color);
66 | }
--------------------------------------------------------------------------------
/Android/app/src/main/assets/shaders/instancing.wgsl:
--------------------------------------------------------------------------------
1 | #import bevy_pbr::mesh_types
2 | #import bevy_pbr::mesh_view_bindings
3 |
4 | @group(1) @binding(0)
5 | var mesh: Mesh;
6 |
7 | // NOTE: Bindings must come before functions that use them!
8 | #import bevy_pbr::mesh_functions
9 |
10 | struct Vertex {
11 | @location(0) position: vec3,
12 | @location(1) normal: vec3,
13 | @location(2) uv: vec2,
14 |
15 | @location(3) i_pos_scale: vec4,
16 | @location(4) i_color: vec4,
17 | };
18 |
19 | struct VertexOutput {
20 | @builtin(position) clip_position: vec4,
21 | @location(0) color: vec4,
22 | };
23 |
24 | @vertex
25 | fn vertex(vertex: Vertex) -> VertexOutput {
26 | let position = vertex.position * vertex.i_pos_scale.w + vertex.i_pos_scale.xyz;
27 | var out: VertexOutput;
28 | out.clip_position = mesh_position_local_to_clip(mesh.model, vec4(position, 1.0));
29 | out.color = vertex.i_color;
30 | return out;
31 | }
32 |
33 | @fragment
34 | fn fragment(in: VertexOutput) -> @location(0) vec4 {
35 | return in.color;
36 | }
37 |
--------------------------------------------------------------------------------
/Android/app/src/main/assets/shaders/line_material.wgsl:
--------------------------------------------------------------------------------
1 | struct LineMaterial {
2 | color: vec4,
3 | };
4 |
5 | @group(1) @binding(0)
6 | var material: LineMaterial;
7 |
8 | @fragment
9 | fn fragment(
10 | #import bevy_pbr::mesh_vertex_output
11 | ) -> @location(0) vec4 {
12 | return material.color;
13 | }
14 |
--------------------------------------------------------------------------------
/Android/app/src/main/assets/shaders/shader_defs.wgsl:
--------------------------------------------------------------------------------
1 | struct CustomMaterial {
2 | color: vec4,
3 | };
4 |
5 | @group(1) @binding(0)
6 | var material: CustomMaterial;
7 |
8 | @fragment
9 | fn fragment(
10 | #import bevy_pbr::mesh_vertex_output
11 | ) -> @location(0) vec4 {
12 | #ifdef IS_RED
13 | return vec4(1.0, 0.0, 0.0, 1.0);
14 | #else
15 | return material.color;
16 | #endif
17 | }
18 |
--------------------------------------------------------------------------------
/Android/app/src/main/assets/sounds/breakout_collision.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/Android/app/src/main/assets/sounds/breakout_collision.ogg
--------------------------------------------------------------------------------
/Android/app/src/main/cpp/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # For more information about using CMake with Android Studio, read the
2 | # documentation: https://d.android.com/studio/projects/add-native-code.html
3 |
4 | # Sets the minimum version of CMake required to build the native library.
5 |
6 | cmake_minimum_required(VERSION 3.18.1)
7 |
8 | # Declares and names the project.
9 |
10 | project("cxx_runtime_trigger")
11 |
12 | # Creates and names a library, sets it as either STATIC
13 | # or SHARED, and provides the relative paths to its source code.
14 | # You can define multiple libraries, and CMake builds them for you.
15 | # Gradle automatically packages shared libraries with your APK.
16 |
17 | add_library( # Sets the name of the library.
18 | cxx_runtime_trigger
19 |
20 | # Sets the library as a shared library.
21 | SHARED
22 |
23 | # Provides a relative path to your source file(s).
24 | native-lib.cpp)
25 |
26 | # Searches for a specified prebuilt library and stores the path as a
27 | # variable. Because CMake includes system libraries in the search path by
28 | # default, you only need to specify the name of the public NDK library
29 | # you want to add. CMake verifies that the library exists before
30 | # completing its build.
31 |
32 | find_library( # Sets the name of the path variable.
33 | log-lib
34 |
35 | # Specifies the name of the NDK library that
36 | # you want CMake to locate.
37 | log)
38 |
39 | # Specifies libraries CMake should link to your target library. You
40 | # can link multiple libraries, such as libraries you define in this
41 | # build script, prebuilt third-party libraries, or system libraries.
42 |
43 | target_link_libraries( # Specifies the target library.
44 | cxx_runtime_trigger
45 |
46 | # Links the target library to the log library
47 | # included in the NDK.
48 | ${log-lib})
--------------------------------------------------------------------------------
/Android/app/src/main/cpp/native-lib.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | //#include
3 |
--------------------------------------------------------------------------------
/Android/app/src/main/java/name/jinleili/bevy/BevySurfaceView.kt:
--------------------------------------------------------------------------------
1 | package name.jinleili.bevy
2 |
3 | import android.content.Context
4 | import android.graphics.Canvas
5 | import android.hardware.*
6 | import android.util.AttributeSet
7 | import android.view.SurfaceHolder
8 | import android.view.SurfaceView
9 |
10 | class BevySurfaceView : SurfaceView, SurfaceHolder.Callback2 {
11 | private var rustBrige = RustBridge()
12 | private var bevy_app: Long = Long.MAX_VALUE
13 | private var ndk_inited = false
14 | private var idx: Int = 0
15 | private var sensorManager: SensorManager? = null
16 | private var mSensor: Sensor? = null
17 | private var sensorValues: FloatArray = FloatArray(3)
18 |
19 | constructor(context: Context) : super(context) {
20 | sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
21 | mSensor = sensorManager?.getDefaultSensor(Sensor.TYPE_GRAVITY)
22 | }
23 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
24 | }
25 | constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(
26 | context,
27 | attrs,
28 | defStyle
29 | ) {
30 | }
31 |
32 | init {
33 | // 将当前类设置为 SurfaceHolder 的回调接口代理
34 | holder.addCallback(this)
35 | }
36 |
37 | override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
38 | }
39 |
40 | // 绘制表面被创建后,创建/重新创建 Bevy App
41 | override fun surfaceCreated(holder: SurfaceHolder) {
42 | holder.let { h ->
43 | if (!ndk_inited) {
44 | ndk_inited = true
45 | rustBrige.init_ndk_context(this.context)
46 | }
47 |
48 | if (bevy_app == Long.MAX_VALUE) {
49 | // Get the screen's density scale
50 | val scaleFactor: Float = resources.displayMetrics.density
51 | bevy_app = rustBrige.create_bevy_app(this.context.assets, h.surface, scaleFactor)
52 | }
53 |
54 | // SurfaceView 默认不会自动开始绘制,setWillNotDraw(false) 用于通知 App 已经准备好开始绘制了。
55 | setWillNotDraw(false)
56 |
57 | var sensorEventListener = object : SensorEventListener {
58 | override fun onSensorChanged(event: SensorEvent?) {
59 | if (event != null) {
60 | sensorValues = event.values
61 | }
62 | }
63 |
64 | override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
65 | }
66 | }
67 | mSensor?.also { sensor ->
68 | sensorManager?.registerListener(sensorEventListener, sensor, SensorManager.SENSOR_DELAY_GAME)
69 | }
70 | }
71 | }
72 |
73 | // 绘制表面被销毁后,也销毁 Bevy 中的 Android window
74 | override fun surfaceDestroyed(holder: SurfaceHolder) {
75 | if (bevy_app != Long.MAX_VALUE) {
76 | rustBrige.release_bevy_app(bevy_app)
77 | bevy_app = Long.MAX_VALUE
78 | }
79 | }
80 |
81 | override fun surfaceRedrawNeeded(holder: SurfaceHolder) {
82 | }
83 |
84 | // API Level 26+
85 | // override fun surfaceRedrawNeededAsync(holder: SurfaceHolder, drawingFinished: Runnable) {
86 | // super.surfaceRedrawNeededAsync(holder, drawingFinished)
87 | // }
88 |
89 | override fun draw(canvas: Canvas?) {
90 | super.draw(canvas)
91 | if (bevy_app == Long.MAX_VALUE) {
92 | return
93 | }
94 | rustBrige.device_motion(bevy_app, sensorValues[0], sensorValues[1], sensorValues[2])
95 | rustBrige.enter_frame(bevy_app)
96 | // invalidate() 函数通知通知 App,在下一个 UI 刷新周期重新调用 draw() 函数
97 | invalidate()
98 | }
99 |
100 | fun changeExample(index: Int) {
101 | if (bevy_app != Long.MAX_VALUE && this.idx != index) {
102 | this.idx = index
103 | }
104 | }
105 | }
--------------------------------------------------------------------------------
/Android/app/src/main/java/name/jinleili/bevy/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package name.jinleili.bevy
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.layout.*
7 | import androidx.compose.material.Surface
8 | import androidx.compose.material.Text
9 | import androidx.compose.runtime.*
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.platform.LocalConfiguration
13 | import androidx.compose.ui.res.colorResource
14 | import androidx.compose.ui.text.font.FontWeight
15 | import androidx.compose.ui.unit.dp
16 | import androidx.compose.ui.unit.sp
17 | import androidx.compose.ui.viewinterop.AndroidView
18 | import name.jinleili.bevy.ui.theme.MyApplicationTheme
19 |
20 | class MainActivity : ComponentActivity() {
21 |
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState)
24 |
25 | setContent {
26 | MyApplicationTheme {
27 | Surface(
28 | modifier = Modifier.fillMaxSize(),
29 | color = colorResource(id = R.color.white)
30 | ) {
31 | SurfaceCard()
32 | }
33 | }
34 | }
35 | }
36 | }
37 |
38 | var surfaceView: BevySurfaceView? = null
39 |
40 | @Composable
41 | fun SurfaceCard() {
42 | var selected by remember { mutableStateOf("Breakout") }
43 | val toggleValues = listOf("Breakout", "XXX",)
44 | val screenWidth = LocalConfiguration.current.screenWidthDp
45 | Column(modifier = Modifier.fillMaxSize()) {
46 | Row(
47 | verticalAlignment = Alignment.CenterVertically,
48 | horizontalArrangement = Arrangement.Center,
49 | modifier = Modifier
50 | .height(44.dp)
51 | .padding(horizontal = 0.dp, vertical = 7.dp)
52 | .fillMaxWidth()
53 | ) {
54 | Text(text = "Bevy in Android App", fontSize = 20.sp, fontWeight = FontWeight.Bold)
55 | }
56 | // Row(
57 | // verticalAlignment = Alignment.CenterVertically,
58 | // horizontalArrangement = Arrangement.Center,
59 | // modifier = Modifier
60 | // .height(54.dp)
61 | // .padding(horizontal = 0.dp, vertical = 9.dp)
62 | // .fillMaxWidth()
63 | // ) {
64 | // ToggleButton(
65 | // currentSelection = selected,
66 | // toggleStates = toggleValues,
67 | // onToggleChange = { title ->
68 | // selected = title
69 | // toggleValues.forEachIndexed { idx, v ->
70 | // if (v == title) {
71 | // surfaceView?.changeExample(idx)
72 | // }
73 | // }
74 | // },
75 | // )
76 | //
77 | // }
78 | Spacer(modifier = Modifier.height(8.dp))
79 | AndroidView(
80 | factory = { ctx ->
81 | val sv = BevySurfaceView(context = ctx)
82 | surfaceView = sv
83 | sv
84 | },
85 | modifier = Modifier
86 | .fillMaxWidth()
87 | .height((screenWidth.toFloat() * 1.6).dp),
88 | )
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Android/app/src/main/java/name/jinleili/bevy/RustBridge.kt:
--------------------------------------------------------------------------------
1 | package name.jinleili.bevy
2 |
3 | import android.view.Surface
4 | import android.content.Context
5 | import android.content.res.AssetManager
6 |
7 | class RustBridge {
8 | init {
9 | System.loadLibrary("bevy_in_app")
10 | }
11 |
12 | external fun init_ndk_context(ctx: Context)
13 | external fun create_bevy_app(asset_manager: AssetManager, surface: Surface, scale_factor: Float): Long
14 | external fun enter_frame(bevy_app: Long)
15 | external fun device_motion(bevy_app: Long, x: Float, y: Float, z: Float)
16 | external fun release_bevy_app(bevy_app: Long)
17 | }
--------------------------------------------------------------------------------
/Android/app/src/main/java/name/jinleili/bevy/ToggleButton.kt:
--------------------------------------------------------------------------------
1 | package name.jinleili.bevy
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.*
5 | import androidx.compose.foundation.selection.toggleable
6 | import androidx.compose.foundation.shape.RoundedCornerShape
7 | import androidx.compose.material.Divider
8 | import androidx.compose.material.MaterialTheme
9 | import androidx.compose.material.Text
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.graphics.Color
13 | import androidx.compose.ui.unit.dp
14 |
15 | @Composable
16 | fun ToggleButton(
17 | currentSelection: String,
18 | toggleStates: List,
19 | onToggleChange: (String) -> Unit
20 | ) {
21 | val selectedTint = MaterialTheme.colors.primary
22 | val unselectedTint = Color.Unspecified
23 | Row(
24 | modifier = Modifier
25 | .height(IntrinsicSize.Min)
26 | .background(color = Color.LightGray, shape = RoundedCornerShape(6.dp))
27 | ) {
28 | toggleStates.forEachIndexed { index, toggleState ->
29 | val isSelected = currentSelection.lowercase() == toggleState.lowercase()
30 | val backgroundTint = if (isSelected) selectedTint else unselectedTint
31 | val textColor = if (isSelected) Color.White else Color.Unspecified
32 |
33 | if (index != 0) {
34 | Divider(
35 | color = Color(0x55666666),
36 | modifier = Modifier
37 | .fillMaxHeight()
38 | .padding(horizontal = 0.dp, vertical = 7.dp)
39 | .width(1.dp)
40 | )
41 | }
42 |
43 | Row(
44 | modifier = Modifier
45 | .background(color = backgroundTint, shape = RoundedCornerShape(6.dp))
46 | .toggleable(
47 | value = isSelected,
48 | enabled = true,
49 | onValueChange = { _ ->
50 | // if (selected) {
51 | onToggleChange(toggleState)
52 | // }
53 | })
54 | ) {
55 | Text(toggleState, color = textColor, modifier = Modifier.padding(vertical = 2.dp, horizontal = 4.dp))
56 | }
57 |
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Android/app/src/main/java/name/jinleili/bevy/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package name.jinleili.bevy.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Purple200 = Color(0xFFBB86FC)
6 | val Purple500 = Color(0xFF6200EE)
7 | val Purple700 = Color(0xFF3700B3)
8 | val Teal200 = Color(0xFF03DAC5)
--------------------------------------------------------------------------------
/Android/app/src/main/java/name/jinleili/bevy/ui/theme/Shape.kt:
--------------------------------------------------------------------------------
1 | package name.jinleili.bevy.ui.theme
2 |
3 | import androidx.compose.foundation.shape.RoundedCornerShape
4 | import androidx.compose.material.Shapes
5 | import androidx.compose.ui.unit.dp
6 |
7 | val Shapes = Shapes(
8 | small = RoundedCornerShape(4.dp),
9 | medium = RoundedCornerShape(4.dp),
10 | large = RoundedCornerShape(0.dp)
11 | )
--------------------------------------------------------------------------------
/Android/app/src/main/java/name/jinleili/bevy/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package name.jinleili.bevy.ui.theme
2 |
3 | import androidx.compose.foundation.isSystemInDarkTheme
4 | import androidx.compose.material.MaterialTheme
5 | import androidx.compose.material.darkColors
6 | import androidx.compose.material.lightColors
7 | import androidx.compose.runtime.Composable
8 |
9 | private val DarkColorPalette = darkColors(
10 | primary = Purple200,
11 | primaryVariant = Purple700,
12 | secondary = Teal200
13 | )
14 |
15 | private val LightColorPalette = lightColors(
16 | primary = Purple500,
17 | primaryVariant = Purple700,
18 | secondary = Teal200
19 |
20 | /* Other default colors to override
21 | background = Color.White,
22 | surface = Color.White,
23 | onPrimary = Color.White,
24 | onSecondary = Color.Black,
25 | onBackground = Color.Black,
26 | onSurface = Color.Black,
27 | */
28 | )
29 |
30 | @Composable
31 | fun MyApplicationTheme(
32 | darkTheme: Boolean = isSystemInDarkTheme(),
33 | content: @Composable () -> Unit
34 | ) {
35 | val colors = if (darkTheme) {
36 | DarkColorPalette
37 | } else {
38 | LightColorPalette
39 | }
40 |
41 | MaterialTheme(
42 | colors = colors,
43 | typography = Typography,
44 | shapes = Shapes,
45 | content = content
46 | )
47 | }
--------------------------------------------------------------------------------
/Android/app/src/main/java/name/jinleili/bevy/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package name.jinleili.bevy.ui.theme
2 |
3 | import androidx.compose.material.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | body1 = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp
15 | )
16 | /* Other default text styles to override
17 | button = TextStyle(
18 | fontFamily = FontFamily.Default,
19 | fontWeight = FontWeight.W500,
20 | fontSize = 14.sp
21 | ),
22 | caption = TextStyle(
23 | fontFamily = FontFamily.Default,
24 | fontWeight = FontWeight.Normal,
25 | fontSize = 12.sp
26 | )
27 | */
28 | )
--------------------------------------------------------------------------------
/Android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/Android/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/Android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Android/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/Android/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/Android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/Android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/Android/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/Android/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/Android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/Android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/Android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/Android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/Android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/Android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/Android/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 | #00000000
11 |
12 |
--------------------------------------------------------------------------------
/Android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Bevy in App
3 |
--------------------------------------------------------------------------------
/Android/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
--------------------------------------------------------------------------------
/Android/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | ext {
4 | compose_version = '1.2.0'
5 | }
6 | }
7 | plugins {
8 | id 'com.android.application' version '7.4.2' apply false
9 | id 'com.android.library' version '7.4.2' apply false
10 | id 'org.jetbrains.kotlin.android' version '1.7.0' apply false
11 | }
12 |
13 | task clean(type: Delete) {
14 | delete rootProject.buildDir
15 | }
--------------------------------------------------------------------------------
/Android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/Android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/Android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/Android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Jan 19 17:47:12 CST 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/Android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/Android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/Android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "bevy"
16 | include ':app'
17 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "bevy-in-app"
3 | version = "0.3.0"
4 | authors = ["jinleili"]
5 | edition = "2021"
6 | rust-version = "1.78"
7 |
8 | [lib]
9 | crate-type = ["rlib", "staticlib", "cdylib"]
10 |
11 | [features]
12 | # default = ["bevy_debug_stepping"]
13 | bevy_debug_stepping = ["bevy/bevy_debug_stepping"]
14 |
15 | [dependencies]
16 | libc = "*"
17 | raw-window-handle = "0.6"
18 | wgpu = "0.19"
19 | bevy = { git = "https://github.com/bevyengine/bevy", rev = "ec01c2dc4562adb979cf5497a48b53efa8250901", features = [
20 | # bevy = { path = "../bevy", features = [
21 | "bevy_audio",
22 | "bevy_sprite",
23 | "bevy_winit",
24 | "bevy_pbr",
25 | "bevy_text",
26 | "bevy_ui",
27 | "tonemapping_luts",
28 | "vorbis",
29 | "png",
30 | "bevy_debug_stepping",
31 | ], default-features = false }
32 | bevy_tasks = { git = "https://github.com/bevyengine/bevy", rev = "ec01c2dc4562adb979cf5497a48b53efa8250901" }
33 | uuid = { version = "1.7.0", features = ["v4"] }
34 | log = "0.4"
35 |
36 | [target.'cfg(target_os = "ios")'.dependencies]
37 | core-graphics = "*"
38 | objc = "*"
39 |
40 | [target.'cfg(target_os = "android")'.dependencies]
41 | android_logger = "0.13"
42 | jni = "0.21"
43 | jni_fn = "0.1"
44 | # 需要保持与 bevy 一致
45 | ndk-sys = "0.4.1+23.1.7779620"
46 | ndk = "0.7.0"
47 | ndk-context = "*"
48 | log-panics = "*"
49 |
50 | [profile.dev]
51 | opt-level = 1 # 1 for minimal optimization and good debugging.
52 |
--------------------------------------------------------------------------------
/LICENSE.MIT:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Jinlei Li
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Bevy in App
2 |
3 | Integrate the [Bevy engine](https://github.com/bevyengine/bevy) into existing iOS | Android apps.
4 |
5 | If you want to add a mini-game to an existing app, or implement some dynamic UI components, charts ..., or just want to take advantage of the **Motion Sensors** on your phone for some cool gameplay, you can't use `WinitPlugin`. Because `winit` will take over the entire app initialization process and windowing, but we need to create `bevy::App` in an existing app instance, and we may also want `bevy::App` to run in an `iOS UIView` or `Android SurfaceView` of any size.
6 |
7 | This repository implements such a scenario and uses the phone's motion sensor to play breakout mini-games.
8 |
9 | ## Screenshot
10 |
11 | |  |  |
12 | | ------------------------------------------ | -------------------------------------------------- |
13 |
14 | ## **iOS**
15 |
16 | ```sh
17 | # Add iOS target
18 | rustup target add aarch64-apple-ios
19 |
20 | # Build for iOS target
21 | sh ./ios_build.sh --release
22 | ```
23 |
24 | Then, Open `iOS/bevy_in_iOS.xcodeproj` with Xcode,connect an iOS device and run.
25 |
26 | ## **Android**
27 |
28 | ### Set up Android environment
29 |
30 | Assuming your computer already has Android Studio installed, go to `Android Studio` > `Tools` > `SDK Manager` > `Android SDK` > `SDK Tools`. Check the following options for installation and click OK.
31 |
32 | - [x] Android SDK Build-Tools
33 | - [x] Android SDK Command-line Tools
34 | - [x] NDK(Side by side)
35 |
36 | Then, set two following environment variables:
37 |
38 | ```sh
39 | export ANDROID_SDK_ROOT=$HOME/Library/Android/sdk
40 | # Replace the NDK version number with the version you installed
41 | export NDK_HOME=$ANDROID_SDK_ROOT/ndk/23.1.7779620
42 | ```
43 |
44 | ### Add build targets
45 |
46 | ```sh
47 | # Since simulator and virtual devices only support GLES,
48 | # `x86_64-linux-android` and `i686-linux-android` targets are not necessary
49 | rustup target add aarch64-linux-android
50 | ```
51 |
52 | ### Build
53 |
54 | ```sh
55 | # Install cargo-so subcommand
56 | cargo install cargo-so
57 |
58 | # Build
59 | sh ./android_build.sh --release
60 | ```
61 |
62 | ## Compatible Bevy versions
63 |
64 | | Bevy version | `bevy-in-app` version |
65 | |:-------------|:--------------------------|
66 | | `0.14 dev` | `0.3` |
67 | | `0.11`-`0.12`| `0.2` |
68 | | `0.10` | `0.1` |
69 |
--------------------------------------------------------------------------------
/android_build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | RELEASE_MODE=${1}
4 | LIB_FOLDER="debug"
5 |
6 | # build to Android target
7 | if [ "${RELEASE_MODE}" = "--release" ]; then
8 | LIB_FOLDER="release"
9 | cargo so b --lib --target aarch64-linux-android ${RELEASE_MODE}
10 | else
11 | RUST_BACKTRACE=full RUST_LOG=wgpu_hal=debug cargo so b --lib --target aarch64-linux-android
12 | fi
13 |
14 | # copy .so files to jniLibs folder
15 | ARM64="android/app/libs/arm64-v8a"
16 | ARMv7a="android/app/libs/armeabi-v7a"
17 |
18 | if [ ! -d "$ARM64" ]; then
19 | mkdir "$ARM64"
20 | fi
21 | if [ ! -d "$ARMv7a" ]; then
22 | mkdir "$ARMv7a"
23 | fi
24 |
25 | cp target/aarch64-linux-android/${LIB_FOLDER}/libbevy_in_app.so "${ARM64}/libbevy_in_app.so"
--------------------------------------------------------------------------------
/assets/bevy_in_android.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/assets/bevy_in_android.png
--------------------------------------------------------------------------------
/assets/bevy_in_ios.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/assets/bevy_in_ios.png
--------------------------------------------------------------------------------
/assets/branding/bevy_bird_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/assets/branding/bevy_bird_dark.png
--------------------------------------------------------------------------------
/assets/branding/bevy_logo_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/assets/branding/bevy_logo_dark.png
--------------------------------------------------------------------------------
/assets/branding/bevy_logo_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/assets/branding/bevy_logo_light.png
--------------------------------------------------------------------------------
/assets/fonts/FiraMono-LICENSE:
--------------------------------------------------------------------------------
1 | Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A.
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | http://scripts.sil.org/OFL
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/assets/fonts/FiraMono-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/assets/fonts/FiraMono-Medium.ttf
--------------------------------------------------------------------------------
/assets/fonts/FiraSans-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/assets/fonts/FiraSans-Bold.ttf
--------------------------------------------------------------------------------
/assets/sounds/Windless Slopes.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/assets/sounds/Windless Slopes.ogg
--------------------------------------------------------------------------------
/assets/sounds/breakout_collision.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/assets/sounds/breakout_collision.ogg
--------------------------------------------------------------------------------
/iOS/base/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // bevy_in_iOS
4 | //
5 | // Created by Jinlei Li on 2022/12/20.
6 | //
7 |
8 | import UIKit
9 |
10 | @main
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 | var window: UIWindow?
13 |
14 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
15 | window = UIWindow(frame: UIScreen.main.bounds)
16 | let mainStroryBoard = UIStoryboard(name: "Main", bundle: nil)
17 | window?.rootViewController = mainStroryBoard.instantiateInitialViewController()
18 |
19 | window?.makeKeyAndVisible()
20 | return true
21 | }
22 |
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/iOS/base/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/iOS/base/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/iOS/base/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/iOS/base/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/iOS/base/MetalView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MetalView.swift
3 | // bevy_in_iOS
4 | //
5 | // Created by Jinlei Li on 2022/12/20.
6 | //
7 |
8 | import UIKit
9 | import Foundation
10 |
11 | class MetalView: UIView {
12 | override class var layerClass: AnyClass {
13 | return CAMetalLayer.self
14 | }
15 |
16 | override func awakeFromNib() {
17 | super.awakeFromNib()
18 | configLayer()
19 | self.layer.backgroundColor = UIColor.clear.cgColor
20 | }
21 |
22 | private func configLayer() {
23 | guard let layer = self.layer as? CAMetalLayer else {
24 | return
25 | }
26 | layer.presentsWithTransaction = false
27 | layer.framebufferOnly = true
28 | // nativeScale is real physical pixel scale
29 | // https://tomisacat.xyz/tech/2017/06/17/scale-nativescale-contentsscale.html
30 | self.contentScaleFactor = UIScreen.main.nativeScale
31 | }
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/iOS/base/ViewController+CoreMotion.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController+CoreMotion.swift
3 | // bevy_in_iOS
4 | //
5 | // Created by 李金磊 on 2022/12/22.
6 | //
7 |
8 | import CoreMotion
9 |
10 | extension ViewController {
11 | func startGyroUpdates() {
12 | if !motionManager.isGyroAvailable {
13 | print("陀螺仪不可用")
14 | return
15 | }
16 | if motionManager.isGyroActive {
17 | return
18 | }
19 |
20 | motionManager.startGyroUpdates(to: OperationQueue.init()) { gyroData, error in
21 | guard let gyroData = gyroData else {
22 | print("startGyroUpdates error: \(error!)")
23 | return
24 | }
25 | // 获取陀螺仪数据
26 | self.rotationRate = gyroData.rotationRate
27 | }
28 | }
29 |
30 | func stopGyroUpdates() {
31 | motionManager.stopGyroUpdates()
32 | }
33 |
34 | func startAccelerometerUpdates() {
35 | if !motionManager.isAccelerometerAvailable {
36 | print("加速计不可用")
37 | return
38 | }
39 | if motionManager.isAccelerometerActive {
40 | return
41 | }
42 | motionManager.startAccelerometerUpdates(to: OperationQueue.init()) { accelerometerData, error in
43 | guard let accelerometerData = accelerometerData else {
44 | print("startAccelerometerUpdates error: \(error!)")
45 | return
46 | }
47 | // 获取加速计数据
48 | print("\(accelerometerData)")
49 | }
50 | }
51 |
52 | func stopAccelerometerUpdates() {
53 | motionManager.stopAccelerometerUpdates()
54 | }
55 |
56 | func startDeviceMotionUpdates() {
57 | motionManager.startDeviceMotionUpdates(to: OperationQueue.init()) { deviceMotion, error in
58 | guard let deviceMotion = deviceMotion else {
59 | print("startDeviceMotionUpdates error: \(error!)")
60 | return
61 | }
62 | // Gravity 获取重力值在各个方向上的分量,根据这个就可以获得手机的空间位置,倾斜角度等
63 | self.gravity = deviceMotion.gravity
64 | }
65 | }
66 |
67 | func stopDeviceMotionUpdates() {
68 | motionManager.stopDeviceMotionUpdates()
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/iOS/base/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // bevy_in_iOS
4 | //
5 | // Created by Jinlei Li on 2022/12/20.
6 | //
7 |
8 | import UIKit
9 | import CoreMotion
10 |
11 | class ViewController: UIViewController {
12 | @IBOutlet var metalV: MetalView!
13 | var bevyApp: OpaquePointer?
14 |
15 | var rotationRate: CMRotationRate?
16 | var gravity: CMAcceleration?
17 | lazy var motionManager: CMMotionManager = {
18 | let manager = CMMotionManager.init()
19 | manager.gyroUpdateInterval = 0.032
20 | manager.accelerometerUpdateInterval = 0.032
21 | manager.deviceMotionUpdateInterval = 0.032
22 | return manager
23 | }()
24 |
25 | lazy var displayLink: CADisplayLink = {
26 | CADisplayLink.init(target: self, selector: #selector(enterFrame))
27 | }()
28 |
29 | override func viewDidLoad() {
30 | super.viewDidLoad()
31 |
32 | self.displayLink.add(to: .current, forMode: .default)
33 | self.displayLink.isPaused = true
34 | }
35 |
36 | override func viewDidAppear(_ animated: Bool) {
37 | super.viewDidAppear(animated)
38 | self.view.backgroundColor = .white
39 | if bevyApp == nil {
40 | self.createBevyApp()
41 | }
42 | self.displayLink.isPaused = false
43 | self.startDeviceMotionUpdates()
44 | }
45 |
46 | override func viewWillDisappear(_ animated: Bool) {
47 | super.viewWillDisappear(animated)
48 | displayLink.isPaused = true
49 | self.stopDeviceMotionUpdates()
50 | }
51 |
52 | func createBevyApp() {
53 | let viewPointer = Unmanaged.passUnretained(self.metalV).toOpaque()
54 | let maximumFrames = Int32(UIScreen.main.maximumFramesPerSecond)
55 |
56 | bevyApp = create_bevy_app(viewPointer, maximumFrames, Float(UIScreen.main.nativeScale))
57 | }
58 |
59 | @IBAction func recreateBevyApp() {
60 | if let bevy = bevyApp {
61 | displayLink.isPaused = true
62 | release_bevy_app(bevy)
63 | }
64 |
65 | createBevyApp()
66 | displayLink.isPaused = false
67 | }
68 |
69 | @objc func enterFrame() {
70 | guard let bevy = self.bevyApp else {
71 | return
72 | }
73 | // call rust
74 | if let gravity = gravity {
75 | device_motion(bevy, Float(gravity.x), Float(gravity.y), Float(gravity.z))
76 | }
77 | enter_frame(bevy)
78 | }
79 |
80 | // MARK: touch
81 | override func touchesBegan(_ touches: Set, with event: UIEvent?) {
82 | if let bevy = self.bevyApp, let touch: UITouch = touches.first {
83 | let location = touch.location(in: self.metalV);
84 | touch_started(bevy, Float(location.x), Float(location.y));
85 | }
86 | }
87 |
88 | override func touchesMoved(_ touches: Set, with event: UIEvent?) {
89 | if let bevy = self.bevyApp, let touch: UITouch = touches.first {
90 | let location = touch.location(in: self.metalV);
91 | touch_moved(bevy, Float(location.x), Float(location.y));
92 | }
93 | }
94 |
95 | override func touchesEnded(_ touches: Set, with event: UIEvent?) {
96 | if let bevy = self.bevyApp, let touch: UITouch = touches.first {
97 | let location = touch.location(in: self.metalV);
98 | touch_ended(bevy, Float(location.x), Float(location.y));
99 | }
100 | }
101 |
102 | override func touchesCancelled(_ touches: Set, with event: UIEvent?) {
103 | if let bevy = self.bevyApp, let touch: UITouch = touches.first {
104 | let location = touch.location(in: self.metalV);
105 | touch_cancelled(bevy, Float(location.x), Float(location.y));
106 | }
107 | }
108 |
109 | deinit {
110 | if let bevy = bevyApp {
111 | release_bevy_app(bevy)
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/iOS/base/bevy_in_iOS-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // bevy_in_iOS-Bridging-Header.h
3 | // bevy_in_iOS
4 | //
5 | // Created by Jinlei Li on 2022/12/20.
6 | //
7 |
8 | #ifndef bevy_in_iOS_Bridging_Header_h
9 | #define bevy_in_iOS_Bridging_Header_h
10 |
11 | #import "libbevy_in_app.h"
12 |
13 | #endif /* bevy_in_iOS_Bridging_Header_h */
14 |
--------------------------------------------------------------------------------
/iOS/base/libbevy_in_app.h:
--------------------------------------------------------------------------------
1 | //
2 | // libbevy_in_app.h
3 | // bevy_in_iOS
4 | //
5 | // Created by Jinlei Li on 2022/12/20.
6 | //
7 |
8 | #ifndef libbevy_in_app_h
9 | #define libbevy_in_app_h
10 |
11 | #include
12 |
13 | // 这个不透明结构体用来指代 Rust 端的 Bevy App 对象
14 | struct bevy_app;
15 |
16 | struct bevy_app *create_bevy_app(void *view, int maximum_frames, float scale_factor);
17 |
18 | void enter_frame(struct bevy_app *app);
19 | void release_bevy_app(struct bevy_app *app);
20 |
21 | void touch_started(struct bevy_app *app, float x, float y);
22 | void touch_moved(struct bevy_app *app, float x, float y);
23 | void touch_ended(struct bevy_app *app, float x, float y);
24 | void touch_cancelled(struct bevy_app *app, float x, float y);
25 |
26 | // Gyroscope, Accelerometer, DeviceMotion
27 | void gyroscope_motion(struct bevy_app *app, float x, float y, float z);
28 | void accelerometer_motion(struct bevy_app *app, float x, float y, float z);
29 | void device_motion(struct bevy_app *app, float x, float y, float z);
30 |
31 | #endif /* libbevy_in_app_h */
32 |
--------------------------------------------------------------------------------
/iOS/bevy_in_iOS.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 56;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | AD4B1FED26EB1B90008A6AEE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD4B1FEC26EB1B90008A6AEE /* AppDelegate.swift */; };
11 | AD4B1FF126EB1B90008A6AEE /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD4B1FF026EB1B90008A6AEE /* ViewController.swift */; };
12 | AD4B1FF426EB1B90008A6AEE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AD4B1FF226EB1B90008A6AEE /* Main.storyboard */; };
13 | AD4B1FF626EB1B91008A6AEE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AD4B1FF526EB1B91008A6AEE /* Assets.xcassets */; };
14 | AD4B1FF926EB1B91008A6AEE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AD4B1FF726EB1B91008A6AEE /* LaunchScreen.storyboard */; };
15 | AD4B200126EB1D32008A6AEE /* MetalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD4B200026EB1D31008A6AEE /* MetalView.swift */; };
16 | AD6712AF2A6211F300CAA248 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = AD6712AE2A6211F300CAA248 /* assets */; };
17 | AD6C6A82295400160079516D /* CoreMotion.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AD6C6A81295400160079516D /* CoreMotion.framework */; };
18 | AD6C6A84295401050079516D /* ViewController+CoreMotion.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6C6A83295401050079516D /* ViewController+CoreMotion.swift */; };
19 | AD9EA281293B0CAF0079F1B4 /* libs in Resources */ = {isa = PBXBuildFile; fileRef = AD9EA280293B0CAF0079F1B4 /* libs */; };
20 | ADC0D0F0295161DA00388970 /* libbevy_in_app.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ADC0D0EF295161DA00388970 /* libbevy_in_app.a */; };
21 | ADC0D0F2295172CA00388970 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ADC0D0F1295172CA00388970 /* AudioToolbox.framework */; };
22 | /* End PBXBuildFile section */
23 |
24 | /* Begin PBXFileReference section */
25 | AD4B1FE926EB1B90008A6AEE /* bevy_in_iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bevy_in_iOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
26 | AD4B1FEC26EB1B90008A6AEE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
27 | AD4B1FF026EB1B90008A6AEE /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
28 | AD4B1FF326EB1B90008A6AEE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
29 | AD4B1FF526EB1B91008A6AEE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
30 | AD4B1FF826EB1B91008A6AEE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
31 | AD4B1FFA26EB1B91008A6AEE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
32 | AD4B200026EB1D31008A6AEE /* MetalView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetalView.swift; sourceTree = ""; };
33 | AD6712AE2A6211F300CAA248 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = assets; path = ../assets; sourceTree = ""; };
34 | AD6C6A81295400160079516D /* CoreMotion.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMotion.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.1.sdk/System/Library/Frameworks/CoreMotion.framework; sourceTree = DEVELOPER_DIR; };
35 | AD6C6A83295401050079516D /* ViewController+CoreMotion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ViewController+CoreMotion.swift"; sourceTree = ""; };
36 | AD9EA280293B0CAF0079F1B4 /* libs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = libs; sourceTree = ""; };
37 | ADA4699026EB27CB008B1C7B /* libbevy_in_app.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = libbevy_in_app.h; sourceTree = ""; };
38 | ADA4699226EB2F9A008B1C7B /* bevy_in_iOS-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "bevy_in_iOS-Bridging-Header.h"; sourceTree = ""; };
39 | ADC0D0EF295161DA00388970 /* libbevy_in_app.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libbevy_in_app.a; path = libs/debug/libbevy_in_app.a; sourceTree = ""; };
40 | ADC0D0F1295172CA00388970 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.1.sdk/System/Library/Frameworks/AudioToolbox.framework; sourceTree = DEVELOPER_DIR; };
41 | /* End PBXFileReference section */
42 |
43 | /* Begin PBXFrameworksBuildPhase section */
44 | AD4B1FE626EB1B90008A6AEE /* Frameworks */ = {
45 | isa = PBXFrameworksBuildPhase;
46 | buildActionMask = 2147483647;
47 | files = (
48 | ADC0D0F0295161DA00388970 /* libbevy_in_app.a in Frameworks */,
49 | AD6C6A82295400160079516D /* CoreMotion.framework in Frameworks */,
50 | ADC0D0F2295172CA00388970 /* AudioToolbox.framework in Frameworks */,
51 | );
52 | runOnlyForDeploymentPostprocessing = 0;
53 | };
54 | /* End PBXFrameworksBuildPhase section */
55 |
56 | /* Begin PBXGroup section */
57 | AD0CB7E127A4061E0041811B /* base */ = {
58 | isa = PBXGroup;
59 | children = (
60 | AD4B1FF026EB1B90008A6AEE /* ViewController.swift */,
61 | AD6C6A83295401050079516D /* ViewController+CoreMotion.swift */,
62 | ADA4699026EB27CB008B1C7B /* libbevy_in_app.h */,
63 | AD4B200026EB1D31008A6AEE /* MetalView.swift */,
64 | ADA4699226EB2F9A008B1C7B /* bevy_in_iOS-Bridging-Header.h */,
65 | AD4B1FEC26EB1B90008A6AEE /* AppDelegate.swift */,
66 | AD4B1FF226EB1B90008A6AEE /* Main.storyboard */,
67 | AD4B1FF526EB1B91008A6AEE /* Assets.xcassets */,
68 | );
69 | path = base;
70 | sourceTree = "";
71 | };
72 | AD4B1FE026EB1B90008A6AEE = {
73 | isa = PBXGroup;
74 | children = (
75 | AD6712AE2A6211F300CAA248 /* assets */,
76 | AD9EA280293B0CAF0079F1B4 /* libs */,
77 | AD0CB7E127A4061E0041811B /* base */,
78 | AD4B1FEB26EB1B90008A6AEE /* bevy_in_iOS */,
79 | AD4B1FEA26EB1B90008A6AEE /* Products */,
80 | ADA4699326EB3079008B1C7B /* Frameworks */,
81 | );
82 | sourceTree = "";
83 | };
84 | AD4B1FEA26EB1B90008A6AEE /* Products */ = {
85 | isa = PBXGroup;
86 | children = (
87 | AD4B1FE926EB1B90008A6AEE /* bevy_in_iOS.app */,
88 | );
89 | name = Products;
90 | sourceTree = "";
91 | };
92 | AD4B1FEB26EB1B90008A6AEE /* bevy_in_iOS */ = {
93 | isa = PBXGroup;
94 | children = (
95 | AD4B1FF726EB1B91008A6AEE /* LaunchScreen.storyboard */,
96 | AD4B1FFA26EB1B91008A6AEE /* Info.plist */,
97 | );
98 | path = bevy_in_iOS;
99 | sourceTree = "";
100 | };
101 | ADA4699326EB3079008B1C7B /* Frameworks */ = {
102 | isa = PBXGroup;
103 | children = (
104 | AD6C6A81295400160079516D /* CoreMotion.framework */,
105 | ADC0D0F1295172CA00388970 /* AudioToolbox.framework */,
106 | ADC0D0EF295161DA00388970 /* libbevy_in_app.a */,
107 | );
108 | name = Frameworks;
109 | sourceTree = "";
110 | };
111 | /* End PBXGroup section */
112 |
113 | /* Begin PBXNativeTarget section */
114 | AD4B1FE826EB1B90008A6AEE /* bevy_in_iOS */ = {
115 | isa = PBXNativeTarget;
116 | buildConfigurationList = AD4B1FFD26EB1B91008A6AEE /* Build configuration list for PBXNativeTarget "bevy_in_iOS" */;
117 | buildPhases = (
118 | AD4B1FE526EB1B90008A6AEE /* Sources */,
119 | AD4B1FE626EB1B90008A6AEE /* Frameworks */,
120 | AD4B1FE726EB1B90008A6AEE /* Resources */,
121 | );
122 | buildRules = (
123 | );
124 | dependencies = (
125 | );
126 | name = bevy_in_iOS;
127 | productName = wgpu_test;
128 | productReference = AD4B1FE926EB1B90008A6AEE /* bevy_in_iOS.app */;
129 | productType = "com.apple.product-type.application";
130 | };
131 | /* End PBXNativeTarget section */
132 |
133 | /* Begin PBXProject section */
134 | AD4B1FE126EB1B90008A6AEE /* Project object */ = {
135 | isa = PBXProject;
136 | attributes = {
137 | LastSwiftUpdateCheck = 1400;
138 | LastUpgradeCheck = 1400;
139 | TargetAttributes = {
140 | AD4B1FE826EB1B90008A6AEE = {
141 | CreatedOnToolsVersion = 12.5.1;
142 | };
143 | };
144 | };
145 | buildConfigurationList = AD4B1FE426EB1B90008A6AEE /* Build configuration list for PBXProject "bevy_in_iOS" */;
146 | compatibilityVersion = "Xcode 14.0";
147 | developmentRegion = en;
148 | hasScannedForEncodings = 0;
149 | knownRegions = (
150 | en,
151 | Base,
152 | );
153 | mainGroup = AD4B1FE026EB1B90008A6AEE;
154 | productRefGroup = AD4B1FEA26EB1B90008A6AEE /* Products */;
155 | projectDirPath = "";
156 | projectRoot = "";
157 | targets = (
158 | AD4B1FE826EB1B90008A6AEE /* bevy_in_iOS */,
159 | );
160 | };
161 | /* End PBXProject section */
162 |
163 | /* Begin PBXResourcesBuildPhase section */
164 | AD4B1FE726EB1B90008A6AEE /* Resources */ = {
165 | isa = PBXResourcesBuildPhase;
166 | buildActionMask = 2147483647;
167 | files = (
168 | AD6712AF2A6211F300CAA248 /* assets in Resources */,
169 | AD4B1FF926EB1B91008A6AEE /* LaunchScreen.storyboard in Resources */,
170 | AD4B1FF626EB1B91008A6AEE /* Assets.xcassets in Resources */,
171 | AD9EA281293B0CAF0079F1B4 /* libs in Resources */,
172 | AD4B1FF426EB1B90008A6AEE /* Main.storyboard in Resources */,
173 | );
174 | runOnlyForDeploymentPostprocessing = 0;
175 | };
176 | /* End PBXResourcesBuildPhase section */
177 |
178 | /* Begin PBXSourcesBuildPhase section */
179 | AD4B1FE526EB1B90008A6AEE /* Sources */ = {
180 | isa = PBXSourcesBuildPhase;
181 | buildActionMask = 2147483647;
182 | files = (
183 | AD4B200126EB1D32008A6AEE /* MetalView.swift in Sources */,
184 | AD4B1FF126EB1B90008A6AEE /* ViewController.swift in Sources */,
185 | AD4B1FED26EB1B90008A6AEE /* AppDelegate.swift in Sources */,
186 | AD6C6A84295401050079516D /* ViewController+CoreMotion.swift in Sources */,
187 | );
188 | runOnlyForDeploymentPostprocessing = 0;
189 | };
190 | /* End PBXSourcesBuildPhase section */
191 |
192 | /* Begin PBXVariantGroup section */
193 | AD4B1FF226EB1B90008A6AEE /* Main.storyboard */ = {
194 | isa = PBXVariantGroup;
195 | children = (
196 | AD4B1FF326EB1B90008A6AEE /* Base */,
197 | );
198 | name = Main.storyboard;
199 | sourceTree = "";
200 | };
201 | AD4B1FF726EB1B91008A6AEE /* LaunchScreen.storyboard */ = {
202 | isa = PBXVariantGroup;
203 | children = (
204 | AD4B1FF826EB1B91008A6AEE /* Base */,
205 | );
206 | name = LaunchScreen.storyboard;
207 | sourceTree = "";
208 | };
209 | /* End PBXVariantGroup section */
210 |
211 | /* Begin XCBuildConfiguration section */
212 | AD4B1FFB26EB1B91008A6AEE /* Debug */ = {
213 | isa = XCBuildConfiguration;
214 | buildSettings = {
215 | ALWAYS_SEARCH_USER_PATHS = NO;
216 | CLANG_ANALYZER_NONNULL = YES;
217 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
218 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
219 | CLANG_CXX_LIBRARY = "libc++";
220 | CLANG_ENABLE_MODULES = YES;
221 | CLANG_ENABLE_OBJC_ARC = YES;
222 | CLANG_ENABLE_OBJC_WEAK = YES;
223 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
224 | CLANG_WARN_BOOL_CONVERSION = YES;
225 | CLANG_WARN_COMMA = YES;
226 | CLANG_WARN_CONSTANT_CONVERSION = YES;
227 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
228 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
229 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
230 | CLANG_WARN_EMPTY_BODY = YES;
231 | CLANG_WARN_ENUM_CONVERSION = YES;
232 | CLANG_WARN_INFINITE_RECURSION = YES;
233 | CLANG_WARN_INT_CONVERSION = YES;
234 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
235 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
236 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
237 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
238 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
239 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
240 | CLANG_WARN_STRICT_PROTOTYPES = YES;
241 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
242 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
243 | CLANG_WARN_UNREACHABLE_CODE = YES;
244 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
245 | COPY_PHASE_STRIP = NO;
246 | DEBUG_INFORMATION_FORMAT = dwarf;
247 | ENABLE_STRICT_OBJC_MSGSEND = YES;
248 | ENABLE_TESTABILITY = YES;
249 | GCC_C_LANGUAGE_STANDARD = gnu11;
250 | GCC_DYNAMIC_NO_PIC = NO;
251 | GCC_NO_COMMON_BLOCKS = YES;
252 | GCC_OPTIMIZATION_LEVEL = 0;
253 | GCC_PREPROCESSOR_DEFINITIONS = (
254 | "DEBUG=1",
255 | "$(inherited)",
256 | );
257 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
258 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
259 | GCC_WARN_UNDECLARED_SELECTOR = YES;
260 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
261 | GCC_WARN_UNUSED_FUNCTION = YES;
262 | GCC_WARN_UNUSED_VARIABLE = YES;
263 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
264 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
265 | MTL_FAST_MATH = YES;
266 | ONLY_ACTIVE_ARCH = YES;
267 | SDKROOT = iphoneos;
268 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
269 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
270 | };
271 | name = Debug;
272 | };
273 | AD4B1FFC26EB1B91008A6AEE /* Release */ = {
274 | isa = XCBuildConfiguration;
275 | buildSettings = {
276 | ALWAYS_SEARCH_USER_PATHS = NO;
277 | CLANG_ANALYZER_NONNULL = YES;
278 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
279 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
280 | CLANG_CXX_LIBRARY = "libc++";
281 | CLANG_ENABLE_MODULES = YES;
282 | CLANG_ENABLE_OBJC_ARC = YES;
283 | CLANG_ENABLE_OBJC_WEAK = YES;
284 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
285 | CLANG_WARN_BOOL_CONVERSION = YES;
286 | CLANG_WARN_COMMA = YES;
287 | CLANG_WARN_CONSTANT_CONVERSION = YES;
288 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
289 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
290 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
291 | CLANG_WARN_EMPTY_BODY = YES;
292 | CLANG_WARN_ENUM_CONVERSION = YES;
293 | CLANG_WARN_INFINITE_RECURSION = YES;
294 | CLANG_WARN_INT_CONVERSION = YES;
295 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
296 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
297 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
298 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
299 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
300 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
301 | CLANG_WARN_STRICT_PROTOTYPES = YES;
302 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
303 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
304 | CLANG_WARN_UNREACHABLE_CODE = YES;
305 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
306 | COPY_PHASE_STRIP = NO;
307 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
308 | ENABLE_NS_ASSERTIONS = NO;
309 | ENABLE_STRICT_OBJC_MSGSEND = YES;
310 | GCC_C_LANGUAGE_STANDARD = gnu11;
311 | GCC_NO_COMMON_BLOCKS = YES;
312 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
313 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
314 | GCC_WARN_UNDECLARED_SELECTOR = YES;
315 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
316 | GCC_WARN_UNUSED_FUNCTION = YES;
317 | GCC_WARN_UNUSED_VARIABLE = YES;
318 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
319 | MTL_ENABLE_DEBUG_INFO = NO;
320 | MTL_FAST_MATH = YES;
321 | SDKROOT = iphoneos;
322 | SWIFT_COMPILATION_MODE = wholemodule;
323 | SWIFT_OPTIMIZATION_LEVEL = "-O";
324 | VALIDATE_PRODUCT = YES;
325 | };
326 | name = Release;
327 | };
328 | AD4B1FFE26EB1B91008A6AEE /* Debug */ = {
329 | isa = XCBuildConfiguration;
330 | buildSettings = {
331 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
332 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
333 | CODE_SIGN_ENTITLEMENTS = "";
334 | CODE_SIGN_STYLE = Automatic;
335 | CURRENT_PROJECT_VERSION = 1.0;
336 | DEVELOPMENT_TEAM = P3HQ9GDFCW;
337 | INFOPLIST_FILE = bevy_in_iOS/Info.plist;
338 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
339 | "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2;
340 | LD_RUNPATH_SEARCH_PATHS = (
341 | "$(inherited)",
342 | "@executable_path/Frameworks",
343 | );
344 | LIBRARY_SEARCH_PATHS = (
345 | "$(PROJECT_DIR)/libs/debug",
346 | "$(PROJECT_DIR)",
347 | );
348 | MACOSX_DEPLOYMENT_TARGET = 12.0;
349 | MARKETING_VERSION = 1.0;
350 | OTHER_LDFLAGS = "$(inherited)";
351 | PRODUCT_BUNDLE_IDENTIFIER = "com.lijinlei.zixi.bevy-in-ios";
352 | PRODUCT_NAME = "$(TARGET_NAME)";
353 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
354 | SUPPORTS_MACCATALYST = YES;
355 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
356 | SWIFT_OBJC_BRIDGING_HEADER = "base/bevy_in_iOS-Bridging-Header.h";
357 | SWIFT_VERSION = 5.0;
358 | TARGETED_DEVICE_FAMILY = "1,2,6";
359 | };
360 | name = Debug;
361 | };
362 | AD4B1FFF26EB1B91008A6AEE /* Release */ = {
363 | isa = XCBuildConfiguration;
364 | buildSettings = {
365 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
366 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
367 | CODE_SIGN_ENTITLEMENTS = "";
368 | CODE_SIGN_STYLE = Automatic;
369 | CURRENT_PROJECT_VERSION = 1.0;
370 | DEVELOPMENT_TEAM = P3HQ9GDFCW;
371 | INFOPLIST_FILE = bevy_in_iOS/Info.plist;
372 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
373 | "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2;
374 | LD_RUNPATH_SEARCH_PATHS = (
375 | "$(inherited)",
376 | "@executable_path/Frameworks",
377 | );
378 | LIBRARY_SEARCH_PATHS = (
379 | "$(PROJECT_DIR)/libs/release",
380 | "$(PROJECT_DIR)",
381 | "$(PROJECT_DIR)/libs/debug",
382 | );
383 | MACOSX_DEPLOYMENT_TARGET = 12.0;
384 | MARKETING_VERSION = 1.0;
385 | OTHER_LDFLAGS = "$(inherited)";
386 | PRODUCT_BUNDLE_IDENTIFIER = "com.lijinlei.zixi.bevy-in-ios";
387 | PRODUCT_NAME = "$(TARGET_NAME)";
388 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
389 | SUPPORTS_MACCATALYST = YES;
390 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
391 | SWIFT_OBJC_BRIDGING_HEADER = "base/bevy_in_iOS-Bridging-Header.h";
392 | SWIFT_VERSION = 5.0;
393 | TARGETED_DEVICE_FAMILY = "1,2,6";
394 | };
395 | name = Release;
396 | };
397 | /* End XCBuildConfiguration section */
398 |
399 | /* Begin XCConfigurationList section */
400 | AD4B1FE426EB1B90008A6AEE /* Build configuration list for PBXProject "bevy_in_iOS" */ = {
401 | isa = XCConfigurationList;
402 | buildConfigurations = (
403 | AD4B1FFB26EB1B91008A6AEE /* Debug */,
404 | AD4B1FFC26EB1B91008A6AEE /* Release */,
405 | );
406 | defaultConfigurationIsVisible = 0;
407 | defaultConfigurationName = Release;
408 | };
409 | AD4B1FFD26EB1B91008A6AEE /* Build configuration list for PBXNativeTarget "bevy_in_iOS" */ = {
410 | isa = XCConfigurationList;
411 | buildConfigurations = (
412 | AD4B1FFE26EB1B91008A6AEE /* Debug */,
413 | AD4B1FFF26EB1B91008A6AEE /* Release */,
414 | );
415 | defaultConfigurationIsVisible = 0;
416 | defaultConfigurationName = Release;
417 | };
418 | /* End XCConfigurationList section */
419 | };
420 | rootObject = AD4B1FE126EB1B90008A6AEE /* Project object */;
421 | }
422 |
--------------------------------------------------------------------------------
/iOS/bevy_in_iOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/iOS/bevy_in_iOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/iOS/bevy_in_iOS.xcodeproj/project.xcworkspace/xcuserdata/LiJinlei.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jinleili/bevy-in-app/945bda11ab8cb9f4a57a6a390456bca3c78082c1/iOS/bevy_in_iOS.xcodeproj/project.xcworkspace/xcuserdata/LiJinlei.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/iOS/bevy_in_iOS.xcodeproj/xcshareddata/xcschemes/bevy_in_iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
44 |
46 |
52 |
53 |
54 |
55 |
58 |
59 |
60 |
61 |
65 |
66 |
70 |
71 |
75 |
76 |
80 |
81 |
85 |
86 |
90 |
91 |
92 |
93 |
99 |
100 |
102 |
103 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/iOS/bevy_in_iOS.xcodeproj/xcuserdata/LiJinlei.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/iOS/bevy_in_iOS.xcodeproj/xcuserdata/LiJinlei.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | bevy_in_iOS.xcscheme_^#shared#^_
8 |
9 | isShown
10 |
11 | orderHint
12 | 0
13 |
14 | mac_app.xcscheme_^#shared#^_
15 |
16 | orderHint
17 | 2
18 |
19 |
20 | SuppressBuildableAutocreation
21 |
22 | AD0CB7C227A402060041811B
23 |
24 | primary
25 |
26 |
27 | AD10185828E195F5003845D3
28 |
29 | primary
30 |
31 |
32 | AD4B1FE826EB1B90008A6AEE
33 |
34 | primary
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/iOS/bevy_in_iOS/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/iOS/bevy_in_iOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 |
28 | UIApplicationSupportsIndirectInputEvents
29 |
30 | UILaunchStoryboardName
31 | LaunchScreen
32 | UIMainStoryboardFile
33 | Main
34 | UIRequiredDeviceCapabilities
35 |
36 | arm64
37 |
38 | UISupportedInterfaceOrientations
39 |
40 | UIInterfaceOrientationPortrait
41 |
42 | UISupportedInterfaceOrientations~ipad
43 |
44 | UIInterfaceOrientationPortrait
45 | UIInterfaceOrientationPortraitUpsideDown
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/ios_build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Stop subsequent execution when encountering any errors
4 | set -e
5 |
6 | TARGET=${1}
7 | RELEASE_MODE=${2}
8 |
9 | if [ ! ${TARGET} ]; then
10 | : ${TARGET:=aarch64-apple-ios}
11 | fi
12 |
13 | if [ "${TARGET}" = "--release" ]; then
14 | TARGET="aarch64-apple-ios"
15 | : ${RELEASE_MODE:=--release}
16 | fi
17 |
18 | cargo build --target ${TARGET} ${RELEASE_MODE}
19 |
20 | # Copy libbevy_in_app.a to iOS project
21 | #
22 | # Why copy?
23 | # On Xcode 14.1, when xxx..dylib file exists in the library search path, Xcode will try to reference it and report an error:
24 | # Dylib (/Users/XXX/bevy-in-app/target/aarch64-apple-ios/debug/libbevy_in_app.dylib) was built for newer iOS version (16.1) than being linked (13.0)
25 |
26 | LIB_FOLDER=
27 | case ${RELEASE_MODE} in
28 | "--release") : ${LIB_FOLDER:=release} ;;
29 | *) : ${LIB_FOLDER:=debug} ;;
30 | esac
31 |
32 | if [ ! -d "iOS/libs/${LIB_FOLDER}/" ]; then
33 | mkdir -p "iOS/libs/${LIB_FOLDER}"
34 | fi
35 |
36 | cp target/${TARGET}/${LIB_FOLDER}/libbevy_in_app.a iOS/libs/${LIB_FOLDER}/libbevy_in_app.a
37 |
--------------------------------------------------------------------------------
/src/android_asset_io.rs:
--------------------------------------------------------------------------------
1 | use bevy::{
2 | asset::io::{
3 | AssetReader, AssetReaderError, AssetSource, AssetSourceId, PathStream, Reader, VecReader,
4 | },
5 | prelude::*,
6 | utils::BoxedFuture,
7 | };
8 | use ndk::asset::AssetManager;
9 | use std::{
10 | ffi::CString,
11 | path::{Path, PathBuf},
12 | };
13 |
14 | /// *mut ndk_sys::AAssetManager 无法实现 send
15 | pub static ASSET_MANAGER: std::sync::OnceLock = std::sync::OnceLock::new();
16 |
17 | pub struct AndroidAssetManager(pub *mut ndk_sys::AAssetManager);
18 |
19 | impl Default for AndroidAssetManager {
20 | fn default() -> Self {
21 | Self(std::ptr::null_mut())
22 | }
23 | }
24 |
25 | pub struct AndroidAssetIoPlugin;
26 |
27 | impl Plugin for AndroidAssetIoPlugin {
28 | fn build(&self, app: &mut App) {
29 | let android_asset_manager = app
30 | .world_mut()
31 | .remove_non_send_resource::()
32 | .unwrap();
33 | let asset_manager = unsafe {
34 | AssetManager::from_ptr(std::ptr::NonNull::new(android_asset_manager.0).unwrap())
35 | };
36 | let _ = ASSET_MANAGER.set(asset_manager);
37 |
38 | // override bevy default asset reader
39 | // https://github.com/bevyengine/bevy/pull/9885
40 | app.register_asset_source(
41 | AssetSourceId::Default,
42 | AssetSource::build()
43 | .with_reader(|| Box::new(AndroidAssetIo::new("assets".to_string()))),
44 | );
45 | }
46 | }
47 |
48 | #[allow(dead_code)]
49 | struct AndroidAssetIo {
50 | root_path: PathBuf,
51 | }
52 |
53 | impl AndroidAssetIo {
54 | pub fn new>(path: P) -> Self {
55 | AndroidAssetIo {
56 | root_path: path.as_ref().to_owned(),
57 | }
58 | }
59 | }
60 |
61 | impl AssetReader for AndroidAssetIo {
62 | fn read<'a>(
63 | &'a self,
64 | path: &'a Path,
65 | ) -> BoxedFuture<'a, Result>, AssetReaderError>> {
66 | Box::pin(async move {
67 | let mut opened_asset = ASSET_MANAGER
68 | .get()
69 | .unwrap()
70 | .open(&CString::new(path.to_str().unwrap()).unwrap())
71 | .ok_or(AssetReaderError::NotFound(path.to_path_buf()))?;
72 | let bytes = opened_asset.get_buffer()?;
73 | let reader: Box = Box::new(VecReader::new(bytes.to_vec()));
74 | Ok(reader)
75 | })
76 | }
77 |
78 | fn read_directory<'a>(
79 | &'a self,
80 | path: &'a Path,
81 | ) -> BoxedFuture<'a, Result, AssetReaderError>> {
82 | error!("Reading directories is not supported with the AndroidAssetReader");
83 | Box::pin(async move { Err(AssetReaderError::NotFound(path.to_path_buf())) })
84 | }
85 |
86 | fn read_meta<'a>(
87 | &'a self,
88 | path: &'a Path,
89 | ) -> BoxedFuture<'a, Result>, AssetReaderError>> {
90 | Box::pin(async move {
91 | let meta_path = get_meta_path(path);
92 | let mut opened_asset = ASSET_MANAGER
93 | .get()
94 | .unwrap()
95 | .open(&CString::new(meta_path.to_str().unwrap()).unwrap())
96 | .ok_or(AssetReaderError::NotFound(meta_path))?;
97 | let bytes = opened_asset.get_buffer()?;
98 | let reader: Box = Box::new(VecReader::new(bytes.to_vec()));
99 | Ok(reader)
100 | })
101 | }
102 |
103 | fn is_directory<'a>(
104 | &'a self,
105 | _path: &'a Path,
106 | ) -> BoxedFuture<'a, std::result::Result> {
107 | error!("Reading directories is not supported with the AndroidAssetReader");
108 | Box::pin(async move { Ok(false) })
109 | }
110 | }
111 |
112 | /// Appends `.meta` to the given path.
113 | pub(crate) fn get_meta_path(path: &Path) -> PathBuf {
114 | let mut meta_path = path.to_path_buf();
115 | let mut extension = path
116 | .extension()
117 | .expect("asset paths must have extensions")
118 | .to_os_string();
119 | extension.push(".meta");
120 | meta_path.set_extension(extension);
121 | meta_path
122 | }
123 |
--------------------------------------------------------------------------------
/src/app_view/android.rs:
--------------------------------------------------------------------------------
1 | use core::ffi::c_void;
2 | use jni::sys::jobject;
3 | use jni::JNIEnv;
4 | use raw_window_handle::{
5 | AndroidDisplayHandle, AndroidNdkWindowHandle, DisplayHandle, HandleError, HasDisplayHandle,
6 | HasWindowHandle, RawDisplayHandle, RawWindowHandle, WindowHandle,
7 | };
8 | use std::sync::{Arc, Mutex};
9 |
10 | #[derive(Debug, Clone)]
11 | pub struct AndroidViewObj {
12 | pub native_window: NativeWindow,
13 | pub scale_factor: f32,
14 | }
15 |
16 | #[derive(Debug)]
17 | pub struct AppView {
18 | pub view_obj: super::SendSyncWrapper,
19 | }
20 |
21 | impl std::ops::Deref for AppView {
22 | type Target = AndroidViewObj;
23 | fn deref(&self) -> &Self::Target {
24 | &self.view_obj.0
25 | }
26 | }
27 |
28 | impl AppView {
29 | pub fn new(view_obj: AndroidViewObj) -> Self {
30 | Self {
31 | view_obj: super::SendSyncWrapper(view_obj),
32 | }
33 | }
34 |
35 | pub fn logical_resolution(&self) -> (f32, f32) {
36 | (
37 | self.get_width() as f32 / self.scale_factor,
38 | self.get_height() as f32 / self.scale_factor,
39 | )
40 | }
41 |
42 | fn get_width(&self) -> u32 {
43 | self.native_window.get_width()
44 | }
45 |
46 | fn get_height(&self) -> u32 {
47 | self.native_window.get_height()
48 | }
49 | }
50 |
51 | impl HasWindowHandle for AppView {
52 | fn window_handle(&self) -> Result {
53 | unsafe {
54 | let a_native_window = self.native_window.a_native_window.lock().unwrap();
55 | let handle = AndroidNdkWindowHandle::new(
56 | std::ptr::NonNull::new(*a_native_window as *mut _ as *mut c_void).unwrap(),
57 | );
58 | Ok(WindowHandle::borrow_raw(RawWindowHandle::AndroidNdk(
59 | handle,
60 | )))
61 | }
62 | }
63 | }
64 |
65 | impl HasDisplayHandle for AppView {
66 | fn display_handle(&self) -> Result, HandleError> {
67 | unsafe {
68 | Ok(DisplayHandle::borrow_raw(RawDisplayHandle::Android(
69 | AndroidDisplayHandle::new(),
70 | )))
71 | }
72 | }
73 | }
74 |
75 | #[derive(Debug, Clone)]
76 | pub struct NativeWindow {
77 | a_native_window: Arc>,
78 | }
79 |
80 | impl Default for NativeWindow {
81 | fn default() -> Self {
82 | Self {
83 | a_native_window: Arc::new(Mutex::new(std::ptr::null_mut())),
84 | }
85 | }
86 | }
87 |
88 | impl NativeWindow {
89 | pub fn new(env: *mut JNIEnv, surface: jobject) -> Self {
90 | let a_native_window = unsafe {
91 | // 获取与安卓端 surface 对象关联的 ANativeWindow,以便能通过 Rust 与之交互。
92 | // 此函数在返回 ANativeWindow 的同时会自动将其引用计数 +1,以防止该对象在安卓端被意外释放。
93 | ndk_sys::ANativeWindow_fromSurface(env as *mut _, surface as *mut _)
94 | };
95 | Self {
96 | a_native_window: Arc::new(Mutex::new(a_native_window)),
97 | }
98 | }
99 |
100 | fn get_width(&self) -> u32 {
101 | unsafe { ndk_sys::ANativeWindow_getWidth(*self.a_native_window.lock().unwrap()) as u32 }
102 | }
103 |
104 | fn get_height(&self) -> u32 {
105 | unsafe { ndk_sys::ANativeWindow_getHeight(*self.a_native_window.lock().unwrap()) as u32 }
106 | }
107 | }
108 |
109 | impl Drop for NativeWindow {
110 | fn drop(&mut self) {
111 | unsafe {
112 | ndk_sys::ANativeWindow_release(*self.a_native_window.lock().unwrap());
113 | }
114 | }
115 | }
116 |
117 | unsafe impl Send for NativeWindow {}
118 | unsafe impl Sync for NativeWindow {}
119 |
--------------------------------------------------------------------------------
/src/app_view/app_views.rs:
--------------------------------------------------------------------------------
1 | use super::{AppView, AppViewWindow};
2 | use bevy::ecs::entity::Entity;
3 | use bevy::utils::HashMap;
4 | use bevy::window::WindowWrapper;
5 |
6 | #[derive(Debug, Default)]
7 | pub struct AppViews {
8 | views: HashMap,
9 | entity_to_window_id: HashMap,
10 | }
11 |
12 | impl AppViews {
13 | pub fn create_window(
14 | &mut self,
15 | #[cfg(target_os = "ios")] view_obj: super::IOSViewObj,
16 | #[cfg(target_os = "android")] view_obj: super::AndroidViewObj,
17 | entity: Entity,
18 | ) -> &AppViewWindow {
19 | let app_view = AppViewWindow(WindowWrapper::new(AppView::new(view_obj)));
20 | let window_id = super::WindowId::new();
21 | self.entity_to_window_id.insert(entity, window_id);
22 |
23 | self.views.entry(window_id).insert(app_view).into_mut()
24 | }
25 |
26 | /// Get the AppView that is associated with our entity.
27 | pub fn get_view(&self, entity: Entity) -> Option<&AppViewWindow> {
28 | self.entity_to_window_id
29 | .get(&entity)
30 | .and_then(|window_id| self.views.get(window_id))
31 | }
32 |
33 | /// This should mostly just be called when the window is closing.
34 | pub fn remove_view(&mut self, entity: Entity) -> Option {
35 | let window_id = self.entity_to_window_id.remove(&entity)?;
36 | self.views.remove(&window_id)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/app_view/ios.rs:
--------------------------------------------------------------------------------
1 | use core_graphics::geometry::CGRect;
2 | use objc::{runtime::Object, *};
3 | use raw_window_handle::{
4 | DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, RawDisplayHandle,
5 | RawWindowHandle, UiKitDisplayHandle, UiKitWindowHandle, WindowHandle,
6 | };
7 |
8 | #[derive(Debug, Copy, Clone)]
9 | pub struct IOSViewObj {
10 | pub view: *mut Object,
11 | pub scale_factor: f32,
12 | }
13 |
14 | impl Default for IOSViewObj {
15 | fn default() -> Self {
16 | Self {
17 | view: std::ptr::null_mut(),
18 | scale_factor: 1.0,
19 | }
20 | }
21 | }
22 |
23 | #[derive(Debug)]
24 | pub struct AppView {
25 | pub view_obj: super::SendSyncWrapper,
26 | }
27 |
28 | impl std::ops::Deref for AppView {
29 | type Target = IOSViewObj;
30 | fn deref(&self) -> &Self::Target {
31 | &self.view_obj.0
32 | }
33 | }
34 |
35 | impl AppView {
36 | pub fn new(view_obj: IOSViewObj) -> Self {
37 | Self {
38 | view_obj: super::SendSyncWrapper(view_obj),
39 | }
40 | }
41 |
42 | pub fn logical_resolution(&self) -> (f32, f32) {
43 | let s: CGRect = unsafe { msg_send![self.view, frame] };
44 | (s.size.width as f32, s.size.height as f32)
45 | }
46 | }
47 |
48 | impl HasWindowHandle for AppView {
49 | fn window_handle(&self) -> Result {
50 | Ok(unsafe {
51 | WindowHandle::borrow_raw(RawWindowHandle::UiKit(UiKitWindowHandle::new({
52 | let ui_view = self.view as _;
53 | std::ptr::NonNull::new(ui_view).unwrap()
54 | })))
55 | })
56 | }
57 | }
58 |
59 | impl HasDisplayHandle for AppView {
60 | fn display_handle(&self) -> Result {
61 | Ok(
62 | unsafe {
63 | DisplayHandle::borrow_raw(RawDisplayHandle::UiKit(UiKitDisplayHandle::new()))
64 | },
65 | )
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/app_view/mod.rs:
--------------------------------------------------------------------------------
1 | use bevy::app::{App, Plugin};
2 | use bevy::ecs::{
3 | entity::Entity,
4 | event::EventWriter,
5 | prelude::*,
6 | removal_detection::RemovedComponents,
7 | system::{Commands, NonSendMut, Query, SystemState},
8 | };
9 | use bevy::log::info;
10 | use bevy::window::{
11 | exit_on_all_closed, RawHandleWrapper, Window, WindowClosed, WindowCreated, WindowWrapper,
12 | };
13 | use std::ops::Deref;
14 | use uuid::Uuid;
15 |
16 | #[derive(Eq, Hash, PartialEq, Debug, Copy, Clone)]
17 | pub(crate) struct WindowId(Uuid);
18 |
19 | impl WindowId {
20 | pub fn new() -> Self {
21 | WindowId(Uuid::new_v4())
22 | }
23 | }
24 |
25 | #[cfg_attr(target_os = "ios", path = "ios.rs")]
26 | #[cfg_attr(target_os = "android", path = "android.rs")]
27 | mod view;
28 | pub use view::*;
29 |
30 | mod app_views;
31 | use app_views::AppViews;
32 |
33 | #[derive(Clone, Debug)]
34 | pub(crate) struct SendSyncWrapper(pub(crate) T);
35 |
36 | unsafe impl Send for SendSyncWrapper {}
37 | unsafe impl Sync for SendSyncWrapper {}
38 |
39 | // 封装 AppViewWindow
40 | #[derive(Debug)]
41 | pub struct AppViewWindow(pub(crate) WindowWrapper);
42 |
43 | impl Deref for AppViewWindow {
44 | type Target = AppView;
45 |
46 | fn deref(&self) -> &Self::Target {
47 | &self.0
48 | }
49 | }
50 |
51 | pub struct AppViewPlugin;
52 |
53 | impl Plugin for AppViewPlugin {
54 | fn build(&self, app: &mut App) {
55 | app.init_non_send_resource::().add_systems(
56 | bevy::app::Last,
57 | (
58 | changed_window.ambiguous_with(exit_on_all_closed),
59 | // Update the state of the window before attempting to despawn to ensure consistent event ordering
60 | despawn_window.after(changed_window),
61 | ),
62 | );
63 | }
64 | }
65 |
66 | #[allow(unused, clippy::type_complexity)]
67 | pub fn create_bevy_window(app: &mut App) {
68 | #[cfg(target_os = "ios")]
69 | let view_obj = app
70 | .world_mut()
71 | .remove_non_send_resource::()
72 | .unwrap();
73 | #[cfg(target_os = "android")]
74 | let view_obj = app
75 | .world_mut()
76 | .remove_non_send_resource::()
77 | .unwrap();
78 |
79 | let mut create_window_system_state: SystemState<(
80 | Commands,
81 | Query<(Entity, &mut Window), Added>,
82 | EventWriter,
83 | NonSendMut,
84 | )> = SystemState::from_world(app.world_mut());
85 | let (mut commands, mut new_windows, mut created_window_writer, mut app_views) =
86 | create_window_system_state.get_mut(app.world_mut());
87 |
88 | for (entity, mut bevy_window) in new_windows.iter_mut() {
89 | if app_views.get_view(entity).is_some() {
90 | continue;
91 | }
92 | let app_view = app_views.create_window(view_obj, entity);
93 | let logical_res = app_view.logical_resolution();
94 |
95 | // Update resolution of bevy window
96 | bevy_window
97 | .resolution
98 | .set_scale_factor(app_view.scale_factor as f32);
99 | bevy_window.resolution.set(logical_res.0, logical_res.1);
100 | info!("bevy_window: {:?}", bevy_window.resolution);
101 |
102 | // commands.entity(entity).insert(RawHandleWrapper {
103 | // window_handle: app_view.window_handle().unwrap().as_raw(),
104 | // display_handle: app_view.display_handle().unwrap().as_raw(),
105 | // });
106 | commands
107 | .entity(entity)
108 | .insert(RawHandleWrapper::new(&app_view.0).unwrap());
109 |
110 | created_window_writer.send(WindowCreated { window: entity });
111 | break;
112 | }
113 | create_window_system_state.apply(app.world_mut());
114 | }
115 |
116 | pub(crate) fn despawn_window(
117 | mut closed: RemovedComponents,
118 | window_entities: Query<&Window>,
119 | mut close_events: EventWriter,
120 | mut app_views: NonSendMut,
121 | ) {
122 | for entity in closed.read() {
123 | info!("Closing window {:?}", entity);
124 | if !window_entities.contains(entity) {
125 | app_views.remove_view(entity);
126 | close_events.send(WindowClosed { window: entity });
127 | }
128 | }
129 | }
130 |
131 | pub(crate) fn changed_window(
132 | mut _changed_windows: Query<(Entity, &mut Window), Changed>,
133 | _app_views: NonSendMut,
134 | ) {
135 | // TODO:
136 | }
137 |
--------------------------------------------------------------------------------
/src/app_view/view.rs:
--------------------------------------------------------------------------------
1 | #[allow(dead_code)]
2 | #[derive(Debug, Default)]
3 | pub struct View;
4 |
--------------------------------------------------------------------------------
/src/breakout_game.rs:
--------------------------------------------------------------------------------
1 | use bevy::{
2 | math::bounding::{Aabb2d, BoundingCircle, BoundingVolume, IntersectsVolume},
3 | prelude::*,
4 | sprite::MaterialMesh2dBundle,
5 | };
6 |
7 | use super::stepping;
8 |
9 | // These constants are defined in `Transform` units.
10 | // Using the default 2D camera they correspond 1:1 with screen pixels.
11 | const PADDLE_SIZE: Vec2 = Vec2::new(100.0, 15.0);
12 | const GAP_BETWEEN_PADDLE_AND_FLOOR: f32 = 50.0;
13 | const PADDLE_SPEED: f32 = 300.0;
14 | // How close can the paddle get to the wall
15 | const PADDLE_PADDING: f32 = 10.0;
16 |
17 | // We set the z-value of the ball to 1 so it renders on top in the case of overlapping sprites.
18 | const BALL_STARTING_POSITION: Vec3 = Vec3::new(0.0, -50.0, 1.0);
19 | const BALL_DIAMETER: f32 = 30.;
20 | const BALL_SPEED: f32 = 300.0;
21 | const INITIAL_BALL_DIRECTION: Vec2 = Vec2::new(0.5, -0.5);
22 |
23 | const WALL_THICKNESS: f32 = 6.0;
24 | // x coordinates
25 | const LEFT_WALL: f32 = -190.;
26 | const RIGHT_WALL: f32 = 190.;
27 | // y coordinates
28 | const BOTTOM_WALL: f32 = -300.;
29 | const TOP_WALL: f32 = 300.;
30 |
31 | const BRICK_SIZE: Vec2 = Vec2::new(80., 20.);
32 | // These values are exact
33 | const GAP_BETWEEN_PADDLE_AND_BRICKS: f32 = 200.0;
34 | const GAP_BETWEEN_BRICKS: f32 = 10.0;
35 | // These values are lower bounds, as the number of bricks is computed
36 | const GAP_BETWEEN_BRICKS_AND_CEILING: f32 = 15.0;
37 | const GAP_BETWEEN_BRICKS_AND_SIDES: f32 = 10.0;
38 |
39 | const SCOREBOARD_FONT_SIZE: f32 = 24.0;
40 | const SCOREBOARD_TEXT_PADDING: Val = Val::Px(25.0);
41 |
42 | pub struct BreakoutGamePlugin;
43 | impl Plugin for BreakoutGamePlugin {
44 | fn build(&self, app: &mut App) {
45 | app.add_plugins(
46 | stepping::SteppingPlugin::default()
47 | .add_schedule(Update)
48 | .add_schedule(FixedUpdate)
49 | .at(Val::Percent(35.0), Val::Percent(50.0)),
50 | )
51 | .insert_resource(Score(0))
52 | .insert_resource(ClearColor(BACKGROUND_COLOR))
53 | .add_event::()
54 | .add_systems(Startup, setup)
55 | // Add our gameplay simulation systems to the fixed timestep schedule
56 | // which runs at 64 Hz by default
57 | .add_systems(
58 | FixedUpdate,
59 | (
60 | apply_velocity,
61 | move_paddle,
62 | check_for_collisions,
63 | play_collision_sound,
64 | )
65 | // `chain`ing systems together runs them in order
66 | .chain(),
67 | )
68 | .add_systems(Update, update_scoreboard);
69 | }
70 | }
71 |
72 | const BACKGROUND_COLOR: Color = Color::srgb(0.9, 0.9, 0.9);
73 | const PADDLE_COLOR: Color = Color::srgb(0.3, 0.3, 0.7);
74 | const BALL_COLOR: Color = Color::srgb(1.0, 0.5, 0.5);
75 | const BRICK_COLOR: Color = Color::srgb(0.5, 0.5, 1.0);
76 | const WALL_COLOR: Color = Color::srgb(0.8, 0.8, 0.8);
77 | const TEXT_COLOR: Color = Color::srgb(0.5, 0.5, 1.0);
78 | const SCORE_COLOR: Color = Color::srgb(1.0, 0.5, 0.5);
79 |
80 | #[derive(Component)]
81 | struct Paddle;
82 |
83 | #[derive(Component)]
84 | struct Ball;
85 |
86 | #[derive(Component, Deref, DerefMut)]
87 | struct Velocity(Vec2);
88 |
89 | #[derive(Component)]
90 | struct Collider;
91 |
92 | #[derive(Event, Default)]
93 | struct CollisionEvent;
94 |
95 | #[derive(Component)]
96 | struct Brick;
97 |
98 | #[derive(Resource, Deref)]
99 | struct CollisionSound(Handle);
100 |
101 | // This bundle is a collection of the components that define a "wall" in our game
102 | #[derive(Bundle)]
103 | struct WallBundle {
104 | // You can nest bundles inside of other bundles like this
105 | // Allowing you to compose their functionality
106 | sprite_bundle: SpriteBundle,
107 | collider: Collider,
108 | }
109 |
110 | /// Which side of the arena is this wall located on?
111 | enum WallLocation {
112 | Left,
113 | Right,
114 | Bottom,
115 | Top,
116 | }
117 |
118 | impl WallLocation {
119 | /// Location of the *center* of the wall, used in `transform.translation()`
120 | fn position(&self) -> Vec2 {
121 | match self {
122 | WallLocation::Left => Vec2::new(LEFT_WALL, 0.),
123 | WallLocation::Right => Vec2::new(RIGHT_WALL, 0.),
124 | WallLocation::Bottom => Vec2::new(0., BOTTOM_WALL),
125 | WallLocation::Top => Vec2::new(0., TOP_WALL),
126 | }
127 | }
128 |
129 | /// (x, y) dimensions of the wall, used in `transform.scale()`
130 | fn size(&self) -> Vec2 {
131 | let arena_height = TOP_WALL - BOTTOM_WALL;
132 | let arena_width = RIGHT_WALL - LEFT_WALL;
133 | // Make sure we haven't messed up our constants
134 | assert!(arena_height > 0.0);
135 | assert!(arena_width > 0.0);
136 |
137 | match self {
138 | WallLocation::Left | WallLocation::Right => {
139 | Vec2::new(WALL_THICKNESS, arena_height + WALL_THICKNESS)
140 | }
141 | WallLocation::Bottom | WallLocation::Top => {
142 | Vec2::new(arena_width + WALL_THICKNESS, WALL_THICKNESS)
143 | }
144 | }
145 | }
146 | }
147 |
148 | impl WallBundle {
149 | // This "builder method" allows us to reuse logic across our wall entities,
150 | // making our code easier to read and less prone to bugs when we change the logic
151 | fn new(location: WallLocation) -> WallBundle {
152 | WallBundle {
153 | sprite_bundle: SpriteBundle {
154 | transform: Transform {
155 | // We need to convert our Vec2 into a Vec3, by giving it a z-coordinate
156 | // This is used to determine the order of our sprites
157 | translation: location.position().extend(0.0),
158 | // The z-scale of 2D objects must always be 1.0,
159 | // or their ordering will be affected in surprising ways.
160 | // See https://github.com/bevyengine/bevy/issues/4149
161 | scale: location.size().extend(1.0),
162 | ..default()
163 | },
164 | sprite: Sprite {
165 | color: WALL_COLOR,
166 | ..default()
167 | },
168 | ..default()
169 | },
170 | collider: Collider,
171 | }
172 | }
173 | }
174 |
175 | // This resource tracks the game's score
176 | #[derive(Resource, Deref, DerefMut)]
177 | struct Score(usize);
178 |
179 | #[derive(Component)]
180 | struct ScoreboardUi;
181 |
182 | // Add the game's entities to our world
183 | fn setup(
184 | mut commands: Commands,
185 | mut meshes: ResMut>,
186 | mut materials: ResMut>,
187 | asset_server: Res,
188 | ) {
189 | // Camera
190 | commands.spawn(Camera2dBundle::default());
191 |
192 | // Sound
193 | let ball_collision_sound = asset_server.load("sounds/breakout_collision.ogg");
194 | commands.insert_resource(CollisionSound(ball_collision_sound));
195 |
196 | // Paddle
197 | let paddle_y = BOTTOM_WALL + GAP_BETWEEN_PADDLE_AND_FLOOR;
198 |
199 | commands.spawn((
200 | SpriteBundle {
201 | transform: Transform {
202 | translation: Vec3::new(0.0, paddle_y, 0.0),
203 | scale: PADDLE_SIZE.extend(1.0),
204 | ..default()
205 | },
206 | sprite: Sprite {
207 | color: PADDLE_COLOR,
208 | ..default()
209 | },
210 | ..default()
211 | },
212 | Paddle,
213 | Collider,
214 | ));
215 |
216 | // Ball
217 | commands.spawn((
218 | MaterialMesh2dBundle {
219 | mesh: meshes.add(Circle::default()).into(),
220 | material: materials.add(BALL_COLOR),
221 | transform: Transform::from_translation(BALL_STARTING_POSITION)
222 | .with_scale(Vec2::splat(BALL_DIAMETER).extend(1.)),
223 | ..default()
224 | },
225 | Ball,
226 | Velocity(INITIAL_BALL_DIRECTION.normalize() * BALL_SPEED),
227 | ));
228 |
229 | // Scoreboard
230 | commands.spawn((
231 | ScoreboardUi,
232 | TextBundle::from_sections([
233 | TextSection::new(
234 | "Score: ",
235 | TextStyle {
236 | font_size: SCOREBOARD_FONT_SIZE,
237 | color: TEXT_COLOR,
238 | ..default()
239 | },
240 | ),
241 | TextSection::from_style(TextStyle {
242 | font_size: SCOREBOARD_FONT_SIZE,
243 | color: SCORE_COLOR,
244 | ..default()
245 | }),
246 | ])
247 | .with_style(Style {
248 | position_type: PositionType::Absolute,
249 | top: SCOREBOARD_TEXT_PADDING,
250 | left: SCOREBOARD_TEXT_PADDING,
251 | ..default()
252 | }),
253 | ));
254 |
255 | // Walls
256 | commands.spawn(WallBundle::new(WallLocation::Left));
257 | commands.spawn(WallBundle::new(WallLocation::Right));
258 | commands.spawn(WallBundle::new(WallLocation::Bottom));
259 | commands.spawn(WallBundle::new(WallLocation::Top));
260 |
261 | // Bricks
262 | let total_width_of_bricks = (RIGHT_WALL - LEFT_WALL) - 2. * GAP_BETWEEN_BRICKS_AND_SIDES;
263 | let bottom_edge_of_bricks = paddle_y + GAP_BETWEEN_PADDLE_AND_BRICKS;
264 | let total_height_of_bricks = TOP_WALL - bottom_edge_of_bricks - GAP_BETWEEN_BRICKS_AND_CEILING;
265 |
266 | assert!(total_width_of_bricks > 0.0);
267 | assert!(total_height_of_bricks > 0.0);
268 |
269 | // Given the space available, compute how many rows and columns of bricks we can fit
270 | let n_columns = (total_width_of_bricks / (BRICK_SIZE.x + GAP_BETWEEN_BRICKS)).floor() as usize;
271 | let n_rows = (total_height_of_bricks / (BRICK_SIZE.y + GAP_BETWEEN_BRICKS)).floor() as usize;
272 | let n_vertical_gaps = n_columns - 1;
273 |
274 | // Because we need to round the number of columns,
275 | // the space on the top and sides of the bricks only captures a lower bound, not an exact value
276 | let center_of_bricks = (LEFT_WALL + RIGHT_WALL) / 2.0;
277 | let left_edge_of_bricks = center_of_bricks
278 | // Space taken up by the bricks
279 | - (n_columns as f32 / 2.0 * BRICK_SIZE.x)
280 | // Space taken up by the gaps
281 | - n_vertical_gaps as f32 / 2.0 * GAP_BETWEEN_BRICKS;
282 |
283 | // In Bevy, the `translation` of an entity describes the center point,
284 | // not its bottom-left corner
285 | let offset_x = left_edge_of_bricks + BRICK_SIZE.x / 2.;
286 | let offset_y = bottom_edge_of_bricks + BRICK_SIZE.y / 2.;
287 |
288 | for row in 0..n_rows {
289 | for column in 0..n_columns {
290 | let brick_position = Vec2::new(
291 | offset_x + column as f32 * (BRICK_SIZE.x + GAP_BETWEEN_BRICKS),
292 | offset_y + row as f32 * (BRICK_SIZE.y + GAP_BETWEEN_BRICKS),
293 | );
294 |
295 | // brick
296 | commands.spawn((
297 | SpriteBundle {
298 | sprite: Sprite {
299 | color: BRICK_COLOR,
300 | ..default()
301 | },
302 | transform: Transform {
303 | translation: brick_position.extend(0.0),
304 | scale: Vec3::new(BRICK_SIZE.x, BRICK_SIZE.y, 1.0),
305 | ..default()
306 | },
307 | ..default()
308 | },
309 | Brick,
310 | Collider,
311 | ));
312 | }
313 | }
314 | }
315 |
316 | fn move_paddle(
317 | keyboard_input: Res>,
318 | mut query: Query<&mut Transform, With>,
319 | time: Res