From 19bff3176d8b9b5098383b9c4211a1ee2c33c3eb Mon Sep 17 00:00:00 2001 From: Stefan Stefanov Date: Sat, 10 Feb 2024 14:59:13 +0200 Subject: [PATCH] working game loop, lacking alien shooting --- assets/texture_atlas.png | Bin 688 -> 690 bytes game.odin | 236 +++++++++++++++++++++++++++------------ main.odin | 24 +++- screens.odin | 10 +- 4 files changed, 194 insertions(+), 76 deletions(-) diff --git a/assets/texture_atlas.png b/assets/texture_atlas.png index a8b53c73e584001cbc01a274ba3d3aa3620668e5..e554b3c43967b8ad4f344d7465b3be65a5edbd67 100644 GIT binary patch literal 690 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSjKx9jP7LeL$-HD>V9N1yaSW-L z^Y)fw&>;r_hl`oryzUoepBJC~`b1|<%rq{a@1GK+q-1ni z@PEH0RBWYk%`34*$BUT`NHWavXDskzuwZWZtjTbWli|6q!W>}+zWw%>|H~ibWH9$; zxUIAEY|&S*U0e)asINs#~c6c?HjLuopp!lf!SK|U)psi%K!K^=T3Xm7rkz$ z+xf>V4cUGT(O1k%uHU&+dfkF^nz?1eKM#5C1@&tMbx!Ltywhp8F@49Ej_X$SnXd6s zSMUC;a@z50`g4|s?stXjqwgm7*Z+O}YGuItmw&4pqvb^BpFGdhQuZyZAX_NLSn-u@ z-HVEM@lM}??)Q!B{bKv&D$rwW%r{a`&YgdI{VZXIb&?14yyX@hQS`E4y>^gTe~DWM4fc47bU literal 688 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSjKx9jP7LeL$-HD>V9N4zaSW-L z^Y)f+&>;r_hlhFHyzUoepBKOV`b1|<%rfY z#x7RS<3#k?jQHb{?Bg{-<8^`Z)ezX_XhW0QTwOb^~(1o{|4z-Oin_*A)t_$uDI1JJC+u;eV8J#Pn!}JFW~r1uegHn3vUM zxbm-k^{&QRG3M`Upv9@*cU)ilZs+m+|4QG6WPJbfZ|?!!{;v9y=b2i{J}!T-(X-%e zOUCyfD<8Z67uYplm?7_D){^%N&jWp6E_q<#BEGu3{h59YJ1#QtiI$!@k?dy0y6rS4 zL;1(d2RE|bpOdfcyDy^7a3_-C?W_Fos_Hj?4{QT^vP|%>_Wu2sZhyYVpVJO9)A--> z+b;v(yJ{CO9eAtM_Wjew>V}osUuxzs6x^>>Ih=aX?{6P42{L%P`njxgN@xNACaeug diff --git a/game.odin b/game.odin index 86a7941..32fa3b0 100644 --- a/game.odin +++ b/game.odin @@ -28,6 +28,14 @@ SHIP_TO :: glm.vec2{0, 112} BULLET_TO := [2]glm.vec2{{0, 80}, {16, 80}} BULLET_FRAME_ANIM := 0 +ShuffleDirection :: enum { + RIGHT, + LEFT, + DOWN, +} +STEP_MULTIPLIER_DEFAULT :: 10.0 +step_multiplier := STEP_MULTIPLIER_DEFAULT + AlienKind :: enum { ORANGE, GREEN, @@ -50,16 +58,12 @@ Alien :: struct { } Bullet :: struct { - alive: bool, - position: glm.vec2, + alive: bool, + player_bullet: bool, + position: glm.vec2, } -collideAABB :: proc( - a_rect: glm.vec2, - a_pos: ^glm.vec2, - b_rect: glm.vec2, - b_pos: ^glm.vec2, -) -> bool { +collideAABB :: proc(a_rect: glm.vec2, a_pos: glm.vec2, b_rect: glm.vec2, b_pos: glm.vec2) -> bool { return( a_pos.x < b_pos.x + f32(b_rect.x / 2) && a_pos.x + f32(a_rect.x / 2) > b_pos.x && @@ -70,7 +74,15 @@ collideAABB :: proc( setup_game :: proc(state: ^GameState) { using state - player_pos = glm.vec2{f32(screen_width / 2), f32(screen_height) - PLAYER_RECT.x} + + player_pos = glm.vec2 { + f32(screen_width) / (2 * camera.zoom), + f32(screen_height) / camera.zoom - PLAYER_RECT.x, + } + player_score = 0 + player_health = 3 + + step_multiplier = STEP_MULTIPLIER_DEFAULT // setup the initial positions of the aliens for row in 0 ..< ALIEN_ROWS { @@ -81,14 +93,13 @@ setup_game :: proc(state: ^GameState) { f32((row + 1) * int(ALIEN_RECT.x + 10)), } alien_ptr.alive = true + alien_ptr.id = row % 2 == 0 ? .ORANGE : .GREEN } } - // aliens[ALIENS - 1].alive = true if player_score > player_high_score { player_high_score = player_score } - player_score = 0 reset_game = false } @@ -96,72 +107,155 @@ setup_game :: proc(state: ^GameState) { update_game :: proc(state: ^GameState) { using state - // If we're entering the game screen then we need to setup initial state of the game variables - if last_frame_screen != .GAMEPLAY || reset_game { - setup_game(state) - log.info("Done setting up game") + // Poll for keyboard commands (input) + { + // If we're entering the game screen then we need to setup initial state of the game variables + if last_frame_screen != .GAMEPLAY || reset_game { + setup_game(state) + log.info("Done setting up game") + } + + // update bullet frame idx + if frame_counter % 10 == 0 {BULLET_FRAME_ANIM = (BULLET_FRAME_ANIM + 1) % len(BULLET_TO)} + + // Press enter to change to ENDING screen + if (rl.IsKeyPressed(rl.KeyboardKey.ENTER)) { + state.screen = .ENDING + log.info("Updated screen enum", state.screen) + } + + // Press space to change to fire + if (rl.IsKeyPressed(rl.KeyboardKey.SPACE)) { + log.info("FIRE!") + bullet := &bullets[bullet_index];bullet_index = (bullet_index + 1) % MAX_BULLETS + bullet.alive = true + bullet.player_bullet = true + bullet.position = player_pos + bullet.position.y = bullet.position.y - ((PLAYER_RECT.y / 2) + BULLET_RECT.y / 2.0) + } + + if (rl.IsKeyDown(rl.KeyboardKey.RIGHT)) { + player_pos.x = player_pos.x + f32(PLAYER_SPEED * delta_time) + } + if (rl.IsKeyDown(rl.KeyboardKey.LEFT)) { + player_pos.x = player_pos.x - f32(PLAYER_SPEED * delta_time) + } + } - // update bullet frame idx - if frame_counter % 10 == 0 { BULLET_FRAME_ANIM = (BULLET_FRAME_ANIM + 1) % len(BULLET_TO) } + // Check ending scenarios + { + game_over := false + if player_health <= 0 { + game_over = true + game_end = .PlayerDied + } - // Press enter to change to ENDING screen - if (rl.IsKeyPressed(rl.KeyboardKey.ENTER)) { - state.screen = .ENDING - log.info("Updated screen enum", state.screen) - } - - // Press space to change to fire - if (rl.IsKeyPressed(rl.KeyboardKey.SPACE)) { - log.info("FIRE!") - bullet := &bullets[bullet_index];bullet_index = (bullet_index + 1) % MAX_BULLETS - bullet.alive = true - bullet.position = player_pos - bullet.position.y = bullet.position.y - ((PLAYER_RECT.y / 2) + BULLET_RECT.y / 2.0) - } - - if (rl.IsKeyDown(rl.KeyboardKey.RIGHT)) { - player_pos.x = player_pos.x + f32(PLAYER_SPEED * delta_time) - } - if (rl.IsKeyDown(rl.KeyboardKey.LEFT)) { - player_pos.x = player_pos.x - f32(PLAYER_SPEED * delta_time) - } - - all_aliens_dead := true - for &alien in aliens { - if !alien.alive {continue} - all_aliens_dead = false - } - - if all_aliens_dead { - screen = .ENDING - } - - // movement update - for &bullet in bullets { - if !bullet.alive {continue} - - bullet.position.y = bullet.position.y - f32(BULLET_SPEED * delta_time) - } - - for &bullet in bullets { - if !bullet.alive {continue} + all_aliens_dead := true for &alien in aliens { - if !alien.alive {continue} - if collideAABB(ALIEN_RECT, &alien.position, BULLET_RECT, &bullet.position) { - bullet.alive = false - alien.alive = false - player_score += 10 - log.info("HIT 'EM HARD") + if alien.alive { + all_aliens_dead = false + if alien.position.y + ALIEN_RECT.y >= player_pos.y { + game_end = .AliensReachedPlayer + game_over = true + break + } } } + if all_aliens_dead && !game_over { + game_over = true + game_end = .AllAliensKilled + } + + if game_over { + screen = .ENDING + return + } } + // Update bullets & aliens + { + for &bullet, bi in bullets { + if !bullet.alive {continue} + // Update bullet pos first + bullet.position.y -= f32(BULLET_SPEED * delta_time) + } + corner_alien_pos := + shuffle_dir == .RIGHT ? aliens[ALIENS_PER_ROW - 1].position : aliens[0].position + if corner_alien_pos.x <= SPRITE_CELL || + corner_alien_pos.x >= f32(screen_width / GLOBAL_SPRITE_SCALE) - SPRITE_CELL || + shuffle_dir == .DOWN { + switch shuffle_dir { + case .RIGHT: + fallthrough + case .LEFT: + { + last_shuffle_dir = shuffle_dir + shuffle_dir = .DOWN + } + case .DOWN: + { + step_multiplier += 1.0 + shuffle_dir = last_shuffle_dir == .RIGHT ? .LEFT : .RIGHT + last_shuffle_dir = .DOWN + } + } + } + shuffle_step_size: f64 = f64(SPRITE_CELL / 2) * delta_time * step_multiplier + alien_vel: f64 = shuffle_dir == .RIGHT ? shuffle_step_size : -shuffle_step_size + for &alien, ai in aliens { + // Update alien pos first + if shuffle_dir != .DOWN { + alien.position.x += f32(alien_vel) + } else { + alien.position.y += ALIEN_RECT.y + } + + // We will update the positions regardless if it's dead or not + // but only check collisions if the alien is alive + if !alien.alive {continue} + + for &bullet, bi in bullets { + if !bullet.alive {continue} + // Collision check bullet + if collideAABB(ALIEN_RECT, alien.position, BULLET_RECT, bullet.position) { + bullet, alien = damage_alien(state, bullets[bi], aliens[ai]) + } + } + } + + } } -// ship_sprite_cell_offset := rl.Rectangle{SHIP_TO.x, SHIP_TO.y, SPRITE_CELL, SPRITE_CELL} -// bullet_sprite_cell_offset := rl.Rectangle{BULLET_TO.x, BULLET_TO.y, SPRITE_CELL, SPRITE_CELL} +// Since I'm using a #soa array I can't directly modify the alien & bullet entities... +damage_alien :: proc( + state: ^GameState, + bullet_in: Bullet, + alien_in: Alien, +) -> ( + bullet: Bullet, + alien: Alien, +) { + using state + bullet = bullet_in + alien = alien_in + + bullet.alive = false + alien.alive = false + // Count score or take player health depending whose bullet it is + player_score += 10 + log.info("HIT 'EM HARD") + return +} + +damage_player :: proc(state: ^GameState, bullet_in: Bullet) -> (bullet: Bullet) { + using state + bullet = bullet_in + bullet.alive = false + player_health -= 1 + return +} draw_game :: proc(state: ^GameState) { using state @@ -199,7 +293,12 @@ draw_game :: proc(state: ^GameState) { if !bullet.alive {continue} rl.DrawTexturePro( texture_atlas, - {BULLET_TO[BULLET_FRAME_ANIM].x, BULLET_TO[BULLET_FRAME_ANIM].y, SPRITE_CELL, SPRITE_CELL}, + { + BULLET_TO[BULLET_FRAME_ANIM].x, + BULLET_TO[BULLET_FRAME_ANIM].y, + SPRITE_CELL, + SPRITE_CELL, + }, {bullet.position.x, bullet.position.y, BULLET_RECT.x, BULLET_RECT.y}, {SPRITE_CELL, SPRITE_CELL}, 0, @@ -238,7 +337,6 @@ draw_game :: proc(state: ^GameState) { ) } - rl.DrawText(rl.TextFormat("Score: %d", player_score), 130, 220, 20, rl.MAROON) + rl.DrawText(rl.TextFormat("Score: %d", player_score), 20, 0, 20, rl.WHITE) // rl.DrawText("GAMEPLAY SCREEN", 20, 20, 40, rl.MAROON) } - diff --git a/main.odin b/main.odin index 0d9c7fd..750c7fd 100644 --- a/main.odin +++ b/main.odin @@ -11,8 +11,13 @@ import rl "vendor:raylib" TEXTURE_ATLAS_PATH :: "./assets/texture_atlas.png" -DEBUG_MODE :: true +DEBUG_MODE :: false +GameEndType :: enum { + AllAliensKilled, + PlayerDied, + AliensReachedPlayer, +} GameState :: struct { // window @@ -29,6 +34,7 @@ GameState :: struct { screen: GameScreen, previous_screen: GameScreen, last_frame_screen: GameScreen, + game_end: GameEndType, reset_game: bool, aliens: #soa[ALIENS]Alien, bullets: #soa[MAX_BULLETS]Bullet, @@ -37,17 +43,23 @@ GameState :: struct { player_health: c.int, player_score: c.int, player_high_score: c.int, + shuffle_dir: ShuffleDirection, + last_shuffle_dir: ShuffleDirection, } state: GameState -texture_atlas_image : rl.Image -texture_atlas : rl.Texture2D +texture_atlas_image: rl.Image +texture_atlas: rl.Texture2D + +camera := rl.Camera2D { + zoom = 2, +} setup :: proc(state: ^GameState) { using state target_fps = 60 - screen_width = 800 - screen_height = 600 + screen_width = 800 * 2 + screen_height = 600 * 2 title = "Space Invaders (raylib+odin-lang edition)" current_frame_time = rl.GetTime() @@ -72,7 +84,9 @@ update :: proc(state: ^GameState) { draw :: proc(state: ^GameState) { rl.BeginDrawing() rl.ClearBackground(rl.RAYWHITE) + rl.BeginMode2D(camera) draw_screen(state) + rl.EndMode2D() rl.EndDrawing() } diff --git a/screens.odin b/screens.odin index 09faa81..40e8bc5 100644 --- a/screens.odin +++ b/screens.odin @@ -92,9 +92,15 @@ draw_screen :: proc(state: ^GameState) { } draw_ending_screen :: proc(state: ^GameState) { -using state + using state // TODO: Draw ENDING screen here! rl.DrawRectangle(0, 0, state.screen_width, state.screen_height, rl.BLACK) rl.DrawText("Game End", 20, 20, 40, rl.WHITE) - rl.DrawText(rl.TextFormat("Player score: %d\nHighscore: %d", player_score, player_high_score), 120, 220, 20, rl.WHITE) + rl.DrawText( + rl.TextFormat("Player score: %d\nHighscore: %d", player_score, player_high_score), + 120, + 220, + 20, + rl.WHITE, + ) }