translate to GDScript
parent
83b1935e20
commit
6d225dcb07
@ -1,67 +0,0 @@
|
||||
extends Node3D
|
||||
|
||||
@export var asteroid_collision_shape: CollisionShape3D
|
||||
@export var asteroid_mesh: MeshInstance3D
|
||||
@export var asteroid_rb: RigidBody3D
|
||||
@export var explosion: PackedScene
|
||||
@export var particle_collision: GpuParticlesCollisionSphere3D
|
||||
|
||||
var rotation: Vector3
|
||||
var rot_speed: float = 100.0
|
||||
var size: float
|
||||
var speed: float
|
||||
|
||||
func _enter_tree():
|
||||
position = Vector3(rand_range(-4.0, 4.0), 0, -6.4)
|
||||
|
||||
size = rand_range(0.25, 0.75)
|
||||
var scale = Vector3(size, size, size)
|
||||
asteroid_mesh.scale = scale
|
||||
asteroid_rb.scale = scale
|
||||
asteroid_collision_shape.scale = scale
|
||||
|
||||
speed = randf() * GameManager.instance.level * 0.1 + 1.0
|
||||
rotation = Vector3(rand_range(-rot_speed, rot_speed), rand_range(-rot_speed, rot_speed), rand_range(-rot_speed, rot_speed))
|
||||
|
||||
func _ready():
|
||||
pass
|
||||
|
||||
func _physics_process(delta):
|
||||
asteroid_rb.rotate(rotation.normalized(), delta)
|
||||
|
||||
if GameManager.instance.game_over:
|
||||
return
|
||||
|
||||
var collision = asteroid_rb.move_and_collide(Vector3(0, 0, speed * delta))
|
||||
if collision and collision.collider is Node3D:
|
||||
var collider = collision.collider as Node3D
|
||||
var parent = collider.get_parent()
|
||||
var collision_position = collision.position
|
||||
match parent:
|
||||
# Asteroid:
|
||||
# # collided_asteroid.explode(collision_position)
|
||||
# # explode(collision_position)
|
||||
# break
|
||||
PlayerShip:
|
||||
PlayerShip.instance.explode(collision_position)
|
||||
explode(collision_position)
|
||||
GameArea:
|
||||
explode(Vector3.ZERO)
|
||||
LaserBeam:
|
||||
GameManager.instance.points += GameManager.instance.level
|
||||
parent.queue_free()
|
||||
explode(collision_position)
|
||||
_:
|
||||
print("Asteroid collided with unknown.")
|
||||
|
||||
func explode(ex_position: Vector3):
|
||||
if ex_position != Vector3.ZERO:
|
||||
SoundManager.instance.play(SoundManager.Sound.AsteroidExplosion, ex_position)
|
||||
if explosion and explosion.instance() is GpuParticles3D:
|
||||
var ex = explosion.instance() as GpuParticles3D
|
||||
get_parent().add_child(ex)
|
||||
ex.position = ex_position
|
||||
ex.emitting = true
|
||||
|
||||
GameManager.instance.asteroid_number -= 1
|
||||
queue_free()
|
||||
@ -0,0 +1,67 @@
|
||||
extends Node3D
|
||||
|
||||
@export var asteroid_collision_shape: CollisionShape3D
|
||||
@export var asteroid_mesh: MeshInstance3D
|
||||
@export var asteroid_rb: RigidBody3D
|
||||
@export var explosion: PackedScene
|
||||
@export var particle_collision: GpuParticlesCollisionSphere3D
|
||||
|
||||
var rotation: Vector3
|
||||
var rot_speed: float = 100.0
|
||||
var size: float
|
||||
var speed: float
|
||||
|
||||
func _enter_tree():
|
||||
position = Vector3(rand_range(-4.0, 4.0), 0, -6.4)
|
||||
|
||||
size = rand_range(0.25, 0.75)
|
||||
var scale = Vector3(size, size, size)
|
||||
asteroid_mesh.scale = scale
|
||||
asteroid_rb.scale = scale
|
||||
asteroid_collision_shape.scale = scale
|
||||
|
||||
speed = randf() * GameManager.instance.level * 0.1 + 1.0
|
||||
rotation = Vector3(rand_range(-rot_speed, rot_speed), rand_range(-rot_speed, rot_speed), rand_range(-rot_speed, rot_speed))
|
||||
|
||||
func _ready():
|
||||
pass
|
||||
|
||||
func _physics_process(delta):
|
||||
asteroid_rb.rotate(rotation.normalized(), delta)
|
||||
|
||||
if GameManager.instance.game_over:
|
||||
return
|
||||
|
||||
var collision = asteroid_rb.move_and_collide(Vector3(0, 0, speed * delta))
|
||||
if collision and collision.collider is Node3D:
|
||||
var collider = collision.collider as Node3D
|
||||
var parent = collider.get_parent()
|
||||
var collision_position = collision.position
|
||||
match parent:
|
||||
# Asteroid:
|
||||
# # collided_asteroid.explode(collision_position)
|
||||
# # explode(collision_position)
|
||||
# break
|
||||
PlayerShip:
|
||||
PlayerShip.instance.explode(collision_position)
|
||||
explode(collision_position)
|
||||
GameArea:
|
||||
explode(Vector3.ZERO)
|
||||
LaserBeam:
|
||||
GameManager.instance.points += GameManager.instance.level
|
||||
parent.queue_free()
|
||||
explode(collision_position)
|
||||
_:
|
||||
print("Asteroid collided with unknown.")
|
||||
|
||||
func explode(ex_position: Vector3):
|
||||
if ex_position != Vector3.ZERO:
|
||||
SoundManager.instance.play(SoundManager.Sound.AsteroidExplosion, ex_position)
|
||||
if explosion and explosion.instance() is GpuParticles3D:
|
||||
var ex = explosion.instance() as GpuParticles3D
|
||||
get_parent().add_child(ex)
|
||||
ex.position = ex_position
|
||||
ex.emitting = true
|
||||
|
||||
GameManager.instance.asteroid_number -= 1
|
||||
queue_free()
|
||||
@ -1,13 +0,0 @@
|
||||
using Godot;
|
||||
|
||||
namespace GodotspaceShooter.Scripts;
|
||||
|
||||
public partial class Background : Sprite3D {
|
||||
private const float moveVelocity = -250f;
|
||||
|
||||
public override void _Process(double delta) {
|
||||
Rect2 region = RegionRect;
|
||||
region.Position += new Vector2(0, moveVelocity * (float)delta);
|
||||
RegionRect = region;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
extends Sprite3D
|
||||
|
||||
const MOVE_VELOCITY = -250.0
|
||||
|
||||
func _process(delta):
|
||||
var region = region_rect
|
||||
region.position += Vector2(0, MOVE_VELOCITY * delta)
|
||||
region_rect = region
|
||||
@ -1,9 +0,0 @@
|
||||
using Godot;
|
||||
|
||||
namespace GodotspaceShooter.Scripts;
|
||||
|
||||
public partial class Explosion : GpuParticles3D {
|
||||
public override void _Process(double delta) {
|
||||
if (!Emitting) QueueFree();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
extends GPUParticles3D
|
||||
|
||||
func _process(delta):
|
||||
if not emitting:
|
||||
queue_free()
|
||||
@ -1,5 +0,0 @@
|
||||
using Godot;
|
||||
|
||||
namespace GodotspaceShooter.Scripts;
|
||||
|
||||
public partial class GameArea : Node3D { }
|
||||
@ -0,0 +1,3 @@
|
||||
extends Node3D
|
||||
|
||||
class_name GameArea
|
||||
@ -1,146 +0,0 @@
|
||||
using System;
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
|
||||
namespace GodotspaceShooter.Scripts;
|
||||
|
||||
public partial class GameManager : Node {
|
||||
private int asteroidMaxNumber = 3;
|
||||
private int asteroidNumber;
|
||||
|
||||
private bool gameOver;
|
||||
public bool GameOver {
|
||||
get => gameOver;
|
||||
private set {
|
||||
gameOver = value;
|
||||
SoundManager.Instance.Stop(SoundManager.Sound.BackgroundMusic);
|
||||
SoundManager.Instance.Play(SoundManager.Sound.GameOver);
|
||||
OnGameOver?.Invoke(this,EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
[Export] private Array<PackedScene> asteroids;
|
||||
[Export] private Node3D asteroidsContainer;
|
||||
|
||||
private int level = 1;
|
||||
public int MaxLives { get; private set; } = 10;
|
||||
private int lives = 10;
|
||||
private int nextLevelPoints = 11;
|
||||
private int points;
|
||||
private float spawnRate = 1f;
|
||||
private float spawnTimer;
|
||||
|
||||
public event EventHandler OnRefreshStats;
|
||||
public event EventHandler OnNewCountdown;
|
||||
public event EventHandler OnGameOver;
|
||||
|
||||
public int AsteroidNumber {
|
||||
get => asteroidNumber;
|
||||
set {
|
||||
asteroidNumber = value;
|
||||
OnRefreshStats?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public int Lives {
|
||||
get => lives;
|
||||
set {
|
||||
lives = value;
|
||||
if (lives <= 0) GameOver = true;
|
||||
OnRefreshStats?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public int Points {
|
||||
get => points;
|
||||
set {
|
||||
points = value;
|
||||
if (points >= nextLevelPoints) NextLevel();
|
||||
OnRefreshStats?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public int Level {
|
||||
get => level;
|
||||
private set {
|
||||
level = value;
|
||||
OnRefreshStats?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public int LastLevelPoints { get; private set; }
|
||||
|
||||
public int NextLevelPoints {
|
||||
get => nextLevelPoints;
|
||||
private set {
|
||||
nextLevelPoints = value;
|
||||
OnRefreshStats?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private int AsteroidMaxNumber {
|
||||
get => asteroidMaxNumber;
|
||||
set {
|
||||
asteroidMaxNumber = value;
|
||||
OnRefreshStats?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private bool gamePaused;
|
||||
public bool GamePaused{
|
||||
get => gamePaused;
|
||||
set {
|
||||
gamePaused = value;
|
||||
GetTree().Paused = gamePaused;
|
||||
OnRefreshStats?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private bool countdown;
|
||||
public bool Countdown {
|
||||
get => countdown;
|
||||
set {
|
||||
countdown = value;
|
||||
if (countdown) OnNewCountdown?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public static GameManager Instance { get; private set; }
|
||||
[Export] public bool Debug { get; private set; } = true;
|
||||
|
||||
public override void _Ready() {
|
||||
Instance = this;
|
||||
Lives = MaxLives;
|
||||
spawnTimer = spawnRate;
|
||||
Countdown = true;
|
||||
SoundManager.Instance.Play(SoundManager.Sound.BackgroundMusic);
|
||||
}
|
||||
|
||||
public override void _Process(double delta) {
|
||||
if (!GamePaused && !Countdown) {
|
||||
spawnTimer -= (float)delta;
|
||||
if (spawnTimer < 0) {
|
||||
if (AsteroidNumber < asteroidMaxNumber) SpawnNewAsteroid();
|
||||
spawnTimer = spawnRate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnNewAsteroid() {
|
||||
if (GameOver) return;
|
||||
GD.Print("Spawn new asteroid");
|
||||
AsteroidNumber++;
|
||||
Node asteroid = asteroids.PickRandom().Instantiate();
|
||||
asteroidsContainer.AddChild(asteroid);
|
||||
asteroid.Name = $"Asteroid{AsteroidNumber}";
|
||||
}
|
||||
|
||||
private void NextLevel() {
|
||||
Ui.ShowMessage("Next Level!");
|
||||
Level++;
|
||||
LastLevelPoints = NextLevelPoints;
|
||||
NextLevelPoints = Level * 33;
|
||||
AsteroidMaxNumber = 3 + Level;
|
||||
spawnRate = 1f - Level * 0.1f;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
extends Node
|
||||
|
||||
class_name GameManager
|
||||
|
||||
var asteroid_max_number = 3
|
||||
var asteroid_number = 0
|
||||
|
||||
var game_over = false setget set_game_over
|
||||
var level = 1
|
||||
var max_lives = 10
|
||||
var lives = 10 setget set_lives
|
||||
var next_level_points = 11
|
||||
var points = 0 setget set_points
|
||||
var spawn_rate = 1.0
|
||||
var spawn_timer = 0.0
|
||||
|
||||
signal refresh_stats
|
||||
signal new_countdown
|
||||
signal game_over_signal
|
||||
|
||||
var asteroids = []
|
||||
var asteroids_container = null
|
||||
|
||||
var last_level_points = 0
|
||||
var game_paused = false setget set_game_paused
|
||||
var countdown = false setget set_countdown
|
||||
|
||||
static var instance = null
|
||||
export var debug = true
|
||||
|
||||
func _ready():
|
||||
instance = self
|
||||
lives = max_lives
|
||||
spawn_timer = spawn_rate
|
||||
countdown = true
|
||||
SoundManager.instance.play(SoundManager.Sound.BACKGROUND_MUSIC)
|
||||
|
||||
func _process(delta):
|
||||
if not game_paused and not countdown:
|
||||
spawn_timer -= delta
|
||||
if spawn_timer < 0:
|
||||
if asteroid_number < asteroid_max_number:
|
||||
spawn_new_asteroid()
|
||||
spawn_timer = spawn_rate
|
||||
|
||||
func spawn_new_asteroid():
|
||||
if game_over:
|
||||
return
|
||||
print("Spawn new asteroid")
|
||||
asteroid_number += 1
|
||||
var asteroid = asteroids[randi() % asteroids.size()].instance()
|
||||
asteroids_container.add_child(asteroid)
|
||||
asteroid.name = "Asteroid%d" % asteroid_number
|
||||
|
||||
func next_level():
|
||||
Ui.show_message("Next Level!")
|
||||
level += 1
|
||||
last_level_points = next_level_points
|
||||
next_level_points = level * 33
|
||||
asteroid_max_number = 3 + level
|
||||
spawn_rate = 1.0 - level * 0.1
|
||||
|
||||
func set_game_over(value):
|
||||
game_over = value
|
||||
SoundManager.instance.stop(SoundManager.Sound.BACKGROUND_MUSIC)
|
||||
SoundManager.instance.play(SoundManager.Sound.GAME_OVER)
|
||||
emit_signal("game_over_signal")
|
||||
|
||||
func set_lives(value):
|
||||
lives = value
|
||||
if lives <= 0:
|
||||
game_over = true
|
||||
emit_signal("refresh_stats")
|
||||
|
||||
func set_points(value):
|
||||
points = value
|
||||
if points >= next_level_points:
|
||||
next_level()
|
||||
emit_signal("refresh_stats")
|
||||
|
||||
func set_game_paused(value):
|
||||
game_paused = value
|
||||
get_tree().paused = game_paused
|
||||
emit_signal("refresh_stats")
|
||||
|
||||
func set_countdown(value):
|
||||
countdown = value
|
||||
if countdown:
|
||||
emit_signal("new_countdown")
|
||||
@ -1,90 +0,0 @@
|
||||
using System;
|
||||
using Godot;
|
||||
|
||||
namespace GodotspaceShooter.Scripts;
|
||||
|
||||
public partial class InputManager : Node
|
||||
{
|
||||
private const string GAME_PAUSE = "Game_Pause";
|
||||
private const string PLAYER_MOVE_FORWARD = "Player_Move_Forward";
|
||||
private const string PLAYER_MOVE_BACKWARDS = "Player_Move_Backwards";
|
||||
private const string PLAYER_MOVE_LEFT = "Player_Move_Left";
|
||||
private const string PLAYER_MOVE_RIGHT = "Player_Move_Right";
|
||||
private const string PLAYER_FIRE = "Player_Fire";
|
||||
|
||||
private float speed;
|
||||
private Vector3 moveDirection = Vector3.Zero;
|
||||
private Vector3 rotationDirection = Vector3.Zero;
|
||||
|
||||
public event EventHandler StartCountdown;
|
||||
|
||||
public static InputManager Instance { get; private set; }
|
||||
|
||||
public override void _Ready() => Instance = this;
|
||||
|
||||
public override void _Input(InputEvent @event) {
|
||||
float acceleration = PlayerShip.Instance.Acceleration;
|
||||
|
||||
//Countdown
|
||||
if (GameManager.Instance.Countdown && Input.IsAnythingPressed()) StartCountdown?.Invoke(this,EventArgs.Empty);
|
||||
|
||||
//Pause
|
||||
if (Input.IsActionJustReleased(GAME_PAUSE)) GameManager.Instance.GamePaused = GameManager.Instance.GamePaused switch { true => false, false => true };
|
||||
if (GameManager.Instance.GamePaused || GameManager.Instance.Countdown || GameManager.Instance.GameOver) return;
|
||||
|
||||
//Movement
|
||||
if (Input.IsActionPressed(PLAYER_MOVE_FORWARD)) {
|
||||
moveDirection.Z = -1;
|
||||
speed += acceleration;
|
||||
rotationDirection.X = -1;
|
||||
}
|
||||
if (Input.IsActionJustReleased(PLAYER_MOVE_FORWARD)) {
|
||||
moveDirection.Z = 0f;
|
||||
rotationDirection.X = 0f;
|
||||
}
|
||||
|
||||
if (Input.IsActionPressed(PLAYER_MOVE_BACKWARDS)) {
|
||||
moveDirection.Z = 1;
|
||||
speed += acceleration;
|
||||
rotationDirection.X = 1;
|
||||
}
|
||||
if (Input.IsActionJustReleased(PLAYER_MOVE_BACKWARDS)) {
|
||||
moveDirection.Z = 0f;
|
||||
rotationDirection.X = 0f;
|
||||
}
|
||||
|
||||
if (Input.IsActionPressed(PLAYER_MOVE_LEFT)) {
|
||||
moveDirection.X = -1;
|
||||
speed += acceleration;
|
||||
rotationDirection.Z = -1;
|
||||
}
|
||||
if (Input.IsActionJustReleased(PLAYER_MOVE_LEFT)) {
|
||||
moveDirection.X = 0f;
|
||||
rotationDirection.Z = 0f;
|
||||
}
|
||||
|
||||
if (Input.IsActionPressed(PLAYER_MOVE_RIGHT)) {
|
||||
moveDirection.X = 1;
|
||||
speed += acceleration;
|
||||
rotationDirection.Z = 1;
|
||||
}
|
||||
if (Input.IsActionJustReleased(PLAYER_MOVE_RIGHT)) {
|
||||
moveDirection.X = 0f;
|
||||
rotationDirection.Z = 0f;
|
||||
}
|
||||
|
||||
// if (Input.IsActionJustReleased(PLAYER_MOVE_FORWARD) || Input.IsActionJustReleased(PLAYER_MOVE_BACKWARDS) || Input.IsActionJustReleased(PLAYER_MOVE_LEFT) || Input.IsActionJustReleased(PLAYER_MOVE_RIGHT)) {
|
||||
// moveDirection = Vector3.Zero;
|
||||
// rotationDirection = Vector3.Zero;
|
||||
// speed = 0f;
|
||||
// }
|
||||
|
||||
PlayerShip.Instance.MoveDirection = moveDirection.Normalized();
|
||||
PlayerShip.Instance.RotationDirection = rotationDirection.Normalized();
|
||||
PlayerShip.Instance.Speed = speed;
|
||||
|
||||
//Shooting
|
||||
if (Input.IsActionJustPressed(PLAYER_FIRE)) PlayerShip.Instance.Shooting = true;
|
||||
// if (Input.IsActionJustReleased(PLAYER_FIRE)) PlayerShip.Instance.Shooting = false;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
extends Node
|
||||
|
||||
const GAME_PAUSE = "Game_Pause"
|
||||
const PLAYER_MOVE_FORWARD = "Player_Move_Forward"
|
||||
const PLAYER_MOVE_BACKWARDS = "Player_Move_Backwards"
|
||||
const PLAYER_MOVE_LEFT = "Player_Move_Left"
|
||||
const PLAYER_MOVE_RIGHT = "Player_Move_Right"
|
||||
const PLAYER_FIRE = "Player_Fire"
|
||||
|
||||
var speed = 0.0
|
||||
var move_direction = Vector3.ZERO
|
||||
var rotation_direction = Vector3.ZERO
|
||||
|
||||
signal start_countdown
|
||||
|
||||
onready var instance = self
|
||||
|
||||
func _ready():
|
||||
instance = self
|
||||
|
||||
func _input(event):
|
||||
var acceleration = PlayerShip.instance.acceleration
|
||||
|
||||
# Countdown
|
||||
if GameManager.instance.countdown and Input.is_anything_pressed():
|
||||
emit_signal("start_countdown")
|
||||
|
||||
# Pause
|
||||
if Input.is_action_just_released(GAME_PAUSE):
|
||||
GameManager.instance.game_paused = not GameManager.instance.game_paused
|
||||
if GameManager.instance.game_paused or GameManager.instance.countdown or GameManager.instance.game_over:
|
||||
return
|
||||
|
||||
# Movement
|
||||
if Input.is_action_pressed(PLAYER_MOVE_FORWARD):
|
||||
move_direction.z = -1
|
||||
speed += acceleration
|
||||
rotation_direction.x = -1
|
||||
if Input.is_action_just_released(PLAYER_MOVE_FORWARD):
|
||||
move_direction.z = 0.0
|
||||
rotation_direction.x = 0.0
|
||||
|
||||
if Input.is_action_pressed(PLAYER_MOVE_BACKWARDS):
|
||||
move_direction.z = 1
|
||||
speed += acceleration
|
||||
rotation_direction.x = 1
|
||||
if Input.is_action_just_released(PLAYER_MOVE_BACKWARDS):
|
||||
move_direction.z = 0.0
|
||||
rotation_direction.x = 0.0
|
||||
|
||||
if Input.is_action_pressed(PLAYER_MOVE_LEFT):
|
||||
move_direction.x = -1
|
||||
speed += acceleration
|
||||
rotation_direction.z = -1
|
||||
if Input.is_action_just_released(PLAYER_MOVE_LEFT):
|
||||
move_direction.x = 0.0
|
||||
rotation_direction.z = 0.0
|
||||
|
||||
if Input.is_action_pressed(PLAYER_MOVE_RIGHT):
|
||||
move_direction.x = 1
|
||||
speed += acceleration
|
||||
rotation_direction.z = 1
|
||||
if Input.is_action_just_released(PLAYER_MOVE_RIGHT):
|
||||
move_direction.x = 0.0
|
||||
rotation_direction.z = 0.0
|
||||
|
||||
PlayerShip.instance.move_direction = move_direction.normalized()
|
||||
PlayerShip.instance.rotation_direction = rotation_direction.normalized()
|
||||
PlayerShip.instance.speed = speed
|
||||
|
||||
# Shooting
|
||||
if Input.is_action_just_pressed(PLAYER_FIRE):
|
||||
PlayerShip.instance.shooting = true
|
||||
@ -1,13 +0,0 @@
|
||||
using Godot;
|
||||
|
||||
namespace GodotspaceShooter.Scripts;
|
||||
|
||||
public partial class LaserBeam : Node3D {
|
||||
[Export] private RigidBody3D rigidBody;
|
||||
private float speed = 10f;
|
||||
|
||||
public override void _Ready() {
|
||||
Vector3 movement = new(0f, 0f, -speed);
|
||||
rigidBody.AddConstantForce(movement);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
extends Node3D
|
||||
|
||||
@export var rigid_body: RigidBody3D
|
||||
var speed: float = 10.0
|
||||
|
||||
func _ready():
|
||||
var movement = Vector3(0.0, 0.0, -speed)
|
||||
rigid_body.add_constant_force(movement)
|
||||
@ -1,89 +0,0 @@
|
||||
using Godot;
|
||||
|
||||
namespace GodotspaceShooter.Scripts;
|
||||
|
||||
public partial class PlayerShip : Node3D {
|
||||
[Export] private PackedScene explosion;
|
||||
[Export] private Node3D jet;
|
||||
[Export] private PackedScene laser;
|
||||
[Export] private RigidBody3D rbPlayer;
|
||||
[Export] private Node3D shots;
|
||||
|
||||
private const float jetTimerMax = 0.5f;
|
||||
private const float laserTimerMax = 1f;
|
||||
private float laserTimer = laserTimerMax;
|
||||
private float jetTimer = jetTimerMax;
|
||||
private float maxSpeed = 5f;
|
||||
private float speed;
|
||||
public float Acceleration { get; private set; } = .5f;
|
||||
|
||||
public float Speed {
|
||||
get => speed;
|
||||
set {
|
||||
speed = value;
|
||||
if (speed > maxSpeed) speed = maxSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 MoveDirection { get; set; }
|
||||
public Vector3 RotationDirection { get; set; }
|
||||
public bool Shooting { get; set; }
|
||||
|
||||
public static PlayerShip Instance { get; private set; }
|
||||
|
||||
public override void _Ready() {
|
||||
Position = new(0, 0, 5);
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public override void _Process(double delta) {
|
||||
if (Shooting)
|
||||
if (laser.Instantiate() is Node3D shot) {
|
||||
shot.Position = rbPlayer.Position + new Vector3(0f, 0f, 0.74f);
|
||||
shots.AddChild(shot);
|
||||
SoundManager.Instance.Play(SoundManager.Sound.Laser, rbPlayer.Position);
|
||||
Shooting = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void _PhysicsProcess(double delta) {
|
||||
//Movement
|
||||
GD.Print($"Move {MoveDirection.X}, {MoveDirection.Z}, Speed {Speed}, Rot {RotationDirection.X}, {RotationDirection.Z}");
|
||||
if (Speed > 0f) Speed -= Acceleration * (float)delta * 10f;
|
||||
|
||||
if (MoveDirection.Length() > 0) {
|
||||
jet.Visible = true;
|
||||
KinematicCollision3D collision = rbPlayer.MoveAndCollide(MoveDirection * Speed * (float)delta);
|
||||
if (collision?.GetCollider() is Node3D collider) {
|
||||
Node3D parent = collider.GetParent<Node3D>();
|
||||
Vector3 collisionPosition = collision.GetPosition();
|
||||
switch (parent) {
|
||||
case GameArea:
|
||||
GD.Print($"PlayerShip hits GameArea");
|
||||
break;
|
||||
case Asteroid asteroid:
|
||||
Explode(collisionPosition);
|
||||
asteroid.Explode(collisionPosition);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
jet.Visible = false;
|
||||
}
|
||||
|
||||
//Rotation
|
||||
rbPlayer.RotationDegrees = RotationDirection;
|
||||
}
|
||||
|
||||
public void Explode(Vector3 collisionPosition) {
|
||||
if (explosion?.Instantiate() is GpuParticles3D ex) {
|
||||
GetParent().AddChild(ex);
|
||||
ex.Position = collisionPosition;
|
||||
ex.Emitting = true;
|
||||
}
|
||||
|
||||
SoundManager.Instance.Play(SoundManager.Sound.PlayerExplosion, collisionPosition);
|
||||
GameManager.Instance.Lives--;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
extends Node3D
|
||||
|
||||
@export var explosion: PackedScene
|
||||
@export var jet: Node3D
|
||||
@export var laser: PackedScene
|
||||
@export var rbPlayer: RigidBody3D
|
||||
@export var shots: Node3D
|
||||
|
||||
const jetTimerMax = 0.5
|
||||
const laserTimerMax = 1.0
|
||||
var laserTimer = laserTimerMax
|
||||
var jetTimer = jetTimerMax
|
||||
var maxSpeed = 5.0
|
||||
var speed = 0.0
|
||||
var acceleration = 0.5
|
||||
|
||||
var move_direction = Vector3()
|
||||
var rotation_direction = Vector3()
|
||||
var shooting = false
|
||||
|
||||
static var instance: PlayerShip
|
||||
|
||||
func _ready():
|
||||
position = Vector3(0, 0, 5)
|
||||
instance = self
|
||||
|
||||
func _process(delta):
|
||||
if shooting:
|
||||
var shot = laser.instantiate() as Node3D
|
||||
if shot:
|
||||
shot.position = rbPlayer.position + Vector3(0, 0, 0.74)
|
||||
shots.add_child(shot)
|
||||
SoundManager.instance.play(SoundManager.Sound.Laser, rbPlayer.position)
|
||||
shooting = false
|
||||
|
||||
func _physics_process(delta):
|
||||
# Movement
|
||||
print("Move %f, %f, Speed %f, Rot %f, %f" % [move_direction.x, move_direction.z, speed, rotation_direction.x, rotation_direction.z])
|
||||
if speed > 0.0:
|
||||
speed -= acceleration * delta * 10.0
|
||||
|
||||
if move_direction.length() > 0:
|
||||
jet.visible = true
|
||||
var collision = rbPlayer.move_and_collide(move_direction * speed * delta)
|
||||
if collision and collision.collider:
|
||||
var collider = collision.collider as Node3D
|
||||
var parent = collider.get_parent() as Node3D
|
||||
var collision_position = collision.position
|
||||
if parent is GameArea:
|
||||
print("PlayerShip hits GameArea")
|
||||
elif parent is Asteroid:
|
||||
explode(collision_position)
|
||||
parent.explode(collision_position)
|
||||
else:
|
||||
jet.visible = false
|
||||
|
||||
# Rotation
|
||||
rbPlayer.rotation_degrees = rotation_direction
|
||||
|
||||
func explode(collision_position: Vector3):
|
||||
var ex = explosion.instantiate() as GpuParticles3D
|
||||
if ex:
|
||||
get_parent().add_child(ex)
|
||||
ex.position = collision_position
|
||||
ex.emitting = true
|
||||
|
||||
SoundManager.instance.play(SoundManager.Sound.PlayerExplosion, collision_position)
|
||||
GameManager.instance.lives -= 1
|
||||
@ -1,78 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Godot;
|
||||
|
||||
namespace GodotspaceShooter.Scripts;
|
||||
|
||||
public partial class SoundManager : Node {
|
||||
[Export] private AudioStreamPlayer backgroundMusic;
|
||||
[Export] private AudioStreamPlayer gameOverMusic;
|
||||
[Export] private AudioStreamPlayer3D asteroidExplosionSound;
|
||||
[Export] private AudioStreamPlayer3D playerExplosionSound;
|
||||
[Export] private AudioStreamPlayer3D laserSound;
|
||||
|
||||
public static SoundManager Instance { get; private set; }
|
||||
|
||||
public enum Sound {
|
||||
BackgroundMusic,
|
||||
AsteroidExplosion,
|
||||
PlayerExplosion,
|
||||
Laser,
|
||||
GameOver
|
||||
}
|
||||
|
||||
public override void _Ready() => Instance = this;
|
||||
|
||||
public void Play(Sound sound, [Optional] Vector3 position) {
|
||||
float pitchScale = 0;
|
||||
if (sound is Sound.Laser or Sound.AsteroidExplosion or Sound.PlayerExplosion) pitchScale = (float)GD.RandRange(0.25f, 1.75f);
|
||||
|
||||
switch (sound) {
|
||||
case Sound.AsteroidExplosion:
|
||||
asteroidExplosionSound.Position = position;
|
||||
asteroidExplosionSound.PitchScale = pitchScale;
|
||||
asteroidExplosionSound.Play();
|
||||
break;
|
||||
case Sound.Laser:
|
||||
laserSound.Position = position;
|
||||
laserSound.PitchScale = pitchScale;
|
||||
laserSound.Play();
|
||||
break;
|
||||
case Sound.PlayerExplosion:
|
||||
playerExplosionSound.Position = position;
|
||||
playerExplosionSound.PitchScale = pitchScale;
|
||||
playerExplosionSound.Play();
|
||||
break;
|
||||
case Sound.BackgroundMusic:
|
||||
backgroundMusic.Play();
|
||||
break;
|
||||
case Sound.GameOver:
|
||||
gameOverMusic.Play();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(sound), sound, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop(Sound sound) {
|
||||
switch (sound) {
|
||||
case Sound.AsteroidExplosion:
|
||||
asteroidExplosionSound.Stop();
|
||||
break;
|
||||
case Sound.Laser:
|
||||
laserSound.Stop();
|
||||
break;
|
||||
case Sound.PlayerExplosion:
|
||||
playerExplosionSound.Stop();
|
||||
break;
|
||||
case Sound.BackgroundMusic:
|
||||
backgroundMusic.Stop();
|
||||
break;
|
||||
case Sound.GameOver:
|
||||
gameOverMusic.Stop();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(sound), sound, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
extends Node
|
||||
|
||||
@export var background_music: AudioStreamPlayer
|
||||
@export var game_over_music: AudioStreamPlayer
|
||||
@export var asteroid_explosion_sound: AudioStreamPlayer3D
|
||||
@export var player_explosion_sound: AudioStreamPlayer3D
|
||||
@export var laser_sound: AudioStreamPlayer3D
|
||||
|
||||
enum Sound {
|
||||
BACKGROUND_MUSIC,
|
||||
ASTEROID_EXPLOSION,
|
||||
PLAYER_EXPLOSION,
|
||||
LASER,
|
||||
GAME_OVER
|
||||
}
|
||||
|
||||
var instance: SoundManager
|
||||
|
||||
func _ready():
|
||||
instance = self
|
||||
|
||||
func play(sound: int, position: Vector3 = Vector3()):
|
||||
var pitch_scale: float = 0
|
||||
if sound in [Sound.LASER, Sound.ASTEROID_EXPLOSION, Sound.PLAYER_EXPLOSION]:
|
||||
pitch_scale = rand_range(0.25, 1.75)
|
||||
|
||||
match sound:
|
||||
Sound.ASTEROID_EXPLOSION:
|
||||
asteroid_explosion_sound.position = position
|
||||
asteroid_explosion_sound.pitch_scale = pitch_scale
|
||||
asteroid_explosion_sound.play()
|
||||
Sound.LASER:
|
||||
laser_sound.position = position
|
||||
laser_sound.pitch_scale = pitch_scale
|
||||
laser_sound.play()
|
||||
Sound.PLAYER_EXPLOSION:
|
||||
player_explosion_sound.position = position
|
||||
player_explosion_sound.pitch_scale = pitch_scale
|
||||
player_explosion_sound.play()
|
||||
Sound.BACKGROUND_MUSIC:
|
||||
background_music.play()
|
||||
Sound.GAME_OVER:
|
||||
game_over_music.play()
|
||||
_:
|
||||
push_error("Invalid sound: %s" % str(sound))
|
||||
|
||||
func stop(sound: int):
|
||||
match sound:
|
||||
Sound.ASTEROID_EXPLOSION:
|
||||
asteroid_explosion_sound.stop()
|
||||
Sound.LASER:
|
||||
laser_sound.stop()
|
||||
Sound.PLAYER_EXPLOSION:
|
||||
player_explosion_sound.stop()
|
||||
Sound.BACKGROUND_MUSIC:
|
||||
background_music.stop()
|
||||
Sound.GAME_OVER:
|
||||
game_over_music.stop()
|
||||
_:
|
||||
push_error("Invalid sound: %s" % str(sound))
|
||||
@ -1,115 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Godot;
|
||||
|
||||
namespace GodotspaceShooter.Scripts;
|
||||
public partial class Ui : Node2D
|
||||
{
|
||||
[ExportCategory("Stats")]
|
||||
[Export] private Label lblLevel;
|
||||
[Export] private Label lblPoints;
|
||||
[Export] private ProgressBar barHealth;
|
||||
[Export] private ProgressBar barLevel;
|
||||
[ExportCategory("GameOver")]
|
||||
[Export] private VBoxContainer vBoxGameOver;
|
||||
[Export] private Label lblGameOver;
|
||||
[Export] private Button butGameOverRestart;
|
||||
[Export] private Label lblReachedPoints;
|
||||
[Export] private Button butGameOverExit;
|
||||
[ExportCategory("Pause")]
|
||||
[Export] private VBoxContainer vBoxPause;
|
||||
[Export] private Button buttonResume;
|
||||
[Export] private Button buttonPauseRestart;
|
||||
[Export] private Button butPauseExit;
|
||||
[ExportCategory("Countdown")]
|
||||
[Export] private VBoxContainer vBoxCountdown;
|
||||
[Export] private Label lblCountdown;
|
||||
[Export] private TextureRect textAnyKey;
|
||||
[Export] private Label lblAnyKey;
|
||||
|
||||
private const double countdownMaxTimer = 3;
|
||||
private double countdownTimer = countdownMaxTimer;
|
||||
private bool countdownRunning;
|
||||
|
||||
|
||||
public override void _Ready() {
|
||||
if (GameManager.Instance is null)
|
||||
GD.PrintErr("No GameManager found!");
|
||||
else {
|
||||
GameManager.Instance.OnRefreshStats += GameManager_RefreshStats;
|
||||
GameManager.Instance.OnNewCountdown += GameManager_NewCountdown;
|
||||
GameManager.Instance.OnGameOver += GameManager_GameOver;
|
||||
InputManager.Instance.StartCountdown += InputManager_StartCountdown;
|
||||
lblGameOver.Visible = GameManager.Instance.GameOver;
|
||||
butGameOverRestart.Visible = GameManager.Instance.GameOver;
|
||||
lblReachedPoints.Visible = GameManager.Instance.GameOver;
|
||||
butGameOverRestart.Pressed += AllButtonRestartOnPressed;
|
||||
buttonResume.Pressed += ButtonResumeOnPressed;
|
||||
buttonPauseRestart.Pressed += AllButtonRestartOnPressed;
|
||||
butPauseExit.Pressed += AllButExitOnPressed;
|
||||
butGameOverExit.Pressed += AllButExitOnPressed;
|
||||
GameManager_RefreshStats(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void AllButExitOnPressed() => GetTree().Quit();
|
||||
|
||||
private void GameManager_GameOver(object sender, EventArgs e) {
|
||||
GD.Print($"Game Over! You have reached level {GameManager.Instance.Level} and {GameManager.Instance.Points} points!");
|
||||
lblReachedPoints.Text = $"You have reached level {GameManager.Instance.Level} and {GameManager.Instance.Points} points!";
|
||||
foreach (CanvasItem canvasItem in vBoxGameOver.GetChildren().Cast<CanvasItem>()) canvasItem.Visible = true;
|
||||
vBoxGameOver.Visible = true;
|
||||
}
|
||||
|
||||
private void InputManager_StartCountdown(object sender, EventArgs e) {
|
||||
lblAnyKey.Visible = false;
|
||||
textAnyKey.Visible = false;
|
||||
lblCountdown.Visible = true;
|
||||
countdownRunning = true;
|
||||
}
|
||||
|
||||
private void GameManager_NewCountdown(object sender, EventArgs e) {
|
||||
lblCountdown.Text = countdownTimer.ToString(CultureInfo.InvariantCulture);
|
||||
foreach (CanvasItem canvasItem in vBoxCountdown.GetChildren().Cast<CanvasItem>()) canvasItem.Visible = true;
|
||||
vBoxCountdown.Visible = true;
|
||||
lblCountdown.Visible = false;
|
||||
}
|
||||
|
||||
public override void _Process(double delta) {
|
||||
if (GameManager.Instance.Countdown && countdownRunning) {
|
||||
countdownTimer -= delta;
|
||||
lblCountdown.Text = Mathf.RoundToInt(countdownTimer).ToString();
|
||||
if (countdownTimer <= 0) {
|
||||
foreach (CanvasItem canvasItem in vBoxCountdown.GetChildren().Cast<CanvasItem>()) canvasItem.Visible = false;
|
||||
vBoxCountdown.Visible = false;
|
||||
countdownRunning = false;
|
||||
GameManager.Instance.Countdown = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GameManager_RefreshStats(object sender, EventArgs e) {
|
||||
lblLevel.Text = $"{GameManager.Instance.Level}";
|
||||
lblPoints.Text = $"{GameManager.Instance.Points}";
|
||||
|
||||
barHealth.MaxValue = GameManager.Instance.MaxLives;
|
||||
barHealth.Value = GameManager.Instance.Lives;
|
||||
barHealth.TooltipText = $"{GameManager.Instance.Lives} / {GameManager.Instance.MaxLives}";
|
||||
|
||||
barLevel.MaxValue = GameManager.Instance.NextLevelPoints;
|
||||
barLevel.Value = GameManager.Instance.Points - GameManager.Instance.LastLevelPoints;
|
||||
barLevel.TooltipText = $"{GameManager.Instance.Points} / {GameManager.Instance.NextLevelPoints}";
|
||||
|
||||
vBoxPause.Visible = GameManager.Instance.GamePaused;
|
||||
vBoxGameOver.Visible = GameManager.Instance.GameOver;
|
||||
}
|
||||
|
||||
private static void ButtonResumeOnPressed() => GameManager.Instance.GamePaused = false;
|
||||
private void AllButtonRestartOnPressed() {
|
||||
GameManager.Instance.GamePaused = false;
|
||||
GetTree().ReloadCurrentScene();
|
||||
}
|
||||
|
||||
public static void ShowMessage(string message) => GD.Print(message);
|
||||
}
|
||||
@ -0,0 +1,104 @@
|
||||
extends Node2D
|
||||
|
||||
@export_category("Stats")
|
||||
@export var lblLevel: Label
|
||||
@export var lblPoints: Label
|
||||
@export var barHealth: ProgressBar
|
||||
@export var barLevel: ProgressBar
|
||||
@export_category("GameOver")
|
||||
@export var vBoxGameOver: VBoxContainer
|
||||
@export var lblGameOver: Label
|
||||
@export var butGameOverRestart: Button
|
||||
@export var lblReachedPoints: Label
|
||||
@export var butGameOverExit: Button
|
||||
@export_category("Pause")
|
||||
@export var vBoxPause: VBoxContainer
|
||||
@export var buttonResume: Button
|
||||
@export var buttonPauseRestart: Button
|
||||
@export var butPauseExit: Button
|
||||
@export_category("Countdown")
|
||||
@export var vBoxCountdown: VBoxContainer
|
||||
@export var lblCountdown: Label
|
||||
@export var textAnyKey: TextureRect
|
||||
@export var lblAnyKey: Label
|
||||
|
||||
const countdownMaxTimer = 3.0
|
||||
var countdownTimer = countdownMaxTimer
|
||||
var countdownRunning = false
|
||||
|
||||
func _ready():
|
||||
if GameManager.instance == null:
|
||||
print_err("No GameManager found!")
|
||||
else:
|
||||
GameManager.instance.connect("refresh_stats", self, "_on_GameManager_refresh_stats")
|
||||
GameManager.instance.connect("new_countdown", self, "_on_GameManager_new_countdown")
|
||||
GameManager.instance.connect("game_over", self, "_on_GameManager_game_over")
|
||||
InputManager.instance.connect("start_countdown", self, "_on_InputManager_start_countdown")
|
||||
lblGameOver.visible = GameManager.instance.game_over
|
||||
butGameOverRestart.visible = GameManager.instance.game_over
|
||||
lblReachedPoints.visible = GameManager.instance.game_over
|
||||
butGameOverRestart.connect("pressed", self, "_on_AllButtonRestart_pressed")
|
||||
buttonResume.connect("pressed", self, "_on_ButtonResume_pressed")
|
||||
buttonPauseRestart.connect("pressed", self, "_on_AllButtonRestart_pressed")
|
||||
butPauseExit.connect("pressed", self, "_on_AllButExit_pressed")
|
||||
butGameOverExit.connect("pressed", self, "_on_AllButExit_pressed")
|
||||
_on_GameManager_refresh_stats()
|
||||
|
||||
func _on_AllButExit_pressed():
|
||||
get_tree().quit()
|
||||
|
||||
func _on_GameManager_game_over():
|
||||
print("Game Over! You have reached level %d and %d points!" % [GameManager.instance.level, GameManager.instance.points])
|
||||
lblReachedPoints.text = "You have reached level %d and %d points!" % [GameManager.instance.level, GameManager.instance.points]
|
||||
for canvas_item in vBoxGameOver.get_children():
|
||||
canvas_item.visible = true
|
||||
vBoxGameOver.visible = true
|
||||
|
||||
func _on_InputManager_start_countdown():
|
||||
lblAnyKey.visible = false
|
||||
textAnyKey.visible = false
|
||||
lblCountdown.visible = true
|
||||
countdownRunning = true
|
||||
|
||||
func _on_GameManager_new_countdown():
|
||||
lblCountdown.text = str(countdownTimer)
|
||||
for canvas_item in vBoxCountdown.get_children():
|
||||
canvas_item.visible = true
|
||||
vBoxCountdown.visible = true
|
||||
lblCountdown.visible = false
|
||||
|
||||
func _process(delta):
|
||||
if GameManager.instance.countdown and countdownRunning:
|
||||
countdownTimer -= delta
|
||||
lblCountdown.text = str(round(countdownTimer))
|
||||
if countdownTimer <= 0:
|
||||
for canvas_item in vBoxCountdown.get_children():
|
||||
canvas_item.visible = false
|
||||
vBoxCountdown.visible = false
|
||||
countdownRunning = false
|
||||
GameManager.instance.countdown = false
|
||||
|
||||
func _on_GameManager_refresh_stats():
|
||||
lblLevel.text = str(GameManager.instance.level)
|
||||
lblPoints.text = str(GameManager.instance.points)
|
||||
|
||||
barHealth.max_value = GameManager.instance.max_lives
|
||||
barHealth.value = GameManager.instance.lives
|
||||
barHealth.tooltip_text = "%d / %d" % [GameManager.instance.lives, GameManager.instance.max_lives]
|
||||
|
||||
barLevel.max_value = GameManager.instance.next_level_points
|
||||
barLevel.value = GameManager.instance.points - GameManager.instance.last_level_points
|
||||
barLevel.tooltip_text = "%d / %d" % [GameManager.instance.points, GameManager.instance.next_level_points]
|
||||
|
||||
vBoxPause.visible = GameManager.instance.game_paused
|
||||
vBoxGameOver.visible = GameManager.instance.game_over
|
||||
|
||||
func _on_ButtonResume_pressed():
|
||||
GameManager.instance.game_paused = false
|
||||
|
||||
func _on_AllButtonRestart_pressed():
|
||||
GameManager.instance.game_paused = false
|
||||
get_tree().reload_current_scene()
|
||||
|
||||
func show_message(message):
|
||||
print(message)
|
||||
Loading…
Reference in New Issue