├── .gitignore ├── rust ├── shared │ ├── src │ │ ├── types.rs │ │ ├── resources.rs │ │ ├── data.rs │ │ ├── time.rs │ │ ├── lib.rs │ │ ├── components.rs │ │ ├── data │ │ │ ├── collision_events.rs │ │ │ └── keycode.rs │ │ ├── utils.rs │ │ ├── bevy_plugin_syncable.rs │ │ ├── plugin.rs │ │ ├── components │ │ │ ├── textmeshui.rs │ │ │ ├── collision.rs │ │ │ ├── transform.rs │ │ │ └── prefab.rs │ │ ├── core_stage.rs │ │ ├── world_link.rs │ │ ├── log_plugin.rs │ │ └── bevy_app_syncable.rs │ └── Cargo.toml ├── runity │ ├── src │ │ ├── lib.rs │ │ ├── api.rs │ │ ├── logging.rs │ │ ├── game.rs │ │ └── world.rs │ └── Cargo.toml ├── Cargo.toml ├── game │ ├── Cargo.toml │ └── src │ │ ├── score.rs │ │ ├── gold.rs │ │ ├── spike.rs │ │ ├── particles.rs │ │ └── lib.rs └── demo_game │ ├── Cargo.toml │ └── src │ └── lib.rs ├── runity_logo.png ├── unity └── Runity │ ├── RustEntityComponent.cs │ ├── Time.cs │ ├── ScriptablePrefabs.cs │ ├── RunityMono.cs │ ├── RustyGameObject.cs │ ├── Runity.cs │ ├── Logging.cs │ ├── CollisionTrackerComponent.cs │ └── World.cs └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /rust/shared/src/types.rs: -------------------------------------------------------------------------------- 1 | pub type EntityId = u64; -------------------------------------------------------------------------------- /rust/shared/src/resources.rs: -------------------------------------------------------------------------------- 1 | pub mod available_entity_ids; -------------------------------------------------------------------------------- /rust/shared/src/data.rs: -------------------------------------------------------------------------------- 1 | pub mod keycode; 2 | pub mod collision_events; -------------------------------------------------------------------------------- /runity_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/runity/HEAD/runity_logo.png -------------------------------------------------------------------------------- /rust/runity/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod api; 2 | pub mod logging; 3 | pub mod world; 4 | pub mod game; -------------------------------------------------------------------------------- /rust/shared/src/time.rs: -------------------------------------------------------------------------------- 1 | #[repr(C)] 2 | pub struct Time { 3 | pub delta_time: f32, 4 | } -------------------------------------------------------------------------------- /unity/Runity/RustEntityComponent.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | 4 | namespace Runity { 5 | public class RustEntityComponent : MonoBehaviour { 6 | public UInt64 IdentifierBits; 7 | } 8 | } -------------------------------------------------------------------------------- /unity/Runity/Time.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | namespace Runity { 4 | [StructLayout(LayoutKind.Sequential)] 5 | public struct Time { 6 | public float delta_time; 7 | } 8 | } -------------------------------------------------------------------------------- /rust/shared/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod time; 2 | pub mod log_plugin; 3 | pub mod plugin; 4 | pub mod components; 5 | pub mod types; 6 | pub mod world_link; 7 | pub mod bevy_app_syncable; 8 | pub mod bevy_plugin_syncable; 9 | pub mod core_stage; 10 | pub mod data; 11 | pub mod utils; 12 | -------------------------------------------------------------------------------- /rust/shared/src/components.rs: -------------------------------------------------------------------------------- 1 | pub mod prefab; 2 | pub mod transform; 3 | pub mod collision; 4 | pub mod textmeshui; 5 | 6 | pub mod prelude { 7 | pub use super::prefab::{Prefab, *}; 8 | pub use super::transform::Transform; 9 | pub use super::collision::Collision; 10 | pub use super::textmeshui::TextMeshUi; 11 | } 12 | -------------------------------------------------------------------------------- /unity/Runity/ScriptablePrefabs.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace Runity { 6 | [System.Serializable] 7 | public struct PrefabBinding { 8 | public GameObject Prefab; 9 | public string RustName; 10 | } 11 | 12 | [CreateAssetMenu(fileName = "Runity_ScriptablePrefabs", menuName ="Runity/ScriptablePrefabs")] 13 | public class ScriptablePrefabs : ScriptableObject { 14 | public PrefabBinding[] PrefabBindings; 15 | } 16 | } -------------------------------------------------------------------------------- /rust/shared/src/data/collision_events.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | use crate::types::EntityId; 4 | 5 | #[repr(C)] 6 | #[derive(Debug, Clone, Copy)] 7 | pub struct CollisionEvent { 8 | pub owner_entity: EntityId, 9 | pub other_entity: EntityId, 10 | pub collision_type: CollisionType, 11 | } 12 | 13 | #[repr(C)] 14 | #[derive(Debug, Clone, Copy)] 15 | pub enum CollisionType { 16 | OnCollisionEnter, 17 | OnCollisionExit, 18 | OnCollisionStay, 19 | OnTriggerEnter, 20 | OnTriggerExit, 21 | OnTriggerStay, 22 | } -------------------------------------------------------------------------------- /rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "allprojects" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [workspace] 7 | members = ["runity","game","shared", "demo_game"] 8 | 9 | #[lib] 10 | #name = "runity" 11 | #crate-type = ["dylib"] 12 | # 13 | #[dependencies] 14 | #cgmath = "0.18.0" 15 | #rand = "0.8.4" 16 | #log = "*" 17 | #simplelog = "*" 18 | #lazy_static= "*" 19 | #bevy_ecs = "0.5" 20 | 21 | [lib] 22 | name = "runity" 23 | crate-type = ["dylib"] 24 | path = "runity/" 25 | 26 | #[lib] 27 | #name = "game" 28 | #crate-type = ["dylib"] 29 | #path = "game/" -------------------------------------------------------------------------------- /rust/game/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "game" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | name = "game" 10 | crate-type = ["dylib"] 11 | 12 | [dependencies] 13 | shared = {path = "../shared"} 14 | cgmath = "0.18.0" 15 | rand = "0.8.4" 16 | log = "*" 17 | lazy_static= "*" 18 | bevy = { git = "https://github.com/bevyengine/bevy", rev = "6a8a8c9d21f32e0e46623db9438813b009f9e014", default-features = false} 19 | dlopen = "0.1.8" 20 | dlopen_derive = "0.1.4" -------------------------------------------------------------------------------- /rust/shared/src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CString, os::raw::c_char}; 2 | 3 | use log::error; 4 | 5 | // only use for passing to other language 6 | pub fn string_to_cstring_ptr(string: &str) -> Result<*const c_char, ()> { 7 | let name_cstr = match CString::new(string) { 8 | Ok(name_cstr) => name_cstr, 9 | Err(err) => { 10 | error!("failed to turn {} into cstring err {:?}", string, err); 11 | return Err(()); 12 | }, 13 | }; 14 | let name_ptr = name_cstr.as_ptr(); 15 | std::mem::forget(name_cstr); 16 | Ok(name_ptr) 17 | } 18 | -------------------------------------------------------------------------------- /rust/shared/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shared" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | #[lib] 9 | #name = "runity" 10 | #crate-type = ["dylib"] 11 | 12 | [dependencies] 13 | cgmath = "0.18.0" 14 | rand = "0.8.4" 15 | log = "*" 16 | simplelog = "*" 17 | lazy_static= "*" 18 | #bevy_ecs = "0.5" 19 | #bevy_app = "0.5" 20 | 21 | bevy = { git = "https://github.com/bevyengine/bevy", rev = "6a8a8c9d21f32e0e46623db9438813b009f9e014", default-features = false} 22 | 23 | dlopen = "0.1.8" 24 | dlopen_derive = "0.1.4" -------------------------------------------------------------------------------- /rust/shared/src/bevy_plugin_syncable.rs: -------------------------------------------------------------------------------- 1 | // MODIFIED bevy plugin 2 | // instead of using bevy's app, use the custom made one 3 | use crate::bevy_app_syncable::App; 4 | use std::any::Any; 5 | 6 | /// A collection of Bevy App logic and configuration 7 | /// 8 | /// Plugins configure an [App](crate::App). When an [App](crate::App) registers 9 | /// a plugin, the plugin's [Plugin::build] function is run. 10 | pub trait Plugin: Any + Send + Sync { 11 | fn build(&self, app: &mut App); 12 | fn name(&self) -> &str { 13 | std::any::type_name::() 14 | } 15 | } 16 | 17 | pub type CreatePlugin = unsafe fn() -> *mut dyn Plugin; 18 | -------------------------------------------------------------------------------- /rust/runity/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "runity" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | name = "runity" 10 | crate-type = ["dylib"] 11 | 12 | [dependencies] 13 | shared = {path = "../shared"} 14 | cgmath = "0.18.0" 15 | rand = "0.8.4" 16 | log = "*" 17 | simplelog = "*" 18 | lazy_static= "*" 19 | #bevy_ecs = "0.5" 20 | #bevy_app = "0.5" 21 | bevy = { git = "https://github.com/bevyengine/bevy", rev = "6a8a8c9d21f32e0e46623db9438813b009f9e014", default-features = false} 22 | 23 | dlopen = "0.1.8" 24 | dlopen_derive = "0.1.4" -------------------------------------------------------------------------------- /rust/demo_game/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "demo_game" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | name = "demo_game" 10 | crate-type = ["dylib"] 11 | 12 | [dependencies] 13 | shared = {path = "../shared"} 14 | cgmath = "0.18.0" 15 | rand = "0.8.4" 16 | log = "*" 17 | #simplelog = "*" 18 | lazy_static= "*" 19 | #bevy_ecs = "0.5" 20 | #bevy_app = "0.5" 21 | bevy = { git = "https://github.com/bevyengine/bevy", rev = "6a8a8c9d21f32e0e46623db9438813b009f9e014", default-features = false} 22 | dlopen = "0.1.8" 23 | dlopen_derive = "0.1.4" -------------------------------------------------------------------------------- /rust/shared/src/plugin.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | pub trait Plugin: Any + Send + Sync { 4 | fn init(&mut self, app: &mut crate::bevy_app_syncable::App); 5 | } 6 | 7 | // setup dll bindings 8 | #[macro_export] 9 | macro_rules! declare_plugin { 10 | ($plugin_type:ty, $constructor:path) => { 11 | #[no_mangle] 12 | pub extern "C" fn _plugin_create() -> *mut Plugin { 13 | use shared::log_plugin::init_logger; 14 | init_logger(); 15 | // make sure the constructor is the correct type. 16 | let constructor: fn() -> $plugin_type = $constructor; 17 | let object = constructor(); 18 | let boxed: Box = Box::new(object); 19 | Box::into_raw(boxed) 20 | } 21 | }; 22 | } -------------------------------------------------------------------------------- /unity/Runity/RunityMono.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace Runity { 6 | public class RunityMono : MonoBehaviour { 7 | private RunityBackend m_runity; 8 | [SerializeField] private ScriptablePrefabs m_scriptablePrefabs; 9 | [SerializeField] private string m_gameLibraryName; 10 | [SerializeField] private Level m_unityLoggerLevel = Level.Info; 11 | [SerializeField] private Level m_writerLoggerLevel = Level.Debug; 12 | // Start is called before the first frame update 13 | void Start() { 14 | m_runity = new RunityBackend( 15 | m_gameLibraryName, 16 | m_scriptablePrefabs, 17 | m_unityLoggerLevel, 18 | m_writerLoggerLevel 19 | ); 20 | } 21 | void Update() { 22 | m_runity.Update(); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /rust/shared/src/components/textmeshui.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use dlopen::wrapper::Container; 3 | 4 | use crate::{utils, world_link::WorldLink}; 5 | 6 | #[derive(Component)] 7 | pub struct TextMeshUi { 8 | pub text: String, 9 | } 10 | 11 | impl TextMeshUi { 12 | pub fn new(text: String) -> Self { 13 | Self { 14 | text, 15 | } 16 | } 17 | } 18 | 19 | pub fn upload_component_textmeshui_system( 20 | entitiy_added_collision: Query<(Entity, &TextMeshUi), Changed>, 21 | world_link: Res>, 22 | ) { 23 | for (entity, text_mesh_ui) in entitiy_added_collision.iter() { 24 | // SEND TO UNITY 25 | log::debug!("telling unity to update textmeshui, entity: {}", entity.to_bits()); 26 | if let Ok(text_str) = utils::string_to_cstring_ptr(text_mesh_ui.text.as_str()) { 27 | world_link.upload_component_textmeshui(entity.to_bits(), text_str); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /unity/Runity/RustyGameObject.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Runity { 4 | // Used to access parts of a Unity GameObject, from rust 5 | public struct RustyGameObject { 6 | public UnityEngine.GameObject GameObject; 7 | public Dictionary HashedComponents; 8 | 9 | public RustyGameObject(UnityEngine.GameObject a_gameObject) { 10 | GameObject = a_gameObject; 11 | HashedComponents = new Dictionary(); 12 | } 13 | 14 | public T GetComponentFromId(int a_componentId) 15 | where T: UnityEngine.Component 16 | { 17 | if(HashedComponents.ContainsKey(a_componentId)) 18 | return HashedComponents[a_componentId] as T; 19 | else { 20 | var component = GameObject.GetComponent(); 21 | HashedComponents[a_componentId] = component; 22 | return HashedComponents[a_componentId] as T; 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /rust/shared/src/components/collision.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use dlopen::wrapper::Container; 3 | use std::collections::{HashMap, VecDeque}; 4 | use crate::{data::collision_events::CollisionEvent, world_link::WorldLink}; 5 | 6 | use super::prefab::Prefab; 7 | 8 | #[derive(Component)] 9 | pub struct Collision { 10 | pub events: VecDeque, 11 | } 12 | 13 | impl Default for Collision { 14 | fn default() -> Self { 15 | Self { events: VecDeque::with_capacity(2), } 16 | } 17 | } 18 | 19 | pub fn collision_clear_events_system( 20 | mut query: Query<&mut Collision, With>, 21 | ) { 22 | for mut collision in query.iter_mut() { 23 | collision.events.clear(); 24 | } 25 | } 26 | 27 | pub fn sync_collision_added( 28 | entitiy_added_collision: Query>, 29 | world_link: Res>, 30 | ) { 31 | for entity in entitiy_added_collision.iter() { 32 | // SEND TO UNITY 33 | log::debug!("telling unity to track collision prefab {}", entity.to_bits()); 34 | world_link.entity_track_collision(entity.to_bits()); 35 | } 36 | } -------------------------------------------------------------------------------- /rust/shared/src/core_stage.rs: -------------------------------------------------------------------------------- 1 | use bevy::ecs::schedule::StageLabel; 2 | 3 | /// The names of the default App stages 4 | /// 5 | /// The relative stages are added by [`App::add_default_stages`]. 6 | #[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] 7 | pub enum CoreStage { 8 | /// Runs only once at the beginning of the app. 9 | /// 10 | /// Consists of the sub-stages defined in [`StartupStage`]. Systems added here are 11 | /// referred to as "startup systems". 12 | Startup, 13 | /// Name of app stage that runs before all other app stages 14 | First, 15 | /// Name of app stage responsible for performing setup before an update. Runs before UPDATE. 16 | PreUpdate, 17 | /// Name of app stage responsible for doing most app logic. Systems should be registered here 18 | /// by default. 19 | Update, 20 | /// Name of app stage responsible for processing the results of UPDATE. Runs after UPDATE. 21 | PostUpdate, 22 | /// Name of app stage that runs after all other app stages 23 | Last, 24 | /// the last stage is to send to unity 25 | /// calls into unity must be single threaded, very important 26 | UploadToUnity, 27 | LateUploadToUnity, 28 | } -------------------------------------------------------------------------------- /rust/shared/src/world_link.rs: -------------------------------------------------------------------------------- 1 | use std::os::raw::c_char; 2 | 3 | use dlopen; 4 | use dlopen_derive::*; 5 | use dlopen::wrapper::{Container, WrapperApi}; 6 | 7 | use crate::{components::{transform::CTransform}, data::keycode::KeyCode, types::EntityId}; 8 | 9 | // the game.dll conneciton to runity.dll 10 | // is going to be a resource in the bevy ecosystem 11 | #[derive(WrapperApi)] 12 | pub struct WorldLink { 13 | spawn_prefab: fn(name: *const c_char, id: EntityId), 14 | spawn_prefab_with_transform: fn(name: *const c_char, id: EntityId, transform: *mut CTransform), 15 | spawn_prefab_with_transform_and_parent: fn(name: *const c_char, id: EntityId, transform: *mut CTransform, parent_id: EntityId), 16 | despawn_gameobject: fn(id: EntityId), 17 | upload_component_transform: fn(id: EntityId, transform: *mut CTransform), 18 | upload_component_textmeshui: fn(id: EntityId, text: *const c_char), 19 | input_key_just_pressed: fn(key: KeyCode) -> bool, 20 | input_key_just_released: fn(key: KeyCode) -> bool, 21 | input_key_held: fn(key: KeyCode) -> bool, 22 | entity_track_collision: fn(id: EntityId), 23 | } 24 | 25 | impl WorldLink { 26 | pub fn new(runity_lib_path: &str) -> Result, dlopen::Error> { 27 | unsafe {Container::load(runity_lib_path)} 28 | } 29 | } -------------------------------------------------------------------------------- /rust/game/src/score.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::{BuildWorldChildren, Component, EventReader, Query, ResMut, With}; 2 | use cgmath::Vector3; 3 | use shared::{bevy_app_syncable::App, bevy_plugin_syncable::Plugin, components::{prefab::Prefab, prelude::{TextMeshUi, Transform}}}; 4 | 5 | use crate::EventResetLevel; 6 | 7 | pub struct PlayerScore(pub i32); 8 | 9 | pub fn score_reset_system( 10 | mut events: EventReader, 11 | mut player_score: ResMut, 12 | mut text_query: Query<&mut TextMeshUi, With>, 13 | ) { 14 | if events.iter().count() > 0 { 15 | player_score.0 = 0; 16 | for mut text in text_query.iter_mut() { 17 | text.text = format!("score: {}", player_score.0); 18 | } 19 | } 20 | } 21 | 22 | pub struct ScorePlugin; 23 | #[derive(Component)] 24 | pub struct ScoreTag; 25 | 26 | impl Plugin for ScorePlugin { 27 | fn build(&self, app: &mut App) { 28 | // spawn canvas will score 29 | app.world.spawn() 30 | .insert(Prefab::new("canvas")) 31 | .with_children(|parent| { 32 | parent.spawn() 33 | .insert(Prefab::new("score_ui")) 34 | .insert(Transform{position: Vector3::new(0.,362.,0.)}) 35 | .insert(TextMeshUi::new("score: X".to_string())) 36 | .insert(ScoreTag); 37 | }); 38 | 39 | app.insert_resource(PlayerScore(0)); 40 | app.add_system(score_reset_system); 41 | } 42 | } -------------------------------------------------------------------------------- /unity/Runity/Runity.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using System; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace Runity { 8 | public class RunityBackend { 9 | private Api m_api; 10 | private Logging m_logging; 11 | private World m_world; 12 | 13 | public RunityBackend( 14 | string a_gameLibName, 15 | ScriptablePrefabs a_prefabBindings, 16 | Level a_unityLoggerLevel, 17 | Level a_writerLoggerLevel 18 | ) { 19 | m_logging = new Logging(); 20 | m_world = new World(a_prefabBindings); 21 | m_api = new Api(a_gameLibName, a_unityLoggerLevel, a_writerLoggerLevel); 22 | } 23 | 24 | public void Update() { 25 | // before scripts run, send them latest collision events 26 | m_world.DownloadEntityCollisionEvents(); 27 | m_api.Update(); 28 | } 29 | } 30 | 31 | public class Api { 32 | public Api(string a_gameLibName, Level a_unityLoggerLevel, Level a_writerLoggerLevel) { 33 | api_init(a_unityLoggerLevel, a_writerLoggerLevel); 34 | api_init_game_lib(a_gameLibName); 35 | } 36 | 37 | public void Update() { 38 | Time time; 39 | time.delta_time = UnityEngine.Time.deltaTime; 40 | api_update(time); 41 | } 42 | 43 | [DllImport("runity")] public static extern void api_init(Level a_unityLoggerLevel, Level a_writerLoggLevel); 44 | [DllImport("runity")] public static extern void api_update(Time a_time); 45 | 46 | // game specific rust dll 47 | [DllImport("runity")] public static extern void api_init_game_lib(string a_gameLibName); 48 | } 49 | } -------------------------------------------------------------------------------- /rust/shared/src/components/transform.rs: -------------------------------------------------------------------------------- 1 | use bevy::ecs::prelude::*; 2 | use dlopen::wrapper::Container; 3 | use log::debug; 4 | use crate::world_link::WorldLink; 5 | 6 | use super::prefab::Prefab; 7 | use cgmath::{Vector3}; 8 | 9 | #[derive(Component, Copy, Clone, Debug)] 10 | pub struct Transform { 11 | pub position: Vector3, 12 | } 13 | 14 | impl Into for Transform { 15 | fn into(self) -> CTransform { 16 | CTransform { 17 | // position: CVector{ 18 | x: self.position.x, 19 | y: self.position.y, 20 | z: self.position.z, 21 | // }, 22 | } 23 | } 24 | } 25 | 26 | #[repr(C)] 27 | #[derive(Clone, Debug)] 28 | pub struct CTransform { 29 | pub x: f32, 30 | pub y: f32, 31 | pub z: f32, 32 | } 33 | 34 | // #[repr(C)] 35 | // #[derive(Copy, Clone, Debug)] 36 | // pub struct CVector { 37 | // pub x: f32, 38 | // pub y: f32, 39 | // pub z: f32, 40 | // } 41 | 42 | fn download_unity_transform( 43 | mut transforms: Query<(&mut Prefab, &mut Transform)>, 44 | ) { 45 | for (prefab, transform) in transforms.iter_mut() { 46 | } 47 | } 48 | 49 | pub fn upload_component_transform_system( 50 | prefab_with_transform: Query<(Entity, &Transform), With>, 51 | world_link: Res>, 52 | ) { 53 | // reset prefab existance flags 54 | for (entity, transform) in prefab_with_transform.iter() { 55 | let transform: CTransform = (*transform).into(); 56 | let transform_ptr = Box::into_raw(Box::new(transform)); 57 | world_link.upload_component_transform(entity.to_bits(), transform_ptr); 58 | // drop the value 59 | unsafe { 60 | let _drop_transform = Box::from_raw(transform_ptr); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /unity/Runity/Logging.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace Runity { 6 | public class Logging { 7 | private DLog m_cbLog; 8 | public Logging() { 9 | m_cbLog = CallbackLog; 10 | bind_log_callback(m_cbLog); 11 | } 12 | 13 | public void CallbackLog(LogMessage a_message) { 14 | switch(a_message.level) { 15 | case Level.Error: 16 | UnityEngine.Debug.LogError("[RUST] ERROR: " + a_message.message); 17 | break; 18 | case Level.Warn: 19 | UnityEngine.Debug.LogWarning("[RUST] WARN: " + a_message.message); 20 | break; 21 | case Level.Info: 22 | UnityEngine.Debug.Log("[RUST] INFO: " + a_message.message); 23 | break; 24 | case Level.Debug: 25 | UnityEngine.Debug.Log("[RUST] DEBUG: " + a_message.message); 26 | break; 27 | case Level.Trace: 28 | UnityEngine.Debug.Log("[RUST] TRACE: " + a_message.message); 29 | break; 30 | } 31 | } 32 | [DllImport("runity")] public static extern void bind_log_callback([MarshalAs(UnmanagedType.FunctionPtr)] DLog a_callback); 33 | [UnmanagedFunctionPointer(CallingConvention.StdCall)] 34 | public delegate void DLog(LogMessage a_message); 35 | } 36 | 37 | [StructLayout(LayoutKind.Sequential)] 38 | public struct LogMessage { 39 | public Level level; 40 | public string message; 41 | } 42 | 43 | [System.Serializable] 44 | public enum Level: int { 45 | Off = 0, 46 | Error = 1, 47 | Warn, 48 | Info, 49 | Debug, 50 | Trace, 51 | } 52 | } -------------------------------------------------------------------------------- /rust/game/src/gold.rs: -------------------------------------------------------------------------------- 1 | use bevy::core::Timer; 2 | use bevy::prelude::{Commands, Component, Entity, Query, Res, ResMut, With}; 3 | use cgmath::Vector3; 4 | use dlopen::wrapper::Container; 5 | use log::info; 6 | use rand::Rng; 7 | use shared::components::prefab::Prefab; 8 | use shared::world_link::WorldLink; 9 | use shared::data::keycode::*; 10 | use shared::components::transform::*; 11 | 12 | use crate::{RemoveOnReset}; 13 | 14 | #[derive(Component)] pub struct YVelocity(pub f32); 15 | #[derive(Component)] pub struct Gold; 16 | 17 | pub fn gold_movement_system( 18 | time: Res, 19 | mut query: Query<(Entity, &mut Transform, &mut YVelocity), With>) 20 | { 21 | let delta = time.delta().as_secs_f32(); 22 | for (_entity, mut transform, mut velocity) in query.iter_mut() { 23 | velocity.0 -= 8f32 * delta; 24 | transform.position.y += velocity.0 * delta; 25 | } 26 | } 27 | 28 | pub fn gold_destroy_system( 29 | mut commands: Commands, 30 | query: Query<(Entity, &Transform), With>) 31 | { 32 | for (entity, transform) in query.iter() { 33 | if transform.position.y <= -2.0f32 { commands.entity(entity).despawn(); } 34 | } 35 | } 36 | 37 | pub enum GoldSpawnerState { 38 | None, 39 | Spawning(Timer), 40 | } 41 | 42 | pub fn gold_spawn_system( 43 | mut commands: Commands, 44 | time: Res, 45 | mut gold_spawner_state: ResMut, 46 | ) { 47 | let spawn_timer: &mut Timer = match &mut *gold_spawner_state { 48 | GoldSpawnerState::None => return, 49 | GoldSpawnerState::Spawning(timer) => timer, 50 | }; 51 | if !spawn_timer.tick(time.delta()).just_finished() { 52 | return; 53 | } 54 | // timer finished 55 | let spawn_x_bounds = -10f32..10f32; 56 | let mut rand_rng = rand::thread_rng(); 57 | commands.spawn() 58 | .insert(Prefab::new("gold")) 59 | .insert(Gold) 60 | .insert(RemoveOnReset) 61 | .insert(Transform{position: Vector3::new(rand_rng.gen_range(spawn_x_bounds), 17f32, 0f32)}) 62 | .insert(YVelocity(0f32)); 63 | } -------------------------------------------------------------------------------- /rust/game/src/spike.rs: -------------------------------------------------------------------------------- 1 | use bevy::core::Timer; 2 | use bevy::prelude::{Commands, Component, Entity, Query, Res, ResMut, With}; 3 | use cgmath::Vector3; 4 | use dlopen::wrapper::Container; 5 | use log::info; 6 | use rand::Rng; 7 | use shared::components::prefab::Prefab; 8 | use shared::world_link::WorldLink; 9 | use shared::data::keycode::*; 10 | use shared::components::transform::*; 11 | 12 | use crate::{Player, RemoveOnReset}; 13 | 14 | #[derive(Component)] pub struct YVelocity(pub f32); 15 | #[derive(Component)] pub struct Spike; 16 | 17 | pub fn spike_movement_system( 18 | time: Res, 19 | mut query: Query<(Entity, &mut Transform, &mut YVelocity), With>) 20 | { 21 | let delta = time.delta().as_secs_f32(); 22 | for (_entity, mut transform, mut velocity) in query.iter_mut() { 23 | velocity.0 -= 8f32 * delta; 24 | transform.position.y += velocity.0 * delta; 25 | } 26 | } 27 | 28 | pub fn spike_destroy_system( 29 | mut commands: Commands, 30 | query: Query<(Entity, &Transform), With>) 31 | { 32 | for (entity, transform) in query.iter() { 33 | if transform.position.y <= -2.0f32 { commands.entity(entity).despawn(); } 34 | } 35 | } 36 | 37 | pub enum SpikeSpawnerState { 38 | None, 39 | Spawning(Timer), 40 | } 41 | 42 | pub fn spike_spawn_system( 43 | mut commands: Commands, 44 | time: Res, 45 | mut spike_spawner_state: ResMut, 46 | ) { 47 | let mut spawn_timer: &mut Timer = match &mut *spike_spawner_state { 48 | SpikeSpawnerState::None => return, 49 | SpikeSpawnerState::Spawning(timer) => timer, 50 | }; 51 | if !spawn_timer.tick(time.delta()).just_finished() { 52 | return; 53 | } 54 | // timer finished 55 | let spawn_x_bounds = -10f32..10f32; 56 | let mut rand_rng = rand::thread_rng(); 57 | commands.spawn() 58 | .insert(Prefab::new("spike")) 59 | .insert(Spike) 60 | .insert(RemoveOnReset) 61 | .insert(Transform{position: Vector3::new(rand_rng.gen_range(spawn_x_bounds), 17f32, 0f32)}) 62 | .insert(YVelocity(0f32)); 63 | } -------------------------------------------------------------------------------- /rust/game/src/particles.rs: -------------------------------------------------------------------------------- 1 | use bevy::core::Timer; 2 | use bevy::prelude::{Commands, Component, Entity, Query, Res, ResMut, With}; 3 | use cgmath::Vector3; 4 | use dlopen::wrapper::Container; 5 | use rand::Rng; 6 | use shared::components::prefab::Prefab; 7 | use shared::world_link::WorldLink; 8 | use shared::data::keycode::*; 9 | use shared::components::transform::*; 10 | 11 | use crate::{Player, RemoveOnReset}; 12 | use crate::spike::Spike; 13 | 14 | #[derive(Component)] 15 | pub struct SelfDestructTimer(pub f32); 16 | #[derive(Component)] 17 | pub struct Velocity(pub cgmath::Vector3); 18 | 19 | pub fn destroy_system( 20 | mut commands: Commands, 21 | time: Res, 22 | mut query: Query<(Entity, &mut SelfDestructTimer)>) 23 | { 24 | for (entity, mut timer) in query.iter_mut() { 25 | timer.0 -= time.delta_seconds(); 26 | if timer.0 <= 0.0f32 { commands.entity(entity).despawn(); } } 27 | } 28 | 29 | pub fn movement_system( 30 | time: Res, 31 | world_link: Res>, 32 | mut query: Query<(Entity, &mut Transform, &mut Velocity)>) 33 | { 34 | let delta = time.delta().as_secs_f32(); 35 | let mut rand = rand::thread_rng(); 36 | for (_entity, mut transform, mut velocity) in query.iter_mut() { 37 | if world_link.input_key_held(KeyCode::Space) { 38 | velocity.0.y = -2f32; 39 | velocity.0.x += rand.gen_range(-3f32..3f32); 40 | velocity.0.z += rand.gen_range(-3f32..3f32); 41 | } 42 | velocity.0.y += 8f32 * delta; 43 | transform.position += velocity.0 * delta; 44 | } 45 | } 46 | 47 | pub struct CubeSpawnTimer(pub Timer); 48 | 49 | pub fn spawn_system( 50 | mut commands: Commands, 51 | time: Res, 52 | player_query: Query<&Transform, With>, 53 | mut cube_timer: ResMut, 54 | ) { 55 | if cube_timer.0.tick(time.delta()).just_finished() { 56 | let mut rand_rng = rand::thread_rng(); 57 | for player_transform in player_query.iter() { 58 | // spawn 59 | commands.spawn() 60 | .insert(Prefab::new("particle")) 61 | //.insert(Transform{position: Vector3::new(rand_rng.gen_range(-2f32..2f32), 0f32, rand_rng.gen_range(-2f32..2f32))}) 62 | .insert(Transform{position: player_transform.position}) 63 | .insert(RemoveOnReset) 64 | .insert(Velocity(Vector3::new(rand_rng.gen_range(-1f32..1f32), rand_rng.gen_range(-1f32..1f32), rand_rng.gen_range(-1f32..1f32)))) 65 | .insert(SelfDestructTimer(rand_rng.gen_range(0.3f32..19f32))); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /rust/runity/src/api.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CStr, os::raw::c_char, sync::Mutex}; 2 | 3 | use log::{LevelFilter, debug, error, info, trace, warn}; 4 | use shared::time::Time; 5 | use simplelog::{CombinedLogger, Config, WriteLogger}; 6 | use lazy_static::lazy_static; 7 | 8 | use crate::{game::Game, logging::{LogMessage, UnityLogger}, world::World}; 9 | 10 | lazy_static! { 11 | pub static ref API: Mutex = Mutex::new(Api::new()); 12 | } 13 | 14 | 15 | pub struct Api { 16 | pub world: World, 17 | pub game: Game, 18 | } 19 | 20 | impl Api { 21 | pub fn new() -> Self { 22 | Self { 23 | world: World::new(), 24 | game: Game::new(), 25 | } 26 | } 27 | } 28 | 29 | #[no_mangle] pub extern fn api_init_game_lib(lib_name: *const c_char) { 30 | info!("api init game lib..."); 31 | let mut api = API.lock().unwrap(); 32 | 33 | // FIREHOSE for unity editor. 34 | // recreate bevy app to reset the bevy world 35 | api.game.app = shared::bevy_app_syncable::App::default(); 36 | 37 | let c_str = unsafe { 38 | if lib_name.is_null() { 39 | error!("library name STR PTR is null! ABORTING LIBRARY LOADING"); 40 | return; 41 | } 42 | CStr::from_ptr(lib_name) 43 | }; 44 | let lib_name = match c_str.to_str() { 45 | Ok(r_str) => { 46 | r_str 47 | }, 48 | Err(utf_err) => { 49 | error!("can't convert c# string to rust reason: {:?}\n ABORTING LIBRARY LOADING", utf_err); 50 | return; 51 | } 52 | }; 53 | let library_creation_result = api.game.init_library(lib_name); 54 | if let Err(err) = library_creation_result { 55 | match err { 56 | crate::game::GameCreationError::DllLoadError(err) => { 57 | error!("{}", format!("failed to load library: {:?}. Reason: {:?}", lib_name, err).to_string()); 58 | drop(err); 59 | }, 60 | } 61 | } 62 | } 63 | 64 | 65 | #[no_mangle] pub extern fn api_init(unity_log_level: LevelFilter, writer_log_level: LevelFilter) { 66 | let mut loggers: Vec> = vec![Box::new(UnityLogger{level: unity_log_level})]; 67 | if writer_log_level != LevelFilter::Off { 68 | loggers.push(WriteLogger::new(writer_log_level, Config::default(), std::fs::File::create("my_rust_log.log").unwrap())); 69 | } 70 | let _init_result = CombinedLogger::init(loggers); 71 | } 72 | 73 | #[no_mangle] pub extern fn api_update(time: Time) { 74 | let mut api = API.lock().unwrap(); 75 | if let Some(mut rust_plugin) = api.game.rust_plugin.take() { 76 | api.game.update_bevy_world(); 77 | api.game.rust_plugin = Some(rust_plugin); 78 | } 79 | 80 | //info!("hello unity?"); 81 | //warn!("hello unity?"); 82 | //trace!("hello unity?"); 83 | //debug!("hello unity?"); 84 | //error!("hello unity?"); 85 | } -------------------------------------------------------------------------------- /rust/shared/src/log_plugin.rs: -------------------------------------------------------------------------------- 1 | use log::{Level, LevelFilter, Log, Metadata, Record}; 2 | use dlopen; 3 | use dlopen_derive::*; 4 | use dlopen::wrapper::{Container, WrapperApi}; 5 | use simplelog::{CombinedLogger, Config, SharedLogger}; 6 | use lazy_static::lazy_static; 7 | use std::{ffi::CString, os::raw::c_char, sync::Mutex}; 8 | 9 | lazy_static!{ 10 | pub static ref LOG_API: Mutex>> = Mutex::new(None); 11 | } 12 | 13 | 14 | // the game conneciton 15 | #[derive(WrapperApi)] 16 | pub struct LogApi { 17 | warn: fn(message: *const c_char), 18 | info: fn(message: *const c_char), 19 | error: fn(message: *const c_char), 20 | debug: fn(message: *const c_char), 21 | trace: fn(message: *const c_char), 22 | } 23 | 24 | impl LogApi { 25 | pub fn new(runity_lib_path: &str) -> Result, dlopen::Error> { 26 | let cont_result: Result, dlopen::Error> = unsafe { Container::load(runity_lib_path)}; 27 | let container = match cont_result { 28 | Ok(container) => container, 29 | Err(err) => return Err(err), 30 | }; 31 | Ok(container) 32 | } 33 | } 34 | 35 | // used by game dll do communicate back to the rust runity ap 36 | struct GameDllLogger; 37 | impl log::Log for GameDllLogger { 38 | fn enabled(&self, metadata: &Metadata) -> bool { 39 | metadata.level() <= Level::Info 40 | } 41 | 42 | fn log(&self, record: &Record) { 43 | if self.enabled(record.metadata()) { 44 | //println!("{} - {}", record.level(), record.args()); 45 | let log_api_option = LOG_API.lock().unwrap(); 46 | if let Some(log_api) = &*log_api_option { 47 | let message = CString::new(format!("[GAME] {}", record.args().to_string())).unwrap(); 48 | let message = message.as_ptr(); 49 | match record.level() { 50 | Level::Error => log_api.error(message), 51 | Level::Warn => log_api.warn(message), 52 | Level::Info => log_api.info(message), 53 | Level::Debug => log_api.debug(message), 54 | Level::Trace => log_api.trace(message), 55 | } 56 | } 57 | } 58 | } 59 | 60 | fn flush(&self) {} 61 | } 62 | 63 | impl SharedLogger for GameDllLogger { 64 | fn level(&self) -> LevelFilter { 65 | LevelFilter::Debug 66 | } 67 | fn config(&self) -> Option<&Config> { 68 | None//(Some(&self.config) 69 | } 70 | fn as_log(self: Box) -> Box { 71 | Box::new(*self) 72 | } 73 | } 74 | 75 | pub fn init_logger() { 76 | let mut log_api_option = LOG_API.lock().unwrap(); 77 | let log_api_result = LogApi::new("runity.dll"); 78 | if let Ok(log_api) = log_api_result { 79 | *log_api_option = Some(log_api); 80 | } 81 | 82 | let _init_result = CombinedLogger::init(vec![ 83 | Box::new(GameDllLogger), 84 | ]); 85 | } -------------------------------------------------------------------------------- /unity/Runity/CollisionTrackerComponent.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections.Generic; 3 | using System; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace Runity { 7 | [StructLayout(LayoutKind.Sequential)] 8 | public struct CollisionEvent { 9 | public UInt64 OwnerEntityId; 10 | public UInt64 OtherEntityId; 11 | public CollisionType CollisionType; 12 | public CollisionEvent(UInt64 a_ownerEntityId, UInt64 a_otherEntityId, CollisionType a_collisionType) { 13 | OwnerEntityId = a_ownerEntityId; 14 | OtherEntityId = a_otherEntityId; 15 | CollisionType = a_collisionType; 16 | } 17 | 18 | } 19 | 20 | public enum CollisionType { 21 | OnCollisionEnter, 22 | OnCollisionExit, 23 | OnCollisionStay, 24 | OnTriggerEnter, 25 | OnTriggerExit, 26 | OnTriggerStay, 27 | } 28 | 29 | public class CollisionTrackerComponent : MonoBehaviour { 30 | public Queue CollisionEvents; 31 | public UInt64 OwnerEntityIdBits; 32 | 33 | void OnCollisionEnter(Collision a_collision) { 34 | var entityIdentifier = a_collision.gameObject.GetComponent(); 35 | if(entityIdentifier != null) 36 | CollisionEvents.Enqueue(new CollisionEvent(OwnerEntityIdBits, entityIdentifier.IdentifierBits, CollisionType.OnCollisionEnter)); 37 | } 38 | void OnCollisionExit(Collision a_collision) { 39 | var entityIdentifier = a_collision.gameObject.GetComponent(); 40 | if(entityIdentifier != null) 41 | CollisionEvents.Enqueue(new CollisionEvent(OwnerEntityIdBits, entityIdentifier.IdentifierBits, CollisionType.OnCollisionExit)); 42 | } 43 | void OnCollisionStay(Collision a_collision) { 44 | var entityIdentifier = a_collision.gameObject.GetComponent(); 45 | if(entityIdentifier != null) 46 | CollisionEvents.Enqueue(new CollisionEvent(OwnerEntityIdBits, entityIdentifier.IdentifierBits, CollisionType.OnCollisionStay)); 47 | } 48 | void OnTriggerEnter(Collider a_collider) { 49 | var entityIdentifier = a_collider.gameObject.GetComponent(); 50 | if(entityIdentifier != null) 51 | CollisionEvents.Enqueue(new CollisionEvent(OwnerEntityIdBits, entityIdentifier.IdentifierBits, CollisionType.OnTriggerEnter)); 52 | } 53 | void OnTriggerExit(Collider a_collider) { 54 | var entityIdentifier = a_collider.gameObject.GetComponent(); 55 | if(entityIdentifier != null) 56 | CollisionEvents.Enqueue(new CollisionEvent(OwnerEntityIdBits, entityIdentifier.IdentifierBits, CollisionType.OnTriggerExit)); 57 | } 58 | void OnTriggerStay(Collider a_collider) { 59 | var entityIdentifier = a_collider.gameObject.GetComponent(); 60 | if(entityIdentifier != null) 61 | CollisionEvents.Enqueue(new CollisionEvent(OwnerEntityIdBits, entityIdentifier.IdentifierBits, CollisionType.OnTriggerStay)); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /rust/runity/src/logging.rs: -------------------------------------------------------------------------------- 1 | use log::{Level, LevelFilter, Log, Metadata, Record}; 2 | use std::{ffi::{CStr, CString}, os::raw::c_char, sync::Mutex}; 3 | use simplelog::{Config, SharedLogger}; 4 | use lazy_static::lazy_static; 5 | 6 | lazy_static! { 7 | static ref UNITY_LOGGER_CALLBACK: Mutex = Mutex::new(UnityLoggerCallback::new()); 8 | } 9 | 10 | // C# replica 11 | #[repr(C)] 12 | pub struct LogMessage { 13 | level: LevelFilter, 14 | message: *const c_char, 15 | } 16 | 17 | impl LogMessage { 18 | pub fn new(record: &Record) -> Self { 19 | let c_message = CString::new(record.args().to_string()).unwrap(); 20 | let p_message = c_message.as_ptr(); 21 | std::mem::forget(c_message); 22 | Self { 23 | level: record.level().to_level_filter(), 24 | message: p_message, 25 | } 26 | } 27 | } 28 | 29 | // calls into unity 30 | struct UnityLoggerCallback { 31 | pub cb_log: Box, 32 | } 33 | 34 | impl UnityLoggerCallback { 35 | pub fn new() -> Self { 36 | Self { 37 | cb_log: Box::new(|_|()), 38 | } 39 | } 40 | } 41 | 42 | // called from unity 43 | #[no_mangle] pub extern fn bind_log_callback(callback: extern fn(LogMessage)) { 44 | let mut u_logger = UNITY_LOGGER_CALLBACK.lock().unwrap(); 45 | u_logger.cb_log = Box::new(move |log_msg| callback(log_msg)); 46 | } 47 | 48 | // rust side Log implementation 49 | pub struct UnityLogger { 50 | pub level: LevelFilter, 51 | } 52 | 53 | impl log::Log for UnityLogger { 54 | fn enabled(&self, metadata: &Metadata) -> bool { 55 | metadata.level() <= self.level 56 | } 57 | fn log(&self, record: &Record) { 58 | if !self.enabled(record.metadata()){ 59 | return; 60 | } 61 | 62 | // todo: SHOULD NOT TRY_LOCK, But have to because function within API, can't log currently 63 | if let Ok(u_logger) = UNITY_LOGGER_CALLBACK.try_lock() { 64 | (u_logger.cb_log)(LogMessage::new(record)); 65 | } 66 | } 67 | fn flush(&self) { } 68 | } 69 | 70 | impl SharedLogger for UnityLogger { 71 | fn level(&self) -> LevelFilter { 72 | self.level 73 | } 74 | fn config(&self) -> Option<&Config> { 75 | None//(Some(&self.config) 76 | } 77 | fn as_log(self: Box) -> Box { 78 | Box::new(*self) 79 | } 80 | } 81 | 82 | 83 | // used from game dll 84 | #[no_mangle] pub extern fn warn(message: *const c_char) { 85 | let message = unsafe {CStr::from_ptr(message)}; 86 | if let Ok(message) = message.to_str() { 87 | log::warn!("{}", message); 88 | } 89 | } 90 | 91 | #[no_mangle] pub extern fn info(message: *const c_char) { 92 | let message = unsafe {CStr::from_ptr(message)}; 93 | if let Ok(message) = message.to_str() { 94 | log::info!("{}", message); 95 | } 96 | } 97 | 98 | #[no_mangle] pub extern fn debug(message: *const c_char) { 99 | let message = unsafe {CStr::from_ptr(message)}; 100 | if let Ok(message) = message.to_str() { 101 | log::debug!("{}", message); 102 | } 103 | } 104 | 105 | #[no_mangle] pub extern fn trace(message: *const c_char) { 106 | let message = unsafe {CStr::from_ptr(message)}; 107 | if let Ok(message) = message.to_str() { 108 | log::info!("{}", message); 109 | } 110 | } 111 | 112 | #[no_mangle] pub extern fn error(message: *const c_char) { 113 | let message = unsafe {CStr::from_ptr(message)}; 114 | if let Ok(message) = message.to_str() { 115 | log::error!("{}", message); 116 | } 117 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # runity 2 | ![](runity_logo.png) 3 | 4 | Highly experimental Rust implementation for Unity. 5 | 6 | Utilizing Bevy's entity component system, we can write code like any other Bevy project, 7 | but use Unity as the runtime. 8 | 9 | I documented the creation of this project on my youtube channel called Tantan: 10 | https://youtu.be/L7M_vbo1N2g 11 | 12 | ### Runity features: 13 | This is Runity currently capable of controlling from Rust to Unity 14 | 1. spawn prefab 15 | 2. spawn prefab as a child of another game object 16 | 3. destroy prefab 17 | 4. process collision events (very barebones) 18 | 5. modify transform position 19 | 6. modify TextMesh text 20 | 21 | ### how to build 22 | I currently don't provide a unity demo project, but I have some example code in rust/demo_game. 23 | If you follow these instructions the game code will expect you to setup 2 ScriptablePrefabs called 'monkey' and 'banana' 24 | - Unity 25 | 1. add the 'Runity' unity plugin to your unity project. (You can copy the Runity folder into you unity plugins folder "unity/Runity") 26 | 2. in your scene, add a RunityMono component and assign the name of your 'demo_game.dll' or what your Rust game project will be called. 27 | - Rust 28 | 1. Fork or clone this repository 29 | 2. in the rust folder, we have runity/shared/demo_game, build runity and demo_game 30 | 3. move 'runity.dll' into the unity project, where you placed the runity plugin (unity_project/assets/plugins/runity/runity.dll) 31 | 4. move 'demo_game.dll' into the root of the unity project (unity_project/) 32 | 33 | ### What is up with 'game' folder and 'demo_game'? 34 | the game folder was the spike game for the video. 35 | There you can find some example code on how I handled collision and text as you can see in the video I made about this project. 36 | demo_game was the code for the small monkey banana project I also showed in my video. 37 | 38 | ### How it works 39 | ##### unity/Runity, C# plugin code 40 | Responsible for loading the runity.dll. All C# & Rust communication goes through these scripts. 41 | 42 | ##### rust/runity (dll project) 43 | Handles all C# communication, responsible for the Bevy world. Loads and communicates with the game code 'game.dll'. 44 | Bevy systems are setup to sync the Rust side into C#. 45 | Because of the 'ugly' FFI nature of code I seperated the game code with the communication code 46 | 47 | ##### rust/game or rust/demo_game (dll projects) 48 | Example game projects, all gameplay code is written in such a project, communicates with runity.dll 49 | 50 | ##### shared 51 | this library is used by both runity and game projects 52 | 53 | 54 | ### Areas of improvement 55 | Some ideas I got that needs to be improved 56 | * Instead of holding the game.dll inside the unity project, it should be kept outside so we don't have to close Unity every recompile 57 | * asking Unity for Input is a slow operation because the system always has to be singlethreaded. Maybe inputs should be dumped every frame into rust, allowing for multithreaded systems accessing input 58 | * Spawning gameobjects within a hiearchy of children/parents doesn’t work. They must be sent to Unity in a correct order, which they don’t atm. 59 | * the game.dll and runity.dll has to be compiled withing the same project (I believe). Would be nice if it could be a seperate project. 60 | * RectTransform implementation is hardcoded in C# (don’t look very scary) 61 | * Figure out how to actually process collsion in a neat way instead of having to access the whole bevy world in the system, causing very ugly and inefficent code. 62 | * Hashing of component in C# currently uses a hardcoded identifier. Adding new components may cause unsound logic. (Done in case we need Rust to access Unity Component values like syncing transform before the Update, which we currently don’t do) 63 | -------------------------------------------------------------------------------- /rust/demo_game/src/lib.rs: -------------------------------------------------------------------------------- 1 | use bevy::{prelude::{Res, Commands, IntoExclusiveSystem, Component, Query, With}, core::Time}; 2 | use dlopen::wrapper::Container; 3 | use log::info; 4 | use shared::{plugin::Plugin, declare_plugin, components::{prefab::Prefab, prelude::Transform}, world_link::WorldLink, data::keycode::KeyCode}; 5 | use cgmath::{Vector3, Zero}; 6 | use rand::prelude::*; 7 | 8 | pub fn spawn_system( 9 | mut commands: Commands, 10 | monkies: Query<&Transform, With>, 11 | world_link: Res>, 12 | ) { 13 | if world_link.input_key_held(KeyCode::Space) { 14 | let mut rng = thread_rng(); 15 | for transform in monkies.iter() { 16 | commands.spawn() 17 | .insert(Prefab::new("banana")) 18 | .insert(Transform{position: transform.position + Vector3::new(rng.gen_range(-1f32..1f32), 0f32, rng.gen_range(-1f32..1f32))}) 19 | .insert(Velocity(Vector3::new(rng.gen_range(-3f32..3f32), rng.gen_range(0f32..5f32), rng.gen_range(-3f32..3f32)))) 20 | .insert(MonkeyController); 21 | } 22 | } 23 | } 24 | 25 | #[derive(Component)] 26 | pub struct Velocity(pub Vector3); 27 | pub const GRAVITY: f32 = 20f32; 28 | 29 | pub fn velocity_system( 30 | time: Res