Python Forum

Full Version: Spawning platforms that don't touch
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Pages: 1 2
I have this from a "doodle jump" type game.

while len(self.platforms) < 7:
            width = random.randrange(50, 100)
            Platform(self, random.randrange(0, WIDTH - width),
                         random.randrange(-60, -40), self.kind)
I want it so when a platform spawns on top of another, one is deleted. They are all the self.platforms sprite group.

Is there a way to do something like this?
pygame.sprite.spritecollide(self, [group-not-including-self], True, False)

or I should remove each from the group, check, then add it back. That seems ugly. Confused
There 2 options I can think of.
1. Don't add created sprite to group. Until after collision.
2. You can use group.copy() to make a duplicate group. Then remove self. Then remove all sprites in main group.
dup_group = group.copy()
dup_group.remove(self)
collision = pygame.sprite.sprite.collide(self, dup_group, False)
group.remove(collision)
I ran into some problems. If I leave the "kill" part of the collision at False, the sprites piled up at the top of the screen and eventually gave huge lag. So I changed it and have tried every collision type and way of killing sprites because what happens is that when the player falls and is supposed to die, the game slows to a halt and eventually gives a
Error:
pygame ERROR: out of memory.
I don't know what is piling up.

Here is the latest version that I put in the Platform._init_. Works great at first but lags after a while and lags when the player gets a powerup and zips up really fast:
dup_group = self.game.platforms.copy()
dup_group.remove(self)
collision = pg.sprite.spritecollide(self, dup_group, True)
self.game.platforms.remove(collision)    
So I tried in the update section where the code I first posted is, where the Platform object is created. Same thing:
while len(self.platforms) < 7:
          width = random.randrange(50, 100)
          p = Platform(self, random.randrange(0, WIDTH - width),
                             random.randrange(-60, -40), self.kind)
            dup_group = self.platforms.copy()
            dup_group.remove(p)
            collision = pg.sprite.spritecollide(p, dup_group, False)
            self.platforms.remove(collision)  
Tried adding a dup_group.empty() just in case. Nothing.

Here is the kill code for when the player falls to his death. This is when the game freezes and eventually runs out of memory:
 # Die!
        if self.player.rect.bottom > HEIGHT:
            for sprite in self.all_sprites:
                sprite.rect.y -= max(self.player.vel.y, 10)
                if sprite.rect.bottom < 0:
                    sprite.kill()
        if len(self.platforms) == 0:
            self.die_sound.play()
            self.playing = False
This is all part of a tutorial. Thought it was strange that what I want to do wasn't in the tutorial... guess I found out why.
Ok... well, I got it working with the other method you suggested, but it just won't do. The scrolling and generation of platforms are directly tied, so when a collision is detected, it stutters noticeably. If it has to do it more that once in a row it looks and plays really poorly. Sometimes it gets stuck in a loop of doing it and the game stops completely. So I think performance issues are why this part is missing from the tutorial.

Thank you for your help in any case.
Show code and point to tutorial.
1. Performance issue can be from while len(self.platforms) < 7. This should be cap to how many times in one loop.

2. If platform create or load image. This is definitely using alot of memory and slowing program down.
This is why you always pass image to sprite. This references same image.

3. Since it in a loop. I would not add sprite into group until after collision.

After rereading your comments. It sound like to much is going on in this while loop. Definitely would need see code. This sound like it needs to be divide up more.
Example how I would handle it.
import os
import pygame
import random
from pygame.sprite import Sprite, Group, spritecollide

class Engine:
    def __init__(self, title, width, height, center=True, flags=0):
        if center:
            os.environ['SDL_VIDEO_CENTERED'] = '1'

        pygame.display.set_caption(title)
        self.surface = pygame.display.set_mode((width, height), flags)
        self.rect = self.surface.get_rect()
        self.clock = pygame.time.Clock()
        self.running = False
        self.delta = 0
        self.fps = 60

class Images:
    def __init__(self):
        self.platforms = []
        self.create_platforms()

    def create_platforms(self):
        colors = pygame.Color("orange"), pygame.Color("firebrick")
        for enum, width in enumerate(range(50, 101, 5)):
            platform = pygame.Surface((width, 10))
            platform.fill(colors[enum % 2])
            self.platforms.append(platform)

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

    def update(self, delta, engine):
        self.center.y += self.speed * delta
        self.rect.center = self.center

        if self.rect.y > engine.rect.bottom:
            self.kill()

def main():
    engine = Engine("Moving Platforms", 120, 400)
    platforms = Group()
    images = Images()
    countdown = 1000
    interval = 1000

    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

        countdown -= engine.delta
        if countdown < 0 and len(platforms) < 7:
            countdown = interval
            image = random.choice(images.platforms)
            width = image.get_rect().width
            position = random.randrange(0, engine.rect.width - width), random.randrange(-60, -40)
            platform = Platform(image, position)
            if len(spritecollide(platform, platforms, False)) == 0:
                platforms.add(platform)

        platforms.update(engine.delta, engine)

        engine.surface.fill(pygame.Color("black"))
        platforms.draw(engine.surface)
        pygame.display.flip()
        engine.delta = engine.clock.tick(engine.fps)

main()
It's this tutorial: https://www.youtube.com/watch?v=uWvb3QzA...WldfCLu1pq

In this tutorial, the sprite sheet is loaded then the pieces for each image are defined in the sprite. Would it help if I defined the exact image in the load section and referred to it in the sprite section?

Here is my sprite.
This is the sprite. The "Decor" is stuff like grass and rocks that decorate the platforms of each type. When I had the collision part (I, deleted it) it was above where it calls the decor, so it shouldn't have been running if there is a collision. Looking now I should have put it before it called "POW" too, but I don't know how much help that would have been. I had a print statement showing "collision". It would show an empty list each time a platform was made, then show the sprite if a collision took place. Or if it happened 2 or three 3 in a row, I'd see it and the resulting hesitation.
class Platform(pg.sprite.Sprite):
    def __init__(self, game, x, y, kind):
        self._layer = PLATFORM_LAYER
        self.groups = game.all_sprites, game.platforms
        pg.sprite.Sprite.__init__(self, self.groups)
        self.game = game
        self.kind = kind
        if self.kind == 1: # grass
            images = [self.game.spritesheet.get_image(0, 288, 360, 94),
                      self.game.spritesheet.get_image(213, 1662, 201, 100)]
        if self.kind == 2: #wood
            images = [self.game.spritesheet.get_image(0, 960, 380, 94),
                      self.game.spritesheet.get_image(218, 1558, 200, 100)]
        if self.kind == 3: #stone
            images = [self.game.spritesheet.get_image(0, 96, 380, 94),
                      self.game.spritesheet.get_image(382, 408, 200, 100)]
        if self.kind == 4: #sand
            images = [self.game.spritesheet.get_image(0, 672, 380, 94), 
                      self.game.spritesheet.get_image(208, 1879, 201, 100),]
            
        self.image = choice(images)
        self.image.set_colorkey(BLACK)
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        if randrange(100) < POW_SPAWN_PCT:
            Pow(self.game, self)
        num = randrange(5) 
        for n in range(num):
        
            n = Decor(self.game, self, self.kind)
I personally don't care for his tutorials.
1. Uses way to many globals. You should get use to not using globals.
2. He always predefine colors. Pygame has over 400 builtin colors.

Example how you can handle spritesheet. This will let you grab images by name not rect.
import xml.etree.ElementTree as ET
from pygame.image import load as image_load
from pygame.transform import scale
from pygame import Rect

class JumpySpriteSheet:
    def __init__(self, filename, xmlfile):
        self.spritesheet = image_load(filename).convert_alpha()
        self.scale_sheet()
        self.image_dict = {}
        self.read_xml(xmlfile)

    def scale_sheet(self):
        size = self.spritesheet.get_size()
        size = size[0] // 2, size[1] // 2
        self.spritesheet = scale(self.spritesheet, size)

    def read_xml(self, xmlfile):
        tree = ET.parse(xmlfile)
        root = tree.getroot()

        for child in root:
            rect = Rect([int(child.get(attrib)) // 2 for attrib in ['x', 'y', 'width', 'height']])
            self.image_dict[child.get('name')[:-4]] = rect

    # reference
    def get_image(self, name):
        return self.spritesheet.subsurface(self.image_dict[name])
I get back to you with more later.
By using globals, are you referring to the "settings.py".

I like how you do this. I found if annoying to have to type in all the sprite info. But all the numbers in this XML are in reverse order for some reason. I just assumed it was designed for a different program.

I've done all his pygame tutorials anyway. lol

What pygame video tutorials do you approve of?
Don't know any video tutorials. Just what I see people use.

I learn form this forum and docs. Using internet looking up specific question.
How I learn form this forum. Was answer questions but not post it. See what other people answer where.
  1. I learn to be explicit. I normally avoid abbreviation. Just make program harder to read.
  2. Pygame Rect are very powerful. They work great for placement and layouts.
  3. Pygame Vector2 will help reduce math and make it bit simpler once learn.
  4. Learn to pass images. This reference them.
  5. Learn to refactor code. Having one class handle multiply things. It just get messy.
  6. Learn to build a boiler plate. Just lets me code quickly.

My Boilerplate.
import pygame
import os

class Scene:
    # SceneManager Interface
    def __init__(self, manager):
        self.manager = manager

    def scene_draw(self, surface):
        self.on_draw(surface)

    def scene_drop(self):
        self.on_drop()

    def scene_event(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.on_quit()
            else:
                self.on_event(event)

    def scene_focus(self, *args, **kwargs):
        self.on_focus(*args, **kwargs)

    def scene_new(self,*args, **kwargs):
        self.on_new(*args, **kwargs)

    def scene_update(self, delta):
        self.on_update(delta)

    def flip(self, scene, *args, **kwargs):
        self.manager.flip(scene, *args, **kwargs)

    def flip_new(self, scene, *args, **kwargs):
        self.manager.flip_new(scene, *args, **kwargs)

    def on_quit(self):
        self.manager.quit()

    # Scene Interface
    def on_draw(self, surface): pass
    def on_drop(self): pass
    def on_event(self, event): pass
    def on_focus(self): pass
    def on_new(self): pass
    def on_update(self, delta): pass

class Manager:
    def __init__(self, title, width, height, center=True, flags=0):
        if center is True:
            os.environ['SDL_VIDEO_CENTERED'] = '1'
        elif center:
            os.environ['SDL_VIDEO_WINDOW_POS'] = '{0}, {1}'.format(*center)

        # Basic pygame setup
        pygame.display.set_caption(title)
        self.surface = pygame.display.set_mode((width, height), flags)
        self.rect = self.surface.get_rect()
        self.clock = pygame.time.Clock()
        self.running = False
        self.delta = 0
        self.fps = 60

        # Scene handling
        self._scene = Scene(self)
        self.scenes = {}

        self.extension = []

    def add_scene(self, scene, name=None):
        if name is None:
            name = scene.__class__.__name__

        self.scenes[name] = scene

    def _flip(self, scene):
        self._scene.scene_drop()
        if isinstance(scene, Scene):
            self._scene = scene
        else:
            # String: Load live scene
            self._scene = self.scenes[scene]

    def flip(self, scene, *args, **kwargs):
        self._flip(scene)
        self._scene.scene_focus(*args, **kwargs)

    def flip_new(self, scene, *args, **kwargs):
        self._flip(scene)
        self._scene.scene_new(*args, **kwargs)

    def mainloop(self):
        self.running = True
        while self.running:
            self._scene.scene_event()
            self._scene.scene_update(self.delta)
            self._scene.scene_draw(self.surface)
            for extension in self.extension:
                extension(self)

            pygame.display.flip()
            self.delta = self.clock.tick(self.fps)

    def quit(self):
        self.running = False
Example. Didn't use one global variable.
import pygame
from scene import Scene, Manager
from pygame.sprite import Sprite, Group

class GameSprite(Sprite):
    def __init__(self, image, position, anchor="topleft"):
        Sprite.__init__(self)
        self.image = image
        self.rect = image.get_rect(**{anchor: position})
        self.updates = []

    def draw(self, surface):
        surface.blit(self.image, self.rect)

    def update(self, keys_pressed, delta):
        for update in self.updates:
            update(keys_pressed, delta)

class LifePool:
    def __init__(self, value):
        self.max_value = value
        self.value = value

    def __add__(self, value):
        self.value = min(self.value + value, self.max_value)

    def __sub__(self, value):
        self.value = max(self.value - value, 0)

    def get_percent(self):
        return self.value / self.max_value

class CharacterMovement:
    def __init__(self, sprite, speed):
        self.sprite = sprite
        self.speed = speed * 0.01
        self.center = pygame.Vector2(sprite.rect.center)

        self._vector = pygame.Vector2(0, 0)

    def move_up(self):
        self._vector.y -= 1

    def move_down(self):
        self._vector.y += 1

    def move_left(self):
        self._vector.x -= 1

    def move_right(self):
        self._vector.x += 1

    def update(self, delta):
        if self._vector != pygame.Vector2(0, 0):
            self._vector.normalize_ip()
            self.center += self._vector * self.speed * delta

            self.sprite.rect.center = self.center
            self._vector = pygame.Vector2(0, 0)

class CharacterMovementKeys:
    def __init__(self, movement, up=pygame.K_w, down=pygame.K_s, left=pygame.K_a, right=pygame.K_d):
        self.movement = movement
        self.up = self.list_form(up)
        self.down = self.list_form(down)
        self.left = self.list_form(left)
        self.right = self.list_form(right)

    def list_form(self, key):
        if isinstance(key, list):
            return key
        elif isinstance(key, tuple):
            return list(key)
        return [key]

    def move(self, key_pressed, delta):
        if any([key_pressed[key] for key in self.up]):
            self.movement.move_up()

        if any([key_pressed[key] for key in self.down]):
            self.movement.move_down()

        if any([key_pressed[key] for key in self.left]):
            self.movement.move_left()

        if any([key_pressed[key] for key in self.right]):
            self.movement.move_right()

        self.movement.update(delta)

class Character:
    def __init__(self, image, position, anchor="topleft"):
        self.sprite = GameSprite(image, position, anchor)
        self.life = LifePool(20)
        self.create_life_sprite()
        self.movement = CharacterMovement(self.sprite, 8)
        self.keys = CharacterMovementKeys(self.movement)
        self.sprite.updates.append(self.keys.move)
        self.sprite.updates.append(self.update_life_position)

    def create_hit_points_image(self):
        size = self.sprite.rect.width, 5
        surface = pygame.Surface(size)
        surface.fill(pygame.Color('firebrick'))
        percent = self.life.get_percent()
        width = int(size[0] * percent)
        rect = (0, 0, width, size[1])
        pygame.draw.rect(surface, pygame.Color('lawngreen'), rect)
        return surface

    def create_life_sprite(self):
        image = self.create_hit_points_image()
        position = self.sprite.rect.midtop
        position = position[0], position[1] - image.get_height() - 2
        self.life_sprite = GameSprite(image, position, "midtop")

    def update_life_position(self, keys_pressed, delta):
        position = self.sprite.rect.midtop
        position = position[0], position[1] - self.life_sprite.rect.height - 2
        self.life_sprite.rect.midtop = position

class MainScene(Scene):
    def __init__(self, manager):
        Scene.__init__(self, manager)
        image = self.create_character_image()
        self.character = Character(image, manager.rect.center, "center")

        self.sprites = Group(self.character.sprite)
        self.life_bar = Group(self.character.life_sprite)

        self.show_life_bar = True

    def create_character_image(self):
        surface = pygame.Surface((30, 30))
        surface.fill(pygame.Color("dodgerblue"))
        return surface

    def on_draw(self, surface):
        surface.fill(pygame.Color("black"))
        self.sprites.draw(surface)
        if self.show_life_bar:
            self.life_bar.draw(surface)

    def on_event(self, event):
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                self.show_life_bar = not self.show_life_bar

    def on_update(self, delta):
        keys_pressed = pygame.key.get_pressed()
        self.sprites.update(keys_pressed, delta)

def main():
    pygame.init()
    manager = Manager("Example", 800, 600)
    manager.flip(MainScene(manager))
    manager.mainloop()

if __name__ == "__main__":
    main()
Pages: 1 2