main.zig (7785B)
1 const raylib = @import("raylib"); 2 const raymath = @import("raymath"); 3 4 const Bullet = @import("./bullet.zig"); 5 const constants = @import("./constants.zig"); 6 const Grunt = @import("./enemy_types/grunt.zig"); 7 const Player = @import("./player.zig"); 8 const GameState = @import("./state.zig").GameState; 9 const Timer = @import("./timer.zig"); 10 const Button = @import("./ui.zig").Button; 11 const UpgradeId = @import("./upgrade.zig").UpgradeId; 12 const upgrade_meta = @import("./upgrade.zig").meta; 13 const upgrade_fields = @typeInfo(UpgradeId).@"enum".fields; 14 15 const start_button = Button.new( 16 .{ .x = 100, .y = 70 }, 17 .{ 18 .x = (constants.screen_width / 2) - 50, 19 .y = (constants.screen_height / 2) - 35, 20 }, 21 ); 22 23 pub fn main() !void { 24 var game_state: GameState = .init(); 25 26 raylib.InitWindow( 27 constants.screen_width, 28 constants.screen_height, 29 constants.screen_title, 30 ); 31 defer raylib.CloseWindow(); 32 33 raylib.SetTargetFPS(constants.fps); 34 while (!raylib.WindowShouldClose()) { 35 switch (game_state.screen) { 36 .title => { 37 raylib.BeginDrawing(); 38 defer raylib.EndDrawing(); 39 40 raylib.ClearBackground(raylib.RAYWHITE); 41 42 raylib.DrawRectangleV( 43 start_button.position, 44 start_button.size, 45 raylib.BLACK, 46 ); 47 48 const start_button_text = "start"; 49 const start_button_font_size: f32 = 18.0; 50 const start_button_text_width = raylib.MeasureText(start_button_text, start_button_font_size); 51 raylib.DrawText( 52 start_button_text, 53 @intFromFloat(start_button.position.x + (start_button.size.x - @as(f32, @floatFromInt(start_button_text_width))) / 2), 54 @intFromFloat(start_button.position.y + (start_button.size.y - start_button_font_size) / 2), 55 start_button_font_size, 56 raylib.WHITE, 57 ); 58 59 if (start_button.clicked(raylib.GetMousePosition())) { 60 game_state.reset(); 61 } 62 }, 63 .upgrade => { 64 raylib.BeginDrawing(); 65 defer raylib.EndDrawing(); 66 67 raylib.ClearBackground(raylib.RAYWHITE); 68 69 raylib.DrawText("upgrades", 20, 40, 30, raylib.BLACK); 70 raylib.DrawText( 71 raylib.TextFormat("points: %d", game_state.enemies_killed), 72 20, 73 90, 74 20, 75 raylib.BLACK, 76 ); 77 raylib.DrawText( 78 raylib.TextFormat("last run: %0.1fs", game_state.time), 79 20, 80 120, 81 18, 82 raylib.GRAY, 83 ); 84 85 const row_size: raylib.Vector2 = .{ .x = 700, .y = 60 }; 86 const row_spacing: f32 = 80; 87 var row_position: raylib.Vector2 = .{ .x = 50, .y = 200 }; 88 89 inline for (upgrade_fields) |field| { 90 const id: UpgradeId = @enumFromInt(field.value); 91 const meta = upgrade_meta[@intFromEnum(id)]; 92 93 const level = game_state.upgrades.level(id); 94 const cost = game_state.upgrades.cost(id); 95 const buyable = !game_state.upgrades.maxed(id) and game_state.enemies_killed >= cost; 96 97 const row = Button.new(row_size, row_position); 98 raylib.DrawRectangleV( 99 row.position, 100 row.size, 101 if (buyable) raylib.BLACK else raylib.GRAY, 102 ); 103 raylib.DrawText( 104 meta.name.ptr, 105 @intFromFloat(row.position.x + 20), 106 @intFromFloat(row.position.y + row.size.y / 2 - 10), 107 20, 108 raylib.WHITE, 109 ); 110 111 if (game_state.upgrades.maxed(id)) { 112 raylib.DrawText( 113 "MAX", 114 @intFromFloat(row.position.x + row.size.x - 120), 115 @intFromFloat(row.position.y + row.size.y / 2 - 10), 116 18, 117 raylib.WHITE, 118 ); 119 } else { 120 raylib.DrawText( 121 raylib.TextFormat("lvl %d/%d cost %d", level, meta.max_level, cost), 122 @intFromFloat(row.position.x + row.size.x - 300), 123 @intFromFloat(row.position.y + row.size.y / 2 - 10), 124 18, 125 raylib.WHITE, 126 ); 127 } 128 129 if (buyable and row.clicked(raylib.GetMousePosition())) { 130 game_state.enemies_killed -= cost; 131 _ = game_state.upgrades.upgrade(id); 132 } 133 134 row_position.y += row_spacing; 135 } 136 137 const continue_button = Button.new( 138 .{ .x = 300, .y = 60 }, 139 .{ 140 .x = (constants.screen_width / 2) - 150, 141 .y = row_position.y + 60, 142 }, 143 ); 144 raylib.DrawRectangleV( 145 continue_button.position, 146 continue_button.size, 147 raylib.BLACK, 148 ); 149 raylib.DrawText( 150 "continue", 151 @intFromFloat(continue_button.position.x + continue_button.size.x / 2 - 30), 152 @intFromFloat(continue_button.position.y + continue_button.size.y / 2 - 10), 153 20, 154 raylib.WHITE, 155 ); 156 157 if (continue_button.clicked(raylib.GetMousePosition())) { 158 game_state.reset(); 159 } 160 }, 161 .gameplay => { 162 // -- tick 163 for (0..game_state.enemy_count) |idx| { 164 game_state.enemies[idx].move(game_state.player.position); 165 } 166 game_state.player.handleMovement(); 167 game_state.tick(); 168 169 // -- render 170 raylib.BeginDrawing(); 171 defer raylib.EndDrawing(); 172 173 raylib.ClearBackground(raylib.RAYWHITE); 174 175 // Render entities 176 game_state.player.render(); 177 for (0..game_state.enemy_count) |idx| { 178 game_state.enemies[idx].render(); 179 } 180 181 game_state.orphaned_bullet_pool.render(.{ .x = constants.screen_width, .y = constants.screen_height }); 182 183 // Health overlay 184 const health_percent: f32 = @max( 185 0, 186 @min(1, game_state.player.health / game_state.player.max_health), 187 ); 188 const damage_percent = 1 - health_percent; 189 raylib.DrawRectangle( 190 0, 191 0, 192 constants.screen_width, 193 constants.screen_height, 194 raylib.ColorAlpha(raylib.RED, damage_percent), 195 ); 196 197 // Score 198 raylib.DrawText( 199 raylib.TextFormat("score: %0.0f", game_state.time), 200 10, 201 10, 202 18, 203 raylib.BLACK, 204 ); 205 }, 206 } 207 } 208 }