Final version. Fixed menus, music. Added tutorial and control schemes

This commit is contained in:
Vlad Rud 2024-10-06 10:59:07 +03:00
parent 061f255be4
commit 83ca097f8d
12 changed files with 215 additions and 41 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
sineus-final.exe sineus-final.exe
*.zip

BIN
Ragnarokkr.exe Normal file

Binary file not shown.

BIN
assets/gfx/crosshair.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 940 B

View File

@ -25,9 +25,35 @@ Game :: struct {
snake_max_health: int, snake_max_health: int,
snake_health: int, snake_health: int,
music: rl.Music, 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 { game_init :: proc(prev: ^GameState = nil) -> ^GameState {
state := new(Game) state := new(Game)
@ -61,9 +87,7 @@ game_setup :: proc(game: ^Game) {
game.health = 100 game.health = 100
game.snake_max_health = 0 game.snake_max_health = 0
snake_spawn({10, 10, 0}, math.PI, 70) snake_spawn({0, -10, 0}, math.PI * 3 / 2, 100)
for segment in Segments { for segment in Segments {
game.snake_max_health += int(segment.health) game.snake_max_health += int(segment.health)
} }
@ -79,6 +103,8 @@ game_setup :: proc(game: ^Game) {
game.camera.target.x = clamp(game.camera.target.x, -GameField.x/2, GameField.x/2) 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.target.y = clamp(game.camera.target.y, 0, GameField.y)
game.camera.position = game.camera.target + vec3backward * 50 game.camera.position = game.camera.target + vec3backward * 50
game.tutorial_timer = 3
} }
@ -89,6 +115,30 @@ game_gen_level :: proc(game: ^Game) {
game_update :: proc(state: ^GameState, delta: f32) { game_update :: proc(state: ^GameState, delta: f32) {
game := transmute(^Game)state game := transmute(^Game)state
using game 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) { // if rl.IsMusicStreamPlaying(game.music) {
rl.UpdateMusicStream(game.music) rl.UpdateMusicStream(game.music)
// } // }
@ -153,8 +203,24 @@ game_draw :: proc(state: ^GameState) {
// hb_width := hb_health * WSize.x // hb_width := hb_health * WSize.x
// rl.DrawRectangleV({WSize.x / 2 - hb_width / 2, WSize.y - height - 7}, {hb_width, height + 4}, rl.RED) // 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) // draw_text_centered(Res.Fonts.Title, hb_text, {WSize.x / 2, WSize.y - height / 2}, height)
draw_hbar(hb_text, hb_health, {0, WSize.y - height}, {WSize.x, height}, rl.RED) 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) 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)
}
} }
} }

View File

@ -64,7 +64,7 @@ gameover_draw :: proc(state: ^GameState) {
SubtitleFontSize :: 48 SubtitleFontSize :: 48
rl.DrawRectangleV(gameover.position - gameover.size / 2, gameover.size, rl.Color{90, 30, 150, 10}) // rl.DrawRectangleV(gameover.position - gameover.size / 2, gameover.size, rl.Color{90, 30, 150, 10})
draw_text_centered(Res.Fonts.Title, TitleText, gameover.position - {0, 100}, TitleFontSize, 1, rl.WHITE) draw_text_centered(Res.Fonts.Title, TitleText, gameover.position - {0, 100}, TitleFontSize, 1, rl.WHITE)
for c, i in SubtitleText { for c, i in SubtitleText {

View File

@ -19,6 +19,9 @@ WSizei := [2]i32{}
WindowShouldExit := false WindowShouldExit := false
NeedTutorial := true
KeyboardOnly := false
Overlay_Opacity : f32 = 0 Overlay_Opacity : f32 = 0
@ -58,7 +61,7 @@ Res : Resources
load_sfx :: proc(name: string, volume: f32 = 1) -> rl.Sound { load_sfx :: proc(name: string, volume: f32 = 1) -> rl.Sound {
p := filepath.join([]string{".\\assets\\sfx\\", name}) p := filepath.join([]string{"./assets/sfx/", name})
cstr := strings.clone_to_cstring(p) cstr := strings.clone_to_cstring(p)
snd := rl.LoadSound(cstr) snd := rl.LoadSound(cstr)
rl.SetSoundVolume(snd, volume) rl.SetSoundVolume(snd, volume)
@ -66,7 +69,7 @@ load_sfx :: proc(name: string, volume: f32 = 1) -> rl.Sound {
} }
load_music :: proc(name: string, volume: f32 = 1) -> rl.Music { load_music :: proc(name: string, volume: f32 = 1) -> rl.Music {
p := filepath.join([]string{".\\assets\\music\\", name}) p := filepath.join([]string{"./assets/music/", name})
cstr := strings.clone_to_cstring(p) cstr := strings.clone_to_cstring(p)
snd := rl.LoadMusicStream(cstr) snd := rl.LoadMusicStream(cstr)
rl.SetMusicVolume(snd, volume) rl.SetMusicVolume(snd, volume)
@ -81,14 +84,15 @@ change_track :: proc(music: rl.Music) {
rl.PlayMusicStream(current_music) rl.PlayMusicStream(current_music)
} }
Cursor : rl.Texture
load_resources :: proc() { load_resources :: proc() {
Res.Fonts.Title = rl.LoadFontEx(".\\assets\\fonts\\norse.otf", 96*2, nil, 2048) Res.Fonts.Title = rl.LoadFontEx("./assets/fonts/norse.otf", 96*2, nil, 2048)
Res.Fonts.UI = rl.LoadFontEx(".\\assets\\fonts\\PTSerif-Regular.ttf", 96, nil, 2048) Res.Fonts.UI = rl.LoadFontEx("./assets/fonts/PTSerif-Regular.ttf", 96, nil, 2048)
Res.Models.PlayerModel = rl.LoadModel(".\\assets\\models\\chariot.glb") Res.Models.PlayerModel = rl.LoadModel("./assets/models/chariot.glb")
Res.Models.SnakeHeadTop = rl.LoadModel(".\\assets\\models\\snake_head_top.obj") Res.Models.SnakeHeadTop = rl.LoadModel("./assets/models/snake_head_top.obj")
Res.Models.SnakeHeadJaw = rl.LoadModel(".\\assets\\models\\snake_jaw.obj") Res.Models.SnakeHeadJaw = rl.LoadModel("./assets/models/snake_jaw.obj")
Res.Models.SnakeBody = rl.LoadModel(".\\assets\\models\\snake_body.obj") Res.Models.SnakeBody = rl.LoadModel("./assets/models/snake_body.obj")
Res.Sfx.Drums = load_sfx("drums.ogg") Res.Sfx.Drums = load_sfx("drums.ogg")
Res.Sfx.Lightning = load_sfx("lightning.ogg", 0.5) Res.Sfx.Lightning = load_sfx("lightning.ogg", 0.5)
@ -107,11 +111,17 @@ load_resources :: proc() {
Res.Music.Second = load_music("alexander-nakarada-the-northern-path.mp3", 0.7) Res.Music.Second = load_music("alexander-nakarada-the-northern-path.mp3", 0.7)
} }
Fullscreen := true
main :: proc() { main :: proc() {
rl.SetConfigFlags(rl.ConfigFlags{.MSAA_4X_HINT, .WINDOW_MAXIMIZED, .WINDOW_RESIZABLE}) rl.SetConfigFlags(rl.ConfigFlags{.MSAA_4X_HINT, .FULLSCREEN_MODE, .VSYNC_HINT, .WINDOW_RESIZABLE})
rl.InitWindow(800, 480, "Ragnarøkkr") rl.SetWindowMinSize(800, 480)
rl.InitWindow(0, 0, "Ragnarøkkr")
rl.InitAudioDevice() rl.InitAudioDevice()
rl.HideCursor()
Cursor = rl.LoadTexture("./assets/gfx/crosshair.png")
load_resources() load_resources()
WSizei = {rl.GetScreenWidth(), rl.GetScreenHeight()} WSizei = {rl.GetScreenWidth(), rl.GetScreenHeight()}
@ -141,6 +151,8 @@ main :: proc() {
state->draw() state->draw()
rl.DrawRectangleV({}, WSize, rl.Color{0, 0, 0, u8(Overlay_Opacity * 255)}) rl.DrawRectangleV({}, WSize, rl.Color{0, 0, 0, u8(Overlay_Opacity * 255)})
pos := rl.GetMousePosition()
rl.DrawTextureEx(Cursor, pos - {16, 16} * 3, 0, 3, rl.WHITE)
rl.EndDrawing() rl.EndDrawing()
} }
} }

View File

@ -6,16 +6,28 @@ import "core:math/ease"
Menu_Buttons :: enum { Menu_Buttons :: enum {
START, START,
HOW_TO_PLAY, TUTORIAL,
KEYBOARD_ONLY,
FULLSCREEN,
EXIT EXIT
} }
menu_strings := [Menu_Buttons]cstring { menu_strings := [Menu_Buttons]cstring {
.START = "Старт", .START = "Старт",
.HOW_TO_PLAY = "Как играть?", .TUTORIAL = "Обучение",
.KEYBOARD_ONLY = "Только клавиатура",
.FULLSCREEN = "Полный экран",
.EXIT = "Выход" .EXIT = "Выход"
} }
menu_items := [Menu_Buttons]MenuItem {
.START = {text = menu_strings[.START]},
.TUTORIAL = {text = menu_strings[.TUTORIAL], param = &NeedTutorial, type = MenuItemType.BOOL},
.KEYBOARD_ONLY = {text = menu_strings[.KEYBOARD_ONLY], param = &KeyboardOnly, type = MenuItemType.BOOL},
.FULLSCREEN = {text = menu_strings[.FULLSCREEN], param = &Fullscreen, type = MenuItemType.BOOL},
.EXIT = {text = menu_strings[.EXIT]}
}
Menu :: struct { Menu :: struct {
using state: GameState, using state: GameState,
@ -31,9 +43,9 @@ menu_init :: proc(prev: ^GameState = nil) -> ^GameState {
position = {100, WSize.y / 2}, position = {100, WSize.y / 2},
line_size = 60, line_size = 60,
font_size = 48, font_size = 48,
elements = &menu_strings, elements = &menu_items,
menu_pressed = menu_button_pressed, menu_pressed = menu_button_pressed,
background = rl.Color{50, 10, 110, 10} background = rl.Color{50, 10, 110, 0}
} }
state.update = menu_update state.update = menu_update
state.draw = menu_draw state.draw = menu_draw
@ -56,9 +68,15 @@ menu_button_pressed :: proc(state: ^GameState, el: Menu_Buttons) {
game := transmute(^Game)state.previous game := transmute(^Game)state.previous
change_track(Res.Music.First) change_track(Res.Music.First)
stack_pop() stack_pop()
case .HOW_TO_PLAY: case .TUTORIAL:
// howtoplay := howtoplay_init(state) NeedTutorial = !NeedTutorial
// stack_push(howtoplay) case .KEYBOARD_ONLY:
KeyboardOnly = !KeyboardOnly
NeedTutorial = true
case .FULLSCREEN:
rl.ToggleFullscreen()
Fullscreen = rl.IsWindowFullscreen()
case .EXIT: case .EXIT:
WindowShouldExit = true WindowShouldExit = true
return return

View File

@ -4,6 +4,23 @@ import rl "vendor:raylib"
import "core:math/ease" import "core:math/ease"
import "core:fmt" import "core:fmt"
MenuItemType :: enum {
NONE,
BOOL,
INT,
}
BoolStrings := map[bool]cstring {
true = "вкл",
false = "выкл"
}
MenuItem :: struct{
text: cstring,
param: rawptr,
type: MenuItemType,
}
MenuList :: struct($T: typeid) { MenuList :: struct($T: typeid) {
state: ^GameState, state: ^GameState,
position: vec2, position: vec2,
@ -12,7 +29,7 @@ MenuList :: struct($T: typeid) {
active_element: T, active_element: T,
active_marker: vec2, active_marker: vec2,
tween: ^Tween, tween: ^Tween,
elements: ^[T]cstring, elements: ^[T]MenuItem,
menu_pressed: proc(state: ^GameState, element: T), menu_pressed: proc(state: ^GameState, element: T),
background: rl.Color, background: rl.Color,
mouse_pos: vec2, mouse_pos: vec2,
@ -80,7 +97,13 @@ menu_list_draw :: proc(list: ^MenuList($T)) {
rl.DrawTextEx(Res.Fonts.UI, ">", list.position + list.active_marker + {-30, 0}, 48, 2, rl.WHITE) rl.DrawTextEx(Res.Fonts.UI, ">", list.position + list.active_marker + {-30, 0}, 48, 2, rl.WHITE)
for el, i in list.elements { for el, i in list.elements {
pos := list.position + {0, f32(i) * list.line_size} pos := list.position + {0, f32(i) * list.line_size}
rl.DrawTextEx(Res.Fonts.UI, el, pos, list.font_size, 2, rl.WHITE) text := el.text
if el.type == .BOOL {
param := transmute(^bool)el.param
value := param^
text = rl.TextFormat("%s: %s", el.text, BoolStrings[value])
}
rl.DrawTextEx(Res.Fonts.UI, text, pos, list.font_size, 2, rl.WHITE)
} }
} }
@ -88,7 +111,7 @@ menu_list_draw :: proc(list: ^MenuList($T)) {
menu_list_get_size :: proc(list: ^MenuList($T)) -> vec2 { menu_list_get_size :: proc(list: ^MenuList($T)) -> vec2 {
size := vec2{} size := vec2{}
for el, i in list.elements { for el, i in list.elements {
line_size := rl.MeasureTextEx(Res.Fonts.UI, el, list.font_size, 2) line_size := rl.MeasureTextEx(Res.Fonts.UI, el.text, list.font_size, 2)
if line_size.x > size.x { if line_size.x > size.x {
size.x = line_size.x size.x = line_size.x
} }

View File

@ -14,6 +14,11 @@ pause_strings := [Pause_Buttons]cstring {
.EXIT = "Прервать игру" .EXIT = "Прервать игру"
} }
pause_items := [Pause_Buttons]MenuItem {
.CONTINUE = {text = pause_strings[.CONTINUE]},
.EXIT = {text = pause_strings[.EXIT]}
}
Pause :: struct { Pause :: struct {
using state: GameState, using state: GameState,
@ -30,9 +35,9 @@ pause_init :: proc(prev: ^GameState = nil) -> ^GameState {
position = {-300, WSize.y / 2}, position = {-300, WSize.y / 2},
line_size = 60, line_size = 60,
font_size = 48, font_size = 48,
elements = &pause_strings, elements = &pause_items,
menu_pressed = pause_button_pressed, menu_pressed = pause_button_pressed,
background = rl.Color{50, 10, 110, 10} background = rl.Color{50, 10, 110, 0}
} }
state.update = pause_update state.update = pause_update
state.draw = pause_draw state.draw = pause_draw

View File

@ -29,6 +29,9 @@ Player :: struct {
is_invulnerable: bool, is_invulnerable: bool,
is_dead: bool, is_dead: bool,
intro_timer: f32, intro_timer: f32,
power: f32,
reload_timer: f32,
reloading: bool,
// animation: rl.ModelAnimation, // animation: rl.ModelAnimation,
// animTime: f32, // animTime: f32,
// animFrame: i32, // animFrame: i32,
@ -47,6 +50,7 @@ player_spawn :: proc(position: vec3) -> Player {
can_dodge = true, can_dodge = true,
can_shoot = true, can_shoot = true,
intro_timer = 2, intro_timer = 2,
power = 100,
} }
} }
@ -82,16 +86,26 @@ player_update :: proc(player: ^Player, game: ^Game, delta: f32) {
if !is_dodging { if !is_dodging {
dir_vector : vec3 dir_vector : vec3
if intro_timer <= 0 { if intro_timer <= 0 {
dir = angle_rotate(dir, mouse_angle, math.PI * 2 * delta) thrust_key := rl.KeyboardKey.W
dir_vector = get_vec_from_angle(dir) if !KeyboardOnly {
dir = angle_rotate(dir, mouse_angle, math.PI * 2 * delta)
dir_vector = get_vec_from_angle(dir)
} else {
thrust_key = rl.KeyboardKey.UP
if rl.IsKeyDown(rl.KeyboardKey.LEFT) {
dir += math.PI * 2 * delta
}
if rl.IsKeyDown(rl.KeyboardKey.RIGHT) {
dir -= math.PI * 2 * delta
}
dir_vector = get_vec_from_angle(dir)
}
thrust = 0 thrust = 0
if rl.IsKeyDown(thrust_key) {
if rl.IsKeyDown(rl.KeyboardKey.W) { thrust = 110
thrust = 70
} }
} else { } else {
thrust = 70 thrust = 110
dir_vector = vec3left dir_vector = vec3left
dir = math.atan2(-dir_vector.y, dir_vector.x) dir = math.atan2(-dir_vector.y, dir_vector.x)
} }
@ -115,7 +129,17 @@ player_update :: proc(player: ^Player, game: ^Game, delta: f32) {
} }
} }
if rl.IsMouseButtonPressed(rl.MouseButton.RIGHT) && can_dodge && intro_timer <= 0 { dodge := false
shoot := false
if KeyboardOnly {
dodge = rl.IsKeyPressed(rl.KeyboardKey.LEFT_SHIFT)
shoot = rl.IsKeyDown(rl.KeyboardKey.SPACE)
} else {
dodge = rl.IsMouseButtonPressed(rl.MouseButton.RIGHT)
shoot = rl.IsMouseButtonDown(rl.MouseButton.LEFT)
}
if dodge && can_dodge && intro_timer <= 0 {
is_dodging = true is_dodging = true
can_dodge = false can_dodge = false
rl.StopSound(Res.Sfx.Rocket) rl.StopSound(Res.Sfx.Rocket)
@ -136,7 +160,7 @@ player_update :: proc(player: ^Player, game: ^Game, delta: f32) {
// player.animTime += delta // player.animTime += delta
// player.animFrame = i32(player.animTime * 60) % player.animation.frameCount // player.animFrame = i32(player.animTime * 60) % player.animation.frameCount
// rl.UpdateModelAnimation(PlayerModel, player.animation, player.animFrame) // rl.UpdateModelAnimation(PlayerModel, player.animation, player.animFrame)
shooting := rl.IsMouseButtonDown(rl.MouseButton.LEFT) && !is_dodging && intro_timer <= 0 shooting := shoot && !is_dodging && intro_timer <= 0 && !reloading
if shooting { if shooting {
if !rl.IsSoundPlaying(Res.Sfx.Lightning) { if !rl.IsSoundPlaying(Res.Sfx.Lightning) {
rl.PlaySound(Res.Sfx.Lightning) rl.PlaySound(Res.Sfx.Lightning)
@ -150,10 +174,26 @@ player_update :: proc(player: ^Player, game: ^Game, delta: f32) {
player := transmute(^Player)data player := transmute(^Player)data
player.can_shoot = true player.can_shoot = true
}) })
player.power -= 2
player.reload_timer = 1
if player.power <= 0 {
player.reloading = true
player.power = 0
}
} }
} else { } else {
rl.StopSound(Res.Sfx.Lightning) rl.StopSound(Res.Sfx.Lightning)
} }
if !shooting && can_shoot {
reload_timer -= delta
if reload_timer <= 0 {
player.power += 20 * delta
if player.power > 100 {
player.power = 100
player.reloading = false
}
}
}
} }
got_hit := false got_hit := false

View File

@ -52,19 +52,22 @@ SnakeSegment :: struct {
prev: ^SnakeSegment prev: ^SnakeSegment
} }
SnakeActive := false
snake_spawn :: proc(pos: vec3, dir: f32, length: int){ snake_spawn :: proc(pos: vec3, dir: f32, length: int){
dir_vec := rl.Vector3RotateByAxisAngle(vec3right, vec3backward, dir) // dir_vec := rl.Vector3RotateByAxisAngle(vec3right, vec3backward, dir)
dir_vec := vec3down
direction := math.atan2(-dir_vec.y, dir_vec.x)
SnakeActive = false
Head = SnakeHead{ Head = SnakeHead{
pos = pos, pos = pos,
dir = dir, dir = dir,
radius = 3, radius = 3,
state = .Chasing, state = .Diving,
vel = dir_vec * 20, vel = dir_vec * 20,
health = 100, health = 100,
max_health = 100, max_health = 100,
state_timer = 30, state_timer = 15,
} }
for i := 0; i < length; i += 1 { for i := 0; i < length; i += 1 {
@ -93,6 +96,9 @@ snake_clear :: proc() {
} }
snake_process :: proc(game: ^Game, delta: f32) { snake_process :: proc(game: ^Game, delta: f32) {
if !SnakeActive || Head.is_dead{
return
}
// Head.state = .Shot // Head.state = .Shot
// Head.state_timer = 200 // Head.state_timer = 200
switch Head.state { switch Head.state {
@ -124,6 +130,7 @@ snake_process :: proc(game: ^Game, delta: f32) {
if Head.health <= 0 && !Head.is_dead { if Head.health <= 0 && !Head.is_dead {
Head.is_dead = true Head.is_dead = true
explode(Head.pos, 9, 0.9, rl.YELLOW) explode(Head.pos, 9, 0.9, rl.YELLOW)
rl.StopMusicStream(current_music)
rl.PlaySound(Res.Sfx.PlayerDead) rl.PlaySound(Res.Sfx.PlayerDead)
timer_start(3, game, proc(data: rawptr) { timer_start(3, game, proc(data: rawptr) {
state := transmute(^Game)data state := transmute(^Game)data
@ -274,7 +281,9 @@ snake_shot :: proc(game: ^Game, delta: f32) {
snake_draw :: proc(game: ^Game) { snake_draw :: proc(game: ^Game) {
if !SnakeActive || Head.is_dead {
return
}
dir_vector := get_vec_from_angle(Head.dir) dir_vector := get_vec_from_angle(Head.dir)
roll := -math.PI / 2 + math.cos(Head.dir) * math.PI / 2 roll := -math.PI / 2 + math.cos(Head.dir) * math.PI / 2
rlgl.PushMatrix() rlgl.PushMatrix()