First day

This commit is contained in:
Vlad Rud 2024-10-04 21:16:58 +03:00
commit ade25af03a
25 changed files with 1317 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
sineus-final.exe

View File

@ -0,0 +1,2 @@
license: Freeware
link: https://www.fontspace.com/norse-font-f21080

94
assets/fonts/OFL.txt Normal file
View File

@ -0,0 +1,94 @@
Copyright (c) 2010, ParaType Ltd. (http://www.paratype.com/public),
with Reserved Font Names "PT Sans", "PT Serif" and "ParaType".
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,49 @@
JOEL CARROUCHE - FREE FONT LICENSE
version 1.3, january 2021
By downloading / installing this font you agree to the terms of this licence :
RIGHTS GRANTED
This font is a freeware. You are free to use it to create graphics, logos, and artwork for personal or commercial projects.
You may also embed the font file in pdf documents, applications, web pages or flash animations.
RESTRICTIONS
You may not modifiy the font files.
You may not sell this font or licenses for this font.
You may not redistribute or share this font without written permission of Joël Carrouché.
This means you cannot make the font available for download on your website without prior consent.
COPYRIGHT
Except for the right to use the font mentionned above, all other rights remain the sole property of Joël Carrouché.
NO WARRANTY
The font files are provided as is, without warranty of any kind, either expressed or implied. The author shall not be liable for any damage resulting from the use of the font.
CONTACT
www.joelcarrouche.com
If you use this font for something cool, let me know !

BIN
assets/fonts/norse.otf Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,4 @@
# Made in Blockbench 4.11.1
newmtl m_3faa339c-1a1f-acb0-d167-578e9ad5cc6b
map_Kd background.png
newmtl none

View File

@ -0,0 +1,111 @@
# Made in Blockbench 4.11.1
mtllib background.mtl
o plane
v 0.5 0.3125 0.5
v 0.5 0 -0.5
v -0.5 0.3125 0.5
v -0.5 0 -0.5
v 0.5 0.14583367270962758 0.16666775267080824
v -0.5 0.14583367270962758 0.16666775267080824
v 0.5 0.04166717573166087 -0.16666503765868518
v -0.5 0.04166717573166087 -0.16666503765868518
v 0.5 -0.5077522268648397 -0.8120173603004518
v -0.5 -0.5077522268648397 -0.8120173603004518
vt 0 0.9296875
vt 0 0.6666650376586851
vt 1 0.6666650376586851
vt 1 0.9296875
vt 1 0
vt 1 0.33333224732919176
vt 0 0.33333224732919176
vt 0 0
vt 1 0.33333224732919176
vt 1 0.6666650376586851
vt 0 0.6666650376586851
vt 0 0.33333224732919176
vt 0 1
vt 0 0.9375
vt 1 0.9375
vt 1 1
vn 0 0.9922777648072294 -0.12403562983743448
vn 0 0.8944269724450329 -0.447214032609457
vn 0 0.9544799780350297 -0.2982749931359468
vn 0 0.5235550571328688 -0.8519918439460548
usemtl m_3faa339c-1a1f-acb0-d167-578e9ad5cc6b
f 4/1/1 8/2/1 7/3/1 2/4/1
f 1/5/2 5/6/2 6/7/2 3/8/2
f 5/9/3 7/10/3 8/11/3 6/12/3
f 2/13/4 9/14/4 10/15/4 4/16/4
o plane
v -0.5 0.3125 0.5
v -0.5 0 -0.5
v -1.5 0.3125 0.5
v -1.5 0 -0.5
v -0.5 0.14583367270962758 0.16666775267080824
v -1.5 0.14583367270962758 0.16666775267080824
v -0.5 0.04166717573166087 -0.16666503765868518
v -1.5 0.04166717573166087 -0.16666503765868518
v -0.5 -0.5077522268648397 -0.8120173603004518
v -1.5 -0.5077522268648397 -0.8120173603004518
vt 0 0.9296875
vt 0 0.6666650376586851
vt 1 0.6666650376586851
vt 1 0.9296875
vt 1 0
vt 1 0.33333224732919176
vt 0 0.33333224732919176
vt 0 0
vt 1 0.33333224732919176
vt 1 0.6666650376586851
vt 0 0.6666650376586851
vt 0 0.33333224732919176
vt 0 1
vt 0 0.9375
vt 1 0.9375
vt 1 1
vn 0 0.9922777648072294 -0.12403562983743448
vn 0 0.8944269724450329 -0.447214032609457
vn 0 0.9544799780350297 -0.2982749931359468
vn 0 0.5235550571328688 -0.8519918439460548
usemtl m_3faa339c-1a1f-acb0-d167-578e9ad5cc6b
f 14/17/5 18/18/5 17/19/5 12/20/5
f 11/21/6 15/22/6 16/23/6 13/24/6
f 15/25/7 17/26/7 18/27/7 16/28/7
f 12/29/8 19/30/8 20/31/8 14/32/8
o plane
v 1.5 0.3125 0.5
v 1.5 0 -0.5
v 0.5 0.3125 0.5
v 0.5 0 -0.5
v 1.5 0.14583367270962758 0.16666775267080824
v 0.5 0.14583367270962758 0.16666775267080824
v 1.5 0.04166717573166087 -0.16666503765868518
v 0.5 0.04166717573166087 -0.16666503765868518
v 1.5 -0.5077522268648397 -0.8120173603004518
v 0.5 -0.5077522268648397 -0.8120173603004518
vt 0 0.9296875
vt 0 0.6666650376586851
vt 1 0.6666650376586851
vt 1 0.9296875
vt 1 0
vt 1 0.33333224732919176
vt 0 0.33333224732919176
vt 0 0
vt 1 0.33333224732919176
vt 1 0.6666650376586851
vt 0 0.6666650376586851
vt 0 0.33333224732919176
vt 0 1
vt 0 0.9375
vt 1 0.9375
vt 1 1
vn 0 0.9922777648072294 -0.12403562983743448
vn 0 0.8944269724450329 -0.447214032609457
vn 0 0.9544799780350297 -0.2982749931359468
vn 0 0.5235550571328688 -0.8519918439460548
usemtl m_3faa339c-1a1f-acb0-d167-578e9ad5cc6b
f 24/33/9 28/34/9 27/35/9 22/36/9
f 21/37/10 25/38/10 26/39/10 23/40/10
f 25/41/11 27/42/11 28/43/11 26/44/11
f 22/45/12 29/46/12 30/47/12 24/48/12

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 B

144
game.odin Normal file
View File

@ -0,0 +1,144 @@
package main
import rl "vendor:raylib"
import "vendor:raylib/rlgl"
import "core:fmt"
import "core:math"
import "core:math/ease"
import "core:math/rand"
import "core:strings"
import "core:strconv"
// Virtual game field dimensions
GameField := vec2{400, 200}
Game :: struct {
using state: GameState,
health: u8,
player: Player,
camera: rl.Camera3D,
camera_offset: vec3,
score: u32,
background: rl.Texture,
plane: rl.Model,
bullets: [dynamic]Bullet,
snake_max_health: int,
snake_health: int
}
game_init :: proc(prev: ^GameState = nil) -> ^GameState {
state := new(Game)
state.previous = prev
state.variant = state
state.draw = game_draw
state.update = game_update
state.free = game_free
rlgl.DisableBackfaceCulling()
img := rl.GenImageChecked(1024, 1024, 128, 128, rl.Color{60, 255, 255, 255}, rl.Color{30, 220, 220, 255})
state.background = rl.LoadTextureFromImage(img)
state.plane = rl.LoadModel(".\\assets\\models\\background.obj")
rl.UnloadImage(img)
game_setup(state)
return state
}
game_setup :: proc(game: ^Game) {
clear(&game.bullets)
game.player = player_spawn({GameField.x / 2 + 50, 20, 0})
game.health = 100
snake_spawn({10, 10, 0}, math.PI, 70)
for segment in Segments {
game.snake_max_health += int(segment.health)
}
game.camera = rl.Camera3D{
target = game.player.pos,
position = game.player.pos + vec3backward * 50,
fovy = 60,
//offset = WSize/2,
projection = rl.CameraProjection.PERSPECTIVE,
up = vec3up
}
game.camera.target.x = clamp(game.camera.target.x, -GameField.x/2, GameField.x/2)
game.camera.target.y = clamp(game.camera.target.y, 0, GameField.y)
game.camera.position = game.camera.target + vec3backward * 50
}
game_gen_level :: proc(game: ^Game) {
}
game_update :: proc(state: ^GameState, delta: f32) {
game := transmute(^Game)state
using game
player_update(&player, game, delta)
#reverse for &bullet, i in bullets {
bullet_process(&bullet, game, delta)
if !bullet.alive {
unordered_remove(&bullets, i)
}
}
snake_process(game, delta)
target_offset := player.vel / 5
camera_offset = rl.Vector3MoveTowards(camera_offset, target_offset, rl.Vector3Length(target_offset - camera_offset) * 10 * delta)
camera.target = player.pos + camera_offset
camera.target.x = clamp(camera.target.x, -GameField.x/2, GameField.x/2)
camera.target.y = clamp(camera.target.y, 0, GameField.y)
camera.position = camera.target + vec3backward * 50
}
game_draw :: proc(state: ^GameState) {
game := transmute(^Game)state
using game
rl.BeginMode3D(camera)
rl.DrawModel(game.plane, {0, 0, 500}, 1000, rl.WHITE)
yy : i32 = 0
snake_draw(game)
player_draw(&player)
for bullet in bullets {
bullet_draw(bullet)
}
rl.EndMode3D()
rl.DrawText(rl.TextFormat("HEALTH: %d", snake_health), 0, 0, 20, rl.BLACK)
rl.DrawText(rl.TextFormat("STATE: %s", Head.state), 0, 20, 20, rl.BLACK)
hb_text : cstring = "Jörmungandr"
height := 30 * (WSize.y / 480)
hb_health : f32 = f32(snake_health) / f32(snake_max_health)
if snake_health == 0 {
hb_health = f32(Head.health) / f32(Head.max_health)
hb_text = "Jörmungandr's head"
}
rl.DrawRectangleV({0, WSize.y - height - 10}, {WSize.x, height + 10}, rl.WHITE)
hb_width := hb_health * WSize.x
rl.DrawRectangleV({WSize.x / 2 - hb_width / 2, WSize.y - height - 7}, {hb_width, height + 4}, rl.RED)
draw_text_centered(FontTitle, hb_text, {WSize.x / 2, WSize.y - height / 2}, height)
}
game_free :: proc(state: ^GameState) {
game := transmute(^Game)state
rl.UnloadTexture(game.background)
free(state)
}

55
main.odin Normal file
View File

@ -0,0 +1,55 @@
package main
import rl "vendor:raylib"
vec3 :: [3]f32
vec2 :: [2]f32
vec3right := vec3{1, 0, 0}
vec3left := vec3{-1, 0, 0}
vec3up := vec3{0, 1, 0}
vec3down := vec3{0, -1, 0}
vec3forward := vec3{0, 0, 1}
vec3backward := vec3{0, 0, -1}
WSize := [2]f32{}
WSizei := [2]i32{}
WindowShouldExit := false
FontUI: rl.Font
FontTitle: rl.Font
main :: proc() {
rl.SetConfigFlags(rl.ConfigFlags{.MSAA_4X_HINT, .WINDOW_MAXIMIZED, .WINDOW_RESIZABLE})
rl.InitWindow(800, 480, "Ragnarøkkr")
WSizei = {rl.GetScreenWidth(), rl.GetScreenHeight()}
WSize = {f32(WSizei.x), f32(WSizei.y)}
FontUI = rl.LoadFontEx(".\\assets\\fonts\\PTSerif-Regular.ttf", 96, nil, 2048)
FontTitle = rl.LoadFontEx(".\\assets\\fonts\\norse.otf", 96*2, nil, 2048)
game := game_init()
stack_push(game)
menu := menu_init(game)
stack_push(menu)
for !WindowShouldExit {
if rl.IsWindowResized() {
WSizei = {rl.GetScreenWidth(), rl.GetScreenHeight()}
WSize = {f32(WSizei.x), f32(WSizei.y)}
}
state := stack_top()
delta := rl.GetFrameTime()
timers_process(delta)
tweens_process(delta)
state->update(delta)
rl.BeginDrawing()
rl.ClearBackground(rl.SKYBLUE)
state->draw()
rl.EndDrawing()
}
}

83
menu.odin Normal file
View File

@ -0,0 +1,83 @@
package main
import rl "vendor:raylib"
import "core:math/ease"
Menu_Buttons :: enum {
START,
HOW_TO_PLAY,
EXIT
}
menu_strings := [Menu_Buttons]cstring {
.START = "Старт",
.HOW_TO_PLAY = "Как играть?",
.EXIT = "Выход"
}
Menu :: struct {
using state: GameState,
list: MenuList(Menu_Buttons),
}
menu_init :: proc(prev: ^GameState = nil) -> ^GameState {
state := new(Menu)
state.variant = state
state.list = MenuList(Menu_Buttons){
state = state,
position = {100, WSize.y / 2},
line_size = 60,
font_size = 48,
elements = &menu_strings,
menu_pressed = menu_button_pressed,
background = rl.Color{50, 10, 110, 10}
}
state.update = menu_update
state.draw = menu_draw
state.free = menu_free
state.previous = prev
return state
}
menu_update :: proc(state: ^GameState, delta: f32) {
menu := transmute(^Menu)state
menu.list.position.y = WSize.y / 2
menu_list_update(&menu.list)
}
menu_button_pressed :: proc(state: ^GameState, el: Menu_Buttons) {
switch el {
case .START:
stack_pop()
case .HOW_TO_PLAY:
// howtoplay := howtoplay_init(state)
// stack_push(howtoplay)
case .EXIT:
WindowShouldExit = true
return
}
}
menu_draw :: proc(state: ^GameState) {
menu := transmute(^Menu)state
menu.previous.draw(menu.previous)
TitleFontSize :: 96
TitleSpacing :: 3
TitleText :: "Ragnarøkkr"
TitleSize := rl.MeasureTextEx(FontTitle, TitleText, TitleFontSize, TitleSpacing)
rl.DrawTextPro(FontTitle, TitleText, {WSize.x - 50, 50}, {TitleSize.x, 0}, 0, 96, 3, rl.WHITE)
menu_list_draw(&menu.list)
}
menu_free :: proc(state: ^GameState) {
free(state)
}

98
menu_list.odin Normal file
View File

@ -0,0 +1,98 @@
package main
import rl "vendor:raylib"
import "core:math/ease"
import "core:fmt"
MenuList :: struct($T: typeid) {
state: ^GameState,
position: vec2,
line_size: f32,
font_size: f32,
active_element: T,
active_marker: vec2,
tween: ^Tween,
elements: ^[T]cstring,
menu_pressed: proc(state: ^GameState, element: T),
background: rl.Color,
mouse_pos: vec2,
}
menu_list_update :: proc(list: ^MenuList($T)) {
activate_element := false
prev_element := cast(i8)list.active_element
cur_element := prev_element
last_mouse_pos := list.mouse_pos
list.mouse_pos = rl.GetMousePosition()
size := menu_list_get_size(list)
if rl.CheckCollisionPointRec(list.mouse_pos, rl.Rectangle{
x = list.position.x,
y = list.position.y,
width = size.x,
height = size.y,
}) {
if last_mouse_pos != list.mouse_pos {
mouse_relative := list.mouse_pos - list.position
cur_element = i8(mouse_relative.y / list.line_size)
}
if rl.IsMouseButtonPressed(rl.MouseButton.LEFT) {
list.menu_pressed(list.state, list.active_element)
}
}
last_mouse_pos = list.mouse_pos
if rl.IsKeyPressed(rl.KeyboardKey.DOWN) {
cur_element += 1
}
if rl.IsKeyPressed(rl.KeyboardKey.UP) {
cur_element -= 1
}
if prev_element != cur_element {
if cur_element < 0 { cur_element = len(T) -1 }
if cur_element == len(T) { cur_element = 0 }
list.active_element = cast(T)cur_element
if list.tween != nil {
tween_cancel(list.tween)
}
list.tween = tween_to(
&list.active_marker.y,
f32(list.active_element) * list.line_size,
0.25,
ease.Ease.Quadratic_Out,
)
}
if rl.IsKeyPressed(rl.KeyboardKey.ENTER) || rl.IsKeyPressed(rl.KeyboardKey.SPACE) {
list.menu_pressed(list.state, list.active_element)
}
}
menu_list_draw :: proc(list: ^MenuList($T)) {
if list.background[3] != 0 {
size := menu_list_get_size(list)
rl.DrawRectangleV(list.position - {40, 40}, size + {80, 80}, list.background)
}
rl.DrawTextEx(FontUI, ">", list.position + list.active_marker + {-30, 0}, 48, 2, rl.WHITE)
for el, i in list.elements {
pos := list.position + {0, f32(i) * list.line_size}
rl.DrawTextEx(FontUI, el, pos, list.font_size, 2, rl.WHITE)
}
}
menu_list_get_size :: proc(list: ^MenuList($T)) -> vec2 {
size := vec2{}
for el, i in list.elements {
line_size := rl.MeasureTextEx(FontUI, el, list.font_size, 2)
if line_size.x > size.x {
size.x = line_size.x
}
size.y += list.line_size
}
return size
}

7
ols.json Normal file
View File

@ -0,0 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/DanielGavin/ols/master/misc/ols.schema.json",
"enable_document_symbols": true,
"enable_hover": true,
"enable_snippets": true,
"enable_format": true
}

134
player.odin Normal file
View File

@ -0,0 +1,134 @@
package main
import rl "vendor:raylib"
import "core:math"
import "core:fmt"
Player :: struct {
pos: vec3,
vel: vec3,
dir: f32,
radius: f32,
thrust: f32,
max_speed: f32,
is_dodging: bool,
can_dodge: bool,
can_shoot: bool,
is_invulnerable: bool,
is_dead: bool
}
player_spawn :: proc(position: vec3) -> Player {
return Player{
pos = position,
radius = 1,
max_speed = 40,
dir = 0,
vel = {-40, 0, 0},
can_dodge = true,
can_shoot = true
}
}
player_update :: proc(player: ^Player, game: ^Game, delta: f32) {
using player
pos += vel * 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 {
if !is_dodging {
dir = angle_rotate(dir, mouse_angle, math.PI * 2 * delta)
dir_vector := get_vec_from_angle(dir)
thrust = 0
if rl.IsKeyDown(rl.KeyboardKey.W) {
thrust = 70
}
if thrust > 0 {
vel = rl.Vector3MoveTowards(vel, dir_vector * max_speed, thrust * delta)
}
}
if thrust == 0 {
vel = rl.Vector3MoveTowards(vel, {0, -30, 0}, 20 * delta)
}
if rl.IsMouseButtonPressed(rl.MouseButton.RIGHT) && can_dodge {
is_dodging = true
can_dodge = false
timer_start(0.45, player, proc(data: rawptr) {
player := transmute(^Player)data
player.is_dodging = false
})
timer_start(0.55, player, proc(data: rawptr) {
player := transmute(^Player)data
player.can_dodge = true
})
}
if rl.IsMouseButtonDown(rl.MouseButton.LEFT) && can_shoot {
b := bullet_spawn(pos, dir)
append(&game.bullets, b)
can_shoot = false
timer_start(0.1, player, proc(data: rawptr) {
player := transmute(^Player)data
player.can_shoot = true
})
}
}
got_hit := false
if !is_invulnerable && !is_dodging && !is_dead {
for segment in Segments {
if rl.Vector3DistanceSqrt(pos, segment.pos) < math.pow(radius + segment.radius, 2) {
got_hit = true
break
}
}
}
if got_hit {
game.health -= 10
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
}
}
}
player_draw :: proc(player: ^Player) {
using player
dir_vector := get_vec_from_angle(dir)
color := rl.GREEN
if is_dodging {
color = rl.GRAY
}
if is_invulnerable {
color = rl.YELLOW
}
rl.DrawCircle3D(pos, radius, vec3up, 0, color)
rl.DrawLine3D(pos, pos + dir_vector * radius, rl.BLACK)
rl.DrawLine3D(pos, pos + vel, rl.RED)
}

52
player_bullets.odin Normal file
View File

@ -0,0 +1,52 @@
package main
import rl "vendor:raylib"
bullets : [dynamic]Bullet
Bullet :: struct{
pos: vec3,
vel: vec3,
radius: f32,
alive: bool,
}
bullet_spawn :: proc(pos: vec3, dir: f32) -> Bullet {
return Bullet {
pos = pos,
vel = get_vec_from_angle(dir) * 70,
alive = true,
radius = 0.4
}
}
bullet_process :: proc(bullet: ^Bullet, game: ^Game, delta: f32) {
bullet.vel = rl.Vector3MoveTowards(bullet.vel, {}, 60 * delta)
bullet.pos += bullet.vel * delta
if rl.Vector3LengthSqr(bullet.vel) < 0.2 {
bullet.alive = false
}
if rl.CheckCollisionCircles(bullet.pos.xy, bullet.radius, Head.pos.xy, Head.radius) {
bullet.alive = false
if game.snake_health == 0 {
Head.health -= 1
}
return
}
for &segment in Segments {
if rl.CheckCollisionCircles(bullet.pos.xy, bullet.radius, segment.pos.xy, segment.collider_radius) {
bullet.alive = false
if segment.health > 0 {
segment.health -= 1
if segment.health == 0 {
segment.collider_radius = 1.5
}
}
}
}
}
bullet_draw :: proc(bullet: Bullet) {
rl.DrawSphere(bullet.pos, bullet.radius, rl.WHITE)
}

263
snake.odin Normal file
View File

@ -0,0 +1,263 @@
package main
import rl "vendor:raylib"
import "core:math"
import "core:fmt"
import "core:math/linalg"
Snake_Health := 1000
Segments: [dynamic]^SnakeSegment
Head: SnakeHead
SnakeState :: enum {
Chasing,
Chase_to_Dive,
Dive_to_Chase,
Diving,
Hunt,
Shot
}
SnakeHead :: struct {
pos: vec3,
vel: vec3,
dir: f32,
radius: f32,
health: int,
max_health: int,
next: ^SnakeSegment,
state: SnakeState,
next_state: SnakeState,
state_timer: f32,
shot_timer: f32,
is_shooting: bool,
is_dead: bool,
}
SnakeSegment :: struct {
pos: vec3,
vel: vec3,
dir: f32,
radius: f32,
collider_radius: f32,
active: bool,
health: u8,
head: ^SnakeHead,
next: ^SnakeSegment,
prev: ^SnakeSegment
}
snake_spawn :: proc(pos: vec3, dir: f32, length: int){
dir_vec := rl.Vector3RotateByAxisAngle(vec3right, vec3backward, dir)
Head = SnakeHead{
pos = pos,
dir = dir,
radius = 3,
state = .Chasing,
vel = dir_vec * 20,
health = 100,
max_health = 100,
state_timer = 5,
}
for i := 0; i < length; i += 1 {
segment := new(SnakeSegment)
segment.active = false
segment.health = 3
segment.radius = 2.5
segment.collider_radius = 2.5
segment.pos = pos - dir_vec * segment.radius
segment.dir = dir
segment.head = &Head
if i != 0 {
segment.prev = Segments[i-1]
Segments[i-1].next = segment
} else {
Head.next = segment
}
append(&Segments, segment)
}
}
snake_clear :: proc() {
for segment in Segments {
free(segment)
}
}
snake_process :: proc(game: ^Game, delta: f32) {
switch Head.state {
case .Chasing:
snake_chase_smooth(game, delta)
case .Diving:
snake_dive(game, delta)
case .Chase_to_Dive,.Dive_to_Chase:
snake_dropdown(game, delta)
case .Hunt:
snake_hunt(game, delta)
case .Shot:
snake_shot(game, delta)
}
total_health := 0
for segment in Segments {
total_health += int(segment.health)
}
game.snake_health = total_health
if game.snake_health == 0 && Head.next != nil {
Head.next = nil
Head.state = .Hunt
Head.state_timer = 20
fmt.println("Tail is dead")
return
}
for segment, i in Segments {
if segment.prev == nil && total_health == 0 { // Хвост падает, когда у него не осталось жизней
segment.vel.y -= 30 * delta
segment.pos += segment.vel * delta
continue
}
target_pos := Head.pos
if segment.prev != nil {
target_pos = segment.prev.pos
} else {
segment.vel = Head.vel
}
diff := target_pos - segment.pos
if rl.Vector3Length(diff) > segment.radius {
segment.pos = target_pos - rl.Vector3Normalize(diff) * segment.radius
}
segment.dir = math.atan2(-diff.y, diff.x)
}
}
snake_chase :: proc(game: ^Game, delta: f32) {
using game
diff := player.pos - Head.pos
target_angle := math.atan2(-diff.y, diff.x)
dir_diff := angle_cycle(target_angle - Head.dir, -math.PI, math.PI)
Head.dir = angle_rotate(Head.dir, target_angle, min(abs(dir_diff), math.PI) * delta)
Head.vel = rl.Vector3RotateByAxisAngle(vec3right, vec3backward, Head.dir) * 500
Head.pos += Head.vel * delta
}
snake_chase_smooth :: proc(game: ^Game, delta: f32) {
using game
if rl.IsKeyPressed(rl.KeyboardKey.K) {
for segment in Segments {
segment.health = 0
}
}
Head.state_timer -= delta
if Head.state_timer <= 0 {
Head.state = .Chase_to_Dive
Head.next_state = .Diving
Head.state_timer = 10
}
if Head.pos.y < -3 && Head.vel.y < 0 {
Head.pos.y = -3
Head.vel.y = -Head.vel.y
}
diff := player.pos - Head.pos
norm := rl.Vector3Normalize(diff)
Head.vel = rl.Vector3MoveTowards(Head.vel, norm * 70, 70 * delta)
Head.vel = rl.Vector3ClampValue(Head.vel, 30, 50)
Head.dir = math.atan2(-Head.vel.y, Head.vel.x)
Head.pos += Head.vel * delta
}
snake_dive :: proc(game: ^Game, delta: f32) {
Head.state_timer -= delta
if Head.state_timer <= 0 {
Head.state = .Dive_to_Chase
Head.next_state = .Chasing
}
if Head.pos.y < 0 && Segments[len(Segments)-1].pos.y < 0 && Head.vel.y < 0 {
Head.pos.x = game.player.pos.x
Head.pos.y = -5
Head.vel.x = 0
Head.vel.y = 70
} else {
grav : f32 = 20
if Head.vel.y < 0 { grav = 30 }
else if Head.pos.y < game.player.pos.y {
grav = 7
}
Head.vel.y -= grav * delta
Head.vel.x = (game.player.pos.x - Head.pos.x) * 2
}
Head.dir = math.atan2(-Head.vel.y, Head.vel.x)
Head.pos += Head.vel * delta
}
snake_dropdown :: proc(game: ^Game, delta: f32) {
Head.vel.y -= 100 * delta
Head.pos += Head.vel * delta
if Segments[len(Segments)-1].pos.y < 0 {
Head.state = Head.next_state
Head.state_timer = 20
}
}
snake_hunt :: proc(game: ^Game, delta: f32) {
Head.state_timer -= delta
if Head.state_timer <= 0 {
Head.state_timer = 10
Head.state = .Shot
}
diff := game.player.pos - Head.pos
target_pos := game.player.pos + rl.Vector3Normalize(diff) * 10
target_diff := target_pos - Head.pos
Head.vel = rl.Vector3MoveTowards(Head.vel, rl.Vector3Normalize(target_diff) * 50, 40 * delta)
fmt.println(Head.vel)
Head.dir = math.atan2(-Head.vel.y, Head.vel.x)
Head.pos += Head.vel * delta
}
snake_shot :: proc(game: ^Game, delta: f32) {
Head.state_timer -= delta
if Head.state_timer <= 0 {
Head.state_timer = 20
Head.state = .Hunt
}
Head.is_shooting = false
if Head.state_timer < 8 && Head.state_timer > 3 {
Head.is_shooting = true
}
diff := game.player.pos - Head.pos
angle := math.atan2(-diff.y, diff.x)
Head.dir = angle_rotate(Head.dir, angle, angle_cycle(abs(angle - Head.dir), -math.PI, math.PI) * delta * 2)
Head.vel = rl.Vector3MoveTowards(Head.vel, {}, 30 * delta)
Head.pos += Head.vel * delta
}
snake_draw :: proc(game: ^Game) {
dir_vector := get_vec_from_angle(Head.dir)
rl.DrawCircle3D(Head.pos, Head.radius, vec3up, 0, rl.RED)
rl.DrawLine3D(Head.pos, Head.pos + dir_vector * Head.radius, rl.BLACK)
if Head.is_shooting {
rl.DrawLine3D(Head.pos, Head.pos + get_vec_from_angle(Head.dir) * 300, rl.YELLOW)
}
for segment in Segments {
dir_vector := get_vec_from_angle(segment.dir)
col := rl.RED
if segment.health == 0 {
col = rl.GRAY
}
rl.DrawCircle3D(segment.pos, segment.radius, vec3up, 0, col)
rl.DrawLine3D(segment.pos, segment.pos + dir_vector * segment.radius, rl.BLACK)
}
}

44
state.odin Normal file
View File

@ -0,0 +1,44 @@
package main
import "core:slice"
StateVariant :: union{^Game, ^Menu}
GameState :: struct {
update: proc(state: ^GameState, delta: f32),
draw: proc(state: ^GameState),
free: proc(state: ^GameState),
previous: ^GameState,
variant: StateVariant
}
new_state :: proc($T: typeid) -> ^T {
state := new(T)
state.variant = state
return state
}
state_stack : [dynamic]^GameState
stack_push :: proc(state: ^GameState) -> (bool) {
append(&state_stack, state)
return true
}
stack_pop :: proc() -> (bool) {
if len(state_stack) == 0 {
return false
}
state := pop(&state_stack)
state->free()
return true
}
stack_top :: proc() -> ^GameState {
return state_stack[len(state_stack)-1]
}

63
timer.odin Normal file
View File

@ -0,0 +1,63 @@
package main
import "core:math"
import "core:math/linalg"
import "core:slice"
Timer :: struct {
duration: f32,
time: f32,
active: bool,
finished: proc(data: rawptr),
data: rawptr
}
TIMER_SIZE :: 128
timers : [dynamic]^Timer
timers_clean :: proc() {
for timer, i in timers {
free(timer)
}
}
timer_start :: proc(
time: f32,
data: rawptr = nil,
callback: proc(data: rawptr) = nil
) -> ^Timer {
timer := new(Timer)
timer.duration = time
timer.active = true
timer.data = data
timer.finished = callback
append(&timers, timer)
return timer
}
timer_cancel :: proc(t: ^Timer) {
t.active = false
}
timers_process :: proc(delta: f32) {
#reverse for timer, i in timers {
if !timer.active {
free(timer)
unordered_remove(&timers, i)
continue
}
timer.time += delta
if timer.time >= timer.duration {
timer.active = false
if timer.finished != nil {
timer.finished(timer.data)
}
}
}
}

80
tween.odin Normal file
View File

@ -0,0 +1,80 @@
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)
}
}
}

32
utils.odin Normal file
View File

@ -0,0 +1,32 @@
package main
import "core:math"
import "core:math/linalg"
import rl "vendor:raylib"
angle_cycle :: proc(value, min, max: f32) -> f32 {
delta := (max - min)
result := linalg.mod(value - min, delta)
if result < 0 {
result += delta
}
return min + result
}
angle_rotate :: proc(angle, target, speed: f32) -> f32 {
diff := angle_cycle(target - angle, -math.PI, math.PI)
if diff < -speed {return angle - speed}
if diff > speed {return angle + speed}
return target
}
get_vec_from_angle :: proc(angle: f32) -> vec3 {
return rl.Vector3RotateByAxisAngle(vec3right, vec3backward, angle)
}
draw_text_centered :: proc(font: rl.Font, text: cstring, pos: vec2, size: f32) {
text_size := rl.MeasureTextEx(font, text, size, 1)
rl.DrawTextPro(font, text, pos, text_size / 2, 0, size, 1, rl.BLACK)
}