Posts: 419
Threads: 34
Joined: May 2019
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.
Posts: 544
Threads: 15
Joined: Oct 2016
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)
99 percent of computer problems exists between chair and keyboard.
Posts: 419
Threads: 34
Joined: May 2019
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.
Posts: 419
Threads: 34
Joined: May 2019
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.
Posts: 544
Threads: 15
Joined: Oct 2016
Jan-22-2020, 06:49 AM
(This post was last modified: Jan-22-2020, 07:04 AM by Windspar.)
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.
99 percent of computer problems exists between chair and keyboard.
Posts: 544
Threads: 15
Joined: Oct 2016
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()
99 percent of computer problems exists between chair and keyboard.
Posts: 419
Threads: 34
Joined: May 2019
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)
Posts: 544
Threads: 15
Joined: Oct 2016
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.
99 percent of computer problems exists between chair and keyboard.
Posts: 419
Threads: 34
Joined: May 2019
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?
Posts: 544
Threads: 15
Joined: Oct 2016
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.
- I learn to be explicit. I normally avoid abbreviation. Just make program harder to read.
- Pygame Rect are very powerful. They work great for placement and layouts.
- Pygame Vector2 will help reduce math and make it bit simpler once learn.
- Learn to pass images. This reference them.
- Learn to refactor code. Having one class handle multiply things. It just get messy.
- 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()
99 percent of computer problems exists between chair and keyboard.
|