예제 #1
0
    def __init__(self, name, spawn_pos):
        self.name = name

        self._spawn_pos = WVec(spawn_pos)

        self.pos = WVec()
        self._req_pos = WVec()
        self._vel = WVec()
        self._req_vel = WVec()

        self.spawn()

        self._is_on_ground = False

        self._w_size = WVec(0.6, 1.8)
        self._bounds_w_shift = WBounds(
            min=WVec(-self._w_size.x / 2, 0.0),
            max=WVec(self._w_size.x / 2, self._w_size.y),
        )

        self._anim_surf_walking = AnimatedSurface(
            os.path.join(RESOURCES_PATH, self._MAIN_PLAYER_DIR, "walking"),
            w_height=self._w_size.y,
            neutrals=(0, 8),
        )
        self._anim_surf_sprinting = AnimatedSurface(
            os.path.join(RESOURCES_PATH, self._MAIN_PLAYER_DIR, "sprinting"),
            w_height=self._w_size.y,
            neutrals=(0, 6),
        )
        self._anim_surf = self._anim_surf_walking

        self._walking_speed = PLAYER_ABILITY_FACTOR * 4.5 / CAM_FPS
        self._sprinting_speed = PLAYER_ABILITY_FACTOR * 7.5 / CAM_FPS
        self._jumping_speed = PLAYER_ABILITY_FACTOR * 7.75 / CAM_FPS
예제 #2
0
 def _update_colliders(self):
     self.colliders = Colliders()
     for block_w_pos in self.blocks_map:
         if not (block_w_pos + WVec(-1, 0)) in self.blocks_map:
             self.colliders.left.append(block_w_pos)
         if not (block_w_pos + WVec(+1, 0)) in self.blocks_map:
             self.colliders.right.append(block_w_pos)
         if not (block_w_pos + WVec(0, -1)) in self.blocks_map:
             self.colliders.down.append(block_w_pos)
         if not (block_w_pos + WVec(0, +1)) in self.blocks_map:
             self.colliders.up.append(block_w_pos)
예제 #3
0
    def __init__(self):
        self._seed = random.randint(0, 2**20) + 0.15681

        self.chunks_existing_map = {}
        self._chunks_visible_map = {}

        self._c_view = CBounds(CVec(0, 0), CVec(
            0,
            0))  # Needs to keep the arguments, in order to remain of type int.
        self._max_view = WBounds(WVec(0, 0), WVec(0, 0))  # This too.

        self._max_surf = pg.Surface((0, 0))
        self._force_draw = True

        self._action_cooldown_remaining = 0
예제 #4
0
    def __init__(self, dir_path, w_height, neutrals=(), frame_rate=30):
        self.neutrals = neutrals

        self._images = []
        self._images_flipped = []
        images_path = os.path.join(dir_path, "images")
        masks_path = os.path.join(dir_path, "masks")
        for image_file, mask_file in zip(
                sorted(os.scandir(images_path), key=lambda x: x.name),
                sorted(os.scandir(masks_path), key=lambda x: x.name)):
            image = pg.image.load(image_file.path).convert()
            mask = pg.image.load(mask_file.path).convert()
            self._images.append((image, mask))
            self._images_flipped.append((pg.transform.flip(image, True, False),
                                         pg.transform.flip(mask, True, False)))

        self.pix_size = PixVec(*self._images[0][0].get_size())
        pix_to_w_factor = w_height / self.pix_size.y
        self.w_size = WVec(x=self.pix_size.x * pix_to_w_factor, y=w_height)

        self.action = AnimAction.pause
        self._frame = 0
        self._frame_rate = frame_rate
        self.is_flipped = False

        self.surf = pg.Surface(self.pix_size)
        self._light_surf = pg.Surface(self.pix_size)
예제 #5
0
 def gen_chunk_blocks(self, chunk_w_pos: WVec):
     blocks_map = {}
     for w_shift_x in range(CHUNK_W_SIZE.x):
         for w_shift_y in range(CHUNK_W_SIZE.y):
             block_w_pos = WVec(chunk_w_pos.x + w_shift_x,
                                chunk_w_pos.y + w_shift_y)
             block_type = self._choose_block_type_at_pos(block_w_pos)
             if block_type is None:
                 continue
             blocks_map[block_w_pos] = Block(block_type)
     return blocks_map
예제 #6
0
def pix_to_w_shift(pix_shift: PixVec,
                   source_surf_pix_size: PixVec,
                   dest_surf_pix_size: PixVec,
                   source_pivot: PixVec = PixVec(),
                   dest_pivot: PixVec = PixVec(),
                   *,
                   scale=BLOCK_PIX_SIZE.x):
    return WVec(
        (pix_shift.x + source_pivot.x - dest_pivot.x) / scale,
        (-pix_shift.y + dest_surf_pix_size.y - source_surf_pix_size.y +
         source_pivot.y - dest_pivot.y) / scale,
    )
예제 #7
0
    def __init__(self):
        self._pos = WVec()
        self._req_pos = WVec(self._pos)

        self._vel = WVec()
        self._req_vel = WVec(self._vel)

        self._zoom_vel = 1.0
        self._req_zoom_vel = 1.0

        self._scale = CAM_DEFAULT_SCALE

        if FULLSCREEN:
            self._screen = pg.display.set_mode((0, 0), pg.FULLSCREEN)
        else:
            self._screen = pg.display.set_mode((1280, 720))
        self._pix_size = PixVec(self._screen.get_size())

        self.selected_block_w_pos = WVec(self._pos)
        self.selected_space_w_pos = WVec(self._pos)
        self._block_selector_surf = pg.image.load(
            os.path.join(GUI_PATH, "block_selector.png")).convert()
        self._block_selector_space_only_surf = pg.image.load(
            os.path.join(GUI_PATH, "block_selector_space_only.png")).convert()

        # Surfs to reuse
        self._world_max_surf_scaled = pg.Surface((0, 0))
        self._player_surf_scaled = pg.Surface((0, 0))
        self._player_surf_scaled.set_colorkey(C_KEY)
        self._block_selector_surf_scaled = pg.Surface((0, 0))
        self._block_selector_surf_scaled.set_colorkey(C_KEY)

        self._clock = pg.time.Clock()
        self._font = pg.font.SysFont(pg.font.get_default_font(), 24)
예제 #8
0
    def set_transforms(self, pos, vel=WVec()):
        """Set the transforms directly without going through a request.
        """
        self.pos = WVec(pos)
        self._req_pos = WVec(self.pos)

        self._vel = WVec(vel)
        self._req_vel = WVec(self._vel)
예제 #9
0
 def _get_chunk_maps_around(self, w_pos: WVec, c_radius):
     chunks_map = {}
     for pos_x in range(floor(w_pos.x - c_radius * CHUNK_W_SIZE.x),
                        floor(w_pos.x + (c_radius + 1) * CHUNK_W_SIZE.x),
                        CHUNK_W_SIZE.x):
         for pos_y in range(
                 floor(w_pos.y - c_radius * CHUNK_W_SIZE.y),
                 floor(w_pos.y + (c_radius + 1) * CHUNK_W_SIZE.y),
                 CHUNK_W_SIZE.y):
             chunk_map = self._get_chunk_map_at_w_pos(WVec(pos_x, pos_y))
             if chunk_map is None:
                 continue
             chunks_map.update((chunk_map, ))
     return chunks_map
예제 #10
0
    def move(self, world, substeps=1):
        """Apply requested and physics-induced movements.
        """
        self._vel.x += (self._req_vel.x -
                        self._vel.x) * PLAYER_POS_DAMPING_FACTOR
        self._vel.y += self._req_vel.y

        self._vel += self._ACC

        self._is_on_ground = False  # Assumption, to be corrected inside self._collide.

        collision_steps = (floor(self._vel.norm()) + 1) * substeps
        for _ in range(collision_steps):
            self._req_pos = self.pos + self._vel / collision_steps
            self._collide(world)
            self.pos = WVec(self._req_pos)
예제 #11
0
    def _update_chunks_visible(self):
        self._max_view = WBounds(
            self._c_view.min * CHUNK_W_SIZE,
            (self._c_view.max + 1) * CHUNK_W_SIZE,
        )

        self._chunks_visible_map = {}
        for chunk_w_pos_x in range(self._max_view.min.x, self._max_view.max.x,
                                   CHUNK_W_SIZE.x):
            for chunk_w_pos_y in range(self._max_view.min.y,
                                       self._max_view.max.y, CHUNK_W_SIZE.y):
                chunk_w_pos = WVec(chunk_w_pos_x, chunk_w_pos_y)
                if chunk_w_pos in self.chunks_existing_map:
                    chunk_visible = self.chunks_existing_map[chunk_w_pos]
                else:
                    chunk_visible = self._create_chunk(chunk_w_pos)

                self._chunks_visible_map[chunk_w_pos] = chunk_visible
예제 #12
0
    def get_block_pos_and_space_pos(self,
                                    start_w_pos: WVec,
                                    end_w_pos: WVec,
                                    max_distance,
                                    *,
                                    substeps=5,
                                    max_rays=3) -> BlockSelection:
        w_vel = end_w_pos - start_w_pos
        w_speed = w_vel.norm()
        w_dir = w_vel.dir_()
        w_dirs = w_vel.dirs_()

        c_radius = min(w_speed, max_distance) // max(CHUNK_W_SIZE) + 1

        block_w_pos: WVec
        block_w_pos = floor(end_w_pos)

        blocks_map = self._get_blocks_map_around(end_w_pos, c_radius)

        got_block = True
        if block_w_pos not in blocks_map:
            got_block = False
            for dir_ in w_dirs:
                if block_w_pos + dir_ in blocks_map:
                    break
            else:
                # Return early if there's no block at or next to block_w_pos:
                return BlockSelection(None, None)

        ray_origin_shift = WVec()
        w_dir_horiz, w_dir_vert = w_dirs
        if (w_dir_horiz == Dir.right) ^ (not got_block):
            ray_origin_shift.x = BLOCK_BOUND_SHIFTS.min.x
        else:
            ray_origin_shift.x = BLOCK_BOUND_SHIFTS.max.x
        if (w_dir_vert == Dir.up) ^ (not got_block):
            ray_origin_shift.y = BLOCK_BOUND_SHIFTS.min.y
        else:
            ray_origin_shift.y = BLOCK_BOUND_SHIFTS.max.y

        poss_to_check = set()
        for ray_index in range(max_rays):
            poss_to_check.add(block_w_pos +
                              WVec(ray_origin_shift.x, ray_index /
                                   (max_rays - 1)))
            poss_to_check.add(block_w_pos +
                              WVec(ray_index /
                                   (max_rays - 1), ray_origin_shift.y))

        hits = 0
        found_path = False
        for pos_to_check in poss_to_check:
            w_vel_iter = pos_to_check - start_w_pos
            w_speed_iter = w_vel_iter.norm()
            w_vel_step = w_vel_iter / (w_speed_iter * substeps)
            max_mult = floor(w_speed_iter * substeps)
            for mult in range(max_mult + 1):
                w_pos = floor(start_w_pos + w_vel_step * mult)
                if w_pos in blocks_map and not w_pos == block_w_pos:
                    break
            else:
                hits += 1
                if hits < 2:  # This is to avoid selecting blocks for which only 1 corner is visible.
                    continue
                found_path = True
                break

        if not found_path:
            return BlockSelection(None, None)

        if got_block:
            block_w_pos_shifts = [-w_dir_horiz, -w_dir_vert]
        else:
            block_w_pos_shifts = [w_dir_vert, w_dir_horiz]
        block_center_rel_w_pos = end_w_pos - block_w_pos - WVec(0.5, 0.5)
        if -w_dir.y * block_center_rel_w_pos.y > -w_dir.x * block_center_rel_w_pos.x:
            block_w_pos_shifts.reverse()

        block_w_pos_shift = block_w_pos_shifts[0]
        if got_block:
            if block_w_pos + block_w_pos_shift in blocks_map:
                block_w_pos_shift = block_w_pos_shifts[1]
            return BlockSelection(block_w_pos,
                                  block_w_pos_shift,
                                  space_only=False)
        else:  # got space:
            if block_w_pos + block_w_pos_shift not in blocks_map:
                block_w_pos_shift = block_w_pos_shifts[1]
            return BlockSelection(block_w_pos + block_w_pos_shift,
                                  -block_w_pos_shift,
                                  space_only=True)
예제 #13
0
 def cell_index_to_block_w_pos(self, ij):
     i, j = ij
     x = j - 1
     y = CHUNK_W_SIZE.y - i
     w_shift = WVec(x, y)
     return self._w_shift_to_block_w_pos(w_shift)
예제 #14
0
    def set_transforms(self, pos: WVec, vel: WVec = WVec()):
        self._pos = WVec(pos)
        self._req_pos = WVec(self._pos)

        self._vel = WVec(vel)
        self._req_vel = WVec(self._vel)
예제 #15
0
 def action_w_pos(self):
     """Getter for the position from which the player acts upon its environment.
     """
     action_w_pos = WVec(self.pos)
     action_w_pos.y += self._w_size.y * self._ACTION_POS_RATIO
     return action_w_pos
예제 #16
0
import os

import pygame as pg

from core.classes import PixVec, WVec, WBounds, Dir

# ==== TECHNICAL DIMENSIONS ====
PLAYER_S_POS = PixVec(0.5, 0.333)
HOTBAR_S_POS = PixVec(0.5, 0.1)
HOTBAR_ORIG_PIX_SIZE = PixVec(184, 24)
HOTBAR_PIX_SIZE = HOTBAR_ORIG_PIX_SIZE * 4
BLOCK_PIX_SIZE = PixVec(
    16, 16)  # Should stay equal to block texture size resolution. That is, 16.
CHUNK_W_SIZE = WVec(8, 8)
CHUNK_PIX_SIZE = BLOCK_PIX_SIZE * CHUNK_W_SIZE
WORLD_HEIGHT_BOUNDS = WVec(0, 2**8)
BLOCK_BOUND_SHIFTS = WBounds(WVec(0, 0), WVec(1, 1))

# ==== COLORS ====
C_KEY = pg.Color(255, 0, 0)

C_BLACK = pg.Color(0, 0, 0)
C_WHITE = pg.Color(255, 255, 255)
C_SKY = pg.Color(120, 190, 225)

# ==== CAM ====
CAM_FPS = 60
CAM_DEFAULT_SCALE = 64.0
CAM_SCALE_BOUNDS = (16.0, 128.0)

# ==== GAME DYNAMICS ====
예제 #17
0
class Player:
    _ACC = WVec(GRAVITY)
    _ACTION_POS_RATIO = 0.75
    _MAIN_PLAYER_DIR = "steve"

    def __init__(self, name, spawn_pos):
        self.name = name

        self._spawn_pos = WVec(spawn_pos)

        self.pos = WVec()
        self._req_pos = WVec()
        self._vel = WVec()
        self._req_vel = WVec()

        self.spawn()

        self._is_on_ground = False

        self._w_size = WVec(0.6, 1.8)
        self._bounds_w_shift = WBounds(
            min=WVec(-self._w_size.x / 2, 0.0),
            max=WVec(self._w_size.x / 2, self._w_size.y),
        )

        self._anim_surf_walking = AnimatedSurface(
            os.path.join(RESOURCES_PATH, self._MAIN_PLAYER_DIR, "walking"),
            w_height=self._w_size.y,
            neutrals=(0, 8),
        )
        self._anim_surf_sprinting = AnimatedSurface(
            os.path.join(RESOURCES_PATH, self._MAIN_PLAYER_DIR, "sprinting"),
            w_height=self._w_size.y,
            neutrals=(0, 6),
        )
        self._anim_surf = self._anim_surf_walking

        self._walking_speed = PLAYER_ABILITY_FACTOR * 4.5 / CAM_FPS
        self._sprinting_speed = PLAYER_ABILITY_FACTOR * 7.5 / CAM_FPS
        self._jumping_speed = PLAYER_ABILITY_FACTOR * 7.75 / CAM_FPS

    # ==== GET DATA ====

    def get_bounds(self, w_pos=None) -> WBounds:
        """Return the boundaries of the Player at its current position, or at w_pos if the argument has been passed.
        """
        if w_pos is None:
            w_pos = self.pos

        return get_bounds(w_pos, self._bounds_w_shift)

    @property
    def is_dead(self):
        return self.pos.y < PLAYER_POS_MIN_HEIGHT

    @property
    def action_w_pos(self):
        """Getter for the position from which the player acts upon its environment.
        """
        action_w_pos = WVec(self.pos)
        action_w_pos.y += self._w_size.y * self._ACTION_POS_RATIO
        return action_w_pos

    # ==== DRAW ====

    def draw(self, camera, world):
        sky_light = world.get_sky_light_at_w_pos(self.pos)
        camera.draw_player(self._anim_surf, self.pos, sky_light)

    # ==== REQUEST MOVEMENTS ====

    def req_move_right(self):
        self._anim_surf_walking.sync(self._anim_surf)
        self._anim_surf = self._anim_surf_walking
        self._anim_surf.action = AnimAction.play
        self._anim_surf.is_flipped = False
        self._req_vel.x = self._walking_speed

    def req_move_left(self):
        self._anim_surf_walking.sync(self._anim_surf)
        self._anim_surf = self._anim_surf_walking
        self._anim_surf.action = AnimAction.play
        self._anim_surf.is_flipped = True
        self._req_vel.x = -self._walking_speed

    def req_sprint_right(self):
        self._anim_surf_sprinting.sync(self._anim_surf)
        self._anim_surf = self._anim_surf_sprinting
        self._anim_surf.action = AnimAction.play
        self._anim_surf.is_flipped = False
        self._req_vel.x = self._sprinting_speed

    def req_sprint_left(self):
        self._anim_surf_sprinting.sync(self._anim_surf)
        self._anim_surf = self._anim_surf_sprinting
        self._anim_surf.action = AnimAction.play
        self._anim_surf.is_flipped = True
        self._req_vel.x = -self._sprinting_speed

    def req_h_move_stop(self):
        self._anim_surf.action = AnimAction.end
        self._req_vel.x = 0

    def req_v_move_stop(self):
        self._req_vel.y = 0

    def req_jump(self):
        if self._is_on_ground:
            self._req_vel.y = self._jumping_speed
        else:
            self.req_jump_stop()

    def req_jump_stop(self):
        self._req_vel.y = 0

    # ==== APPLY MOVEMENTS ====

    def set_transforms(self, pos, vel=WVec()):
        """Set the transforms directly without going through a request.
        """
        self.pos = WVec(pos)
        self._req_pos = WVec(self.pos)

        self._vel = WVec(vel)
        self._req_vel = WVec(self._vel)

    def spawn(self):
        """Set or reset the player to its spawning state.
        """
        self.set_transforms(self._spawn_pos, (0.0, -100.0 / CAM_FPS))

    def _collide(self, world, threshold=0.001):
        """Check for collisions with the world and update the transforms accordingly.
        """
        world_colliders = world.get_colliders_around(self.pos, c_radius=1)

        tested_horiz_pos = (self._req_pos.x, self.pos.y)
        tested_horiz_pos_bounds = self.get_bounds(tested_horiz_pos)

        tested_vert_pos = (self.pos.x, self._req_pos.y)
        tested_vert_pos_bounds = self.get_bounds(tested_vert_pos)

        for pos_x in range(tested_vert_pos_bounds.min.x,
                           tested_vert_pos_bounds.max.x + 1):
            if self._vel.y < 0:
                pos_y = tested_vert_pos_bounds.min.y
                if (pos_x, pos_y) in world_colliders.up:
                    self._req_pos.y = pos_y + BLOCK_BOUND_SHIFTS.max.y - self._bounds_w_shift.min.y + threshold
                    self._vel.y = 0
                    self._is_on_ground = True
                    break

            else:
                pos_y = tested_vert_pos_bounds.max.y
                if (pos_x, pos_y) in world_colliders.down:
                    self._req_pos.y = pos_y + BLOCK_BOUND_SHIFTS.min.y - self._bounds_w_shift.max.y - threshold
                    self._vel.y = 0
                    break

        for pos_y in range(tested_horiz_pos_bounds.min.y,
                           tested_horiz_pos_bounds.max.y + 1):
            if self._vel.x <= 0:
                pos_x = tested_horiz_pos_bounds.min.x
                if (pos_x, pos_y) in world_colliders.right:
                    self._req_pos.x = pos_x + BLOCK_BOUND_SHIFTS.max.x - self._bounds_w_shift.min.x + threshold
                    self._vel.x = 0
                    break

            else:
                pos_x = tested_horiz_pos_bounds.max.x
                if (pos_x, pos_y) in world_colliders.left:
                    self._req_pos.x = pos_x + BLOCK_BOUND_SHIFTS.min.x - self._bounds_w_shift.max.x - threshold
                    self._vel.x = 0
                    break

    def move(self, world, substeps=1):
        """Apply requested and physics-induced movements.
        """
        self._vel.x += (self._req_vel.x -
                        self._vel.x) * PLAYER_POS_DAMPING_FACTOR
        self._vel.y += self._req_vel.y

        self._vel += self._ACC

        self._is_on_ground = False  # Assumption, to be corrected inside self._collide.

        collision_steps = (floor(self._vel.norm()) + 1) * substeps
        for _ in range(collision_steps):
            self._req_pos = self.pos + self._vel / collision_steps
            self._collide(world)
            self.pos = WVec(self._req_pos)

    # ==== SAVE AND LOAD ====

    def load_from_disk(self, dir_path):
        try:
            with open(os.path.join(dir_path, f"{self.name}.json")) as file:
                data = json.load(file)
        except FileNotFoundError:
            return LoadResult.no_file

        self.set_transforms(data["pos"], data["vel"])
        self._is_on_ground = data["is_on_ground"]
        self._anim_surf.is_flipped = data["is_reversed"]
        return LoadResult.success

    def save_to_disk(self, dir_path):
        data = {
            "pos": tuple(self.pos),
            "vel": tuple(self._vel),
            "is_on_ground": self._is_on_ground,
            "is_reversed": self._anim_surf.is_flipped,
        }
        with open(os.path.join(dir_path, f"{self.name}.json"), "w") as file:
            json.dump(data, file, indent=4)