Created
June 21, 2025 12:43
-
-
Save EncodeTheCode/05715fd10be952dc4725ead1bc3d908c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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