FreshRSS

Zobrazení pro čtení

Jsou dostupné nové články, klikněte pro obnovení stránky.

Connecting Isolated Paths in Randomly Generated Maps

I am new to game development, I previously created games using tiledmap editor and pygame, but manually creating map was real pain, so i begun to look how can i procedurally generate map, after banging head for few week and googling i finally created a map. However, I'm facing an issue where some paths on the map end up isolated from each other. I'm looking for suggestions or algorithms to connect these isolated parts of the map, ensuring that there is always a continuous path from any point to any other point on the map. The code works on looking at adjacent neighbour but in the end most of the time it cause isolation of part of map by closing path.

from random import random
from pprint import pprint, pformat

WALL = 1
EMPTY = 0
POSSIBLE_WALL_PERCENT = 0.4
ADJACENT_WALL_THRESHOLD = 5
range_X = 1
range_Y = 1

class Map:
    def __init__(self, width: int, height: int):
        self.width = width
        self.height = height
        self.grid = [[EMPTY for _ in range(width)] for _ in range(height)]

        self.random_wall_fill()
        self.place_walls()

    def random_wall_fill(self):
        for row in range(self.height):
            for col in range(self.width):
                if random() < POSSIBLE_WALL_PERCENT:
                    self.grid[row][col] = WALL

    def get_adjacent_wall(self, x: int, y: int, range_x: int, range_y: int):
        wall_count: int = 0
        start_x, end_x = x - range_x, x + range_x
        start_y, end_y = y - range_y, y + range_y  

        for i_y in range(start_y, end_y + 1):
            for i_x in range(start_x, end_x + 1):
                if (i_x != x or i_y != y) and self.is_wall(i_x, i_y):
                    wall_count += 1

        return wall_count

    def place_walls(self):
        for y in range(self.height):
            for x in range(self.width):
                if self.grid[y][x] == EMPTY:
                    if self.place_wall_logic(x, y):
                        self.grid[y][x] = WALL

    def place_wall_logic(self, x, y):
        num_walls = self.get_adjacent_wall(x, y, range_X, range_Y)
        if num_walls >= ADJACENT_WALL_THRESHOLD:
            return True
        else:
            return False

    def is_wall(self, x: int, y: int):
        if self.out_of_bound(x, y):
            return True
        elif self.grid[y][x] == WALL:
            return True
        return False

    def out_of_bound(self, x: int, y: int):
        if x < 0 or x >= len(self.grid[0]):
            return True
        elif y < 0 or y >= len(self.grid):
            return True
        return False

    def display_map(self):
        for row in self.grid:
            for cell in row:
                if cell == WALL:
                    print("0", end=" ")
                else:
                    print(".", end=" ")
            print() 

    def __repr__(self):
        return pformat(self.grid)

level_map = Map(30, 30)
level_map.display_map()

map screenshot

Poor performance with camera zoom

I've created a Camera class that takes a source surface and draws it to a destination surface using the camera's parameters (position, zoom, size, etc.).

The class works well, but the framerate drops significantly when zooming into the surface.

I've tried 2 things:

  • I've added convert_alpha() to the textures, but that doesn't seem to help

  • I've tried using surface.subsurface() to get the portion of the source surface to draw. This does speed things up a bit, but this breaks the camera's behaviour (zooming in is jerky, the source surface isn't centred in the camera when zoomed out).

The Camera class is here: https://github.com/rik-cross/pygamepal/blob/main/src/pygamepal/camera.py

    def draw(self, surface, destSurface):

        # draw border
        pygame.draw.rect(destSurface, self.borderColor, 
                         (self.position[0] - self.borderThickness, self.position[1] - self.borderThickness, 
                          self.size[0] + self.borderThickness * 2, self.size[1] + self.borderThickness * 2), self.borderThickness, border_radius = 1)
        # ensure that the surface is clipped to the camera dimensions
        destSurface.set_clip((self.position[0], self.position[1], self.size[0], self.size[1]))
        # fill the surface to the background color
        destSurface.fill(self.backgroundColor)

        # blit the (zoomed) surface to the destination, and set the target as the center
        x = 0 - (self.size[0] / 2 - self._currentTarget[0] * self._currentZoom)
        y = 0 - (self.size[1] / 2 - self._currentTarget[1] * self._currentZoom)
        # add screen shake
        x += self._shakeCurrent[0]
        y += self._shakeCurrent[1]

        # draw the surface to the destination using the correct position, size, center and zoom
        destSurface.blit(pygame.transform.scale(surface, (surface.get_width() * self._currentZoom, surface.get_height() * self._currentZoom)), 
                         self.position, 
                         (x, y,
                          self.size[0], self.size[1]))
        # reset surface clipping
        destSurface.set_clip()

The example with poor performance is here: https://github.com/rik-cross/pygamepal/blob/main/examples/fullExample.py (walk towards the trees to see the framerate drop).

class GameScene(pygamepal.Scene):
    
    def zoomCamera(self, this, other, zoom):
        self.camera.zoom = zoom
    
    def setChestState(self, this, other, state):
        self.chest.spriteImage.pause = False
        self.chest.spriteImage.state = state

    def init(self):
        
        #
        # add textures
        #

        self.map = pygame.image.load(os.path.join('images', 'map.png'))
        self.map = self.map.convert_alpha()

        #
        # add sprites
        #

        # player

        self.player = Player()
        self.addSprite(self.player)

        # trees

        self.addSprite(
            pygamepal.Sprite(
                imageName = os.path.join('images', 'tree.png'),
                position = (40, 50),
                collider = pygamepal.Collider(offset = (6, 25), size = (12, 5))
            )
        )
        self.addSprite(
            pygamepal.Sprite(
                imageName = os.path.join('images', 'tree.png'),
                position = (70, 40),
                collider = pygamepal.Collider(offset = (6, 25), size = (12, 5))
            )
        )
        self.addSprite(
            pygamepal.Sprite(
                imageName = os.path.join('images', 'tree.png'),
                position = (20, 10),
                collider = pygamepal.Collider(offset = (6, 25), size = (12, 5))
            )
        )

        # chest

        # load a texture
        chestSpritesheet = pygame.image.load(os.path.join('images','chest_spritesheet.png'))
        # split texture into a 2D list of sub-textures
        splitTextures = pygamepal.splitTexture(chestSpritesheet, 48, 48)
        
        self.chest = pygamepal.Sprite(position = (175, 175), size = (16, 14))
        self.chest.collider = pygamepal.Collider(offset = (0, 8), size = (16, 6))
        self.chest.spriteImage = pygamepal.SpriteImage()
        self.chest.spriteImage.addTextures(splitTextures[0][0], splitTextures[0][1], splitTextures[0][2], splitTextures[0][3], offset = (16, 18), state = 'open', animationDelay = 4, loop = False)
        self.chest.spriteImage.addTextures(splitTextures[0][3], splitTextures[0][2], splitTextures[0][1], splitTextures[0][0], offset = (16, 18), state = 'close', animationDelay = 4, loop = False)
        self.chest.spriteImage.pause = True
        self.chest.trigger = pygamepal.Trigger(size = (26, 24), offset = (-5, -5),
            onEnter = lambda this, other, state = 'open': self.setChestState(this, other, state),
            onExit = lambda this, other, state = 'close': self.setChestState(this, other, state))
        self.addSprite(self.chest)

        #
        # customise the scene
        #

        self.backgroundColor = 'black'
        self.sortKey = self.sortByBottom

        #
        # customise the scene camera
        #
        
        self.camera.backgroundColor = 'black'
        self.camera.setTarget(self.player.getCenter(), instant = True)
        self.camera.lazyFollow = 0.9
        self.camera.setZoom(4, instant = True)
        self.camera.lazyZoom = 0.9
        self.camera.clamp = True
        self.camera.clampRect = (0, 0, 256, 256)
        
        #
        # add triggers for the camera
        #

        self.forestTrigger = pygamepal.Trigger(position = (10, 20), size = (100, 70),
            onEnter = lambda this, other, zoom = 6 : self.zoomCamera(this, other, zoom),
            onExit = lambda this, other, zoom = 4 : self.zoomCamera(this, other, zoom))
        self.addTrigger(self.forestTrigger)

        #
        # add map bounds colliders
        #

        # top
        self.addCollider(pygamepal.Collider((0, 0), (256, 2)))
        # bottom
        self.addCollider(pygamepal.Collider((0, 254), (256, 2)))
        # left
        self.addCollider(pygamepal.Collider((0, 0), (2, 256)))
        # right
        self.addCollider(pygamepal.Collider((254, 0), (2, 256)))

    def update(self):

        # [ESC] to return to menu scene
        if self.game.input.isKeyPressed(pygame.K_ESCAPE):
            self.game.currentScene = menuScene
        
        # camera tracks the player
        self.camera.target = self.player.getCenter()

    def draw(self):

        self.sceneSurface.blit(self.map, (0, 0))

I thought I'd ask for help in case there's an easy fix!

Thanks!

❌