1
2
3
4
5
6
7
8
9
10
11package chase_in_space
12
13import "core:fmt"
14import "core:math/linalg"
15import "vendor:sdl2"
16
17Game :: struct {
18 renderer: ^sdl2.Renderer,
19 keyboard: []u8,
20 time: f64,
21 dt: f64,
22 entities: [dynamic]Entity,
23}
24
25EntityType :: enum {
26 PLAYER,
27 ENEMY,
28 PROJECTILE,
29}
30
31Entity :: struct {
32 type: EntityType,
33 hp: int,
34 pos: [2]f32,
35 vel: [2]f32,
36 reload_counter: f32,
37 bullet_decay: f32,
38 dash_counter: f32,
39}
40
41render_entity :: proc(entity: ^Entity, game: ^Game) {
42 switch entity.type {
43 case .PLAYER:
44 sdl2.SetRenderDrawColor(game.renderer, 255, 0, 255, 0)
45 sdl2.RenderDrawRectF(
46 game.renderer,
47 &sdl2.FRect{x = entity.pos.x, y = entity.pos.y, w = 10, h = 10},
48 )
49 case .ENEMY:
50 sdl2.SetRenderDrawColor(game.renderer, 0, 255, 255, 0)
51 sdl2.RenderDrawRectF(
52 game.renderer,
53 &sdl2.FRect{x = entity.pos.x, y = entity.pos.y, w = 10, h = 10},
54 )
55 case .PROJECTILE:
56 sdl2.SetRenderDrawColor(game.renderer, 0, 255, 255, 0)
57 sdl2.RenderDrawPointF(game.renderer, entity.pos.x, entity.pos.y)
58 }
59}
60
61find_entity :: proc(type: EntityType, game: ^Game) -> ^Entity {
62 for _, i in game.entities {
63 if game.entities[i].type == type {
64 return &game.entities[i]
65 }
66 }
67 return nil
68}
69
70update_entity :: proc(entity: ^Entity, game: ^Game) {
71 dt := f32(game.dt)
72 switch entity.type {
73 case .PLAYER:
74 dir := [2]f32{0, 0}
75 if b8(game.keyboard[sdl2.SCANCODE_UP ]) | b8(game.keyboard[sdl2.SCANCODE_W]) { dir.y -= 1 }
76 if b8(game.keyboard[sdl2.SCANCODE_DOWN ]) | b8(game.keyboard[sdl2.SCANCODE_S]) { dir.y += 1 }
77 if b8(game.keyboard[sdl2.SCANCODE_LEFT ]) | b8(game.keyboard[sdl2.SCANCODE_A]) { dir.x -= 1 }
78 if b8(game.keyboard[sdl2.SCANCODE_RIGHT]) | b8(game.keyboard[sdl2.SCANCODE_D]) { dir.x += 1 }
79 dir = linalg.normalize0(dir)
80 entity.pos += dir * 0.2 * dt
81
82 if b8(game.keyboard[sdl2.SCANCODE_LSHIFT]) && entity.dash_counter == 0 && dir != 0 {
83 entity.vel = dir * 5.0
84 entity.dash_counter += 150
85 } else {
86 entity.dash_counter = max(entity.dash_counter - dt, 0)
87 }
88 entity.pos += entity.vel * dt
89 entity.vel -= entity.vel * 0.9999 / dt
90
91 entity.pos.x = clamp(entity.pos.x, 0, 640 - 10)
92 entity.pos.y = clamp(entity.pos.y, 0, 480 - 10)
93 case .ENEMY:
94
95 player := find_entity(.PLAYER, game)
96 if player == nil { return }
97 dir := player.pos - entity.pos
98 dir = linalg.normalize0(dir)
99 entity.pos += dir * 0.12 * dt
100
101 for _, i in game.entities {
102 if game.entities[i].type == .ENEMY && entity != &game.entities[i] {
103 edir := entity.pos - game.entities[i].pos
104 dis := linalg.length(edir)
105 if dis > 0 {
106 entity.pos += edir * (1. / (dis * dis)) * 0.1 * dt
107 }
108 }
109 }
110
111 if entity.reload_counter <= 0 {
112 append(
113 &game.entities,
114 Entity{
115 type = .PROJECTILE,
116 pos = entity.pos,
117 vel = 0.5 * dir,
118 hp = 1,
119 bullet_decay = 750,
120 },
121 )
122 entity.reload_counter = 1000
123 } else {
124 entity.reload_counter -= dt
125 }
126 case .PROJECTILE:
127 entity.pos += entity.vel * dt
128 entity.bullet_decay -= 1.0 * dt
129 if entity.bullet_decay < 0 {
130 entity.hp = 0
131 } else {
132 player := find_entity(.PLAYER, game)
133 if player == nil { return }
134 if player.pos.x < entity.pos.x && entity.pos.x < player.pos.x + 10 && player.pos.y < entity.pos.y &&
135 entity.pos.y < player.pos.y + 10 {
136 player.hp -= 1
137 fmt.printf("HIT (HP: {})\n", player.hp)
138 entity.hp = 0
139 }
140 }
141 }
142}
143
144get_time :: proc() -> f64 {
145 return f64(sdl2.GetPerformanceCounter()) * 1000 / f64(sdl2.GetPerformanceFrequency())
146}
147
148main :: proc() {
149 assert(sdl2.Init(sdl2.INIT_VIDEO) == 0, sdl2.GetErrorString())
150 defer sdl2.Quit()
151
152 window := sdl2.CreateWindow(
153 "Odin Game",
154 sdl2.WINDOWPOS_CENTERED,
155 sdl2.WINDOWPOS_CENTERED,
156 640,
157 480,
158 sdl2.WINDOW_SHOWN,
159 )
160 assert(window != nil, sdl2.GetErrorString())
161 defer sdl2.DestroyWindow(window)
162
163
164 renderer := sdl2.CreateRenderer(window, -1, sdl2.RENDERER_ACCELERATED)
165 assert(renderer != nil, sdl2.GetErrorString())
166 defer sdl2.DestroyRenderer(renderer)
167
168 tickrate := 240.0
169 ticktime := 1000.0 / tickrate
170
171 game := Game {
172 renderer = renderer,
173 time = get_time(),
174 dt = ticktime,
175 entities = make([dynamic]Entity),
176 }
177 defer delete(game.entities)
178
179 append(&game.entities, Entity{type = .PLAYER, pos = { 50.0, 400.0}, hp = 10})
180 append(&game.entities, Entity{type = .ENEMY , pos = { 50.0, 50.0}, hp = 1})
181 append(&game.entities, Entity{type = .ENEMY , pos = {100.0, 100.0}, hp = 1})
182 append(&game.entities, Entity{type = .ENEMY , pos = {200.0, 200.0}, hp = 1})
183
184 dt := 0.0
185
186 for {
187 event: sdl2.Event
188 for sdl2.PollEvent(&event) {
189 #partial switch event.type {
190 case .QUIT:
191 return
192 case .KEYDOWN:
193 if event.key.keysym.scancode == sdl2.SCANCODE_ESCAPE {
194 return
195 }
196 }
197 }
198
199 time := get_time()
200 dt += time - game.time
201
202 game.keyboard = sdl2.GetKeyboardStateAsSlice()
203 game.time = time
204
205
206 for dt >= ticktime {
207 dt -= ticktime
208
209 for _, i in game.entities {
210 update_entity(&game.entities[i], &game)
211 }
212
213 for i := 0; i < len(game.entities); {
214 if game.entities[i].hp <= 0 {
215 ordered_remove(&game.entities, i)
216 } else {
217 i += 1
218 }
219 }
220 }
221
222 sdl2.SetRenderDrawColor(renderer, 0, 0, 0, 0)
223 sdl2.RenderClear(renderer)
224 for _, i in game.entities {
225 render_entity(&game.entities[i], &game)
226 }
227 sdl2.RenderPresent(renderer)
228 }
229}