Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save EncodeTheCode/41926a6d940a374981f87ffe46364c15 to your computer and use it in GitHub Desktop.
Save EncodeTheCode/41926a6d940a374981f87ffe46364c15 to your computer and use it in GitHub Desktop.
import pygame as g, sys, math
import numpy as n
from pathlib import Path
# Init
# Initialize pygame
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) # lock mouse to window
g.mouse.set_visible(True) # we'll hide/show on toggle
# --- Crosshair setup ---
gfx = Path("gfx")
crosshair_img = g.image.load(gfx / "xhair.png").convert_alpha()
# center of your 285×128 image is at (142,63):
crosshair_offset = g.math.Vector2(142, 63)
crosshair_mode = False
def enter_first_person_mode():
g.mouse.set_visible(False)
def enter_third_person_mode():
g.mouse.set_visible(True)
# 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
# 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)
yaw = pitch = 0.0
d = 0.0
blink = False
bt = 0.0
e = n.array([0.0,1.0,8.0], float)
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()
elif ev.type == g.KEYDOWN and ev.key == g.K_F3:
crosshair_mode = not crosshair_mode
if crosshair_mode:
enter_first_person_mode()
else:
enter_third_person_mode()
# Mouse-look
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))
# Movement
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
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
if bt>=0.5:
bt=0; blink=not blink
else:
blink=False; bt=0
# Render world
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))
# Crosshair fixed at screen center
if crosshair_mode:
cx, cy = w // 2, h // 2
s.blit(crosshair_img,
(cx - crosshair_offset.x,
cy - crosshair_offset.y))
g.display.flip()
@EncodeTheCode
Copy link
Author

Centers crosshair on the center of the screen with the crosshair offset position with the x and y positions. It fixes the functionality of the mouse look while also some issues remain with the looking around part. Still no fix in mind at the moment, working on a solution for third person mode and the viewing angles the player character can have.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment