package main import rl "vendor:raylib" import "vendor:raylib/rlgl" import "core:fmt" import "core:math" import "core:math/ease" import "core:math/rand" import "core:strings" import "core:strconv" // Virtual game field dimensions GameField := vec2{400, 200} Game :: struct { using state: GameState, health: u8, player: Player, camera: rl.Camera3D, camera_offset: vec3, score: u32, bullets: [dynamic]Bullet, snake_max_health: int, snake_health: int, music: rl.Music, tutorial_enabled: bool, tutorial_finished: bool, tutorial_timer: f32, tutorial_step: TutorSteps } TutorSteps :: enum { THRUST, LOOK, SHOOT, DODGE, DONE } TutorTextsMouse := [TutorSteps]cstring{ .THRUST = "Используйте W/Ц для ускорения", .LOOK = "Двигайте мышью для наведения", .SHOOT = "Левая кнопка мыши: стрельба", .DODGE = "Правая кнопка мыши: уклонение", .DONE = "Удачи!" } TutorTextsKeyboard := [TutorSteps]cstring{ .THRUST = "Используйте стрелку \"Вверх\" для ускорения", .LOOK = "Стрелки \"Влево\"/\"Вправо\" для наведения", .SHOOT = "Пробел: стрельба", .DODGE = "Shift: уклонение", .DONE = "Удачи!" } game_init :: proc(prev: ^GameState = nil) -> ^GameState { state := new(Game) state.previous = prev state.variant = state state.draw = game_draw state.update = game_update state.free = game_free // rlgl.DisableBackfaceCulling() game_setup(state) return state } game_setup :: proc(game: ^Game) { clear(&game.bullets) clear(&Segments) clear(&trails) clear(&explosions) explosions_init() trail_init() game.player = player_spawn({GameField.x / 2 + 70, 20, 0}) // rl.StopMusicStream(game.music) game.health = 100 game.snake_max_health = 0 snake_spawn({0, -10, 0}, math.PI * 3 / 2, 100) for segment in Segments { game.snake_max_health += int(segment.health) } game.camera = rl.Camera3D{ target = game.player.pos, position = game.player.pos + vec3backward * 50, fovy = 60, //offset = WSize/2, projection = rl.CameraProjection.PERSPECTIVE, up = vec3up } game.camera.target.x = clamp(game.camera.target.x, -GameField.x/2, GameField.x/2) game.camera.target.y = clamp(game.camera.target.y, 0, GameField.y) game.camera.position = game.camera.target + vec3backward * 50 game.tutorial_timer = 3 } game_gen_level :: proc(game: ^Game) { } game_update :: proc(state: ^GameState, delta: f32) { game := transmute(^Game)state using game tutorial_enabled = NeedTutorial && !tutorial_finished && player.intro_timer <= 0 if tutorial_enabled { tutorial_timer -= delta if tutorial_timer <= 0 { tutorial_timer = 3 switch tutorial_step { case .THRUST: tutorial_step = .LOOK case .LOOK: tutorial_step = .SHOOT case .SHOOT: tutorial_step = .DODGE case .DODGE: tutorial_step = .DONE; tutorial_timer = 2 case .DONE: tutorial_finished = true NeedTutorial = false SnakeActive = true rl.PlaySound(Res.Sfx.SnakeRoarBlast) } } } else { if player.intro_timer <= 0 && !SnakeActive { SnakeActive = true } } // if rl.IsMusicStreamPlaying(game.music) { rl.UpdateMusicStream(game.music) // } explosions_process(delta) if rl.IsKeyPressed(rl.KeyboardKey.ESCAPE) { pause := pause_init(game) stack_push(pause) } player_update(&player, game, delta) #reverse for &bullet, i in bullets { bullet_process(&bullet, game, delta) if !bullet.alive { unordered_remove(&bullets, i) } } snake_process(game, delta) target_offset := player.vel / 5 camera_offset = rl.Vector3MoveTowards(camera_offset, target_offset, rl.Vector3Length(target_offset - camera_offset) * 10 * delta) camera.target = player.pos + camera_offset camera.target.x = clamp(camera.target.x, -GameField.x/2, GameField.x/2) camera.target.y = clamp(camera.target.y, 0, GameField.y) camera.position = camera.target + vec3backward * 50 trail_update(delta) } game_draw :: proc(state: ^GameState) { rlgl.SetLineWidth(4) game := transmute(^Game)state using game rl.BeginMode3D(camera) rl.DrawModel(Res.Models.Background, {0, 0, 500}, 1000, rl.WHITE) yy : i32 = 0 snake_draw(game) player_draw(&player) for &bullet, i in bullets { bullet_draw(&bullet, game) if i != 0 { // rl.DrawLine3D(bullets[i].pos, bullets[i-1].pos, rl.WHITE) } } trail_draw() explosions_draw() rl.EndMode3D() if stack_top() == game { hb_text : cstring = "Jörmungandr" height := 30 * (WSize.y / 480) hb_health : f32 = f32(snake_health) / f32(snake_max_health) if snake_health == 0 { hb_health = f32(Head.health) / f32(Head.max_health) hb_text = "Jörmungandr's head" } // rl.DrawRectangleV({0, WSize.y - height - 10}, {WSize.x, height + 10}, rl.WHITE) // hb_width := hb_health * WSize.x // rl.DrawRectangleV({WSize.x / 2 - hb_width / 2, WSize.y - height - 7}, {hb_width, height + 4}, rl.RED) // draw_text_centered(Res.Fonts.Title, hb_text, {WSize.x / 2, WSize.y - height / 2}, height) if SnakeActive { draw_hbar(hb_text, hb_health, {0, WSize.y - height}, {WSize.x, height}, rl.RED) } draw_hbar("Þórr", f32(health) / 100.0, {0, 0}, {WSize.x, height}, rl.GREEN) reload_color := rl.SKYBLUE if player.reloading { reload_color = rl.RED } draw_hbar("", f32(player.power) / 100.0, {0, height}, {WSize.x, 10}, reload_color) if tutorial_enabled { texts := TutorTextsMouse if KeyboardOnly { texts = TutorTextsKeyboard } draw_text_centered(Res.Fonts.UI, texts[tutorial_step], WSize / 2 + vec2{0, 100}, 48, 1, rl.WHITE) } } } draw_hbar :: proc(text: cstring, value: f32, pos, size: vec2, color: rl.Color) { rl.DrawRectangleV(pos, size, rl.WHITE) hb_width := value * size.x rl.DrawRectangleV(pos + {size.x / 2 - hb_width / 2, 2}, {hb_width, size.y - 4}, color) draw_text_centered(Res.Fonts.Title, text, pos + size / 2, size.y / 1.3) } game_free :: proc(state: ^GameState) { game := transmute(^Game)state free(state) }