Final version. Fixed menus, music. Added tutorial and control schemes
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -1 +1,2 @@
 | 
				
			|||||||
sineus-final.exe
 | 
					sineus-final.exe
 | 
				
			||||||
 | 
					*.zip
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								Ragnarokkr.exe
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Ragnarokkr.exe
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								assets/gfx/crosshair.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											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  | 
							
								
								
									
										72
									
								
								game.odin
									
									
									
									
									
								
							
							
						
						
									
										72
									
								
								game.odin
									
									
									
									
									
								
							@ -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)
 | 
				
			||||||
 | 
							if SnakeActive {
 | 
				
			||||||
			draw_hbar(hb_text, hb_health, {0, WSize.y - height}, {WSize.x, height}, rl.RED)
 | 
								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)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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 {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										32
									
								
								main.odin
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								main.odin
									
									
									
									
									
								
							@ -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()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										32
									
								
								menu.odin
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								menu.odin
									
									
									
									
									
								
							@ -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
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
				
			|||||||
@ -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 
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										54
									
								
								player.odin
									
									
									
									
									
								
							
							
						
						
									
										54
									
								
								player.odin
									
									
									
									
									
								
							@ -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 {
 | 
				
			||||||
 | 
					                thrust_key := rl.KeyboardKey.W
 | 
				
			||||||
 | 
					                if !KeyboardOnly {
 | 
				
			||||||
                    dir = angle_rotate(dir, mouse_angle, math.PI * 2 * delta)
 | 
					                    dir = angle_rotate(dir, mouse_angle, math.PI * 2 * delta)
 | 
				
			||||||
                    dir_vector = get_vec_from_angle(dir)
 | 
					                    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
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										19
									
								
								snake.odin
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								snake.odin
									
									
									
									
									
								
							@ -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()
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user