Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save EncodeTheCode/05715fd10be952dc4725ead1bc3d908c to your computer and use it in GitHub Desktop.
Save EncodeTheCode/05715fd10be952dc4725ead1bc3d908c to your computer and use it in GitHub Desktop.
import pygame as g, sys, math
import numpy as n
# Init
g.init()
s = g.display.set_mode((800, 600))
w, h = s.get_size()
g.display.set_caption("3D LOS with NumPy Optimization")
c = g.time.Clock()
g.event.set_grab(True)
g.mouse.set_visible(False)
# Constants
tile_view = 25
entity_view = 15
danger_range = 50
FOV = math.radians(45)
MOVE_SPEED = 3.0
MOUSE_SENS = 0.001
MAX_D = 100.0
D_RATE = 20.0
D_COOLDOWN = 80.0
BAR_ALPHA = 150
# Colors & Fonts
cols = {
'WHITE': (255, 255, 255),
'GRAY': (180, 180, 180),
'DGRAY': (80, 80, 80),
'ORANGE': (255, 165, 0),
'BLACK': (0, 0, 0)
}
font = g.font.SysFont(None, 24)
flash_colors = [(133, 40, 32), (136, 41, 31), (138, 38, 33), (130, 39, 31)]
# State
p = n.array([0.0, 1.0, -5.0], float) # player pos
yaw = pitch = 0.0
d = 0.0 # danger
blink = False
bt = 0.0
e = n.array([0.0, 1.0, 8.0], float) # enemy pos
wpos = n.array([0.0, 1.5, 4.0], float)
wsize = n.array([4.0, 3.0, 0.5], float)
enemy_dir = n.array([0.0, 0.0, -1.0], float)
# Helpers
def flash_col(): return flash_colors[(g.time.get_ticks() // 500) % 4]
def normalize(v): return v / n.linalg.norm(v) if n.linalg.norm(v) > 0 else v
def project(pt):
rel = pt - p
cy, sy = math.cos(yaw), math.sin(yaw)
cp, sp = math.cos(pitch), math.sin(pitch)
x = rel[0]*cy - rel[2]*sy
z = rel[0]*sy + rel[2]*cy
y_ = rel[1]
y2 = y_ * cp - z * sp
z2 = y_ * sp + z * cp
z2 = max(z2, 0.01)
f = h / (2 * math.tan(FOV / 2))
return int(x * f / z2 + w / 2), int(-y2 * f / z2 + h / 2), z2
def draw_box(center, size, color):
offsets = n.array([
[-.5, -.5, -.5], [ .5, -.5, -.5], [ .5, .5, -.5], [-.5, .5, -.5],
[-.5, -.5, .5], [ .5, -.5, .5], [ .5, .5, .5], [-.5, .5, .5]
]) * size
corners = [project(center + o) for o in offsets]
faces = [(0,1,2,3),(4,5,6,7),(0,1,5,4),(2,3,7,6),(1,2,6,5),(0,3,7,4)]
for f_ in faces:
face = [corners[i] for i in f_]
if any(z <= 0 for *_, z in face): continue
poly = [(x, y) for x, y, _ in face]
g.draw.polygon(s, color, poly)
def draw_grid():
cx, _, cz = p
for X in range(int(cx - tile_view), int(cx + tile_view) + 1):
for Z in range(int(cz - tile_view), int(cz + tile_view) + 1):
a = project(n.array([X, 0, Z], float))
b = project(n.array([X + 1, 0, Z], float))
c_ = project(n.array([X, 0, Z + 1], float))
if a[2] > 0 and b[2] > 0 and c_[2] > 0:
g.draw.line(s, cols['GRAY'], a[:2], b[:2])
g.draw.line(s, cols['GRAY'], a[:2], c_[:2])
def intersects(a, b, pos, sz):
d = b - a
tmin, tmax = -n.inf, n.inf
for i in range(3):
mn = pos[i] - sz[i] / 2
mx = pos[i] + sz[i] / 2
if abs(d[i]) < 1e-6:
if a[i] < mn or a[i] > mx: return False
else:
t1 = (mn - a[i]) / d[i]
t2 = (mx - a[i]) / d[i]
tmin = max(tmin, min(t1, t2))
tmax = min(tmax, max(t1, t2))
if tmax < tmin: return False
return True
# Main loop
while True:
dt = c.tick(60) / 1000
col = flash_col()
for ev in g.event.get():
if ev.type == g.QUIT:
sys.exit()
mx, my = g.mouse.get_rel()
yaw += mx * MOUSE_SENS
pitch += my * MOUSE_SENS
pitch = max(-math.pi / 2 + 0.01, min(math.pi / 2 - 0.01, pitch))
keys = g.key.get_pressed()
mv = n.zeros(3)
fwd = n.array([math.sin(yaw), 0, math.cos(yaw)])
rt = n.array([math.cos(yaw), 0, -math.sin(yaw)])
if keys[g.K_w]: mv += fwd
if keys[g.K_s]: mv -= fwd
if keys[g.K_a]: mv -= rt
if keys[g.K_d]: mv += rt
if keys[g.K_q]: mv[1] -= 1
if keys[g.K_e]: mv[1] += 1
if n.linalg.norm(mv) > 0:
p += normalize(mv) * MOVE_SPEED * dt
# Danger check
dist = n.linalg.norm(p - e)
vis = n.dot(enemy_dir, normalize(p - e)) > -0.5
los = not intersects(e, p, wpos, wsize)
if dist <= danger_range and vis and los:
if d == 0: d = 11.67
d = min(MAX_D, d + D_RATE * dt)
else:
d = max(0, d - D_COOLDOWN * dt)
if d >= MAX_D:
bt += dt
blink = bt >= 0.5
bt %= 0.5
else:
blink = False
bt = 0
# Rendering
s.fill(cols['BLACK'])
draw_grid()
if n.linalg.norm(p - wpos) <= entity_view: draw_box(wpos, wsize, cols['DGRAY'])
if n.linalg.norm(p - e) <= entity_view: draw_box(e, [1, 2, 1], cols['ORANGE'])
# HUD
fw = int(200 * (d / MAX_D))
if d > 0 and (d < MAX_D or blink): g.draw.rect(s, col, (10, 40, fw, 16))
s.blit(font.render("Danger", True, cols['WHITE']), (10, 20))
g.display.flip()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment