Загрузить файлы в «/»
This commit is contained in:
@@ -0,0 +1,300 @@
|
||||
import pygame
|
||||
import math
|
||||
import random
|
||||
import os
|
||||
|
||||
WIDTH, HEIGHT = 800, 600
|
||||
HALF_HEIGHT = HEIGHT // 2
|
||||
FPS = 60
|
||||
|
||||
pygame.init()
|
||||
screen = pygame.display.set_mode((WIDTH, HEIGHT))
|
||||
pygame.display.set_caption("3DMAZE")
|
||||
clock = pygame.time.Clock()
|
||||
|
||||
TILE_SIZE = 64
|
||||
fov = math.pi / 3
|
||||
NUM_RAYS = 160
|
||||
SCALE = WIDTH // NUM_RAYS
|
||||
|
||||
# --- НАСТРОЙКА РАЗМЕРА ЛАБИРИНТА ---
|
||||
MAP_SIZE = 32 # Задайте любой размер (например, 32, 64, 128)
|
||||
world_map = {}
|
||||
|
||||
def generate_maze(size):
|
||||
"""Генерирует карту заданного размера с границами и случайными стенами"""
|
||||
grid = {}
|
||||
for row in range(size):
|
||||
for col in range(size):
|
||||
# Внешние границы лабиринта — всегда стены
|
||||
if col == 0 or col == size - 1 or row == 0 or row == size - 1:
|
||||
grid[(col, row)] = 1
|
||||
# Стартовая зона 3х3 в левом верхнем углу — всегда пустая
|
||||
elif 1 <= col <= 3 and 1 <= row <= 3:
|
||||
grid[(col, row)] = 0
|
||||
else:
|
||||
# Внутри — случайные стены (плотность 30%)
|
||||
grid[(col, row)] = 1 if random.random() < 0.30 else 0
|
||||
return grid
|
||||
|
||||
world_map = generate_maze(MAP_SIZE)
|
||||
|
||||
# Игрок начинает в пустой зоне (клетка 2, 2)
|
||||
cell_x = 2
|
||||
cell_y = 2
|
||||
|
||||
player_x = cell_x * TILE_SIZE + 32
|
||||
player_y = cell_y * TILE_SIZE + 32
|
||||
player_angle = 0.0
|
||||
player_speed = 100
|
||||
rotation_speed = 2.0
|
||||
|
||||
demo_mode = True
|
||||
day_mode = False
|
||||
current_dir_index = 0
|
||||
DIRECTIONS = [0.0, math.pi / 2, math.pi, -math.pi / 2]
|
||||
|
||||
ai_state = "MOVE"
|
||||
target_angle = 0.0
|
||||
|
||||
def get_wall_type(col, row):
|
||||
# Если вышли за пределы сгенерированной карты, возвращаем стену
|
||||
if (col, row) in world_map:
|
||||
return world_map[(col, row)]
|
||||
return 1
|
||||
|
||||
def load_or_generate_textures():
|
||||
if os.path.exists("wall.png"):
|
||||
try:
|
||||
wall_tex = pygame.image.load("wall.png").convert()
|
||||
wall_tex = pygame.transform.scale(wall_tex, (TILE_SIZE, TILE_SIZE))
|
||||
except Exception: wall_tex = None
|
||||
else: wall_tex = None
|
||||
|
||||
if wall_tex is None:
|
||||
wall_tex = pygame.Surface((TILE_SIZE, TILE_SIZE))
|
||||
wall_tex.fill((125, 45, 30))
|
||||
for y in [0, 16, 32, 48, 63]: pygame.draw.line(wall_tex, (110, 105, 100), (0, y), (64, y), 2)
|
||||
for x in [0, 32, 64]:
|
||||
pygame.draw.line(wall_tex, (110, 105, 100), (x, 0), (x, 16), 2)
|
||||
pygame.draw.line(wall_tex, (110, 105, 100), (x, 32), (x, 48), 2)
|
||||
for x in [16, 48]:
|
||||
pygame.draw.line(wall_tex, (110, 105, 100), (x, 16), (x, 32), 2)
|
||||
pygame.draw.line(wall_tex, (110, 105, 100), (x, 48), (x, 64), 2)
|
||||
|
||||
if os.path.exists("top.png"):
|
||||
try:
|
||||
ceil_tex = pygame.image.load("top.png").convert()
|
||||
ceil_tex = pygame.transform.scale(ceil_tex, (TILE_SIZE, TILE_SIZE))
|
||||
except Exception: ceil_tex = None
|
||||
else: ceil_tex = None
|
||||
|
||||
if ceil_tex is None:
|
||||
ceil_tex = pygame.Surface((TILE_SIZE, TILE_SIZE))
|
||||
ceil_tex.fill((75, 75, 75))
|
||||
|
||||
if os.path.exists("floor.png"):
|
||||
try:
|
||||
floor_tex = pygame.image.load("floor.png").convert()
|
||||
floor_tex = pygame.transform.scale(floor_tex, (TILE_SIZE, TILE_SIZE))
|
||||
except Exception: floor_tex = None
|
||||
else: floor_tex = None
|
||||
|
||||
if floor_tex is None:
|
||||
floor_tex = pygame.Surface((TILE_SIZE, TILE_SIZE))
|
||||
floor_tex.fill((45, 45, 50))
|
||||
for i in range(0, TILE_SIZE, 16):
|
||||
pygame.draw.line(floor_tex, (25, 25, 28), (i, 0), (i, TILE_SIZE), 1)
|
||||
pygame.draw.line(floor_tex, (25, 25, 28), (0, i), (TILE_SIZE, i), 1)
|
||||
|
||||
return wall_tex, ceil_tex, floor_tex
|
||||
|
||||
tex_wall, tex_ceil, tex_floor = load_or_generate_textures()
|
||||
|
||||
running = True
|
||||
while running:
|
||||
dt = clock.tick(FPS) / 1000.0
|
||||
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
running = False
|
||||
elif event.type == pygame.KEYDOWN:
|
||||
if event.key == pygame.K_y:
|
||||
demo_mode = not demo_mode
|
||||
ai_state = "MOVE"
|
||||
elif event.key == pygame.K_l:
|
||||
day_mode = not day_mode
|
||||
|
||||
if demo_mode:
|
||||
if ai_state == "MOVE":
|
||||
target_x = cell_x * TILE_SIZE + 32
|
||||
target_y = cell_y * TILE_SIZE + 32
|
||||
move_cos = round(math.cos(DIRECTIONS[current_dir_index]))
|
||||
move_sin = round(math.sin(DIRECTIONS[current_dir_index]))
|
||||
dist = math.hypot(target_x - player_x, target_y - player_y)
|
||||
|
||||
if dist > 1.0:
|
||||
step = player_speed * dt
|
||||
if step > dist: step = dist
|
||||
player_x += move_cos * step
|
||||
player_y += move_sin * step
|
||||
else:
|
||||
# Мы дошли до центра клетки. Определяем, куда можно идти дальше.
|
||||
player_x, player_y = target_x, target_y
|
||||
|
||||
# Проверяем все 4 направления: какое из них свободно?
|
||||
available_dirs = []
|
||||
for idx, angle in enumerate(DIRECTIONS):
|
||||
t_cos = round(math.cos(angle))
|
||||
t_sin = round(math.sin(angle))
|
||||
if get_wall_type(cell_x + t_cos, cell_y + t_sin) == 0:
|
||||
available_dirs.append(idx)
|
||||
|
||||
# Направление, противоположное текущему (откуда пришли)
|
||||
backward_dir_index = (current_dir_index + 2) % 4
|
||||
|
||||
# Фильтруем варианты, убирая путь назад (чтобы не топтаться на месте)
|
||||
forward_and_sides = [d for d in available_dirs if d != backward_dir_index]
|
||||
|
||||
if forward_and_sides:
|
||||
# Если можно идти вперед, даем этому приоритет (например, 75%)
|
||||
if current_dir_index in forward_and_sides and random.random() < 0.75:
|
||||
next_dir = current_dir_index
|
||||
else:
|
||||
# В остальных случаях случайно выбираем поворот (налево/направо/вперед)
|
||||
next_dir = random.choice(forward_and_sides)
|
||||
else:
|
||||
# Если идти вообще некуда (тупик) — только тогда разворачиваемся назад
|
||||
next_dir = backward_dir_index if backward_dir_index in available_dirs else current_dir_index
|
||||
|
||||
# Если направление изменилось, запускаем режим поворота камеры
|
||||
if next_dir != current_dir_index:
|
||||
current_dir_index = next_dir
|
||||
target_angle = DIRECTIONS[current_dir_index]
|
||||
ai_state = "TURN"
|
||||
else:
|
||||
# Если идем прямо, просто обновляем целевую клетку
|
||||
cell_x += move_cos
|
||||
cell_y += move_sin
|
||||
|
||||
elif ai_state == "TURN":
|
||||
angle_diff = (target_angle - player_angle + math.pi) % (2 * math.pi) - math.pi
|
||||
if abs(angle_diff) > 0.01:
|
||||
player_angle += math.copysign(min(rotation_speed * dt, abs(angle_diff)), angle_diff)
|
||||
else:
|
||||
player_angle = target_angle
|
||||
move_cos = round(math.cos(DIRECTIONS[current_dir_index]))
|
||||
move_sin = round(math.sin(DIRECTIONS[current_dir_index]))
|
||||
cell_x += move_cos
|
||||
cell_y += move_sin
|
||||
ai_state = "MOVE"
|
||||
else:
|
||||
keys = pygame.key.get_pressed()
|
||||
if keys[pygame.K_LEFT] or keys[pygame.K_a]: player_angle -= 3.0 * dt
|
||||
if keys[pygame.K_RIGHT] or keys[pygame.K_d]: player_angle += 3.0 * dt
|
||||
|
||||
cos_a, sin_a = math.cos(player_angle), math.sin(player_angle)
|
||||
mx = player_speed * cos_a * dt if (keys[pygame.K_w] or keys[pygame.K_UP]) else (-player_speed * cos_a * dt if (keys[pygame.K_s] or keys[pygame.K_DOWN]) else 0)
|
||||
my = player_speed * sin_a * dt if (keys[pygame.K_w] or keys[pygame.K_UP]) else (-player_speed * sin_a * dt if (keys[pygame.K_s] or keys[pygame.K_DOWN]) else 0)
|
||||
|
||||
r = 12
|
||||
if get_wall_type(int((player_x + mx + math.copysign(r, mx)) // TILE_SIZE), int(player_y // TILE_SIZE)) == 0: player_x += mx
|
||||
if get_wall_type(int(player_x // TILE_SIZE), int((player_y + my + math.copysign(r, my)) // TILE_SIZE)) == 0: player_y += my
|
||||
|
||||
cell_x = int(player_x // TILE_SIZE)
|
||||
cell_y = int(player_y // TILE_SIZE)
|
||||
|
||||
screen.fill((140, 140, 145) if day_mode else (15, 15, 15))
|
||||
|
||||
# Рэйкастинг
|
||||
start_angle = player_angle - fov / 2
|
||||
for ray in range(NUM_RAYS):
|
||||
angle = start_angle + ray * (fov / NUM_RAYS)
|
||||
r_cos = math.cos(angle) if math.cos(angle) != 0 else 0.00001
|
||||
r_sin = math.sin(angle) if math.sin(angle) != 0 else 0.00001
|
||||
|
||||
# Вертикали
|
||||
x_depth, x_texture = 9999, 0
|
||||
x_step = TILE_SIZE / r_cos
|
||||
first_x = (int(player_x // TILE_SIZE) + 1) * TILE_SIZE if r_cos > 0 else int(player_x // TILE_SIZE) * TILE_SIZE
|
||||
current_depth_x = (first_x - player_x) / r_cos
|
||||
|
||||
for _ in range(30):
|
||||
tx = player_x + current_depth_x * r_cos
|
||||
ty = player_y + current_depth_x * r_sin
|
||||
col = int((tx + (1 if r_cos > 0 else -1)) // TILE_SIZE)
|
||||
row = int(ty // TILE_SIZE)
|
||||
if get_wall_type(col, row) > 0:
|
||||
x_depth, x_texture = current_depth_x, int(ty % TILE_SIZE)
|
||||
break
|
||||
current_depth_x += abs(x_step)
|
||||
|
||||
# Горизонтали
|
||||
y_depth, y_texture = 9999, 0
|
||||
y_step = TILE_SIZE / r_sin
|
||||
first_y = (int(player_y // TILE_SIZE) + 1) * TILE_SIZE if r_sin > 0 else int(player_y // TILE_SIZE) * TILE_SIZE
|
||||
current_depth_y = (first_y - player_y) / r_sin
|
||||
|
||||
for _ in range(30):
|
||||
tx = player_x + current_depth_y * r_cos
|
||||
ty = player_y + current_depth_y * r_sin
|
||||
col = int(tx // TILE_SIZE)
|
||||
row = int((ty + (1 if r_sin > 0 else -1)) // TILE_SIZE)
|
||||
if get_wall_type(col, row) > 0:
|
||||
y_depth, y_texture = current_depth_y, int(tx % TILE_SIZE)
|
||||
break
|
||||
current_depth_y += abs(y_step)
|
||||
|
||||
if x_depth < y_depth:
|
||||
depth, x_tex_coord, is_vertical_wall = x_depth, x_texture, True
|
||||
else:
|
||||
depth, x_tex_coord, is_vertical_wall = y_depth, y_texture, False
|
||||
|
||||
if depth < 9999:
|
||||
fixed_depth = depth * math.cos(player_angle - angle)
|
||||
fixed_depth = max(fixed_depth, 1)
|
||||
proj_height = min(int((TILE_SIZE / fixed_depth) * 600), HEIGHT * 2)
|
||||
|
||||
wall_column = tex_wall.subsurface(x_tex_coord, 0, 1, TILE_SIZE)
|
||||
wall_column = pygame.transform.scale(wall_column, (SCALE, proj_height))
|
||||
|
||||
if day_mode: shadow = 15 if is_vertical_wall else 0
|
||||
else:
|
||||
base_shadow = int(fixed_depth * 0.75)
|
||||
if is_vertical_wall: base_shadow += 20
|
||||
shadow = max(0, min(255, base_shadow))
|
||||
|
||||
if shadow > 0:
|
||||
shadow_surf = pygame.Surface((SCALE, proj_height))
|
||||
shadow_surf.fill((shadow, shadow, shadow))
|
||||
wall_column.blit(shadow_surf, (0, 0), special_flags=pygame.BLEND_SUB)
|
||||
|
||||
wall_y_pos = HALF_HEIGHT - proj_height // 2
|
||||
screen.blit(wall_column, (ray * SCALE, wall_y_pos))
|
||||
|
||||
# Пол / Потолок
|
||||
cos_tilt = math.cos(player_angle - angle)
|
||||
for screen_y in range(0, wall_y_pos, 4):
|
||||
if screen_y >= HALF_HEIGHT: break
|
||||
|
||||
current_dist = (TILE_SIZE * HEIGHT) / (2.0 * (HALF_HEIGHT - screen_y) * cos_tilt)
|
||||
|
||||
tex_x = int(player_x + r_cos * current_dist) % TILE_SIZE
|
||||
tex_y = int(player_y + r_sin * current_dist) % TILE_SIZE
|
||||
|
||||
f_shadow = 0 if day_mode else max(0, min(255, int(current_dist * 0.75)))
|
||||
|
||||
c_color = tex_ceil.get_at((tex_x, tex_y))
|
||||
if f_shadow > 0:
|
||||
c_color = (max(0, c_color[0] - f_shadow), max(0, c_color[1] - f_shadow), max(0, c_color[2] - f_shadow))
|
||||
pygame.draw.rect(screen, c_color, (ray * SCALE, screen_y, SCALE, 4))
|
||||
|
||||
floor_y = HEIGHT - screen_y - 4
|
||||
fl_color = tex_floor.get_at((tex_x, tex_y))
|
||||
if f_shadow > 0:
|
||||
fl_color = (max(0, fl_color[0] - f_shadow), max(0, fl_color[1] - f_shadow), max(0, fl_color[2] - f_shadow))
|
||||
pygame.draw.rect(screen, fl_color, (ray * SCALE, floor_y, SCALE, 4))
|
||||
|
||||
pygame.display.flip()
|
||||
|
||||
pygame.quit()
|
||||
Reference in New Issue
Block a user