Compare commits

..

5 Commits

Author SHA1 Message Date
Nefrace f72ac633ee Added NTween as submodule 2024-11-04 17:34:48 +03:00
Nefrace 4df0bcd5ff removed ntween from directory 2024-11-04 17:34:19 +03:00
Nefrace b251553275 Moved tweens to NTween 2024-11-04 17:28:34 +03:00
Vlad Rud 8a9e20f370 Fixed resizing issues on Windows 2024-10-13 19:23:20 +03:00
Nefrace 4f0b5dace4 Fixed issues with fullscreen. Replaced it with borderless window 2024-10-13 19:17:47 +03:00
12 changed files with 571 additions and 522 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "ntween"]
path = ntween
url = https://git.nefrace.ru/nefrace/ntween.git

Binary file not shown.

Binary file not shown.

View File

@ -1,14 +1,15 @@
package main package main
import rl "vendor:raylib"
import "core:math/ease"
import "core:fmt" import "core:fmt"
import "core:math/ease"
import "ntween"
import rl "vendor:raylib"
GameOver :: struct { GameOver :: struct {
using state: GameState, using state: GameState,
position: vec2, position: vec2,
size: vec2, size: vec2,
ready_to_go: bool, ready_to_go: bool,
} }
@ -22,8 +23,16 @@ gameover_init :: proc(prev: ^GameState = nil) -> ^GameState {
state.free = gameover_free state.free = gameover_free
state.previous = prev state.previous = prev
tween_to(&state.position.y, WSize.y / 2, 1, ease.Ease.Back_Out, state, gameover_ready) ntween.animate(
&vec2_tweens,
&state.position,
WSize / 2,
1,
ease.Ease.Back_Out,
state,
gameover_ready,
)
return state return state
} }
@ -33,16 +42,24 @@ gameover_update :: proc(state: ^GameState, delta: f32) {
if rl.IsKeyPressed(rl.KeyboardKey.ESCAPE) { if rl.IsKeyPressed(rl.KeyboardKey.ESCAPE) {
gameover.ready_to_go = false gameover.ready_to_go = false
rl.StopMusicStream(current_music) rl.StopMusicStream(current_music)
tween_to(&Overlay_Opacity, 1.0, 0.5, ease.Ease.Cubic_Out, state, proc(data: rawptr) { ntween.animate(
state := transmute(^GameState)data &f32_tweens,
stack_pop() &Overlay_Opacity,
game := transmute(^Game)state.previous 1.0,
game_setup(game) 0.5,
menu := menu_init(game) ease.Ease.Cubic_Out,
stack_push(menu) state,
free(state) proc(data: rawptr) {
tween_to(&Overlay_Opacity, 0, 0.5, ease.Ease.Cubic_Out) state := transmute(^GameState)data
}) stack_pop()
game := transmute(^Game)state.previous
game_setup(game)
menu := menu_init(game)
stack_push(menu)
free(state)
ntween.animate(&f32_tweens, &Overlay_Opacity, 0, 0.5, ease.Ease.Cubic_Out)
},
)
} }
} }
} }
@ -50,7 +67,7 @@ gameover_update :: proc(state: ^GameState, delta: f32) {
gameover_draw :: proc(state: ^GameState) { gameover_draw :: proc(state: ^GameState) {
gameover := transmute(^GameOver)state gameover := transmute(^GameOver)state
if state.previous != nil { if state.previous != nil {
state.previous->draw() state.previous->draw()
} }
@ -63,12 +80,26 @@ gameover_draw :: proc(state: ^GameState) {
SubtitleText := [?]cstring{"Нажмите Escape чтобы выйти"} SubtitleText := [?]cstring{"Нажмите Escape чтобы выйти"}
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 {
draw_text_centered(Res.Fonts.UI, c, gameover.position - {0, f32(10 - i * 50)}, SubtitleFontSize, 1, rl.WHITE) draw_text_centered(
Res.Fonts.UI,
c,
gameover.position - {0, f32(10 - i * 50)},
SubtitleFontSize,
1,
rl.WHITE,
)
// rl.DrawTextPro(Res.Fonts.UI, c, winning.position - {0, f32(10 - i * 50)}, SubtitleSizes[i] / 2, 0, SubtitleFontSize, SubtitleSpacing, rl.WHITE) // rl.DrawTextPro(Res.Fonts.UI, c, winning.position - {0, f32(10 - i * 50)}, SubtitleSizes[i] / 2, 0, SubtitleFontSize, SubtitleSpacing, rl.WHITE)
} }

229
main.odin
View File

@ -1,11 +1,15 @@
package main package main
import rl "vendor:raylib" import "core:fmt"
import "core:path/filepath" import "core:path/filepath"
import "core:strings" import "core:strings"
import "ntween"
import rl "vendor:raylib"
vec3 :: [3]f32 vec3 :: [3]f32
vec3i :: [3]i32
vec2 :: [2]f32 vec2 :: [2]f32
vec2i :: [2]i32
vec3right := vec3{1, 0, 0} vec3right := vec3{1, 0, 0}
vec3left := vec3{-1, 0, 0} vec3left := vec3{-1, 0, 0}
vec3up := vec3{0, 1, 0} vec3up := vec3{0, 1, 0}
@ -17,55 +21,57 @@ vec3backward := vec3{0, 0, -1}
WSize := [2]f32{} WSize := [2]f32{}
WSizei := [2]i32{} WSizei := [2]i32{}
WSizeLast := vec2i{}
WindowShouldExit := false WindowShouldExit := false
NeedTutorial := true NeedTutorial := true
KeyboardOnly := false KeyboardOnly := false
Overlay_Opacity : f32 = 0 Overlay_Opacity: f32 = 0
Resources :: struct { Resources :: struct {
Fonts: struct { Fonts: struct {
UI: rl.Font, UI: rl.Font,
Title: rl.Font, Title: rl.Font,
}, },
Models: struct { Models: struct {
PlayerModel: rl.Model, PlayerModel: rl.Model,
SnakeHeadTop: rl.Model, SnakeHeadTop: rl.Model,
SnakeHeadJaw: rl.Model, SnakeHeadJaw: rl.Model,
SnakeBody: rl.Model, SnakeBody: rl.Model,
Background: rl.Model, Background: rl.Model,
}, },
Sfx: struct { Sfx: struct {
Drums: rl.Sound, Drums: rl.Sound,
Lightning: rl.Sound, Lightning: rl.Sound,
LightningHit: rl.Sound, LightningHit: rl.Sound,
Rocket: rl.Sound, Rocket: rl.Sound,
PlayerHit: rl.Sound, PlayerHit: rl.Sound,
PlayerSwoosh: rl.Sound, PlayerSwoosh: rl.Sound,
PlayerDead: rl.Sound, PlayerDead: rl.Sound,
SnakeGrowl: rl.Sound, SnakeGrowl: rl.Sound,
SnakeRoarBlast: rl.Sound, SnakeRoarBlast: rl.Sound,
SnakeEarthHit: rl.Sound, SnakeEarthHit: rl.Sound,
SnakeSegmentExplode: rl.Sound, SnakeSegmentExplode: rl.Sound,
SnakeBeam: rl.Sound, SnakeBeam: rl.Sound,
}, },
Music: struct { Music: struct {
First: rl.Music, First: rl.Music,
Second: rl.Music, Second: rl.Music,
} },
} }
Res : Resources Res: Resources
res_paths := map[typeid]string{ res_paths := map[typeid]string {
rl.Music = "music", rl.Music = "music",
rl.Sound = "sfx", rl.Sound = "sfx",
rl.Model = "models", rl.Model = "models",
} }
get_path :: proc(name: string, $T: typeid) -> cstring{ get_path :: proc(name: string, $T: typeid) -> cstring {
p := filepath.join([]string{"./assets", res_paths[T], name}) p := filepath.join([]string{"./assets", res_paths[T], name})
cstr := strings.clone_to_cstring(p) cstr := strings.clone_to_cstring(p)
return cstr return cstr
@ -79,100 +85,121 @@ load_model :: proc(name: string) -> rl.Model {
load_sfx :: proc(name: string, volume: f32 = 1) -> rl.Sound { load_sfx :: proc(name: string, volume: f32 = 1) -> rl.Sound {
p := get_path(name, rl.Sound) p := get_path(name, rl.Sound)
snd := rl.LoadSound(p) snd := rl.LoadSound(p)
rl.SetSoundVolume(snd, volume) rl.SetSoundVolume(snd, volume)
return snd return snd
} }
load_music :: proc(name: string, volume: f32 = 1) -> rl.Music { load_music :: proc(name: string, volume: f32 = 1) -> rl.Music {
p := get_path(name, rl.Music) p := get_path(name, rl.Music)
snd := rl.LoadMusicStream(p) snd := rl.LoadMusicStream(p)
rl.SetMusicVolume(snd, volume) rl.SetMusicVolume(snd, volume)
return snd return snd
} }
current_music : rl.Music current_music: rl.Music
change_track :: proc(music: rl.Music) { change_track :: proc(music: rl.Music) {
rl.StopMusicStream(current_music) rl.StopMusicStream(current_music)
current_music = music current_music = music
rl.PlayMusicStream(current_music) rl.PlayMusicStream(current_music)
} }
Cursor : rl.Texture 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 = { Res.Models = {
PlayerModel = load_model("chariot.glb"), PlayerModel = load_model("chariot.glb"),
SnakeHeadTop = load_model("snake_head_top.obj"), SnakeHeadTop = load_model("snake_head_top.obj"),
SnakeHeadJaw = load_model("snake_jaw.obj"), SnakeHeadJaw = load_model("snake_jaw.obj"),
SnakeBody = load_model("snake_body.obj"), SnakeBody = load_model("snake_body.obj"),
Background = load_model("background.obj"), Background = load_model("background.obj"),
} }
Res.Sfx = { Res.Sfx = {
Drums = load_sfx("drums.ogg"), Drums = load_sfx("drums.ogg"),
Lightning = load_sfx("lightning.ogg", 0.5), Lightning = load_sfx("lightning.ogg", 0.5),
LightningHit = load_sfx("lightning-hit.ogg", 0.3), LightningHit = load_sfx("lightning-hit.ogg", 0.3),
Rocket = load_sfx("rocket.ogg", 0.5), Rocket = load_sfx("rocket.ogg", 0.5),
PlayerHit = load_sfx("player-hit.ogg", 0.5), PlayerHit = load_sfx("player-hit.ogg", 0.5),
PlayerSwoosh = load_sfx("player-swoosh.ogg"), PlayerSwoosh = load_sfx("player-swoosh.ogg"),
PlayerDead = load_sfx("player-dead.ogg"), PlayerDead = load_sfx("player-dead.ogg"),
SnakeGrowl = load_sfx("snake-growl.ogg", 0.7), SnakeGrowl = load_sfx("snake-growl.ogg", 0.7),
SnakeRoarBlast = load_sfx("snake-roar-blast.ogg", 0.8), SnakeRoarBlast = load_sfx("snake-roar-blast.ogg", 0.8),
SnakeBeam = load_sfx("snake-beam.ogg"), SnakeBeam = load_sfx("snake-beam.ogg"),
SnakeSegmentExplode = load_sfx("snake-segment-explode.ogg", 0.8), SnakeSegmentExplode = load_sfx("snake-segment-explode.ogg", 0.8),
SnakeEarthHit = load_sfx("snake-earth-hit.ogg", 0.6), SnakeEarthHit = load_sfx("snake-earth-hit.ogg", 0.6),
} }
Res.Music.First = load_music("alexander-nakarada-mjolnir.mp3", 0.7) Res.Music.First = load_music("alexander-nakarada-mjolnir.mp3", 0.7)
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 f32_tweens: ntween.Tween_Map(f32)
vec2_tweens: ntween.Tween_Map(vec2)
vec3_tweens: ntween.Tween_Map(vec3)
main :: proc() { main :: proc() {
rl.SetConfigFlags(rl.ConfigFlags{.MSAA_4X_HINT, .FULLSCREEN_MODE, .VSYNC_HINT, .WINDOW_RESIZABLE}) f32_tweens = ntween.init(f32)
vec2_tweens = ntween.init(vec2)
rl.InitWindow(0, 0, "Ragnarøkkr") vec3_tweens = ntween.init(vec3)
rl.InitAudioDevice() // rl.SetConfigFlags(rl.ConfigFlags{.MSAA_4X_HINT, .FULLSCREEN_MODE, .VSYNC_HINT, .WINDOW_RESIZABLE})
rl.SetWindowMinSize(800, 480) rl.SetConfigFlags(rl.ConfigFlags{.VSYNC_HINT, .WINDOW_RESIZABLE})
rl.HideCursor() rl.InitWindow(800, 600, "Ragnarøkkr")
Cursor = rl.LoadTexture("./assets/gfx/crosshair.png") rl.InitAudioDevice()
load_resources() rl.SetWindowMinSize(800, 600)
WSizei = {rl.GetScreenWidth(), rl.GetScreenHeight()} rl.HideCursor()
WSize = {f32(WSizei.x), f32(WSizei.y)} Cursor = rl.LoadTexture("./assets/gfx/crosshair.png")
load_resources()
game := game_init() WSizei = {rl.GetScreenWidth(), rl.GetScreenHeight()}
stack_push(game) WSize = {f32(WSizei.x), f32(WSizei.y)}
menu := menu_init(game)
stack_push(menu)
for !WindowShouldExit { game := game_init()
if rl.IsWindowResized() { stack_push(game)
WSizei = {rl.GetScreenWidth(), rl.GetScreenHeight()} menu := menu_init(game)
WSize = {f32(WSizei.x), f32(WSizei.y)} stack_push(menu)
}
rl.UpdateMusicStream(current_music) for !WindowShouldExit {
if rl.IsWindowResized() {
WSizei = {rl.GetScreenWidth(), rl.GetScreenHeight()}
WSize = {f32(WSizei.x), f32(WSizei.y)}
}
state := stack_top() rl.UpdateMusicStream(current_music)
delta := rl.GetFrameTime()
timers_process(delta)
tweens_process(delta)
state->update(delta)
rl.BeginDrawing() state := stack_top()
rl.ClearBackground(rl.SKYBLUE) delta := rl.GetFrameTime()
state->draw() timers_process(delta)
ntween.process(&f32_tweens, delta)
ntween.process(&vec2_tweens, delta)
ntween.process(&vec3_tweens, delta)
state->update(delta)
rl.DrawRectangleV({}, WSize, rl.Color{0, 0, 0, u8(Overlay_Opacity * 255)}) rl.BeginDrawing()
pos := rl.GetMousePosition() rl.ClearBackground(rl.SKYBLUE)
rl.DrawTextureEx(Cursor, pos - {16, 16} * 3, 0, 3, rl.WHITE) state->draw()
rl.EndDrawing()
} 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()
}
}
Fullscreen := false
toggle_fullscreen :: proc() {
monitor := rl.GetCurrentMonitor()
rl.ToggleBorderlessWindowed()
WSizei = {rl.GetScreenWidth(), rl.GetScreenHeight()}
WSize = {f32(WSizei.x), f32(WSizei.y)}
Fullscreen = !Fullscreen
// rl.ToggleFullscreen()
} }

View File

@ -75,8 +75,8 @@ menu_button_pressed :: proc(state: ^GameState, el: Menu_Buttons) {
KeyboardOnly = !KeyboardOnly KeyboardOnly = !KeyboardOnly
NeedTutorial = true NeedTutorial = true
case .FULLSCREEN: case .FULLSCREEN:
rl.ToggleFullscreen() toggle_fullscreen()
Fullscreen = rl.IsWindowFullscreen() // Fullscreen = rl.IsWindowFullscreen()
case .EXIT: case .EXIT:
WindowShouldExit = true WindowShouldExit = true

View File

@ -1,8 +1,9 @@
package main package main
import rl "vendor:raylib"
import "core:math/ease"
import "core:fmt" import "core:fmt"
import "core:math/ease"
import "ntween"
import rl "vendor:raylib"
MenuItemType :: enum { MenuItemType :: enum {
NONE, NONE,
@ -11,28 +12,28 @@ MenuItemType :: enum {
} }
BoolStrings := map[bool]cstring { BoolStrings := map[bool]cstring {
true = "вкл", true = "вкл",
false = "выкл" false = "выкл",
} }
MenuItem :: struct{ MenuItem :: struct {
text: cstring, text: cstring,
param: rawptr, param: rawptr,
type: MenuItemType, type: MenuItemType,
} }
MenuList :: struct($T: typeid) { MenuList :: struct($T: typeid) {
state: ^GameState, state: ^GameState,
position: vec2, position: vec2,
line_size: f32, line_size: f32,
font_size: f32, font_size: f32,
active_element: T, active_element: T,
active_marker: vec2, active_marker: vec2,
tween: ^Tween, tween: ^ntween.Tween(f32),
elements: ^[T]MenuItem, 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,
} }
@ -46,14 +47,12 @@ menu_list_update :: proc(list: ^MenuList($T)) {
list.mouse_pos = rl.GetMousePosition() list.mouse_pos = rl.GetMousePosition()
size := menu_list_get_size(list) size := menu_list_get_size(list)
if rl.CheckCollisionPointRec(list.mouse_pos, rl.Rectangle{ if rl.CheckCollisionPointRec(
x = list.position.x, list.mouse_pos,
y = list.position.y, rl.Rectangle{x = list.position.x, y = list.position.y, width = size.x, height = size.y},
width = size.x, ) {
height = size.y,
}) {
if last_mouse_pos != list.mouse_pos { if last_mouse_pos != list.mouse_pos {
mouse_relative := list.mouse_pos - list.position mouse_relative := list.mouse_pos - list.position
cur_element = i8(mouse_relative.y / list.line_size) cur_element = i8(mouse_relative.y / list.line_size)
} }
if rl.IsMouseButtonPressed(rl.MouseButton.LEFT) { if rl.IsMouseButtonPressed(rl.MouseButton.LEFT) {
@ -63,7 +62,6 @@ menu_list_update :: proc(list: ^MenuList($T)) {
last_mouse_pos = list.mouse_pos last_mouse_pos = list.mouse_pos
if rl.IsKeyPressed(rl.KeyboardKey.DOWN) { if rl.IsKeyPressed(rl.KeyboardKey.DOWN) {
cur_element += 1 cur_element += 1
} }
@ -71,13 +69,14 @@ menu_list_update :: proc(list: ^MenuList($T)) {
cur_element -= 1 cur_element -= 1
} }
if prev_element != cur_element { if prev_element != cur_element {
if cur_element < 0 { cur_element = len(T) -1 } if cur_element < 0 {cur_element = len(T) - 1}
if cur_element == len(T) { cur_element = 0 } if cur_element == len(T) {cur_element = 0}
list.active_element = cast(T)cur_element list.active_element = cast(T)cur_element
if list.tween != nil { if list.tween != nil {
tween_cancel(list.tween) ntween.cancel(list.tween)
} }
list.tween = tween_to( list.tween = ntween.animate(
&f32_tweens,
&list.active_marker.y, &list.active_marker.y,
f32(list.active_element) * list.line_size, f32(list.active_element) * list.line_size,
0.25, 0.25,
@ -94,10 +93,17 @@ menu_list_draw :: proc(list: ^MenuList($T)) {
size := menu_list_get_size(list) size := menu_list_get_size(list)
rl.DrawRectangleV(list.position - {40, 40}, size + {80, 80}, list.background) rl.DrawRectangleV(list.position - {40, 40}, size + {80, 80}, list.background)
} }
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}
text := el.text text := el.text
if el.type == .BOOL { if el.type == .BOOL {
param := transmute(^bool)el.param param := transmute(^bool)el.param
value := param^ value := param^

1
ntween Submodule

@ -0,0 +1 @@
Subproject commit 7136c68f3e63470b9b1c80109dd0904151e08070

View File

@ -1,51 +1,50 @@
package main package main
import rl "vendor:raylib"
import "core:math/ease" import "core:math/ease"
import "ntween"
import rl "vendor:raylib"
Pause_Buttons :: enum { Pause_Buttons :: enum {
CONTINUE, CONTINUE,
EXIT EXIT,
} }
pause_strings := [Pause_Buttons]cstring { pause_strings := [Pause_Buttons]cstring {
.CONTINUE = "Продолжить", .CONTINUE = "Продолжить",
.EXIT = "Прервать игру" .EXIT = "Прервать игру",
} }
pause_items := [Pause_Buttons]MenuItem { pause_items := [Pause_Buttons]MenuItem {
.CONTINUE = {text = pause_strings[.CONTINUE]}, .CONTINUE = {text = pause_strings[.CONTINUE]},
.EXIT = {text = pause_strings[.EXIT]} .EXIT = {text = pause_strings[.EXIT]},
} }
Pause :: struct { Pause :: struct {
using state: GameState, using state: GameState,
active: bool,
active: bool, list: MenuList(Pause_Buttons),
list: MenuList(Pause_Buttons),
} }
pause_init :: proc(prev: ^GameState = nil) -> ^GameState { pause_init :: proc(prev: ^GameState = nil) -> ^GameState {
state := new(Pause) state := new(Pause)
state.variant = state state.variant = state
state.list = MenuList(Pause_Buttons){ state.list = MenuList(Pause_Buttons) {
state = state, state = state,
position = {-300, WSize.y / 2}, position = {-300, WSize.y / 2},
line_size = 60, line_size = 60,
font_size = 48, font_size = 48,
elements = &pause_items, elements = &pause_items,
menu_pressed = pause_button_pressed, menu_pressed = pause_button_pressed,
background = rl.Color{50, 10, 110, 0} background = rl.Color{50, 10, 110, 0},
} }
state.update = pause_update state.update = pause_update
state.draw = pause_draw state.draw = pause_draw
state.free = pause_free state.free = pause_free
state.previous = prev state.previous = prev
state.active = true state.active = true
tween_to(&state.list.position.x, 100, 0.5, ease.Ease.Back_Out) ntween.animate(&f32_tweens, &state.list.position.x, 100, 0.5, ease.Ease.Back_Out)
return state return state
} }
@ -53,20 +52,27 @@ pause_init :: proc(prev: ^GameState = nil) -> ^GameState {
pause_update :: proc(state: ^GameState, delta: f32) { pause_update :: proc(state: ^GameState, delta: f32) {
pause := transmute(^Pause)state pause := transmute(^Pause)state
pause.list.position.y = WSize.y / 2 pause.list.position.y = WSize.y / 2
menu_list_update(&pause.list) menu_list_update(&pause.list)
} }
pause_button_pressed :: proc(state: ^GameState, el: Pause_Buttons) { pause_button_pressed :: proc(state: ^GameState, el: Pause_Buttons) {
pause := transmute(^Pause)state pause := transmute(^Pause)state
if !pause.active { return } if !pause.active {return}
switch el { switch el {
case .CONTINUE: case .CONTINUE:
stack_pop() stack_pop()
case .EXIT: case .EXIT:
pause.active = false pause.active = false
rl.StopMusicStream(current_music) rl.StopMusicStream(current_music)
tween_to(&Overlay_Opacity, 1.0, 0.5, ease.Ease.Cubic_Out, state, proc(data: rawptr) { ntween.animate(
&f32_tweens,
&Overlay_Opacity,
1.0,
0.5,
ease.Ease.Cubic_Out,
state,
proc(data: rawptr) {
state := transmute(^GameState)data state := transmute(^GameState)data
stack_pop() stack_pop()
game := transmute(^Game)stack_top() game := transmute(^Game)stack_top()
@ -74,21 +80,31 @@ pause_button_pressed :: proc(state: ^GameState, el: Pause_Buttons) {
menu := menu_init(game) menu := menu_init(game)
stack_push(menu) stack_push(menu)
//free(state) //free(state)
tween_to(&Overlay_Opacity, 0, 0.5, ease.Ease.Cubic_Out) ntween.animate(&f32_tweens, &Overlay_Opacity, 0, 0.5, ease.Ease.Cubic_Out)
}) },
)
} }
} }
pause_draw :: proc(state: ^GameState) { pause_draw :: proc(state: ^GameState) {
pause := transmute(^Pause)state pause := transmute(^Pause)state
pause.previous.draw(pause.previous) pause.previous.draw(pause.previous)
TitleFontSize :: 96 TitleFontSize :: 96
TitleSpacing :: 3 TitleSpacing :: 3
TitleText :: "Ragnarøkkr" TitleText :: "Ragnarøkkr"
TitleSize := rl.MeasureTextEx(Res.Fonts.Title, TitleText, TitleFontSize, TitleSpacing) TitleSize := rl.MeasureTextEx(Res.Fonts.Title, TitleText, TitleFontSize, TitleSpacing)
rl.DrawTextPro(Res.Fonts.Title, TitleText, {WSize.x - 50, 50}, {TitleSize.x, 0}, 0, 96, 3, rl.WHITE) rl.DrawTextPro(
Res.Fonts.Title,
TitleText,
{WSize.x - 50, 50},
{TitleSize.x, 0},
0,
96,
3,
rl.WHITE,
)
menu_list_draw(&pause.list) menu_list_draw(&pause.list)

View File

@ -1,270 +1,280 @@
package main package main
import "core:fmt"
import "core:math"
import "core:math/ease"
import "core:math/rand"
import "core:slice"
import "core:strings"
import "ntween"
import rl "vendor:raylib" import rl "vendor:raylib"
import "vendor:raylib/rlgl" import "vendor:raylib/rlgl"
import "core:math"
import "core:strings"
import "core:math/rand"
import "core:math/ease"
import "core:fmt"
import "core:slice"
// PlayerAnims : [^]rl.ModelAnimation // PlayerAnims : [^]rl.ModelAnimation
// PlayerAnimsCount : i32 // PlayerAnimsCount : i32
Player :: struct { Player :: struct {
pos: vec3, pos: vec3,
vel: vec3, vel: vec3,
dir: f32, dir: f32,
radius: f32, radius: f32,
thrust: f32, thrust: f32,
max_speed: f32, max_speed: f32,
rolling: f32, rolling: f32,
charge: f32, charge: f32,
is_dodging: bool, is_dodging: bool,
can_dodge: bool, can_dodge: bool,
can_shoot: bool, can_shoot: bool,
is_invulnerable: bool, is_invulnerable: bool,
is_dead: bool, is_dead: bool,
intro_timer: f32, intro_timer: f32,
power: f32, power: f32,
reload_timer: f32, reload_timer: f32,
reloading: bool, reloading: bool,
// animation: rl.ModelAnimation, // animation: rl.ModelAnimation,
// animTime: f32, // animTime: f32,
// animFrame: i32, // animFrame: i32,
} }
player_spawn :: proc(position: vec3) -> Player { player_spawn :: proc(position: vec3) -> Player {
// PlayerAnims = rl.LoadModelAnimations(PlayerModelPath, &PlayerAnimsCount) // PlayerAnims = rl.LoadModelAnimations(PlayerModelPath, &PlayerAnimsCount)
return Player{ return Player {
pos = position, pos = position,
radius = 1, radius = 1,
max_speed = 40, max_speed = 40,
dir = 0, dir = 0,
vel = {-80, 0, 0}, vel = {-80, 0, 0},
can_dodge = true, can_dodge = true,
can_shoot = true, can_shoot = true,
intro_timer = 2, intro_timer = 2,
power = 100, power = 100,
} }
} }
player_update :: proc(player: ^Player, game: ^Game, delta: f32) { player_update :: proc(player: ^Player, game: ^Game, delta: f32) {
using player using player
if intro_timer > 0 { if intro_timer > 0 {
intro_timer -= delta intro_timer -= delta
} }
mouse_ray := rl.GetMouseRay(rl.GetMousePosition(), game.camera)
mouse_pos : vec3
hit := rl.GetRayCollisionQuad(mouse_ray,
{-1000, -1000, 0},
{-1000, 1000, 0},
{1000, 1000, 0},
{1000, -1000, 0}
)
if hit.hit {
mouse_pos = hit.point
}
mouse_diff := mouse_pos - pos
mouse_angle := math.atan2(-mouse_diff.y, mouse_diff.x)
if !is_dead { mouse_ray := rl.GetMouseRay(rl.GetMousePosition(), game.camera)
pos += vel * delta mouse_pos: vec3
if pos.y < radius { hit := rl.GetRayCollisionQuad(
pos.y = radius mouse_ray,
vel.y = - vel.y {-1000, -1000, 0},
} {-1000, 1000, 0},
if !is_dodging { {1000, 1000, 0},
dir_vector : vec3 {1000, -1000, 0},
if intro_timer <= 0 { )
thrust_key := rl.KeyboardKey.W if hit.hit {
if !KeyboardOnly { mouse_pos = hit.point
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
if rl.IsKeyDown(thrust_key) {
thrust = 110
}
} else {
thrust = 110
dir_vector = vec3left
dir = math.atan2(-dir_vector.y, dir_vector.x)
}
if thrust > 0 {
roll := -math.PI / 2 + math.cos(dir) * math.PI / 2
vel = rl.Vector3MoveTowards(vel, dir_vector * max_speed, thrust * delta)
offset := rl.Vector3RotateByAxisAngle(vec3backward, vec3right, roll) * 2.6
offset = rl.Vector3RotateByAxisAngle(offset, vec3backward, dir)
pl := pos + offset
pr := pos - offset
trail(pl, 1, rand.float32_range(1.7, 3.5))
trail(pr, 1, rand.float32_range(1.7, 3.5))
}
}
if thrust == 0 {
vel = rl.Vector3MoveTowards(vel, {0, -30, 0}, 20 * delta)
rl.StopSound(Res.Sfx.Rocket)
} else {
if !rl.IsSoundPlaying(Res.Sfx.Rocket) && !is_dodging {
rl.PlaySound(Res.Sfx.Rocket)
}
}
dodge := false mouse_diff := mouse_pos - pos
shoot := false mouse_angle := math.atan2(-mouse_diff.y, mouse_diff.x)
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 { if !is_dead {
is_dodging = true pos += vel * delta
can_dodge = false if pos.y < radius {
rl.StopSound(Res.Sfx.Rocket) pos.y = radius
rl.PlaySound(Res.Sfx.PlayerSwoosh) vel.y = -vel.y
tween_to(&player.rolling, math.PI*2, 0.42, ease.Ease.Quadratic_Out) }
timer_start(0.45, player, proc(data: rawptr) { if !is_dodging {
player := transmute(^Player)data dir_vector: vec3
player.is_dodging = false if intro_timer <= 0 {
player.rolling = 0 thrust_key := rl.KeyboardKey.W
}) if !KeyboardOnly {
timer_start(0.55, player, proc(data: rawptr) { dir = angle_rotate(dir, mouse_angle, math.PI * 2 * delta)
player := transmute(^Player)data dir_vector = get_vec_from_angle(dir)
player.can_dodge = true } 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
if rl.IsKeyDown(thrust_key) {
thrust = 110
}
} else {
thrust = 110
dir_vector = vec3left
dir = math.atan2(-dir_vector.y, dir_vector.x)
}
if thrust > 0 {
roll := -math.PI / 2 + math.cos(dir) * math.PI / 2
vel = rl.Vector3MoveTowards(vel, dir_vector * max_speed, thrust * delta)
offset := rl.Vector3RotateByAxisAngle(vec3backward, vec3right, roll) * 2.6
offset = rl.Vector3RotateByAxisAngle(offset, vec3backward, dir)
pl := pos + offset
pr := pos - offset
trail(pl, 1, rand.float32_range(1.7, 3.5))
trail(pr, 1, rand.float32_range(1.7, 3.5))
}
}
if thrust == 0 {
vel = rl.Vector3MoveTowards(vel, {0, -30, 0}, 20 * delta)
rl.StopSound(Res.Sfx.Rocket)
} else {
if !rl.IsSoundPlaying(Res.Sfx.Rocket) && !is_dodging {
rl.PlaySound(Res.Sfx.Rocket)
}
}
// player.animation = PlayerAnims[1] dodge := false
// player.animTime += delta shoot := false
// player.animFrame = i32(player.animTime * 60) % player.animation.frameCount if KeyboardOnly {
// rl.UpdateModelAnimation(PlayerModel, player.animation, player.animFrame) dodge = rl.IsKeyPressed(rl.KeyboardKey.LEFT_SHIFT)
shooting := shoot && !is_dodging && intro_timer <= 0 && !reloading shoot = rl.IsKeyDown(rl.KeyboardKey.SPACE)
if shooting { } else {
if !rl.IsSoundPlaying(Res.Sfx.Lightning) { dodge = rl.IsMouseButtonPressed(rl.MouseButton.RIGHT)
rl.PlaySound(Res.Sfx.Lightning) shoot = rl.IsMouseButtonDown(rl.MouseButton.LEFT)
} }
if can_shoot {
roll := -math.PI / 2 + math.cos(dir) * math.PI / 2
b := bullet_spawn(pos + get_vec_from_angle(dir) * 3 + get_vec_from_angle(dir+math.PI/2)*.3, dir)
append(&game.bullets, b)
can_shoot = false
timer_start(0.07, player, proc(data: rawptr) {
player := transmute(^Player)data
player.can_shoot = true
})
player.power -= 2
player.reload_timer = 1
if player.power <= 0 {
player.reloading = true
player.power = 0
}
}
} else {
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 if dodge && can_dodge && intro_timer <= 0 {
is_dodging = true
can_dodge = false
rl.StopSound(Res.Sfx.Rocket)
rl.PlaySound(Res.Sfx.PlayerSwoosh)
ntween.animate(
&f32_tweens,
&player.rolling,
math.PI * 2,
0.42,
ease.Ease.Quadratic_Out,
)
timer_start(0.45, player, proc(data: rawptr) {
player := transmute(^Player)data
player.is_dodging = false
player.rolling = 0
})
timer_start(0.55, player, proc(data: rawptr) {
player := transmute(^Player)data
player.can_dodge = true
})
}
hit: if !is_invulnerable && !is_dodging && !is_dead { // player.animation = PlayerAnims[1]
if rl.CheckCollisionCircles(pos.xy, radius, Head.pos.xy, Head.radius) { // player.animTime += delta
got_hit = true // player.animFrame = i32(player.animTime * 60) % player.animation.frameCount
break hit // rl.UpdateModelAnimation(PlayerModel, player.animation, player.animFrame)
} shooting := shoot && !is_dodging && intro_timer <= 0 && !reloading
ray := rl.Ray{ if shooting {
position = Head.pos, if !rl.IsSoundPlaying(Res.Sfx.Lightning) {
direction = get_vec_from_angle(Head.dir) rl.PlaySound(Res.Sfx.Lightning)
} }
if Head.is_shooting && rl.GetRayCollisionSphere(ray, pos, radius + 3).hit { if can_shoot {
got_hit = true roll := -math.PI / 2 + math.cos(dir) * math.PI / 2
break hit b := bullet_spawn(
} pos + get_vec_from_angle(dir) * 3 + get_vec_from_angle(dir + math.PI / 2) * .3,
for segment in Segments { dir,
if rl.CheckCollisionCircles(pos.xy, radius, segment.pos.xy, radius) { )
got_hit = true append(&game.bullets, b)
break can_shoot = false
} timer_start(0.07, player, proc(data: rawptr) {
} player := transmute(^Player)data
} player.can_shoot = true
if rl.IsKeyPressed(rl.KeyboardKey.M) { })
got_hit = true player.power -= 2
} player.reload_timer = 1
if player.power <= 0 {
player.reloading = true
player.power = 0
}
}
} else {
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
hit: if !is_invulnerable && !is_dodging && !is_dead {
if rl.CheckCollisionCircles(pos.xy, radius, Head.pos.xy, Head.radius) {
got_hit = true
break hit
}
ray := rl.Ray {
position = Head.pos,
direction = get_vec_from_angle(Head.dir),
}
if Head.is_shooting && rl.GetRayCollisionSphere(ray, pos, radius + 3).hit {
got_hit = true
break hit
}
for segment in Segments {
if rl.CheckCollisionCircles(pos.xy, radius, segment.pos.xy, radius) {
got_hit = true
break
}
}
}
if rl.IsKeyPressed(rl.KeyboardKey.M) {
got_hit = true
}
if got_hit {
game.health -= 10
rl.PlaySound(Res.Sfx.PlayerHit)
is_invulnerable = true
timer_start(1, player, proc(data: rawptr) {
plr := transmute(^Player)data
plr.is_invulnerable = false
})
if game.health <= 0 && !is_dead {
is_dead = true
explode(pos, 10, 0.8, rl.WHITE)
rl.StopMusicStream(current_music)
rl.PlaySound(Res.Sfx.PlayerDead)
timer_start(3, game, proc(data: rawptr) {
state := transmute(^Game)data
screen := gameover_init(state)
stack_push(screen)
})
}
}
if got_hit {
game.health -= 10
rl.PlaySound(Res.Sfx.PlayerHit)
is_invulnerable = true
timer_start(1, player, proc(data: rawptr) {
plr := transmute(^Player)data
plr.is_invulnerable = false
})
if game.health <= 0 && !is_dead {
is_dead = true
explode(pos, 10, 0.8, rl.WHITE)
rl.StopMusicStream(current_music)
rl.PlaySound(Res.Sfx.PlayerDead)
timer_start(3, game, proc(data: rawptr) {
state := transmute(^Game)data
screen := gameover_init(state)
stack_push(screen)
})
}
}
} }
player_draw :: proc(player: ^Player) { player_draw :: proc(player: ^Player) {
using player using player
if player.is_dead { return } if player.is_dead {return}
dir_vector := get_vec_from_angle(dir) dir_vector := get_vec_from_angle(dir)
color := rl.WHITE color := rl.WHITE
if is_invulnerable { if is_invulnerable {
color = rl.Color{255, 170, 170, 255} color = rl.Color{255, 170, 170, 255}
} }
roll := -math.PI / 2 + math.cos(dir) * math.PI / 2 roll := -math.PI / 2 + math.cos(dir) * math.PI / 2
rlgl.PushMatrix() rlgl.PushMatrix()
rlgl.Translatef(pos.x, pos.y, pos.z) rlgl.Translatef(pos.x, pos.y, pos.z)
rlgl.Rotatef(math.to_degrees(dir), 0, 0, -1) rlgl.Rotatef(math.to_degrees(dir), 0, 0, -1)
rlgl.Rotatef(math.to_degrees(roll + player.rolling), 1, 0, 0) rlgl.Rotatef(math.to_degrees(roll + player.rolling), 1, 0, 0)
// rl.DrawCircle3D({}, radius, vec3up, 0, color) // rl.DrawCircle3D({}, radius, vec3up, 0, color)
// rl.DrawLine3D({}, {-4, 0, 0}, rl.GREEN) // rl.DrawLine3D({}, {-4, 0, 0}, rl.GREEN)
rl.DrawModel(Res.Models.PlayerModel, {}, 6, color) rl.DrawModel(Res.Models.PlayerModel, {}, 6, color)
rlgl.PopMatrix() rlgl.PopMatrix()
// rl.DrawLine3D(pos, pos + dir_vector * radius, rl.BLACK) // rl.DrawLine3D(pos, pos + dir_vector * radius, rl.BLACK)
// rl.DrawLine3D(pos, pos + vel, rl.RED) // rl.DrawLine3D(pos, pos + vel, rl.RED)
} }

View File

@ -1,80 +0,0 @@
package main
import "core:math"
import "core:math/ease"
import "core:math/linalg"
import "core:slice"
Tween :: struct {
ptr: ^f32,
from: f32,
to: f32,
time: f32,
duration: f32,
ease_type: ease.Ease,
active: bool,
finished: proc(data: rawptr),
data: rawptr
}
tweens : [dynamic]^Tween
tween_clean :: proc() {
for tween, i in tweens {
free(tween)
}
}
tween_to :: proc(
value: ^f32, to: f32, duration: f32,
ease: ease.Ease = ease.Ease.Quartic_In_Out,
data: rawptr = nil,
callback: proc(data: rawptr) = nil
) -> ^Tween {
tween := new(Tween)
tween.ptr = value
tween.from = value^
tween.to = to
tween.duration = duration
tween.ease_type = ease
tween.active = true
tween.data = data
tween.finished = callback
append(&tweens, tween)
return tween
}
tween_cancel :: proc(t: ^Tween) {
t.active = false
}
tweens_process :: proc(delta: f32) {
#reverse for tween, i in tweens {
tween.time += delta
p := clamp(tween.time / tween.duration, 0, 1)
val := ease.ease(tween.ease_type, p)
if tween.ptr != nil {
tween.ptr^ = math.lerp(tween.from, tween.to, val)
} else {
tween.active = false
}
if tween.time >= tween.duration {
tween.active = false
if tween.finished != nil {
tween.finished(tween.data)
}
}
if !tween.active {
free(tween)
unordered_remove(&tweens, i)
}
}
}

View File

@ -1,14 +1,15 @@
package main package main
import rl "vendor:raylib"
import "core:math/ease"
import "core:fmt" import "core:fmt"
import "core:math/ease"
import "ntween"
import rl "vendor:raylib"
Winning :: struct { Winning :: struct {
using state: GameState, using state: GameState,
position: vec2, position: vec2,
size: vec2, size: vec2,
ready_to_go: bool, ready_to_go: bool,
} }
@ -22,8 +23,16 @@ winning_init :: proc(prev: ^GameState = nil) -> ^GameState {
state.free = winning_free state.free = winning_free
state.previous = prev state.previous = prev
tween_to(&state.position.y, WSize.y / 2, 1, ease.Ease.Back_Out, state, winning_ready) ntween.animate(
&vec2_tweens,
&state.position,
WSize / 2,
1,
ease.Ease.Back_Out,
state,
winning_ready,
)
return state return state
} }
@ -33,16 +42,24 @@ winning_update :: proc(state: ^GameState, delta: f32) {
if rl.IsKeyPressed(rl.KeyboardKey.ESCAPE) { if rl.IsKeyPressed(rl.KeyboardKey.ESCAPE) {
winning.ready_to_go = false winning.ready_to_go = false
rl.StopMusicStream(current_music) rl.StopMusicStream(current_music)
tween_to(&Overlay_Opacity, 1.0, 0.5, ease.Ease.Cubic_Out, nil, proc(data: rawptr) { ntween.animate(
state := transmute(^GameState)data &f32_tweens,
stack_pop() &Overlay_Opacity,
game := transmute(^Game)state.previous 1.0,
game_setup(game) 0.5,
menu := menu_init(game) ease.Ease.Cubic_Out,
stack_push(menu) nil,
free(state) proc(data: rawptr) {
tween_to(&Overlay_Opacity, 0, 0.5, ease.Ease.Cubic_Out) state := transmute(^GameState)data
}) stack_pop()
game := transmute(^Game)state.previous
game_setup(game)
menu := menu_init(game)
stack_push(menu)
free(state)
ntween.animate(&f32_tweens, &Overlay_Opacity, 0, 0.5, ease.Ease.Cubic_Out)
},
)
} }
} }
} }
@ -50,7 +67,7 @@ winning_update :: proc(state: ^GameState, delta: f32) {
winning_draw :: proc(state: ^GameState) { winning_draw :: proc(state: ^GameState) {
winning := transmute(^Winning)state winning := transmute(^Winning)state
if state.previous != nil { if state.previous != nil {
state.previous->draw() state.previous->draw()
} }
@ -59,15 +76,33 @@ winning_draw :: proc(state: ^GameState) {
TitleSpacing :: 3 TitleSpacing :: 3
TitleText :: "GAME OVER" TitleText :: "GAME OVER"
SubtitleText := [?]cstring{"Тор смог спасти Асгард", "от Рагнарёка!", "Нажмите Escape чтобы выйти"} SubtitleText := [?]cstring {
"Тор смог спасти Асгард",
"от Рагнарёка!",
"Нажмите Escape чтобы выйти",
}
SubtitleFontSize :: 48 SubtitleFontSize :: 48
rl.DrawRectangleV(winning.position - winning.size / 2, winning.size, rl.Color{90, 30, 150, 10}) rl.DrawRectangleV(winning.position - winning.size / 2, winning.size, rl.Color{90, 30, 150, 10})
draw_text_centered(Res.Fonts.Title, TitleText, winning.position - {0, 100}, TitleFontSize, 1, rl.WHITE) draw_text_centered(
Res.Fonts.Title,
TitleText,
winning.position - {0, 100},
TitleFontSize,
1,
rl.WHITE,
)
for c, i in SubtitleText { for c, i in SubtitleText {
draw_text_centered(Res.Fonts.UI, c, winning.position - {0, f32(10 - i * 50)}, SubtitleFontSize, 1, rl.WHITE) draw_text_centered(
Res.Fonts.UI,
c,
winning.position - {0, f32(10 - i * 50)},
SubtitleFontSize,
1,
rl.WHITE,
)
// rl.DrawTextPro(Res.Fonts.UI, c, winning.position - {0, f32(10 - i * 50)}, SubtitleSizes[i] / 2, 0, SubtitleFontSize, SubtitleSpacing, rl.WHITE) // rl.DrawTextPro(Res.Fonts.UI, c, winning.position - {0, f32(10 - i * 50)}, SubtitleSizes[i] / 2, 0, SubtitleFontSize, SubtitleSpacing, rl.WHITE)
} }