Upload files to "/"
parent
a72eaabefa
commit
34c4e1d749
|
@ -0,0 +1,76 @@
|
|||
import pygame
|
||||
from settings import *
|
||||
from map import world_map, WORLD_WIDTH, WORLD_HEIGHT
|
||||
from numba import njit
|
||||
|
||||
@njit(fastmath=True, cache=True)
|
||||
def mapping(a, b):
|
||||
return int(a // TILE) * TILE, int(b // TILE) * TILE
|
||||
|
||||
@njit(fastmath=True, cache=True)
|
||||
def ray_casting(player_pos, player_angle, world_map):
|
||||
casted_walls = []
|
||||
ox, oy = player_pos
|
||||
texture_v, texture_h = 1, 1
|
||||
xm, ym = mapping(ox, oy)
|
||||
cur_angle = player_angle - HALF_FOV
|
||||
for ray in range(NUM_RAYS):
|
||||
sin_a = math.sin(cur_angle)
|
||||
sin_a = sin_a if sin_a else 0.000001
|
||||
cos_a = math.cos(cur_angle)
|
||||
cos_a = cos_a if cos_a else 0.000001
|
||||
|
||||
# verticals
|
||||
x, dx = (xm + TILE, 1) if cos_a >= 0 else (xm, -1)
|
||||
for i in range(0, WORLD_WIDTH, TILE):
|
||||
depth_v = (x - ox) / cos_a
|
||||
yv = oy + depth_v * sin_a
|
||||
tile_v = mapping(x + dx, yv)
|
||||
if tile_v in world_map:
|
||||
texture_v = world_map[tile_v]
|
||||
break
|
||||
x += dx * TILE
|
||||
|
||||
# horizontals
|
||||
y, dy = (ym + TILE, 1) if sin_a >= 0 else (ym, -1)
|
||||
for i in range(0, WORLD_HEIGHT, TILE):
|
||||
depth_h = (y - oy) / sin_a
|
||||
xh = ox + depth_h * cos_a
|
||||
tile_h = mapping(xh, y + dy)
|
||||
if tile_h in world_map:
|
||||
texture_h = world_map[tile_h]
|
||||
break
|
||||
y += dy * TILE
|
||||
|
||||
# projection
|
||||
depth, offset, texture = (depth_v, yv, texture_v) if depth_v < depth_h else (depth_h, xh, texture_h)
|
||||
offset = int(offset) % TILE
|
||||
depth *= math.cos(player_angle - cur_angle)
|
||||
depth = max(depth, 0.00001)
|
||||
proj_height = int(PROJ_COEFF / depth)
|
||||
|
||||
casted_walls.append((depth, offset, proj_height, texture))
|
||||
cur_angle += DELTA_ANGLE
|
||||
return casted_walls
|
||||
|
||||
def ray_casting_walls(player, textures):
|
||||
casted_walls = ray_casting(player.pos, player.angle, world_map)
|
||||
wall_shot = casted_walls[CENTER_RAY][0], casted_walls[CENTER_RAY][2]
|
||||
walls = []
|
||||
for ray, casted_values in enumerate(casted_walls):
|
||||
depth, offset, proj_height, texture = casted_values
|
||||
if proj_height > HEIGHT:
|
||||
coeff = proj_height / HEIGHT
|
||||
texture_height = TEXTURE_HEIGHT / coeff
|
||||
wall_column = textures[texture].subsurface(offset * TEXTURE_SCALE,
|
||||
HALF_TEXTURE_HEIGHT - texture_height // 2,
|
||||
TEXTURE_SCALE, texture_height)
|
||||
wall_column = pygame.transform.scale(wall_column, (SCALE, HEIGHT))
|
||||
wall_pos = (ray * SCALE, 0)
|
||||
else:
|
||||
wall_column = textures[texture].subsurface(offset * TEXTURE_SCALE, 0, TEXTURE_SCALE, TEXTURE_HEIGHT)
|
||||
wall_column = pygame.transform.scale(wall_column, (SCALE, proj_height))
|
||||
wall_pos = (ray * SCALE, HALF_HEIGHT - proj_height // 2)
|
||||
|
||||
walls.append((depth, wall_column, wall_pos))
|
||||
return walls, wall_shot
|
|
@ -0,0 +1,60 @@
|
|||
import math
|
||||
|
||||
# game settings
|
||||
WIDTH = 1200
|
||||
HEIGHT = 800
|
||||
HALF_WIDTH = WIDTH // 2
|
||||
HALF_HEIGHT = HEIGHT // 2
|
||||
PENTA_HEIGHT = 5 * HEIGHT
|
||||
DOUBLE_HEIGHT = 2 * HEIGHT
|
||||
FPS = 60
|
||||
TILE = 100
|
||||
FPS_POS = (WIDTH - 65, 5)
|
||||
|
||||
# minimap settings
|
||||
MINIMAP_SCALE = 5
|
||||
MINIMAP_RES = (WIDTH // MINIMAP_SCALE, HEIGHT // MINIMAP_SCALE)
|
||||
MAP_SCALE = 2 * MINIMAP_SCALE # 1 -> 12 x 8, 2 -> 24 x 16, 3 -> 36 x 24
|
||||
MAP_TILE = TILE // MAP_SCALE
|
||||
MAP_POS = (0, HEIGHT - HEIGHT // MINIMAP_SCALE)
|
||||
|
||||
# ray casting settings
|
||||
FOV = math.pi / 3
|
||||
HALF_FOV = FOV / 2
|
||||
NUM_RAYS = 300
|
||||
MAX_DEPTH = 800
|
||||
DELTA_ANGLE = FOV / NUM_RAYS
|
||||
DIST = NUM_RAYS / (2 * math.tan(HALF_FOV))
|
||||
PROJ_COEFF = 3 * DIST * TILE
|
||||
SCALE = WIDTH // NUM_RAYS
|
||||
|
||||
# sprite settings
|
||||
DOUBLE_PI = math.pi * 2
|
||||
CENTER_RAY = NUM_RAYS // 2 - 1
|
||||
FAKE_RAYS = 100
|
||||
FAKE_RAYS_RANGE = NUM_RAYS - 1 + 2 * FAKE_RAYS
|
||||
|
||||
# texture settings (1200 x 1200)
|
||||
TEXTURE_WIDTH = 1200
|
||||
TEXTURE_HEIGHT = 1200
|
||||
HALF_TEXTURE_HEIGHT = TEXTURE_HEIGHT // 2
|
||||
TEXTURE_SCALE = TEXTURE_WIDTH // TILE
|
||||
|
||||
# player settings
|
||||
player_pos = (HALF_WIDTH // 4, HALF_HEIGHT - 50)
|
||||
player_angle = 0
|
||||
player_speed = 3
|
||||
|
||||
# colors
|
||||
WHITE = (255, 255, 255)
|
||||
BLACK = (0, 0, 0)
|
||||
RED = (220, 0, 0)
|
||||
GREEN = (0, 80, 0)
|
||||
BLUE = (0, 0, 255)
|
||||
DARKGRAY = (40, 40, 40)
|
||||
PURPLE = (120, 0, 120)
|
||||
SKYBLUE = (0, 186, 255)
|
||||
YELLOW = (220, 220, 0)
|
||||
SANDY = (244, 164, 96)
|
||||
DARKBROWN = (97, 61, 25)
|
||||
DARKORANGE = (255, 140, 0)
|
|
@ -0,0 +1,309 @@
|
|||
import pygame
|
||||
from settings import *
|
||||
from collections import deque
|
||||
from ray_casting import mapping
|
||||
from numba.core import types
|
||||
from numba.typed import Dict
|
||||
from numba import int32
|
||||
|
||||
|
||||
class Sprites:
|
||||
def __init__(self):
|
||||
self.sprite_parameters = {
|
||||
'sprite_barrel': {
|
||||
'sprite': pygame.image.load('sprites/barrel/base/0.png').convert_alpha(),
|
||||
'viewing_angles': None,
|
||||
'shift': 1.8,
|
||||
'scale': (0.4, 0.4),
|
||||
'side': 30,
|
||||
'animation': deque(
|
||||
[pygame.image.load(f'sprites/barrel/anim/{i}.png').convert_alpha() for i in range(12)]),
|
||||
'death_animation': deque([pygame.image.load(f'sprites/barrel/death/{i}.png')
|
||||
.convert_alpha() for i in range(4)]),
|
||||
'is_dead': None,
|
||||
'dead_shift': 2.6,
|
||||
'animation_dist': 800,
|
||||
'animation_speed': 10,
|
||||
'blocked': True,
|
||||
'flag': 'decor',
|
||||
'obj_action': []
|
||||
},
|
||||
'sprite_pin': {
|
||||
'sprite': pygame.image.load('sprites/pin/base/0.png').convert_alpha(),
|
||||
'viewing_angles': None,
|
||||
'shift': 0.6,
|
||||
'scale': (0.6, 0.6),
|
||||
'side': 30,
|
||||
'animation': deque([pygame.image.load(f'sprites/pin/anim/{i}.png').convert_alpha() for i in range(8)]),
|
||||
'death_animation': [],
|
||||
'is_dead': 'immortal',
|
||||
'dead_shift': None,
|
||||
'animation_dist': 800,
|
||||
'animation_speed': 10,
|
||||
'blocked': True,
|
||||
'flag': 'decor',
|
||||
'obj_action': []
|
||||
},
|
||||
'sprite_flame': {
|
||||
'sprite': pygame.image.load('sprites/flame/base/0.png').convert_alpha(),
|
||||
'viewing_angles': None,
|
||||
'shift': 0.7,
|
||||
'scale': (0.6, 0.6),
|
||||
'side': 30,
|
||||
'animation': deque(
|
||||
[pygame.image.load(f'sprites/flame/anim/{i}.png').convert_alpha() for i in range(16)]),
|
||||
'death_animation': [],
|
||||
'is_dead': 'immortal',
|
||||
'dead_shift': 1.8,
|
||||
'animation_dist': 1800,
|
||||
'animation_speed': 5,
|
||||
'blocked': None,
|
||||
'flag': 'decor',
|
||||
'obj_action': []
|
||||
},
|
||||
'npc_devil': {
|
||||
'sprite': [pygame.image.load(f'sprites/devil/base/{i}.png').convert_alpha() for i in range(8)],
|
||||
'viewing_angles': True,
|
||||
'shift': 0.0,
|
||||
'scale': (1.1, 1.1),
|
||||
'side': 50,
|
||||
'animation': [],
|
||||
'death_animation': deque([pygame.image.load(f'sprites/devil/death/{i}.png')
|
||||
.convert_alpha() for i in range(6)]),
|
||||
'is_dead': None,
|
||||
'dead_shift': 0.6,
|
||||
'animation_dist': None,
|
||||
'animation_speed': 10,
|
||||
'blocked': True,
|
||||
'flag': 'npc',
|
||||
'obj_action': deque(
|
||||
[pygame.image.load(f'sprites/devil/anim/{i}.png').convert_alpha() for i in range(9)]),
|
||||
},
|
||||
'sprite_door_v': {
|
||||
'sprite': [pygame.image.load(f'sprites/doors/door_v/{i}.png').convert_alpha() for i in range(16)],
|
||||
'viewing_angles': True,
|
||||
'shift': 0.1,
|
||||
'scale': (2.6, 1.2),
|
||||
'side': 100,
|
||||
'animation': [],
|
||||
'death_animation': [],
|
||||
'is_dead': 'immortal',
|
||||
'dead_shift': 0,
|
||||
'animation_dist': 0,
|
||||
'animation_speed': 0,
|
||||
'blocked': True,
|
||||
'flag': 'door_h',
|
||||
'obj_action': []
|
||||
},
|
||||
'sprite_door_h': {
|
||||
'sprite': [pygame.image.load(f'sprites/doors/door_h/{i}.png').convert_alpha() for i in range(16)],
|
||||
'viewing_angles': True,
|
||||
'shift': 0.1,
|
||||
'scale': (2.6, 1.2),
|
||||
'side': 100,
|
||||
'animation': [],
|
||||
'death_animation': [],
|
||||
'is_dead': 'immortal',
|
||||
'dead_shift': 0,
|
||||
'animation_dist': 0,
|
||||
'animation_speed': 0,
|
||||
'blocked': True,
|
||||
'flag': 'door_v',
|
||||
'obj_action': []
|
||||
},
|
||||
'npc_soldier0': {
|
||||
'sprite': [pygame.image.load(f'sprites/npc/soldier0/base/{i}.png').convert_alpha() for i in range(8)],
|
||||
'viewing_angles': True,
|
||||
'shift': 0.8,
|
||||
'scale': (0.4, 0.6),
|
||||
'side': 30,
|
||||
'animation': [],
|
||||
'death_animation': deque([pygame.image.load(f'sprites/npc/soldier0/death/{i}.png')
|
||||
.convert_alpha() for i in range(10)]),
|
||||
'is_dead': None,
|
||||
'dead_shift': 1.7,
|
||||
'animation_dist': None,
|
||||
'animation_speed': 6,
|
||||
'blocked': True,
|
||||
'flag': 'npc',
|
||||
'obj_action': deque([pygame.image.load(f'sprites/npc/soldier0/action/{i}.png')
|
||||
.convert_alpha() for i in range(4)])
|
||||
},
|
||||
}
|
||||
|
||||
self.list_of_objects = [
|
||||
SpriteObject(self.sprite_parameters['sprite_barrel'], (7.1, 2.1)),
|
||||
SpriteObject(self.sprite_parameters['sprite_barrel'], (5.9, 2.1)),
|
||||
SpriteObject(self.sprite_parameters['sprite_pin'], (8.7, 2.5)),
|
||||
SpriteObject(self.sprite_parameters['npc_devil'], (7, 4)),
|
||||
SpriteObject(self.sprite_parameters['sprite_flame'], (8.6, 5.6)),
|
||||
SpriteObject(self.sprite_parameters['sprite_door_v'], (3.5, 3.5)),
|
||||
SpriteObject(self.sprite_parameters['sprite_door_h'], (1.5, 4.5)),
|
||||
SpriteObject(self.sprite_parameters['npc_soldier0'], (2.5, 1.5)),
|
||||
SpriteObject(self.sprite_parameters['npc_soldier0'], (5.51, 1.5)),
|
||||
SpriteObject(self.sprite_parameters['npc_soldier0'], (6.61, 2.92)),
|
||||
SpriteObject(self.sprite_parameters['npc_soldier0'], (7.68, 1.47)),
|
||||
SpriteObject(self.sprite_parameters['npc_soldier0'], (8.75, 3.65)),
|
||||
SpriteObject(self.sprite_parameters['npc_soldier0'], (1.27, 11.5)),
|
||||
SpriteObject(self.sprite_parameters['npc_soldier0'], (1.26, 8.29)),
|
||||
]
|
||||
|
||||
@property
|
||||
def sprite_shot(self):
|
||||
return min([obj.is_on_fire for obj in self.list_of_objects], default=(float('inf'), 0))
|
||||
|
||||
@property
|
||||
def blocked_doors(self):
|
||||
blocked_doors = Dict.empty(key_type=types.UniTuple(int32, 2), value_type=int32)
|
||||
for obj in self.list_of_objects:
|
||||
if obj.flag in {'door_h', 'door_v'} and obj.blocked:
|
||||
i, j = mapping(obj.x, obj.y)
|
||||
blocked_doors[(i, j)] = 0
|
||||
return blocked_doors
|
||||
|
||||
|
||||
class SpriteObject:
|
||||
def __init__(self, parameters, pos):
|
||||
self.object = parameters['sprite'].copy()
|
||||
self.viewing_angles = parameters['viewing_angles']
|
||||
self.shift = parameters['shift']
|
||||
self.scale = parameters['scale']
|
||||
self.animation = parameters['animation'].copy()
|
||||
# ---------------------
|
||||
self.death_animation = parameters['death_animation'].copy()
|
||||
self.is_dead = parameters['is_dead']
|
||||
self.dead_shift = parameters['dead_shift']
|
||||
# ---------------------
|
||||
self.animation_dist = parameters['animation_dist']
|
||||
self.animation_speed = parameters['animation_speed']
|
||||
self.blocked = parameters['blocked']
|
||||
self.flag = parameters['flag']
|
||||
self.obj_action = parameters['obj_action'].copy()
|
||||
self.x, self.y = pos[0] * TILE, pos[1] * TILE
|
||||
self.side = parameters['side']
|
||||
self.dead_animation_count = 0
|
||||
self.animation_count = 0
|
||||
self.npc_action_trigger = False
|
||||
self.door_open_trigger = False
|
||||
self.door_prev_pos = self.y if self.flag == 'door_h' else self.x
|
||||
self.delete = False
|
||||
if self.viewing_angles:
|
||||
if len(self.object) == 8:
|
||||
self.sprite_angles = [frozenset(range(338, 361)) | frozenset(range(0, 23))] + \
|
||||
[frozenset(range(i, i + 45)) for i in range(23, 338, 45)]
|
||||
else:
|
||||
self.sprite_angles = [frozenset(range(348, 361)) | frozenset(range(0, 11))] + \
|
||||
[frozenset(range(i, i + 23)) for i in range(11, 348, 23)]
|
||||
self.sprite_positions = {angle: pos for angle, pos in zip(self.sprite_angles, self.object)}
|
||||
|
||||
@property
|
||||
def is_on_fire(self):
|
||||
if CENTER_RAY - self.side // 2 < self.current_ray < CENTER_RAY + self.side // 2 and self.blocked:
|
||||
return self.distance_to_sprite, self.proj_height
|
||||
return float('inf'), None
|
||||
|
||||
@property
|
||||
def pos(self):
|
||||
return self.x - self.side // 2, self.y - self.side // 2
|
||||
|
||||
def object_locate(self, player):
|
||||
|
||||
dx, dy = self.x - player.x, self.y - player.y
|
||||
self.distance_to_sprite = math.sqrt(dx ** 2 + dy ** 2)
|
||||
|
||||
self.theta = math.atan2(dy, dx)
|
||||
gamma = self.theta - player.angle
|
||||
if dx > 0 and 180 <= math.degrees(player.angle) <= 360 or dx < 0 and dy < 0:
|
||||
gamma += DOUBLE_PI
|
||||
self.theta -= 1.4 * gamma
|
||||
|
||||
delta_rays = int(gamma / DELTA_ANGLE)
|
||||
self.current_ray = CENTER_RAY + delta_rays
|
||||
if self.flag not in {'door_h', 'door_v'}:
|
||||
self.distance_to_sprite *= math.cos(HALF_FOV - self.current_ray * DELTA_ANGLE)
|
||||
|
||||
fake_ray = self.current_ray + FAKE_RAYS
|
||||
if 0 <= fake_ray <= FAKE_RAYS_RANGE and self.distance_to_sprite > 30:
|
||||
self.proj_height = min(int(PROJ_COEFF / self.distance_to_sprite),
|
||||
DOUBLE_HEIGHT if self.flag not in {'door_h', 'door_v'} else HEIGHT)
|
||||
sprite_width = int(self.proj_height * self.scale[0])
|
||||
sprite_height = int(self.proj_height * self.scale[1])
|
||||
half_sprite_width = sprite_width // 2
|
||||
half_sprite_height = sprite_height // 2
|
||||
shift = half_sprite_height * self.shift
|
||||
|
||||
# logic for doors, npc, decor
|
||||
if self.flag in {'door_h', 'door_v'}:
|
||||
if self.door_open_trigger:
|
||||
self.open_door()
|
||||
self.object = self.visible_sprite()
|
||||
sprite_object = self.sprite_animation()
|
||||
else:
|
||||
if self.is_dead and self.is_dead != 'immortal':
|
||||
sprite_object = self.dead_animation()
|
||||
shift = half_sprite_height * self.dead_shift
|
||||
sprite_height = int(sprite_height / 1.3)
|
||||
elif self.npc_action_trigger:
|
||||
sprite_object = self.npc_in_action()
|
||||
else:
|
||||
self.object = self.visible_sprite()
|
||||
sprite_object = self.sprite_animation()
|
||||
|
||||
|
||||
# sprite scale and pos
|
||||
sprite_pos = (self.current_ray * SCALE - half_sprite_width, HALF_HEIGHT - half_sprite_height + shift)
|
||||
sprite = pygame.transform.scale(sprite_object, (sprite_width, sprite_height))
|
||||
return (self.distance_to_sprite, sprite, sprite_pos)
|
||||
else:
|
||||
return (False,)
|
||||
|
||||
def sprite_animation(self):
|
||||
if self.animation and self.distance_to_sprite < self.animation_dist:
|
||||
sprite_object = self.animation[0]
|
||||
if self.animation_count < self.animation_speed:
|
||||
self.animation_count += 1
|
||||
else:
|
||||
self.animation.rotate()
|
||||
self.animation_count = 0
|
||||
return sprite_object
|
||||
return self.object
|
||||
|
||||
def visible_sprite(self):
|
||||
if self.viewing_angles:
|
||||
if self.theta < 0:
|
||||
self.theta += DOUBLE_PI
|
||||
self.theta = 360 - int(math.degrees(self.theta))
|
||||
|
||||
for angles in self.sprite_angles:
|
||||
if self.theta in angles:
|
||||
return self.sprite_positions[angles]
|
||||
return self.object
|
||||
|
||||
def dead_animation(self):
|
||||
if len(self.death_animation):
|
||||
if self.dead_animation_count < self.animation_speed:
|
||||
self.dead_sprite = self.death_animation[0]
|
||||
self.dead_animation_count += 1
|
||||
else:
|
||||
self.dead_sprite = self.death_animation.popleft()
|
||||
self.dead_animation_count = 0
|
||||
return self.dead_sprite
|
||||
|
||||
def npc_in_action(self):
|
||||
sprite_object = self.obj_action[0]
|
||||
if self.animation_count < self.animation_speed:
|
||||
self.animation_count += 1
|
||||
else:
|
||||
self.obj_action.rotate()
|
||||
self.animation_count = 0
|
||||
return sprite_object
|
||||
|
||||
def open_door(self):
|
||||
if self.flag == 'door_h':
|
||||
self.y -= 3
|
||||
if abs(self.y - self.door_prev_pos) > TILE:
|
||||
self.delete = True
|
||||
elif self.flag == 'door_v':
|
||||
self.x -= 3
|
||||
if abs(self.x - self.door_prev_pos) > TILE:
|
||||
self.delete = True
|
Loading…
Reference in New Issue