sdl2/chase_in_space

Code

chase_in_space.odin ¶
229 linesSource

1/*
2	Simple game about enemies chasing and shooting you in 2D space.
3
4	Original repo: https://github.com/wbogocki/Odin-Play
5
6	To play:
7		- Move with WSAD or arrow keys
8		- Jump with Shift + direction
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		// Dash
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		// Keep it in the map
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		// Towards player
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		// Away from other enemies
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		// Shoot
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	// Must not do VSync because we run the tick loop on the same thread as rendering.
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		// Running on the same thread as rendering so in the end still limited by the rendering FPS.
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}

Declarations Used 36