asteroids-odin/game.odin

274 lines
8.0 KiB
Odin

package asteroids
import rl "vendor:raylib"
import "core:math"
import "core:math/rand"
import "core:fmt"
Vec2 :: [2]f32
Rect :: rl.Rectangle
SCREEN_WIDTH : i32 = 1000
SCREEN_HEIGHT : i32 = 800
BW := f32(SCREEN_WIDTH)
BH := f32(SCREEN_HEIGHT)
SCALE: f32 = 0.37
SHIP_W: f32 = 70 * SCALE
SHIP_H: f32 = 100 * SCALE
THRUST_SPEED : f32 = 2.3
ANGULAR_SPEED : f32 = 0.045
BULLET_SPEED : f32 = 7.5
BULLET_RADIUS : f32 = 5
Bullet :: struct {
pos: Vec2,
vel: Vec2,
}
Asteroid :: struct {
rect: Rect,
vel: Vec2,
rot: f32,
}
Player :: struct {
pos: Vec2,
points: [4]Vec2,
angle: f32,
vel: Vec2
}
PlayerSpinningPart :: struct {
points: [2]Vec2,
rotation_speed: f32,
vel: Vec2,
}
Death :: struct {
flying_parts: [4]PlayerSpinningPart
}
PlayerState :: union #no_nil {
Player,
Death,
}
GameState :: struct {
dt: f32,
player_state: PlayerState,
bullets: [dynamic]Bullet,
asteroids: [dynamic]Asteroid,
bullet_pop_idxs: [dynamic]int,
asteroid_pop_idxs: [dynamic]int,
}
update_ship_shape :: proc(p : ^Player) {
p.points[0] = rl.Vector2Rotate({ SHIP_H*0.5, 0}, p.angle) + p.pos
p.points[1] = rl.Vector2Rotate({-SHIP_H*0.5, -SHIP_W*0.5}, p.angle) + p.pos
p.points[2] = rl.Vector2Rotate({-SHIP_H*0.5*0.5, 0}, p.angle) + p.pos
p.points[3] = rl.Vector2Rotate({-SHIP_H*0.5, SHIP_W*0.5}, p.angle) + p.pos
}
random_direction :: proc() -> Vec2 {
angle := rand.float32() * math.PI
return {math.cos(angle), math.sin(angle)}
}
swap_n_pop :: proc(bullets: ^[dynamic]$T, idx: int) {
last := len(bullets) - 1
if last == 0 {
clear(bullets)
} else {
bullets[last], bullets[idx] = bullets[idx], bullets[last]
pop(bullets)
}
}
init_game :: proc() -> ^GameState {
start_pos := Vec2{BW / 2 - SHIP_W / 2, BH / 2 - SHIP_H / 2}
state := GameState {
dt = rl.GetFrameTime(),
player_state = Player {
pos = start_pos,
angle = -math.PI / 2,
},
bullets = make([dynamic]Bullet, 0, 64),
asteroids = make([dynamic]Asteroid, 0, 64),
asteroid_pop_idxs = make([dynamic]int, 0, 64),
bullet_pop_idxs = make([dynamic]int, 0, 64),
}
get_rand_angle :: proc(min: i32, max: i32) -> f32 {
return f32(rl.GetRandomValue(min, max)) * rl.DEG2RAD
}
for i := 0; i < 5; i += 1 {
rand_angle := get_rand_angle(-50, 50)
rand_pos := Vec2{ f32(rl.GetRandomValue(0, i32(BW))), -50 }
rand_size := f32(rl.GetRandomValue(25, 60))
rand_vy := f32(rl.GetRandomValue(1, 2)) * 0.5
asteroid := Asteroid {
rect = {rand_pos.x,rand_pos.y,rand_size,rand_size},
vel = Vec2{rand_angle, rand_vy} * 50,
rot = 0,
}
append(&state.asteroids, asteroid)
}
return new_clone(state)
}
player_input :: proc(s: ^GameState) {
switch &player in s.player_state {
case Player:
if rl.IsKeyDown(.D) {
player.angle += ANGULAR_SPEED
}
if rl.IsKeyDown(.A) {
player.angle -= ANGULAR_SPEED
}
if rl.IsKeyDown(.W) {
player.vel.x += math.cos(player.angle)
player.vel.y += math.sin(player.angle)
}
if rl.IsKeyPressed(.SPACE) {
b_vel := Vec2{ math.cos(player.angle) , math.sin(player.angle) } * BULLET_SPEED
append(&s.bullets, Bullet{player.points[0], b_vel})
}
case Death:
if rl.IsKeyDown(.ENTER) {
// restart game
}
}
}
update :: proc(s: ^GameState) {
switch &player in s.player_state {
case Player:
update_ship_shape(&player)
player.pos += player.vel * (THRUST_SPEED * s.dt)
ship_collision := false
for i := 0; i < len(s.asteroids); i += 1 {
s.asteroids[i].rect.x += s.asteroids[i].vel.x * s.dt
s.asteroids[i].rect.y += s.asteroids[i].vel.y * s.dt
for j := 0; j < len(player.points); j += 1 {
if rl.CheckCollisionPointRec(player.points[j], s.asteroids[i].rect) {
ship_collision = true
}
}
}
for i := 0; i < len(s.bullets); i += 1 {
if ( s.bullets[i].pos.x < 0 || s.bullets[i].pos.x > BW
|| s.bullets[i].pos.y < 0 || s.bullets[i].pos.y > BH) {
append(&s.bullet_pop_idxs, i)
}
s.bullets[i].pos += s.bullets[i].vel
for j := 0; j < len(s.asteroids); j += 1 {
if rl.CheckCollisionCircleRec(s.bullets[i].pos, BULLET_RADIUS, s.asteroids[j].rect) {
append(&s.bullet_pop_idxs, i)
append(&s.asteroid_pop_idxs, j)
}
}
}
bpi_len := len(s.bullet_pop_idxs)
for i := 0; i < bpi_len; i += 1 {
swap_n_pop(&s.bullets, s.bullet_pop_idxs[i])
}
if bpi_len > 0 {
clear(&s.bullet_pop_idxs)
}
api_len := len(s.asteroid_pop_idxs)
for i := 0; i < api_len; i += 1 {
swap_n_pop(&s.asteroids, s.asteroid_pop_idxs[i])
}
if bpi_len > 0 {
clear(&s.asteroid_pop_idxs)
}
if ship_collision {
// Randomize everything
s.player_state = Death {
flying_parts = {
PlayerSpinningPart { {player.points[0],player.points[1]}, 0.2, random_direction() },
PlayerSpinningPart { {player.points[1],player.points[2]}, 0.2, random_direction() },
PlayerSpinningPart { {player.points[2],player.points[3]}, 0.2, random_direction() },
PlayerSpinningPart { {player.points[3],player.points[0]}, 0.2, random_direction() },
}
}
} else {
if player.pos.x + SHIP_W < 0 { player.pos.x = BW + SHIP_W}
if player.pos.x - SHIP_W > BW { player.pos.x = -SHIP_W}
if player.pos.y + SHIP_H < 0 { player.pos.y = BH + SHIP_H}
if player.pos.y - SHIP_H > BH { player.pos.y = -SHIP_H}
}
case Death:
for i := 0; i < len(player.flying_parts); i += 1 {
part := &player.flying_parts[i]
part.points[0] += part.vel
part.points[1] += part.vel
}
for i := 0; i < len(s.bullets); i += 1 {
if ( s.bullets[i].pos.x < 0 || s.bullets[i].pos.x > BW
|| s.bullets[i].pos.y < 0 || s.bullets[i].pos.y > BH) {
append(&s.bullet_pop_idxs, i)
}
s.bullets[i].pos += s.bullets[i].vel
}
for i := 0; i < len(s.asteroids); i += 1 {
s.asteroids[i].rect.x += s.asteroids[i].vel.x * s.dt
s.asteroids[i].rect.y += s.asteroids[i].vel.y * s.dt
}
}
}
draw2d :: proc(s: ^GameState) {
switch &player in s.player_state {
case Player:
// Draw ship
rl.DrawLineEx(player.points[0], player.points[1], 1, rl.WHITE)
rl.DrawLineEx(player.points[1], player.points[2], 1, rl.WHITE)
rl.DrawLineEx(player.points[2], player.points[3], 1, rl.WHITE)
rl.DrawLineEx(player.points[3], player.points[0], 1, rl.WHITE)
case Death:
for i := 0; i < len(player.flying_parts); i += 1 {
part := &player.flying_parts[i]
rl.DrawLineEx(part.points[0], part.points[1], 1, rl.WHITE)
}
}
for i := 0; i < len(s.asteroids); i += 1 {
rl.DrawRectangleLinesEx(s.asteroids[i].rect, 1, rl.WHITE)
}
for i := 0; i < len(s.bullets); i += 1 {
rl.DrawCircleLinesV(s.bullets[i].pos, BULLET_RADIUS, rl.WHITE)
}
}
main :: proc() {
rl.SetTraceLogLevel(.ERROR)
rl.InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Asteroids")
rl.SetTargetFPS(60)
state := init_game()
for !rl.WindowShouldClose() {
state.dt = rl.GetFrameTime()
player_input(state)
update(state)
rl.BeginDrawing()
rl.ClearBackground(rl.BLACK)
draw2d(state)
rl.EndDrawing()
}
rl.CloseWindow()
}