initial commit
This commit is contained in:
commit
ff9d8ffab6
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/target
|
||||
/dist
|
||||
/.idea
|
||||
Cargo.lock
|
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "dreamsincode"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bevy = { version = "0.8.1" }
|
||||
web-sys = { version = "0.3.60" }
|
||||
gloo-events = { version = "0.1.2" }
|
||||
console_error_panic_hook = { version = "0.1.7" }
|
||||
|
||||
[profile.release]
|
||||
opt-level = "z"
|
35
Trunk.release.toml
Normal file
35
Trunk.release.toml
Normal file
@ -0,0 +1,35 @@
|
||||
# An example Trunk.toml with all possible fields along with their defaults.
|
||||
|
||||
[build]
|
||||
# The index HTML file to drive the bundling process.
|
||||
target = "index.html"
|
||||
# Build in release mode.
|
||||
release = true
|
||||
# The output dir for all final assets.
|
||||
dist = "dist/release"
|
||||
# The public URL from which assets are to be served.
|
||||
public_url = "/"
|
||||
# Whether to include hash values in the output file names.
|
||||
filehash = true
|
||||
|
||||
[watch]
|
||||
# Paths to watch. The `build.target`'s parent folder is watched by default.
|
||||
watch = []
|
||||
# Paths to ignore.
|
||||
ignore = ["."]
|
||||
|
||||
[serve]
|
||||
# The address to serve on.
|
||||
addr = "127.0.0.1"
|
||||
# The port to serve on.
|
||||
port = 8080
|
||||
# Open a browser tab once the initial build is complete.
|
||||
open = true
|
||||
# Disable auto-reload of the web app.
|
||||
no_autoreload = true
|
||||
|
||||
[clean]
|
||||
# The output dir for all final assets.
|
||||
dist = "dist"
|
||||
# Optionally perform a cargo clean.
|
||||
cargo = true
|
35
Trunk.toml
Normal file
35
Trunk.toml
Normal file
@ -0,0 +1,35 @@
|
||||
# An example Trunk.toml with all possible fields along with their defaults.
|
||||
|
||||
[build]
|
||||
# The index HTML file to drive the bundling process.
|
||||
target = "index.html"
|
||||
# Build in release mode.
|
||||
release = false
|
||||
# The output dir for all final assets.
|
||||
dist = "dist/debug"
|
||||
# The public URL from which assets are to be served.
|
||||
public_url = "/"
|
||||
# Whether to include hash values in the output file names.
|
||||
filehash = true
|
||||
|
||||
[watch]
|
||||
# Paths to watch. The `build.target`'s parent folder is watched by default.
|
||||
watch = []
|
||||
# Paths to ignore.
|
||||
ignore = []
|
||||
|
||||
[serve]
|
||||
# The address to serve on.
|
||||
addr = "127.0.0.1"
|
||||
# The port to serve on.
|
||||
port = 8080
|
||||
# Open a browser tab once the initial build is complete.
|
||||
open = true
|
||||
# Disable auto-reload of the web app.
|
||||
no_autoreload = false
|
||||
|
||||
[clean]
|
||||
# The output dir for all final assets.
|
||||
dist = "debug"
|
||||
# Optionally perform a cargo clean.
|
||||
cargo = false
|
11
assets/shaders/custom_material.frag
Normal file
11
assets/shaders/custom_material.frag
Normal file
@ -0,0 +1,11 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) out vec4 o_Target;
|
||||
|
||||
layout(set = 1, binding = 0) uniform CustomMaterial {
|
||||
vec4 Color;
|
||||
};
|
||||
|
||||
void main() {
|
||||
o_Target = Color;
|
||||
}
|
27
assets/shaders/custom_material.vert
Normal file
27
assets/shaders/custom_material.vert
Normal file
@ -0,0 +1,27 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec3 Vertex_Position;
|
||||
layout(location = 1) in vec3 Vertex_Normal;
|
||||
layout(location = 2) in vec2 Vertex_Uv;
|
||||
|
||||
layout(set = 0, binding = 0) uniform CameraViewProj {
|
||||
mat4 ViewProj;
|
||||
mat4 View;
|
||||
mat4 InverseView;
|
||||
mat4 Projection;
|
||||
vec3 WorldPosition;
|
||||
float near;
|
||||
float far;
|
||||
float width;
|
||||
float height;
|
||||
};
|
||||
|
||||
layout(set = 2, binding = 0) uniform Mesh {
|
||||
mat4 Model;
|
||||
mat4 InverseTransposeModel;
|
||||
uint flags;
|
||||
};
|
||||
|
||||
void main() {
|
||||
gl_Position = ViewProj * Model * vec4(Vertex_Position, 1.0);
|
||||
}
|
BIN
assets/sounds/bg.ogg
Normal file
BIN
assets/sounds/bg.ogg
Normal file
Binary file not shown.
16
index.html
Normal file
16
index.html
Normal file
@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"/>
|
||||
<title>dreamsinco.de</title>
|
||||
<meta name="description" content="dreamsinco.de">
|
||||
<meta name="author" content="vance">
|
||||
<link data-trunk rel="copy-dir" href="assets"/>
|
||||
<link data-trunk rel="inline" href="web/index.css" />
|
||||
<link data-trunk rel="rust" data-wasm-opt="z" />
|
||||
</head>
|
||||
<body>
|
||||
<link data-trunk rel="inline" href="web/sound.js" />
|
||||
</body>
|
||||
</html>
|
64
src/fullviewport.rs
Normal file
64
src/fullviewport.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use bevy::{
|
||||
prelude::{App, Plugin, Res, ResMut},
|
||||
window::Windows,
|
||||
};
|
||||
use std::sync::{
|
||||
mpsc::{Receiver, Sender},
|
||||
Mutex,
|
||||
};
|
||||
|
||||
type OnResizeSender = Sender<()>;
|
||||
type OnResizeReceiver = Receiver<()>;
|
||||
|
||||
pub struct FullViewportPlugin;
|
||||
|
||||
impl Plugin for FullViewportPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
let channel = std::sync::mpsc::channel();
|
||||
let resize_sender: OnResizeSender = channel.0;
|
||||
let resize_receiver: OnResizeReceiver = channel.1;
|
||||
|
||||
app.insert_resource(Mutex::new(resize_sender))
|
||||
.insert_resource(Mutex::new(resize_receiver))
|
||||
.add_startup_system(setup_viewport_resize_system)
|
||||
.add_system(viewport_resize_system);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_viewport_size() -> (f32, f32) {
|
||||
let web_window = web_sys::window().expect("could not get window");
|
||||
let document_element = web_window
|
||||
.document()
|
||||
.expect("could not get document")
|
||||
.document_element()
|
||||
.expect("could not get document element");
|
||||
|
||||
let width = document_element.client_width();
|
||||
let height = document_element.client_height();
|
||||
|
||||
(width as f32, height as f32)
|
||||
}
|
||||
|
||||
fn setup_viewport_resize_system(resize_sender: Res<Mutex<OnResizeSender>>) {
|
||||
let web_window = web_sys::window().expect("could not get window");
|
||||
let local_sender = resize_sender.lock().unwrap().clone();
|
||||
|
||||
local_sender.send(()).unwrap();
|
||||
|
||||
gloo_events::EventListener::new(&web_window, "resize", move |_event| {
|
||||
local_sender.send(()).unwrap();
|
||||
})
|
||||
.forget();
|
||||
}
|
||||
|
||||
fn viewport_resize_system(
|
||||
mut windows: ResMut<Windows>,
|
||||
resize_receiver: Res<Mutex<OnResizeReceiver>>,
|
||||
) {
|
||||
if resize_receiver.lock().unwrap().try_recv().is_ok() {
|
||||
if let Some(window) = windows.get_primary_mut() {
|
||||
let size = get_viewport_size();
|
||||
window.set_resolution(size.0, size.1);
|
||||
}
|
||||
}
|
||||
}
|
104
src/main.rs
Normal file
104
src/main.rs
Normal file
@ -0,0 +1,104 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
mod fullviewport;
|
||||
#[cfg(target_family = "wasm")]
|
||||
use fullviewport::FullViewportPlugin;
|
||||
|
||||
fn main() {
|
||||
#[cfg(all(target_family = "wasm", debug_assertions))]
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
let mut app = App::new();
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
app.add_plugin(FullViewportPlugin);
|
||||
|
||||
app.add_plugins(DefaultPlugins)
|
||||
.add_startup_system(setup)
|
||||
.add_startup_system(start_background_audio)
|
||||
.add_system(rotate)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct Rotatable {
|
||||
speed: f32,
|
||||
}
|
||||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
commands.insert_resource(ClearColor(Color::BLACK));
|
||||
commands.insert_resource(AmbientLight {
|
||||
color: Color::VIOLET,
|
||||
brightness: 0.05,
|
||||
});
|
||||
|
||||
commands.spawn_bundle(Camera3dBundle {
|
||||
transform: Transform::from_xyz(0.0, 25.0, 50.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
commands.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Plane { size: 1000.0 })),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::BLACK,
|
||||
//metallic: 1.0,
|
||||
reflectance: 1.0,
|
||||
//perceptual_roughness: 0.0,
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
commands
|
||||
.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Cube { size: 4.0 })),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::BLACK,
|
||||
metallic: 1.0,
|
||||
//reflectance: 1.0,
|
||||
//perceptual_roughness: 0.0,
|
||||
double_sided: true,
|
||||
..Default::default()
|
||||
}),
|
||||
transform: Transform::from_translation(Vec3::new(0.0, 8.0, 0.0)),
|
||||
..Default::default()
|
||||
})
|
||||
.insert(Rotatable { speed: 0.1 });
|
||||
|
||||
|
||||
commands.spawn_bundle(DirectionalLightBundle {
|
||||
directional_light: DirectionalLight {
|
||||
color: Color::PINK,
|
||||
illuminance: 100000.0,
|
||||
shadows_enabled: true,
|
||||
..Default::default()
|
||||
},
|
||||
transform: Transform {
|
||||
translation: Vec3::new(0.0, 16.0, 0.0),
|
||||
rotation: Quat::from_rotation_x(-std::f32::consts::PI),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
fn start_background_audio(asset_server: Res<AssetServer>, audio: Res<Audio>) {
|
||||
audio.play_with_settings(asset_server.load("sounds/bg.ogg"), PlaybackSettings::LOOP);
|
||||
}
|
||||
|
||||
fn rotate(mut rotatables: Query<(&mut Transform, &Rotatable)>, timer: Res<Time>) {
|
||||
for (mut transform, rotatable) in rotatables.iter_mut() {
|
||||
let rotation = 2.0 * std::f32::consts::PI * timer.delta_seconds();
|
||||
let rotation_change = Quat::from_scaled_axis(Vec3::new(
|
||||
rotation * rotatable.speed,
|
||||
rotation * rotatable.speed * -2.,
|
||||
rotation * rotatable.speed,
|
||||
));
|
||||
transform.rotate(rotation_change);
|
||||
}
|
||||
}
|
8
web/index.css
Normal file
8
web/index.css
Normal file
@ -0,0 +1,8 @@
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
canvas {
|
||||
touch-action: none;
|
||||
}
|
62
web/sound.js
Normal file
62
web/sound.js
Normal file
@ -0,0 +1,62 @@
|
||||
// Insert hack to make sound autoplay on Chrome as soon as the user interacts with the tab:
|
||||
// https://developers.google.com/web/updates/2018/11/web-audio-autoplay#moving-forward
|
||||
|
||||
// the following function keeps track of all AudioContexts and resumes them on the first user
|
||||
// interaction with the page. If the function is called and all contexts are already running,
|
||||
// it will remove itself from all event listeners.
|
||||
(function () {
|
||||
// An array of all contexts to resume on the page
|
||||
const audioContextList = [];
|
||||
|
||||
// An array of various user interaction events we should listen for
|
||||
const userInputEventNames = [
|
||||
"click",
|
||||
"contextmenu",
|
||||
"auxclick",
|
||||
"dblclick",
|
||||
"mousedown",
|
||||
"mouseup",
|
||||
"pointerup",
|
||||
"touchend",
|
||||
"keydown",
|
||||
"keyup",
|
||||
];
|
||||
|
||||
// A proxy object to intercept AudioContexts and
|
||||
// add them to the array for tracking and resuming later
|
||||
self.AudioContext = new Proxy(self.AudioContext, {
|
||||
construct(target, args) {
|
||||
const result = new target(...args);
|
||||
audioContextList.push(result);
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
// To resume all AudioContexts being tracked
|
||||
function resumeAllContexts(_event) {
|
||||
let count = 0;
|
||||
|
||||
audioContextList.forEach((context) => {
|
||||
if (context.state !== "running") {
|
||||
context.resume();
|
||||
} else {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
|
||||
// If all the AudioContexts have now resumed then we unbind all
|
||||
// the event listeners from the page to prevent unnecessary resume attempts
|
||||
// Checking count > 0 ensures that the user interaction happens AFTER the game started up
|
||||
if (count > 0 && count === audioContextList.length) {
|
||||
userInputEventNames.forEach((eventName) => {
|
||||
document.removeEventListener(eventName, resumeAllContexts);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// We bind the resume function for each user interaction
|
||||
// event on the page
|
||||
userInputEventNames.forEach((eventName) => {
|
||||
document.addEventListener(eventName, resumeAllContexts);
|
||||
});
|
||||
})();
|
Loading…
Reference in New Issue
Block a user