Compare commits
7 Commits
780e3aa520
...
f31a8339d7
| Author | SHA1 | Date |
|---|---|---|
|
|
f31a8339d7 | 1 year ago |
|
|
62680b3b90 | 1 year ago |
|
|
5724e670b7 | 1 year ago |
|
|
3f3d7ef001 | 1 year ago |
|
|
f9acf9e2ff | 1 year ago |
|
|
07b5fbce1a | 1 year ago |
|
|
b44147604d | 1 year ago |
File diff suppressed because it is too large
Load Diff
@ -1,34 +1,8 @@
|
||||
[package]
|
||||
name = "acts-of-gods"
|
||||
version = "0.1.0"
|
||||
version = "0.0.0"
|
||||
edition = "2021"
|
||||
|
||||
|
||||
[dependencies]
|
||||
bevy = { version = "0.14", features = ["file_watcher", "dynamic_linking"] }
|
||||
bevy_mod_picking = "0.20"
|
||||
thiserror = "1"
|
||||
nom = "7"
|
||||
# Entity Uuid parsing
|
||||
# TODO: Remove this
|
||||
uuid = "1.7.0"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
chrono = { version = "0.4" }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = "thin"
|
||||
|
||||
[profile.wasm-release]
|
||||
inherits = "release"
|
||||
# opt-level = "z"
|
||||
opt-level = "s"
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
strip = "debuginfo"
|
||||
bevy = "0.14"
|
||||
bevy_common_assets = "0.11"
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
# Assets
|
||||
|
||||
## Custom Assets
|
||||
|
||||
### `.entity` files
|
||||
|
||||
Files ending in `.entity` store entity information.
|
||||
|
||||
Note: This data is not auto-magically de/serialized by Bevy, we write our own parser using `nom`.
|
||||
This custom reading/writing can be found in `src/save.rs`.
|
||||
|
||||
All `.entity` files list one component per line.
|
||||
Components include:
|
||||
* `name <string>` Human readable name for entity
|
||||
* `uuid <UUID string>` Globally unique ID for entity
|
||||
* `transform translation <f32> <f32> <f32> rotation <f32> <f32> <f32> <f32> scale <f32> <f32> <f32>` 3D Location, Rotation, and Scale for entity
|
||||
* `model "<path string>" "<string>"` Declares the entity's 3d model
|
||||
* `camera` Marks the entity as a camera
|
||||
Binary file not shown.
Binary file not shown.
@ -1 +0,0 @@
|
||||
00/a.entity
|
||||
@ -1 +0,0 @@
|
||||
name "Hello world"
|
||||
@ -1,6 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Name=ActsOfGods
|
||||
Exec=ActsOfGods
|
||||
Icon=ActsOfGods
|
||||
Type=Application
|
||||
Categories=Game;
|
||||
@ -1,32 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Acts of Gods</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>ActsOfGods</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>ActsOfGods.icns</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>your.domain.bevy-game</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>ActsOfGods</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.1.0</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.games</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.9</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -1,16 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<body style="margin: 0px;">
|
||||
<script type="module">
|
||||
import init from './acts-of-gods.js'
|
||||
|
||||
init().catch((error) => {
|
||||
if (!error.message.startsWith("Using exceptions for control flow, don't mind me. This isn't actually an error!")) {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -1,18 +0,0 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
components = [
|
||||
"clippy",
|
||||
"llvm-tools-preview",
|
||||
"rls",
|
||||
"rustc-dev",
|
||||
"rustfmt",
|
||||
# "wasm-bindgen-cli",
|
||||
# "wasm-server-runner",
|
||||
]
|
||||
targets = [
|
||||
# "aarch64-apple-darwin",
|
||||
# "wasm32-unknown-unknown",
|
||||
# "x86_64-apple-darwin",
|
||||
# "x86_64-unknown-linux-gnu",
|
||||
"x86_64-pc-windows-msvc",
|
||||
]
|
||||
@ -1,5 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
TARGET="x86_64-unknown-linux-gnu"
|
||||
|
||||
cargo build --release --target $TARGET
|
||||
@ -1,8 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
AARCH_TARGET="aarch64-apple-darwin"
|
||||
X64_TARGET="x86_64-apple-darwin"
|
||||
|
||||
# Generate x86 and arm builds
|
||||
cargo build --release --target $AARCH_TARGET
|
||||
cargo build --release --target $X64_TARGET
|
||||
@ -1,13 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
BUILD_FILE="./target/$TARGET/release/$NAME.wasm"
|
||||
OUT_DIR="./platforms/web/"
|
||||
NAME="acts-of-gods"
|
||||
TARGET="wasm32-unknown-unknown"
|
||||
|
||||
cargo build --profile wasm-release --target $TARGET
|
||||
|
||||
wasm-bindgen --target web \
|
||||
--out-dir $OUT_DIR \
|
||||
--out-name $NAME \
|
||||
$BUILD_FILE
|
||||
@ -1,3 +0,0 @@
|
||||
|
||||
# Build x86_64 release
|
||||
cargo build --release --target x86_64-pc-windows-msvc
|
||||
@ -1,3 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cp media/ActsOfGods.png platforms/linux/ActsOfGods.AppDir/ActsOfGods.png
|
||||
@ -1,34 +0,0 @@
|
||||
#!/bin/bash
|
||||
# https://gist.github.com/ansarizafar/6fa64f44aa933794c4d6638eec32b9aa
|
||||
|
||||
BASE="media/ActsOfGods.png"
|
||||
OUT_DIR="media/ActsOfGods.iconset"
|
||||
mkdir -p $OUT_DIR
|
||||
|
||||
# 1024x1024
|
||||
sips "$BASE" -Z 1024 -o $OUT_DIR/icon_512x512@2.png
|
||||
|
||||
# 512x512
|
||||
sips "$BASE" -Z 512 -o $OUT_DIR/icon_512x512.png
|
||||
sips "$BASE" -Z 512 -o $OUT_DIR/icon_256x256@2.png
|
||||
|
||||
# 256x256
|
||||
sips "$BASE" -Z 256 -o $OUT_DIR/icon_256x256.png
|
||||
sips "$BASE" -Z 256 -o $OUT_DIR/icon_128x128@2.png
|
||||
|
||||
# 128x128
|
||||
sips "$BASE" -Z 128 -o $OUT_DIR/icon_128x128.png
|
||||
sips "$BASE" -Z 128 -o $OUT_DIR/icon_64x64@2.png
|
||||
|
||||
# 64x64
|
||||
sips "$BASE" -Z 64 -o $OUT_DIR/icon_64x64.png
|
||||
sips "$BASE" -Z 64 -o $OUT_DIR/icon_32x32@2.png
|
||||
|
||||
# 32x32
|
||||
sips "$BASE" -Z 32 -o $OUT_DIR/icon_32x32.png
|
||||
sips "$BASE" -Z 32 -o $OUT_DIR/icon_16x16@2.png
|
||||
|
||||
# 16x16
|
||||
sips "$BASE" -Z 16 -o $OUT_DIR/icon_16x16.png
|
||||
|
||||
iconutil -c icns $OUT_DIR
|
||||
@ -1,37 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
NAME="acts-of-gods"
|
||||
PACKAGE_NAME="ActsOfGods-alpha-linux"
|
||||
|
||||
# Variables for copying binary file
|
||||
TARGET="x86_64-unknown-linux-gnu"
|
||||
APP_DIR="platforms/linux/ActsOfGods.AppDir/"
|
||||
TARGET_DIR="target/$TARGET/release/"
|
||||
BUILD_BIN="$TARGET_DIR/$NAME"
|
||||
PACKAGE_BIN="$APP_DIR/AppRun"
|
||||
|
||||
# Vars for dynamic libraries
|
||||
PACKAGE_LIB_DIR="$APP_DIR/usr/lib/"
|
||||
LIB_DIR="lib/linux/"
|
||||
|
||||
# Assets
|
||||
ASSETS_DIR="assets"
|
||||
|
||||
# Package variables
|
||||
PACKAGE_FILE="$PACKAGE_NAME.AppImage"
|
||||
DEST_FILE="packages/$PACKAGE_FILE"
|
||||
|
||||
########################################
|
||||
|
||||
# Copy binary to build dir
|
||||
cp -f $BUILD_BIN $PACKAGE_BIN
|
||||
|
||||
# Copy dynamic libraries
|
||||
mkdir -p $PACKAGE_LIB_DIR
|
||||
cp -f $LIB_DIR/* $PACKAGE_LIB_DIR/
|
||||
|
||||
# Copy assets
|
||||
cp -rf $ASSET_DIR $APP_DIR/
|
||||
|
||||
# Build AppImage file
|
||||
appimagetool $APP_DIR $DEST_FILE
|
||||
@ -1,52 +0,0 @@
|
||||
#!/bin/bash
|
||||
# https://github.com/create-dmg/create-dmg/tree/master?tab=readme-ov-file#create-dmg
|
||||
|
||||
NAME='Acts of Gods.app'
|
||||
APP="platforms/macos/$NAME"
|
||||
CONTENTS="$APP/Contents"
|
||||
GAME="$CONTENTS/MacOS"
|
||||
RESOURCES="$CONTENTS/Resources"
|
||||
FRAMEWORKS="$CONTENTS/Frameworks"
|
||||
|
||||
mkdir -p "$APP"
|
||||
mkdir -p "$GAME"
|
||||
mkdir -p "$RESOURCES"
|
||||
mkdir -p "$FRAMEWORKS"
|
||||
|
||||
# Copy icons to package
|
||||
cp -f media/ActsOfGods.icns "$RESOURCES"
|
||||
|
||||
# Generate cross-architecture binary
|
||||
rm -f "$GAME/ActsOfGods"
|
||||
lipo "target/x86_64-apple-darwin/release/acts-of-gods" \
|
||||
"target/aarch64-apple-darwin/release/acts-of-gods" \
|
||||
-create -output "$GAME/ActsOfGods"
|
||||
|
||||
# Copy assets
|
||||
rm -rf "$GAME/assets"
|
||||
cp -r assets "$GAME/"
|
||||
|
||||
# Copy fmod libraries
|
||||
cp -f lib/macos/libfmod.dylib "$FRAMEWORKS/"
|
||||
cp -f lib/macos/libfmodstudio.dylib "$FRAMEWORKS/"
|
||||
|
||||
# Update dynamic linking search paths
|
||||
install_name_tool -change @rpath/libfmod.dylib "@loader_path/../Frameworks/libfmod.dylib" "$GAME/"
|
||||
install_name_tool -change @rpath/libfmodstudio.dylib "@loader_path/../Frameworks/libfmodstudio.dylib" "$GAME/ActsOfGods"
|
||||
|
||||
# Build dmg file
|
||||
PACKAGE_FILE="packages/ActsOfGods-alpha-macos.dmg"
|
||||
rm -f $PACKAGE_FILE
|
||||
create-dmg \
|
||||
--volname "Acts of Gods" \
|
||||
--volicon "media/ActsOfGods.icns" \
|
||||
--window-pos 200 120 \
|
||||
--window-size 800 400 \
|
||||
--app-drop-link 600 200 \
|
||||
--icon-size 100 \
|
||||
--icon "$NAME" 200 190 \
|
||||
--hide-extension "$NAME" \
|
||||
--app-drop-link 600 185 \
|
||||
$PACKAGE_FILE \
|
||||
./platforms/macos/
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Just zip platforms/web
|
||||
@ -1,11 +0,0 @@
|
||||
Copy-Item -Force "./target/x86_64-pc-windows-msvc/release/acts-of-gods.exe" "./platforms/windows/ActsOfGods.exe"
|
||||
Copy-Item -Force "./lib/windows/*" "./platforms/windows/"
|
||||
Copy-Item -Force -Recurse "./assets" "./platforms/windows"
|
||||
|
||||
$compress = @{
|
||||
Path = "./platforms/windows/*"
|
||||
CompressionLevel = "Optimal"
|
||||
DestinationPath = "./packages/ActsOfGods-alpha-windows.zip"
|
||||
}
|
||||
|
||||
Compress-Archive -Force @compress
|
||||
@ -1,127 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Menu Plugin; empty struct for Plugin impl
|
||||
pub(crate) struct CameraPlugin;
|
||||
|
||||
impl Plugin for CameraPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(
|
||||
Update,
|
||||
move_editor_fly_camera.run_if(any_with_component::<FlyCamera>),
|
||||
);
|
||||
app.add_systems(
|
||||
Update,
|
||||
rotate_editor_fly_camera.run_if(any_with_component::<FlyCamera>),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub(crate) struct FlyCamera;
|
||||
|
||||
/// Fly camera system for moving around like a drone
|
||||
/// TODO: Only if key is pressed!
|
||||
fn move_editor_fly_camera(
|
||||
mut cameras: Query<(&Camera, &mut Transform), With<FlyCamera>>,
|
||||
windows: Query<&Window>,
|
||||
primary_window: Query<Entity, With<PrimaryWindow>>,
|
||||
keys: Res<ButtonInput<KeyCode>>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
(keys.any_pressed([
|
||||
KeyCode::KeyW,
|
||||
KeyCode::KeyS,
|
||||
KeyCode::KeyA,
|
||||
KeyCode::KeyD,
|
||||
KeyCode::KeyQ,
|
||||
KeyCode::KeyE,
|
||||
]))
|
||||
.then(|| {
|
||||
// Iterate over all cameras
|
||||
cameras.iter_mut().for_each(|(c, mut t)| {
|
||||
// Determine which window this camera is attached to
|
||||
let target_window = match c.target {
|
||||
RenderTarget::Window(wr) => match wr {
|
||||
WindowRef::Entity(e) => Some(e),
|
||||
WindowRef::Primary => Some(primary_window.get_single().unwrap()),
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
let window = windows.get(target_window.unwrap()).unwrap();
|
||||
|
||||
// If the target window is focused
|
||||
window.focused.then(|| {
|
||||
let move_speed = if keys.pressed(KeyCode::ShiftLeft) {
|
||||
16.0
|
||||
} else {
|
||||
4.0
|
||||
};
|
||||
let mut delta = Vec3::ZERO;
|
||||
if keys.pressed(KeyCode::KeyW) {
|
||||
delta += t.forward() * move_speed * time.delta_seconds()
|
||||
}
|
||||
if keys.pressed(KeyCode::KeyS) {
|
||||
delta += t.back() * move_speed * time.delta_seconds()
|
||||
}
|
||||
if keys.pressed(KeyCode::KeyA) {
|
||||
delta += t.left() * move_speed * time.delta_seconds()
|
||||
}
|
||||
if keys.pressed(KeyCode::KeyD) {
|
||||
delta += t.right() * move_speed * time.delta_seconds()
|
||||
}
|
||||
if keys.pressed(KeyCode::KeyE) {
|
||||
delta += Vec3::Y * move_speed * time.delta_seconds()
|
||||
}
|
||||
if keys.pressed(KeyCode::KeyQ) {
|
||||
delta += Vec3::NEG_Y * move_speed * time.delta_seconds()
|
||||
}
|
||||
t.translation += delta;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn rotate_editor_fly_camera(
|
||||
mut cameras: Query<(&Camera, &mut Transform), With<FlyCamera>>,
|
||||
windows: Query<&Window>,
|
||||
primary_window: Query<Entity, With<PrimaryWindow>>,
|
||||
mouse: Res<ButtonInput<MouseButton>>,
|
||||
mut cursor_events: EventReader<CursorMoved>,
|
||||
) {
|
||||
(!cursor_events.is_empty()).then(|| {
|
||||
// Iterate over all cameras
|
||||
cameras.iter_mut().for_each(|(c, mut t)| {
|
||||
// Determine which window this camera is attached to
|
||||
let target_window = match c.target {
|
||||
RenderTarget::Window(wr) => match wr {
|
||||
WindowRef::Entity(e) => Some(e),
|
||||
WindowRef::Primary => Some(primary_window.get_single().unwrap()),
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
let window = windows.get(target_window.unwrap()).unwrap();
|
||||
if mouse.pressed(MouseButton::Middle) {
|
||||
cursor_events
|
||||
.read()
|
||||
.filter_map(|CursorMoved { delta, window, .. }| {
|
||||
(*window == target_window.unwrap()).then_some(delta)
|
||||
})
|
||||
.for_each(|delta| {
|
||||
if let Some(Vec2 { x, y }) = delta {
|
||||
// Cribbing from bevy_flycam
|
||||
// Link: https://github.com/sburris0/bevy_flycam/blob/baffe50e0961ad1491d467fa6ab5551f9f21db8f/src/lib.rs#L145-L151
|
||||
let (mut yaw, mut pitch, _) = t.rotation.to_euler(EulerRot::YXZ);
|
||||
let window_scale = window.height().min(window.width());
|
||||
let sensitivity = 0.00012;
|
||||
pitch -= (sensitivity * y * window_scale).to_radians();
|
||||
yaw -= (sensitivity * x * window_scale).to_radians();
|
||||
t.rotation = Quat::from_axis_angle(Vec3::Y, yaw)
|
||||
* Quat::from_axis_angle(Vec3::X, pitch);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cursor_events.clear();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
///
|
||||
/// ECS scheduler active when an entity has a given component added
|
||||
///
|
||||
pub(crate) fn any_component_added<C: Component>(q: Query<Entity, Added<C>>) -> bool {
|
||||
!q.is_empty()
|
||||
}
|
||||
@ -1,123 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub(crate) struct EditorPlugin;
|
||||
|
||||
impl Plugin for EditorPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_state::<EditorState>()
|
||||
.add_systems(OnEnter(EditorState::Open), spawn_editor)
|
||||
.add_systems(OnExit(EditorState::Open), despawn_editor)
|
||||
.add_systems(
|
||||
Update,
|
||||
toggle_editor_state.run_if(input_just_pressed(KeyCode::F3)),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
handle_window_close.run_if(on_event::<WindowCloseRequested>()),
|
||||
plane_gizmos,
|
||||
)
|
||||
.run_if(in_state(EditorState::Open)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Tracking the open/closed state of the editor
|
||||
#[derive(States, Debug, Clone, PartialEq, Eq, Hash, Default, Component)]
|
||||
pub(crate) enum EditorState {
|
||||
/// The editor is closed => the editor window is disabled
|
||||
#[default]
|
||||
Closed,
|
||||
/// The editor is open => the editor window is visible
|
||||
Open,
|
||||
}
|
||||
|
||||
impl std::ops::Not for &EditorState {
|
||||
type Output = EditorState;
|
||||
|
||||
fn not(self) -> Self::Output {
|
||||
match self {
|
||||
EditorState::Open => EditorState::Closed,
|
||||
EditorState::Closed => EditorState::Open,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<&EditorState> for bool {
|
||||
fn from(value: &EditorState) -> bool {
|
||||
match value {
|
||||
EditorState::Open => true,
|
||||
EditorState::Closed => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tag component for editor entities
|
||||
#[derive(Component, Reflect, Debug, Default)]
|
||||
#[reflect(Component, Default)]
|
||||
pub(crate) struct EditorTag;
|
||||
|
||||
fn toggle_editor_state(
|
||||
curr_state: Res<State<EditorState>>,
|
||||
mut next_state: ResMut<NextState<EditorState>>,
|
||||
keys: Res<ButtonInput<KeyCode>>,
|
||||
mut catch: Local<bool>,
|
||||
) {
|
||||
if keys.pressed(KeyCode::F3) && !*catch {
|
||||
*catch = true;
|
||||
match curr_state.get() {
|
||||
EditorState::Open => next_state.set(EditorState::Closed),
|
||||
EditorState::Closed => next_state.set(EditorState::Open),
|
||||
}
|
||||
} else {
|
||||
*catch = false;
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_editor(mut _commands: Commands) {
|
||||
todo!("Spawn editor");
|
||||
}
|
||||
|
||||
fn despawn_editor(mut _comands: Commands) {
|
||||
todo!("Despawn editor");
|
||||
}
|
||||
|
||||
/// Handles window close requests which are only weird because of the editor
|
||||
/// When the editor window closes, just make it invisible and change the editor state to "Closed".
|
||||
fn handle_window_close(
|
||||
mut events: EventReader<WindowCloseRequested>,
|
||||
editor_windows: Query<Entity, With<EditorTag>>,
|
||||
mut editor_state: ResMut<NextState<EditorState>>,
|
||||
) {
|
||||
events
|
||||
.read()
|
||||
// Filter events down to jus those affecting the editor window
|
||||
.filter_map(|WindowCloseRequested { window }| editor_windows.get(*window).ok())
|
||||
// For each related close event, send out a "Close the editor" state transition
|
||||
.for_each(|_| {
|
||||
editor_state.set(EditorState::Closed);
|
||||
});
|
||||
}
|
||||
|
||||
fn plane_gizmos(mut gizmos: Gizmos) {
|
||||
let r = 20.0;
|
||||
let start = -r as i8;
|
||||
let end = r as i8;
|
||||
(start..=end).for_each(|n| {
|
||||
{
|
||||
let offset = Vec3::Z * (n as f32);
|
||||
gizmos.line(Vec3::NEG_X * r + offset, Vec3::X * r + offset, GRAY);
|
||||
}
|
||||
{
|
||||
let offset = Vec3::X * (n as f32);
|
||||
gizmos.line(Vec3::NEG_Z * r + offset, Vec3::Z * r + offset, GRAY);
|
||||
}
|
||||
});
|
||||
|
||||
// World origin arrows
|
||||
{
|
||||
gizmos.arrow(Vec3::ZERO, Vec3::X, RED);
|
||||
gizmos.arrow(Vec3::ZERO, Vec3::Y, DARK_GREEN);
|
||||
gizmos.arrow(Vec3::ZERO, Vec3::Z, BLUE);
|
||||
}
|
||||
}
|
||||
@ -1,38 +1,3 @@
|
||||
#![feature(let_chains)]
|
||||
|
||||
/// Camera controller
|
||||
pub(crate) mod camera;
|
||||
/// ECS scheduling `run_if` conditions
|
||||
pub(crate) mod conditions;
|
||||
/// Editor: debugging and run-time modifications to the game
|
||||
pub(crate) mod editor;
|
||||
/// Menu: Main and otherwise
|
||||
pub(crate) mod menu;
|
||||
/// Save file parsing
|
||||
pub(crate) mod parser;
|
||||
/// Helper module containing common imports across the project
|
||||
pub(crate) mod prelude;
|
||||
/// Level saving/loading logic
|
||||
pub(crate) mod save;
|
||||
/// Window handling
|
||||
pub(crate) mod window;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||
// handled in crate::window::handle_window_close and crate::editor::handle_window_close
|
||||
close_when_requested: false,
|
||||
exit_condition: bevy::window::ExitCondition::OnPrimaryClosed,
|
||||
..default()
|
||||
}))
|
||||
.add_plugins(camera::CameraPlugin)
|
||||
// .add_plugins(editor::EditorPlugin)
|
||||
// .add_plugins(menu::MenuPlugin)
|
||||
// .add_plugins(window::WindowPlugin)
|
||||
.add_plugins(save::SavePlugin {
|
||||
fns: vec![parse_save_name],
|
||||
})
|
||||
.run();
|
||||
println!("Hello, world!");
|
||||
}
|
||||
|
||||
@ -1,58 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub(crate) struct MenuPlugin;
|
||||
|
||||
impl Plugin for MenuPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(Startup, init_menu);
|
||||
}
|
||||
}
|
||||
|
||||
fn init_menu(mut commands: Commands) {
|
||||
// commands.spawn(Camera3dBundle { ..default() });
|
||||
commands
|
||||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
max_width: Val::Percent(60.0),
|
||||
max_height: Val::Percent(100.0),
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
})
|
||||
.with_children(|parent| {
|
||||
parent.spawn(TextBundle {
|
||||
style: Style { ..default() },
|
||||
text: Text {
|
||||
justify: JustifyText::Center,
|
||||
sections: vec![
|
||||
TextSection::new(
|
||||
"ACTS",
|
||||
TextStyle {
|
||||
font_size: 384.0,
|
||||
color: Color::WHITE,
|
||||
..default()
|
||||
},
|
||||
),
|
||||
TextSection::new(
|
||||
"of\n",
|
||||
TextStyle {
|
||||
font_size: 16.0,
|
||||
color: Color::WHITE,
|
||||
..default()
|
||||
},
|
||||
),
|
||||
TextSection::new(
|
||||
"GODS",
|
||||
TextStyle {
|
||||
font_size: 384.0,
|
||||
color: Color::WHITE,
|
||||
..default()
|
||||
},
|
||||
),
|
||||
],
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -1,460 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub(crate) enum SaveEntityParseError {
|
||||
#[error("Failed to parse `{0}`")]
|
||||
Component(String),
|
||||
#[error("Failed to parse Entity")]
|
||||
Nom(nom::Err<nom::error::Error<Box<str>>>),
|
||||
}
|
||||
|
||||
// Convert Nom error to parse error
|
||||
// https://stackoverflow.com/a/77974858/3096574
|
||||
impl From<nom::Err<nom::error::Error<&str>>> for SaveEntityParseError {
|
||||
fn from(err: nom::Err<nom::error::Error<&str>>) -> Self {
|
||||
SaveEntityParseError::Nom(err.map_input(|input| input.into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) enum Token {
|
||||
Tag(String),
|
||||
Str(String),
|
||||
Num(f32),
|
||||
}
|
||||
|
||||
///
|
||||
/// Simple function to tokenize a string into an array of Token values
|
||||
///
|
||||
pub(crate) fn tokenize(line: &str) -> Vec<Token> {
|
||||
let mut l = line;
|
||||
let mut tokens = Vec::new();
|
||||
|
||||
while l.len() > 0 {
|
||||
debug!("Line: {:?}", l);
|
||||
if let Ok((rem, (_, s, _))) = tuple((
|
||||
char::<&str, ()>('"'),
|
||||
take_until("\""),
|
||||
char::<&str, ()>('"'),
|
||||
))(l)
|
||||
{
|
||||
debug!("Parsed string {:?}", s);
|
||||
tokens.push(Token::Str(s.into()));
|
||||
l = rem;
|
||||
} else if let Ok((rem, num)) = float::<&str, ()>(l) {
|
||||
debug!("Parsed float {:?}", num);
|
||||
tokens.push(Token::Num(num.into()));
|
||||
l = rem;
|
||||
} else if let Ok((rem, (_, tag, _))) = tuple((space0, alphanumeric1::<&str, ()>, space0))(l)
|
||||
{
|
||||
debug!("Parsed tag {:?}", tag);
|
||||
tokens.push(Token::Tag(tag.into()));
|
||||
l = rem;
|
||||
} else {
|
||||
debug!("Breaking loop");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
tokens
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tokenize() {
|
||||
let line = "foo \"bar\" 1.23 baz -3.45 \"asdf\" \"multi word string\" etc";
|
||||
assert_eq!(
|
||||
tokenize(line),
|
||||
vec![
|
||||
Token::Tag("foo".into()),
|
||||
Token::Str("bar".into()),
|
||||
Token::Num(1.23),
|
||||
Token::Tag("baz".into()),
|
||||
Token::Num(-3.45),
|
||||
Token::Str("asdf".into()),
|
||||
Token::Str("multi word string".into()),
|
||||
Token::Tag("etc".into())
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns reflected `Transform`
|
||||
///
|
||||
pub(crate) fn parse_save_transform(
|
||||
tokens: &Vec<Token>,
|
||||
) -> Result<Box<dyn Reflect>, SaveEntityParseError> {
|
||||
// Tag(Transform),
|
||||
// Tag(Translation), Number, Number, Number
|
||||
// Tag(Rotation), Number, Number, Number, Number
|
||||
// Tag(Scale), Number, Number, Number
|
||||
|
||||
// return Err(SaveEntityParseError::Transform);
|
||||
|
||||
todo!("parse_save_transform");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_transform() {
|
||||
let line = "transform translation 1.0 2.0 3.0 rotation 0.1 0.2 0.3 1.0 scale 1.1 1.2 1.3";
|
||||
let tokens = tokenize(line);
|
||||
let parsed = parse_save_transform(&tokens).unwrap();
|
||||
let expected = Transform {
|
||||
translation: Vec3::new(1.0, 2.0, 3.0),
|
||||
rotation: Quat::from_xyzw(0.1, 0.2, 0.3, 1.0),
|
||||
scale: Vec3::new(1.1, 1.2, 1.3),
|
||||
};
|
||||
|
||||
assert!(expected
|
||||
.clone_value()
|
||||
.reflect_partial_eq(parsed.as_reflect())
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns a reflected `Name`
|
||||
///
|
||||
pub(crate) fn parse_save_name(
|
||||
tokens: &Vec<Token>,
|
||||
) -> Result<Box<dyn Reflect>, SaveEntityParseError> {
|
||||
if let Some((Token::Tag(t), &[Token::Str(ref s)])) = tokens.split_first()
|
||||
&& *t == String::from("name")
|
||||
{
|
||||
Ok(Name::new(s.clone()).clone_value())
|
||||
} else {
|
||||
Err(SaveEntityParseError::Component("Name".into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_name() {
|
||||
{
|
||||
let line = "name Van";
|
||||
let tokens = tokenize(line);
|
||||
let parsed = parse_save_name(&tokens).unwrap();
|
||||
let expected = Name::new("Van");
|
||||
|
||||
assert!(expected
|
||||
.clone_value()
|
||||
.reflect_partial_eq(parsed.as_reflect())
|
||||
.unwrap());
|
||||
}
|
||||
{
|
||||
let line = "name Big Mike";
|
||||
let tokens = tokenize(line);
|
||||
let parsed = parse_save_name(&tokens).unwrap();
|
||||
let expected = Name::new("Big Mike");
|
||||
|
||||
assert!(expected
|
||||
.clone_value()
|
||||
.reflect_partial_eq(parsed.as_reflect())
|
||||
.unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Reflect, PartialEq)]
|
||||
#[reflect(Component, PartialEq)]
|
||||
pub(crate) struct SaveModel {
|
||||
gltf_file: PathBuf,
|
||||
scene_name: String,
|
||||
}
|
||||
|
||||
impl Component for SaveModel {
|
||||
const STORAGE_TYPE: StorageType = StorageType::Table;
|
||||
|
||||
fn register_component_hooks(hooks: &mut ComponentHooks) {
|
||||
todo!("Assign Scene Handle for SaveModel")
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns a reflected `SaveModel`
|
||||
///
|
||||
pub(crate) fn parse_save_model(
|
||||
tokens: &Vec<Token>,
|
||||
) -> Result<Box<dyn Reflect>, SaveEntityParseError> {
|
||||
todo!("parse_save_model");
|
||||
|
||||
/*
|
||||
Ok(SaveModel {
|
||||
gltf_file: gltf_path.into(),
|
||||
scene_name: scene_name.into(),
|
||||
}
|
||||
.clone_value())
|
||||
*/
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_model() {
|
||||
let line = "model \"models/foo.glb\" \"My Scene\"";
|
||||
let tokens = tokenize(line);
|
||||
let parsed = parse_save_model(&tokens).unwrap();
|
||||
let expected = SaveModel {
|
||||
gltf_file: "models/foo.glb".into(),
|
||||
scene_name: "My Scene".into(),
|
||||
};
|
||||
|
||||
assert!(expected
|
||||
.clone_value()
|
||||
.reflect_partial_eq(parsed.as_reflect())
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Reflect, Clone)]
|
||||
#[reflect_value(Component, Default, PartialEq)]
|
||||
pub(crate) enum SaveCameraRenderTarget {
|
||||
#[default]
|
||||
Default,
|
||||
Window(PathBuf),
|
||||
}
|
||||
|
||||
impl Component for SaveCameraRenderTarget {
|
||||
const STORAGE_TYPE: StorageType = StorageType::Table;
|
||||
|
||||
fn register_component_hooks(hooks: &mut ComponentHooks) {
|
||||
todo!("Assign Render Target")
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns a reflected `SaveCameraRenderTarget
|
||||
///
|
||||
pub(crate) fn parse_save_camera(
|
||||
tokens: &Vec<Token>,
|
||||
) -> Result<Box<dyn Reflect>, SaveEntityParseError> {
|
||||
todo!("parse_save_camera");
|
||||
|
||||
// Nothing parsed well
|
||||
// Err(SaveEntityParseError::Camera)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_camera() {
|
||||
{
|
||||
let line = "camera";
|
||||
let tokens = tokenize(line);
|
||||
let parsed = parse_save_camera(&tokens).unwrap();
|
||||
let expected = SaveCameraRenderTarget::Default;
|
||||
assert!(expected
|
||||
.clone_value()
|
||||
.reflect_partial_eq(parsed.as_reflect())
|
||||
.unwrap());
|
||||
}
|
||||
{
|
||||
let line = "camera target window";
|
||||
let tokens = tokenize(line);
|
||||
let parsed = parse_save_camera(&tokens).unwrap();
|
||||
let expected = SaveCameraRenderTarget::Default;
|
||||
assert!(expected
|
||||
.clone_value()
|
||||
.reflect_partial_eq(parsed.as_reflect())
|
||||
.unwrap());
|
||||
}
|
||||
{
|
||||
let line = "camera target";
|
||||
let tokens = tokenize(line);
|
||||
let parsed = parse_save_camera(&tokens);
|
||||
assert!(parsed.is_err());
|
||||
}
|
||||
{
|
||||
let line = "camera target window \"some.entity\"";
|
||||
let tokens = tokenize(line);
|
||||
let parsed = parse_save_camera(&tokens).unwrap();
|
||||
let expected = SaveCameraRenderTarget::Window("some.entity".into());
|
||||
assert!(expected
|
||||
.clone_value()
|
||||
.reflect_partial_eq(parsed.as_reflect())
|
||||
.unwrap());
|
||||
}
|
||||
{
|
||||
let line = "notcamera";
|
||||
let tokens = tokenize(line);
|
||||
let parsed = parse_save_camera(&tokens);
|
||||
assert!(parsed.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
/// SaveParent entity which is a reference to which entity this is a child of
|
||||
/// A run-time system converts this to a bevy Parent component
|
||||
#[derive(PartialEq, Debug, Reflect, Clone)]
|
||||
#[reflect_value(Component, PartialEq)]
|
||||
pub(crate) struct SaveParent(String);
|
||||
|
||||
impl Component for SaveParent {
|
||||
const STORAGE_TYPE: StorageType = StorageType::Table;
|
||||
|
||||
fn register_component_hooks(hooks: &mut ComponentHooks) {
|
||||
todo!("Assign parent for entity")
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Parses a parent entity with this format:
|
||||
/// ```text
|
||||
/// parent some_other_file.entity
|
||||
/// ```
|
||||
///
|
||||
/// Returns a reflected `SaveParent`
|
||||
///
|
||||
pub(crate) fn parse_save_parent(
|
||||
tokens: &Vec<Token>,
|
||||
) -> Result<Box<dyn Reflect>, SaveEntityParseError> {
|
||||
todo!("parse_save_parent");
|
||||
|
||||
// Ok(SaveParent(parent_file.into()).clone_value())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_parent() {
|
||||
let line = "parent \"some_other_file.entity\"";
|
||||
let tokens = tokenize(line);
|
||||
let parsed = parse_save_parent(&tokens).unwrap();
|
||||
let expected = SaveParent("some_other_file.entity".into());
|
||||
assert!(expected
|
||||
.clone_value()
|
||||
.reflect_partial_eq(parsed.as_reflect())
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
///
|
||||
/// Parse the Window component (which is very big!)
|
||||
/// We only sparsely define the bits that we want to edit (for now)
|
||||
///
|
||||
/// Returns a reflected `Window`
|
||||
///
|
||||
pub(crate) fn parse_save_window(
|
||||
tokens: &Vec<Token>,
|
||||
) -> Result<Box<dyn Reflect>, SaveEntityParseError> {
|
||||
todo!("parse_save_window");
|
||||
|
||||
/*
|
||||
Ok(Window {
|
||||
title: window_title.into(),
|
||||
visible: visibility,
|
||||
..default()
|
||||
}
|
||||
.clone_value())
|
||||
*/
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_window() {
|
||||
let line = "window \"Editor\" visible false";
|
||||
let tokens = tokenize(line);
|
||||
let parsed = parse_save_window(&tokens).unwrap();
|
||||
let expected = Window {
|
||||
visible: false,
|
||||
title: "Editor".into(),
|
||||
..default()
|
||||
};
|
||||
assert!(expected
|
||||
.clone_value()
|
||||
.reflect_partial_eq(parsed.as_reflect())
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
///
|
||||
/// The UI Text bundle specified as a sparse subset of a bundle
|
||||
/// ```text
|
||||
/// uiText "This is the text" color #abc123 size 12.34
|
||||
/// ```
|
||||
///
|
||||
/// Returns a reflected `Text`
|
||||
///
|
||||
pub(crate) fn parse_save_ui_text(
|
||||
tokens: &Vec<Token>,
|
||||
) -> Result<Box<dyn Reflect>, SaveEntityParseError> {
|
||||
todo!("parse_save_ui_text");
|
||||
|
||||
/*
|
||||
let style = TextStyle {
|
||||
color: Color::Srgba(Srgba::hex(hex_color).unwrap()),
|
||||
font_size,
|
||||
..default()
|
||||
};
|
||||
Ok(Text::from_section(text, style).clone_value())
|
||||
*/
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_save_ui_text() {
|
||||
let line = "uiText \"This is the text\" color #caffee size 14.6";
|
||||
let tokens = tokenize(line);
|
||||
let parsed = parse_save_ui_text(&tokens).unwrap();
|
||||
let expected = Text::from_section(
|
||||
"This is the text",
|
||||
TextStyle {
|
||||
color: Srgba::hex("#caffee").unwrap().into(),
|
||||
font_size: 14.6,
|
||||
..default()
|
||||
},
|
||||
);
|
||||
assert!(expected
|
||||
.clone_value()
|
||||
.reflect_partial_eq(parsed.as_reflect())
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns a parser function for a basic stand-alone tag
|
||||
/// e.g., "editor_tag" or "ui_node"
|
||||
///
|
||||
pub(crate) fn parse_save_tag<T: Component + Reflect + Default>(
|
||||
t: &str,
|
||||
) -> impl FnOnce(&Vec<Token>) -> Result<Box<dyn Reflect>, SaveEntityParseError> + '_ {
|
||||
move |tokens: &Vec<Token>| -> Result<Box<dyn Reflect>, SaveEntityParseError> {
|
||||
todo!("parse_save_tag")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect, PartialEq, Debug, Default)]
|
||||
#[reflect(Component)]
|
||||
struct TestingTag;
|
||||
|
||||
#[test]
|
||||
fn test_save_tag() {
|
||||
let line = "testing";
|
||||
let tokens = tokenize(line);
|
||||
let parsed = parse_save_tag::<TestingTag>("testing")(&tokens).unwrap();
|
||||
let expected = TestingTag;
|
||||
assert!(expected
|
||||
.clone_value()
|
||||
.reflect_partial_eq(parsed.as_reflect())
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Reflect, PartialEq)]
|
||||
#[reflect(Component)]
|
||||
pub(crate) struct SaveTargetCamera(String);
|
||||
|
||||
impl Component for SaveTargetCamera {
|
||||
const STORAGE_TYPE: StorageType = StorageType::Table;
|
||||
|
||||
fn register_component_hooks(hooks: &mut ComponentHooks) {
|
||||
todo!("Assign target camera")
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses the a SaveTargetCamera which at runtime is converted to a TargetCamera
|
||||
/// ```text
|
||||
/// targetCamera "./level-camera.entity"
|
||||
/// ```
|
||||
///
|
||||
/// Returns reflected `SaveTargetCamera`
|
||||
pub(crate) fn parse_save_target_camera(
|
||||
tokens: &Vec<Token>,
|
||||
) -> Result<Box<dyn Reflect>, SaveEntityParseError> {
|
||||
todo!("parse_save_target_camera")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_target_camera() {
|
||||
let line = "targetCamera \"./level-camera.entity\"";
|
||||
let tokens = tokenize(line);
|
||||
let parsed = parse_save_target_camera(&tokens).unwrap();
|
||||
let expected = SaveTargetCamera("./level-camera.entity".into());
|
||||
|
||||
assert!(expected
|
||||
.clone_value()
|
||||
.reflect_partial_eq(parsed.as_reflect())
|
||||
.unwrap());
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
pub(crate) use bevy::ecs::reflect::ReflectCommandExt;
|
||||
pub(crate) use bevy::{
|
||||
app::AppExit,
|
||||
asset::{io::Reader, AssetLoader, AsyncReadExt, LoadContext},
|
||||
color::palettes::css::{BLUE, DARK_GREEN, GRAY, RED},
|
||||
gltf::Gltf,
|
||||
input::common_conditions::input_just_pressed,
|
||||
prelude::*,
|
||||
render::camera::RenderTarget,
|
||||
window::{PrimaryWindow, WindowCloseRequested, WindowRef},
|
||||
};
|
||||
pub(crate) use nom::{
|
||||
bytes::complete::take_until,
|
||||
character::complete::{char, space0, alphanumeric1},
|
||||
number::complete::float,
|
||||
sequence::tuple,
|
||||
};
|
||||
pub(crate) use std::path::PathBuf;
|
||||
pub(crate) use thiserror::Error;
|
||||
|
||||
pub(crate) use crate::{conditions::*, parser::*};
|
||||
|
||||
pub(crate) use bevy::ecs::component::{ComponentHooks, StorageType};
|
||||
@ -1,290 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
type ParseFn =
|
||||
for<'a> fn(&'a Vec<Token>) -> Result<Box<(dyn Reflect + 'static)>, SaveEntityParseError>;
|
||||
|
||||
/// Menu Plugin; contains parser functions
|
||||
pub(crate) struct SavePlugin {
|
||||
pub fns: Vec<ParseFn>,
|
||||
}
|
||||
|
||||
impl Plugin for SavePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.register_type::<SaveCameraRenderTarget>()
|
||||
.register_type::<GltfScene>()
|
||||
.register_type::<SaveModel>()
|
||||
.init_asset::<SaveEntity>()
|
||||
.register_asset_loader(SaveEntityLoader {
|
||||
fns: self.fns.clone(),
|
||||
})
|
||||
.init_asset::<SaveScene>()
|
||||
.init_asset_loader::<SaveSceneLoader>()
|
||||
.add_systems(Startup, load_scenes)
|
||||
.add_systems(
|
||||
Update,
|
||||
spawn_scenes.run_if(on_event::<AssetEvent<SaveScene>>()),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
sync_entities
|
||||
.run_if(on_event::<AssetEvent<SaveEntity>>())
|
||||
.after(spawn_scenes),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
spawn_entities
|
||||
.run_if(any_component_added::<Handle<SaveEntity>>)
|
||||
.after(sync_entities),
|
||||
)
|
||||
.add_systems(Update, debug_entities);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Asset, TypePath, Default)]
|
||||
pub(crate) struct SaveScene {
|
||||
entities: Vec<Handle<SaveEntity>>,
|
||||
}
|
||||
|
||||
// TODO: SCALABILITY: SaveEntity should maybe be a HashSet or Vec of Box<into Bundle>
|
||||
// Then we tell the parse "For each line, run through each of these concrete parsers"
|
||||
// If it matches, add it to the set of Box<Bundle>
|
||||
// Ironically we basically want DynamicEntity: https://docs.rs/bevy/latest/bevy/scene/struct.DynamicEntity.html
|
||||
#[derive(Asset, TypePath, Default)]
|
||||
pub(crate) struct SaveEntity {
|
||||
components: Vec<Box<dyn Reflect>>,
|
||||
}
|
||||
|
||||
impl SaveEntity {
|
||||
fn parse(
|
||||
text: &str,
|
||||
load_context: &mut LoadContext,
|
||||
fns: &Vec<ParseFn>,
|
||||
) -> Result<SaveEntity, SaveEntityParseError> {
|
||||
let lines = text.split('\n');
|
||||
let mut entity = SaveEntity { ..default() };
|
||||
lines
|
||||
.into_iter()
|
||||
.filter(|line| !line.is_empty())
|
||||
.for_each(|line| {
|
||||
// track if this line matched any components
|
||||
let mut good = false;
|
||||
|
||||
// Tokenize the line
|
||||
let tokens = tokenize(line);
|
||||
|
||||
// Run line against all parsers
|
||||
for f in fns {
|
||||
if let Ok(v) = f(&tokens) {
|
||||
entity.components.push(v);
|
||||
good = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !good {
|
||||
error!(
|
||||
file = load_context.path().to_str().unwrap(),
|
||||
line = line,
|
||||
"failed to parse component",
|
||||
);
|
||||
}
|
||||
});
|
||||
Ok(entity)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Debug, Reflect, PartialEq)]
|
||||
#[reflect(Component)]
|
||||
struct GltfScene {
|
||||
gltf: Handle<Gltf>,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct SaveEntityLoader {
|
||||
fns: Vec<ParseFn>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
enum SaveEntityLoaderError {
|
||||
#[error("Could not load asset: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("Could not parse entity: {0}")]
|
||||
Parse(#[from] SaveEntityParseError),
|
||||
}
|
||||
|
||||
impl AssetLoader for SaveEntityLoader {
|
||||
type Asset = SaveEntity;
|
||||
type Settings = ();
|
||||
type Error = SaveEntityLoaderError;
|
||||
|
||||
async fn load<'a>(
|
||||
&'a self,
|
||||
reader: &'a mut Reader<'_>,
|
||||
_settings: &'a (),
|
||||
load_context: &'a mut LoadContext<'_>,
|
||||
) -> Result<Self::Asset, Self::Error> {
|
||||
let mut bytes = Vec::new();
|
||||
reader.read_to_end(&mut bytes).await?;
|
||||
|
||||
let s = std::str::from_utf8(bytes.as_slice()).unwrap();
|
||||
let save_entity = SaveEntity::parse(s, load_context, &self.fns)?;
|
||||
Ok(save_entity)
|
||||
}
|
||||
|
||||
fn extensions(&self) -> &[&str] {
|
||||
&["entity"]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct SaveSceneLoader;
|
||||
|
||||
impl AssetLoader for SaveSceneLoader {
|
||||
type Asset = SaveScene;
|
||||
type Settings = ();
|
||||
type Error = SaveEntityLoaderError;
|
||||
|
||||
async fn load<'a>(
|
||||
&'a self,
|
||||
reader: &'a mut Reader<'_>,
|
||||
_settings: &'a (),
|
||||
load_context: &'a mut LoadContext<'_>,
|
||||
) -> Result<Self::Asset, Self::Error> {
|
||||
let mut bytes = Vec::new();
|
||||
reader.read_to_end(&mut bytes).await?;
|
||||
|
||||
let s = std::str::from_utf8(bytes.as_slice()).unwrap();
|
||||
|
||||
let asset_path = load_context.path().to_path_buf();
|
||||
let parent_dir = asset_path.parent().unwrap();
|
||||
let entities: Vec<Handle<SaveEntity>> = s
|
||||
.lines()
|
||||
.map(|line| parent_dir.join(line))
|
||||
.map(|path| load_context.load(path))
|
||||
.collect();
|
||||
|
||||
Ok(SaveScene { entities })
|
||||
}
|
||||
|
||||
fn extensions(&self) -> &[&str] {
|
||||
&["scene"]
|
||||
}
|
||||
}
|
||||
|
||||
/// Testing system for loading a specific scene
|
||||
fn load_scenes(loader: Res<AssetServer>, mut commands: Commands) {
|
||||
let handle: Handle<SaveScene> = loader.load("scenes/00.scene");
|
||||
info!("Loading scene {:?}", handle);
|
||||
commands.spawn(handle);
|
||||
}
|
||||
|
||||
/// Spawns scenes with the Handle<Scene> marker
|
||||
fn spawn_scenes(
|
||||
query: Query<(Entity, &Handle<SaveScene>)>,
|
||||
mut events: EventReader<AssetEvent<SaveScene>>,
|
||||
save_scenes: Res<Assets<SaveScene>>,
|
||||
server: Res<AssetServer>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
events.read().for_each(|event| {
|
||||
if let AssetEvent::LoadedWithDependencies { id } = event {
|
||||
debug!("Spawning scene {:?}", server.get_id_handle(*id).unwrap());
|
||||
query
|
||||
.iter()
|
||||
.filter(|(_, handle)| handle.id() == *id)
|
||||
.for_each(|(entity, handle)| {
|
||||
let scene = save_scenes.get(handle).unwrap();
|
||||
|
||||
// Get entity with SaveEntity handle
|
||||
let mut e = commands.entity(entity);
|
||||
|
||||
// Clear the entity of descendants
|
||||
debug!("Despawning scene descendants");
|
||||
e.despawn_descendants();
|
||||
|
||||
debug!("Populating children in scene");
|
||||
// Populate with entities
|
||||
e.with_children(|parent| {
|
||||
scene.entities.iter().for_each(|this| {
|
||||
parent.spawn(this.clone());
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Spawns entities with the Handle<SaveEntity> component
|
||||
fn spawn_entities(
|
||||
events: Query<
|
||||
(Entity, &Handle<SaveEntity>),
|
||||
Or<(Added<Handle<SaveEntity>>, Changed<Handle<SaveEntity>>)>,
|
||||
>,
|
||||
save_entities: Res<Assets<SaveEntity>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
events.iter().for_each(|(entity, handle)| {
|
||||
// Get a handle on the
|
||||
let mut e = commands.entity(entity);
|
||||
|
||||
debug!("Despawning entity descendants {:?}", entity);
|
||||
e.despawn_descendants();
|
||||
|
||||
debug!("Clearing components on entity {:?}", entity);
|
||||
// Clear any existing components on the entity;
|
||||
e.retain::<Handle<SaveEntity>>();
|
||||
|
||||
// Get the entity asset containing reflected component
|
||||
let save_entity = save_entities.get(handle).unwrap();
|
||||
|
||||
// Add each component to the entity
|
||||
debug!("Populating entity with components {:?}", entity);
|
||||
save_entity.components.iter().for_each(|component| {
|
||||
e.insert_reflect(component.clone_value());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// When an entity asset is updated, re-spawn it
|
||||
/// NOTE: This should only be enabled in development mode(?)
|
||||
fn sync_entities(
|
||||
query: Query<(Entity, &Handle<SaveEntity>)>,
|
||||
server: Res<AssetServer>,
|
||||
mut events: EventReader<AssetEvent<SaveEntity>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
// Any time a SaveEntity asset is updated
|
||||
events.read().for_each(|event| {
|
||||
match event {
|
||||
AssetEvent::LoadedWithDependencies { id } => {
|
||||
debug!(
|
||||
"SaveEnity loaded {:?}",
|
||||
server.get_id_handle(*id).unwrap().path().unwrap()
|
||||
);
|
||||
// Find any entities with this Handle<SaveEntity>
|
||||
query
|
||||
.iter()
|
||||
.filter(|(_entity, handle)| handle.id() == *id)
|
||||
.for_each(|(entity, handle)| {
|
||||
debug!("Found entity with same ID, updating");
|
||||
|
||||
// Get this entity with SaveEntity handle
|
||||
let mut e = commands.entity(entity);
|
||||
|
||||
// Re-Insert the SaveEntity handle
|
||||
e.remove::<Handle<SaveEntity>>().insert(handle.clone());
|
||||
});
|
||||
}
|
||||
_ => debug!("Skipping SaveEntity event {:?}", event),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn debug_entities(
|
||||
events: Query<Entity, Or<(Changed<Handle<SaveEntity>>, Added<Handle<SaveEntity>>)>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
events.iter().for_each(|e| {
|
||||
commands.entity(e).log_components();
|
||||
});
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub(crate) struct UiPlugin;
|
||||
|
||||
impl Plugin for UiPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(Startup, init_ui);
|
||||
}
|
||||
}
|
||||
|
||||
fn init_ui(
|
||||
mut commands: Commands,
|
||||
) {
|
||||
todo!()
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub(crate) struct WindowPlugin;
|
||||
|
||||
impl Plugin for WindowPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(
|
||||
Update,
|
||||
setup_primary.run_if(any_component_added::<PrimaryWindow>),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
handle_window_close.run_if(on_event::<WindowCloseRequested>()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_primary(mut events: Query<&mut Window, Added<PrimaryWindow>>) {
|
||||
events.iter_mut().for_each(|mut window| {
|
||||
window.title = "Acts of Gods".into();
|
||||
});
|
||||
}
|
||||
|
||||
/// Handles window close requests which are only weird because of the editor
|
||||
/// When the primary window closes shut down the game.
|
||||
fn handle_window_close(
|
||||
mut events: EventReader<WindowCloseRequested>,
|
||||
primary: Query<Entity, With<PrimaryWindow>>,
|
||||
mut exit: EventWriter<AppExit>,
|
||||
) {
|
||||
events
|
||||
.read()
|
||||
// FilterMap this entity to the primary window
|
||||
// Meaning if we get Some(entity) the event was for the primary
|
||||
// If we get None it was for a different window so we skip the for_each
|
||||
.filter_map(|WindowCloseRequested { window }| primary.get(*window).ok())
|
||||
// If this was the primary window, send an AppExit
|
||||
.for_each(|_| {
|
||||
exit.send(AppExit::Success);
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue