From 17a9983eae11d51aff5d16b7ec2a7c6733544647 Mon Sep 17 00:00:00 2001 From: TheFelidae Date: Sun, 15 Dec 2024 19:57:43 -0800 Subject: [PATCH] added faq --- docs/assets/world-loading-pattern.png | Bin 0 -> 593 bytes docs/faq.md | 78 ++++++++++ docs/technical/world_pipeline.md | 54 +++++++ src/data/world.rs | 2 +- src/game/mod.rs | 2 + src/game/world_chunkmgr/mod.rs | 0 src/game/world_observation/mod.rs | 207 ++++++++++++++++++++++++++ 7 files changed, 342 insertions(+), 1 deletion(-) create mode 100644 docs/assets/world-loading-pattern.png create mode 100644 docs/faq.md create mode 100644 docs/technical/world_pipeline.md create mode 100644 src/game/world_chunkmgr/mod.rs create mode 100644 src/game/world_observation/mod.rs diff --git a/docs/assets/world-loading-pattern.png b/docs/assets/world-loading-pattern.png new file mode 100644 index 0000000000000000000000000000000000000000..d75b130d9f324ef44a09d26d2aa4e761684d5847 GIT binary patch literal 593 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSjKx9jP7LeL$-HD>VB+?4aSW-L z^Y)HmmPn#R!^8T+_YU0CR;l2BFYrdkrZGYA1ZU8V&hig(N$V!uGd_}~=o&IZ_EY+^ z+IjzOJA8YZoM`u~ptSb0#R;oE!(+#9&+l&AUsq>-ZN~mTUlK3=`FnS};W3H*`|2%q zX4)T(pSM54=tLcdf*(U6Q-`p_9EL|M0_qJGj7Kn(N!tUn zzw>9;$>=9+W$=sN$Fc2bK1jLj^ZkVY3dV=Q~58nZupJp3n2$&)3bfudX}&oy81AL*7>NmOXC%@exmX)y@M= zXA#)`n!zvo{;&VLW7!Sb^#9kF$us;{Yp?(tHK)2k^x0kJ4Uss!3AXpSoWeP_j_9Xy z3EylFzWI6czx{Ux9iRzF!2xq0ROlV!qnw6a#byVtem%SRXYTpiIKvTP!HU^`ZEo$U fW>6y(0QC$h3%I(31!PYE(+Gp7tDnm{r-UW|bO-56 literal 0 HcmV?d00001 diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000..747cbf4 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,78 @@ +# Frequently Asked Questions + + +## What is the purpose of Starlight Engine? + +The purpose of this project is to provide tools to build voxel-based games. The project has the following goals in mind: + +- **Modularity**: The project is designed to be modular, so that you can easily add or remove features. +- **Performance**: The project is designed to be performant, so that you can build large worlds. +- **Capability**: The project is designed to be capable, so that you can build complex games. + - This includes features like the ability to handle immense amounts of blocks, entities, and more. + - This also includes features like the ability to change *all aspects* of the game through scripting. +- **Ease of Use**: The project is designed to be easy to use, so that you can quickly build your game. +- **Developer-Friendly**: The project is designed to be developer-friendly. We aim to provide extensive developer tools and debugging features. + - We include and use the `egui` library for runtime debugging tools. + - With them, we allow you to track various aspects of the game, such as the state of the world, the state of the game, and more - Visualized in real-time. + +## What is the current state of Starlight Engine? Is it ready for use? + +Starlight Engine is not yet ready for use. The project is still very early development (it begun mid-December of 2024), +and... Well, it can't run *anything* yet, we're still experimenting with the engine's design and features. + + +## What is the license of Starlight Engine? + +This project is licensed under the GNU General Public License v3.0. You can find the license in the `LICENSE` file in the root of the repository. +Too long? This means that you can use this project for free, but you must share any changes you make to the project with others, +and you must attribute. +You can still use it commercially. + +## Is Starlight Engine free? + +Yes, this project is free. You can use it for free, modify it for free, and distribute it for free. You can also use it commercially. + +***If someone is selling Starlight Engine to you, unmodified, with no other inclusions, packaged material or software, you are being scammed***. Report it to the project maintainers, or the platform you are on. +You are not being scammed if Starlight Engine is being sold as part of a larger package, such as a game, however. + +## Is Starlight Engine open-source? + +It's not only free as in free beer, but also free as in freedom (libre). This project is open-source, which means that you can view the source code, modify it, and distribute it. You can also use it commercially. + +## Can I contribute to Starlight Engine? + +Yes, you can contribute to this project. You can contribute by submitting a pull request, opening an issue, or discussing it in Discussions. You can also contribute by sharing the project with others, or simply by using it and showing what you've made. + +Also, we develop some of the dependencies, or at least contribute to some them. If you see a problem with one of the dependencies, you can contribute to that as well. + +## What kind of games can I build with Starlight Engine? + +Starlight Engine is primarily tailored towards making cubic voxel-based games - The classic example of the *kind* of game you can make with Starlight Engine is Minecraft. However, you are not locked into making Minecraft clones - you can make any kind of game you like, as long as it is voxel-based. + +There may be some features added later to allow custom management of voxel shapes, but for now, it is limited to world voxels. +So games like StarMade and Space Engineers? Not possible. Games like Minecraft, 7 Days to Die, and so on? Absolutely. + + +## Where did you get this idea? + +The idea for this project came from developers who got agitated by shortcomings in other similar voxel engines - Luanti, previously known as Minetest, being the most notable one. The developers wanted to create a voxel engine that was more modular, performant, and capable than existing engines. + +Starlight Engine aims to provide compatibility, or at least optional compatibility, with Luanti, so that developers can easily port their mods and games to Starlight Engine. +This feature is not yet implemented, but is planned. + + +## Is this Minecraft? + +No, this is not Minecraft. This project is a voxel engine, which means that it is a tool to build games with blocks. Minecraft is a game that uses a voxel engine to build a game. This project is not a game, but a tool to build games. + + +## Can I use this project to build a game like Minecraft? + +Not in its current state, but yes, you can use this project to build a game like Minecraft. Potentially even identical to Minecraft. However, you must be aware of the following: + +- You must not use any assets, code, etc from Minecraft in your game. Cloning Minecraft is fine, *copying* Minecraft is not. + - You can *recommend* Minecraft, or that users adapt Minecraft textures ***locally***, just don't distribute them. + - You can say it's a "Minecraft *clone*" if you like. +- You should not use the name "Minecraft" for your game, since it is a trademarked name. +- Avoid using the Creeper, as it (at least supposedly) is a trademarked character due to its recognizability and brand association. +- Read Mojang's guidelines for any other restrictions. diff --git a/docs/technical/world_pipeline.md b/docs/technical/world_pipeline.md new file mode 100644 index 0000000..43fed75 --- /dev/null +++ b/docs/technical/world_pipeline.md @@ -0,0 +1,54 @@ +# World Pipeline documentation + +This document describes the world pipeline, which is the process of generating and presenting the world in the game. + +## Overview + +The world pipeline has these steps: + +1. ***Scripting Engine***: The scripting engine is responsible for managing and running mod/game code. + > Mutates: Game state, World state + > May *immediately* cause generation/loading of chunks. + - Submits `ObservationInShouldLoad` events to the event queue if a chunk was loaded/generated by this. + - Submits `WorldChunkUpdate` events to the event queue if a chunk was updated by this, but also already existed. +2. ***Observation***: We start by determining what the player can see, and from this determine what is to be loaded and what is to be unloaded. + > No mutations + - Consumes `ObservationInShouldLoad` + - Submits `ObservationLoad` and `ObservationUnload` +3. ***Management***: We orchestrate the storage of chunks in the world, and keep track of what is loaded and what is not. + > This is responsible for maintaining a buffer of loaded chunks around those presented. + - Consumes `ObservationLoad` and `ObservationUnload` + - Submits `WorldMgrLoad` and `WorldMgrUnload` +4. ***Population***: We load or generate contents of chunks into memory. + > Mutates: World chunk list + - Consumes `WorldMgrLoad` + - If we successfully generate a chunk, Submits a `WorldGenGenerateSuccess` event to the event queue. + - If we successfully load a chunk from storage, Submits a `WorldGenRestoreSuccess` event to the event queue. + - Either way, we also submit a `WorldGenLoadSuccess` event to the event queue. +5. ***Simulation***: Simulate the world, mutating the state of chunks. + > Mutates: World chunk content state + > We iterate over all loaded chunks and simulate them. + - Submits one or more `WorldChunkUpdate` events to the event queue if a chunk is updated. +6. ***Cleanup***: We unload chunks that are no longer needed gracefully. + > Mutates: Persistent storage + - Consumes `WorldMgrUnload` + - Submits `WorldCleanUnloadSuccess` events to the event queue. +7. ***Physics***: We simulate the physics of entities in the world. + > Mutates: Entity state + - Consumes no events. + - Submits `WorldPhysCollisionWithEntity` events to the event queue if entities collide with each other. + - Submits `WorldPhysCollisionWithWorld` events to the event queue if entities collide with the world. +8. ***(CLIENT) Presentation***: We manage what chunks needs to be drawn. + > No mutations + - Consumes `WorldGenLoadSuccess` and `WorldCleanUnloadSuccess` + - Consumes `WorldChunkUpdate` + - Submits `WorldPresentationAdd` and `WorldPresentationRemove` events to the event queue. + - Submits `WorldPresentationUpdate` events to the event queue if an existing chunk is updated (mutually exclusive with `WorldPresentationAdd`). +9. ***(CLIENT) Meshing***: We generate meshes for chunks that need to be drawn and commit them to Bevy Engine. + > Mutates: Bevy Engine ECS + - Consumes `WorldPresentationAdd` + > Generates a mesh for the chunk and adds it to the ECS Waypoint 1as an entity. + - Consumes `WorldPresentationRemove` + > Removes the mesh entity from the ECS. + - Consumes `WorldPresentationUpdate` + > Fetches the chunk from the ECS and updates the mesh. diff --git a/src/data/world.rs b/src/data/world.rs index f1582e1..a139a4c 100644 --- a/src/data/world.rs +++ b/src/data/world.rs @@ -99,7 +99,7 @@ impl WorldChunkStorage { } } -#[derive(Component, Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Component, Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct WorldChunkCoordinate { pub x: i32, pub y: i32, diff --git a/src/game/mod.rs b/src/game/mod.rs index 81f67cc..0cae7fb 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -12,12 +12,14 @@ pub mod mesher; pub mod mods; pub mod registry; pub mod world_generator; +pub mod world_observation; pub fn app() -> App { let mut app = App::new(); app.add_plugins(DefaultPlugins); app.add_plugins(PlayerPlugin); app.add_plugins(WorldGeneratorPlugin::default()); + app.add_plugins(world_observation::WorldObservationPlugin::default()); app } diff --git a/src/game/world_chunkmgr/mod.rs b/src/game/world_chunkmgr/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/game/world_observation/mod.rs b/src/game/world_observation/mod.rs new file mode 100644 index 0000000..59395dd --- /dev/null +++ b/src/game/world_observation/mod.rs @@ -0,0 +1,207 @@ +/// # World Observation Module +/// +/// The world observation module is responsible for observing the world and determining which chunks should be loaded and unloaded. +/// See docs/technical/world_pipeline.md for more details +/// +/// ## Fires +/// +/// - `ObservationLoadEvent`: When a chunk should be loaded +/// - `ObservationUnloadEvent`: When a chunk should be unloaded + +/* -------------------------------------------------------------------------- */ +/* Misc */ +/* -------------------------------------------------------------------------- */ + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum WorldObserverStatus { + NeedsRefresh, + FromPosition(WorldChunkCoordinate), +} + +/// A component that observes the world. +/// +/// When this is attached to an entity, the entity will be able to observe the world chunks, which will be loaded and unloaded as needed +#[derive(Component, Debug)] +pub struct WorldObserver { + pub status: WorldObserverStatus, + pub view_distance: i32, +} + +impl WorldObserver { + pub fn new() -> Self { + Self { + status: WorldObserverStatus::NeedsRefresh, + view_distance: 3, + } + } +} + +/* -------------------------------------------------------------------------- */ +/* Events */ +/* -------------------------------------------------------------------------- */ + +/// An event that signals that a chunk should be loaded (or generated), since the observation module deems it so +#[derive(Event, Debug)] +pub struct ObservationLoadEvent { + chunk_pos: WorldChunkCoordinate +} + +/// An event that signals that a chunk should be unloaded, since the observation module deems it so +#[derive(Event, Debug)] +pub struct ObservationUnloadEvent { + chunk_pos: WorldChunkCoordinate +} + +/* -------------------------------------------------------------------------- */ +/* Plugin */ +/* -------------------------------------------------------------------------- */ + +use bevy::{app::{App, Plugin, Startup, Update}, prelude::{Commands, Component, Entity, Event, Query, RemovedComponents, Resource, Transform}}; +use egui::mutex::Mutex; + +use crate::data::world::WorldChunkCoordinate; + +use super::world_generator::GameWorld; + +#[derive(Resource)] +pub struct WorldObservationPluginState { + pub debug_menu: bool +} + + +pub struct WorldObservationPlugin { +} + +impl Plugin for WorldObservationPlugin { + fn build(&self, app: &mut App) { + // Add events + app.add_event::(); + app.add_event::(); + app.insert_resource(WorldObservationPluginState { + debug_menu: false + }); + app.add_systems(Startup, sys_setup); + app.add_systems(Update, sys_update); + + } +} + +impl Default for WorldObservationPlugin { + fn default() -> Self { + WorldObservationPlugin {} + } +} + +/* -------------------------------------------------------------------------- */ +/* Basic systems */ +/* -------------------------------------------------------------------------- */ + +fn sys_setup() { + +} + + +fn sys_update( + mut commands: Commands, + mut observers: Query<(Entity, &mut WorldObserver, &Transform)>, + mut removed: RemovedComponents, + world: Query<&GameWorld>, +) { + let world = world.single(); + let commands = Mutex::new(&mut commands); + for (entity, mut observer, transform) in observers.iter_mut() { + let mut distance: i32 = 0; + { + distance = observer.view_distance; + } + let status = &mut observer.status; + + match status { + WorldObserverStatus::NeedsRefresh => { + // Refresh ALL chunks in a DISTANCE radius + for x in -distance..distance { + for y in -distance..distance { + for z in -distance..distance { + let x = x + world.prev_user_position.0 as i32; + let y = y + world.prev_user_position.1 as i32; + let z = z + world.prev_user_position.2 as i32; + + let mut commands = commands.lock(); + commands.send_event(ObservationLoadEvent { chunk_pos: WorldChunkCoordinate { x, y, z } }); + } + } + } + + observer.status = + WorldObserverStatus::FromPosition(WorldChunkCoordinate::from_world( + transform.translation.x, + transform.translation.y, + transform.translation.z, + )); + } + WorldObserverStatus::FromPosition(pos) => { + // Identify current position + let tr = transform.translation; + let current_pos = WorldChunkCoordinate::from_world(tr.x, tr.y, tr.z); + if *pos != current_pos { + // Identify chunks newly coming in and out of visibility, don't worry about chunks that stay in visibility + let d_x = current_pos.x - pos.x; + let d_y = current_pos.y - pos.y; + let d_z = current_pos.z - pos.z; + + // Identify newly visible chunks + for x in -distance..distance { + for y in -distance..distance { + for z in -distance..distance { + let x = x + current_pos.x; + let y = y + current_pos.y; + let z = z + current_pos.z; + // AABB check + if x < pos.x - distance + || x > pos.x + distance + || y < pos.y - distance + || y > pos.y + distance + || z < pos.z - distance + || z > pos.z + distance + { + // Load chunk + let mut commands = commands.lock(); + commands.send_event(ObservationLoadEvent { chunk_pos: WorldChunkCoordinate { x, y, z } }); + } + } + } + } + + // Identify no longer visible chunks + for x in -distance..distance { + for y in -distance..distance { + for z in -distance..distance { + let x = x + pos.x; + let y = y + pos.y; + let z = z + pos.z; + if x < current_pos.x - distance + || x > current_pos.x + distance + || y < current_pos.y - distance + || y > current_pos.y + distance + || z < current_pos.z - distance + || z > current_pos.z + distance + { + // Unload chunk + let mut commands = commands.lock(); + commands.send_event(ObservationUnloadEvent { chunk_pos: WorldChunkCoordinate { x, y, z } }); + } + } + } + } + } + + *pos = current_pos; + } + } + } +} + + +/* -------------------------------------------------------------------------- */ +/* Responder systems */ +/* -------------------------------------------------------------------------- */ \ No newline at end of file