[SOLVED] How to Use Sprite Manager and Creator Defined Displayables to Collide Certain Sprites

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Post Reply
Message
Author
User avatar
GemstoneSystem
Newbie
Posts: 11
Joined: Sun Jun 19, 2022 5:04 pm
Projects: Shards
itch: gemmysystem
Contact:

[SOLVED] How to Use Sprite Manager and Creator Defined Displayables to Collide Certain Sprites

#1 Post by GemstoneSystem » Sun Aug 07, 2022 9:06 pm

EDIT: My girlfriend helped me figure out what I needed to do to make multiple sprites without rendering them and positioning them individually so I'm good now!

So I'm making my fifth PyGame-to-Ren'Py port using Creator Defined Displayables but this is my first that will require using Sprite Manager to create the sprites and I'm a bit lost. I've read the documentation but I'm not entirely sure how to apply it to my particular situation. Basically, I need to use Sprite Manager to make a group of sprites containing four colors of monster (all the same size, general shape, and range of behaviors) that a different knight sprite (potentially also using Sprite Manager, if it's necessary for collision detection) can collide with, destroying them on impact. The catch is it only counts as a kill towards the monster if it's the right color and if not, you lose a life and are sent back to the starting area. This game is currently complete in PyGame but anyone who has ported something from PyGame to Ren'Py knows that it requires a LOT of adjustment to even work. The differences in the code between my previous four games in PyGame and then in Ren'Py are astronomical.

Here's the documentation about sprites and Sprite Manager: https://www.renpy.org/doc/html/sprites. ... te-classes

I don't know if my usual collision code will work and can be used with the Sprite Manager but I'll include it, just in case (px and py are the player's x and y, mx and my are the monster's x and y, and SPRITE_SIZE is the height AND width of both sprites because they're the same size as each other and the same size in both directions):

Code: Select all

def is_colliding():
    return (
        self.px <= self.mx + self.SPRITE_SIZE and
        self.px + self.SPRITE_SIZE >= self.mx and
        self.py <= self.my + self.SPRITE_SIZE and
        self.py + self.SPRITE_SIZE >= self.my
    )
And here's my completed game in PyGame for anyone who knows anything about that and can figure out what I need to do by looking at it:

Code: Select all

import pygame
import random


# Initialize pygame
pygame.init()

# Set display surface
WINDOW_WIDTH = 1200
WINDOW_HEIGHT = 700
display_surface = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption("Monster Wrangler")

# Set FPS and clock
FPS = 60
clock = pygame.time.Clock()


# Define classes
class Game():
    """A class to control gameplay"""
    def __init__(self, player, monster_group):
        """Initialize the game object"""
        # Set game values
        self.score = 0
        self.round_number = 0

        self.round_time = 0
        self.frame_count = 0

        self.player = player
        self.monster_group = monster_group

        # Set sounds and music
        self.next_level_sound = pygame.mixer.Sound("next_level.wav")

        # Set font
        self.font = pygame.font.Font("Abrushow.ttf", 24)

        # Set images
        blue_image = pygame.image.load("blue_monster.png")
        green_image = pygame.image.load("green_monster.png")
        purple_image = pygame.image.load("purple_monster.png")
        yellow_image = pygame.image.load("yellow_monster.png")
        # This list corresponds to the monster type attribute (0: Blue, 1: Green, 2: Purple, 3: Yellow)
        self.target_monster_images = [blue_image, green_image, purple_image, yellow_image]

        self.target_monster_type = random.randint(0, 3)
        self.target_monster_image = self.target_monster_images[self.target_monster_type]

        self.target_monster_rect = self.target_monster_image.get_rect()
        self.target_monster_rect.centerx = WINDOW_WIDTH//2
        self.target_monster_rect.top = 30

    def update(self):
        """Update our game object"""
        self.frame_count += 1
        if self.frame_count == FPS:
            self.round_time += 1
            self.frame_count = 0

        # Check for collisions
        self.check_collisions()

    def draw(self):
        """Draw the HUD and other to the display"""
        # Set colors
        WHITE = (255, 255, 255)
        BLUE = (20, 176, 235)
        GREEN = (87, 201, 47)
        PURPLE = (226, 73, 243)
        YELLOW = (243, 157, 20)

        # Add the monster colors to a list where the index of the color matches target_monster_images
        colors = [BLUE, GREEN, PURPLE, YELLOW]

        # Set text
        catch_text = self.font.render("Current Catch", True, WHITE)
        catch_rect = catch_text.get_rect()
        catch_rect.centerx = WINDOW_WIDTH//2
        catch_rect.top = 5

        score_text = self.font.render("Score: " + str(self.score), True, WHITE)
        score_rect = score_text.get_rect()
        score_rect.topleft = (5, 5)

        lives_text = self.font.render("Lives: " + str(self.player.lives), True, WHITE)
        lives_rect = lives_text.get_rect()
        lives_rect.topleft = (5, 35)

        round_text = self.font.render("Current Round: " + str(self.round_number), True, WHITE)
        round_rect = round_text.get_rect()
        round_rect.topleft = (5, 65)

        time_text = self.font.render("Round Time: " + str(self.round_time), True, WHITE)
        time_rect = time_text.get_rect()
        time_rect.topright = (WINDOW_WIDTH - 10, 5)

        warp_text = self.font.render("Warps: " + str(self.player.warps), True, WHITE)
        warp_rect = warp_text.get_rect()
        warp_rect.topright = (WINDOW_WIDTH - 10, 35)

        # Blit the HUD
        display_surface.blit(catch_text, catch_rect)
        display_surface.blit(score_text, score_rect)
        display_surface.blit(round_text, round_rect)
        display_surface.blit(lives_text, lives_rect)
        display_surface.blit(time_text, time_rect)
        display_surface.blit(warp_text, warp_rect)
        display_surface.blit(self.target_monster_image, self.target_monster_rect)

        pygame.draw.rect(display_surface, colors[self.target_monster_type], (WINDOW_WIDTH//2 - 32, 30, 64, 64), 2)
        pygame.draw.rect(display_surface, colors[self.target_monster_type], (0, 100, WINDOW_WIDTH, WINDOW_HEIGHT - 200), 4)

    def check_collisions(self):
        """Check for collisions between player and monsters"""
        # Check for collision between player and an individual monster
        # We must test the type of the monster to see if it matches the type of our target monster
        collided_monster = pygame.sprite.spritecollideany(self.player, self.monster_group)

        # We collided with a monster
        if collided_monster:
            # Caught the correct monster
            if collided_monster.type == self.target_monster_type:
                self.score += 100 * self.round_number
                # Remove caught monster
                collided_monster.remove(self.monster_group)
                if self.monster_group:
                    # There are more monsters to catch
                    self.player.catch_sound.play()
                    self.choose_new_target()
                else:
                    # The round is complete
                    self.player.reset()
                    self.start_new_round()
            # Caught the wrong monster
            else:
                self.player.die_sound.play()
                self.player.lives -= 1
                # Check for game over
                if self.player.lives <= 0:
                    self.pause_game("Final Score: " + str(self.score), "Press Enter to play again")
                    self.reset_game()
                self.player.reset()

    def start_new_round(self):
        """Populate board with new monsters"""
        # Provide a score bonus based on how quickly the round was finished
        self.score += int(10000 * self.round_number/(1 + self.round_time))

        # Reset round values
        self.round_time = 0
        self.frame_count = 0
        self.round_number += 1
        self.player.warps += 1

        # Remove any remaining monsters from a game reset
        for monster in self.monster_group:
            self.monster_group.remove(monster)

        # Add monsters to the monster group
        for i in range(self.round_number):
            self.monster_group.add(Monster(random.randint(0, WINDOW_WIDTH - 64), random.randint(100, WINDOW_HEIGHT - 164), self.target_monster_images[0], 0))
            self.monster_group.add(Monster(random.randint(0, WINDOW_WIDTH - 64), random.randint(100, WINDOW_HEIGHT - 164), self.target_monster_images[1], 1))
            self.monster_group.add(Monster(random.randint(0, WINDOW_WIDTH - 64), random.randint(100, WINDOW_HEIGHT - 164), self.target_monster_images[2], 2))
            self.monster_group.add(Monster(random.randint(0, WINDOW_WIDTH - 64), random.randint(100, WINDOW_HEIGHT - 164), self.target_monster_images[3], 3))

        # Choose a new target monster
        self.choose_new_target()

        self.next_level_sound.play()

    def choose_new_target(self):
        """Choose a new target monster for the player"""
        target_monster = random.choice(self.monster_group.sprites())
        self.target_monster_type = target_monster.type
        self.target_monster_image = target_monster.image

    def pause_game(self, main_text, sub_text):
        """Pause the game"""
        global running

        # Set color
        WHITE = (255, 255, 255)
        BLACK = (0, 0, 0)

        # Create the main pause text
        main_text = self.font.render(main_text, True, WHITE)
        main_rect = main_text.get_rect()
        main_rect.center = (WINDOW_WIDTH//2, WINDOW_HEIGHT//2)

        sub_text = self.font.render(sub_text, True, WHITE)
        sub_rect = sub_text.get_rect()
        sub_rect.center = (WINDOW_WIDTH//2, WINDOW_HEIGHT//2 + 64)

        # Display the pause text
        display_surface.fill(BLACK)
        display_surface.blit(main_text, main_rect)
        display_surface.blit(sub_text, sub_rect)
        pygame.display.update()

        # Pause the game
        is_paused = True
        while is_paused:
            for event in pygame.event.get():
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_RETURN:
                        is_paused = False
                if event.type == pygame.QUIT:
                    is_paused = False
                    running = False

    def reset_game(self):
        """Reset the game"""
        self.score = 0
        self.round_number = 0

        self.player.lives = 5
        self.player.warps = 2
        self.player.reset()

        self.start_new_round()


class Player(pygame.sprite.Sprite):
    """A player class that the user can control"""
    def __init__(self):
        """Initialize the player"""
        super().__init__()
        self.image = pygame.image.load("knight.png")
        self.rect = self.image.get_rect()
        self.rect.centerx = WINDOW_WIDTH//2
        self.rect.bottom = WINDOW_HEIGHT

        self.lives = 5
        self.warps = 2
        self.velocity = 8

        self.catch_sound = pygame.mixer.Sound("catch.wav")
        self.die_sound = pygame.mixer.Sound("die.wav")
        self.warp_sound = pygame.mixer.Sound("warp.wav")

    def update(self):
        """Update the player"""
        keys = pygame.key.get_pressed()

        # Move the player within the bounds of the screen
        if keys[pygame.K_LEFT] and self.rect.left > 0:
            self.rect.x -= self.velocity
        if keys[pygame.K_RIGHT] and self.rect.right < WINDOW_WIDTH:
            self.rect.x += self.velocity
        if keys[pygame.K_UP] and self.rect.top > 100:
            self.rect.y -= self.velocity
        if keys[pygame.K_DOWN] and self.rect.bottom < WINDOW_HEIGHT - 100:
            self.rect.y += self.velocity

    def warp(self):
        """Warp the player to the bottom 'safe zone'"""
        if self.warps > 0:
            self.warps -= 1
            self.warp_sound.play()
            self.rect.bottom = WINDOW_HEIGHT

    def reset(self):
        """Resets the player's position"""
        self.rect.centerx = WINDOW_WIDTH//2
        self.rect.bottom = WINDOW_HEIGHT


class Monster(pygame.sprite.Sprite):
    """A class to create enemy objects"""
    def __init__(self, x, y, image, monster_type):
        """Initialize the monster"""
        super().__init__()
        self.image = image
        self.rect = self.image.get_rect()
        self.rect.topleft = (x, y)

        # Monster type is an int (0: Blue, 1: Green, 2: Purple, 3: Yellow)
        self.type = monster_type

        # Set random motion
        self.dx = random.choice([-1, 1])
        self.dy = random.choice([-1, 1])
        self.velocity = random.randint(1, 5)

    def update(self):
        """Update the monster"""
        self.rect.x += self.dx * self.velocity
        self.rect.y += self.dy * self.velocity

        # Bounce the monster off the edges of the HUD
        if self.rect.left <= 0 or self.rect.right >= WINDOW_WIDTH:
            self.dx = -1 * self.dx
        if self.rect.top <= 100 or self.rect.bottom >= WINDOW_HEIGHT - 100:
            self.dy = -1 * self.dy


# Create a player group and Player object
my_player_group = pygame.sprite.Group()
my_player = Player()
my_player_group.add(my_player)

# Create a monster group
my_monster_group = pygame.sprite.Group()

running = True

# Create a Game object
my_game = Game(my_player, my_monster_group)
my_game.pause_game("Monster Wrangler", "Press Enter to begin")
my_game.start_new_round()

# The main game loop
while running:
    # Check to see if the user wants to quit
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

        # Player wants to warp
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                my_player.warp()

    # Fill the display
    display_surface.fill((0, 0, 0))

    # Update and draw sprite groups
    my_player_group.update()
    my_player_group.draw(display_surface)

    my_monster_group.update()
    my_monster_group.draw(display_surface)

    # Update and draw the game
    my_game.update()
    my_game.draw()

    # Update display and tick clock
    pygame.display.update()
    clock.tick(FPS)

# End the game
pygame.quit()
Last edited by GemstoneSystem on Mon Aug 08, 2022 2:18 pm, edited 1 time in total.

User avatar
GemstoneSystem
Newbie
Posts: 11
Joined: Sun Jun 19, 2022 5:04 pm
Projects: Shards
itch: gemmysystem
Contact:

Re: How to Use Sprite Manager and Creator Defined Displayables to Collide Certain Sprites

#2 Post by GemstoneSystem » Sun Aug 07, 2022 11:00 pm

As I've dug deeper into Sprite Manager, I've come to realize it has many limitations and I'm beginning to wonder if it's even possible to combine it with a Creator Defined Displayable. If not, does anyone have any suggestions for what else I might do for what I am trying to achieve? My previous games have only utilized one to two sprites at a time but this game can have exponentially more (as each round adds one of each monster for a total of four times the round number).

User avatar
Andredron
Miko-Class Veteran
Posts: 535
Joined: Thu Dec 28, 2017 2:37 pm
Completed: Kimi ga nozomu renpy-port(demo), Albatross Koukairoku(demo)
Projects: Sisters ~Natsu no Saigo no Hi~(renpy-port)
Location: Russia
Contact:

Re: [SOLVED] How to Use Sprite Manager and Creator Defined Displayables to Collide Certain Sprites

#3 Post by Andredron » Mon Aug 08, 2022 6:40 pm

Brother, thank you for the codes, and for sharing solutions. God grant you good health.
I'm writing a Renpy textbook (in Russian). https://disk.yandex.ru/i/httNEajU7iFWHA (all information is out of date) Update 22.06.18

Help me to register in QQ International

Honest Critique

Post Reply

Who is online

Users browsing this forum: Bing [Bot]