Python Forum

Full Version: Speed issue with sprite update
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Pages: 1 2
This is my first attempt at the update for a sprite:
def update(self, event, dt):
               
        self.rect.y += 5
        temp_group = self.game.all_sprites.copy()
        temp_group.remove(self)
        block_hit_list = pg.sprite.spritecollide(self, temp_group, False)
        for block in block_hit_list:
 
                self.rect.bottom = block.rect.top
        temp_group.empty()
I'll add acceleration later, but the problem is with performance.

The game is one of blocks in a grid that the player mines around in. I want these particular blocks to fall if they are undermined, but having each of this kind of block try to move and perform this check each frame drags the program to a crawl.

I need to find a better way to have blocks fall if there is nothing underneath them. I'm going to work now on only doing this check when the player destroys a block, as that is the only time it would change, but I want to have a LOT of blocks in this game, so any ideas on speeding this up will help a lot.

I'm not invested in this method at all, so radical options are welcome. tnx
Everything can not be a sprite. I wish it could. But it can't.
For more complex world.
Tile map for background.
Object map for items that may change.
Sprites for player and enemies. Things that move around a lot.
Map out world you can check what below pretty quickly.

Otherwise.
You can always reduce what is check.
Have a list of column groups. That how you compare what in that column.
I would also suggest column disturbance. For it only trigger that column to be updated.
Unfortunately, each square needs to be a sprite. It's inspired by "mines of mars" for mobile. VERY good game and a lesson in atmosphere.

https://www.youtube.com/watch?v=dUT-4EG5Fjo

If you look, none of the blocks are static. They can be destroyed and/or fall or explode, etc. But the mines of mars has THOUSANDS of blocks and runs on mobile, so the devs were smart about it.

I'll have the destruction of a block trigger the block above to fall if is the the type to do so. Most are mine-able and just hang there if undermined, but some are indestructible and fall so they can trap you, crush you, or block of blocks you wanted to mine. Others still fall and explode... but I'm not there yet lol.

Thanks
Why do you think tile maps are static ?
They may be static like. Doesn't mean data isn't changeable.
Tile maps let you handle thousand of blocks.
Only draws what it see.
If block is hit.
- Change image/image-name in tile map. Image/image-name can tell you how much life is left.
- Check block above. If block can fall. Then change it to sprite. When falling is complete. Change it back into a tile.

Just can't have all sprites in one group. If you check against every sprite every time. That going get really slow. Tile maps let you check square above. When sprite is falling. Just check square below. This will be so much faster. More you have your program do. The slower it will become.
oh... I'll look into that more then, though not quite sure where to start. It is critical that each map be random, so I can figure out how to write a text file to use as the map with randomized characters, but I've only ever learned to read a map in and spawn sprites at those positions. I could use some help with this. Do you know of a more advanced .txt map turorial? I have no idea where to start on the map being interactive in the way you say. This is obviously how games in this genre work.

I got it working to a point, minus the speed issue. Took footage to show where I'm at.

https://github.com/Michaelm1789/All-my-s...er.avi.avi


don't know how the file name got so screwed up. Hopefully it'll run for you.
(Feb-17-2020, 09:31 AM)Windspar Wrote: [ -> ]Only draws what it see.
If block is hit.
- Change image/image-name in tile map. Image/image-name can tell you how much life is left.
- Check block above. If block can fall. Then change it to sprite. When falling is complete. Change it back into a tile.

I've made the program use a tile map now. At the moment it just spawns sprites for each tile, and the program runs just as it did before. If I make the map very big it is a huge slowdown, presumably due to it having recalculate all of the shifting sprite locations.

If it only rendered what is in the play window then speed won't be an issue. How would I go about doing that? I can easily enough have it just draw images, but then I don't know how to have the player interact with them so that they turn into sprites.

The game is working how I want at this point, I just don't know how to stop it from recalculating locations for 1000 off screen objects.

Here is where I'm at:

class TileMap:
    def __init__(self, game):
        self.data = []
        self.game = game
        letters = ("s", "d", "d", "d")

        for lines in range(200):
            line = ""
            for column in range(30):
                
                char = random.choice(letters)
                line = str(line + char)
            self.data.append(line)

        self.tilewidth = len(self.data[0])
        self.tileheight = len(self.data)
        self.width = self.tilewidth * self.game.block_size
        self.height = self.tileheight * self.game.block_size

    def make_map(self):
        map_surface = pg.Surface((self.width, self.height))
        self.render(map_surface)
        return map_surface

    def render(self, surface):

        for row, tiles in enumerate(self.data):
            for col, tile in enumerate(tiles):
                if tile == "s":
                    block = Block_stone(self.game, col, row, self.game.player)
                    self.game.all_sprites.add(block)
                    self.game.stone_blocks.add(block)
                    self.game.block_list.add(block)
                if tile == "d":
                    block = Block(self.game, col, row, self.game.player)
                    self.game.all_sprites.add(block)
                    self.game.block_list.add(block)

class Camera(object):
    def __init__(self, complex_camera, width, height):
        self.camera_func = complex_camera
        self.state = pg.Rect(0, 0, width, height)

    def apply(self, target):
        return target.rect.move(self.state.topleft)

    def update(self, target):
        self.state = self.camera_func(self.state, target.rect)

def complex_camera(camera, target_rect):
    x = -target_rect.center[0] + 300 
    y = -target_rect.center[1] + 300
    camera.topleft += (pg.Vector2((x, y)) - pg.Vector2(camera.topleft)) * 0.06 
    camera.x = max(-(camera.width-300), min(0, camera.x))
    camera.y = max(-(camera.height-300), min(1000, camera.y))

    return camera
1. Tile map wouldn't use a big surface. You would loop through the tiles.

I have more time this weekend to give a working example. Code below will probably change.
This is very rough an unfinished an untested at the moment.
import pygame
from itertools import product

# Optional
class TileLayer:
    def __init__(self):
        self.background = None
        self.foreground = None
        self.foreground_rect = None

    def draw(self, surface, position, images, player):
        surface.blit(images[self.background], position)
        if foreground_rect:
            if player.rect.bottom < self.foreground_rect.top:
                player.draw(surface)
                surface.blit(images[self.foreground], position)
            else:
                surface.blit(images[self.foreground], position)
                player.draw(surface)

        elif foreground:
            surface.blit(images[self.foreground], position)

class TileSprite(Sprite):
    def __init__(self, image, position, anchor="topleft"):
        self.image = image
        self.rect = self.image.get_rect(**{anchor: position})
        self.tile_position = pygame.Vector2(self.rect.center)
        self.map_position = pygame.Vector2(self.rect.center)

    def move(self, position, camera_position):
        self.tile_position += pygame.Vector2(position)
        self.map_position = self.tile_position - camera_position
        self.rect.center = self.map_position

    def update(self, camera_position):
        self.map_position = self.tile_position - camera_position
        self.rect.center = self.map_position

class TileMap:
    def __init__(self, images, screen_rect, tilesize):
        self.images = images
        self.map_data = [[]]
        self.tilesize = tilesize
        self.display_size = pygame.Vector2(int(screen_rect.w / tilesize.x) + 1,
                                           int(screen_rect.h / tilesize.y) + 1)

    def draw(self, surface, camera, player):
        start = int(camera.position.x)
        x_range = range(start, int(start + self.display_size.x))
        start = int(camera.position.y)
        y_range = range(start, int(start + self.display_size.y))
        for x, y in product(x_range, y_range):
            try:
                position = x * self.tilesize.x , y * self.tilesize.y
                # TileLayer optional
                self.map_data[y][x]].draw(surface, position, self.images, player)
                # else
                # surface.blit(self.images[self.map_data[y][x]], position)
            except:
                pass

# TileCamera moves the map. Not the player. Player would remain in the center.
class TileCamera:
    def __init__(self):
        self.tilemap = TileMap()
        self.sprites = Group()
        self.position = pygame.Vector2()
        self.speed = 0.08

    def draw(self, surface):
        self.tilemap(surface, self)

    def update(self, delta):
        keys_pressed = pygame.key.get_pressed()
        direction = pygame.Vector2(0, 0)
        if keys_pressed[pygame.K_w]:
            direction.y -= 1

        if keys_pressed[pygame.K_s]:
            direction.y += 1

        if keys_pressed[pygame.K_a]:
            direction.x -= 1

        if keys_pressed[pygame.K_d]:
            direction.x += 1

        if direction = pygame.Vector2(0, 0):
            direction.normlize_ip()
            self.position = direction * self.speed * delta

        self.sprites.update(self.position)
Here what I got so far. https://github.com/Windspar/TileMapExample
It keeps around 54 to 60 frames. For random map 1000 by 1000.
Map size should make no difference in fps.
Display size of map will effect fps.

I make another example for you. Other people have asked this question to. But for a zelda like game.
Same code with some adjustment to do what you want.
I thank you for this. I've been spending some time looking at it. Some of it is unfamiliar to me, but I think this is the real trick, right?

 def draw(self, surface, camera, player):
        x_pos = camera.position.x / self.tilesize
        y_pos = camera.position.y / self.tilesize
        x_range = self.get_range(camera.position.x, self.display_size.x, self.map_size[0])
        y_range = self.get_range(camera.position.y, self.display_size.y, self.map_size[1])
        for x, y in product(x_range, y_range):
            try:
                position = int((x - x_pos) * self.tilesize) , int((y - y_pos) * self.tilesize)
                tile = self.map_data[y][x]
                if isinstance(tile, TileLayer):
                    tile.draw(surface, position, self.images, player)
                else:
                    surface.blit(self.images[tile], position)
            except:
                pass
This is the part that save CPU power, no? It looks to me to be the part that finds and draws only the tiles who's coordinates occur on the screen surface. That's the big difference between how I'm currently doing it.
Yes. Just looks up what in screen area. All the rest is ignore. But need to remove try and except. That was for something I was testing. Forgot to remove it.
Example just for background or foreground.
    def draw(self, surface, camera):
        location = camera.position / self.tilesize
        x_range = self.get_range(camera.position.x, self.display_size.x, self.mapsize[0])
        y_range = self.get_range(camera.position.y, self.display_size.y, self.mapsize[1])
        for x, y in product(x_range, y_range):
            position = int((x - location.x) * self.tilesize), int((y - location.y) * self.tilesize)
            tile = self.map_data[y][x]
            if tile:
                surface.blit(self.map_images[tile], position)

    def get_range(self, start_position, end_position, max_position):
        start = start_position / self.tilesize
        start_range = max(int(start), 0)
        end_range = min(int(start + end_position), max_position)
        return range(start_range, end_range)
Pages: 1 2