Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save EncodeTheCode/d92fc24400ca2fceb941deea2b8abb6c to your computer and use it in GitHub Desktop.
Save EncodeTheCode/d92fc24400ca2fceb941deea2b8abb6c to your computer and use it in GitHub Desktop.
import pygame
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)
# Colors
WHITE = (255, 255, 255)
GRAY = (180, 180, 180)
DARK_GRAY = (80, 80, 80)
GREEN = (0, 200, 0)
ORANGE = (255, 165, 0)
YELLOW = (255, 255, 0)
BLACK = (0, 0, 0)
RED1 = (138, 38, 33)
RED2 = (133, 40, 32)
RED3 = (136, 41, 31)
RED4 = (138, 38, 33)
RED5 = (130, 39, 31)
flashing_color = [(133,40,32), (136,41,31), (138,38,33), (130,39,31)]
def get_flashing_color():
return flashing_color[(pygame.time.get_ticks() // 500) % len(flashing_color)]
SEL_COLOR = get_flashing_color()
transparent_red = (138, 38, 33, 128)
# Settings
WIDTH, HEIGHT = screen.get_size()
FOV = math.radians(45)
MOVE_SPEED = 3.0
MOUSE_SENSITIVITY = 0.001 # Slower mouse
danger = 0.0
max_danger = 100.0
danger_rate = 20.0
danger_cooldown = 80.0
blink = False
blink_timer = 1.5
font = pygame.font.SysFont(None, 24)
# Camera and rotation
camera_pos = pygame.Vector3(0, 1, -5)
yaw = 0.0
pitch = 0.0
def project_point(point, cam_pos, yaw, pitch):
rel = point - cam_pos
cos_y, sin_y = math.cos(yaw), math.sin(yaw)
cos_p, sin_p = math.cos(pitch), math.sin(pitch)
x = rel.x * cos_y - rel.z * sin_y
z = rel.x * sin_y + rel.z * cos_y
y = rel.y
y2 = y * cos_p - z * sin_p
z2 = y * sin_p + z * cos_p
if z2 <= 0.01:
z2 = 0.01
f = HEIGHT / (2 * math.tan(FOV / 2))
x_proj = (x * f) / z2 + WIDTH / 2
y_proj = (-y2 * f) / z2 + HEIGHT / 2
return int(x_proj), int(y_proj), z2
def draw_box(center, size, color, cam_pos, yaw, pitch):
w, h, d = size
x, y, z = center
corners = [
pygame.Vector3(x - w/2, y - h/2, z - d/2),
pygame.Vector3(x + w/2, y - h/2, z - d/2),
pygame.Vector3(x + w/2, y + h/2, z - d/2),
pygame.Vector3(x - w/2, y + h/2, z - d/2),
pygame.Vector3(x - w/2, y - h/2, z + d/2),
pygame.Vector3(x + w/2, y - h/2, z + d/2),
pygame.Vector3(x + w/2, y + h/2, z + d/2),
pygame.Vector3(x - w/2, y + h/2, z + d/2),
]
projected = [project_point(c, cam_pos, yaw, pitch) for c in corners]
screen_pts = [(x, y) for x, y, _ in projected]
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:
pygame.draw.polygon(screen, color, [screen_pts[i] for i in face])
def draw_floor(y, size, spacing, cam_pos, yaw, pitch):
for x in range(-size, size, spacing):
for z in range(1, size * 2, spacing):
p1 = pygame.Vector3(x, y, z)
p2 = pygame.Vector3(x + spacing, y, z)
p3 = pygame.Vector3(x, y, z + spacing)
try:
p1p = project_point(p1, cam_pos, yaw, pitch)
p2p = project_point(p2, cam_pos, yaw, pitch)
p3p = project_point(p3, cam_pos, yaw, pitch)
pygame.draw.line(screen, GRAY, p1p[:2], p2p[:2])
pygame.draw.line(screen, GRAY, p1p[:2], p3p[:2])
except:
continue
def line_intersects_box(start, end, box_pos, box_size):
"""Ray-AABB intersection using slab method"""
dir = end - start
t_min = -math.inf
t_max = math.inf
for i in range(3):
box_min = box_pos[i] - box_size[i] / 2
box_max = box_pos[i] + box_size[i] / 2
if abs(dir[i]) < 1e-6:
if start[i] < box_min or start[i] > box_max:
return False
else:
t1 = (box_min - start[i]) / dir[i]
t2 = (box_max - start[i]) / dir[i]
t_min = max(t_min, min(t1, t2))
t_max = min(t_max, max(t1, t2))
if t_max < t_min:
return False
return True
# Entities
enemy_pos = pygame.Vector3(0, 1, 8)
wall_pos = pygame.Vector3(0, 1.5, 4)
wall_size = (4, 3, 0.5)
# Main loop
running = True
while running:
dt = clock.tick(60) / 1000.0
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Mouse look
mx, my = pygame.mouse.get_rel()
yaw += mx * MOUSE_SENSITIVITY
pitch += my * MOUSE_SENSITIVITY
pitch = max(-math.pi/2 + 0.01, min(math.pi/2 - 0.01, pitch))
# Movement
keys = pygame.key.get_pressed()
move = pygame.Vector3()
forward = pygame.Vector3(math.sin(yaw), 0, math.cos(yaw))
right = pygame.Vector3(math.cos(yaw), 0, -math.sin(yaw))
if keys[pygame.K_w]: move += forward
if keys[pygame.K_s]: move -= forward
if keys[pygame.K_a]: move -= right
if keys[pygame.K_d]: move += right
if keys[pygame.K_q]: move.y -= 1
if keys[pygame.K_e]: move.y += 1
if move.length() > 0:
move = move.normalize() * MOVE_SPEED * dt
camera_pos += move
# Danger logic with LOS check
to_enemy = enemy_pos - camera_pos
view_dir = pygame.Vector3(math.sin(yaw), 0, math.cos(yaw))
in_fov = to_enemy.normalize().dot(view_dir) > 0.8
has_los = not line_intersects_box(camera_pos, enemy_pos, wall_pos, wall_size)
if in_fov and has_los:
if danger == 0.0:
danger = 11.67
elif danger == 11.67:
danger = danger
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, 20, 1, camera_pos, yaw, pitch)
draw_box(wall_pos, wall_size, DARK_GRAY, camera_pos, yaw, pitch)
draw_box(enemy_pos, (1, 2, 1), ORANGE, camera_pos, yaw, pitch)
# Constants
BAR_ALPHA = 150 # 0 (fully transparent) to 255 (opaque)
# HUD
bar_x, bar_y = 10, 40
bar_w, bar_h = 200, 16
fill_w = int(bar_w * (danger / max_danger))
if danger > 0 and (danger < max_danger or blink):
# Create a temporary surface with per-pixel alpha
temp_surf = pygame.Surface((fill_w, bar_h), pygame.SRCALPHA)
# Fill it with SEL_COLOR + alpha (semi-transparent)
sel_color_alpha = SEL_COLOR + (BAR_ALPHA,) if len(SEL_COLOR) == 3 else SEL_COLOR
temp_surf.fill(sel_color_alpha)
# Blit the transparent bar on the main screen
screen.blit(temp_surf, (bar_x, bar_y))
# Outline fully transparent — so skip drawing it
# pygame.draw.rect(screen, WHITE, (bar_x, bar_y, bar_w, bar_h), 2)
# Draw label
screen.blit(font.render("Danger", True, WHITE), (bar_x, bar_y - 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