Skip to content

Commit

Permalink
added faq
Browse files Browse the repository at this point in the history
  • Loading branch information
TheFelidae committed Dec 16, 2024
1 parent 55c9b17 commit 17a9983
Show file tree
Hide file tree
Showing 7 changed files with 342 additions and 1 deletion.
Binary file added docs/assets/world-loading-pattern.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
78 changes: 78 additions & 0 deletions docs/faq.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Frequently Asked Questions

<!-- Basic details -->
## 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.

<!-- FLOSS details -->
## 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.

<!-- Story -->
## 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 X? -->
## 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.

<!-- What can I make? -->
## 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.
54 changes: 54 additions & 0 deletions docs/technical/world_pipeline.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion src/data/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions src/game/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Empty file added src/game/world_chunkmgr/mod.rs
Empty file.
207 changes: 207 additions & 0 deletions src/game/world_observation/mod.rs
Original file line number Diff line number Diff line change
@@ -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::<ObservationLoadEvent>();
app.add_event::<ObservationUnloadEvent>();
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<WorldObserver>,
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 */
/* -------------------------------------------------------------------------- */

0 comments on commit 17a9983

Please sign in to comment.