Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save EncodeTheCode/558ec721ef1e630777138e44a01038ca to your computer and use it in GitHub Desktop.
Save EncodeTheCode/558ec721ef1e630777138e44a01038ca to your computer and use it in GitHub Desktop.
import pygame
import numpy as np
import sys
import math
# Initialize Pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("3D Line of Sight with Mouse Look")
clock = pygame.time.Clock()
pygame.event.set_grab(True)
pygame.mouse.set_visible(False)
# Constants
WIDTH, HEIGHT = screen.get_size()
FOV = math.radians(45)
MOVE_SPEED = 3.0
MOUSE_SENSITIVITY = 0.001
max_danger = 100.0
danger_rate = 20.0
danger_cooldown = 80.0
BAR_ALPHA = 150
font = pygame.font.SysFont(None, 24)
# Colors
WHITE = (255, 255, 255)
GRAY = (180, 180, 180)
DARK_GRAY = (80, 80, 80)
ORANGE = (255, 165, 0)
BLACK = (0, 0, 0)
flashing_color = [(133,40,32), (136,41,31), (138,38,33), (130,39,31)]
# Helper functions
def get_flashing_color():
return flashing_color[(pygame.time.get_ticks() // 500) % len(flashing_color)]
def normalize(v):
norm = np.linalg.norm(v)
return v / norm if norm > 0 else v
# Projection and rendering
def project_point(point, cam_pos, yaw, pitch):
rel = point - cam_pos
cos_y, sin_y = np.cos(yaw), np.sin(yaw)
cos_p, sin_p = np.cos(pitch), np.sin(pitch)
x = rel[0] * cos_y - rel[2] * sin_y
z = rel[0] * sin_y + rel[2] * cos_y
y = rel[1]
y2 = y * cos_p - z * sin_p
z2 = y * sin_p + z * cos_p
z2 = max(z2, 0.01)
f = HEIGHT / (2 * math.tan(FOV / 2))
return (
int((x * f) / z2 + WIDTH / 2),
int((-y2 * f) / z2 + HEIGHT / 2),
z2
)
# Draw box with face culling
def draw_box(center, size, color, cam_pos, yaw, pitch):
w, h, d = size
# 8 corners
offsets = np.array([
[-w/2, -h/2, -d/2], [ w/2, -h/2, -d/2], [ w/2, h/2, -d/2], [-w/2, h/2, -d/2],
[-w/2, -h/2, d/2], [ w/2, -h/2, d/2], [ w/2, h/2, d/2], [-w/2, h/2, d/2],
], dtype=np.float32)
corners = offsets + center
proj = [project_point(c, cam_pos, yaw, pitch) for c in corners]
# Faces defined by indices
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 face in faces:
pts3d = [proj[i] for i in face]
# backface cull: skip if any z <=0
if any(p[2] <= 0 for p in pts3d):
continue
pts2d = [(p[0], p[1]) for p in pts3d]
pygame.draw.polygon(screen, color, pts2d)
# Draw grid limited radius from player
def draw_floor(y, spacing, cam_pos, yaw, pitch):
cx, _y, cz = cam_pos
r = 25
for gx in range(int(cx-r), int(cx+r)+1):
for gz in range(int(cz-r), int(cz+r)+1):
p1 = np.array([gx, y, gz], dtype=np.float32)
p2 = np.array([gx+spacing, y, gz], dtype=np.float32)
p3 = np.array([gx, y, gz+spacing], dtype=np.float32)
try:
pp1 = project_point(p1, cam_pos, yaw, pitch)
pp2 = project_point(p2, cam_pos, yaw, pitch)
pp3 = project_point(p3, cam_pos, yaw, pitch)
except:
continue
pygame.draw.line(screen, GRAY, pp1[:2], pp2[:2])
pygame.draw.line(screen, GRAY, pp1[:2], pp3[:2])
# Ray-box intersection
def line_intersects_box(start, end, box_pos, box_size):
dir = end - start
tmin, tmax = -np.inf, np.inf
for i in range(3):
bmin = box_pos[i] - box_size[i]/2
bmax = box_pos[i] + box_size[i]/2
if abs(dir[i]) < 1e-6:
if start[i] < bmin or start[i] > bmax:
return False
else:
t1 = (bmin - start[i]) / dir[i]
t2 = (bmax - start[i]) / dir[i]
t1, t2 = min(t1, t2), max(t1, t2)
tmin = max(tmin, t1)
tmax = min(tmax, t2)
if tmax < tmin:
return False
return True
# Initial state
enemy_forward = np.array([0, 0, -1], dtype=np.float32)
enemy_pos = np.array([0, 1, 8], dtype=np.float32)
wall_pos = np.array([0, 1.5, 4], dtype=np.float32)
wall_size = np.array([4, 3, 0.5], dtype=np.float32)
camera_pos = np.array([0, 1, -5], dtype=np.float32)
yaw = pitch = 0.0
danger = 0.0
blink = False
blink_timer = 0.0
# Main loop
running = True
while running:
dt = clock.tick(60) / 1000.0
SEL_COLOR = get_flashing_color()
for ev in pygame.event.get():
if ev.type == pygame.QUIT:
running = False
# Mouse look
mx, my = pygame.mouse.get_rel()
yaw += mx * MOUSE_SENSITIVITY
pitch += my * MOUSE_SENSITIVITY
pitch = np.clip(pitch, -np.pi/2+0.01, np.pi/2-0.01)
# Movement
keys = pygame.key.get_pressed()
move = np.zeros(3, dtype=np.float32)
fwd = np.array([np.sin(yaw), 0, np.cos(yaw)], dtype=np.float32)
rt = np.array([np.cos(yaw), 0, -np.sin(yaw)], dtype=np.float32)
if keys[pygame.K_w]: move += fwd
if keys[pygame.K_s]: move -= fwd
if keys[pygame.K_a]: move -= rt
if keys[pygame.K_d]: move += rt
if keys[pygame.K_q]: move[1] -= 1
if keys[pygame.K_e]: move[1] += 1
if np.linalg.norm(move) > 0:
camera_pos += normalize(move) * MOVE_SPEED * dt
# Danger: enemy sees player
dist = np.linalg.norm(camera_pos - enemy_pos)
to_player = normalize(camera_pos - enemy_pos)
in_fov = np.dot(enemy_forward, to_player) > -0.5
los = not line_intersects_box(enemy_pos, camera_pos, wall_pos, wall_size)
if dist <= 50 and in_fov and los:
if danger == 0: danger = 11.67
danger = min(max_danger, danger + danger_rate * dt)
else:
danger = max(0, danger - danger_cooldown * dt)
if danger >= max_danger:
blink_timer += dt
if blink_timer >= 0.5:
blink_timer = 0; blink = not blink
else:
blink = False; blink_timer = 0
# Render
screen.fill(BLACK)
draw_floor(0, 1, camera_pos, yaw, pitch)
if np.linalg.norm(camera_pos - wall_pos) <= 15:
draw_box(wall_pos, wall_size, DARK_GRAY, camera_pos, yaw, pitch)
if np.linalg.norm(camera_pos - enemy_pos) <= 15:
draw_box(enemy_pos, (1,2,1), ORANGE, camera_pos, yaw, pitch)
# HUD
bar_w, bar_h = 200, 16
w = int(bar_w * (danger / max_danger))
if danger > 0 and (danger < max_danger or blink):
pygame.draw.rect(screen, SEL_COLOR, (10, 40, w, bar_h))
screen.blit(font.render("Danger", True, WHITE), (10, 20))
pygame.display.flip()
pygame.quit()
sys.exit()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment