Ragnarokkr/snake.odin

328 lines
9.6 KiB
Odin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import rl "vendor:raylib"
import "vendor:raylib/rlgl"
import "core:math"
import "core:fmt"
import "core:math/linalg"
import "core:math/rand"
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 = 30,
}
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) {
// Head.state = .Shot
// Head.state_timer = 200
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 {
if Head.next != nil {
Head.next = nil
Head.state = .Hunt
Head.state_timer = 20
change_track(Res.Music.Second)
return
}
trail(Head.pos - get_vec_from_angle(Head.dir) * Head.radius * 0.7, 3, rand.float32_range(0.7, 1.8), rl.RED)
if Head.health <= 0 && !Head.is_dead {
Head.is_dead = true
explode(Head.pos, 9, 0.9, rl.YELLOW)
rl.PlaySound(Res.Sfx.PlayerDead)
timer_start(3, game, proc(data: rawptr) {
state := transmute(^Game)data
win := winning_init(state)
stack_push(win)
})
}
}
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
rl.PlaySound(Res.Sfx.SnakeGrowl)
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 = .Chasing
Head.state_timer = 20
rl.PlaySound(Res.Sfx.SnakeGrowl)
}
if Head.pos.y < 0 && Segments[len(Segments)-1].pos.y < 0 && Head.vel.y < 0 {
rl.PlaySound(Res.Sfx.SnakeEarthHit)
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
Head.dir = math.atan2(-Head.vel.y, Head.vel.x)
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
rl.PlaySound(Res.Sfx.SnakeRoarBlast)
}
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)
Head.dir = math.atan2(-Head.vel.y, Head.vel.x)
Head.pos += Head.vel * delta
if Head.pos.y < Head.radius {
Head.pos.y = Head.radius
Head.vel.y = -Head.vel.y / 2
}
}
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
if !rl.IsSoundPlaying(Res.Sfx.SnakeBeam) {
rl.PlaySound(Res.Sfx.SnakeBeam)
}
}
diff := game.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, abs(dir_diff) * delta * 1.5)
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)
roll := -math.PI / 2 + math.cos(Head.dir) * math.PI / 2
rlgl.PushMatrix()
rlgl.Translatef(Head.pos.x, Head.pos.y, Head.pos.z)
rlgl.Rotatef(math.to_degrees(Head.dir), 0, 0, -1)
rlgl.Rotatef(math.to_degrees(roll), 1, 0, 0)
// rl.DrawCircle3D({}, Head.radius, vec3up, 0, rl.RED)
// rl.DrawLine3D({}, {-4, 0, 0}, rl.GREEN)
rl.DrawModel(Res.Models.SnakeHeadTop, {}, 5, rl.WHITE)
rlgl.PushMatrix()
if Head.state == .Shot {
step1 := math.smoothstep(f32(10), f32(8), f32(Head.state_timer))
step2 := math.smoothstep(f32(0), f32(3), f32(Head.state_timer))
angle := step1 * step2 * math.PI / 2.5
fmt.println(Head.state_timer, step1, step2, angle)
rlgl.Rotatef(math.to_degrees(angle), 0, 0, -1)
}
rl.DrawModel(Res.Models.SnakeHeadJaw, {}, 5, rl.WHITE)
rlgl.PopMatrix()
rlgl.Translatef(0.5, -1, 0)
if Head.is_shooting {
rl.DrawCylinderEx({}, vec3right * 300, 1.4, 1.4, 6, rl.YELLOW)
}
rlgl.PopMatrix()
// rl.DrawCircle3D(Head.pos, Head.radius, vec3up, 0, rl.RED)
// rl.DrawLine3D(Head.pos, Head.pos + dir_vector * Head.radius, rl.BLACK)
// if game.snake_health == 0 {
// center := Head.pos + dir_vector * Head.radius * 1.5
// left := Head.pos + rl.Vector3RotateByAxisAngle(dir_vector, vec3backward, math.PI/3) * Head.radius * 1.5
// right := Head.pos + rl.Vector3RotateByAxisAngle(dir_vector, vec3backward, -math.PI/3) * Head.radius * 1.5
// rl.DrawLine3D(left, center, rl.YELLOW)
// rl.DrawLine3D(right, center, rl.YELLOW)
// }
for segment in Segments {
dir_vector := get_vec_from_angle(segment.dir)
col := rl.WHITE
if segment.health == 0 {
col = rl.Color{255, 150, 150, 255}
}
roll := -math.PI / 2 + math.cos(segment.dir) * math.PI / 2
rlgl.PushMatrix()
rlgl.Translatef(segment.pos.x, segment.pos.y, segment.pos.z)
rlgl.Rotatef(math.to_degrees(segment.dir), 0, 0, -1)
rlgl.Rotatef(math.to_degrees(roll), 1, 0, 0)
rl.DrawModel(Res.Models.SnakeBody, {}, 4, col)
rlgl.PopMatrix()
}
}