Skip to content

Commit 4efd076

Browse files
committed
switch from lives to HP based approach
1 parent c03479f commit 4efd076

4 files changed

Lines changed: 116 additions & 45 deletions

File tree

README.md

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88

99
A mission-based space shooter game built with Rust and Macroquad. Complete missions, destroy enemies, collect resources, and progress through increasingly challenging levels!
1010

11+
> *"You're a mercenary pilot, drifting through the outer rim of civilized space. Out here, the only law is what you can enforce with your ship's cannons. The corporations pay well for rust piles and rare metals mined from the asteroid fields, but they don't tell you about the enemy patrols or the void pirates that call these deep space sectors home.*
12+
>
13+
> *Every mission takes you deeper into the unknown. Every asteroid you crack could be your last. But the credits are good, and in this part of the galaxy, that's all that matters. Strap in, pilot. The void awaits."*
14+
1115
## Features
1216

1317
- **Mission-Based Gameplay**: Complete objectives across multiple levels with increasing difficulty
@@ -17,9 +21,10 @@ A mission-based space shooter game built with Rust and Macroquad. Complete missi
1721
- **Loot System**: Collect scrap, rare metals, health packs, and weapon boosts
1822
- **Magnet Effect**: Loot items are automatically attracted to your ship when nearby
1923
- **Animated Loot**: Items rotate and drift realistically in space
20-
- **Resource Management**: Track scrap (ordinary currency) and rare metals (premium currency)
24+
- **Resource Management**: Track rust piles (scrap) and gold (rare metals) separately
25+
- **Health Point System**: Start with 100 HP - bigger asteroids deal more damage!
26+
- **Variable Damage**: Damage scales with asteroid size and bullet type
2127
- **High Score System**: Your high score is automatically saved and persists between sessions
22-
- **Lives System**: Start with 10 lives and collect health packs to extend your survival
2328

2429
## Controls
2530

@@ -82,8 +87,9 @@ The game features a mission-based progression system:
8287
- **Briefing Screen**: View mission objectives before launching
8388
- **Mission Objectives**: Each mission requires completing specific goals:
8489
- Destroy a certain number of enemies
85-
- Collect a certain amount of scrap
86-
- **Mission Success**: Complete objectives to progress to the next level
90+
- Collect a certain amount of rust piles (scrap)
91+
- Collect a certain amount of gold (rare metals)
92+
- **Mission Success**: Complete all objectives to progress to the next level
8793
- **Progressive Difficulty**: Missions become increasingly challenging with more enemies and asteroids
8894

8995
### Scoring
@@ -95,35 +101,49 @@ The game features a mission-based progression system:
95101
Loot items drop from destroyed asteroids and enemies:
96102

97103
**From Regular Asteroids:**
98-
- **Scrap** (40% chance): 1-3 scrap pieces
99-
- **Rare Metal** (5% chance): 1 rare metal piece
104+
- **Rust Piles (Scrap)** (40% chance): 1-3 pieces
105+
- **Gold (Rare Metal)** (5% chance): 1 piece
100106

101107
**From Rare Asteroids** (10% chance to spawn, always drop loot):
102-
- **Rare Metal** (50% chance): 2-4 rare metal pieces
103-
- **Scrap** (30% chance): 5-9 scrap pieces
104-
- **Health Pack** (10% chance): Restores 1 life
108+
- **Gold (Rare Metal)** (50% chance): 2-5 pieces
109+
- **Rust Piles (Scrap)** (30% chance): 5-9 pieces
110+
- **Health Pack** (10% chance): Restores health points
105111
- **Weapon Boost** (10% chance): Rapid fire for 10 seconds
106112

107113
**From Enemy Ships:**
108-
- **Scrap** (30% chance): 5-9 scrap pieces
109-
- **Health Pack** (10% chance): Restores 1 life
114+
- **Rust Piles (Scrap)** (30% chance): 5-9 pieces
115+
- **Health Pack** (10% chance): Restores health points
110116
- **Weapon Boost** (5% chance): Rapid fire for 10 seconds
111117

118+
**Note**: Health packs and weapon boosts do NOT count toward resource collection objectives
119+
112120
**Loot Mechanics:**
113121
- Items drift and rotate in space for visual appeal
114122
- **Magnet Effect**: When within 150 units of your ship, loot is automatically attracted to you
115123
- Items are collected on contact with your ship
124+
- **Resource Tracking**:
125+
- Mission objectives track rust piles and gold separately
126+
- Your inventory shows total resources collected: "Resources: Rust X | Gold Y"
127+
- Mission progress shows: "Kills: X/Y Rust: X/Y Gold: X/Y"
116128

117129
### Gameplay
118130

119-
- Start with 10 lives
120-
- Complete mission objectives to progress
131+
- **Health System**: Start with 100 HP (displayed as HP: current/max)
132+
- **Damage System**:
133+
- **Asteroid Collisions**: Damage scales with asteroid size (bigger asteroids = more damage)
134+
- Base damage: 5 HP per 10 units of radius
135+
- Large asteroids (radius 40): ~20 HP damage
136+
- Medium fragments (radius 20): ~10 HP damage
137+
- Small fragments (radius 10): ~5 HP damage
138+
- **Enemy Bullets**: Deal 15 HP damage
139+
- **Player Bullets**: Deal 10 HP damage to enemies
140+
- Complete mission objectives to progress (kills, rust piles, and gold)
121141
- Destroy asteroids to break them into smaller pieces
122142
- Rare asteroids (10% spawn chance) have distinct appearance and better loot
123143
- Enemy ships spawn based on mission configuration and track your position
124-
- Collect scrap and rare metals for future upgrades (coming soon)
125-
- Colliding with asteroids or enemy bullets reduces your lives
126-
- When all lives are lost, your score is saved if it's a new high score
144+
- Collect rust piles and gold separately - missions require specific amounts of each
145+
- Health packs restore HP (capped at maximum)
146+
- When HP reaches 0, your score is saved if it's a new high score
127147

128148
## Project Structure
129149

src/components.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ pub struct Mission {
99
pub description: String,
1010

1111
// mission objectives
12-
pub target_kills: u32, // how many enemies to destroy
13-
pub target_scrap: u32, // how many scrap to collect
12+
pub target_kills: u32, // how many enemies to destroy
13+
pub target_scrap: u32, // how many scrap (rust piles) to collect
14+
pub target_rare_metal: u32, // how many rare metal (gold) to collect
1415

1516
// level difficulty settings
1617
pub enemy_spawn_interval: f32,
@@ -36,6 +37,7 @@ pub struct Bullet {
3637
pub vel: Vec2,
3738
pub life_time: f32,
3839
pub style: BulletStyle,
40+
pub damage: f32, // Damage dealt by this bullet
3941
}
4042

4143
pub struct Asteroid {
@@ -56,7 +58,8 @@ pub struct Ship {
5658
pub pos: Vec2,
5759
pub vel: Vec2,
5860
pub rotation: f32,
59-
pub lives: i32,
61+
pub health: f32, // Current health points
62+
pub max_health: f32, // Maximum health points
6063
pub shoot_timer: f32,
6164
pub rapid_fire_timer: f32,
6265
pub engine: Engine,
@@ -148,20 +151,22 @@ impl EnemyShip {
148151

149152
impl Ship {
150153
// Returns true if the game is over
151-
pub fn take_damage(&mut self, score: u32) -> bool {
152-
self.lives -= 1;
154+
pub fn take_damage(&mut self, damage: f32, score: u32) -> bool {
155+
self.health -= damage;
153156

154-
if self.lives <= 0 {
157+
if self.health <= 0.0 {
155158
// Save score immediately using our system
156159
crate::systems::save_score(score);
157160
true // Game Over
158161
} else {
159-
// Reset position for next life
160-
self.pos = vec2(screen_width() / 2.0, screen_height() / 2.0);
161-
self.vel = vec2(0.0, 0.0);
162162
false // Still alive
163163
}
164164
}
165+
166+
// Restore health (used by health packs)
167+
pub fn heal(&mut self, amount: f32) {
168+
self.health = (self.health + amount).min(self.max_health);
169+
}
165170
}
166171

167172
impl Engine {

src/main.rs

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ const ACCELERATION: f32 = 150.0;
1717
const BULLET_SPEED: f32 = 400.0;
1818
const BULLET_LIFETIME: f32 = 2.0;
1919
const SHOOT_COOLDOWN: f32 = 0.3;
20+
const PLAYER_BULLET_DAMAGE: f32 = 10.0;
21+
const ENEMY_BULLET_DAMAGE: f32 = 15.0;
22+
const BASE_ASTEROID_DAMAGE: f32 = 5.0; // Base damage per 10 units of radius
2023

2124
fn window_conf() -> Conf {
2225
Conf {
@@ -52,6 +55,7 @@ async fn main() {
5255
// current mission state
5356
let mut mission_kills = 0;
5457
let mut mission_scrap_collected = 0;
58+
let mut mission_rare_metal_collected = 0;
5559

5660
let mut enemy_spawn_timer = 0.0;
5761

@@ -135,10 +139,23 @@ async fn main() {
135139

136140
draw_text_centered("OBJECTIVES:", 20.0, 30, GRAY);
137141

138-
let obj_text = format!(
139-
"- Destroy {} Enemies\n- Collect {} Scrap",
140-
current_mission.target_kills, current_mission.target_scrap
141-
);
142+
let mut objectives = vec![format!(
143+
"- Destroy {} Enemies",
144+
current_mission.target_kills
145+
)];
146+
if current_mission.target_scrap > 0 {
147+
objectives.push(format!(
148+
"- Collect {} Rust Piles",
149+
current_mission.target_scrap
150+
));
151+
}
152+
if current_mission.target_rare_metal > 0 {
153+
objectives.push(format!(
154+
"- Collect {} Gold",
155+
current_mission.target_rare_metal
156+
));
157+
}
158+
let obj_text = objectives.join("\n");
142159
draw_text_centered(&obj_text, 70.0, 30, WHITE);
143160

144161
draw_text_centered("Press [SPACE] to Launch", 200.0, 30, GREEN);
@@ -157,6 +174,7 @@ async fn main() {
157174
// reset the mission counters
158175
mission_kills = 0;
159176
mission_scrap_collected = 0;
177+
mission_rare_metal_collected = 0;
160178
enemy_spawn_timer = current_mission.enemy_spawn_interval;
161179

162180
// reset the ship position (but keep the upgrades if there are any)
@@ -173,6 +191,7 @@ async fn main() {
173191
// 1. Check the mission objectives
174192
if mission_kills >= current_mission.target_kills
175193
&& mission_scrap_collected >= current_mission.target_scrap
194+
&& mission_rare_metal_collected >= current_mission.target_rare_metal
176195
{
177196
state = GameState::MissionSuccess;
178197
}
@@ -219,6 +238,7 @@ async fn main() {
219238
vel: ship_dir * BULLET_SPEED + ship.vel,
220239
life_time: BULLET_LIFETIME,
221240
style: BulletStyle::Player,
241+
damage: PLAYER_BULLET_DAMAGE,
222242
});
223243
ship.shoot_timer = current_cooldown;
224244
}
@@ -239,14 +259,16 @@ async fn main() {
239259
vel: bullet_vel,
240260
life_time: 4.0,
241261
style: BulletStyle::Enemy,
262+
damage: ENEMY_BULLET_DAMAGE,
242263
});
243264
e.shoot_timer = 2.0;
244265
}
245266
}
246267
enemy_ships.retain(|e| e.pos.x > -100.0 && e.pos.x < screen_width() + 100.0);
247268

248269
// 2. UPDATE LOOT (Magnet and Collection)
249-
loot_items.retain_mut(|item| {
270+
let mut items_to_remove = Vec::new();
271+
for (i, item) in loot_items.iter_mut().enumerate() {
250272
// Animation of slowing down the spread (initial explosion velocity)
251273
item.vel *= 0.95;
252274
item.pos += item.vel * dt;
@@ -282,24 +304,30 @@ async fn main() {
282304
match item.item_type {
283305
LootType::Scrap(amount) => {
284306
ship.scrap += amount;
285-
mission_scrap_collected += amount; // Для миссии
307+
mission_scrap_collected += amount; // Count for mission objective
286308
// play_sound_pickup();
287309
}
288310
LootType::RareMetal(amount) => {
289311
ship.rare_metal += amount;
290-
// play_sound_rare();
312+
mission_rare_metal_collected += amount; // Count for mission objective
313+
// play_sound_rare();
291314
}
292315
LootType::HealthPack(hp) => {
293-
ship.lives += hp;
316+
ship.heal(hp as f32);
317+
// Health packs don't count as resources
294318
}
295319
LootType::WeaponBoost => {
296320
ship.rapid_fire_timer = 10.0;
321+
// Weapon boosts don't count as resources
297322
}
298323
}
299-
return false; // Remove from the world
324+
items_to_remove.push(i);
300325
}
301-
true // Leave in the world
302-
});
326+
}
327+
// Remove collected items (in reverse order to maintain indices)
328+
for &i in items_to_remove.iter().rev() {
329+
loot_items.remove(i);
330+
}
303331

304332
// 4. Update Physics
305333
bullets.iter_mut().for_each(|b| {
@@ -371,7 +399,7 @@ async fn main() {
371399
// enemy bullet hits the player
372400
bullets.retain(|b| {
373401
if b.style == BulletStyle::Enemy && (b.pos - ship.pos).length() < 20.0 {
374-
if ship.take_damage(score) {
402+
if ship.take_damage(b.damage, score) {
375403
state = GameState::GameOver(score);
376404
}
377405
false // Remove bullet
@@ -382,8 +410,11 @@ async fn main() {
382410

383411
for i in (0..asteroids.len()).rev() {
384412
if (ship.pos - asteroids[i].pos).length() < asteroids[i].radius + 10.0 {
413+
// Calculate damage based on asteroid size
414+
// Bigger asteroids deal more damage
415+
let asteroid_damage = (asteroids[i].radius / 10.0) * BASE_ASTEROID_DAMAGE;
385416
asteroids.remove(i);
386-
if ship.take_damage(score) {
417+
if ship.take_damage(asteroid_damage, score) {
387418
state = GameState::GameOver(score);
388419
}
389420
// break;
@@ -431,21 +462,31 @@ async fn main() {
431462
draw_ship(&ship, &resources.ship_body, &resources.ship_flame);
432463

433464
draw_text(
434-
&format!("SCORE: {score} LIVES: {}", ship.lives),
465+
&format!(
466+
"SCORE: {score} HP: {:.0}/{:.0}",
467+
ship.health, ship.max_health
468+
),
435469
20.0,
436470
30.0,
437471
30.0,
438472
WHITE,
439473
);
440474

441475
let status = format!(
442-
"Kills: {}/{} Scrap: {}/{}",
476+
"Kills: {}/{} Rust: {}/{} Gold: {}/{}",
443477
mission_kills,
444478
current_mission.target_kills,
445479
mission_scrap_collected,
446-
current_mission.target_scrap
480+
current_mission.target_scrap,
481+
mission_rare_metal_collected,
482+
current_mission.target_rare_metal
447483
);
448484
draw_text(&status, 20.0, screen_height() - 30.0, 30.0, WHITE);
485+
486+
// Display total resources in inventory
487+
let inventory =
488+
format!("Resources: Rust {} | Gold {}", ship.scrap, ship.rare_metal);
489+
draw_text(&inventory, 20.0, screen_height() - 60.0, 25.0, GRAY);
449490
}
450491

451492
GameState::MissionSuccess => {
@@ -488,7 +529,8 @@ fn create_ship() -> Ship {
488529
pos: vec2(screen_width() / 2.0, screen_height() / 2.0),
489530
vel: vec2(0.0, 0.0),
490531
rotation: 0.0,
491-
lives: 10,
532+
health: 100.0,
533+
max_health: 100.0,
492534
shoot_timer: 0.0,
493535
rapid_fire_timer: 0.0,
494536
engine: Engine::basic(),

src/systems.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,10 @@ pub fn get_mission(level: u32) -> Mission {
5353
1 => Mission {
5454
level_id: 1,
5555
title: "Operation: Dust".to_string(),
56-
description: "Destroy 3 scouts and collect 1 scrap.".to_string(),
56+
description: "Destroy 3 scouts and collect resources.".to_string(),
5757
target_kills: 3,
5858
target_scrap: 1,
59+
target_rare_metal: 0,
5960
enemy_spawn_interval: 10.0, // enemies spawn rarely
6061
asteroid_count: 5,
6162
},
@@ -65,15 +66,17 @@ pub fn get_mission(level: u32) -> Mission {
6566
description: "Enemy activity rising. Kill 10 enemies.".to_string(),
6667
target_kills: 10,
6768
target_scrap: 0, // scrap is not important
69+
target_rare_metal: 0,
6870
enemy_spawn_interval: 2.0,
6971
asteroid_count: 8,
7072
},
7173
3 => Mission {
7274
level_id: 3,
7375
title: "Scrap Yard".to_string(),
74-
description: "Collect 20 scrap for upgrades.".to_string(),
76+
description: "Collect 20 rust piles and 3 gold for upgrades.".to_string(),
7577
target_kills: 5,
7678
target_scrap: 20,
79+
target_rare_metal: 3,
7780
enemy_spawn_interval: 2.5,
7881
asteroid_count: 12,
7982
},
@@ -83,7 +86,8 @@ pub fn get_mission(level: u32) -> Mission {
8386
title: format!("Deep Space sector {level}"),
8487
description: "Survive.".to_string(),
8588
target_kills: 10 + level,
86-
target_scrap: 10,
89+
target_scrap: 10 + (level / 2),
90+
target_rare_metal: 2 + (level / 3),
8791
enemy_spawn_interval: (1.5 - (level as f32 * 0.1)).max(0.5),
8892
asteroid_count: 10 + level as usize,
8993
},

0 commit comments

Comments
 (0)