[SOLVED] Shooter Minigame Help - Seeking Refactoring/Organization Advice

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.
Message
Author
thexerox123
Regular
Posts: 134
Joined: Fri Jan 20, 2023 3:21 pm
itch: thexerox123
Contact:

[SOLVED] Shooter Minigame Help - Seeking Refactoring/Organization Advice

#1 Post by thexerox123 »

Edit: Made lots of alterations from this original post; see most recent post for updated code!

I'm trying to finally wrap my head around how to work with classes and event-based programming, but I always seem to hit a wall when it comes to randomizing/controlling the randomization of displayables using for statements and lists.

Image

I was able to get this movement and velocity system working pretty well yesterday, and now am at the point where I want to add 3 parallax layers of randomized clouds moving past at different speeds, rather than just the one cloud recycling endlessly. And I know that I'll need to use similar methods to generate enemies.

Here's my code so far:

Code: Select all

init python:
    import random

    class SleighShooter(renpy.Displayable):
        def __init__(self):

            renpy.Displayable.__init__(self)

            self.key_up = False
            self.key_down = False
            self.key_left = False
            self.key_right = False
            self.accelerate = False

            self.SPEED = 15
            self.BUFFER_DISTANCE = 100
            self.CSPEED0 = 2
            self.CSPEED1 = 5 
            self.CSPEED2 = 10
            self.CSPEED3 = 15

            self.score = 0
            self.misses = 0
            self.parallax1 = 15
            self.pc1 = []
            self.parallax2 = 10
            self.pc2 = []
            self.parallax3 = 4
            self.pc3 = []
            self.velocity = 0
            self.gauge = 0
            self.health = 4

            # Some displayables we use.
            self.player = Image("Sleigh1.png")
            self.cloud = Image("images/Clouds/White/cloud_shape3_1.png")

            # Player position
            self.px = 20
            self.py = 500
            self.pymin = 10
            self.pymax = 1080 - 238
            self.pxmin = 0
            self.pxmax = 960 - 326

            # Cloud positions
            self.cx = 1920 + 128
            self.cy = random.randint(-50, 1130)

            # The time of the past render-frame.
            self.oldst = None

            self.lose = False

            return


        # Draws the screen
        def render(self, width, height, st, at):

            # The Render object we'll be drawing into.
            r = renpy.Render(width, height)

            # Figure out the time elapsed since the previous frame.
            if self.oldst is None:
                self.oldst = st

            dtime = st - self.oldst
            self.oldst = st

            # This draws the player.
            def player(px, py, pymin, pymax):

                # Render the player image.
                player = renpy.render(self.player, width, height, st, at)

                # renpy.render returns a Render object, which we can
                # blit to the Render we're making.
                r.blit(player, (int(self.px), int(self.py)))

            player(self.px, self.py, self.pymin, self.pymax)

            # This draws the clouds.
            def cloud(cx, cy):
                cloud = renpy.render(self.cloud, width, height, st, at)

                r.blit(cloud, (int(self.cx), int(self.cy)))

            #Parallax1
            if self.cx < -300:
                # Cloud is offscreen
                self.cx = width + 300
                self.cy = random.randint(128, height - 128)
            else:
                # Move the cloud
                self.cx -= self.CSPEED1 + self.velocity

            cloud(self.cx, self.cy)


            # Sleigh's travel boundaries
            if self.py < self.pymin:
                self.py = self.pymin               
            if self.py > self.pymax:
                self.py = self.pymax

            if self.px < self.pxmin:
                self.px = self.pxmin               
            if self.px > self.pxmax:
                self.px = self.pxmax

            # Translate keypress to action
            if self.key_up == True:
                self.py -= self.SPEED
            if self.key_down == True:
                self.py += self.SPEED
            if self.key_left == True:
                self.velocity -= 2
                self.px -= self.SPEED + self.gauge
            if self.key_right == True:
                self.velocity += 2
                self.px += self.SPEED + self.gauge
                self.accelerate = True


            if self.velocity >= 70:
                self.px += self.SPEED + 10
                if self.accelerate == False:
                    if self.px <= 700:
                        self.px = 700
            elif self.velocity >= 50:
                if self.accelerate == False:
                    if self.px <= 500:
                        self.px = 500
            elif self.velocity >= 30:
                if self.accelerate == False: 
                    if self.px <= 300:
                        self.px = 300
            elif self.velocity >= 10:
                if self.accelerate == False:
                    if self.px <= 100:
                        self.px = 100
            if self.velocity <= 1:
                self.velocity = 1
            if self.velocity >= 70:
                self.velocity = 70

            if self.velocity >= 70:
                self.gauge = 7
            elif self.velocity >= 60:
                self.gauge = 6
            elif self.velocity >= 50:
                self.gauge = 5
            elif self.velocity >= 40:
                self.gauge = 4
            elif self.velocity >= 30:
                self.gauge = 3
            elif self.velocity >= 20:
                self.gauge = 2
            elif self.velocity >= 10:
                self.gauge = 1
            elif self.velocity == 1:
                self.gauge = 0

            # Check for a loss.
            if self.player_lives == 0:
                self.lose = True

                renpy.timeout(0)

            # Ask that we be re-rendered ASAP, so we can show the next
            # frame.
            renpy.redraw(self, 0)

            # Return the Render object.
            return r

        # Handles events.
        def event(self, ev, x, y, st):

            import pygame

            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_LEFT:
                self.key_left = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_LEFT:
                self.key_left = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_RIGHT:
                self.key_right = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_RIGHT:
                self.key_right = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_UP:
                self.key_up = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_UP:
                self.key_up = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_DOWN:
                self.key_down = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_DOWN:
                self.key_down = False



                # Ensure the screen updates.
            renpy.restart_interaction()


            # If the player loses, return it.
            if self.lose:
                return self.lose
            else:
                raise renpy.IgnoreEvent()



default sleighgame = SleighShooter()

screen sleigh_ride():

    add Image("bg Sky.png")

    add sleighgame

    if sleighgame.health == 1:
        vbox:
            add Image("images/SleighShield4.png")
            xpos 30 ypos 10
    elif sleighgame.health == 2:
        vbox:
            add Image("images/SleighShield3.png")
            xpos 30 ypos 10
    elif sleighgame.health == 3:
        vbox:
            add Image("images/SleighShield2.png")
            xpos 30 ypos 10
    elif sleighgame.health == 4:
        vbox:
            add Image("images/SleighShield1.png")
            xpos 30 ypos 10

    if sleighgame.gauge == 0:
        vbox:
            add Image("images/Gauge/gauge0.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 1:
        vbox:
            add Image("images/Gauge/gauge1.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 2:
        vbox:
            add Image("images/Gauge/gauge2.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 3:
        vbox:
            add Image("images/Gauge/gauge3.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 4:
        vbox:
            add Image("images/Gauge/gauge4.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 5:
        vbox:
            add Image("images/Gauge/gauge5.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 6:
        vbox:
            add Image("images/Gauge/gauge6.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 7:
        vbox:
            add Image("images/Gauge/gauge7.png")
            xpos 20 ypos 120
I know that the way I have the cloud positioning determined currently only allows for one location tuple at a time that's shared by the instances, so that's not going to work. I've started to try to set up variables for the lists of the background, midground, and foreground clouds, but things get muddy for me from there.

(I also know that I'll probably end up wanting to split the clouds out into their own class, but I'm learning how all of this works as I go by way of experimentation, so I started off by just messing around in the main class.)

If anybody has advice on my next step, it would be greatly appreciated! :) I'm able to put more and more pieces together myself when I look to examples, but I'm still just not quite familiar enough with the logic and syntax needed to make it work, yet.

One other thing: I also plan on adding a bit of transient rotation on the sleigh when it accelerates and decelerates... would I need to use a Transform() object? Or pygame.transform.rotate? I'm sure I'll be able to figure it out, but if someone happens to have a good way to do that at-hand, that would also be hugely appreciated! :)
Last edited by thexerox123 on Thu Nov 02, 2023 6:50 pm, edited 6 times in total.

User avatar
Alex
Lemma-Class Veteran
Posts: 3097
Joined: Fri Dec 11, 2009 5:25 pm
Contact:

Re: Shooter Minigame Help - Basic Randomization

#2 Post by Alex »

thexerox123 wrote: Sat Aug 05, 2023 2:47 pm ...
For now you draw a player and a cloud over it (on a foreground). So try to add another cloud before player in render (at the background) and give it a different speed.

thexerox123
Regular
Posts: 134
Joined: Fri Jan 20, 2023 3:21 pm
itch: thexerox123
Contact:

Re: Shooter Minigame Help - Basic Randomization

#3 Post by thexerox123 »

Alex wrote: Sat Aug 05, 2023 4:02 pm
thexerox123 wrote: Sat Aug 05, 2023 2:47 pm ...
For now you draw a player and a cloud over it (on a foreground). So try to add another cloud before player in render (at the background) and give it a different speed.
That step I get, if I add a new set of variables for its positioning, but I have 15 different cloud images that I'd like to use for variation, and want the parallax categorized by the size of those clouds, and then for all of them to be effected by the velocity variable based on which parallax layer they're on.

So it's more the automation of creating the different cloud instances by way of a for loop and lists that I'm trying to understand.

Proof-of-concept image that shows more of the cloud variations:

Image

Though I ended up going after the rotation code first... made this change:

Code: Select all

        def render(self, width, height, st, at):
            if self.rotenote:
                r = renpy.render(
                Transform(self.player, rotate=(self.rotation)),
                self.rotation, st, at)

            # The Render object we'll be drawing into.
            else:
                r = renpy.Render(width, height)
But now need to puzzle through how to setup the variables to have it rotate just a bit in response to acceleration and deceleration and then return to 0.

thexerox123
Regular
Posts: 134
Joined: Fri Jan 20, 2023 3:21 pm
itch: thexerox123
Contact:

Re: Shooter Minigame Help - Basic Randomization

#4 Post by thexerox123 »

Edit: Made an error.

Edit again: I've got the clouds split out into their own class now, at least:

Code: Select all

init python:
    import random

    class SleighShooter(renpy.Displayable):
        def __init__(self):

            renpy.Displayable.__init__(self)

            self.key_up = False
            self.key_down = False
            self.key_left = False
            self.key_right = False
            self.accelerate = False

            self.SPEED = 15
            self.BUFFER_DISTANCE = 100

            self.score = 0
            self.misses = 0

            self.velocity = 0
            self.gauge = 0
            self.health = 4

            # Some displayables we use.
            self.player = Image("Sleigh1.png")


            # Player position
            self.px = 20
            self.py = 500
            self.pymin = 10
            self.pymax = 1080 - 238
            self.pxmin = 0
            self.pxmax = 960 - 326

            self.oldst = None

            self.lose = False

            self.rotenote = False
            self.rotation = 0

            return


        # Draws the screen
        def render(self, width, height, st, at):
            if self.rotenote:
                r = renpy.render(
                Transform(self.player, rotate=(self.rotation)),
                self.rotation, st, at)

            # The Render object we'll be drawing into.
            else:
                r = renpy.Render(width, height)

            # Figure out the time elapsed since the previous frame.
            if self.oldst is None:
                self.oldst = st

            dtime = st - self.oldst
            self.oldst = st

            # This draws the player.
            def player(px, py, pymin, pymax):

                # Render the player image.
                player = renpy.render(self.player, width, height, st, at)

                # renpy.render returns a Render object, which we can
                # blit to the Render we're making.
                r.blit(player, (int(self.px), int(self.py)))

            player(self.px, self.py, self.pymin, self.pymax)


            # Sleigh's travel boundaries
            if self.py < self.pymin:
                self.py = self.pymin               
            if self.py > self.pymax:
                self.py = self.pymax

            if self.px < self.pxmin:
                self.px = self.pxmin               
            if self.px > self.pxmax:
                self.px = self.pxmax

            # Translate keypress to action
            if self.key_up == True:
                self.py -= self.SPEED
            if self.key_down == True:
                self.py += self.SPEED
            if self.key_left == True:
                self.velocity -= 2
                self.px -= self.SPEED + self.gauge
            if self.key_right == True:
                self.velocity += 2
                self.px += self.SPEED + self.gauge
                self.accelerate = True


            if self.velocity >= 70:
                self.px += self.SPEED + 10
                if self.accelerate == False:
                    if self.px <= 700:
                        self.px = 700
            elif self.velocity >= 50:
                if self.accelerate == False:
                    if self.px <= 500:
                        self.px = 500
            elif self.velocity >= 30:
                if self.accelerate == False: 
                    if self.px <= 300:
                        self.px = 300
            elif self.velocity >= 10:
                if self.accelerate == False:
                    if self.px <= 100:
                        self.px = 100
            if self.velocity <= 1:
                self.velocity = 1
            if self.velocity >= 70:
                self.velocity = 70

            if self.velocity >= 70:
                self.gauge = 7
            elif self.velocity >= 60:
                self.gauge = 6
            elif self.velocity >= 50:
                self.gauge = 5
            elif self.velocity >= 40:
                self.gauge = 4
            elif self.velocity >= 30:
                self.gauge = 3
            elif self.velocity >= 20:
                self.gauge = 2
            elif self.velocity >= 10:
                self.gauge = 1
            elif self.velocity == 1:
                self.gauge = 0

            # Check for a loss.
            if self.player_lives == 0:
                self.lose = True

                renpy.timeout(0)

            # Ask that we be re-rendered ASAP, so we can show the next
            # frame.
            renpy.redraw(self, 0)

            # Return the Render object.
            return r

        # Handles events.
        def event(self, ev, x, y, st):

            import pygame

            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_LEFT:
                self.key_left = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_LEFT:
                self.key_left = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_RIGHT:
                self.key_right = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_RIGHT:
                self.key_right = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_UP:
                self.key_up = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_UP:
                self.key_up = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_DOWN:
                self.key_down = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_DOWN:
                self.key_down = False



                # Ensure the screen updates.
            renpy.restart_interaction()


            # If the player loses, return it.
            if self.lose:
                return self.lose
            else:
                raise renpy.IgnoreEvent()

    class CloudGenerator(renpy.Displayable):
        def __init__(self):

            renpy.Displayable.__init__(self)

            self.CSPEED0 = 2
            self.CSPEED1 = 5 
            self.CSPEED2 = 10
            self.CSPEED3 = 15

            self.parallax1 = 15
            self.parallax2 = 10
            self.pc2 = ["images/Clouds/White/cloud_shape3_1.png", "images/Clouds/White/cloud_shape3_2.png", "images/Clouds/White/cloud_shape3_3.png", "images/Clouds/White/cloud_shape3_4.png", "images/Clouds/White/cloud_shape2_1.png", "images/Clouds/White/cloud_shape2_2.png", "images/Clouds/White/cloud_shape2_3.png", "images/Clouds/White/cloud_shape2_4.png"]
            self.parallax3 = 4
            self.pc3 = []

            # Cloud positions
            self.cx = 1920 + 128
            self.cy = random.randint(-50, 1080 + 50)   

            self.cloud = Image("images/Clouds/White/cloud_shape3_1.png")


            # The time of the past render-frame.
            self.oldst = None

            return

        def render(self, width, height, st, at):
            # The Render object we'll be drawing into.
            r = renpy.Render(width, height)


            # Figure out the time elapsed since the previous frame.
            if self.oldst is None:
                self.oldst = st

            dtime = st - self.oldst
            self.oldst = st

            # This draws the clouds.
            def cloud(cx, cy):
                cloud = renpy.render(self.cloud, width, height, st, at)

                r.blit(cloud, (int(self.cx), int(self.cy)))


            #Parallax1
            if self.cx < -300:
                # Cloud is offscreen
                self.cx = width + 300
                self.cy = random.randint(128, height - 128)
            else:
                # Move the cloud
                self.cx -= self.CSPEED1 + sleighgame.velocity

            # def generate_clouds(cx, cy, parallax1):
            #     # Check if necessary to generate new clouds
            #     if self.parallax1 >= 0:
            #         self.pc1 = renpy.random.choices(["images/Clouds/White/cloud_shape2_5.png", "images/Clouds/White/cloud_shape3_5.png", "images/Clouds/White/cloud_shape4_5.png"])
            #         self.cloud = pc1
            #         cloud(self.cx, self.cy)
            #         self.parallax1 -= 1
            #         return

            cloud(self.cx, self.cy)
                
            # Ask that we be re-rendered ASAP, so we can show the next
            # frame.
            renpy.redraw(self, 0)

            return r





default sleighgame = SleighShooter()
default cloudcover = CloudGenerator()

screen sleigh_ride():

    add Image("bg Sky.png")

    add sleighgame

    add cloudcover

    if sleighgame.health == 1:
        vbox:
            add Image("images/SleighShield4.png")
            xpos 30 ypos 10
    elif sleighgame.health == 2:
        vbox:
            add Image("images/SleighShield3.png")
            xpos 30 ypos 10
    elif sleighgame.health == 3:
        vbox:
            add Image("images/SleighShield2.png")
            xpos 30 ypos 10
    elif sleighgame.health == 4:
        vbox:
            add Image("images/SleighShield1.png")
            xpos 30 ypos 10

    if sleighgame.gauge == 0:
        vbox:
            add Image("images/Gauge/gauge0.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 1:
        vbox:
            add Image("images/Gauge/gauge1.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 2:
        vbox:
            add Image("images/Gauge/gauge2.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 3:
        vbox:
            add Image("images/Gauge/gauge3.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 4:
        vbox:
            add Image("images/Gauge/gauge4.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 5:
        vbox:
            add Image("images/Gauge/gauge5.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 6:
        vbox:
            add Image("images/Gauge/gauge6.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 7:
        vbox:
            add Image("images/Gauge/gauge7.png")
            xpos 20 ypos 120
I thought I'd also successfully split the player out into its own class, but realized I'd been saving to a backup rather than the active file and my attempt was not successful, after all. lol :\

Tried to generate multiple instances of clouds with an if conditional, but they wouldn't show up.

I think I need to just bite the bullet and figure out how to iterate with a for loop. And also learn more about class vs instance variables.

But also not sure if I'm using renpy.random.choices correctly. Just realized, I'm not trying to add it to a list, so it should be renpy.random.choice?
Doubt that's my main issue, though.

thexerox123
Regular
Posts: 134
Joined: Fri Jan 20, 2023 3:21 pm
itch: thexerox123
Contact:

Re: Shooter Minigame Help - Basic Randomization

#5 Post by thexerox123 »

Well, made some decent progress today!

Image

Code: Select all

init python:
    import random

    class CloudGenerator(renpy.Displayable):
        def __init__(self, small_count, medium_count, large_count, outer_count, over_count):
            renpy.Displayable.__init__(self)
  
            self.small_count = small_count
            self.medium_count = medium_count
            self.large_count = large_count
            self.outer_count = outer_count
            self.over_count = over_count

            self.small_y_options = range(128, 1080 - 128)  # Adjust this range as needed
            self.medium_y_options = range(128, 1080 - 128)  # Adjust this range as needed
            self.large_y_options = range(100, 900) 
            self.outer_y_options = [-50, 0, 50, 1030, 1000, 970]
            self.over_y_options = range(-50, 1130)

            self.clouds = []  # List to hold cloud instances
            self.cloud_size_images = {
                "small": [
                    "images/Clouds/White/cloud_shape3_5.png",
                    "images/Clouds/White/cloud_shape4_5.png",
                    "images/Clouds/White/cloud_shape2_5.png",
                    "images/Clouds/White/cloud_shape3_4.png",
                    "images/Clouds/White/cloud_shape3_3.png",
                    "images/Clouds/White/cloud_shape3_2.png",
                    "images/Clouds/White/cloud_shape3_1.png",
                    "images/Clouds/White/cloud_shape4_4.png",
                    "images/Clouds/White/cloud_shape4_3.png",
                    "images/Clouds/White/cloud_shape4_2.png",
                    "images/Clouds/White/cloud_shape4_1.png",
                    "images/Clouds/White/cloud_shape2_4.png",
                    "images/Clouds/White/cloud_shape2_3.png",
                    "images/Clouds/White/cloud_shape2_2.png",
                    "images/Clouds/White/cloud_shape2_1.png",
                    # Add more small cloud image paths here
                ],
                "medium": [
                    "images/Clouds/White/cloud_shape3_4.png",
                    "images/Clouds/White/cloud_shape3_3.png",
                    "images/Clouds/White/cloud_shape3_2.png",
                    "images/Clouds/White/cloud_shape3_1.png",
                    "images/Clouds/White/cloud_shape4_4.png",
                    "images/Clouds/White/cloud_shape4_3.png",
                    "images/Clouds/White/cloud_shape4_2.png",
                    "images/Clouds/White/cloud_shape4_1.png",
                    "images/Clouds/White/cloud_shape2_4.png",
                    "images/Clouds/White/cloud_shape2_3.png",
                    "images/Clouds/White/cloud_shape2_2.png",
                    "images/Clouds/White/cloud_shape2_1.png",
                    # Add more medium cloud image paths here
                ],
                "large": [
                    "images/Clouds/White/cloud_large1.png",
                    "images/Clouds/White/cloud_large2.png",
                    # Add more large cloud image paths here
                ]
            }    

            cloud_counts = [self.small_count, self.medium_count, self.large_count, self.outer_count, self.over_count]


            s_buffer_distance = 300
            m_buffer_distance = 300 
            l_buffer_distance = 500

            self.oldst = None

            # Initialize cloud instances based on provided counts
            for i in range(small_count):
                cloud_size = "small"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.1, 0.5])    

                cloud = {
                    "count_type" : "small",
                    "x": 1920 + 128 + i * s_buffer_distance,
                    "y": random.randint(128, 1080 - 128),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)    
                cloud["delay"] += 1

            for i in range(medium_count):
                cloud_size = "medium"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.7, 0.8, 0.9])    

                cloud = {
                    "count_type" : "medium",
                    "x": 1920 + 128 + i * m_buffer_distance,
                    "y": random.randint(128, 1080 - 128),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud) 
                cloud["delay"] += 1 

            for i in range(large_count):
                cloud_size = "large"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.7, 0.8, 0.9])    

                cloud = {
                    "count_type" : "large",
                    "x": 1920 + 128 + i * l_buffer_distance,
                    "y": random.randint(100, 900),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            for i in range(outer_count):
                cloud_size = "large"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.5, 0.6])    

                cloud = {
                    "count_type" : "outer",
                    "x": 1920 + 128 + i * l_buffer_distance,
                    "y": random.choice([-50, 0, 50, 1030, 1000, 970]),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            for i in range(over_count):
                cloud_size = "small"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.6, 0.7])    

                cloud = {
                    "count_type" : "over",
                    "x": 1920 + 128 + i * s_buffer_distance,
                    "y": random.randint(-50, 1130),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            self.oldst = None

        def render(self, width, height, st, at):
            r = renpy.Render(width, height)    

            if self.oldst is None:
                self.oldst = st            

            dtime = st - self.oldst
            self.oldst = st           

            for cloud in self.clouds:
                # Check if the cloud's delay has elapsed
                if cloud["delay"] <= 0:
                    cloud["x"] -= cloud["speed"] + sleighgame.velocity
                    if cloud["x"] < -300:
                        cloud["x"] = width + 300
                        if cloud["count_type"] == "small":
                            cloud["y"] = random.choice(self.small_y_options)
                        elif cloud["count_type"] == "medium":
                            cloud["y"] = random.choice(self.medium_y_options)
                        elif cloud["count_type"] == "large":
                            cloud["y"] = random.choice(self.large_y_options)
                        elif cloud["count_type"] == "outer":
                            cloud["y"] = random.choice(self.outer_y_options)
                        elif cloud["count_type"] == "over":
                            cloud["y"] = random.choice(self.over_y_options)
    

                    cloud_image = renpy.render(cloud["image"], width, height, st, at)
                    r.blit(cloud_image, (int(cloud["x"]), int(cloud["y"])))
                else:
                    cloud["delay"] -= at + st  # Reduce the delay

            renpy.redraw(self, 0)
            return r



    class SleighShooter(renpy.Displayable):
        def __init__(self):

            renpy.Displayable.__init__(self)

            self.key_up = False
            self.key_down = False
            self.key_left = False
            self.key_right = False
            self.accelerate = False

            self.SPEED = 15

            self.score = 0
            self.misses = 0

            self.velocity = 0
            self.gauge = 0
            self.health = 4

            # Some displayables we use.
            self.player = Image("Sleigh1.png")


            # Player position
            self.px = 20
            self.py = 500
            self.pymin = 10
            self.pymax = 1080 - 238
            self.pxmin = 0
            self.pxmax = 960 - 326

            self.oldst = None

            self.lose = False

            self.rotenote = False
            self.rotation = 0

            return


        # Draws the screen
        def render(self, width, height, st, at):
            if self.rotenote:
                r = renpy.render(
                Transform(self.player, rotate=(self.rotation)),
                self.rotation, st, at)

            # The Render object we'll be drawing into.
            else:
                r = renpy.Render(width, height)

            # Figure out the time elapsed since the previous frame.
            if self.oldst is None:
                self.oldst = st

            dtime = st - self.oldst
            self.oldst = st

            # This draws the player.
            def player(px, py, pymin, pymax):

                # Render the player image.
                player = renpy.render(self.player, width, height, st, at)

                # renpy.render returns a Render object, which we can
                # blit to the Render we're making.
                r.blit(player, (int(self.px), int(self.py)))

            player(self.px, self.py, self.pymin, self.pymax)


            # Sleigh's travel boundaries
            if self.py < self.pymin:
                self.py = self.pymin               
            if self.py > self.pymax:
                self.py = self.pymax

            if self.px < self.pxmin:
                self.px = self.pxmin               
            if self.px > self.pxmax:
                self.px = self.pxmax

            # Translate keypress to action
            if self.key_up == True:
                self.py -= self.SPEED
            if self.key_down == True:
                self.py += self.SPEED
            if self.key_left == True:
                self.velocity -= 1.5
                self.px -= self.SPEED + self.gauge
            if self.key_right == True:
                self.velocity += 1.5
                self.px += self.SPEED + self.gauge
                self.accelerate = True


            if self.velocity >= 56:
                self.px += self.SPEED + 10
                if self.accelerate == False:
                    if self.px <= 700:
                        self.px = 700
            elif self.velocity >= 40:
                if self.accelerate == False:
                    if self.px <= 500:
                        self.px = 500
            elif self.velocity >= 24:
                if self.accelerate == False: 
                    if self.px <= 300:
                        self.px = 300
            elif self.velocity >= 8:
                if self.accelerate == False:
                    if self.px <= 100:
                        self.px = 100
            if self.velocity <= 1:
                self.velocity = 1
            if self.velocity >= 56:
                self.velocity = 56


            if self.velocity >= 56:
                self.gauge = 7
            elif self.velocity >= 48:
                self.gauge = 6
            elif self.velocity >= 40:
                self.gauge = 5
            elif self.velocity >= 32:
                self.gauge = 4
            elif self.velocity >= 24:
                self.gauge = 3
            elif self.velocity >= 16:
                self.gauge = 2
            elif self.velocity >= 8:
                self.gauge = 1
            elif self.velocity == 1:
                self.gauge = 0

            # Check for a loss.
            if self.player_lives == 0:
                self.lose = True

                renpy.timeout(0)

            # Ask that we be re-rendered ASAP, so we can show the next
            # frame.
            renpy.redraw(self, 0)

            # Return the Render object.
            return r

        # Handles events.
        def event(self, ev, x, y, st):

            import pygame

            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_LEFT:
                self.key_left = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_LEFT:
                self.key_left = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_RIGHT:
                self.key_right = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_RIGHT:
                self.key_right = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_UP:
                self.key_up = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_UP:
                self.key_up = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_DOWN:
                self.key_down = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_DOWN:
                self.key_down = False



                # Ensure the screen updates.
            renpy.restart_interaction()


            # If the player loses, return it.
            if self.lose:
                return self.lose
            else:
                raise renpy.IgnoreEvent()



default sleighgame = SleighShooter()
default cloudcover = CloudGenerator(small_count=20, medium_count=10, large_count=5, outer_count=0, over_count=0)
default cloudfore = CloudGenerator(small_count=0, medium_count=0, large_count=0, outer_count=8, over_count=10)

screen sleigh_ride():

    add Image("bg Sky.png")

    add cloudcover


    add cloudfore


    add sleighgame


    if sleighgame.health == 1:
        vbox:
            add Image("images/SleighShield4.png")
            xpos 30 ypos 10
    elif sleighgame.health == 2:
        vbox:
            add Image("images/SleighShield3.png")
            xpos 30 ypos 10
    elif sleighgame.health == 3:
        vbox:
            add Image("images/SleighShield2.png")
            xpos 30 ypos 10
    elif sleighgame.health == 4:
        vbox:
            add Image("images/SleighShield1.png")
            xpos 30 ypos 10

    if sleighgame.gauge == 0:
        vbox:
            add Image("images/Gauge/gauge0.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 1:
        vbox:
            add Image("images/Gauge/gauge1.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 2:
        vbox:
            add Image("images/Gauge/gauge2.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 3:
        vbox:
            add Image("images/Gauge/gauge3.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 4:
        vbox:
            add Image("images/Gauge/gauge4.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 5:
        vbox:
            add Image("images/Gauge/gauge5.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 6:
        vbox:
            add Image("images/Gauge/gauge6.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 7:
        vbox:
            add Image("images/Gauge/gauge7.png")
            xpos 20 ypos 120
Now, to figure out how to have an initial spread of clouds onscreen rather than having them all start offscreen.

thexerox123
Regular
Posts: 134
Joined: Fri Jan 20, 2023 3:21 pm
itch: thexerox123
Contact:

Re: Shooter Minigame Help - Basic Randomization

#6 Post by thexerox123 »

Been continuing to make pretty good progress!

Image

Got some basic projectile logic working (with no collision detection yet).

I started trying to figure out enemy logic, but got overwhelmed with that and put it aside. Instead, I made a class for rings that can be flown into and collected, and got the collision logic working with that.

Should be easier to get the enemies working now that I have that basic system working!

Code: Select all

style scoreboard:
    color "#8B0000"  # Set text color to red
    size 50          # Set font size to 50
    xpos 880          # Set x position
    ypos 20          # Set y position

init python:
    import random
    import pygame 

    weapon_variables = {
        "basic": {
            "damage": 10,
            "fire_rate": 0.5,
            "projectile_image": "images/Projectiles/Basic.png",
            "projectile_speed": 800
        },    

        "heavy": {
            "damage": 20,
            "fire_rate": 1.0,
            "projectile_image": "heavy_projectile.png",
            "projectile_speed": 300
        },
        # Define more weapon presets
    }

    basic_weapon = weapon_variables["basic"]

    # Define the paths to your ring images
    ring_image_paths = [
        "Ring1.png",
        "Ring2.png",
    ]    

    # Create a list to hold the Ring instances
    rings = []

    class CloudGenerator(renpy.Displayable):
        def __init__(self, small_count, medium_count, large_count, outer_count, over_count):
            renpy.Displayable.__init__(self)
  
            self.small_count = small_count
            self.medium_count = medium_count
            self.large_count = large_count
            self.outer_count = outer_count
            self.over_count = over_count

            self.small_y_options = range(128, 1080 - 128)  # Adjust this range as needed
            self.medium_y_options = range(128, 1080 - 128)  # Adjust this range as needed
            self.large_y_options = range(100, 900) 
            self.outer_y_options = [-50, 0, 50, 1030, 1000, 970]
            self.over_y_options = range(-50, 1130)

            self.clouds = []  # List to hold cloud instances
            self.cloud_size_images = {
                "small": [
                    "images/Clouds/White/cloud_shape3_5.png",
                    "images/Clouds/White/cloud_shape4_5.png",
                    "images/Clouds/White/cloud_shape2_5.png",
                    "images/Clouds/White/cloud_shape3_4.png",
                    "images/Clouds/White/cloud_shape3_3.png",
                    "images/Clouds/White/cloud_shape3_2.png",
                    "images/Clouds/White/cloud_shape3_1.png",
                    "images/Clouds/White/cloud_shape4_4.png",
                    "images/Clouds/White/cloud_shape4_3.png",
                    "images/Clouds/White/cloud_shape4_2.png",
                    "images/Clouds/White/cloud_shape4_1.png",
                    "images/Clouds/White/cloud_shape2_4.png",
                    "images/Clouds/White/cloud_shape2_3.png",
                    "images/Clouds/White/cloud_shape2_2.png",
                    "images/Clouds/White/cloud_shape2_1.png",
                    # Add more small cloud image paths here
                ],
                "medium": [
                    "images/Clouds/White/cloud_shape3_4.png",
                    "images/Clouds/White/cloud_shape3_3.png",
                    "images/Clouds/White/cloud_shape3_2.png",
                    "images/Clouds/White/cloud_shape3_1.png",
                    "images/Clouds/White/cloud_shape4_4.png",
                    "images/Clouds/White/cloud_shape4_3.png",
                    "images/Clouds/White/cloud_shape4_2.png",
                    "images/Clouds/White/cloud_shape4_1.png",
                    "images/Clouds/White/cloud_shape2_4.png",
                    "images/Clouds/White/cloud_shape2_3.png",
                    "images/Clouds/White/cloud_shape2_2.png",
                    "images/Clouds/White/cloud_shape2_1.png",
                    # Add more medium cloud image paths here
                ],
                "large": [
                    "images/Clouds/White/cloud_large1.png",
                    "images/Clouds/White/cloud_large2.png",
                    # Add more large cloud image paths here
                ]
            }    

            cloud_counts = [self.small_count, self.medium_count, self.large_count, self.outer_count, self.over_count]


            s_buffer_distance = 300
            m_buffer_distance = 300 
            l_buffer_distance = 500

            self.oldst = None

            # Initialize cloud instances based on provided counts
            for i in range(small_count):
                cloud_size = "small"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.1, 0.5])    

                cloud = {
                    "count_type" : "small",
                    "x": 1920 + 128 + i * s_buffer_distance,
                    "y": random.randint(128, 1080 - 128),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)    
                cloud["delay"] += 1

            for i in range(medium_count):
                cloud_size = "medium"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.7, 0.8, 0.9])    

                cloud = {
                    "count_type" : "medium",
                    "x": 1920 + 128 + i * m_buffer_distance,
                    "y": random.randint(128, 1080 - 128),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud) 
                cloud["delay"] += 1 

            for i in range(large_count):
                cloud_size = "large"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.7, 0.8, 0.9])    

                cloud = {
                    "count_type" : "large",
                    "x": 1920 + 128 + i * l_buffer_distance,
                    "y": random.randint(100, 900),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            for i in range(outer_count):
                cloud_size = "large"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.5, 0.6])    

                cloud = {
                    "count_type" : "outer",
                    "x": 1920 + 128 + i * l_buffer_distance,
                    "y": random.choice([-50, 0, 50, 1030, 1000, 970]),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            for i in range(over_count):
                cloud_size = "small"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.6, 0.7])    

                cloud = {
                    "count_type" : "over",
                    "x": 1920 + 128 + i * s_buffer_distance,
                    "y": random.randint(-50, 1130),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            self.oldst = None

        def render(self, width, height, st, at):
            r = renpy.Render(width, height)    

            if self.oldst is None:
                self.oldst = st            

            dtime = st - self.oldst
            self.oldst = st           

            for cloud in self.clouds:
                # Check if the cloud's delay has elapsed
                if cloud["delay"] <= 0:
                    cloud["x"] -= cloud["speed"] + sleighgame.velocity
                    if cloud["x"] < -300:
                        cloud["x"] = width + 300
                        if cloud["count_type"] == "small":
                            cloud["y"] = random.choice(self.small_y_options)
                        elif cloud["count_type"] == "medium":
                            cloud["y"] = random.choice(self.medium_y_options)
                        elif cloud["count_type"] == "large":
                            cloud["y"] = random.choice(self.large_y_options)
                        elif cloud["count_type"] == "outer":
                            cloud["y"] = random.choice(self.outer_y_options)
                        elif cloud["count_type"] == "over":
                            cloud["y"] = random.choice(self.over_y_options)
    

                    cloud_image = renpy.render(cloud["image"], width, height, st, at)
                    r.blit(cloud_image, (int(cloud["x"]), int(cloud["y"])))
                else:
                    cloud["delay"] -= at + st  # Reduce the delay

            renpy.redraw(self, 0)
            return r 

    class Ring(renpy.Displayable):
        def __init__(self, x, y, images, ringspeed, delay=0):
            renpy.Displayable.__init__(self)
            self.x = x
            self.y = y
            self.height = 322
            self.width = 144
            self.images = [Image(image) for image in images]
            self.frame_index = 0
            self.collected = False
            self.missed = False
            self.ringspeed = ringspeed 
            self.delay = delay  # Store the delay attribute
            # Set default animation speed (change this value as needed)
            self.animation_speed = 0.5

        def overlaps_with(self, other):
            left = self.x
            right = self.x + self.width
            top = self.y
            bottom = self.y + self.height    

            other_left = other.px
            other_right = other.px + other.width
            other_top = other.py
            other_bottom = other.py + other.height    

            horizontal_hit = (left <= other_right) and (right >= other_left)
            vertical_hit = (top <= other_bottom) and (bottom >= other_top)    

            if horizontal_hit and vertical_hit:
                return True    

            return False

        def render(self, width, height, st, at):
            if self.collected or self.missed or self.x < -300:
                return None        

            r = renpy.Render(width, height)
            current_frame_index = int(self.frame_index)
            ring_image = renpy.render(self.images[current_frame_index], width, height, st, at)
            r.blit(ring_image, (int(self.x), int(self.y)))        

            return r    

        def update(self, dtime):
            if self.delay > 0:
                self.delay -= dtime
            else:
                self.x -= self.ringspeed * dtime + sleighgame.velocity

            self.frame_index = int((self.frame_index + self.animation_speed * dtime) % len(self.images))


    class Projectile(renpy.Displayable):
        def __init__(self, x, y, image_path, speed, width, height, is_player=False):

            renpy.Displayable.__init__(self)
            self.x = x
            self.y = y
            self.image_path = image_path
            self.image = renpy.display.im.Image(image_path)  # Load the image
            self.speed = speed
            self.width = width
            self.height = height    
            self.is_player = is_player


            # Load projectile images (animation frames if multiple paths are provided)
            if isinstance(self.image_path, list):
                self.animation_frames = [Image(path) for path in image_paths]
                self.frame_index = 0  # Start with the first frame
            else:
                self.image = Image(image_path)          

        def render(self, width, height, st, at):
            if self.is_player:
                self.x += self.speed * dtime
            else:
                self.x -= self.speed * dtime   

            # Render the projectile (animation or single image)
            if hasattr(self, "animation_frames"):
                self.frame_index = (self.frame_index + 1) % len(self.animation_frames)
                current_frame = self.animation_frames[self.frame_index]
                projectile_image = renpy.render(current_frame, width, height, st, at)
            else:
                projectile_image = renpy.render(self.image, width, height, st, at)            

            r.blit(projectile_image, (int(self.x), int(self.y)))

    class SleighShooter(renpy.Displayable):
        def __init__(self):

            renpy.Displayable.__init__(self)

            self.key_up = False
            self.key_down = False
            self.key_left = False
            self.key_right = False
            self.accelerate = False

            self.SPEED = 15

            self.score = 0
            self.misses = 0

            self.velocity = 0
            self.gauge = 0
            self.health = 4

            # Some displayables we use.
            self.player = Image("Sleigh1.png")

            self.width = 326
            self.height = 238


            # Player position
            self.px = 20
            self.py = 500
            self.pymin = 10
            self.pymax = 1080 - 238
            self.pxmin = 0
            self.pxmax = 960 - 326

            self.oldst = None

            self.lose = False

            self.enemies = []

            self.projectiles = []

            self.active_enemy_projectiles = []  # List to store active enemy projectiles

            # Initialize rings with a delay before each one appears
            self.rings = initialize_rings(ring_count=10, ring_width=50, path_to_png1="Ring1.png", path_to_png2="Ring2.png", delay_range=(2.0, 4.0))

            self.rotenote = False
            self.rotation = 0
            self.player_last_fire_time = 0  # Initialize with 0
            return

        def overlaps_with(self, other):
            left = self.px
            right = self.px + self.width
            top = self.py
            bottom = self.py + self.height    

            other_left = other.x
            other_right = other.x + other.width
            other_top = other.y
            other_bottom = other.y + other.height    

            horizontal_hit = (left <= other_right) and (right >= other_left)
            vertical_hit = (top <= other_bottom) and (bottom >= other_top)    

            return horizontal_hit and vertical_hit

        # Draws the screen
        def render(self, width, height, st, at):
            if self.rotenote:
                r = renpy.render(
                Transform(self.player, rotate=(self.rotation)),
                self.rotation, st, at)

            # The Render object we'll be drawing into.
            else:
                r = renpy.Render(width, height)

            # Figure out the time elapsed since the previous frame.
            if self.oldst is None:
                self.oldst = st

            dtime = st - self.oldst
            self.oldst = st

            # This draws the player.
            def player(px, py, width, height, pymin, pymax):

                # Render the player image.
                player = renpy.render(self.player, width, height, st, at)

                # renpy.render returns a Render object, which we can
                # blit to the Render we're making.
                r.blit(player, (int(self.px), int(self.py)))

            player(self.px, self.py, width, height, self.pymin, self.pymax)


            # Sleigh's travel boundaries
            if self.py < self.pymin:
                self.py = self.pymin               
            if self.py > self.pymax:
                self.py = self.pymax

            if self.px < self.pxmin:
                self.px = self.pxmin               
            if self.px > self.pxmax:
                self.px = self.pxmax

            # Translate keypress to action
            if self.key_up == True:
                self.py -= self.SPEED
            if self.key_down == True:
                self.py += self.SPEED
            if self.key_left == True:
                self.velocity -= 1.5
                self.px -= self.SPEED + self.gauge
            if self.key_right == True:
                self.velocity += 1.5
                self.px += self.SPEED + self.gauge
                self.accelerate = True


            if self.velocity >= 56:
                self.px += self.SPEED + 10
                if self.accelerate == False:
                    if self.px <= 700:
                        self.px = 700
            elif self.velocity >= 40:
                if self.accelerate == False:
                    if self.px <= 500:
                        self.px = 500
            elif self.velocity >= 24:
                if self.accelerate == False: 
                    if self.px <= 300:
                        self.px = 300
            elif self.velocity >= 8:
                if self.accelerate == False:
                    if self.px <= 100:
                        self.px = 100
            if self.velocity <= 1:
                self.velocity = 1
            if self.velocity >= 56:
                self.velocity = 56


            # Update and render projectiles
            for projectile in self.projectiles:
                projectile.x += projectile.speed * dtime
                projectile_image = renpy.render(projectile.image, width, height, st, at)
                r.blit(projectile_image, (int(projectile.x), int(projectile.y)))    

                # Remove projectiles that go off-screen
                if projectile.x > width:
                    self.projectiles.remove(projectile)


            # Update and render rings
            for ring in self.rings:
                ring.update(dtime)
                ring_render = ring.render(width, height, st, at)
                if ring_render:
                    r.blit(ring_render, (0, 0))

            # Check for collisions with rings
            for ring in self.rings:
                if not ring.collected and ring.overlaps_with(self):
                    ring.collected = True
                    self.score += 1    

                if not ring.missed and ring.x < -ring.width:
                    ring.missed = True
                    self.misses += 1

            # Check for a loss.
            if self.health == 0:
                self.lose = True

                renpy.timeout(0)

            # Ask that we be re-rendered ASAP, so we can show the next
            # frame.
            renpy.redraw(self, 0)

            # Return the Render object.
            return r

        # Handles events.
        def event(self, ev, x, y, st):

            import pygame

            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_LEFT:
                self.key_left = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_LEFT:
                self.key_left = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_RIGHT:
                self.key_right = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_RIGHT:
                self.key_right = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_UP:
                self.key_up = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_UP:
                self.key_up = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_DOWN:
                self.key_down = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_DOWN:
                self.key_down = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_SPACE:
                current_time = st  # Use the current time passed from the render method            

                # Define player fire rate here (e.g., fires once every 0.5 seconds)
                player_fire_rate = 15            

                # Check if enough time has passed since the last fire
                if current_time - self.player_last_fire_time >= 1.0 / player_fire_rate:
                    basic_projectile = Projectile(
                        self.px + 300,  # Initial x position for the projectile
                        self.py + 95,   # Initial y position for the projectile
                        basic_weapon["projectile_image"],
                        basic_weapon["projectile_speed"],
                        basic_weapon["damage"],
                        basic_weapon["fire_rate"],
                        is_player=True  # Indicate that the projectile is fired by the player
                    )
                    self.projectiles.append(basic_projectile)
                    self.player_last_fire_time = current_time  # Update the last fire time            


            # Update gauge rendering based on velocity
            if self.velocity >= 56:
                self.gauge = 7
            elif self.velocity >= 48:
                self.gauge = 6
            elif self.velocity >= 40:
                self.gauge = 5
            elif self.velocity >= 32:
                self.gauge = 4
            elif self.velocity >= 24:
                self.gauge = 3
            elif self.velocity >= 16:
                self.gauge = 2
            elif self.velocity >= 8:
                self.gauge = 1
            elif self.velocity == 1:
                self.gauge = 0

            # Ensure the screen updates.
            renpy.restart_interaction()            

            # If the player loses, return it.
            if self.lose:
                return self.lose
            else:
                raise renpy.IgnoreEvent()
    
    def initialize_rings(ring_count, ring_width, path_to_png1, path_to_png2, delay_range=(10.0, 14.0)):
        rings = []
        for i in range(ring_count):
            delay = random.uniform(*delay_range)
            delay += i * 1.5  # Increase delay with each ring
            ring_x = renpy.config.screen_width + i * ring_width  # Start offscreen to the right
            ring_y = random.randint(128, renpy.config.screen_height - 128)
            ring_image_paths = [path_to_png1, path_to_png2]  # List of image paths for animation
            ring = Ring(ring_x, ring_y, ring_image_paths, ringspeed=100, delay=delay)
            rings.append(ring)
        return rings


default sleighgame = SleighShooter()
default cloudcover = CloudGenerator(small_count=20, medium_count=10, large_count=5, outer_count=0, over_count=0)
default cloudfore = CloudGenerator(small_count=0, medium_count=0, large_count=0, outer_count=8, over_count=10)

screen sleigh_ride():

    add Image("bg Sky.png")

    add cloudcover


    add cloudfore

    for ring in rings:
        add ring

    add sleighgame

    # Add a text element to display the score at the top
    vbox:
        text "Score: [sleighgame.score]" style "scoreboard"


    if sleighgame.health == 1:
        vbox:
            add Image("images/SleighShield4.png")
            xpos 30 ypos 10
    elif sleighgame.health == 2:
        vbox:
            add Image("images/SleighShield3.png")
            xpos 30 ypos 10
    elif sleighgame.health == 3:
        vbox:
            add Image("images/SleighShield2.png")
            xpos 30 ypos 10
    elif sleighgame.health == 4:
        vbox:
            add Image("images/SleighShield1.png")
            xpos 30 ypos 10

    if sleighgame.gauge == 0:
        vbox:
            add Image("images/Gauge/gauge0.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 1:
        vbox:
            add Image("images/Gauge/gauge1.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 2:
        vbox:
            add Image("images/Gauge/gauge2.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 3:
        vbox:
            add Image("images/Gauge/gauge3.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 4:
        vbox:
            add Image("images/Gauge/gauge4.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 5:
        vbox:
            add Image("images/Gauge/gauge5.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 6:
        vbox:
            add Image("images/Gauge/gauge6.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 7:
        vbox:
            add Image("images/Gauge/gauge7.png")
            xpos 20 ypos 120

I'm sure there are parts of this code that are still a nonsensical mess, I went through a lot of trial and error.

Things still on my to-do list that I would welcome advice for:
- Initializing the clouds so that there are some onscreen at the start
- Giving the sleigh a bit of transient rotation on acceleration and deceleration
- Implementing different enemy movement patterns. Planning on making dicts of movement patterns and attack patterns. Might be better to fuse into one pattern, not sure.

User avatar
plastiekk
Regular
Posts: 112
Joined: Wed Sep 29, 2021 4:08 am
Contact:

Re: Shooter Minigame Help - Basic Randomization with initial onscreen state?

#7 Post by plastiekk »

This looks like a great arcade game! I don't know how you can place the clouds at the beginning of the screen, but as an alternative you could put a text "GET READY!" or a long fade in/dissolve until all the clouds have appeared. such tricks have been used for decades :D

Edit: You would need to have a static map to display the clouds directly. That is, a landscape that can be scrolled back and forth at will like in olf video games a'la mario, gianna sistern etc.
Why on earth did I put the bread in the fridge?

User avatar
plastiekk
Regular
Posts: 112
Joined: Wed Sep 29, 2021 4:08 am
Contact:

Re: Shooter Minigame Help - Basic Randomization with initial onscreen state?

#8 Post by plastiekk »

... dang, what am I doing? lol (i wanted to correct my typo and clicked on the wrong button)
Why on earth did I put the bread in the fridge?

JamesOles
Newbie
Posts: 1
Joined: Fri Aug 04, 2023 5:59 am
Contact:

Re: Shooter Minigame Help - Basic Randomization with initial onscreen state?

#9 Post by JamesOles »

Hi there, I really like your game. Thanks for sharing about your progress with us. I would love to play this game when it is all complete. I am glad I found your post. I was searching for this https://letsgradeit.com/review/masterpapers/ website online because I got a lot of essay assignments from my mentor and I can't complete them on my own and that is why I need a help from professional online essay writer. But before hiring an essay writer, I want to read their reviews and that is why I am looking for that website online and while searching for it online, I found link to your post as well.
Last edited by JamesOles on Tue Aug 22, 2023 4:57 am, edited 1 time in total.

thexerox123
Regular
Posts: 134
Joined: Fri Jan 20, 2023 3:21 pm
itch: thexerox123
Contact:

Re: Shooter Minigame Help - Basic Randomization with initial onscreen state?

#10 Post by thexerox123 »

plastiekk wrote: Fri Aug 11, 2023 11:11 am This looks like a great arcade game! I don't know how you can place the clouds at the beginning of the screen, but as an alternative you could put a text "GET READY!" or a long fade in/dissolve until all the clouds have appeared. such tricks have been used for decades :D

Edit: You would need to have a static map to display the clouds directly. That is, a landscape that can be scrolled back and forth at will like in olf video games a'la mario, gianna sistern etc.
Thanks! :)

Yeah, I already have a GET READY! image for the other minigame in my game, so that's been my backup plan. I'll probably end up just going with that, because everything I've looked into to get the clouds onscreen at the start don't really seem worth the effort, at this point.

Made some more progress over the past few days!

Image

Made it so that there are both static and moving rings now, I can select the number of each and then they get randomized into a list together.

And now, upon collecting a ring, it turns yellow and plays a bell sound effect.

Code: Select all

style scoreboard:
    color "#8B0000"  # Set text color to red
    size 50          # Set font size to 50
    xpos 880          # Set x position
    ypos 20          # Set y position

init python:
    import random
    import pygame 

    weapon_variables = {
        "basic": {
            "damage": 10,
            "fire_rate": 0.5,
            "projectile_image": "images/Projectiles/Basic.png",
            "projectile_speed": 800
        },    

        "heavy": {
            "damage": 20,
            "fire_rate": 1.0,
            "projectile_image": "heavy_projectile.png",
            "projectile_speed": 300
        },
        # Define more weapon presets
    }

    basic_weapon = weapon_variables["basic"]

    left_frames = [
        "Ring1-1.png",
        "Ring2-1.png",
        "Ring3-1.png",
        # Add more animation frame paths here
    ] 

    right_frames = [
        "Ring1-2.png",
        "Ring2-2.png",
        "Ring3-2.png",
    ]

    # Create a list to hold the Ring instances
    rings = []
    movingrings = []


    class CloudGenerator(renpy.Displayable):
        def __init__(self, small_count, medium_count, large_count, outer_count, over_count):
            renpy.Displayable.__init__(self)
  
            self.small_count = small_count
            self.medium_count = medium_count
            self.large_count = large_count
            self.outer_count = outer_count
            self.over_count = over_count

            self.small_y_options = range(128, 1080 - 128)  # Adjust this range as needed
            self.medium_y_options = range(128, 1080 - 128)  # Adjust this range as needed
            self.large_y_options = range(100, 900) 
            self.outer_y_options = [-50, 0, 50, 1030, 1000, 970]
            self.over_y_options = range(-50, 1130)

            self.clouds = []  # List to hold cloud instances
            self.cloud_size_images = {
                "small": [
                    "images/Clouds/White/cloud_shape3_5.png",
                    "images/Clouds/White/cloud_shape4_5.png",
                    "images/Clouds/White/cloud_shape2_5.png",
                    "images/Clouds/White/cloud_shape3_4.png",
                    "images/Clouds/White/cloud_shape3_3.png",
                    "images/Clouds/White/cloud_shape3_2.png",
                    "images/Clouds/White/cloud_shape3_1.png",
                    "images/Clouds/White/cloud_shape4_4.png",
                    "images/Clouds/White/cloud_shape4_3.png",
                    "images/Clouds/White/cloud_shape4_2.png",
                    "images/Clouds/White/cloud_shape4_1.png",
                    "images/Clouds/White/cloud_shape2_4.png",
                    "images/Clouds/White/cloud_shape2_3.png",
                    "images/Clouds/White/cloud_shape2_2.png",
                    "images/Clouds/White/cloud_shape2_1.png",
                    # Add more small cloud image paths here
                ],
                "medium": [
                    "images/Clouds/White/cloud_shape3_4.png",
                    "images/Clouds/White/cloud_shape3_3.png",
                    "images/Clouds/White/cloud_shape3_2.png",
                    "images/Clouds/White/cloud_shape3_1.png",
                    "images/Clouds/White/cloud_shape4_4.png",
                    "images/Clouds/White/cloud_shape4_3.png",
                    "images/Clouds/White/cloud_shape4_2.png",
                    "images/Clouds/White/cloud_shape4_1.png",
                    "images/Clouds/White/cloud_shape2_4.png",
                    "images/Clouds/White/cloud_shape2_3.png",
                    "images/Clouds/White/cloud_shape2_2.png",
                    "images/Clouds/White/cloud_shape2_1.png",
                    # Add more medium cloud image paths here
                ],
                "large": [
                    "images/Clouds/White/cloud_large1.png",
                    "images/Clouds/White/cloud_large2.png",
                    # Add more large cloud image paths here
                ]
            }    

            cloud_counts = [self.small_count, self.medium_count, self.large_count, self.outer_count, self.over_count]


            s_buffer_distance = 300
            m_buffer_distance = 300 
            l_buffer_distance = 500

            self.oldst = None

            # Initialize cloud instances based on provided counts
            for i in range(small_count):
                cloud_size = "small"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.1, 0.5])    

                cloud = {
                    "count_type" : "small",
                    "x": 1920 + 128 + i * s_buffer_distance,
                    "y": random.randint(128, 1080 - 128),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)    
                cloud["delay"] += 1

            for i in range(medium_count):
                cloud_size = "medium"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.7, 0.8, 0.9])    

                cloud = {
                    "count_type" : "medium",
                    "x": 1920 + 128 + i * m_buffer_distance,
                    "y": random.randint(128, 1080 - 128),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud) 
                cloud["delay"] += 1 

            for i in range(large_count):
                cloud_size = "large"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.7, 0.8, 0.9])    

                cloud = {
                    "count_type" : "large",
                    "x": 1920 + 128 + i * l_buffer_distance,
                    "y": random.randint(100, 900),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            for i in range(outer_count):
                cloud_size = "large"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.5, 0.6])    

                cloud = {
                    "count_type" : "outer",
                    "x": 1920 + 128 + i * l_buffer_distance,
                    "y": random.choice([-50, 0, 50, 1030, 1000, 970]),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            for i in range(over_count):
                cloud_size = "small"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.6, 0.7])    

                cloud = {
                    "count_type" : "over",
                    "x": 1920 + 128 + i * s_buffer_distance,
                    "y": random.randint(-50, 1130),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            self.oldst = None

        def render(self, width, height, st, at):
            r = renpy.Render(width, height)    

            if self.oldst is None:
                self.oldst = st            

            dtime = st - self.oldst
            self.oldst = st           

            for cloud in self.clouds:
                # Check if the cloud's delay has elapsed
                if cloud["delay"] <= 0:
                    cloud["x"] -= cloud["speed"] + sleighgame.velocity
                    if cloud["x"] < -600:
                        cloud["x"] = width + 300
                        if cloud["count_type"] == "small":
                            cloud["y"] = random.choice(self.small_y_options)
                        elif cloud["count_type"] == "medium":
                            cloud["y"] = random.choice(self.medium_y_options)
                        elif cloud["count_type"] == "large":
                            cloud["y"] = random.choice(self.large_y_options)
                        elif cloud["count_type"] == "outer":
                            cloud["y"] = random.choice(self.outer_y_options)
                        elif cloud["count_type"] == "over":
                            cloud["y"] = random.choice(self.over_y_options)
    

                    cloud_image = renpy.render(cloud["image"], width, height, st, at)
                    r.blit(cloud_image, (int(cloud["x"]), int(cloud["y"])))
                else:
                    cloud["delay"] -= at + st  # Reduce the delay

            renpy.redraw(self, 0)
            return r 

    class Ring(renpy.Displayable):
        def __init__(self, x, y, images_left, images_right, ringspeed, ring_type, buffer):
            renpy.Displayable.__init__(self)
            self.x = x
            self.y = y
            self.height = 322
            self.width = 144
            self.images_left = [Image(image) for image in left_frames]
            self.images_right = [Image(image) for image in right_frames]
            self.left_frames = left_frames
            self.right_frames = right_frames
            self.frame_index = 0
            self.collected = False
            self.missed = False
            self.ringspeed = ringspeed 
            self.ring_type = ring_type
            self.buffer = buffer

            # Set default animation speed (change this value as needed)
            self.animation_speed = 1
            self.right_half_hit = False
            self.ring_instance = None  # Initialize the ring_instance attribute

            # Add frame indices for different stages of animation
            self.animation_frames = [0, 1, 2]
            self.current_frame = self.animation_frames[0]
            self.animation_delay = 0.2  # Set the delay for each animation frame (adjust as needed)    

            self.animation = False  # Initialize the animation attribute

            # Lists of image paths for each frame
            self.left_frames = images_left
            self.right_frames = images_right

            # New attributes for the moving behavior
            self.moving_y_range = 200  # Adjust the range as needed
            self.moving_direction = 1  # Start moving down
            self.y_initial = y  # Define y_initial here

        def overlaps_with(self, other):
            ring_left = self.x + self.width * 1.2  # Use the left edge of the ring
            sleigh_right = other.px + other.width  # Calculate the right edge x-coordinate of the sleigh        

            horizontal_hit = (ring_left <= sleigh_right) and (ring_left + self.width >= sleigh_right)  # Check for overlap at right side
            vertical_hit = (self.y <= other.py + other.height) and (self.y + self.height >= other.py)        

            if horizontal_hit and vertical_hit:
                self.collected = True 
                self.animation = True
                return True        

            return False

        def render_left_half(self, width, height, st, at):
            if self.collected or self.missed or self.x < -self.width:
                return None    

            if self.ring_type == "Moving":
                self.current_frame = 1

            r = renpy.Render(width, height)
            current_frame_index = int(self.frame_index)    

            ring_image_left = renpy.render(self.images_left[current_frame_index], width, height, st, at)
            r.blit(ring_image_left, (int(self.x), int(self.y)))    

            return r    

        def render_right_half(self, width, height, st, at):
            if self.collected or self.missed or self.x < -self.width:
                return None    

            r = renpy.Render(width, height)
            current_frame_index = int(self.frame_index)    

            ring_image_right = renpy.render(self.images_right[current_frame_index], width, height, st, at)
            r.blit(ring_image_right, (int(self.x) + self.width // 2 - 75, int(self.y)))    

            return r

        def render_anim_left(self, width, height, st, at):
            if not self.collected or self.animation_complete == True:
                return None   

            self.current_frame = 2  

            r = renpy.Render(width, height)    

            # Render the appropriate frame based on the current animation frame
            current_frame_index = self.current_frame
            ring_image_left = renpy.render(self.images_left[current_frame_index], width, height, st, at)    

            r.blit(ring_image_left, (int(self.x), int(self.y)))    

            if self.current_frame >= 3:
                self.animation_complete = True

            return r    

        def render_anim_right(self, width, height, st, at):
            if not self.collected or self.animation_complete == True:
                return None   

            self.current_frame = 2 

            r = renpy.Render(width, height)    

            # Render the appropriate frame based on the current animation frame
            current_frame_index = self.current_frame
            ring_image_right = renpy.render(self.images_right[current_frame_index], width, height, st, at)    

            r.blit(ring_image_right, (int(self.x) + self.width // 2 - 75, int(self.y)))  

            if self.current_frame >= 3:  
                self.animation_complete = True

            return r


        def collect(self):
            self.collected = True
            renpy.play("audio/bells.mp3", "sound")
            self.animation_speed = 0.1
            self.animation_elapsed_time = 0
            self.animation_complete = False    


        def update(self, dtime):

            if self.collected or self.missed:
                sleighgame.totalrings += 1
            if self.collected:
                self.animation_elapsed_time += dtime
                if self.animation_elapsed_time >= self.animation_delay:
                    self.current_frame = (self.current_frame + 1) % len(self.animation_frames)
                    self.animation_elapsed_time = 0    

            self.x -= self.ringspeed * dtime + sleighgame.velocity
            self.frame_index = int((self.frame_index + self.animation_speed * dtime) % len(self.images_left))    

            if self.ring_type == "Moving":
                self.y += self.moving_direction * 200 * dtime  # Adjust the speed as needed    

                if self.y <= self.y_initial - self.moving_y_range:
                    self.moving_direction = 1
                elif self.y >= self.y_initial + self.moving_y_range:
                    self.moving_direction = -1


    class Projectile(renpy.Displayable):
        def __init__(self, x, y, image_path, speed, width, height, is_player=False):

            renpy.Displayable.__init__(self)
            self.x = x
            self.y = y
            self.image_path = image_path
            self.image = renpy.display.im.Image(image_path)  # Load the image
            self.speed = speed
            self.width = width
            self.height = height    
            self.is_player = is_player


            # Load projectile images (animation frames if multiple paths are provided)
            if isinstance(self.image_path, list):
                self.animation_frames = [Image(path) for path in image_paths]
                self.frame_index = 0  # Start with the first frame
            else:
                self.image = Image(image_path)          

        def render(self, width, height, st, at):
            if self.is_player:
                self.x += self.speed * dtime
            else:
                self.x -= self.speed * dtime   

            # Render the projectile (animation or single image)
            if hasattr(self, "animation_frames"):
                self.frame_index = (self.frame_index + 1) % len(self.animation_frames)
                current_frame = self.animation_frames[self.frame_index]
                projectile_image = renpy.render(current_frame, width, height, st, at)
            else:
                projectile_image = renpy.render(self.image, width, height, st, at)            

            r.blit(projectile_image, (int(self.x), int(self.y)))

    class SleighShooter(renpy.Displayable):
        def __init__(self):

            renpy.Displayable.__init__(self)

            self.key_up = False
            self.key_down = False
            self.key_left = False
            self.key_right = False
            self.accelerate = False

            self.SPEED = 15

            self.score = 0
            self.misses = 0

            self.velocity = 0
            self.gauge = 0
            self.health = 4

            # Some displayables we use.
            self.player = Image("Sleigh1.png")

            self.width = 326
            self.height = 238


            # Player position
            self.px = 20
            self.py = 500
            self.pymin = 10
            self.pymax = 1080 - 238
            self.pxmin = 0
            self.pxmax = 960 - 326

            self.oldst = None

            self.lose = False

            self.enemies = []

            self.projectiles = []

            self.active_enemy_projectiles = []  # List to store active enemy projectiles


            # Initialize the rings with their attributes
            rings = initialize_rings(ring_count=20, ring_width=50, path_to_png_left="Ring1-1.png", path_to_png_right="Ring1-2.png", ring_type="Static", buffer_range=(700, 2000))
            rings += initialize_rings(ring_count=5, ring_width=50, path_to_png_left="Ring2-1.png", path_to_png_right="Ring2-2.png", ring_type="Moving", buffer_range=(700, 2000))
            
            # Shuffle the rings list
            import random
            random.shuffle(rings)    

            # Generate rings with proper spacing
            current_x = renpy.config.screen_width  # Start offscreen to the right
            for ring in rings:
                ring.x = current_x
                current_x += ring.buffer + ring.width    

            self.rings = rings  # Assign the shuffled rings back to the class attribute    

            self.totalrings = 0

            self.rotenote = False
            self.rotation = 0
            self.player_last_fire_time = 0  # Initialize with 0
            return

        def calculate_dtime(self, st):
            if self.oldst is None:
                self.oldst = st    

            dtime = st - self.oldst
            self.oldst = st
            return dtime

        def overlaps_with(self, other):
            left = self.px
            right = self.px + self.width
            top = self.py
            bottom = self.py + self.height    

            other_left = other.x
            other_right = other.x + other.width
            other_top = other.y
            other_bottom = other.y + other.height    

            horizontal_hit = (left <= other_right) and (right >= other_left)
            vertical_hit = (top <= other_bottom) and (bottom >= other_top)    

            return horizontal_hit and vertical_hit

        # Draws the screen
        def render(self, width, height, st, at):
            if self.rotenote:
                r = renpy.render(
                Transform(self.player, rotate=(self.rotation)),
                self.rotation, st, at)

            # The Render object we'll be drawing into.
            else:
                r = renpy.Render(width, height)

            # Figure out the time elapsed since the previous frame.
            if self.oldst is None:
                self.oldst = st

            dtime = st - self.oldst
            self.oldst = st

            # Update and render rings
            for ring in self.rings:
                ring.update(dtime)    

                # Render the left half of the ring below the sleigh
                ring_left_half_render = ring.render_left_half(width, height, st, at)
                ring_left_half_anim = ring.render_anim_left(width, height, st, at)
                if ring_left_half_render:
                    r.blit(ring_left_half_render, (0, 0))
                if ring_left_half_anim:
                    r.blit(ring_left_half_anim, (0, 0))

            # This draws the player.
            def player(px, py, width, height, pymin, pymax):

                # Render the player image.
                player = renpy.render(self.player, width, height, st, at)

                # renpy.render returns a Render object, which we can
                # blit to the Render we're making.
                r.blit(player, (int(self.px), int(self.py)))

            player(self.px, self.py, width, height, self.pymin, self.pymax)


            # Sleigh's travel boundaries
            if self.py < self.pymin:
                self.py = self.pymin               
            if self.py > self.pymax:
                self.py = self.pymax

            if self.px < self.pxmin:
                self.px = self.pxmin               
            if self.px > self.pxmax:
                self.px = self.pxmax

            # Translate keypress to action
            if self.key_up == True:
                self.py -= self.SPEED
            if self.key_down == True:
                self.py += self.SPEED
            if self.key_left == True:
                self.velocity -= 1.5
                self.px -= self.SPEED + self.gauge
            if self.key_right == True:
                self.velocity += 1.5
                self.px += self.SPEED + self.gauge
                self.accelerate = True


            if self.velocity >= 56:
                self.px += self.SPEED + 10
                if self.accelerate == False:
                    if self.px <= 700:
                        self.px = 700
            elif self.velocity >= 40:
                if self.accelerate == False:
                    if self.px <= 500:
                        self.px = 500
            elif self.velocity >= 24:
                if self.accelerate == False: 
                    if self.px <= 300:
                        self.px = 300
            elif self.velocity >= 8:
                if self.accelerate == False:
                    if self.px <= 100:
                        self.px = 100
            if self.velocity <= 1:
                self.velocity = 1
            if self.velocity >= 56:
                self.velocity = 56


            # Update and render projectiles
            for projectile in self.projectiles:
                projectile.x += projectile.speed * dtime
                projectile_image = renpy.render(projectile.image, width, height, st, at)
                r.blit(projectile_image, (int(projectile.x), int(projectile.y)))    

                # Remove projectiles that go off-screen
                if projectile.x > width:
                    self.projectiles.remove(projectile)


            # Render the right half of the ring above the sleigh
            for ring in self.rings:
                ring_right_half_render = ring.render_right_half(width, height, st, at)
                ring_right_half_anim = ring.render_anim_right(width, height, st, at)
                if ring_right_half_render:
                    r.blit(ring_right_half_render, (0, 0))
                if ring_right_half_anim:
                    r.blit(ring_right_half_anim, (0, 0))


            # Check for collisions with rings
            for ring in self.rings:
                if not ring.collected and ring.overlaps_with(self):
                    ring.collected = True
                    ring.collect()  # Call the collect method
                    self.score += 1    

                if not ring.missed and ring.x < -ring.width:
                    ring.missed = True
                    self.misses += 1

            # Check for a loss.
            if self.health == 0:
                self.lose = True

                renpy.timeout(0)

            # Ask that we be re-rendered ASAP, so we can show the next
            # frame.
            renpy.redraw(self, 0)

            # Return the Render object.
            return r

        # Handles events.
        def event(self, ev, x, y, st):

            import pygame

            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_LEFT:
                self.key_left = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_LEFT:
                self.key_left = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_RIGHT:
                self.key_right = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_RIGHT:
                self.key_right = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_UP:
                self.key_up = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_UP:
                self.key_up = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_DOWN:
                self.key_down = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_DOWN:
                self.key_down = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_SPACE:
                current_time = st  # Use the current time passed from the render method            

                # Define player fire rate here (e.g., fires once every 0.5 seconds)
                player_fire_rate = 15            

                # Check if enough time has passed since the last fire
                if current_time - self.player_last_fire_time >= 1.0 / player_fire_rate:
                    basic_projectile = Projectile(
                        self.px + 300,  # Initial x position for the projectile
                        self.py + 95,   # Initial y position for the projectile
                        basic_weapon["projectile_image"],
                        basic_weapon["projectile_speed"],
                        basic_weapon["damage"],
                        basic_weapon["fire_rate"],
                        is_player=True  # Indicate that the projectile is fired by the player
                    )
                    self.projectiles.append(basic_projectile)
                    self.player_last_fire_time = current_time  # Update the last fire time            


            # Update gauge rendering based on velocity
            if self.velocity >= 56:
                self.gauge = 7
            elif self.velocity >= 48:
                self.gauge = 6
            elif self.velocity >= 40:
                self.gauge = 5
            elif self.velocity >= 32:
                self.gauge = 4
            elif self.velocity >= 24:
                self.gauge = 3
            elif self.velocity >= 16:
                self.gauge = 2
            elif self.velocity >= 8:
                self.gauge = 1
            elif self.velocity == 1:
                self.gauge = 0

            # Ensure the screen updates.
            renpy.restart_interaction()            

            # If the player loses, return it.
            if self.lose:
                return self.lose
            else:
                raise renpy.IgnoreEvent()
    
    def initialize_rings(ring_count, ring_width, path_to_png_left, path_to_png_right, ring_type, buffer_range=(600, 800)):
        rings = []
        for i in range(ring_count):
            buffer = random.uniform(*buffer_range)
            ring_x = renpy.config.screen_width + buffer + i * ring_width  # Start offscreen to the right
            ring_y = random.randint(128, renpy.config.screen_height - 300)
            ring_images_left = ["images/Ring1-1.png", "images/Ring2-1.png"]
            ring_images_right = ["images/Ring1-2.png", "images/Ring2-2.png"]
            
            if ring_type == "Moving":
                ring = Ring(ring_x, ring_y, ring_images_left, ring_images_right, ringspeed=100, ring_type="Moving", buffer=buffer)
            else:
                ring = Ring(ring_x, ring_y, ring_images_left, ring_images_right, ringspeed=100, ring_type="Static", buffer=buffer)
                
            rings.append(ring)
            
        return rings

default sleighgame = SleighShooter()
default cloudcover = CloudGenerator(small_count=20, medium_count=10, large_count=5, outer_count=0, over_count=0)
default cloudfore = CloudGenerator(small_count=0, medium_count=0, large_count=0, outer_count=8, over_count=10)

screen sleigh_ride():

    add Image("bg Sky.png")

    add cloudcover


    add cloudfore

    # Add the left side of the rings below the sleigh
    for ring in rings:
        add ring.render_left_half(width, height, st, at)

    add sleighgame

    # Add the right side of the rings above the sleigh
    for ring in rings:
        add ring.render_right_half(width, height, st, at)

    # Add a text element to display the score at the top
    vbox:
        text "Score: [sleighgame.score]" style "scoreboard"


    if sleighgame.health == 1:
        vbox:
            add Image("images/SleighShield4.png")
            xpos 30 ypos 10
    elif sleighgame.health == 2:
        vbox:
            add Image("images/SleighShield3.png")
            xpos 30 ypos 10
    elif sleighgame.health == 3:
        vbox:
            add Image("images/SleighShield2.png")
            xpos 30 ypos 10
    elif sleighgame.health == 4:
        vbox:
            add Image("images/SleighShield1.png")
            xpos 30 ypos 10

    if sleighgame.gauge == 0:
        vbox:
            add Image("images/Gauge/gauge0.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 1:
        vbox:
            add Image("images/Gauge/gauge1.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 2:
        vbox:
            add Image("images/Gauge/gauge2.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 3:
        vbox:
            add Image("images/Gauge/gauge3.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 4:
        vbox:
            add Image("images/Gauge/gauge4.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 5:
        vbox:
            add Image("images/Gauge/gauge5.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 6:
        vbox:
            add Image("images/Gauge/gauge6.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 7:
        vbox:
            add Image("images/Gauge/gauge7.png")
            xpos 20 ypos 120


    python:
        for ring in rings:
            ring.animation.update(dtime)

Going to keep this thread going just to continue to share/track my progress on this code, and welcome any feedback on how to make it better!

(I'm pretty sure I have some vestigial variables now from all of the trial and error that it took for me to figure these things out, so I'll have to do some targeted pruning at some point.)

I probably want the ring collecting to remain as its own gametype, so I may add a timer to it, play around with timing to incentivize speed but not make it a nightmare.

Then I'll probably get back to trying to implement enemies and their projectiles, as well as upgrades/different weapons for the player. :)

I feel like those will go smoother than my first attempt at the class went, now that I at least have done some successful collision detection code for it.

thexerox123
Regular
Posts: 134
Joined: Fri Jan 20, 2023 3:21 pm
itch: thexerox123
Contact:

Re: Shooter Minigame Help - Basic Randomization with initial onscreen state?

#11 Post by thexerox123 »

Actually, looks like my next task is to drastically refactor and clean up the code.

I had originally planned for the rings to animate through a few frames and then disappear, but ultimately I liked the look better when it just turned yellow and continued on. Since the original rings were set to stop rendering when collected, I couldn't continue to use the instances to flag animation, so I just made a janky workaround... but now that I don't have them disappear, I realize that it's unneeded.

So, need to switch my animation frame logic over to just the left and right render methods to simplify things. And I also want to split my player logic out into its own class, because collision interactions are getting more and more complex.

I've also started trying to implement "closed" rings that you have to shoot with a projectile to fly through, otherwise collision with them deals damage. Almost got it working, but I had trouble with the collision detection logic.

Tried my hand at all of these fixes/changes last night and this morning, and haven't had luck with any of them, yet. But that's how it's been for everything that I've implemented so far. :lol:

So I guess if anybody has advice on how to make the code and logical organization less of a disaster, I'd be very grateful!



Edit:
I have at least successfully gotten a start by removing a lot of the extraneous animation-related code. Next is to try again at reducing the Ring render methods from 4 to 2.

Re-edit:
Got the reduced render methods working! That will make implementing the new ring types a lot easier, though I still need to split the player out into its own class to make the collision logic not an absolute nightmare to parse.

Re-re-edit:
Cleaned up the image path logic a bunch.

Code: Select all

style scoreboard:
    color "#8B0000"  # Set text color to red
    size 50          # Set font size to 50
    xpos 880          # Set x position
    ypos 20          # Set y position

init python:
    import random
    import pygame 

    weapon_variables = {
        "basic": {
            "damage": 10,
            "fire_rate": 0.5,
            "projectile_image": "images/Projectiles/Basic.png",
            "projectile_speed": 800
        },    

        "heavy": {
            "damage": 20,
            "fire_rate": 1.0,
            "projectile_image": "heavy_projectile.png",
            "projectile_speed": 300
        },
        # Define more weapon presets
    }

    basic_weapon = weapon_variables["basic"]

    left_frames = [
        "Ring1-1.png",
        "Ring2-1.png",
        "Ring3-1.png",
        "ClosedRing1-1.png",
        "ClosedRing2-1.png"
        # Add more animation frame paths here
    ] 

    right_frames = [
        "Ring1-2.png",
        "Ring2-2.png",
        "Ring3-2.png",
        "ClosedRing1-2.png",
        "ClosedRing2-2.png"
    ]

    # Create a list to hold the Ring instances
    rings = []


    class CloudGenerator(renpy.Displayable):
        def __init__(self, small_count, medium_count, large_count, outer_count, over_count):
            renpy.Displayable.__init__(self)
  
            self.small_count = small_count
            self.medium_count = medium_count
            self.large_count = large_count
            self.outer_count = outer_count
            self.over_count = over_count

            self.small_y_options = range(128, 1080 - 128)  # Adjust this range as needed
            self.medium_y_options = range(128, 1080 - 128)  # Adjust this range as needed
            self.large_y_options = range(100, 900) 
            self.outer_y_options = [-50, 0, 50, 1030, 1000, 970]
            self.over_y_options = range(-50, 1130)

            self.clouds = []  # List to hold cloud instances
            self.cloud_size_images = {
                "small": [
                    "images/Clouds/White/cloud_shape3_5.png",
                    "images/Clouds/White/cloud_shape4_5.png",
                    "images/Clouds/White/cloud_shape2_5.png",
                    "images/Clouds/White/cloud_shape3_4.png",
                    "images/Clouds/White/cloud_shape3_3.png",
                    "images/Clouds/White/cloud_shape3_2.png",
                    "images/Clouds/White/cloud_shape3_1.png",
                    "images/Clouds/White/cloud_shape4_4.png",
                    "images/Clouds/White/cloud_shape4_3.png",
                    "images/Clouds/White/cloud_shape4_2.png",
                    "images/Clouds/White/cloud_shape4_1.png",
                    "images/Clouds/White/cloud_shape2_4.png",
                    "images/Clouds/White/cloud_shape2_3.png",
                    "images/Clouds/White/cloud_shape2_2.png",
                    "images/Clouds/White/cloud_shape2_1.png",
                    # Add more small cloud image paths here
                ],
                "medium": [
                    "images/Clouds/White/cloud_shape3_4.png",
                    "images/Clouds/White/cloud_shape3_3.png",
                    "images/Clouds/White/cloud_shape3_2.png",
                    "images/Clouds/White/cloud_shape3_1.png",
                    "images/Clouds/White/cloud_shape4_4.png",
                    "images/Clouds/White/cloud_shape4_3.png",
                    "images/Clouds/White/cloud_shape4_2.png",
                    "images/Clouds/White/cloud_shape4_1.png",
                    "images/Clouds/White/cloud_shape2_4.png",
                    "images/Clouds/White/cloud_shape2_3.png",
                    "images/Clouds/White/cloud_shape2_2.png",
                    "images/Clouds/White/cloud_shape2_1.png",
                    # Add more medium cloud image paths here
                ],
                "large": [
                    "images/Clouds/White/cloud_large1.png",
                    "images/Clouds/White/cloud_large2.png",
                    # Add more large cloud image paths here
                ]
            }    

            cloud_counts = [self.small_count, self.medium_count, self.large_count, self.outer_count, self.over_count]


            s_buffer_distance = 300
            m_buffer_distance = 300 
            l_buffer_distance = 500

            self.oldst = None

            # Initialize cloud instances based on provided counts
            for i in range(small_count):
                cloud_size = "small"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.1, 0.5])    

                cloud = {
                    "count_type" : "small",
                    "x": 1920 + 128 + i * s_buffer_distance,
                    "y": random.randint(128, 1080 - 128),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)    
                cloud["delay"] += 1

            for i in range(medium_count):
                cloud_size = "medium"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.7, 0.8, 0.9])    

                cloud = {
                    "count_type" : "medium",
                    "x": 1920 + 128 + i * m_buffer_distance,
                    "y": random.randint(128, 1080 - 128),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud) 
                cloud["delay"] += 1 

            for i in range(large_count):
                cloud_size = "large"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.7, 0.8, 0.9])    

                cloud = {
                    "count_type" : "large",
                    "x": 1920 + 128 + i * l_buffer_distance,
                    "y": random.randint(100, 900),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            for i in range(outer_count):
                cloud_size = "large"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.5, 0.6])    

                cloud = {
                    "count_type" : "outer",
                    "x": 1920 + 128 + i * l_buffer_distance,
                    "y": random.choice([-50, 0, 50, 1030, 1000, 970]),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            for i in range(over_count):
                cloud_size = "small"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.6, 0.7])    

                cloud = {
                    "count_type" : "over",
                    "x": 1920 + 128 + i * s_buffer_distance,
                    "y": random.randint(-50, 1130),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            self.oldst = None

        def render(self, width, height, st, at):
            r = renpy.Render(width, height)    

            if self.oldst is None:
                self.oldst = st            

            dtime = st - self.oldst
            self.oldst = st           

            for cloud in self.clouds:
                # Check if the cloud's delay has elapsed
                if cloud["delay"] <= 0:
                    cloud["x"] -= cloud["speed"] + sleighgame.velocity
                    if cloud["x"] < -600:
                        cloud["x"] = width + 300
                        if cloud["count_type"] == "small":
                            cloud["y"] = random.choice(self.small_y_options)
                        elif cloud["count_type"] == "medium":
                            cloud["y"] = random.choice(self.medium_y_options)
                        elif cloud["count_type"] == "large":
                            cloud["y"] = random.choice(self.large_y_options)
                        elif cloud["count_type"] == "outer":
                            cloud["y"] = random.choice(self.outer_y_options)
                        elif cloud["count_type"] == "over":
                            cloud["y"] = random.choice(self.over_y_options)
    

                    cloud_image = renpy.render(cloud["image"], width, height, st, at)
                    r.blit(cloud_image, (int(cloud["x"]), int(cloud["y"])))
                else:
                    cloud["delay"] -= at + st  # Reduce the delay

            renpy.redraw(self, 0)
            return r 

    class Ring(renpy.Displayable):
        def __init__(self, x, y, images_left, images_right, ringspeed, ring_type, buffer):
            renpy.Displayable.__init__(self)
            self.x = x
            self.y = y
            self.height = 322
            self.width = 144
            self.images_left = [Image(image) for image in left_frames]
            self.images_right = [Image(image) for image in right_frames]
            self.frame_index = 0
            self.collected = False
            self.missed = False
            self.ringspeed = 100
            self.ring_type = ring_type
            self.buffer = buffer

            self.right_half_hit = False
            self.ring_instance = None  # Initialize the ring_instance attribute

            # Add frame indices for different stages of animation
            self.animation_frames = [0, 1, 2, 3, 4]
            self.current_frame = self.animation_frames[0]

            # Lists of image paths for each frame
            self.left_frames = images_left
            self.right_frames = images_right

            # New attributes for the moving behavior
            self.moving_y_range = 200  # Adjust the range as needed
            self.moving_direction = 1  # Start moving down
            self.y_initial = y  # Define y_initial here

        def overlaps_with(self, other):
            ring_left = self.x + self.width * 1.2  # Use the left edge of the ring
            sleigh_right = other.px + other.width  # Calculate the right edge x-coordinate of the sleigh        

            horizontal_hit = (ring_left <= sleigh_right) and (ring_left + self.width >= sleigh_right)  # Check for overlap at right side
            vertical_hit = (self.y <= other.py + other.height) and (self.y + self.height >= other.py)        

            if horizontal_hit and vertical_hit:
                self.collected = True 
                return True        

            return False

        def render_left_half(self, width, height, st, at):
            if self.missed or self.x < -self.width:
                return None    

            # Determine the current frame index based on animation status and type
            if self.collected:
                current_frame_index = 2
            elif self.ring_type == "Moving":
                current_frame_index = 1
            elif self.ring_type == "Static":
                current_frame_index = 0
            elif self.ring_type == "ClosedMoving":
                current_frame_index = 4
            elif self.ring_type == "ClosedStatic":
                current_frame_index = 3

            # Render the appropriate frame based on the current animation frame
            ring_image_left = renpy.render(self.images_left[current_frame_index], width, height, st, at)
            r = renpy.Render(width, height)
            r.blit(ring_image_left, (int(self.x), int(self.y)))    

            return r    

        def render_right_half(self, width, height, st, at):
            if self.missed or self.x < -self.width:
                return None    

            # Determine the current frame index based on animation status and type
            if self.collected:
                current_frame_index = 2
            elif self.ring_type == "Moving":
                current_frame_index = 1
            elif self.ring_type == "Static":
                current_frame_index = 0
            elif self.ring_type == "ClosedMoving":
                current_frame_index = 4
            elif self.ring_type == "ClosedStatic":
                current_frame_index = 3

            # Render the appropriate frame based on the current animation frame
            ring_image_right = renpy.render(self.images_right[current_frame_index], width, height, st, at)

            r = renpy.Render(width, height)
            r.blit(ring_image_right, (int(self.x) + self.width // 2 - 75, int(self.y)))    

            return r

        def collect(self):
            self.collected = True
            renpy.play("audio/bells.mp3", "sound")



        def update(self, dtime):

            if self.collected or self.missed:
                sleighgame.totalrings += 1

            self.x -= self.ringspeed * dtime + sleighgame.velocity  

            if self.ring_type == "Moving" or self.ring_type == "ClosedMoving":
                self.y += self.moving_direction * 200 * dtime  # Adjust the speed as needed    

                if self.y <= self.y_initial - self.moving_y_range:
                    self.moving_direction = 1
                elif self.y >= self.y_initial + self.moving_y_range:
                    self.moving_direction = -1


    class Projectile(renpy.Displayable):
        def __init__(self, x, y, image_path, speed, width, height, is_player=False):

            renpy.Displayable.__init__(self)
            self.x = x
            self.y = y
            self.image_path = image_path
            self.image = renpy.display.im.Image(image_path)  # Load the image
            self.speed = speed
            self.width = width
            self.height = height    
            self.is_player = is_player


            # Load projectile images (animation frames if multiple paths are provided)
            if isinstance(self.image_path, list):
                self.animation_frames = [Image(path) for path in image_paths]
                self.frame_index = 0  # Start with the first frame
            else:
                self.image = Image(image_path)          

        def render(self, width, height, st, at):
            if self.is_player:
                self.x += self.speed * dtime
            else:
                self.x -= self.speed * dtime   

            # Render the projectile (animation or single image)
            if hasattr(self, "animation_frames"):
                self.frame_index = (self.frame_index + 1) % len(self.animation_frames)
                current_frame = self.animation_frames[self.frame_index]
                projectile_image = renpy.render(current_frame, width, height, st, at)
            else:
                projectile_image = renpy.render(self.image, width, height, st, at)            

            r.blit(projectile_image, (int(self.x), int(self.y)))

    class SleighShooter(renpy.Displayable):
        def __init__(self):

            renpy.Displayable.__init__(self)

            self.key_up = False
            self.key_down = False
            self.key_left = False
            self.key_right = False
            self.accelerate = False

            self.SPEED = 15

            self.score = 0
            self.misses = 0

            self.velocity = 0
            self.gauge = 0
            self.health = 4

            # Some displayables we use.
            self.player = Image("Sleigh1.png")

            self.width = 326
            self.height = 238


            # Player position
            self.px = 20
            self.py = 500
            self.pymin = 10
            self.pymax = 1080 - 238
            self.pxmin = 0
            self.pxmax = 960 - 326

            self.oldst = None

            self.lose = False

            self.enemies = []

            self.projectiles = []

            self.active_enemy_projectiles = []  # List to store active enemy projectiles
            

            # Initialize the rings with their attributes
            rings = initialize_rings(ring_count=20, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="Static", buffer_range=(700, 2000))
            rings += initialize_rings(ring_count=5, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="Moving", buffer_range=(700, 2000))            


            # Shuffle the rings list
            import random
            random.shuffle(rings)    

            # Generate rings with proper spacing
            current_x = renpy.config.screen_width  # Start offscreen to the right
            for ring in rings:
                ring.x = current_x
                current_x += ring.buffer + ring.width    

            self.rings = rings  # Assign the shuffled rings back to the class attribute    

            self.totalrings = 0

            self.rotenote = False
            self.rotation = 0
            self.player_last_fire_time = 0  # Initialize with 0
            return

        def calculate_dtime(self, st):
            if self.oldst is None:
                self.oldst = st    

            dtime = st - self.oldst
            self.oldst = st
            return dtime

        def overlaps_with(self, other):
            left = self.px
            right = self.px + self.width
            top = self.py
            bottom = self.py + self.height    

            other_left = other.x
            other_right = other.x + other.width
            other_top = other.y
            other_bottom = other.y + other.height    

            horizontal_hit = (left <= other_right) and (right >= other_left)
            vertical_hit = (top <= other_bottom) and (bottom >= other_top)    

            return horizontal_hit and vertical_hit

        # Draws the screen
        def render(self, width, height, st, at):
            if self.rotenote:
                r = renpy.render(
                Transform(self.player, rotate=(self.rotation)),
                self.rotation, st, at)

            # The Render object we'll be drawing into.
            else:
                r = renpy.Render(width, height)

            # Figure out the time elapsed since the previous frame.
            if self.oldst is None:
                self.oldst = st

            dtime = st - self.oldst
            self.oldst = st

            # Update and render rings
            for ring in self.rings:
                ring.update(dtime)    

                # Render the left half of the ring below the sleigh
                ring_left_half_render = ring.render_left_half(width, height, st, at)
                if ring_left_half_render:
                    r.blit(ring_left_half_render, (0, 0))

            # This draws the player.
            def player(px, py, width, height, pymin, pymax):

                # Render the player image.
                player = renpy.render(self.player, width, height, st, at)

                # renpy.render returns a Render object, which we can
                # blit to the Render we're making.
                r.blit(player, (int(self.px), int(self.py)))

            player(self.px, self.py, width, height, self.pymin, self.pymax)


            # Sleigh's travel boundaries
            if self.py < self.pymin:
                self.py = self.pymin               
            if self.py > self.pymax:
                self.py = self.pymax

            if self.px < self.pxmin:
                self.px = self.pxmin               
            if self.px > self.pxmax:
                self.px = self.pxmax

            # Translate keypress to action
            if self.key_up == True:
                self.py -= self.SPEED
            if self.key_down == True:
                self.py += self.SPEED
            if self.key_left == True:
                self.velocity -= 1.5
                self.px -= self.SPEED + self.gauge
            if self.key_right == True:
                self.velocity += 1.5
                self.px += self.SPEED + self.gauge
                self.accelerate = True


            if self.velocity >= 56:
                self.px += self.SPEED + 10
                if self.accelerate == False:
                    if self.px <= 700:
                        self.px = 700
            elif self.velocity >= 40:
                if self.accelerate == False:
                    if self.px <= 500:
                        self.px = 500
            elif self.velocity >= 24:
                if self.accelerate == False: 
                    if self.px <= 300:
                        self.px = 300
            elif self.velocity >= 8:
                if self.accelerate == False:
                    if self.px <= 100:
                        self.px = 100
            if self.velocity <= 1:
                self.velocity = 1
            if self.velocity >= 56:
                self.velocity = 56


            # Update and render projectiles
            for projectile in self.projectiles:
                projectile.x += projectile.speed * dtime
                projectile_image = renpy.render(projectile.image, width, height, st, at)
                r.blit(projectile_image, (int(projectile.x), int(projectile.y)))    

                # Remove projectiles that go off-screen
                if projectile.x > width:
                    self.projectiles.remove(projectile)


            # Render the right half of the ring above the sleigh
            for ring in self.rings:
                ring_right_half_render = ring.render_right_half(width, height, st, at)
                if ring_right_half_render:
                    r.blit(ring_right_half_render, (0, 0))


            # Check for collisions with rings
            for ring in self.rings:
                if not ring.collected and ring.overlaps_with(self):
                    ring.collected = True
                    ring.collect()  # Call the collect method
                    self.score += 1    

                if not ring.missed and ring.x < -ring.width:
                    ring.missed = True
                    self.misses += 1

            # Check for a loss.
            if self.health == 0:
                self.lose = True

                renpy.timeout(0)

            # Ask that we be re-rendered ASAP, so we can show the next
            # frame.
            renpy.redraw(self, 0)

            # Return the Render object.
            return r

        # Handles events.
        def event(self, ev, x, y, st):

            import pygame

            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_LEFT:
                self.key_left = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_LEFT:
                self.key_left = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_RIGHT:
                self.key_right = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_RIGHT:
                self.key_right = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_UP:
                self.key_up = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_UP:
                self.key_up = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_DOWN:
                self.key_down = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_DOWN:
                self.key_down = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_SPACE:
                current_time = st  # Use the current time passed from the render method            

                # Define player fire rate here (e.g., fires once every 0.5 seconds)
                player_fire_rate = 15            

                # Check if enough time has passed since the last fire
                if current_time - self.player_last_fire_time >= 1.0 / player_fire_rate:
                    basic_projectile = Projectile(
                        self.px + 300,  # Initial x position for the projectile
                        self.py + 95,   # Initial y position for the projectile
                        basic_weapon["projectile_image"],
                        basic_weapon["projectile_speed"],
                        basic_weapon["damage"],
                        basic_weapon["fire_rate"],
                        is_player=True  # Indicate that the projectile is fired by the player
                    )
                    self.projectiles.append(basic_projectile)
                    self.player_last_fire_time = current_time  # Update the last fire time            


            # Update gauge rendering based on velocity
            if self.velocity >= 56:
                self.gauge = 7
            elif self.velocity >= 48:
                self.gauge = 6
            elif self.velocity >= 40:
                self.gauge = 5
            elif self.velocity >= 32:
                self.gauge = 4
            elif self.velocity >= 24:
                self.gauge = 3
            elif self.velocity >= 16:
                self.gauge = 2
            elif self.velocity >= 8:
                self.gauge = 1
            elif self.velocity == 1:
                self.gauge = 0

            # Ensure the screen updates.
            renpy.restart_interaction()            

            # If the player loses, return it.
            if self.lose:
                return self.lose
            else:
                raise renpy.IgnoreEvent()
                
    def initialize_rings(ring_count, ring_width, images_left, images_right, ring_type, buffer_range=(600, 800)):
        rings = []
        for i in range(ring_count):
            buffer = random.uniform(*buffer_range)
            ring_x = renpy.config.screen_width + buffer + i * ring_width  # Start offscreen to the right
            ring_y = random.randint(128, renpy.config.screen_height - 300)
            
            if ring_type == "Moving":
                ring = Ring(ring_x, ring_y, images_left, images_right, ringspeed=100, ring_type="Moving", buffer=buffer)
            else:
                ring = Ring(ring_x, ring_y, images_left, images_right, ringspeed=100, ring_type="Static", buffer=buffer)
                        
            rings.append(ring)
            
        return rings

default sleighgame = SleighShooter()
default cloudcover = CloudGenerator(small_count=20, medium_count=10, large_count=5, outer_count=0, over_count=0)
default cloudfore = CloudGenerator(small_count=0, medium_count=0, large_count=0, outer_count=8, over_count=10)

screen sleigh_ride():

    add Image("bg Sky.png")

    add cloudcover


    add cloudfore

    # Add the left side of the rings below the sleigh
    for ring in rings:
        add ring.render_left_half(width, height, st, at)

    add sleighgame

    # Add the right side of the rings above the sleigh
    for ring in rings:
        add ring.render_right_half(width, height, st, at)

    # Add a text element to display the score at the top
    vbox:
        text "Score: [sleighgame.score]" style "scoreboard"


    if sleighgame.health == 1:
        vbox:
            add Image("images/SleighShield4.png")
            xpos 30 ypos 10
    elif sleighgame.health == 2:
        vbox:
            add Image("images/SleighShield3.png")
            xpos 30 ypos 10
    elif sleighgame.health == 3:
        vbox:
            add Image("images/SleighShield2.png")
            xpos 30 ypos 10
    elif sleighgame.health == 4:
        vbox:
            add Image("images/SleighShield1.png")
            xpos 30 ypos 10

    if sleighgame.gauge == 0:
        vbox:
            add Image("images/Gauge/gauge0.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 1:
        vbox:
            add Image("images/Gauge/gauge1.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 2:
        vbox:
            add Image("images/Gauge/gauge2.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 3:
        vbox:
            add Image("images/Gauge/gauge3.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 4:
        vbox:
            add Image("images/Gauge/gauge4.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 5:
        vbox:
            add Image("images/Gauge/gauge5.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 6:
        vbox:
            add Image("images/Gauge/gauge6.png")
            xpos 20 ypos 120
    elif sleighgame.gauge == 7:
        vbox:
            add Image("images/Gauge/gauge7.png")
            xpos 20 ypos 120


thexerox123
Regular
Posts: 134
Joined: Fri Jan 20, 2023 3:21 pm
itch: thexerox123
Contact:

Re: Shooter Minigame Help - Seeking Refactoring/Organization Advice

#12 Post by thexerox123 »

Phew, and managed to split the player out into its own class!

Code: Select all

style scoreboard:
    color "#8B0000"  # Set text color to red
    size 50          # Set font size to 50
    xpos 880          # Set x position
    ypos 20          # Set y position

init python:
    import random
    import pygame 

    weapon_variables = {
        "basic": {
            "damage": 10,
            "fire_rate": 0.5,
            "projectile_image": "images/Projectiles/Basic.png",
            "projectile_speed": 800
        },    

        "heavy": {
            "damage": 20,
            "fire_rate": 1.0,
            "projectile_image": "heavy_projectile.png",
            "projectile_speed": 300
        },
        # Define more weapon presets
    }

    basic_weapon = weapon_variables["basic"]

    left_frames = [
        "Ring1-1.png",
        "Ring2-1.png",
        "Ring3-1.png",
        "ClosedRing1-1.png",
        "ClosedRing2-1.png"
        # Add more animation frame paths here
    ] 

    right_frames = [
        "Ring1-2.png",
        "Ring2-2.png",
        "Ring3-2.png",
        "ClosedRing1-2.png",
        "ClosedRing2-2.png"
    ]

    # Create a list to hold the Ring instances
    rings = []


    class CloudGenerator(renpy.Displayable):
        def __init__(self, small_count, medium_count, large_count, outer_count, over_count):
            renpy.Displayable.__init__(self)
  
            self.small_count = small_count
            self.medium_count = medium_count
            self.large_count = large_count
            self.outer_count = outer_count
            self.over_count = over_count

            self.small_y_options = range(128, 1080 - 128)  # Adjust this range as needed
            self.medium_y_options = range(128, 1080 - 128)  # Adjust this range as needed
            self.large_y_options = range(100, 900) 
            self.outer_y_options = [-50, 0, 50, 1030, 1000, 970]
            self.over_y_options = range(-50, 1130)

            self.clouds = []  # List to hold cloud instances
            self.cloud_size_images = {
                "small": [
                    "images/Clouds/White/cloud_shape3_5.png",
                    "images/Clouds/White/cloud_shape4_5.png",
                    "images/Clouds/White/cloud_shape2_5.png",
                    "images/Clouds/White/cloud_shape3_4.png",
                    "images/Clouds/White/cloud_shape3_3.png",
                    "images/Clouds/White/cloud_shape3_2.png",
                    "images/Clouds/White/cloud_shape3_1.png",
                    "images/Clouds/White/cloud_shape4_4.png",
                    "images/Clouds/White/cloud_shape4_3.png",
                    "images/Clouds/White/cloud_shape4_2.png",
                    "images/Clouds/White/cloud_shape4_1.png",
                    "images/Clouds/White/cloud_shape2_4.png",
                    "images/Clouds/White/cloud_shape2_3.png",
                    "images/Clouds/White/cloud_shape2_2.png",
                    "images/Clouds/White/cloud_shape2_1.png",
                    # Add more small cloud image paths here
                ],
                "medium": [
                    "images/Clouds/White/cloud_shape3_4.png",
                    "images/Clouds/White/cloud_shape3_3.png",
                    "images/Clouds/White/cloud_shape3_2.png",
                    "images/Clouds/White/cloud_shape3_1.png",
                    "images/Clouds/White/cloud_shape4_4.png",
                    "images/Clouds/White/cloud_shape4_3.png",
                    "images/Clouds/White/cloud_shape4_2.png",
                    "images/Clouds/White/cloud_shape4_1.png",
                    "images/Clouds/White/cloud_shape2_4.png",
                    "images/Clouds/White/cloud_shape2_3.png",
                    "images/Clouds/White/cloud_shape2_2.png",
                    "images/Clouds/White/cloud_shape2_1.png",
                    # Add more medium cloud image paths here
                ],
                "large": [
                    "images/Clouds/White/cloud_large1.png",
                    "images/Clouds/White/cloud_large2.png",
                    # Add more large cloud image paths here
                ]
            }    

            cloud_counts = [self.small_count, self.medium_count, self.large_count, self.outer_count, self.over_count]


            s_buffer_distance = 300
            m_buffer_distance = 300 
            l_buffer_distance = 500

            self.oldst = None

            # Initialize cloud instances based on provided counts
            for i in range(small_count):
                cloud_size = "small"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.1, 0.5])    

                cloud = {
                    "count_type" : "small",
                    "x": 1920 + 128 + i * s_buffer_distance,
                    "y": random.randint(128, 1080 - 128),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)    
                cloud["delay"] += 1

            for i in range(medium_count):
                cloud_size = "medium"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.7, 0.8, 0.9])    

                cloud = {
                    "count_type" : "medium",
                    "x": 1920 + 128 + i * m_buffer_distance,
                    "y": random.randint(128, 1080 - 128),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud) 
                cloud["delay"] += 1 

            for i in range(large_count):
                cloud_size = "large"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.7, 0.8, 0.9])    

                cloud = {
                    "count_type" : "large",
                    "x": 1920 + 128 + i * l_buffer_distance,
                    "y": random.randint(100, 900),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            for i in range(outer_count):
                cloud_size = "large"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.5, 0.6])    

                cloud = {
                    "count_type" : "outer",
                    "x": 1920 + 128 + i * l_buffer_distance,
                    "y": random.choice([-50, 0, 50, 1030, 1000, 970]),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            for i in range(over_count):
                cloud_size = "small"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.6, 0.7])    

                cloud = {
                    "count_type" : "over",
                    "x": 1920 + 128 + i * s_buffer_distance,
                    "y": random.randint(-50, 1130),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            self.oldst = None

        def render(self, width, height, st, at):
            r = renpy.Render(width, height)    

            if self.oldst is None:
                self.oldst = st            

            dtime = st - self.oldst
            self.oldst = st           

            for cloud in self.clouds:
                # Check if the cloud's delay has elapsed
                if cloud["delay"] <= 0:
                    cloud["x"] -= cloud["speed"] + player.velocity
                    if cloud["x"] < -600:
                        cloud["x"] = width + 300
                        if cloud["count_type"] == "small":
                            cloud["y"] = random.choice(self.small_y_options)
                        elif cloud["count_type"] == "medium":
                            cloud["y"] = random.choice(self.medium_y_options)
                        elif cloud["count_type"] == "large":
                            cloud["y"] = random.choice(self.large_y_options)
                        elif cloud["count_type"] == "outer":
                            cloud["y"] = random.choice(self.outer_y_options)
                        elif cloud["count_type"] == "over":
                            cloud["y"] = random.choice(self.over_y_options)
    

                    cloud_image = renpy.render(cloud["image"], width, height, st, at)
                    r.blit(cloud_image, (int(cloud["x"]), int(cloud["y"])))
                else:
                    cloud["delay"] -= at + st  # Reduce the delay

            renpy.redraw(self, 0)
            return r 

    class Player(renpy.Displayable):
        def __init__(self, x, y, speed):
            self.px = x
            self.py = y
            self.speed = speed
            self.width = 326
            self.height = 238
            self.pymin = 10
            self.pymax = 1080 - 238
            self.pxmin = 0
            self.pxmax = 960 - 326
            self.velocity = 0
            self.gauge = 0
            self.accelerate = False
            self.health = 4
            self.key_up = False
            self.key_down = False
            self.key_left = False
            self.key_right = False
            # Load the player image.
            self.player_image = Image("Sleigh1.png")    

            # Set player dimensions.
            self.width = 326
            self.height = 238
            self.player_last_fire_time = 0  # Initialize with 0
            self.projectiles = []
            return

        def move(self, dtime):
            if self.key_up:
                self.py -= self.speed * dtime
            if self.key_down:
                self.py += self.speed * dtime
            if self.key_left:
                self.velocity -= 1.5
                self.px -= (self.speed + self.gauge) * dtime
            if self.key_right:
                self.velocity += 1.5
                self.px += (self.speed + self.gauge) * dtime
                self.accelerate = True    

            # Sleigh's travel boundaries
            self.py = max(self.pymin, min(self.py, self.pymax))
            self.px = max(self.pxmin, min(self.px, self.pxmax))

        def fire_projectile(self, current_time):
            player_fire_rate = 15
            if current_time - self.player_last_fire_time >= 1.0 / player_fire_rate:
                basic_projectile = Projectile(
                    self.px + 300,  # Initial x position for the projectile
                    self.py + 95,   # Initial y position for the projectile
                    basic_weapon["projectile_image"],
                    basic_weapon["projectile_speed"],
                    basic_weapon["damage"],
                    basic_weapon["fire_rate"],
                    is_player=True  # Indicate that the projectile is fired by the player
                )
                self.projectiles.append(basic_projectile)
                self.player_last_fire_time = current_time  # Update the last fire time    

        def update_gauge(self):
            if self.velocity >= 56:
                self.gauge = 7
            elif self.velocity >= 48:
                self.gauge = 6
            elif self.velocity >= 40:
                self.gauge = 5
            elif self.velocity >= 32:
                self.gauge = 4
            elif self.velocity >= 24:
                self.gauge = 3
            elif self.velocity >= 16:
                self.gauge = 2
            elif self.velocity >= 8:
                self.gauge = 1
            else:
                self.gauge = 0    

        def overlaps_with(self, other):
            left = self.px
            right = self.px + self.width
            top = self.py
            bottom = self.py + self.height    

            other_left = other.x
            other_right = other.x + other.width
            other_top = other.y
            other_bottom = other.y + other.height    

            horizontal_hit = (left <= other_right) and (right >= other_left)
            vertical_hit = (top <= other_bottom) and (bottom >= other_top)    

            return horizontal_hit and vertical_hit

        def render(self, width, height, st, at):
            player_image = renpy.render(self.player, width, height, st, at)
            return player_image
    

        def event(self, ev, x, y, st):
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_LEFT:
                self.key_left = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_LEFT:
                self.key_left = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_RIGHT:
                self.key_right = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_RIGHT:
                self.key_right = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_UP:
                self.key_up = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_UP:
                self.key_up = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_DOWN:
                self.key_down = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_DOWN:
                self.key_down = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_SPACE:
                current_time = st  # Use the current time passed from the render method    

                # Define player fire rate here (e.g., fires once every 0.5 seconds)
                player_fire_rate = 15    

                # Check if enough time has passed since the last fire
                if current_time - self.player_last_fire_time >= 1.0 / player_fire_rate:
                    basic_projectile = Projectile(
                        self.px + 300,  # Initial x position for the projectile
                        self.py + 95,   # Initial y position for the projectile
                        basic_weapon["projectile_image"],
                        basic_weapon["projectile_speed"],
                        basic_weapon["damage"],
                        basic_weapon["fire_rate"],
                        is_player=True  # Indicate that the projectile is fired by the player
                    )
                    self.projectiles.append(basic_projectile)
                    self.player_last_fire_time = current_time  # Update the last fire time    

            # Update gauge rendering based on velocity
            if self.velocity >= 56:
                self.gauge = 7
            elif self.velocity >= 48:
                self.gauge = 6
            elif self.velocity >= 40:
                self.gauge = 5
            elif self.velocity >= 32:
                self.gauge = 4
            elif self.velocity >= 24:
                self.gauge = 3
            elif self.velocity >= 16:
                self.gauge = 2
            elif self.velocity >= 8:
                self.gauge = 1
            elif self.velocity == 1:
                self.gauge = 0 

            # Perform collision detection with rings
            for ring in sleighgame.rings:
                if not ring.collected and ring.overlaps_with(self):
                    ring.collected = True
                    ring.collect()  # Call the collect method
                    self.score += 1    

                if not ring.missed and ring.x < -ring.width:
                    ring.missed = True
                    self.misses += 1    

            # Update player's position based on key presses
            if self.key_up:
                self.py -= self.speed
            if self.key_down:
                self.py += self.speed
            if self.key_left:
                self.velocity -= 1.5
                self.px -= self.speed + self.gauge
            if self.key_right:
                self.velocity += 1.5
                self.px += self.speed + self.gauge
                self.accelerate = True    

            # Handle player's velocity boundaries
            if self.velocity >= 56:
                self.px += self.speed + 10
                if self.accelerate == False:
                    if self.px <= 700:
                        self.px = 700
            elif self.velocity >= 40:
                if self.accelerate == False:
                    if self.px <= 500:
                        self.px = 500
            elif self.velocity >= 24:
                if self.accelerate == False: 
                    if self.px <= 300:
                        self.px = 300
            elif self.velocity >= 8:
                if self.accelerate == False:
                    if self.px <= 100:
                        self.px = 100
            if self.velocity <= 1:
                self.velocity = 1
            if self.velocity >= 56:
                self.velocity = 56

            # Update player's position within screen boundaries
            if self.py < self.pymin:
                self.py = self.pymin
            if self.py > self.pymax:
                self.py = self.pymax
            if self.px < self.pxmin:
                self.px = self.pxmin
            if self.px > self.pxmax:
                self.px = self.pxmax


    class Ring(renpy.Displayable):
        def __init__(self, x, y, images_left, images_right, ringspeed, ring_type, buffer):
            renpy.Displayable.__init__(self)
            self.x = x
            self.y = y
            self.height = 322
            self.width = 144
            self.images_left = [Image(image) for image in left_frames]
            self.images_right = [Image(image) for image in right_frames]
            self.frame_index = 0
            self.collected = False
            self.missed = False
            self.ringspeed = 100
            self.ring_type = ring_type
            self.buffer = buffer

            self.right_half_hit = False
            self.ring_instance = None  # Initialize the ring_instance attribute

            # Add frame indices for different stages of animation
            self.animation_frames = [0, 1, 2, 3, 4]
            self.current_frame = self.animation_frames[0]

            # Lists of image paths for each frame
            self.left_frames = images_left
            self.right_frames = images_right

            # New attributes for the moving behavior
            self.moving_y_range = 200  # Adjust the range as needed
            self.moving_direction = 1  # Start moving down
            self.y_initial = y  # Define y_initial here

        def overlaps_with(self, other):
            ring_left = self.x + self.width * 1.2  # Use the left edge of the ring
            sleigh_right = other.px + other.width  # Calculate the right edge x-coordinate of the sleigh        

            horizontal_hit = (ring_left <= sleigh_right) and (ring_left + self.width >= sleigh_right)  # Check for overlap at right side
            vertical_hit = (self.y <= other.py + other.height) and (self.y + self.height >= other.py)        

            if horizontal_hit and vertical_hit:
                self.collected = True 
                return True        

            return False

        def render_left_half(self, width, height, st, at):
            if self.missed or self.x < -self.width:
                return None    

            # Determine the current frame index based on animation status and type
            if self.collected:
                current_frame_index = 2
            elif self.ring_type == "Moving":
                current_frame_index = 1
            elif self.ring_type == "Static":
                current_frame_index = 0
            elif self.ring_type == "ClosedMoving":
                current_frame_index = 4
            elif self.ring_type == "ClosedStatic":
                current_frame_index = 3

            # Render the appropriate frame based on the current animation frame
            ring_image_left = renpy.render(self.images_left[current_frame_index], width, height, st, at)
            r = renpy.Render(width, height)
            r.blit(ring_image_left, (int(self.x), int(self.y)))    

            return r    

        def render_right_half(self, width, height, st, at):
            if self.missed or self.x < -self.width:
                return None    

            # Determine the current frame index based on animation status and type
            if self.collected:
                current_frame_index = 2
            elif self.ring_type == "Moving":
                current_frame_index = 1
            elif self.ring_type == "Static":
                current_frame_index = 0
            elif self.ring_type == "ClosedMoving":
                current_frame_index = 4
            elif self.ring_type == "ClosedStatic":
                current_frame_index = 3

            # Render the appropriate frame based on the current animation frame
            ring_image_right = renpy.render(self.images_right[current_frame_index], width, height, st, at)

            r = renpy.Render(width, height)
            r.blit(ring_image_right, (int(self.x) + self.width // 2 - 75, int(self.y)))    

            return r

        def collect(self):
            self.collected = True
            renpy.play("audio/bells.mp3", "sound")



        def update(self, dtime):

            if self.collected or self.missed:
                sleighgame.totalrings += 1

            self.x -= self.ringspeed * dtime + player.velocity  

            if self.ring_type == "Moving" or self.ring_type == "ClosedMoving":
                self.y += self.moving_direction * 200 * dtime  # Adjust the speed as needed    

                if self.y <= self.y_initial - self.moving_y_range:
                    self.moving_direction = 1
                elif self.y >= self.y_initial + self.moving_y_range:
                    self.moving_direction = -1


    class Projectile(renpy.Displayable):
        def __init__(self, x, y, image_path, speed, width, height, is_player=False):

            renpy.Displayable.__init__(self)
            self.x = x
            self.y = y
            self.image_path = image_path
            self.image = renpy.display.im.Image(image_path)  # Load the image
            self.speed = speed
            self.width = width
            self.height = height    
            self.is_player = is_player


            # Load projectile images (animation frames if multiple paths are provided)
            if isinstance(self.image_path, list):
                self.animation_frames = [Image(path) for path in image_paths]
                self.frame_index = 0  # Start with the first frame
            else:
                self.image = Image(image_path)          

        def render(self, width, height, st, at):
            if self.is_player:
                self.x += self.speed * dtime
            else:
                self.x -= self.speed * dtime   

            # Render the projectile (animation or single image)
            if hasattr(self, "animation_frames"):
                self.frame_index = (self.frame_index + 1) % len(self.animation_frames)
                current_frame = self.animation_frames[self.frame_index]
                projectile_image = renpy.render(current_frame, width, height, st, at)
            else:
                projectile_image = renpy.render(self.image, width, height, st, at)            

            r.blit(projectile_image, (int(self.x), int(self.y)))

    class SleighShooter(renpy.Displayable):
        def __init__(self):

            renpy.Displayable.__init__(self)

            self.score = 0
            self.misses = 0

            self.oldst = None

            self.lose = False

            self.enemies = []

            self.active_enemy_projectiles = []  # List to store active enemy projectiles
            

            # Initialize the rings with their attributes
            rings = initialize_rings(ring_count=20, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="Static", buffer_range=(700, 2000))
            rings += initialize_rings(ring_count=5, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="Moving", buffer_range=(700, 2000))            

            # Shuffle the rings list
            import random
            random.shuffle(rings)    

            # Generate rings with proper spacing
            current_x = renpy.config.screen_width  # Start offscreen to the right
            for ring in rings:
                ring.x = current_x
                current_x += ring.buffer + ring.width    

            self.rings = rings  # Assign the shuffled rings back to the class attribute    

            self.totalrings = 0
            return

        def calculate_dtime(self, st):
            if self.oldst is None:
                self.oldst = st    

            dtime = st - self.oldst
            self.oldst = st
            return dtime

        # Draws the screen
        def render(self, width, height, st, at):

            r = renpy.Render(width, height)

            # Figure out the time elapsed since the previous frame.
            if self.oldst is None:
                self.oldst = st

            dtime = st - self.oldst
            self.oldst = st

            # Update and render rings
            for ring in self.rings:
                ring.update(dtime)    

                # Render the left half of the ring below the sleigh
                ring_left_half_render = ring.render_left_half(width, height, st, at)
                if ring_left_half_render:
                    r.blit(ring_left_half_render, (0, 0))

            # Render the player's image directly using renpy.render
            player_image = renpy.render(player.player_image, width, height, st, at)
            r.blit(player_image, (int(player.px), int(player.py)))

            # Update and render projectiles
            for projectile in player.projectiles:
                projectile.x += projectile.speed * dtime
                projectile_image = renpy.render(projectile.image, width, height, st, at)
                r.blit(projectile_image, (int(projectile.x), int(projectile.y)))    

                # Remove projectiles that go off-screen
                if projectile.x > width:
                    player.projectiles.remove(projectile)


            # Render the right half of the ring above the sleigh
            for ring in self.rings:
                ring_right_half_render = ring.render_right_half(width, height, st, at)
                if ring_right_half_render:
                    r.blit(ring_right_half_render, (0, 0))


            # Check for collisions with rings
            for ring in self.rings:
                if not ring.collected and ring.overlaps_with(player):
                    ring.collected = True
                    ring.collect()  # Call the collect method
                    self.score += 1    

                if not ring.missed and ring.x < -ring.width:
                    ring.missed = True
                    self.misses += 1

            # Check for a loss.
            if player.health == 0:
                self.lose = True

                renpy.timeout(0)

            # Ask that we be re-rendered ASAP, so we can show the next
            # frame.
            renpy.redraw(self, 0)

            # Return the Render object.
            return r

        # Handles events.
        def event(self, ev, x, y, st):
            # Pass the events to the Player instance
            player.event(ev, x, y, st)

            # Ensure the screen updates.
            renpy.restart_interaction()            

            # If the player loses, return it.
            if self.lose:
                return self.lose
            else:
                raise renpy.IgnoreEvent()

    def initialize_rings(ring_count, ring_width, images_left, images_right, ring_type, buffer_range=(600, 800)):
        rings = []
        for i in range(ring_count):
            buffer = random.uniform(*buffer_range)
            ring_x = renpy.config.screen_width + buffer + i * ring_width  # Start offscreen to the right
            ring_y = random.randint(128, renpy.config.screen_height - 300)
            
            if ring_type == "Moving":
                ring = Ring(ring_x, ring_y, images_left, images_right, ringspeed=100, ring_type="Moving", buffer=buffer)
            else:
                ring = Ring(ring_x, ring_y, images_left, images_right, ringspeed=100, ring_type="Static", buffer=buffer)
                        
            rings.append(ring)
            
        return rings

default player = Player(x=20, y=500, speed=15)
default sleighgame = SleighShooter()
default cloudcover = CloudGenerator(small_count=20, medium_count=10, large_count=5, outer_count=0, over_count=0)
default cloudfore = CloudGenerator(small_count=0, medium_count=0, large_count=0, outer_count=8, over_count=10)

screen sleigh_ride():

    add Image("bg Sky.png")

    add cloudcover


    add cloudfore

    # Add the left side of the rings below the sleigh
    for ring in rings:
        add ring.render_left_half(width, height, st, at)

    add sleighgame

    # Add the right side of the rings above the sleigh
    for ring in rings:
        add ring.render_right_half(width, height, st, at)

    # Add a text element to display the score at the top
    vbox:
        text "Score: [sleighgame.score]" style "scoreboard"


    if player.health == 1:
        vbox:
            add Image("images/SleighShield4.png")
            xpos 30 ypos 10
    elif player.health == 2:
        vbox:
            add Image("images/SleighShield3.png")
            xpos 30 ypos 10
    elif player.health == 3:
        vbox:
            add Image("images/SleighShield2.png")
            xpos 30 ypos 10
    elif player.health == 4:
        vbox:
            add Image("images/SleighShield1.png")
            xpos 30 ypos 10

    if player.gauge == 0:
        vbox:
            add Image("images/Gauge/gauge0.png")
            xpos 20 ypos 120
    elif player.gauge == 1:
        vbox:
            add Image("images/Gauge/gauge1.png")
            xpos 20 ypos 120
    elif player.gauge == 2:
        vbox:
            add Image("images/Gauge/gauge2.png")
            xpos 20 ypos 120
    elif player.gauge == 3:
        vbox:
            add Image("images/Gauge/gauge3.png")
            xpos 20 ypos 120
    elif player.gauge == 4:
        vbox:
            add Image("images/Gauge/gauge4.png")
            xpos 20 ypos 120
    elif player.gauge == 5:
        vbox:
            add Image("images/Gauge/gauge5.png")
            xpos 20 ypos 120
    elif player.gauge == 6:
        vbox:
            add Image("images/Gauge/gauge6.png")
            xpos 20 ypos 120
    elif player.gauge == 7:
        vbox:
            add Image("images/Gauge/gauge7.png")
            xpos 20 ypos 120
The code feels like much less of a disaster, now.

thexerox123
Regular
Posts: 134
Joined: Fri Jan 20, 2023 3:21 pm
itch: thexerox123
Contact:

Re: Shooter Minigame Help - Seeking Refactoring/Organization Advice

#13 Post by thexerox123 »

Sorry for spamming this thread, but I've made good progress today! Got the logic and collisions for "closed" rings working!

Image

Code: Select all

style scoreboard:
    color "#8B0000"  # Set text color to red
    size 50          # Set font size to 50
    xpos 880          # Set x position
    ypos 20          # Set y position


default player = Player(x=20, y=500, speed=15)  # Pass this player instance to sleighgame
default sleighgame = SleighShooter(player=player)
default cloudcover = CloudGenerator(small_count=20, medium_count=10, large_count=5, outer_count=0, over_count=0)
default cloudfore = CloudGenerator(small_count=0, medium_count=0, large_count=0, outer_count=8, over_count=10)

init python:
    import random
    import pygame 



    weapon_variables = {
        "basic": {
            "damage": 10,
            "fire_rate": 0.5,
            "projectile_image": "images/Projectiles/Basic.png",
            "projectile_speed": 800
        },    

        "heavy": {
            "damage": 20,
            "fire_rate": 1.0,
            "projectile_image": "heavy_projectile.png",
            "projectile_speed": 300
        },
        # Define more weapon presets
    }

    basic_weapon = weapon_variables["basic"]

    left_frames = [
        "Ring1-1.png",
        "Ring2-1.png",
        "Ring3-1.png",
        "ClosedRing1-1.png",
        "ClosedRing2-1.png"
        # Add more animation frame paths here
    ] 

    right_frames = [
        "Ring1-2.png",
        "Ring2-2.png",
        "Ring3-2.png",
        "ClosedRing1-2.png",
        "ClosedRing2-2.png"
    ]

    # Create a list to hold the Ring instances
    rings = []


    class CloudGenerator(renpy.Displayable):
        def __init__(self, small_count, medium_count, large_count, outer_count, over_count):
            renpy.Displayable.__init__(self)
  
            self.small_count = small_count
            self.medium_count = medium_count
            self.large_count = large_count
            self.outer_count = outer_count
            self.over_count = over_count

            self.small_y_options = range(128, 1080 - 128)  # Adjust this range as needed
            self.medium_y_options = range(128, 1080 - 128)  # Adjust this range as needed
            self.large_y_options = range(100, 900) 
            self.outer_y_options = [-50, 0, 50, 1030, 1000, 970]
            self.over_y_options = range(-50, 1130)

            self.clouds = []  # List to hold cloud instances
            self.cloud_size_images = {
                "small": [
                    "images/Clouds/White/cloud_shape3_5.png",
                    "images/Clouds/White/cloud_shape4_5.png",
                    "images/Clouds/White/cloud_shape2_5.png",
                    "images/Clouds/White/cloud_shape3_4.png",
                    "images/Clouds/White/cloud_shape3_3.png",
                    "images/Clouds/White/cloud_shape3_2.png",
                    "images/Clouds/White/cloud_shape3_1.png",
                    "images/Clouds/White/cloud_shape4_4.png",
                    "images/Clouds/White/cloud_shape4_3.png",
                    "images/Clouds/White/cloud_shape4_2.png",
                    "images/Clouds/White/cloud_shape4_1.png",
                    "images/Clouds/White/cloud_shape2_4.png",
                    "images/Clouds/White/cloud_shape2_3.png",
                    "images/Clouds/White/cloud_shape2_2.png",
                    "images/Clouds/White/cloud_shape2_1.png",
                    # Add more small cloud image paths here
                ],
                "medium": [
                    "images/Clouds/White/cloud_shape3_4.png",
                    "images/Clouds/White/cloud_shape3_3.png",
                    "images/Clouds/White/cloud_shape3_2.png",
                    "images/Clouds/White/cloud_shape3_1.png",
                    "images/Clouds/White/cloud_shape4_4.png",
                    "images/Clouds/White/cloud_shape4_3.png",
                    "images/Clouds/White/cloud_shape4_2.png",
                    "images/Clouds/White/cloud_shape4_1.png",
                    "images/Clouds/White/cloud_shape2_4.png",
                    "images/Clouds/White/cloud_shape2_3.png",
                    "images/Clouds/White/cloud_shape2_2.png",
                    "images/Clouds/White/cloud_shape2_1.png",
                    # Add more medium cloud image paths here
                ],
                "large": [
                    "images/Clouds/White/cloud_large1.png",
                    "images/Clouds/White/cloud_large2.png",
                    # Add more large cloud image paths here
                ]
            }    

            cloud_counts = [self.small_count, self.medium_count, self.large_count, self.outer_count, self.over_count]


            s_buffer_distance = 300
            m_buffer_distance = 300 
            l_buffer_distance = 500

            self.oldst = None

            # Initialize cloud instances based on provided counts
            for i in range(small_count):
                cloud_size = "small"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.1, 0.5])    

                cloud = {
                    "count_type" : "small",
                    "x": 1920 + 128 + i * s_buffer_distance,
                    "y": random.randint(128, 1080 - 128),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)    
                cloud["delay"] += 1

            for i in range(medium_count):
                cloud_size = "medium"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.7, 0.8, 0.9])    

                cloud = {
                    "count_type" : "medium",
                    "x": 1920 + 128 + i * m_buffer_distance,
                    "y": random.randint(128, 1080 - 128),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud) 
                cloud["delay"] += 1 

            for i in range(large_count):
                cloud_size = "large"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.7, 0.8, 0.9])    

                cloud = {
                    "count_type" : "large",
                    "x": 1920 + 128 + i * l_buffer_distance,
                    "y": random.randint(100, 900),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            for i in range(outer_count):
                cloud_size = "large"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.5, 0.6])    

                cloud = {
                    "count_type" : "outer",
                    "x": 1920 + 128 + i * l_buffer_distance,
                    "y": random.choice([-50, 0, 50, 1030, 1000, 970]),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            for i in range(over_count):
                cloud_size = "small"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.6, 0.7])    

                cloud = {
                    "count_type" : "over",
                    "x": 1920 + 128 + i * s_buffer_distance,
                    "y": random.randint(-50, 1130),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            self.oldst = None

        def render(self, width, height, st, at):
            r = renpy.Render(width, height)    

            if self.oldst is None:
                self.oldst = st            

            dtime = st - self.oldst
            self.oldst = st           

            for cloud in self.clouds:
                # Check if the cloud's delay has elapsed
                if cloud["delay"] <= 0:
                    cloud["x"] -= cloud["speed"] + player.velocity
                    if cloud["x"] < -600:
                        cloud["x"] = width + 300
                        if cloud["count_type"] == "small":
                            cloud["y"] = random.choice(self.small_y_options)
                        elif cloud["count_type"] == "medium":
                            cloud["y"] = random.choice(self.medium_y_options)
                        elif cloud["count_type"] == "large":
                            cloud["y"] = random.choice(self.large_y_options)
                        elif cloud["count_type"] == "outer":
                            cloud["y"] = random.choice(self.outer_y_options)
                        elif cloud["count_type"] == "over":
                            cloud["y"] = random.choice(self.over_y_options)
    

                    cloud_image = renpy.render(cloud["image"], width, height, st, at)
                    r.blit(cloud_image, (int(cloud["x"]), int(cloud["y"])))
                else:
                    cloud["delay"] -= at + st  # Reduce the delay

            renpy.redraw(self, 0)
            return r 

    class Player(renpy.Displayable):
        def __init__(self, x, y, speed):
            self.px = x
            self.py = y
            self.speed = speed
            self.width = 326
            self.height = 238
            self.pymin = 10
            self.pymax = 1080 - 238
            self.pxmin = 0
            self.pxmax = 960 - 326
            self.velocity = 0
            self.gauge = 0
            self.accelerate = False
            self.health = 4
            self.key_up = False
            self.key_down = False
            self.key_left = False
            self.key_right = False
            # Load the player image.
            self.player_image = Image("Sleigh1.png")    

            # Set player dimensions.
            self.width = 326
            self.height = 238
            self.player_last_fire_time = 0  # Initialize with 0
            self.projectiles = []
            return

        def move(self, dtime):
            if self.key_up:
                self.py -= self.speed * dtime
            if self.key_down:
                self.py += self.speed * dtime
            if self.key_left:
                self.velocity -= 1.5
                self.px -= (self.speed + self.gauge) * dtime
            if self.key_right:
                self.velocity += 1.5
                self.px += (self.speed + self.gauge) * dtime
                self.accelerate = True    

            # Sleigh's travel boundaries
            self.py = max(self.pymin, min(self.py, self.pymax))
            self.px = max(self.pxmin, min(self.px, self.pxmax))

        def fire_projectile(self, current_time):
            player_fire_rate = 15
            if current_time - self.player_last_fire_time >= 1.0 / player_fire_rate:
                basic_projectile = Projectile(
                    self.px + 300,  # Initial x position for the projectile
                    self.py + 95,   # Initial y position for the projectile
                    basic_weapon["projectile_image"],
                    basic_weapon["projectile_speed"],
                    basic_weapon["damage"],
                    basic_weapon["fire_rate"],
                    is_player=True  # Indicate that the projectile is fired by the player
                )
                self.projectiles.append(basic_projectile)
                self.player_last_fire_time = current_time  # Update the last fire time    

        def update_gauge(self):
            if self.velocity >= 56:
                self.gauge = 7
            elif self.velocity >= 48:
                self.gauge = 6
            elif self.velocity >= 40:
                self.gauge = 5
            elif self.velocity >= 32:
                self.gauge = 4
            elif self.velocity >= 24:
                self.gauge = 3
            elif self.velocity >= 16:
                self.gauge = 2
            elif self.velocity >= 8:
                self.gauge = 1
            else:
                self.gauge = 0    

        def overlaps_with(self, other):
            left = self.px
            right = self.px + self.width
            top = self.py
            bottom = self.py + self.height    

            other_left = other.x
            other_right = other.x + other.width
            other_top = other.y
            other_bottom = other.y + other.height    

            horizontal_hit = (left <= other_right) and (right >= other_left)
            vertical_hit = (top <= other_bottom) and (bottom >= other_top)    

            return horizontal_hit and vertical_hit

        def render(self, width, height, st, at):
            player_image = renpy.render(self.player, width, height, st, at)
            r.blit(player_image, (int(self.px), int(self.py)))

        def update(self):
            # Call the render method of the SleighShooter class
            sleigh_shooter.render(self.projectiles)

        def event(self, ev, x, y, st):
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_LEFT:
                self.key_left = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_LEFT:
                self.key_left = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_RIGHT:
                self.key_right = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_RIGHT:
                self.key_right = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_UP:
                self.key_up = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_UP:
                self.key_up = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_DOWN:
                self.key_down = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_DOWN:
                self.key_down = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_SPACE:
                current_time = st  # Use the current time passed from the render method    

                # Define player fire rate here (e.g., fires once every 0.5 seconds)
                player_fire_rate = 15    

                # Check if enough time has passed since the last fire
                if current_time - self.player_last_fire_time >= 1.0 / player_fire_rate:
                    basic_projectile = Projectile(
                        self.px + 300,  # Initial x position for the projectile
                        self.py + 95,   # Initial y position for the projectile
                        basic_weapon["projectile_image"],
                        basic_weapon["projectile_speed"],
                        basic_weapon["damage"],
                        basic_weapon["fire_rate"],
                        is_player=True  # Indicate that the projectile is fired by the player
                    )
                    self.projectiles.append(basic_projectile)
                    self.player_last_fire_time = current_time  # Update the last fire time    

            # Update gauge rendering based on velocity
            if self.velocity >= 56:
                self.gauge = 7
            elif self.velocity >= 48:
                self.gauge = 6
            elif self.velocity >= 40:
                self.gauge = 5
            elif self.velocity >= 32:
                self.gauge = 4
            elif self.velocity >= 24:
                self.gauge = 3
            elif self.velocity >= 16:
                self.gauge = 2
            elif self.velocity >= 8:
                self.gauge = 1
            elif self.velocity == 1:
                self.gauge = 0 

            # Update player's position based on key presses
            if self.key_up:
                self.py -= self.speed
            if self.key_down:
                self.py += self.speed
            if self.key_left:
                self.velocity -= 1.5
                self.px -= self.speed + self.gauge
            if self.key_right:
                self.velocity += 1.5
                self.px += self.speed + self.gauge
                self.accelerate = True    

            # Handle player's velocity boundaries
            if self.velocity >= 56:
                self.px += self.speed + 10
                if self.accelerate == False:
                    if self.px <= 700:
                        self.px = 700
            elif self.velocity >= 40:
                if self.accelerate == False:
                    if self.px <= 500:
                        self.px = 500
            elif self.velocity >= 24:
                if self.accelerate == False: 
                    if self.px <= 300:
                        self.px = 300
            elif self.velocity >= 8:
                if self.accelerate == False:
                    if self.px <= 100:
                        self.px = 100
            if self.velocity <= 1:
                self.velocity = 1
            if self.velocity >= 56:
                self.velocity = 56

            # Update player's position within screen boundaries
            if self.py < self.pymin:
                self.py = self.pymin
            if self.py > self.pymax:
                self.py = self.pymax
            if self.px < self.pxmin:
                self.px = self.pxmin
            if self.px > self.pxmax:
                self.px = self.pxmax


    class Ring(renpy.Displayable):
        def __init__(self, x, y, images_left, images_right, ringspeed, ring_type, buffer):
            renpy.Displayable.__init__(self)
            self.x = x
            self.y = y
            self.height = 322
            self.width = 144
            self.images_left = [Image(image) for image in left_frames]
            self.images_right = [Image(image) for image in right_frames]
            self.frame_index = 0
            self.collected = False
            self.missed = False
            self.ringspeed = 100
            self.ring_type = ring_type
            self.buffer = buffer

            self.hit_by_projectile = False
            self.hit_by_player = False

            self.right_half_hit = False
            self.ring_instance = None  # Initialize the ring_instance attribute

            # Add frame indices for different stages of animation
            self.animation_frames = [0, 1, 2, 3, 4]
            self.current_frame = self.animation_frames[0]

            # Lists of image paths for each frame
            self.left_frames = images_left
            self.right_frames = images_right

            # New attributes for the moving behavior
            self.moving_y_range = 200  # Adjust the range as needed
            self.moving_direction = 1  # Start moving down
            self.y_initial = y  # Define y_initial here

        def collides_with_player(self, player):
            ring_left = self.x + self.width * 1.2
            sleigh_right = player.px + player.width
            horizontal_hit = (ring_left <= sleigh_right) and (ring_left + self.width >= sleigh_right)
            vertical_hit = (self.y <= player.py + player.height) and (self.y + self.height >= player.py)
            return horizontal_hit and vertical_hit

        def collides_with_projectile(self, projectile):
            if projectile is None:
                return False
            proj_x, proj_y = projectile.get_position()
            proj_width, proj_height = projectile.get_dimensions()
            proj_left = proj_x
            proj_right = proj_x + proj_width
            proj_top = proj_y
            proj_bottom = proj_y + proj_height
            
            horizontal_hit = (proj_left <= self.x + self.width) and (proj_right >= self.x)
            vertical_hit = (proj_top <= self.y + self.height) and (proj_bottom >= self.y)
            
            return horizontal_hit and vertical_hit

        def render_left_half(self, width, height, st, at):
            if self.missed or self.x < -self.width:
                return None    

            # Determine the current frame index based on animation status and type
            if self.ring_type == "Moving":
                current_frame_index = 1
            elif self.ring_type == "Static":
                current_frame_index = 0
            elif self.ring_type == "ClosedMoving":
                if self.hit_by_projectile == False:
                    current_frame_index = 4
                else:
                    current_frame_index = 1
            elif self.ring_type == "ClosedStatic":
                if self.hit_by_projectile == False:
                    current_frame_index = 3
                else:
                    current_frame_index = 0

            if self.collected:
                current_frame_index = 2

            # Render the appropriate frame based on the current animation frame
            ring_image_left = renpy.render(self.images_left[current_frame_index], width, height, st, at)
            r = renpy.Render(width, height)
            r.blit(ring_image_left, (int(self.x), int(self.y)))    

            return r    

        def render_right_half(self, width, height, st, at):
            if self.missed or self.x < -self.width:
                return None    

            # Determine the current frame index based on animation status and type
            if self.ring_type == "Moving":
                current_frame_index = 1
            elif self.ring_type == "Static":
                current_frame_index = 0
            elif self.ring_type == "ClosedMoving":
                if self.hit_by_projectile == False:
                    current_frame_index = 4
                else:
                    current_frame_index = 1
            elif self.ring_type == "ClosedStatic":
                if self.hit_by_projectile == False:
                    current_frame_index = 3
                else:
                    current_frame_index = 0
            if self.collected:
                current_frame_index = 2
            # Render the appropriate frame based on the current animation frame
            ring_image_right = renpy.render(self.images_right[current_frame_index], width, height, st, at)

            r = renpy.Render(width, height)
            r.blit(ring_image_right, (int(self.x) + self.width // 2 - 72.5, int(self.y)))    

            return r

        def collect(self):
            self.collected = True
            renpy.play("audio/bells.mp3", "sound")



        def update(self, dtime):

            if self.collected or self.missed:
                sleighgame.totalrings += 1

            self.x -= self.ringspeed * dtime + player.velocity  

            if self.ring_type == "Moving" or self.ring_type == "ClosedMoving":
                self.y += self.moving_direction * 200 * dtime  # Adjust the speed as needed    

                if self.y <= self.y_initial - self.moving_y_range:
                    self.moving_direction = 1
                elif self.y >= self.y_initial + self.moving_y_range:
                    self.moving_direction = -1


    class Projectile(renpy.Displayable):
        def __init__(self, x, y, image_path, speed, width, height, is_player=False):

            renpy.Displayable.__init__(self)
            self.x = x
            self.y = y
            self.image_path = image_path
            self.image = renpy.display.im.Image(image_path)  # Load the image
            self.speed = speed
            self.width = width
            self.height = height    
            self.is_player = is_player


            # Load projectile images (animation frames if multiple paths are provided)
            if isinstance(self.image_path, list):
                self.animation_frames = [Image(path) for path in image_paths]
                self.frame_index = 0  # Start with the first frame
            else:
                self.image = Image(image_path)   

        def get_position(self):
            return self.x, self.y    

        def get_dimensions(self):
            return self.width, self.height

        def render(self, width, height, st, at):
            if self.is_player:
                self.x += self.speed * dtime
            else:
                self.x -= self.speed * dtime   

            # Render the projectile (animation or single image)
            if hasattr(self, "animation_frames"):
                self.frame_index = (self.frame_index + 1) % len(self.animation_frames)
                current_frame = self.animation_frames[self.frame_index]
                projectile_image = renpy.render(current_frame, width, height, st, at)
            else:
                projectile_image = renpy.render(self.image, width, height, st, at)            

            r.blit(projectile_image, (int(self.x), int(self.y)))
            return

    class SleighShooter(renpy.Displayable):
        def __init__(self, player):

            renpy.Displayable.__init__(self)

            self.score = 0
            self.misses = 0

            self.oldst = None

            self.lose = False

            self.enemies = []

            self.player = player

            self.active_enemy_projectiles = []  # List to store active enemy projectiles
            

            # Initialize the rings with their attributes
            rings = initialize_rings(ring_count=20, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="Static", buffer_range=(700, 2000))
            rings += initialize_rings(ring_count=5, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="Moving", buffer_range=(700, 2000))            
            rings += initialize_rings(ring_count=5, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="ClosedStatic", buffer_range=(700, 2000))
            rings += initialize_rings(ring_count=5, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="ClosedMoving", buffer_range=(700, 2000))  

            # Shuffle the rings list
            import random
            random.shuffle(rings)    

            # Generate rings with proper spacing
            current_x = renpy.config.screen_width  # Start offscreen to the right
            for ring in rings:
                ring.x = current_x
                current_x += ring.buffer + ring.width    

            self.rings = rings  # Assign the shuffled rings back to the class attribute    

            self.totalrings = 0
            return

        def calculate_dtime(self, st):
            if self.oldst is None:
                self.oldst = st    

            dtime = st - self.oldst
            self.oldst = st
            return dtime

        # Draws the screen
        def render(self, width, height, st, at):

            r = renpy.Render(width, height)

            # Figure out the time elapsed since the previous frame.
            if self.oldst is None:
                self.oldst = st

            dtime = st - self.oldst
            self.oldst = st

            # Collect the render objects for left and right halves of rings
            left_ring_images = []
            right_ring_images = []
            projectiles_to_remove = []

            # Update and render rings
            for ring in self.rings:
                ring.update(dtime)    

                # Render the left half of the ring below the sleigh
                ring_left_half_render = ring.render_left_half(width, height, st, at)
                if ring_left_half_render:
                    r.blit(ring_left_half_render, (0, 0))

            # Render the player's image directly using renpy.render
            player_image = renpy.render(player.player_image, width, height, st, at)
            r.blit(player_image, (int(player.px), int(player.py)))

            # Update and render projectiles
            for projectile in self.player.projectiles:
                projectile.x += projectile.speed * dtime
                projectile_image = renpy.render(projectile.image, width, height, st, at)
                r.blit(projectile_image, (int(projectile.x), int(projectile.y)))    

                # Remove projectiles that go off-screen
                if projectile.x > width:
                    self.player.projectiles.remove(projectile)

            for ring in self.rings:
                ring_right_half_render = ring.render_right_half(width, height, st, at)
                if ring_right_half_render:
                    r.blit(ring_right_half_render, (0, 0))            

                if ring.collected == False and ring.collides_with_player(player):
                    # Collision with player logic            
                    if ring.ring_type == "ClosedStatic" or ring.ring_type == "ClosedMoving":
                        if ring.hit_by_projectile == False and ring.hit_by_player == False:
                            player.health -= 1 
                            ring.hit_by_player = True 
                            renpy.play("audio/Crash1.mp3", "sound")  # Play collision sound

                        elif ring.hit_by_projectile == True:
                            ring.collected = True 
                            ring.collect()
                            self.score += 1

                    elif ring.ring_type == "Static" or ring.ring_type == "Moving":
                        ring.collected = True
                        ring.collect()  # Call the collect method
                        self.score += 1    

                for projectile in player.projectiles:
                    if ring.collected == False and ring.collides_with_projectile(projectile):
                        # Collision with projectile logic            
                        if ring.ring_type == "ClosedStatic" or ring.ring_type == "ClosedMoving":
                            if ring.hit_by_projectile == False:
                                ring.hit_by_projectile = True
                                renpy.play("audio/RingDown.mp3", "sound")  # Play collision sound
                                projectiles_to_remove.append(projectile)



                if not ring.missed and ring.x < -ring.width:
                    ring.missed = True
                    self.misses += 1

            # Remove projectiles outside the loop
            for projectile in projectiles_to_remove:
                self.player.projectiles.remove(projectile)

            # Check for a loss.
            if player.health == 0:
                self.lose = True

                renpy.timeout(0)

            # Ask that we be re-rendered ASAP, so we can show the next
            # frame.
            renpy.redraw(self, 0)

            # Return the Render object.
            return r

        # Handles events.
        def event(self, ev, x, y, st):
            # Pass the events to the Player instance
            player.event(ev, x, y, st)

            # Ensure the screen updates.
            renpy.restart_interaction()            

            # If the player loses, return it.
            if self.lose:
                return self.lose
            else:
                raise renpy.IgnoreEvent()

    def initialize_rings(ring_count, ring_width, images_left, images_right, ring_type, buffer_range=(600, 800)):
        rings = []
        for i in range(ring_count):
            buffer = random.uniform(*buffer_range)
            ring_x = renpy.config.screen_width + buffer + i * ring_width  # Start offscreen to the right
            ring_y = random.randint(128, renpy.config.screen_height - 300)
            
            if ring_type == "Moving":
                ring = Ring(ring_x, ring_y, images_left, images_right, ringspeed=100, ring_type="Moving", buffer=buffer)
            elif ring_type == "Static":
                ring = Ring(ring_x, ring_y, images_left, images_right, ringspeed=100, ring_type="Static", buffer=buffer)
            elif ring_type == "ClosedMoving":
                ring = Ring(ring_x, ring_y, images_left, images_right, ringspeed=100, ring_type="ClosedMoving", buffer=buffer)
            elif ring_type == "ClosedStatic":
                ring = Ring(ring_x, ring_y, images_left, images_right, ringspeed=100, ring_type="ClosedStatic", buffer=buffer)

            rings.append(ring) 
        return rings

screen sleigh_ride():

    add Image("bg Sky.png")

    add cloudcover


    add cloudfore

    # Add the left side of the rings below the sleigh
    for ring in rings:
        add ring.render_left_half(width, height, st, at)

    add sleighgame

    # Add the right side of the rings above the sleigh
    for ring in rings:
        add ring.render_right_half(width, height, st, at)

    # Add a text element to display the score at the top
    vbox:
        text "Score: [sleighgame.score]" style "scoreboard"


    if player.health == 1:
        vbox:
            add Image("images/SleighShield4.png")
            xpos 30 ypos 10
    elif player.health == 2:
        vbox:
            add Image("images/SleighShield3.png")
            xpos 30 ypos 10
    elif player.health == 3:
        vbox:
            add Image("images/SleighShield2.png")
            xpos 30 ypos 10
    elif player.health == 4:
        vbox:
            add Image("images/SleighShield1.png")
            xpos 30 ypos 10

    if player.gauge == 0:
        vbox:
            add Image("images/Gauge/gauge0.png")
            xpos 20 ypos 120
    elif player.gauge == 1:
        vbox:
            add Image("images/Gauge/gauge1.png")
            xpos 20 ypos 120
    elif player.gauge == 2:
        vbox:
            add Image("images/Gauge/gauge2.png")
            xpos 20 ypos 120
    elif player.gauge == 3:
        vbox:
            add Image("images/Gauge/gauge3.png")
            xpos 20 ypos 120
    elif player.gauge == 4:
        vbox:
            add Image("images/Gauge/gauge4.png")
            xpos 20 ypos 120
    elif player.gauge == 5:
        vbox:
            add Image("images/Gauge/gauge5.png")
            xpos 20 ypos 120
    elif player.gauge == 6:
        vbox:
            add Image("images/Gauge/gauge6.png")
            xpos 20 ypos 120
    elif player.gauge == 7:
        vbox:
            add Image("images/Gauge/gauge7.png")
            xpos 20 ypos 120

thexerox123
Regular
Posts: 134
Joined: Fri Jan 20, 2023 3:21 pm
itch: thexerox123
Contact:

Re: Shooter Minigame Help - Seeking Refactoring/Organization Advice

#14 Post by thexerox123 »

Another day of good progress, implemented difficulty levels, the GET READY, FLY images at the start, a timer, and the logic to have the sleigh fly off at the end.

Image

Weirdly, I've struggled to get the bell sound effect to play along with GET READY and FLY... trying to get it to play based on sleighgame.starttimer values, but they just won't play.

Code: Select all

style scoreboard:
    color "#8B0000"  # Set text color to red
    size 50          # Set font size to 50
    xpos 880          # Set x position
    ypos 20          # Set y position

style timekeeper:
    color "#8B0000"
    size 30
    xpos 930
    ypos 80

style tester:
    color "#8B0000"
    size 30
    xpos 930
    ypos 140

style tester2:
    color "#8B0000"
    size 30
    xpos 930 
    ypos 200

style positioned_image:
    xpos 400
    ypos 330


init python:
    import random
    import pygame 

    renpy.music.register_channel("sfx", "sound")
    renpy.music.register_channel("weapon", "sound")

    weapon_variables = {
        "basic": {
            "damage": 10,
            "fire_rate": 0.5,
            "projectile_image": "images/Projectiles/Basic.png",
            "projectile_speed": 800
        },    

        "heavy": {
            "damage": 20,
            "fire_rate": 1.0,
            "projectile_image": "heavy_projectile.png",
            "projectile_speed": 300
        },
        # Define more weapon presets
    }

    basic_weapon = weapon_variables["basic"]

    left_frames = [
        "Ring1-1.png",
        "Ring2-1.png",
        "Ring3-1.png",
        "ClosedRing1-1.png",
        "ClosedRing2-1.png"
        # Add more animation frame paths here
    ] 

    right_frames = [
        "Ring1-2.png",
        "Ring2-2.png",
        "Ring3-2.png",
        "ClosedRing1-2.png",
        "ClosedRing2-2.png"
    ]

    # Create a list to hold the Ring instances
    rings = []
                
    class CloudGenerator(renpy.Displayable):
        def __init__(self, small_count, medium_count, large_count, outer_count, over_count):
            renpy.Displayable.__init__(self)
  
            self.small_count = small_count
            self.medium_count = medium_count
            self.large_count = large_count
            self.outer_count = outer_count
            self.over_count = over_count

            self.small_y_options = range(128, 1080 - 128)  # Adjust this range as needed
            self.medium_y_options = range(128, 1080 - 128)  # Adjust this range as needed
            self.large_y_options = range(100, 900) 
            self.outer_y_options = [-50, 0, 50, 1030, 1000, 970]
            self.over_y_options = range(-50, 1130)

            self.clouds = []  # List to hold cloud instances
            self.cloud_size_images = {
                "small": [
                    "images/Clouds/White/cloud_shape3_5.png",
                    "images/Clouds/White/cloud_shape4_5.png",
                    "images/Clouds/White/cloud_shape2_5.png",
                    "images/Clouds/White/cloud_shape3_4.png",
                    "images/Clouds/White/cloud_shape3_3.png",
                    "images/Clouds/White/cloud_shape3_2.png",
                    "images/Clouds/White/cloud_shape3_1.png",
                    "images/Clouds/White/cloud_shape4_4.png",
                    "images/Clouds/White/cloud_shape4_3.png",
                    "images/Clouds/White/cloud_shape4_2.png",
                    "images/Clouds/White/cloud_shape4_1.png",
                    "images/Clouds/White/cloud_shape2_4.png",
                    "images/Clouds/White/cloud_shape2_3.png",
                    "images/Clouds/White/cloud_shape2_2.png",
                    "images/Clouds/White/cloud_shape2_1.png",
                    # Add more small cloud image paths here
                ],
                "medium": [
                    "images/Clouds/White/cloud_shape3_4.png",
                    "images/Clouds/White/cloud_shape3_3.png",
                    "images/Clouds/White/cloud_shape3_2.png",
                    "images/Clouds/White/cloud_shape3_1.png",
                    "images/Clouds/White/cloud_shape4_4.png",
                    "images/Clouds/White/cloud_shape4_3.png",
                    "images/Clouds/White/cloud_shape4_2.png",
                    "images/Clouds/White/cloud_shape4_1.png",
                    "images/Clouds/White/cloud_shape2_4.png",
                    "images/Clouds/White/cloud_shape2_3.png",
                    "images/Clouds/White/cloud_shape2_2.png",
                    "images/Clouds/White/cloud_shape2_1.png",
                    # Add more medium cloud image paths here
                ],
                "large": [
                    "images/Clouds/White/cloud_large1.png",
                    "images/Clouds/White/cloud_large2.png",
                    # Add more large cloud image paths here
                ]
            }    

            cloud_counts = [self.small_count, self.medium_count, self.large_count, self.outer_count, self.over_count]


            s_buffer_distance = 300
            m_buffer_distance = 300 
            l_buffer_distance = 500

            self.oldst = None

            # Initialize cloud instances based on provided counts
            for i in range(small_count):
                cloud_size = "small"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.1, 0.5])    

                cloud = {
                    "count_type" : "small",
                    "x": 1920 + 128 + i * s_buffer_distance,
                    "y": random.randint(128, 1080 - 128),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)    
                cloud["delay"] += 1

            for i in range(medium_count):
                cloud_size = "medium"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.7, 0.8, 0.9])    

                cloud = {
                    "count_type" : "medium",
                    "x": 1920 + 128 + i * m_buffer_distance,
                    "y": random.randint(128, 1080 - 128),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud) 
                cloud["delay"] += 1 

            for i in range(large_count):
                cloud_size = "large"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.7, 0.8, 0.9])    

                cloud = {
                    "count_type" : "large",
                    "x": 1920 + 128 + i * l_buffer_distance,
                    "y": random.randint(100, 900),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            for i in range(outer_count):
                cloud_size = "large"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.5, 0.6])    

                cloud = {
                    "count_type" : "outer",
                    "x": 1920 + 128 + i * l_buffer_distance,
                    "y": random.choice([-50, 0, 50, 1030, 1000, 970]),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            for i in range(over_count):
                cloud_size = "small"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.6, 0.7])    

                cloud = {
                    "count_type" : "over",
                    "x": 1920 + 128 + i * s_buffer_distance,
                    "y": random.randint(-50, 1130),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            self.oldst = None

        def render(self, width, height, st, at):
            r = renpy.Render(width, height)    

            if self.oldst is None:
                self.oldst = st            

            dtime = st - self.oldst
            self.oldst = st           

            for cloud in self.clouds:
                # Check if the cloud's delay has elapsed
                if cloud["delay"] <= 0:
                    cloud["x"] -= cloud["speed"] + player.velocity
                    if cloud["x"] < -600:
                        cloud["x"] = width + 300
                        if cloud["count_type"] == "small":
                            cloud["y"] = random.choice(self.small_y_options)
                        elif cloud["count_type"] == "medium":
                            cloud["y"] = random.choice(self.medium_y_options)
                        elif cloud["count_type"] == "large":
                            cloud["y"] = random.choice(self.large_y_options)
                        elif cloud["count_type"] == "outer":
                            cloud["y"] = random.choice(self.outer_y_options)
                        elif cloud["count_type"] == "over":
                            cloud["y"] = random.choice(self.over_y_options)
    

                    cloud_image = renpy.render(cloud["image"], width, height, st, at)
                    r.blit(cloud_image, (int(cloud["x"]), int(cloud["y"])))
                else:
                    cloud["delay"] -= at + st  # Reduce the delay

            renpy.redraw(self, 0)
            return r 

    class Player(renpy.Displayable):
        def __init__(self, x, y, speed):
            self.px = x
            self.py = y
            self.speed = speed
            self.width = 326
            self.height = 238
            self.pymin = 10
            self.pymax = 1080 - 238
            self.pxmin = 0
            self.pxmax = 960 - 326
            self.velocity = 0
            self.gauge = 0
            self.accelerate = False
            self.health = 4
            self.key_up = False
            self.key_down = False
            self.key_left = False
            self.key_right = False
            # Load the player image.
            self.player_image = Image("Sleigh1.png")    

            # Set player dimensions.
            self.width = 326
            self.height = 238
            self.player_last_fire_time = 0  # Initialize with 0
            self.projectiles = []
            return

        def move(self, dtime):
            if self.key_up:
                self.py -= self.speed * dtime
            if self.key_down:
                self.py += self.speed * dtime
            if self.key_left:
                self.velocity -= 1.5
                self.px -= (self.speed + self.gauge) * dtime
            if self.key_right:
                self.velocity += 1.5
                self.px += (self.speed + self.gauge) * dtime
                self.accelerate = True    

            # Sleigh's travel boundaries
            self.py = max(self.pymin, min(self.py, self.pymax))
            self.px = max(self.pxmin, min(self.px, self.pxmax))

        def fire_projectile(self, current_time):
            player_fire_rate = 15
            if current_time - self.player_last_fire_time >= 1.0 / player_fire_rate:
                basic_projectile = Projectile(
                    self.px + 300,  # Initial x position for the projectile
                    self.py + 95,   # Initial y position for the projectile
                    basic_weapon["projectile_image"],
                    basic_weapon["projectile_speed"],
                    basic_weapon["damage"],
                    basic_weapon["fire_rate"],
                    is_player=True  # Indicate that the projectile is fired by the player
                )
                self.projectiles.append(basic_projectile)
                self.player_last_fire_time = current_time  # Update the last fire time    


        def overlaps_with(self, other):
            left = self.px
            right = self.px + self.width
            top = self.py
            bottom = self.py + self.height    

            other_left = other.x
            other_right = other.x + other.width
            other_top = other.y
            other_bottom = other.y + other.height    

            horizontal_hit = (left <= other_right) and (right >= other_left)
            vertical_hit = (top <= other_bottom) and (bottom >= other_top)    

            return horizontal_hit and vertical_hit

        def render(self, width, height, st, at):
            player_image = renpy.render(self.player, width, height, st, at)
            r.blit(player_image, (int(self.px), int(self.py)))

        def update(self):
            # Call the render method of the SleighShooter class
            sleigh_shooter.render(self.projectiles)

        def event(self, ev, x, y, st):
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_LEFT:
                self.key_left = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_LEFT:
                self.key_left = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_RIGHT:
                self.key_right = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_RIGHT:
                self.key_right = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_UP:
                self.key_up = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_UP:
                self.key_up = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_DOWN:
                self.key_down = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_DOWN:
                self.key_down = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_SPACE:
                current_time = st  # Use the current time passed from the render method    

                # Define player fire rate here (e.g., fires once every 0.5 seconds)
                player_fire_rate = 15    

                # Check if enough time has passed since the last fire
                if current_time - self.player_last_fire_time >= 1.0 / player_fire_rate:
                    basic_projectile = Projectile(
                        self.px + 300,  # Initial x position for the projectile
                        self.py + 95,   # Initial y position for the projectile
                        basic_weapon["projectile_image"],
                        basic_weapon["projectile_speed"],
                        basic_weapon["damage"],
                        basic_weapon["fire_rate"],
                        is_player=True  # Indicate that the projectile is fired by the player
                    )
                    self.projectiles.append(basic_projectile)
                    renpy.play("audio/Blaster.mp3", "weapon")
                    self.player_last_fire_time = current_time  # Update the last fire time    

            # Update gauge rendering based on velocity
            if self.velocity >= 56:
                self.gauge = 7
            elif self.velocity >= 48:
                self.gauge = 6
            elif self.velocity >= 40:
                self.gauge = 5
            elif self.velocity >= 32:
                self.gauge = 4
            elif self.velocity >= 24:
                self.gauge = 3
            elif self.velocity >= 16:
                self.gauge = 2
            elif self.velocity >= 8:
                self.gauge = 1
            elif self.velocity == 1:
                self.gauge = 0 

            # Update player's position based on key presses
            if self.key_up:
                self.py -= self.speed
            if self.key_down:
                self.py += self.speed
            if self.key_left:
                self.velocity -= 1.5
                self.px -= self.speed + self.gauge
            if self.key_right:
                self.velocity += 1.5
                self.px += self.speed + self.gauge
                self.accelerate = True    

            # Handle player's velocity boundaries
            if self.velocity >= 56:
                if self.velocity == 70:
                    self.px += self.speed + 100
                else:
                    self.px += self.speed + 10
                    if self.accelerate == False:
                        if self.px <= 700:
                            self.px = 700
            elif self.velocity >= 40:
                if self.accelerate == False:
                    if self.px <= 500:
                        self.px = 500
            elif self.velocity >= 24:
                if self.accelerate == False: 
                    if self.px <= 300:
                        self.px = 300
            elif self.velocity >= 8:
                if self.accelerate == False:
                    if self.px <= 100:
                        self.px = 100
            if self.velocity <= 1:
                self.velocity = 1
            if self.velocity >= 56 and self.velocity <= 69:
                self.velocity = 56

            # Update player's position within screen boundaries
            if self.velocity != 70:
                if self.py < self.pymin:
                    self.py = self.pymin
                if self.py > self.pymax:
                    self.py = self.pymax
                if self.px < self.pxmin:
                    self.px = self.pxmin
                if self.px > self.pxmax:
                    self.px = self.pxmax

            if sleighgame.difficulty == "Easy":
                if sleighgame.totalrings == 30:
                    sleighgame.gameover = True
                    self.velocity = 70

            elif sleighgame.difficulty == "Medium":
                if sleighgame.totalrings == 35:
                    sleighgame.gameover = True
                    self.velocity = 70

            elif sleighgame.difficulty == "Hard":
                if sleighgame.totalrings == 40:
                    sleighgame.gameover = True
                    self.velocity = 70

            elif sleighgame.difficulty == "Ultra Hard":
                if sleighgame.totalrings == 45:
                    sleighgame.gameover = True
                    self.velocity = 70


    class Ring(renpy.Displayable):
        def __init__(self, x, y, images_left, images_right, ringspeed, ring_type, buffer):
            renpy.Displayable.__init__(self)
            self.x = x
            self.y = y
            self.height = 322
            self.width = 144
            self.images_left = [Image(image) for image in left_frames]
            self.images_right = [Image(image) for image in right_frames]
            self.frame_index = 0
            self.collected = False
            self.missed = False
            self.ringspeed = 100
            self.ring_type = ring_type
            self.buffer = buffer

            self.hit_by_projectile = False
            self.hit_by_player = False

            self.right_half_hit = False
            self.ring_instance = None  # Initialize the ring_instance attribute

            # Add frame indices for different stages of animation
            self.animation_frames = [0, 1, 2, 3, 4]
            self.current_frame = self.animation_frames[0]

            # Lists of image paths for each frame
            self.left_frames = images_left
            self.right_frames = images_right

            # New attributes for the moving behavior
            self.moving_y_range = 200  # Adjust the range as needed
            self.moving_direction = 1  # Start moving down
            self.y_initial = y  # Define y_initial here

        def collides_with_player(self, player):
            ring_left = self.x + self.width * 1.2
            sleigh_right = player.px + player.width
            horizontal_hit = (ring_left <= sleigh_right) and (ring_left + self.width >= sleigh_right)
            vertical_hit = (self.y <= player.py + player.height) and (self.y + self.height >= player.py)
            return horizontal_hit and vertical_hit

        def collides_with_projectile(self, projectile):
            if projectile is None:
                return False
            proj_x, proj_y = projectile.get_position()
            proj_width, proj_height = projectile.get_dimensions()
            proj_left = proj_x
            proj_right = proj_x + proj_width
            proj_top = proj_y
            proj_bottom = proj_y + proj_height
            
            horizontal_hit = (proj_left <= self.x + self.width) and (proj_right >= self.x)
            vertical_hit = (proj_top <= self.y + self.height) and (proj_bottom >= self.y)
            
            return horizontal_hit and vertical_hit

        def render_left_half(self, width, height, st, at):
            if self.missed or self.x < -self.width:
                return None    

            # Determine the current frame index based on animation status and type
            if self.ring_type == "Moving":
                current_frame_index = 1
            elif self.ring_type == "Static":
                current_frame_index = 0
            elif self.ring_type == "ClosedMoving":
                if self.hit_by_projectile == False:
                    current_frame_index = 4
                else:
                    current_frame_index = 1
            elif self.ring_type == "ClosedStatic":
                if self.hit_by_projectile == False:
                    current_frame_index = 3
                else:
                    current_frame_index = 0

            if self.collected:
                current_frame_index = 2

            # Render the appropriate frame based on the current animation frame
            ring_image_left = renpy.render(self.images_left[current_frame_index], width, height, st, at)
            r = renpy.Render(width, height)
            r.blit(ring_image_left, (int(self.x), int(self.y)))    

            return r    

        def render_right_half(self, width, height, st, at):
            if self.missed or self.x < -self.width:
                return None    

            # Determine the current frame index based on animation status and type
            if self.ring_type == "Moving":
                current_frame_index = 1
            elif self.ring_type == "Static":
                current_frame_index = 0
            elif self.ring_type == "ClosedMoving":
                if self.hit_by_projectile == False:
                    current_frame_index = 4
                else:
                    current_frame_index = 1
            elif self.ring_type == "ClosedStatic":
                if self.hit_by_projectile == False:
                    current_frame_index = 3
                else:
                    current_frame_index = 0
            if self.collected:
                current_frame_index = 2
            # Render the appropriate frame based on the current animation frame
            ring_image_right = renpy.render(self.images_right[current_frame_index], width, height, st, at)

            r = renpy.Render(width, height)
            r.blit(ring_image_right, (int(self.x) + self.width // 2 - 72.5, int(self.y)))    

            return r

        def collect(self):
            self.collected = True
            renpy.play("audio/bells.mp3", "sound")
            sleighgame.totalrings += 1

        def miss(self):
            if self.missed == False:
                self.missed = True 
                sleighgame.totalrings += 1

        def update(self, dtime):

            self.x -= self.ringspeed * dtime + player.velocity 

            if self.x <= -100 and self.collected == False:
                self.miss()

            if self.ring_type == "Moving" or self.ring_type == "ClosedMoving":
                self.y += self.moving_direction * 200 * dtime  # Adjust the speed as needed    

                if self.y <= self.y_initial - self.moving_y_range:
                    self.moving_direction = 1
                elif self.y >= self.y_initial + self.moving_y_range:
                    self.moving_direction = -1


    class Projectile(renpy.Displayable):
        def __init__(self, x, y, image_path, speed, width, height, is_player=False):

            renpy.Displayable.__init__(self)
            self.x = x
            self.y = y
            self.image_path = image_path
            self.image = renpy.display.im.Image(image_path)  # Load the image
            self.speed = speed
            self.width = width
            self.height = height    
            self.is_player = is_player


            # Load projectile images (animation frames if multiple paths are provided)
            if isinstance(self.image_path, list):
                self.animation_frames = [Image(path) for path in image_paths]
                self.frame_index = 0  # Start with the first frame
            else:
                self.image = Image(image_path)   

        def get_position(self):
            return self.x, self.y    

        def get_dimensions(self):
            return self.width, self.height

        def render(self, width, height, st, at):
            if self.is_player:
                self.x += self.speed * dtime
            else:
                self.x -= self.speed * dtime   

            # Render the projectile (animation or single image)
            if hasattr(self, "animation_frames"):
                self.frame_index = (self.frame_index + 1) % len(self.animation_frames)
                current_frame = self.animation_frames[self.frame_index]
                projectile_image = renpy.render(current_frame, width, height, st, at)
            else:
                projectile_image = renpy.render(self.image, width, height, st, at)            

            r.blit(projectile_image, (int(self.x), int(self.y)))
            return


    class SleighShooter(renpy.Displayable):
        def __init__(self, player, difficulty):

            renpy.Displayable.__init__(self)

            self.score = 0
            self.misses = 0

            self.oldst = None

            self.lose = False

            self.difficulty = sleighring_difficulty

            self.enemies = []

            self.player = player

            self.active_enemy_projectiles = []  # List to store active enemy projectiles=

            self.introd = False
            
            self.starttimer = 0
            self.timer = 0  # Initialize the timer

            self.gameover = False

            # Initialize the rings with their attributes
            if self.difficulty == "Easy":
                rings = initialize_rings(ring_count=23, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="Static", buffer_range=(700, 2000))
                rings += initialize_rings(ring_count=7, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="Moving", buffer_range=(700, 2000))            

            elif self.difficulty == "Medium":
                rings = initialize_rings(ring_count=20, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="Static", buffer_range=(700, 2000))
                rings += initialize_rings(ring_count=5, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="Moving", buffer_range=(700, 2000))            
                rings += initialize_rings(ring_count=5, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="ClosedStatic", buffer_range=(1000, 2000))
                rings += initialize_rings(ring_count=5, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="ClosedMoving", buffer_range=(1000, 2000))  

            elif self.difficulty == "Hard":
                rings = initialize_rings(ring_count=10, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="Static", buffer_range=(700, 2000))
                rings += initialize_rings(ring_count=10, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="Moving", buffer_range=(700, 2000))            
                rings += initialize_rings(ring_count=10, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="ClosedStatic", buffer_range=(1000, 2000))
                rings += initialize_rings(ring_count=10, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="ClosedMoving", buffer_range=(1000, 2000))  

            elif self.difficulty == "Ultra Hard":
                rings = initialize_rings(ring_count=5, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="Static", buffer_range=(700, 2000))
                rings += initialize_rings(ring_count=10, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="Moving", buffer_range=(700, 2000))            
                rings += initialize_rings(ring_count=15, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="ClosedStatic", buffer_range=(1000, 2000))
                rings += initialize_rings(ring_count=15, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="ClosedMoving", buffer_range=(1000, 2000))  

            # Shuffle the rings list
            import random
            random.shuffle(rings)    

            # Generate rings with proper spacing
            current_x = renpy.config.screen_width  # Start offscreen to the right
            for ring in rings:
                ring.x = current_x
                current_x += ring.buffer + ring.width    

            self.rings = rings  # Assign the shuffled rings back to the class attribute    

            self.totalrings = 0
            return

        @property
        def timer_formatted(self):
            # Format the timer value with 1 decimal place
            return "{:.0f}".format(self.timer)

        def calculate_dtime(self, st):
            if self.oldst is None:
                self.oldst = st    

            dtime = st - self.oldst
            self.oldst = st
            return dtime

        # Draws the screen
        def render(self, width, height, st, at):

            r = renpy.Render(width, height)

            # Figure out the time elapsed since the previous frame.
            if self.oldst is None:
                self.oldst = st

            dtime = st - self.oldst
            self.oldst = st

            self.starttimer += dtime 

            if self.starttimer >= 5:
                self.introd = True
                
            # Increment the timer
            if self.introd == True and self.gameover == False:
                self.timer += dtime 

            # Collect the render objects for left and right halves of rings
            left_ring_images = []
            right_ring_images = []
            projectiles_to_remove = []

            if self.starttimer == 1:
                renpy.play("audio/bells.mp3", "sound")    

            if self.starttimer == 4:
                renpy.play("audio/bells.mp3", "sound")

            # Update and render rings
            if self.introd == True:
                for ring in self.rings:
                    ring.update(dtime)        

                    # Render the left half of the ring below the sleigh
                    ring_left_half_render = ring.render_left_half(width, height, st, at)
                    if ring_left_half_render:
                        r.blit(ring_left_half_render, (0, 0))

            # Render the player's image directly using renpy.render
            player_image = renpy.render(player.player_image, width, height, st, at)
            r.blit(player_image, (int(player.px), int(player.py)))

            # Update and render projectiles
            for projectile in self.player.projectiles:
                projectile.x += projectile.speed * dtime
                projectile_image = renpy.render(projectile.image, width, height, st, at)
                r.blit(projectile_image, (int(projectile.x), int(projectile.y)))    

                # Remove projectiles that go off-screen
                if projectile.x > width:
                    self.player.projectiles.remove(projectile)

            if self.introd == True:
                for ring in self.rings:
                    ring_right_half_render = ring.render_right_half(width, height, st, at)
                    if ring_right_half_render:
                        r.blit(ring_right_half_render, (0, 0))              

                    if ring.collected == False and ring.collides_with_player(player):
                        # Collision with player logic            
                        if ring.ring_type == "ClosedStatic" or ring.ring_type == "ClosedMoving":
                            if ring.hit_by_projectile == False and ring.hit_by_player == False:
                                player.health -= 1 
                                ring.hit_by_player = True 
                                renpy.play("audio/Crash1.mp3", "sound")  # Play collision sound    

                            elif ring.hit_by_projectile == True:
                                ring.collected = True 
                                ring.collect()
                                self.score +=1    

                        elif ring.ring_type == "Static" or ring.ring_type == "Moving":
                            ring.collected = True
                            ring.collect()  # Call the collect method
                            self.score += 1        

                    for projectile in player.projectiles:
                        if ring.collected == False and ring.collides_with_projectile(projectile):
                            # Collision with projectile logic            
                            if ring.ring_type == "ClosedStatic" or ring.ring_type == "ClosedMoving":
                                if ring.hit_by_projectile == False:
                                    ring.hit_by_projectile = True
                                    renpy.play("audio/RingDown.mp3", "sfx")  # Play collision sound
                                    projectiles_to_remove.append(projectile)

            # Remove projectiles outside the loop
            for projectile in projectiles_to_remove:
                self.player.projectiles.remove(projectile)

            # Check for a loss.
            if player.health == 0:
                self.lose = True

                renpy.timeout(0)

            # Ask that we be re-rendered ASAP, so we can show the next
            # frame.
            renpy.redraw(self, 0)

            # Return the Render object.
            return r

        # Handles events.
        def event(self, ev, x, y, st):
            # Pass the events to the Player instance
            player.event(ev, x, y, st)

            # Ensure the screen updates.
            renpy.restart_interaction()            

            # If the player loses, return it.
            if self.lose:
                return self.lose
            else:
                raise renpy.IgnoreEvent()

    def initialize_rings(ring_count, ring_width, images_left, images_right, ring_type, buffer_range=(600, 800)):
        rings = []
        for i in range(ring_count):
            buffer = random.uniform(*buffer_range)
            ring_x = renpy.config.screen_width + buffer + i * ring_width  # Start offscreen to the right
            ring_y = random.randint(128, renpy.config.screen_height - 300)
            
            if ring_type == "Moving":
                ring = Ring(ring_x, ring_y, images_left, images_right, ringspeed=100, ring_type="Moving", buffer=buffer)
            elif ring_type == "Static":
                ring = Ring(ring_x, ring_y, images_left, images_right, ringspeed=100, ring_type="Static", buffer=buffer)
            elif ring_type == "ClosedMoving":
                ring = Ring(ring_x, ring_y, images_left, images_right, ringspeed=80, ring_type="ClosedMoving", buffer=buffer)
            elif ring_type == "ClosedStatic":
                ring = Ring(ring_x, ring_y, images_left, images_right, ringspeed=80, ring_type="ClosedStatic", buffer=buffer)

            rings.append(ring) 
        return rings


screen sleigh_ride():

    add Image("bg Sky.png")

    add cloudcover

    add cloudfore

    # Add the left side of the rings below the sleigh
    for ring in rings:
        add ring.render_left_half(width, height, st, at)

    add sleighgame

    # Add the right side of the rings above the sleigh
    for ring in rings:
        add ring.render_right_half(width, height, st, at)

    # Display "Get Ready" image at starttimer 1 and remove at starttimer 4
    if sleighgame.starttimer >= 1 and sleighgame.starttimer < 4:
        add Image("images/GetReady.png", style="positioned_image")

    # Display "Fly!" image at starttimer 4 and remove at starttimer 5
    if sleighgame.starttimer >= 4 and sleighgame.starttimer < 5:
        add Image("images/Fly.png", style="positioned_image")

    # Add a text element to display the score at the top
    vbox:
        text "Score: [sleighgame.score]" style "scoreboard"
    vbox:
        text "Time: [sleighgame.timer_formatted]" style "timekeeper"
    # vbox:
    #     text "Total Rings: [sleighgame.totalrings]" style "tester"
    # vbox:
    #     text "Velocity: [player.velocity]" style "tester2"

    if player.health == 1:
        vbox:
            add Image("images/SleighShield4.png")
            xpos 30 ypos 10
    elif player.health == 2:
        vbox:
            add Image("images/SleighShield3.png")
            xpos 30 ypos 10
    elif player.health == 3:
        vbox:
            add Image("images/SleighShield2.png")
            xpos 30 ypos 10
    elif player.health == 4:
        vbox:
            add Image("images/SleighShield1.png")
            xpos 30 ypos 10

    if player.gauge == 0:
        vbox:
            add Image("images/Gauge/gauge0.png")
            xpos 20 ypos 120
    elif player.gauge == 1:
        vbox:
            add Image("images/Gauge/gauge1.png")
            xpos 20 ypos 120
    elif player.gauge == 2:
        vbox:
            add Image("images/Gauge/gauge2.png")
            xpos 20 ypos 120
    elif player.gauge == 3:
        vbox:
            add Image("images/Gauge/gauge3.png")
            xpos 20 ypos 120
    elif player.gauge == 4:
        vbox:
            add Image("images/Gauge/gauge4.png")
            xpos 20 ypos 120
    elif player.gauge == 5:
        vbox:
            add Image("images/Gauge/gauge5.png")
            xpos 20 ypos 120
    elif player.gauge == 6:
        vbox:
            add Image("images/Gauge/gauge6.png")
            xpos 20 ypos 120
    elif player.gauge == 7:
        vbox:
            add Image("images/Gauge/gauge7.png")
            xpos 20 ypos 120

thexerox123
Regular
Posts: 134
Joined: Fri Jan 20, 2023 3:21 pm
itch: thexerox123
Contact:

Re: Shooter Minigame Help - Seeking Refactoring/Organization Advice

#15 Post by thexerox123 »

So, I tried working on a version of this game with enemies, but I keep running into issues. I think I'll spin that off into its own thread, and count this version centered around ring-collecting as complete.

A downloadable version can be found here: https://thexerox123.itch.io/sleigh-ride

Here's the current state of my code:
script.rpy

Code: Select all

init:
    default weapon = "standard"
    default sleighring_difficulty = "Easy"

    default controlcheck = False
    default controls = "Keyboard"

    default weather_condition = "White"
    default cloudcover = CloudGenerator(small_count=20, medium_count=10, large_count=5, outer_count=0, over_count=0)
    default cloudfore = CloudGenerator(small_count=0, medium_count=0, large_count=0, outer_count=8, over_count=10)


    # Initialize high scores for all difficulty levels
    default persistent.easy_scores = []
    default persistent.medium_scores = []
    default persistent.hard_scores = []
    default persistent.ultra_hard_scores = []

    image redshot:
        "tile001.png"
        0.1
        "tile002.png"
        0.1
        "tile003.png"
        0.1
        "tile004.png"
        0.1
        "tile005.png"
        0.1
        "tile006.png"
        0.1
        "tile007.png"
        0.1
        "tile008.png"
        0.1
        "tile009.png"
        0.1
        "tile010.png"
        0.1
        "tile011.png"
        0.1
        "tile012.png"
        0.1
        "tile013.png"
        0.1
        "tile014.png"
        0.1
        "tile015.png"
        0.1
        "tile016.png"
        0.1
        "tile017.png"
        0.1
        "tile018.png"
        0.1
        "tile019.png"
        0.1
        "tile020.png"
        0.1
        "tile021.png"
        0.1
        "tile022.png"
        0.1
        "tile023.png"
        0.1
        "tile024.png"
        0.1
        "tile025.png"
        0.1
        "tile026.png"
        0.1
        "tile027.png"
        0.1
        "tile028.png"
        0.1
        "tile029.png"
        0.1
        "tile030.png"
        0.1
        "tile031.png"
        0.1
        "tile032.png"
        0.1
        "tile033.png"
        0.1
        "tile034.png"
        0.1
        "tile035.png"
        0.1
        "tile036.png"
        0.1
        "tile037.png"
        0.1
        "tile038.png"
        0.1
        "tile039.png"
        0.1
        "tile040.png"
        0.1
        "tile041.png"
        0.1
        "tile042.png"
        0.1
        "tile043.png"
        0.1
        "tile044.png"
        0.1
        "tile045.png"
        0.1
        "tile046.png"
        0.1
        "tile047.png"
        0.1
        "tile048.png"
        0.1
        "tile049.png"
        0.1
        "tile050.png"
        0.1
        "tile051.png"
        0.1
        "tile052.png"
        0.1
        "tile053.png"
        0.1
        "tile054.png"
        0.1
        "tile055.png"
        0.1
        "tile056.png"
        0.1
        "tile057.png"
        0.1
        "tile058.png"
        0.1
        "tile059.png"
        0.1
        repeat    

    image blueshot:
        "Bs1.png"
        0.1
        "Bs2.png"
        0.1
        "Bs3.png"
        0.1
        "Bs4.png"
        0.1
        repeat    

    image gbreadshot:
        "Gs1.png"
        0.1
        "Gs2.png"
        0.1
        repeat    

    image getready = Image("GetReady.png")

    image fly = Image("Fly.png")

    image sleigh = Image("Sleigh1.png") 

    image shield = Image("SleighShield1.png")   

    image cloud = Image("images/Clouds/White/cloud_shape3_1.png")


# Define persistent high score tables for each difficulty level
init python:

    def update_high_scores(difficulty, score, time):
        # Create a tuple with score, time, and an empty name
        entry = ("", score, time)

        # Select the appropriate high score list based on difficulty
        if difficulty == "Easy":
            high_scores = persistent.easy_scores
        elif difficulty == "Medium":
            high_scores = persistent.medium_scores
        elif difficulty == "Hard":
            high_scores = persistent.hard_scores
        elif difficulty == "Ultra Hard":
            high_scores = persistent.ultra_hard_scores
        else:
            return  # Invalid difficulty

        # Check if the player's score qualifies for the high score list
        if len(high_scores) < 10 or score > high_scores[-1][1]:
            # Prompt the player for their name
            name = renpy.input("Congratulations! You've earned a high score. Enter your name:")
            
            # Format the time to display only 2 decimal points
            formatted_time = "{:.2f}".format(time)
            
            entry = (name, score, formatted_time)

            # Add the new entry to the list
            high_scores.append(entry)
            # Sort the list by name (and score as a tiebreaker)
            high_scores.sort(key=lambda x: (x[0], -x[1]), reverse=True)
            # Keep only the top 10 scores
            high_scores = high_scores[:10]

        # Update the persistent variable based on the difficulty
        if difficulty == "Easy":
            persistent.easy_scores = high_scores
        elif difficulty == "Medium":
            persistent.medium_scores = high_scores
        elif difficulty == "Hard":
            persistent.hard_scores = high_scores
        elif difficulty == "Ultra Hard":
            persistent.ultra_hard_scores = high_scores

# The game starts here.

label start:

    scene bg sky

menu:
    "Easy":
        $ sleighring_difficulty = "Easy"
    "Medium":
        $ sleighring_difficulty = "Medium"
    "Hard":
        $ sleighring_difficulty = "Hard"
    "Ultra Hard":
        $ sleighring_difficulty = "Ultra Hard"

# menu: 
#     "Preferred control scheme:"
#     "Keyboard":
#         pass

#     "Right-Hand Controls":
#         $ controls = "Right"

#     "Left-Hand Controls":
#         $ controls = "Left"

#     "Mouse/Drag Controls":
#         $ controls = "Mouse"


label play_sleighgame:

    $ player = Player(x=20, y=500, speed=15)  # Pass this player instance to sleighgame
    $ sleighgame = SleighShooter(player=player, difficulty=sleighring_difficulty)

    window hide  
    $ quick_menu = False

    play music technocrat

    call screen sleigh_ride()

    $ quick_menu = True
    window auto

label game_over:
    $ player_score = sleighgame.score
    $ player_time = sleighgame.timer

    if player.health >= 1:
        python:
            update_high_scores(sleighring_difficulty, player_score, player_time)    

        call screen highscore_table

    else:
        jump playagain

    $ renpy.pause(hard=True)

label playagain:
    hide screen scoreshow
    call screen play_again_prompt
And Sleigh Game.rpy:

Code: Select all

style scoreboard:
    color "#8B0000"  # Set text color to red
    size 50          # Set font size to 50
    xpos 880          # Set x position
    ypos 20          # Set y position

style timekeeper:
    color "#8B0000"
    size 30
    xpos 930
    ypos 80

style tester:
    color "#8B0000"
    size 30
    xpos 930
    ypos 140

style tester2:
    color "#8B0000"
    size 30
    xpos 930 
    ypos 200

style positioned_image:
    xpos 400
    ypos 330

style title:
    size 30
    color "#FFFFFF"
    align (0.5, 0.5)
    xpos 0.5
    ypos 0.05

style table_header:
    size 30
    color "#FFFFFF"
    align (0.5, 0.5)
    xpos 0.5

style table_entry:
    size 26
    color "#FFFFFF"
    align (0.5, 0.5)
    xpos 0.5

style menu_button:
    size 20
    color "#FFFFFF"
    hover_color "#FFD700"
    xpos 0.5
    ypos 0.9


style w_buttons:
    color "#8B0000"  # Set text color to red
    size 50          # Set font size to 50
    xalign 0.5  # Center align the hbox
    yalign 0.95
    spacing 10  # Adjust spacing between buttons


style frame_easy:
    xsize 384
    ysize 648
    background "#00FF00" 

style frame_medium:
    xsize 384
    ysize 648
    background "#0000FF"

style frame_hard:
    xsize 384
    ysize 648
    background "#FFA500" 

style frame_ultra_hard:
    xsize 384
    ysize 648
    background "#FF0000" 

style white_frame:
    xpadding 20  # Adjust the horizontal padding as needed
    ypadding 20  # Adjust the vertical padding as needed
    background Color("#FFFFFF")  # Set the background color to white
    xpos 0.5  # Center the frame horizontally
    ypos 0.5  # Center the frame vertically
    xalign 0.5  # Center the frame horizontally
    yalign 0.5  # Center the frame vertically


init python:
    import random
    import pygame 

    renpy.music.register_channel("sfx", "sound")
    renpy.music.register_channel("weapon", "sound")

    weapon_variables = {
        "basic": {
            "weapon_type": "basic",
            "damage": 10,
            "fire_rate": 0.5,
            "projectile_frames": 2,
            "projectile_speed": 800,
            "animation_frames": [
                f"images/Projectiles/Basic{i:03}.png"
                for i in range(1, 3)
            ],
            "projectile_types": ["projectile1"],
            "frame_delay": 0.01,
            "spos": (300, 95), # Relative to the sleigh
            "fire_sound": ["audio/Blaster.mp3"], 
            "initial_y_velocity": 0,  # Set initial y velocity
            "gravity": 0  # Set gravity 
        },        

        "basic2": {
            "weapon_type": "basic2",
            "damage": 20,
            "fire_rate": 1.0,
            "projectile_frames": 60,
            "projectile_speed": 1000,
            "animation_frames": [
                f"images/Projectiles/tile{i:03}.png"
                for i in range(0, 60)
            ],
            "projectile_types": ["projectile1"],
            "frame_delay": 0.001,
            "spos": (252, 70),  # Relative to the sleigh
            "fire_sound": ["audio/Blaster2.mp3"],
            "initial_y_velocity": 0,  # Set initial y velocity
            "gravity": 0  # Set gravity           
        },

        "basic3": {
            "weapon_type": "basic3",
            "damage": 80,
            "fire_rate": 2,
            "projectile_frames": 4,
            "projectile_speed": 750,
            "animation_frames": [
                f"images/Projectiles/FB{i:03}.png"
                for i in range(1, 4)
            ],
            "projectile_types": ["projectile1"],
            "frame_delay": 0.1,
            "spos": (291, 91),  # Relative to the sleigh
            "fire_sound": ["audio/Blaster3.mp3"],
            "initial_y_velocity": 0,  # Set initial y velocity
            "gravity": 0  # Set gravity
        },

        "tree": {
            "weapon_type": "tree",
            "damage": 20,
            "fire_rate": 1.5,
            "projectile_frames": 0,
            "projectile_speed": 800,
            "animation_frames": [
                f"images/Projectiles/Tree{i:03}.png"
                for i in range(1, 3)
            ],
            "projectile_types": ["projectile1", "projectile2", "projectile3", "projectile4", "projectile5", "projectile6"],
            "frame_delay": 0.1,
            "spos": (230, 50), # Relative to the sleigh
            "fire_sound": ["audio/Tree.mp3"], 
            "initial_y_velocity": 0,  # Set initial y velocity
            "gravity": 0  # Set gravity 
        },        

        "tree2": {
            "weapon_type": "tree2",
            "damage": 20,
            "fire_rate": 0.5,
            "projectile_frames": 2,
            "projectile_speed": 600,
            "animation_frames": [
                f"images/Projectiles/Tree2{i:03}.png"
                for i in range(1, 3)
            ],
            "projectile_types": ["projectile1"],
            "frame_delay": 0.01,
            "spos": (230, 50), # Relative to the sleigh
            "fire_sound": ["audio/Tree.mp3"], 
            "initial_y_velocity": 0,  # Set initial y velocity
            "gravity": 0  # Set gravity 
        },        

        "gbread": {
            "weapon_type": "gbread",
            "damage": 40,
            "fire_rate": 2,
            "projectile_frames": 2,
            "projectile_speed": 1000,
            "animation_frames": [
                f"images/Projectiles/gs{i:03}.png"
                for i in range(1, 3)
            ],
            "projectile_types": ["projectile1"],
            "frame_delay": 0.1,
            "spos": (291, 91),  # Relative to the sleigh
            "fire_sound": ["audio/Cannon.mp3"],
            "initial_y_velocity": 0,  # Set initial y velocity
            "gravity": 0  # Set gravity
            
        },

        "rdeer": {
            "weapon_type": "rdeer",
            "damage": 50,
            "fire_rate": 1.75,
            "projectile_frames": 4,
            "projectile_speed": 700,
            "animation_frames": {
                f"projectile{i}": [f"images/Projectiles/bs{i}-{j}.png" for j in range(1, 5)]
                for i in range(1, 5)
            },
            "projectile_types": ["projectile1", "projectile2", "projectile3", "projectile4"],
            "frame_delay": 0.1,
            "spos": (291, 50),  # Relative to the sleigh
            "fire_sound": [
                "audio/Clink1.mp3",
                "audio/Clink2.mp3",
                "audio/Clink3.mp3",
                "audio/Clink4.mp3",
                "audio/Clink5.mp3"
            ],
            "initial_y_velocity": -300,  # Adjust as needed for the desired arc
            "gravity": 500  # Adjust to achieve the desired arc
            
        },
        # Define more weapon presets
    }


    basic_weapon = weapon_variables["basic"]
    basic_weapon_2 = weapon_variables["basic2"]
    basic_weapon_3 = weapon_variables["basic3"]
    gingerbread_weapon = weapon_variables["gbread"]
    # heavy_weapon = weapon_variables["heavy"]
    rdeer_weapon = weapon_variables["rdeer"]
    tree_weapon = weapon_variables["tree"]
    tree_weapon_2 = weapon_variables["tree2"]
    weapon_type = "basic"

    left_frames = [
        "Ring1-1.png",
        "Ring2-1.png",
        "Ring3-1.png",
        "ClosedRing1-1.png",
        "ClosedRing2-1.png"
        # Add more animation frame paths here
    ] 

    right_frames = [
        "Ring1-2.png",
        "Ring2-2.png",
        "Ring3-2.png",
        "ClosedRing1-2.png",
        "ClosedRing2-2.png"
    ]

    # Create a list to hold the Ring instances
    rings = []


    def update_weapon_type(weapon_type):
        player.current_weapon_type = weapon_type
        player.update_player_image()  # Update the player's image based on the new weapon type

                
    class CloudGenerator(renpy.Displayable):
        def __init__(self, small_count, medium_count, large_count, outer_count, over_count):
            renpy.Displayable.__init__(self)
  
            self.small_count = small_count
            self.medium_count = medium_count
            self.large_count = large_count
            self.outer_count = outer_count
            self.over_count = over_count

            self.small_y_options = range(128, 1080 - 128)  # Adjust this range as needed
            self.medium_y_options = range(128, 1080 - 128)  # Adjust this range as needed
            self.large_y_options = range(100, 900) 
            self.outer_y_options = [-50, 0, 50, 1030, 1000, 970]
            self.over_y_options = range(-50, 1130)

            self.clouds = []  # List to hold cloud instances
            self.cloud_size_images = {
                "small": [
                    "images/Clouds/White/cloud_shape3_5.png",
                    "images/Clouds/White/cloud_shape4_5.png",
                    "images/Clouds/White/cloud_shape2_5.png",
                    "images/Clouds/White/cloud_shape3_4.png",
                    "images/Clouds/White/cloud_shape3_3.png",
                    "images/Clouds/White/cloud_shape3_2.png",
                    "images/Clouds/White/cloud_shape3_1.png",
                    "images/Clouds/White/cloud_shape4_4.png",
                    "images/Clouds/White/cloud_shape4_3.png",
                    "images/Clouds/White/cloud_shape4_2.png",
                    "images/Clouds/White/cloud_shape4_1.png",
                    "images/Clouds/White/cloud_shape2_4.png",
                    "images/Clouds/White/cloud_shape2_3.png",
                    "images/Clouds/White/cloud_shape2_2.png",
                    "images/Clouds/White/cloud_shape2_1.png",
                    # Add more small cloud image paths here
                ],
                "medium": [
                    "images/Clouds/White/cloud_shape3_4.png",
                    "images/Clouds/White/cloud_shape3_3.png",
                    "images/Clouds/White/cloud_shape3_2.png",
                    "images/Clouds/White/cloud_shape3_1.png",
                    "images/Clouds/White/cloud_shape4_4.png",
                    "images/Clouds/White/cloud_shape4_3.png",
                    "images/Clouds/White/cloud_shape4_2.png",
                    "images/Clouds/White/cloud_shape4_1.png",
                    "images/Clouds/White/cloud_shape2_4.png",
                    "images/Clouds/White/cloud_shape2_3.png",
                    "images/Clouds/White/cloud_shape2_2.png",
                    "images/Clouds/White/cloud_shape2_1.png",
                    # Add more medium cloud image paths here
                ],
                "large": [
                    "images/Clouds/White/cloud_large1.png",
                    "images/Clouds/White/cloud_large2.png",
                    # Add more large cloud image paths here
                ]
            }    

            cloud_counts = [self.small_count, self.medium_count, self.large_count, self.outer_count, self.over_count]


            s_buffer_distance = 300
            m_buffer_distance = 300 
            l_buffer_distance = 500

            self.oldst = None

            # Initialize cloud instances based on provided counts
            for i in range(small_count):
                cloud_size = "small"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.1, 0.5])    

                cloud = {
                    "count_type" : "small",
                    "x": 1920 + 128 + i * s_buffer_distance,
                    "y": random.randint(128, 1080 - 128),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)    
                cloud["delay"] += 1

            for i in range(medium_count):
                cloud_size = "medium"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.7, 0.8, 0.9])    

                cloud = {
                    "count_type" : "medium",
                    "x": 1920 + 128 + i * m_buffer_distance,
                    "y": random.randint(128, 1080 - 128),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud) 
                cloud["delay"] += 1 

            for i in range(large_count):
                cloud_size = "large"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.7, 0.8, 0.9])    

                cloud = {
                    "count_type" : "large",
                    "x": 1920 + 128 + i * l_buffer_distance,
                    "y": random.randint(100, 900),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            for i in range(outer_count):
                cloud_size = "large"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.5, 0.6])    

                cloud = {
                    "count_type" : "outer",
                    "x": 1920 + 128 + i * l_buffer_distance,
                    "y": random.choice([-50, 0, 50, 1030, 1000, 970]),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            for i in range(over_count):
                cloud_size = "small"
                cloud_image = random.choice(self.cloud_size_images[cloud_size])
                speed = random.choice([0.6, 0.7])    

                cloud = {
                    "count_type" : "over",
                    "x": 1920 + 128 + i * s_buffer_distance,
                    "y": random.randint(-50, 1130),
                    "speed": speed,
                    "image": Image(cloud_image)
                }
                cloud["delay"] = random.uniform(0, 7)
                self.clouds.append(cloud)
                cloud["delay"] += 1

            self.oldst = None

        def render(self, width, height, st, at):
            r = renpy.Render(width, height)    

            if self.oldst is None:
                self.oldst = st            

            dtime = st - self.oldst
            self.oldst = st           

            for cloud in self.clouds:
                # Check if the cloud's delay has elapsed
                if cloud["delay"] <= 0:
                    cloud["x"] -= cloud["speed"] + player.velocity
                    if cloud["x"] < -600:
                        cloud["x"] = width + 300
                        if cloud["count_type"] == "small":
                            cloud["y"] = random.choice(self.small_y_options)
                        elif cloud["count_type"] == "medium":
                            cloud["y"] = random.choice(self.medium_y_options)
                        elif cloud["count_type"] == "large":
                            cloud["y"] = random.choice(self.large_y_options)
                        elif cloud["count_type"] == "outer":
                            cloud["y"] = random.choice(self.outer_y_options)
                        elif cloud["count_type"] == "over":
                            cloud["y"] = random.choice(self.over_y_options)
    

                    cloud_image = renpy.render(cloud["image"], width, height, st, at)
                    r.blit(cloud_image, (int(cloud["x"]), int(cloud["y"])))
                else:
                    cloud["delay"] -= at + st  # Reduce the delay

            renpy.redraw(self, 0)
            return r 

    class Player(renpy.Displayable):
        def __init__(self, x, y, speed):
            self.px = x
            self.py = y
            self.speed = speed
            self.width = 326
            self.height = 238
            self.pymin = 10
            self.pymax = 1080 - 238
            self.pxmin = 0
            self.pxmax = 960 - 326
            self.velocity = 0
            self.gauge = 0
            self.accelerate = False
            self.health = 4
            self.key_up = False
            self.key_down = False
            self.key_left = False
            self.key_right = False
            # Load the player images.
            self.weapon_image_paths = {
                "basic": "images/Sleigh1.png",
                "basic2": "images/Sleigh2W.png",
                "basic3": "images/Sleigh3W.png",
                "tree": "images/SleighT1.png",
                "tree2": "images/SleighTG2.png",
                "gbread": "images/SleighG2.png",
                "rdeer": "images/SleighRP1.png"
                # Add more mappings for other weapon types
            }
            self.player_image = Image("Sleigh1.png")  # Initialize with default image 
            self.current_weapon_type = "basic"  # Default weapon type
            self.update_player_image()

            # Set player dimensions.
            self.width = 326
            self.height = 238
            self.player_last_fire_time = 0  # Initialize with 0
            self.projectiles = []
            self.explosion_images = [
                Image("images/Projectiles/Layer 23.png"),
                Image("images/Projectiles/Layer 24.png"),
                Image("images/Projectiles/Layer 25.png"),
                Image("images/Projectiles/Layer 26.png")
            ]
            self.explosion_duration = 1.0  # Duration of the explosion animation in seconds
            self.explosion_timer = 0.0
            self.explosion_frame = 0
            self.is_exploding = False
            self.destroyed = False
            return

        def update_player_image(self):
            weapon_image_path = self.weapon_image_paths.get(self.current_weapon_type, "images/Sleigh1.png")  # Default to Sleigh1.png if no mapping found
            self.player_image = Image(weapon_image_path)

        def move(self, dtime):
            if self.key_up:
                self.py -= self.speed * dtime
            if self.key_down:
                self.py += self.speed * dtime
            if self.key_left:
                self.velocity -= 1.5
                self.px -= (self.speed + self.gauge) * dtime
            if self.key_right:
                self.velocity += 1.5
                self.px += (self.speed + self.gauge) * dtime
                self.accelerate = True    

            # Sleigh's travel boundaries
            self.py = max(self.pymin, min(self.py, self.pymax))
            self.px = max(self.pxmin, min(self.px, self.pxmax))

        def fire_projectile(self, current_time, weapon_type):
            player_fire_rate = 15
            if current_time - self.player_last_fire_time >= 1.0 / player_fire_rate:
                weapon_attributes = weapon_variables.get(weapon_type, {})
                projectile_spos = weapon_attributes.get("spos", (0, 0))  # Get the starting position
                fire_sound_options = weapon_attributes.get("fire_sound", ["audio/Blaster.mp3"])
                fire_sound = random.choice(fire_sound_options)  # Randomly select a fire sound
                renpy.play(fire_sound, "weapon")  # Play the randomly selected fire sound        

                projectile_types = weapon_attributes.get("projectile_types", [])
                selected_projectile_type = random.choice(projectile_types) if projectile_types else None        

                selected_projectile_frames = weapon_attributes.get("animation_frames", [])
                if isinstance(selected_projectile_frames, dict):
                    selected_projectile_frames = selected_projectile_frames.get(selected_projectile_type, [])
                elif not isinstance(selected_projectile_frames, list):
                    selected_projectile_frames = []        

                projectile = Projectile(
                    self.px + projectile_spos[0],  # Adjust starting x position
                    self.py + projectile_spos[1],  # Adjust starting y position
                    weapon_attributes.get("projectile_speed"),
                    frame_delay=weapon_attributes.get("frame_delay"),
                    animation_frames=selected_projectile_frames,  # Use the selected projectile's animation frames
                    is_player=True,
                    damage=weapon_attributes.get("damage"),
                    fire_rate=weapon_attributes.get("fire_rate"),
                    initial_y_velocity=weapon_attributes.get("initial_y_velocity"),
                    gravity=weapon_attributes.get("gravity")
                )
                self.projectiles.append(projectile)
                renpy.play(fire_sound, "weapon")
                self.player_last_fire_time = current_time  # Update the last fire time

        def overlaps_with(self, other):
            left = self.px
            right = self.px + self.width
            top = self.py
            bottom = self.py + self.height    

            other_left = other.x
            other_right = other.x + other.width
            other_top = other.y
            other_bottom = other.y + other.height    

            horizontal_hit = (left <= other_right) and (right >= other_left)
            vertical_hit = (top <= other_bottom) and (bottom >= other_top)    

            return horizontal_hit and vertical_hit

        def render(self, width, height, st, at):
            r = renpy.Render(width, height)        

            if not self.destroyed:
                if self.is_exploding:
                    if self.explosion_frame < len(self.explosion_images):
                        explosion_frame = self.explosion_images[self.explosion_frame]
                        explosion_render = renpy.render(explosion_frame, width, height, st, at)
                        r.blit(explosion_render, (int(self.px), int(self.py)))  # Render explosion frame
                else:
                    player_image = renpy.render(self.player_image, width, height, st, at)
                    r.blit(player_image, (int(self.px), int(self.py)))  # Render the player's image


            return r

        def handle_explosion(self):
            if not self.is_exploding:
                self.explosion_timer = 0.0
                self.explosion_frame = 0
                self.is_exploding = True
                self.velocity = 0  # Reset the player's velocity        


        def update(self, dtime, st, at):
            if self.is_exploding:
                self.explosion_timer += dtime    

                # Calculate the current frame based on the elapsed time
                frame_duration = self.explosion_duration / len(self.explosion_images)
                self.explosion_frame = int(self.explosion_timer / frame_duration)  # Use integer division to get the frame    

                # Check if the animation is completed
                if self.explosion_frame >= len(self.explosion_images):
                    self.is_exploding = False  # Reset the is_exploding flag
                    self.destroyed = True  # Set destroyed to True when the explosion is completed
                    self.explosion_timer = 0.0  # Reset the explosion timer
                    self.explosion_frame = 0  # Reset the explosion frame

        def event(self, ev, x, y, st):
            if self.is_exploding:
                return  # Ignore events during explosion
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_LEFT:
                self.key_left = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_LEFT:
                self.key_left = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_RIGHT:
                self.key_right = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_RIGHT:
                self.key_right = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_UP:
                self.key_up = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_UP:
                self.key_up = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_DOWN:
                self.key_down = True
            if ev.type == pygame.KEYUP and ev.key == pygame.K_DOWN:
                self.key_down = False
            if ev.type == pygame.KEYDOWN and ev.key == pygame.K_SPACE:
                self.fire_projectile(st, weapon_type)
   

            # Update gauge rendering based on velocity
            if self.velocity >= 56:
                self.gauge = 7
            elif self.velocity >= 48:
                self.gauge = 6
            elif self.velocity >= 40:
                self.gauge = 5
            elif self.velocity >= 32:
                self.gauge = 4
            elif self.velocity >= 24:
                self.gauge = 3
            elif self.velocity >= 16:
                self.gauge = 2
            elif self.velocity >= 8:
                self.gauge = 1
            elif self.velocity == 1:
                self.gauge = 0 

            # Update player's position based on key presses
            if self.key_up:
                self.py -= self.speed
            if self.key_down:
                self.py += self.speed
            if self.key_left:
                self.velocity -= 1.5
                self.px -= self.speed + self.gauge
            if self.key_right:
                self.velocity += 1.5
                self.px += self.speed + self.gauge
                self.accelerate = True    

            # Handle player's velocity boundaries
            if self.velocity >= 56:
                if self.velocity == 70:
                    self.px += self.speed + 100
                else:
                    self.px += self.speed + 10
                    if self.accelerate == False:
                        if self.px <= 700:
                            self.px = 700
            elif self.velocity >= 40:
                if self.accelerate == False:
                    if self.px <= 500:
                        self.px = 500
            elif self.velocity >= 24:
                if self.accelerate == False: 
                    if self.px <= 300:
                        self.px = 300
            elif self.velocity >= 8:
                if self.accelerate == False:
                    if self.px <= 100:
                        self.px = 100
            if self.velocity <= 1:
                self.velocity = 1
            if self.velocity >= 56 and self.velocity <= 69:
                self.velocity = 56

            # Update player's position within screen boundaries
            if self.velocity != 70:
                if self.py < self.pymin:
                    self.py = self.pymin
                if self.py > self.pymax:
                    self.py = self.pymax
                if self.px < self.pxmin:
                    self.px = self.pxmin
                if self.px > self.pxmax:
                    self.px = self.pxmax


            if sleighgame.difficulty == "Easy":
                if sleighgame.totalrings == 30:
                    sleighgame.gameover = True
                    self.velocity = 70
                    renpy.show_screen("scoreshow")

            elif sleighgame.difficulty == "Medium":
                if sleighgame.totalrings == 35:
                    sleighgame.gameover = True
                    self.velocity = 70
                    renpy.show_screen("scoreshow")

            elif sleighgame.difficulty == "Hard":
                if sleighgame.totalrings == 40:
                    sleighgame.gameover = True
                    self.velocity = 70
                    renpy.show_screen("scoreshow")

            elif sleighgame.difficulty == "Ultra Hard":
                if sleighgame.totalrings == 45:
                    sleighgame.gameover = True
                    self.velocity = 70
                    renpy.show_screen("scoreshow")


    class Ring(renpy.Displayable):
        def __init__(self, x, y, images_left, images_right, ringspeed, ring_type, buffer):
            renpy.Displayable.__init__(self)
            self.x = x
            self.y = y
            self.height = 322
            self.width = 144
            self.images_left = [Image(image) for image in left_frames]
            self.images_right = [Image(image) for image in right_frames]
            self.frame_index = 0
            self.collected = False
            self.missed = False
            self.ringspeed = 100
            self.ring_type = ring_type
            self.buffer = buffer

            self.hit_by_projectile = False
            self.hit_by_player = False

            self.right_half_hit = False
            self.ring_instance = None  # Initialize the ring_instance attribute

            # Add frame indices for different stages of animation
            self.animation_frames = [0, 1, 2, 3, 4]
            self.current_frame = self.animation_frames[0]

            # Lists of image paths for each frame
            self.left_frames = images_left
            self.right_frames = images_right

            # New attributes for the moving behavior
            self.moving_y_range = 200  # Adjust the range as needed
            self.moving_direction = 1  # Start moving down
            self.y_initial = y  # Define y_initial here

        def collides_with_player(self, player):
            ring_left = self.x + self.width * 1.2
            sleigh_right = player.px + player.width
            horizontal_hit = (ring_left <= sleigh_right) and (ring_left + self.width >= sleigh_right)
            vertical_hit = (self.y <= player.py + player.height) and (self.y + self.height >= player.py)
            if not player.destroyed:
                return horizontal_hit and vertical_hit

        def collides_with_projectile(self, projectile):
            if projectile is None:
                return False
            proj_x, proj_y = projectile.get_position()
            proj_width, proj_height = projectile.get_dimensions()
            proj_left = proj_x
            proj_right = proj_x + proj_width
            proj_top = proj_y
            proj_bottom = proj_y + proj_height
            
            horizontal_hit = (proj_left <= self.x + self.width) and (proj_right >= self.x)
            vertical_hit = (proj_top <= self.y + self.height) and (proj_bottom >= self.y)
            
            return horizontal_hit and vertical_hit

        def render_left_half(self, width, height, st, at):
            if self.missed or self.x < -self.width:
                return None    

            # Determine the current frame index based on animation status and type
            if self.ring_type == "Moving":
                current_frame_index = 1
            elif self.ring_type == "Static":
                current_frame_index = 0
            elif self.ring_type == "ClosedMoving":
                if self.hit_by_projectile == False:
                    current_frame_index = 4
                else:
                    current_frame_index = 1
            elif self.ring_type == "ClosedStatic":
                if self.hit_by_projectile == False:
                    current_frame_index = 3
                else:
                    current_frame_index = 0

            if self.collected:
                current_frame_index = 2

            # Render the appropriate frame based on the current animation frame
            ring_image_left = renpy.render(self.images_left[current_frame_index], width, height, st, at)
            r = renpy.Render(width, height)
            r.blit(ring_image_left, (int(self.x), int(self.y)))    

            return r    

        def render_right_half(self, width, height, st, at):
            if self.missed or self.x < -self.width:
                return None    

            # Determine the current frame index based on animation status and type
            if self.ring_type == "Moving":
                current_frame_index = 1
            elif self.ring_type == "Static":
                current_frame_index = 0
            elif self.ring_type == "ClosedMoving":
                if self.hit_by_projectile == False:
                    current_frame_index = 4
                else:
                    current_frame_index = 1
            elif self.ring_type == "ClosedStatic":
                if self.hit_by_projectile == False:
                    current_frame_index = 3
                else:
                    current_frame_index = 0
            if self.collected:
                current_frame_index = 2
            # Render the appropriate frame based on the current animation frame
            ring_image_right = renpy.render(self.images_right[current_frame_index], width, height, st, at)

            r = renpy.Render(width, height)
            r.blit(ring_image_right, (int(self.x) + self.width // 2 - 72.5, int(self.y)))    

            return r

        def collect(self):
            self.collected = True
            renpy.play("audio/bells.mp3", "sound")
            sleighgame.totalrings += 1

        def miss(self):
            if self.missed == False:
                self.missed = True 
                sleighgame.totalrings += 1

        def update(self, dtime):

            self.x -= self.ringspeed * dtime + player.velocity 

            if self.x <= -100 and self.collected == False:
                self.miss()

            if self.ring_type == "Moving" or self.ring_type == "ClosedMoving":
                self.y += self.moving_direction * 200 * dtime  # Adjust the speed as needed    

                if self.y <= self.y_initial - self.moving_y_range:
                    self.moving_direction = 1
                elif self.y >= self.y_initial + self.moving_y_range:
                    self.moving_direction = -1


    class Projectile:
        def __init__(self, x, y, speed, frame_delay, animation_frames=None, image_path=None, is_player=False, damage=0, fire_rate=0, spos=None, initial_y_velocity=0, gravity=0, width=0, height=0):
            self.x = x
            self.y = y
            self.speed = speed
            self.animation_frames = animation_frames or []
            self.image_path = image_path
            self.frame_index = 0
            self.frame_delay = frame_delay  # Adjust as needed
            self.time_since_last_frame = 0
            self.is_player = is_player
            self.damage = damage  # Include the damage parameter
            self.fire_rate = fire_rate
            self.frame_delay = frame_delay
            self.spos = spos    

            self.initial_y_velocity = initial_y_velocity  # Initial vertical velocity
            self.gravity = gravity  # Gravity effect
            self.width = width  # Add width attribute
            self.height = height  # Add height attribute

        def update(self, dt):
            if self.animation_frames:
                self.time_since_last_frame += dt
                if self.time_since_last_frame >= self.frame_delay:
                    self.frame_index = (self.frame_index + 1) % len(self.animation_frames)
                    self.time_since_last_frame = 0    

            # Update x position based on speed
            self.x += self.speed * dt    

            # Update y position based on initial_y_velocity and gravity
            self.y += self.initial_y_velocity * dt
            self.initial_y_velocity += self.gravity * dt

        def get_current_frame(self):
            if self.animation_frames:
                return self.animation_frames[self.frame_index]
            else:
                return None

        def get_position(self):
            return self.x, self.y    

        def get_dimensions(self):
            return self.width, self.height                

        def render(self, dtime, width, height, st, at):
            if self.is_player:
                self.x += self.speed * dtime
            else:
                self.x -= self.speed * dtime    

            self.y += self.initial_y_velocity * dtime
            self.initial_y_velocity += self.gravity * dtime    

            projectile_image_path = self.get_current_frame()    

            if projectile_image_path:
                projectile_image = renpy.ImageReference(projectile_image_path)
                r.blit(projectile_image, (int(self.x), int(self.y)))



    class SleighShooter(renpy.Displayable):
        def __init__(self, player, difficulty):

            renpy.Displayable.__init__(self)

            self.score = 0
            self.misses = 0

            self.oldst = None

            self.lose = False

            self.difficulty = sleighring_difficulty

            self.enemies = []

            self.player = player

            self.active_enemy_projectiles = []  # List to store active enemy projectiles=

            self.introd = False
            
            self.starttimer = 0
            self.timer = 0  # Initialize the timer

            self.gameover = False

            # Initialize the rings with their attributes
            if self.difficulty == "Easy":
                rings = initialize_rings(ring_count=23, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="Static", buffer_range=(700, 2000))
                rings += initialize_rings(ring_count=7, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="Moving", buffer_range=(700, 2000))            

            elif self.difficulty == "Medium":
                rings = initialize_rings(ring_count=20, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="Static", buffer_range=(700, 2000))
                rings += initialize_rings(ring_count=5, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="Moving", buffer_range=(700, 2000))            
                rings += initialize_rings(ring_count=5, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="ClosedStatic", buffer_range=(1000, 2000))
                rings += initialize_rings(ring_count=5, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="ClosedMoving", buffer_range=(1000, 2000))  

            elif self.difficulty == "Hard":
                rings = initialize_rings(ring_count=10, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="Static", buffer_range=(700, 2000))
                rings += initialize_rings(ring_count=10, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="Moving", buffer_range=(700, 2000))            
                rings += initialize_rings(ring_count=10, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="ClosedStatic", buffer_range=(1000, 2000))
                rings += initialize_rings(ring_count=10, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="ClosedMoving", buffer_range=(1000, 2000))  

            elif self.difficulty == "Ultra Hard":
                rings = initialize_rings(ring_count=5, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="Static", buffer_range=(700, 2000))
                rings += initialize_rings(ring_count=10, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="Moving", buffer_range=(700, 2000))            
                rings += initialize_rings(ring_count=15, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="ClosedStatic", buffer_range=(1000, 2000))
                rings += initialize_rings(ring_count=15, ring_width=50, images_left=left_frames, images_right=right_frames, ring_type="ClosedMoving", buffer_range=(1000, 2000))  

            # Shuffle the rings list
            import random
            random.shuffle(rings)    

            # Generate rings with proper spacing
            current_x = renpy.config.screen_width  # Start offscreen to the right
            for ring in rings:
                ring.x = current_x
                current_x += ring.buffer + ring.width    

            self.rings = rings  # Assign the shuffled rings back to the class attribute    

            self.totalrings = 0
            return

        @property
        def timer_formatted(self):
            # Format the timer value with 1 decimal place
            return "{:.0f}".format(self.timer)

        def calculate_dtime(self, st):
            if self.oldst is None:
                self.oldst = st    

            dtime = st - self.oldst
            self.oldst = st
            return dtime

        def update_game_state(self):
            if self.introd and not self.gameover:
                if player.health <= 0:
                    player.handle_explosion()  # Trigger the player's explosion
                    renpy.play("audio/Destroyed.mp3", "sfx")  # Play explosion sound


        # Draws the screen
        def render(self, width, height, st, at):

            r = renpy.Render(width, height)


            # Figure out the time elapsed since the previous frame.
            if self.oldst is None:
                self.oldst = st

            dtime = st - self.oldst
            self.oldst = st

            self.starttimer += dtime 

            if self.starttimer >= 5:
                self.introd = True
                
            # Increment the timer
            if self.introd == True and self.gameover == False:
                self.timer += dtime 

            # Collect the render objects for left and right halves of rings
            left_ring_images = []
            right_ring_images = []
            projectiles_to_remove = []


            # Update and render rings
            if self.introd == True:
                for ring in self.rings:
                    ring.update(dtime)        

                    # Render the left half of the ring below the sleigh
                    ring_left_half_render = ring.render_left_half(width, height, st, at)
                    if ring_left_half_render:
                        r.blit(ring_left_half_render, (0, 0))    

        
            if not self.gameover:
                if player.health > 0 and not player.is_exploding:
                    # Render the player's image
                    player_image = renpy.render(player.player_image, width, height, st, at)
                    r.blit(player_image, (int(player.px), int(player.py)))
                elif player.is_exploding:
                    if player.explosion_frame < len(player.explosion_images):
                        explosion_frame = player.explosion_images[player.explosion_frame]
                        explosion_render = renpy.render(explosion_frame, width, height, st, at)
                        r.blit(explosion_render, (int(player.px), int(player.py)))
                    player.update(st - self.starttimer, st, at)  # Advance the explosion animation and update the timer

            # Update and render projectiles
            for projectile in self.player.projectiles:
                projectile.x += projectile.speed * dtime
                projectile.update(dtime)  # Update the current frame of the projectile
                projectile_image_path = projectile.get_current_frame()  # Get the current frame path
                projectile_image = renpy.display.im.Image(projectile_image_path)  # Create an ImageReference object
                projectile_image = renpy.render(projectile_image, width, height, st, at)  # Render the image
                r.blit(projectile_image, (int(projectile.x), int(projectile.y)))

                # Remove projectiles that go off-screen
                if projectile.x > width:
                    self.player.projectiles.remove(projectile)

            # Call player.update(dtime) here
            player.update(dtime, st, at)

            if self.introd == True:
                for ring in self.rings:
                    ring_right_half_render = ring.render_right_half(width, height, st, at)
                    if ring_right_half_render:
                        r.blit(ring_right_half_render, (0, 0))              

                    if ring.collected == False and ring.collides_with_player(player):
                        # Collision with player logic            
                        if ring.ring_type == "ClosedStatic" or ring.ring_type == "ClosedMoving":
                            if ring.hit_by_projectile == False and ring.hit_by_player == False:
                                player.health -= 1 
                                ring.hit_by_player = True 
                                renpy.play("audio/Crash1.mp3", "sound")  # Play collision sound    

                            elif ring.hit_by_projectile == True:
                                ring.collected = True 
                                ring.collect()
                                self.score +=1    

                        elif ring.ring_type == "Static" or ring.ring_type == "Moving":
                            ring.collected = True
                            ring.collect()  # Call the collect method
                            self.score += 1        

                    for projectile in player.projectiles:
                        if ring.collected == False and ring.collides_with_projectile(projectile):
                            # Collision with projectile logic            
                            if ring.ring_type == "ClosedStatic" or ring.ring_type == "ClosedMoving":
                                if ring.hit_by_projectile == False:
                                    ring.hit_by_projectile = True
                                    renpy.play("audio/RingDown.mp3", "sfx")  # Play collision sound
                                    projectiles_to_remove.append(projectile)

            # Check game over conditions
            self.update_game_state()

            # Remove projectiles outside the loop
            for projectile in projectiles_to_remove:
                self.player.projectiles.remove(projectile)

            # Ask that we be re-rendered ASAP, so we can show the next
            # frame.
            renpy.redraw(self, 0)

            # Return the Render object.
            return r

        # Handles events.
        def event(self, ev, x, y, st):
            # Pass the events to the Player instance
            player.event(ev, x, y, st)            

            # Ensure the screen updates.
            renpy.restart_interaction()        

            # Check if the player's health is zero or below and not destroyed.
            if player.health <= 0 and not player.destroyed:
                player.handle_explosion()
                renpy.play("audio/Destroyed.mp3", "sfx")        

            # Set self.lose based on player.destroyed
            self.lose = player.destroyed        

            if self.lose:
                sleighgame.gameover = True
                renpy.show_screen("scoreshow")
            else:
                raise renpy.IgnoreEvent()

    def initialize_rings(ring_count, ring_width, images_left, images_right, ring_type, buffer_range=(600, 800)):
        rings = []
        for i in range(ring_count):
            buffer = random.uniform(*buffer_range)
            ring_x = renpy.config.screen_width + buffer + i * ring_width  # Start offscreen to the right
            ring_y = random.randint(128, renpy.config.screen_height - 300)
            
            if ring_type == "Moving":
                ring = Ring(ring_x, ring_y, images_left, images_right, ringspeed=100, ring_type="Moving", buffer=buffer)
            elif ring_type == "Static":
                ring = Ring(ring_x, ring_y, images_left, images_right, ringspeed=100, ring_type="Static", buffer=buffer)
            elif ring_type == "ClosedMoving":
                ring = Ring(ring_x, ring_y, images_left, images_right, ringspeed=80, ring_type="ClosedMoving", buffer=buffer)
            elif ring_type == "ClosedStatic":
                ring = Ring(ring_x, ring_y, images_left, images_right, ringspeed=80, ring_type="ClosedStatic", buffer=buffer)

            rings.append(ring) 
        return rings


screen sleigh_ride():

    add Image("bg Sky.png")

    add cloudcover

    add cloudfore

    # Add the left side of the rings below the sleigh
    for ring in rings:
        add ring.render_left_half(width, height, st, at)

    add sleighgame

    # Add the right side of the rings above the sleigh
    for ring in rings:
        add ring.render_right_half(width, height, st, at)

    # Display "Get Ready" image at starttimer 1 and remove at starttimer 4
    if sleighgame.starttimer >= 1 and sleighgame.starttimer < 4:
        add Image("images/GetReady.png", style="positioned_image")

    # Display "Fly!" image at starttimer 4 and remove at starttimer 5
    if sleighgame.starttimer >= 4 and sleighgame.starttimer < 5:
        add Image("images/Fly.png", style="positioned_image")

    # Add a text element to display the score at the top
    vbox:
        text "Score: [sleighgame.score]" style "scoreboard"
    vbox:
        text "Time: [sleighgame.timer_formatted]" style "timekeeper"
    # vbox:
    #     text "Explosion Frame: [player.explosion_frame]" style "tester"
    # vbox: 
    #     text "Explosion Timer: [player.explosion_timer]" style "tester2"
    # vbox:
    #     text "Total Rings: [sleighgame.totalrings]" style "tester"
    # vbox:
    #     text "Velocity: [player.velocity]" style "tester2"


    hbox:
        style "w_buttons"
        textbutton "Basic 1" action [SetVariable("weapon_type", "basic"), lambda: update_weapon_type("basic")]
        textbutton "Basic 2" action [SetVariable("weapon_type", "basic2"), lambda: update_weapon_type("basic2")]
        textbutton "Basic 3" action [SetVariable("weapon_type", "basic3"), lambda: update_weapon_type("basic3")]
        textbutton "Tree 1" action [SetVariable("weapon_type", "tree"), lambda: update_weapon_type("tree")]
        textbutton "Tree 2" action [SetVariable("weapon_type", "tree2"), lambda: update_weapon_type("tree2")]
        textbutton "Gingerbread" action [SetVariable("weapon_type", "gbread"), lambda: update_weapon_type("gbread")]
        textbutton "Reindeer" action [SetVariable("weapon_type", "rdeer"), lambda: update_weapon_type("rdeer")]


    if player.health == 1:
        vbox:
            add Image("images/SleighShield4.png")
            xpos 30 ypos 10
    elif player.health == 2:
        vbox:
            add Image("images/SleighShield3.png")
            xpos 30 ypos 10
    elif player.health == 3:
        vbox:
            add Image("images/SleighShield2.png")
            xpos 30 ypos 10
    elif player.health == 4:
        vbox:
            add Image("images/SleighShield1.png")
            xpos 30 ypos 10

    if player.gauge == 0:
        vbox:
            add Image("images/Gauge/gauge0.png")
            xpos 20 ypos 120
    elif player.gauge == 1:
        vbox:
            add Image("images/Gauge/gauge1.png")
            xpos 20 ypos 120
    elif player.gauge == 2:
        vbox:
            add Image("images/Gauge/gauge2.png")
            xpos 20 ypos 120
    elif player.gauge == 3:
        vbox:
            add Image("images/Gauge/gauge3.png")
            xpos 20 ypos 120
    elif player.gauge == 4:
        vbox:
            add Image("images/Gauge/gauge4.png")
            xpos 20 ypos 120
    elif player.gauge == 5:
        vbox:
            add Image("images/Gauge/gauge5.png")
            xpos 20 ypos 120
    elif player.gauge == 6:
        vbox:
            add Image("images/Gauge/gauge6.png")
            xpos 20 ypos 120
    elif player.gauge == 7:
        vbox:
            add Image("images/Gauge/gauge7.png")
            xpos 20 ypos 120


screen scoreshow():
    vbox:
        imagebutton:
            idle "images/bg transparency.png"
            action Jump("game_over")

    if player.health == 0:
        add Image("images/GameOver.png", style="positioned_image")

    else:
        if sleighgame.difficulty == "Easy":
            if sleighgame.score == 30:    
                add Image("images/Perfect_Score.png", style="positioned_image")
            elif sleighgame.score >= 25:    
                add Image("images/Great_Score.png", style="positioned_image")
            elif sleighgame.score >= 20:    
                add Image("images/Good_Score.png", style="positioned_image")
            elif sleighgame.score >= 15:    
                add Image("images/Okay_Score.png", style="positioned_image")
            elif sleighgame.score <= 14:
                add Image("images/Bad_Score.png", style="positioned_image")    

        elif sleighgame.difficulty == "Medium":
            if sleighgame.score == 35 and player.health == 4:    
                add Image("images/Perfect_Game.png", style="positioned_image")
            elif sleighgame.score == 35:    
                add Image("images/Perfect_Score.png", style="positioned_image")
            elif sleighgame.score >= 30:    
                add Image("images/Great_Score.png", style="positioned_image")
            elif sleighgame.score >= 20:    
                add Image("images/Good_Score.png", style="positioned_image")
            elif sleighgame.score >= 15:    
                add Image("images/Okay_Score.png", style="positioned_image")
            elif sleighgame.score <= 14:
                add Image("images/Bad_Score.png", style="positioned_image")    

        elif sleighgame.difficulty == "Hard":
            if sleighgame.score == 40 and player.health == 4:    
                add Image("images/Perfect_Game.png", style="positioned_image")
            elif sleighgame.score == 40:    
                add Image("images/Perfect_Score.png", style="positioned_image")
            elif sleighgame.score >= 30:    
                add Image("images/Great_Score.png", style="positioned_image")
            elif sleighgame.score >= 20:    
                add Image("images/Good_Score.png", style="positioned_image")
            elif sleighgame.score >= 15:    
                add Image("images/Okay_Score.png", style="positioned_image")
            elif sleighgame.score <= 14:
                add Image("images/Bad_Score.png", style="positioned_image")    

        elif sleighgame.difficulty == "Ultra Hard":
            if sleighgame.score == 45 and player.health == 4:    
                add Image("images/Perfect_Game.png", style="positioned_image")
            elif sleighgame.score == 40:    
                add Image("images/Perfect_Score.png", style="positioned_image")
            elif sleighgame.score >= 35:    
                add Image("images/Great_Score.png", style="positioned_image")
            elif sleighgame.score >= 25:    
                add Image("images/Good_Score.png", style="positioned_image")
            elif sleighgame.score >= 15:    
                add Image("images/Okay_Score.png", style="positioned_image")
            elif sleighgame.score <= 14:
                add Image("images/Bad_Score.png", style="positioned_image")

screen highscore_table():
    hbox:
        spacing 30  # Adjust the spacing between frames as needed
        xalign 0.5
        yalign 0.5

        frame:
            style "frame_easy"
            vbox:
                text "Easy" style "title"
                xalign 0.5 yalign 0.05
                # Display the Easy high score entries
                for i, (name, score, time) in enumerate(persistent.easy_scores, 1):
                    hbox:
                        spacing 40
                        text "[i]. [name]" style "table_entry"
                        text "[score]" style "table_entry"
                        text "[time]" style "table_entry"

        frame:
            style "frame_medium"
            vbox:
                text "Medium" style "title"
                xalign 0.5 yalign 0.05
                # Display the Medium high score entries
                for i, (name, score, time) in enumerate(persistent.medium_scores, 1):
                    hbox:
                        spacing 40
                        text "[i]. [name]" style "table_entry"
                        text "[score]" style "table_entry"
                        text "[time]" style "table_entry"

        frame:
            style "frame_hard"
            vbox:
                text "Hard" style "title"
                xalign 0.5 yalign 0.05
                # Display the Hard high score entries
                for i, (name, score, time) in enumerate(persistent.hard_scores, 1):
                    hbox:
                        spacing 40
                        text "[i]. [name]" style "table_entry"
                        text "[score]" style "table_entry"
                        text "[time]" style "table_entry"

        frame:
            style "frame_ultra_hard"
            vbox:
                text "Ultra Hard" style "title"
                xalign 0.5 yalign 0.05
                # Display the Ultra Hard high score entries
                for i, (name, score, time) in enumerate(persistent.ultra_hard_scores, 1):
                    hbox:
                        spacing 20
                        text "[i]. [name]" style "table_entry"
                        text "[score]" style "table_entry"
                        text "[time]" style "table_entry"

    vbox:
        imagebutton:
            idle "images/bg transparency.png"
            action Jump("playagain")

screen play_again_prompt():
    modal True
    frame:
        style "white_frame" 
        vbox:
            text "{color=#FF0000}Do you want to play again?{/color}"
            textbutton "{color=#FF0000}Yes{/color}" action Jump("start")
            textbutton "{color=#FF0000}No{/color}" action Return()

Post Reply

Who is online

Users browsing this forum: No registered users