use bevy::{prelude::*, window::PrimaryWindow};
use rand::random;
pub const PLAYER_SPEED: f32 = 500.0;
pub const PLAYER_SIZE: f32 = 130.0;
pub const NUMBER_OF_ENEMIES: usize = 3;
pub const ENEMY_SPEED: f32 = 200.0;
pub const ENEMY_SIZE: f32 = 130.0;
pub const NUMBER_OF_STARS:usize = 10;
pub const STAR_SIZE: f32 = 30.0;
pub const STAR_SPAWN_TIME: f32 = 1.0;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_resource::<Score>()
.init_resource::<StarSpawnTimer>()
.add_systems(Startup, spawn_camera)
.add_systems(Startup, spawn_player)
.add_systems(Startup, (spawn_enemies, spawn_stars))
.add_systems(Update, (player_movement, confine_player_movement,
enemy_hit_player,))
.add_systems(Update, (enemy_movement, update_enemy_direction, confine_enemy_movement).chain())
.add_systems(Update, (player_hit_star, update_score, tick_star_spawn_timer, spawn_stars_over_time))
.run();
}
#[derive(Resource)]
pub struct Score{
}
impl Default for Score {
fn default() -> Self {
}
}
#[derive(Resource)]
pub struct StarSpawnTimer{
pub timer: Timer,
}
impl Default for StarSpawnTimer {
fn default() -> Self {
StarSpawnTimer{
timer: Timer::from_seconds(STAR_SPAWN_TIME, TimerMode::Repeating),
}
}
}
#[derive(Component)]
pub struct Player{}
#[derive(Component)]
pub struct Enemy{
pub direction: Vec2,
}
#[derive(Component)]
pub struct Star{}
pub fn spawn_player(
mut commands: Commands,
window_query: Query<&Window, With<PrimaryWindow>>,
asset_server: Res<AssetServer>,
){
let window = window_query.get_single().unwrap();
commands.spawn(
(
Sprite{
image: asset_server.load("sprites/ball_blue_large.png"),
..default()
},
Transform::from_xyz(window.width()/2.0, window.height()/2.0, 0.0),
Player {},
));
}
pub fn spawn_camera(mut commands: Commands, window_query: Query<&Window, With<PrimaryWindow>>) {
let window = window_query.get_single().unwrap();
commands.spawn(
(
Camera2d::default(),
Camera{
},
Transform::from_xyz(window.width()/2.0, window.height()/2.0, 0.0),
)
);
}
pub fn spawn_enemies(
mut commands: Commands,
window_query: Query<&Window, With<PrimaryWindow>>,
asset_server: Res<AssetServer>,
){
let window = window_query.get_single().unwrap();
for _ in 0..NUMBER_OF_ENEMIES{
let random_x = random::<f32>() * (window.width() - ENEMY_SIZE);
let random_y = random::<f32>() * (window.height() - ENEMY_SIZE);
commands.spawn(
(
Sprite{
image: asset_server.load("sprites/ball_red_large.png"),
..default()
},
Transform::from_xyz(random_x, random_y, 0.0),
Enemy {
direction: Vec2::new(random::<f32>()-0.5, random::<f32>()-0.5)
.normalize(),
},
));
}
}
pub fn spawn_stars(
mut commands: Commands,
window_query: Query<&Window, With<PrimaryWindow>>,
asset_server: Res<AssetServer>
){
let window = window_query.get_single().unwrap();
for _ in 0..NUMBER_OF_STARS{
let random_x = random::<f32>() * (window.width() - STAR_SIZE);
let random_y = random::<f32>() * (window.height() - STAR_SIZE);
commands.spawn(
(
Sprite{
image: asset_server.load("sprites/star.png"),
..default()
},
Transform::from_xyz(random_x, random_y, 0.0),
Star{},
));
}
}
pub fn player_movement(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut player_query: Query<&mut Transform, With<Player>>,
time: Res<Time>,
){
if let Ok(mut transform) = player_query.get_single_mut(){
let mut direction = Vec3::ZERO;
if keyboard_input.pressed(KeyCode::ArrowLeft)||keyboard_input.pressed(KeyCode::KeyA){
direction += Vec3::new(-1.0, 0.0, 0.0);
}
if keyboard_input.pressed(KeyCode::ArrowRight)||keyboard_input.pressed(KeyCode::KeyD){
direction += Vec3::new(1.0, 0.0, 0.0);
}
if keyboard_input.pressed(KeyCode::ArrowUp) || keyboard_input.pressed(KeyCode::KeyW) {
direction += Vec3::new(0.0, 1.0, 0.0);
}
if keyboard_input.pressed(KeyCode::ArrowDown) || keyboard_input.pressed(KeyCode::KeyS) {
direction += Vec3::new(0.0, -1.0, 0.0);
}
if direction.length() > 0.0{
direction = direction.normalize();
}
transform.translation += direction * PLAYER_SPEED * time.delta_secs();
}
}
pub fn confine_player_movement(
mut player_query: Query<&mut Transform, With<Player>>,
window_query: Query<&Window, With<PrimaryWindow>>
){
if let Ok(mut player_transform) = player_query.get_single_mut(){
let window = window_query.get_single().unwrap();
let half_player_size = PLAYER_SIZE / 2.0;
let x_min = 0.0 + half_player_size;
let x_max = window.width()/1.0 - half_player_size;
let y_min = 0.0 + half_player_size;
let y_max = window.height()/1.0 -half_player_size;
let mut translation = player_transform.translation;
translation.x = translation.x.clamp(x_min, x_max);
translation.y = translation.y.clamp(y_min, y_max);
player_transform.translation = translation;
}
}
pub fn enemy_movement(
mut enemy_query: Query<(&mut Transform, &Enemy)>, time: Res<Time>){
for(mut transform, enemy)in enemy_query.iter_mut(){
let direction = Vec3::new(enemy.direction.x, enemy.direction.y, 0.0);
transform.translation += direction * ENEMY_SPEED * time.delta_secs();
}
}
pub fn update_enemy_direction(
mut enemy_query: Query<(&Transform, &mut Enemy)>,
window_query: Query<&Window, With<PrimaryWindow>>,
mut commands: Commands,
asset_server: Res<AssetServer>,
){
let window = window_query.get_single().unwrap();
let half_enemy_size = ENEMY_SIZE / 2.0;
let x_min = 0.0 + half_enemy_size;
let x_max = window.width() - half_enemy_size;
let y_min = 0.0 + half_enemy_size;
let y_max = window.height() - half_enemy_size;
for (transform, mut enemy) in enemy_query.iter_mut() {
let mut direction_changed = false;
let translation = transform.translation;
if translation.x < x_min || translation.x > x_max {
enemy.direction.x *= -1.0;
direction_changed = true;
}
if translation.y < y_min || translation.y > y_max {
enemy.direction.y *= -1.0;
direction_changed = true;
}
if direction_changed{
let sound_effect_1 = "audio/pluck_001.ogg";
let sound_effect_2 = "audio/pluck_002.ogg";
let sound_effect:bool = random::<bool>();
if sound_effect{
commands.spawn(AudioPlayer::new(
asset_server.load(sound_effect_1),
));
}else {
commands.spawn(AudioPlayer::new(
asset_server.load(sound_effect_2),
));
};
}
}
}
pub fn confine_enemy_movement(
mut enemy_query: Query<&mut Transform, With<Enemy>>,
window_query: Query<&Window, With<PrimaryWindow>>
){
let window = window_query.get_single().unwrap();
let half_enemy_size = PLAYER_SIZE / 2.0;
let x_min = 0.0 + half_enemy_size;
let x_max = window.width()/1.0 - half_enemy_size;
let y_min = 0.0 + half_enemy_size;
let y_max = window.height()/1.0 - half_enemy_size;
for mut transform in enemy_query.iter_mut(){
let mut translation = transform.translation;
translation.x = translation.x.clamp(x_min, x_max);
translation.y = translation.y.clamp(y_min, y_max);
transform.translation = translation;
}
}
pub fn enemy_hit_player(
mut commands: Commands,
mut player_query: Query<(Entity, &Transform), With<Player>>,
enemy_query: Query<&Transform, With<Enemy>>,
asset_server: Res<AssetServer>,
){
if let Ok*1 = player_query.get_single_mut(){
for enemy_transform in enemy_query.iter(){
let distance = player_transform
.translation
.distance(enemy_transform.translation);
let player_radius = PLAYER_SIZE /2.0;
let enemy_radius = ENEMY_SIZE /2.0;
println!("Enemy hit player! Game Over!!");
let sound_effect = "audio/explosionCrunch_000.ogg";
commands.spawn(AudioPlayer::new(
asset_server.load(sound_effect),
));
commands.entity(player_entity).despawn();
}
}
}
}
pub fn player_hit_star(
mut commands: Commands,
player_query: Query<&Transform, With<Player>>,
star_query: Query<(Entity, &Transform), With<Star>>,
asset_server: Res<AssetServer>,
mut score: ResMut<Score>,
){
if let Ok(player_transform) = player_query.get_single(){
for (star_entity, star_transform) in star_query.iter(){
let distance = player_transform
.translation
.distance(star_transform.translation);
if distance < PLAYER_SIZE / 2.0 + STAR_SIZE / 2.0{
println!("Player hit star!");
let sound_effect = "audio/laserLarge_000.ogg";
commands.spawn(AudioPlayer::new(
asset_server.load(sound_effect),
));
commands.entity(star_entity).despawn();
}
}
}
}
pub fn update_score(score: Res<Score>){
if score.is_changed(){
println!("score: {}", score.value.to_string())
}
}
pub fn tick_star_spawn_timer(mut star_spawn_timer: ResMut<StarSpawnTimer>, time: Res<Time>) {
star_spawn_timer.timer.tick(time.delta());
}
pub fn spawn_stars_over_time(
mut commands: Commands,
window_query: Query<&Window, With<PrimaryWindow>>,
asset_server: Res<AssetServer>,
star_spawn_timer: ResMut<StarSpawnTimer>
){
if star_spawn_timer.timer.finished(){
let window = window_query.get_single().unwrap();
let random_x = random::<f32>() * (window.width() - STAR_SIZE);
let random_y = random::<f32>() * (window.height() - STAR_SIZE);
commands.spawn(
(
Sprite{
image: asset_server.load("sprites/star.png"),
..default()
},
Transform::from_xyz(random_x, random_y, 0.0),
Star{},
));
}
}
今回の新しい要素は、init_resource::<>(),と、Resourceの宣言、スターのランダムな配置(既出)それに、時間経過による新しいスターの生成関数。リソースの表示システム。スターとプレイヤーとの衝突システム(既出)この4つになります。
すると、 commands.init_resource::<MyFancyResource>();のように、入力すると、なんと初期値をわざわざ打ち込まなくてもリソースを宣言できます。
tick_star_spawn_timerシステムでは、引数でtime.delta()という1フレームの微小時間を使うために、Res<Time>を用いています。star_spawn_timer: ResMut<StarSpawnTimer> 可変参照で、StarSpawnTimerというリソースを呼び出しています。
それは今どうなっているかというと、
その次の、spawn_stars_overtimeシステムは基本的に、spawn_starsシステムと同じなのだが、spawn_starsを繰り返しではなく、毎フレーム呼び出す条件にしている。
star hit playerシステムでは、enemy hit playerと一緒で、まず、プレイヤーとスターの座標を取得し、starとplayer間の空間距離を計算し、それがstarとplayerの大きさの和の1/2未満になったら、starエンティティを消すという処理をしているんだけど、その際に、Score Resourceを1足す処理をしている。
タイマーによるスターの追加機能は、最初のstar_spawn_timer.timerのリソース、tick_star_spawn_timerシステム、spawn_star_over_timeシステムの3つの組み合わせにより実装した。