BreakingExpress

Add throwing mechanics to your Python sport

This is a component 12 in an ongoing sequence about creating video video games in Python 3 utilizing the Pygame module. Previous articles are:

  1. Learn how to program in Python by building a simple dice game
  2. Build a game framework with Python using the Pygame module
  3. How to add a player to your Python game
  4. Using Pygame to move your game character around
  5. What’s a hero without a villain? How to add one to your Python game
  6. Put platforms in a Python game with Pygame
  7. Simulate gravity in your Python game
  8. Add jumping to your Python platformer game
  9. Enable your Python game player to run forward and backward
  10. Put some loot in your Python platformer game
  11. Add scorekeeping to your Python game

My earlier article was meant to be the ultimate article on this sequence, and it inspired you to go program your individual additions to this sport. Many of you probably did! I bought emails asking for assist with a standard mechanic that I hadn’t but coated: fight. After all, leaping to keep away from baddies is one factor, however typically it is awfully satisfying to only make them go away. It’s widespread in video video games to toss stuff at your enemies, whether or not it is a ball of fireplace, an arrow, a bolt of lightning, or no matter else matches the sport.

Unlike something you may have programmed on your platformer sport on this sequence to date, throwable gadgets have a time to dwell. Once you throw an object, it is anticipated to journey a long way after which disappear. If it is an arrow or one thing like that, it could disappear when it passes the sting of the display. If it is a fireball or a bolt of lightning, it would fizzle out after some period of time.

That means every time a throwable merchandise is spawned, a novel measure of its lifespan should even be spawned. To introduce this idea, this text demonstrates tips on how to throw just one merchandise at a time. (In different phrases, just one throwable merchandise could exist at a time.) On the one hand, it is a sport limitation, however then again, it’s a sport mechanic in itself. Your participant will not have the ability to throw 50 fireballs without delay, because you solely permit one after the other, so it turns into a problem on your participant to time after they launch a fireball to attempt to hit an enemy. And behind the scenes, this additionally retains your code easy.

If you need to allow extra throwable gadgets without delay, problem your self after you end this tutorial by constructing on the data you acquire.

Create the throwable class

If you adopted together with the opposite articles on this sequence, you ought to be acquainted with the essential __init__ perform when spawning a brand new object on the display. It’s the identical perform you used for spawning your player and your enemies. Here’s an __init__ perform to spawn a throwable object:

class Throwable(pygame.sprite.Sprite):
    """
    Spawn a throwable object
    """

    def __init__(self, x, y, img, throw):
        pygame.sprite.Sprite.__init__(self)
        self.picture = pygame.picture.load(os.path.be a part of('photos',img))
        self.picture.convert_alpha()
        self.picture.set_colorkey(ALPHA)
        self.rect   = self.picture.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.firing = throw

The major distinction on this perform in comparison with your Player class or Enemy class __init__ perform is that it has a self.firing variable. This variable retains observe of whether or not or not a throwable object is at present alive on display, so it stands to cause that when a throwable object is created, the variable is ready to 1.

Measure time to dwell

Next, simply as with Player and Enemy, you want an replace perform in order that the throwable object strikes by itself as soon as it is thrown into the air towards an enemy.

The best approach to decide the lifespan of a throwable object is to detect when it goes off-screen. Which display edge it’s worthwhile to monitor depends upon the physics of your throwable object.

  • If your participant is throwing one thing that travels rapidly alongside the horizontal axis, like a crossbow bolt or arrow or a really quick magical pressure, then you definitely need to monitor the horizontal restrict of your sport display. This is outlined by worldx.
  • If your participant is throwing one thing that travels vertically or each horizontally and vertically, then you should monitor the vertical restrict of your sport display. This is outlined by worldy.

This instance assumes your throwable object goes just a little ahead and finally falls to the bottom. The object doesn’t bounce off the bottom, although, and continues to fall off the display. You can strive completely different settings to see what matches your sport greatest:

    def replace(self,worldy):
        '''
        throw physics
        '''

        if self.rect.y < worldy: #vertical axis
            self.rect.x  += 15 #how briskly it strikes ahead
            self.rect.y  += 5  #how briskly it falls
        else:
            self.kill()     #take away throwable object
            self.firing = zero #unencumber firing slot

To make your throwable object transfer sooner, enhance the momentum of the self.rect values.

If the throwable object is off-screen, then the thing is destroyed, releasing up the RAM that it had occupied. In addition, self.firing is ready again to zero to permit your participant to take one other shot.

Set up your throwable object

Just like together with your participant and enemies, you should create a sprite group in your setup part to carry the throwable object.

Additionally, you should create an inactive throwable object to begin the sport with. If there is not a throwable object when the sport begins, the primary time a participant makes an attempt to throw a weapon, it can fail.

This instance assumes your participant begins with a fireball as a weapon, so every occasion of a throwable object is designated by the hearth variable. In later ranges, because the participant acquires new expertise, you can introduce a brand new variable utilizing a special picture however leveraging the identical Throwable class.

In this block of code, the primary two strains are already in your code, so do not retype them:

player_list = pygame.sprite.Group() #context
player_list.add(participant)             #context
hearth = Throwable(participant.rect.x,participant.rect.y,'hearth.png',zero)
firepower = pygame.sprite.Group()

Notice that a throwable merchandise begins on the similar location because the participant. That makes it appear to be the throwable merchandise is coming from the participant. The first time the fireball is generated, a zero is used in order that self.firing exhibits as out there.

Get throwing in the principle loop

Code that does not seem in the principle loop won’t be used within the sport, so it’s worthwhile to add a couple of issues in your primary loop to get your throwable object into your sport world.

First, add participant controls. Currently, you haven’t any firepower set off. There are two states for a key on a keyboard: the important thing may be down, or the important thing may be up. For motion, you employ each: urgent down begins the participant transferring, and releasing the important thing (the hot button is up) stops the participant. Firing wants just one sign. It’s a matter of style as to which key occasion (a key press or a key launch) you employ to set off your throwable object.

In this code block, the primary two strains are for context:

            if occasion.key == pygame.K_UP or occasion.key == ord('w'):
                participant.soar(platform_list)
            if occasion.key == pygame.K_SPACE:
                if not hearth.firing:
                    hearth = Throwable(participant.rect.x,participant.rect.y,'hearth.png',1)
                    firepower.add(hearth)

Unlike the fireball you created in your setup part, you employ a 1 to set self.firing as unavailable.

Finally, you should replace and draw your throwable object. The order of this issues, so put this code between your current enemy.transfer and player_list.draw strains:

    enemy.transfer()  # context

    if hearth.firing:
        hearth.replace(worldy)
        firepower.draw(world)
    player_list.draw(display)  # context
    enemy_list.draw(display)   # context

Notice that these updates are carried out provided that the self.firing variable is ready to 1. If it’s set to zero, then hearth.firing isn’t true, and the updates are skipped. If you tried to do these updates, it doesn’t matter what, your sport would crash as a result of there would not be a hearth object to replace or draw.

Launch your sport and attempt to throw your weapon.

Detect collisions

If you performed your sport with the brand new throwing mechanic, you most likely seen you could throw objects, nevertheless it does not have any impact in your foes.

The cause is that your enemies don’t verify for a collision. An enemy may be hit by your throwable object and by no means find out about it.

You’ve already carried out collision detection in your Player class, and that is very related. In your Enemy class, add a brand new replace perform:

    def replace(self,firepower, enemy_list):
        """
        detect firepower collision
        """

        fire_hit_list = pygame.sprite.spritecollide(self,firepower,False)
        for hearth in fire_hit_list:
            enemy_list.take away(self)

The code is straightforward. Each enemy object checks to see if it has been hit by the firepower sprite group. If it has, then the enemy is faraway from the enemy group and disappears.

To combine that perform into your sport, name the perform in your new firing block in the principle loop:

    if hearth.firing:                             # context
        hearth.replace(worldy)                    # context
        firepower.draw(display)                  # context
        enemy_list.replace(firepower,enemy_list) # replace enemy

You can strive your sport now, and most every little thing works as anticipated. There’s nonetheless one drawback, although, and that is the route of the throw.

Change the throw mechanic route

Currently, your hero’s fireball strikes solely to the proper. This is as a result of the replace perform of the Throwable class provides pixels to the place of the fireball, and in Pygame, a bigger quantity on the X-axis means motion towards the proper of the display. When your hero turns the opposite method, you most likely need it to throw its fireball to the left.

By this level, you understand how to implement this, no less than technically. However, the best resolution makes use of a variable in what could also be a brand new method for you. Generically, you possibly can “set a flag” (typically additionally termed “flip a bit”) to point the route your hero is dealing with. Once you do this, you possibly can verify that variable to be taught whether or not the fireball wants to maneuver left or proper.

First, create a brand new variable in your Player class to signify which route your hero is dealing with. Because my hero faces proper naturally, I deal with that because the default:

        self.rating = zero
        self.facing_right = True  # add this
        self.is_jumping = True

When this variable is True, your hero sprite is dealing with proper. It should be set anew each time the participant adjustments the hero’s route, so do this in your primary loop on the related keyup occasions:

        if occasion.sort == pygame.KEYUP:
            if occasion.key == pygame.K_LEFT or occasion.key == ord('a'):
                participant.management(steps, zero)
                participant.facing_right = False  # add this line
            if occasion.key == pygame.K_RIGHT or occasion.key == ord('d'):
                participant.management(-steps, zero)
                participant.facing_right = True  # add this line

Finally, change the replace perform of your Throwable class to verify whether or not the hero is dealing with proper or not and so as to add or subtract pixels from the fireball’s place as acceptable:

        if self.rect.y < worldy:
            if participant.facing_right:
                self.rect.x += 15
            else:
                self.rect.x -= 15
            self.rect.y += 5

Try your sport once more and clear your world of some baddies.

As a bonus problem, strive incrementing your participant’s rating at any time when an enemy is vanquished.

The full code

#!/usr/bin/env python3
# by Seth Kenlon

# GPLv3
# This program is free software program: you possibly can redistribute it and/or
# modify it below the phrases of the GNU General Public License as
# printed by the Free Software Foundation, both model three of the
# License, or (at your choice) any later model.
#
# This program is distributed within the hope that it will likely be helpful, however
# WITHOUT ANY WARRANTY; with out even the implied guarantee of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for extra particulars.
#
# You ought to have acquired a duplicate of the GNU General Public License
# together with this program.  If not, see <http://www.gnu.org/licenses/>.

import pygame
import pygame.freetype
import sys
import os

'''
Variables
'''

worldx = 960
worldy = 720
fps = 40
ani = four
world = pygame.show.set_mode([worldx, worldy])
forwardx  = 600
backwardx = 120

BLUE = (80, 80, 155)
BLACK = (23, 23, 23)
WHITE = (254, 254, 254)
ALPHA = (zero, 255, zero)

tx = 64
ty = 64

font_path = os.path.be a part of(os.path.dirname(os.path.realpath(__file__)), "fonts", "amazdoom.ttf")
font_size = tx
pygame.freetype.init()
myfont = pygame.freetype.Font(font_path, font_size)

'''
Objects
'''

def stats(rating, well being):
    myfont.render_to(world, (four, four), "Score:"+str(rating), BLUE, None, measurement=64)
    myfont.render_to(world, (four, 72), "Health:"+str(well being), BLUE, None, measurement=64)

class Throwable(pygame.sprite.Sprite):
    """
    Spawn a throwable object
    """

    def __init__(self, x, y, img, throw):
        pygame.sprite.Sprite.__init__(self)
        self.picture = pygame.picture.load(os.path.be a part of('photos', img))
        self.picture.convert_alpha()
        self.picture.set_colorkey(ALPHA)
        self.rect = self.picture.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.firing = throw

    def replace(self, worldy):
        '''
        throw physics
        '''

        if self.rect.y < worldy:
            if participant.facing_right:
                self.rect.x += 15
            else:
                self.rect.x -= 15
            self.rect.y += 5
        else:
            self.kill()
            self.firing = zero

# x location, y location, img width, img top, img file
class Platform(pygame.sprite.Sprite):
    def __init__(self, xloc, yloc, imgw, imgh, img):
        pygame.sprite.Sprite.__init__(self)
        self.picture = pygame.picture.load(os.path.be a part of('photos', img)).convert()
        self.picture.convert_alpha()
        self.picture.set_colorkey(ALPHA)
        self.rect = self.picture.get_rect()
        self.rect.y = yloc
        self.rect.x = xloc

class Player(pygame.sprite.Sprite):
    """
    Spawn a participant
    """

    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.movex = zero
        self.movey = zero
        self.body = zero
        self.well being = 10
        self.harm = zero
        self.rating = zero
        self.facing_right = True
        self.is_jumping = True
        self.is_falling = True
        self.photos = []
        for i in vary(1, 5):
            img = pygame.picture.load(os.path.be a part of('photos', 'stroll' + str(i) + '.png')).convert()
            img.convert_alpha()
            img.set_colorkey(ALPHA)
            self.photos.append(img)
            self.picture = self.photos[zero]
            self.rect = self.picture.get_rect()

    def gravity(self):
        if self.is_jumping:
            self.movey += three.2

    def management(self, x, y):
        """
        management participant motion
        """

        self.movex += x

    def soar(self):
        if self.is_jumping is False:
            self.is_falling = False
            self.is_jumping = True

    def replace(self):
        """
        Update sprite place
        """

        # transferring left
        if self.movex < zero:
            self.is_jumping = True
            self.body += 1
            if self.body > three * ani:
                self.body = zero
            self.picture = pygame.remodel.flip(self.photos[self.body // ani], True, False)

        # transferring proper
        if self.movex > zero:
            self.is_jumping = True
            self.body += 1
            if self.body > three * ani:
                self.body = zero
            self.picture = self.photos[self.body // ani]

        # collisions
        enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False)
        if self.harm == zero:
            for enemy in enemy_hit_list:
                if not self.rect.incorporates(enemy):
                    self.harm = self.rect.colliderect(enemy)
        if self.harm == 1:
            idx = self.rect.collidelist(enemy_hit_list)
            if idx == -1:
                self.harm = zero   # set harm again to zero
                self.well being -= 1  # subtract 1 hp

        ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False)
        for g in ground_hit_list:
            self.movey = zero
            self.rect.backside = g.rect.prime
            self.is_jumping = False  # cease leaping

        # fall off the world
        if self.rect.y > worldy:
            self.well being -=1
            print(self.well being)
            self.rect.x = tx
            self.rect.y = ty

        plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False)
        for p in plat_hit_list:
            self.is_jumping = False  # cease leaping
            self.movey = zero
            if self.rect.backside <= p.rect.backside:
               self.rect.backside = p.rect.prime
            else:
               self.movey += three.2

        if self.is_jumping and self.is_falling is False:
            self.is_falling = True
            self.movey -= 33  # how excessive to leap

        loot_hit_list = pygame.sprite.spritecollide(self, loot_list, False)
        for loot in loot_hit_list:
            loot_list.take away(loot)
            self.rating += 1
            print(self.rating)

        plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False)

        self.rect.x += self.movex
        self.rect.y += self.movey

class Enemy(pygame.sprite.Sprite):
    """
    Spawn an enemy
    """

    def __init__(self, x, y, img):
        pygame.sprite.Sprite.__init__(self)
        self.picture = pygame.picture.load(os.path.be a part of('photos', img))
        self.picture.convert_alpha()
        self.picture.set_colorkey(ALPHA)
        self.rect = self.picture.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.counter = zero

    def transfer(self):
        """
        enemy motion
        """

        distance = 80
        velocity = eight

        if self.counter >= zero and self.counter <= distance:
            self.rect.x += velocity
        elif self.counter >= distance and self.counter <= distance * 2:
            self.rect.x -= velocity
        else:
            self.counter = zero

        self.counter += 1

    def replace(self, firepower, enemy_list):
        """
        detect firepower collision
        """

        fire_hit_list = pygame.sprite.spritecollide(self, firepower, False)
        for hearth in fire_hit_list:
            enemy_list.take away(self)

class Level:
    def floor(lvl, gloc, tx, ty):
        ground_list = pygame.sprite.Group()
        i = zero
        if lvl == 1:
            whereas i < len(gloc):
                floor = Platform(gloc[i], worldy - ty, tx, ty, 'tile-ground.png')
                ground_list.add(floor)
                i = i + 1

        if lvl == 2:
            print("Level " + str(lvl))

        return ground_list

    def unhealthy(lvl, eloc):
        if lvl == 1:
            enemy = Enemy(eloc[zero], eloc[1], 'enemy.png')
            enemy_list = pygame.sprite.Group()
            enemy_list.add(enemy)
        if lvl == 2:
            print("Level " + str(lvl))

        return enemy_list

    # x location, y location, img width, img top, img file
    def platform(lvl, tx, ty):
        plat_list = pygame.sprite.Group()
        ploc = []
        i = zero
        if lvl == 1:
            ploc.append((200, worldy - ty - 128, three))
            ploc.append((300, worldy - ty - 256, three))
            ploc.append((550, worldy - ty - 128, four))
            whereas i < len(ploc):
                j = zero
                whereas j <= ploc[i][2]:
                    plat = Platform((ploc[i][zero] + (j * tx)), ploc[i][1], tx, ty, 'tile.png')
                    plat_list.add(plat)
                    j = j + 1
                print('run' + str(i) + str(ploc[i]))
                i = i + 1

        if lvl == 2:
            print("Level " + str(lvl))

        return plat_list

    def loot(lvl):
        if lvl == 1:
            loot_list = pygame.sprite.Group()
            loot = Platform(tx*5, ty*5, tx, ty, 'loot_1.png')
            loot_list.add(loot)

        if lvl == 2:
            print(lvl)

        return loot_list

'''
Setup
'''

backdrop = pygame.picture.load(os.path.be a part of('photos', 'stage.png'))
clock = pygame.time.Clock()
pygame.init()
backdropbox = world.get_rect()
primary = True

participant = Player()  # spawn participant
participant.rect.x = zero  # go to x
participant.rect.y = 30  # go to y
player_list = pygame.sprite.Group()
player_list.add(participant)
steps = 10
hearth = Throwable(participant.rect.x, participant.rect.y, 'hearth.png', zero)
firepower = pygame.sprite.Group()

eloc = []
eloc = [300, worldy-ty-80]
enemy_list = Level.unhealthy(1, eloc)
gloc = []

i = zero
whereas i <= (worldx / tx) + tx:
    gloc.append(i * tx)
    i = i + 1

ground_list = Level.floor(1, gloc, tx, ty)
plat_list = Level.platform(1, tx, ty)
enemy_list = Level.unhealthy( 1, eloc )
loot_list = Level.loot(1)

'''
Main Loop
'''

whereas primary:
    for occasion in pygame.occasion.get():
        if occasion.sort == pygame.QUIT:
            pygame.give up()
            strive:
                sys.exit()
            lastly:
                primary = False

        if occasion.sort == pygame.KEYDOWN:
            if occasion.key == ord('q'):
                pygame.give up()
                strive:
                    sys.exit()
                lastly:
                    primary = False
            if occasion.key == pygame.K_LEFT or occasion.key == ord('a'):
                participant.management(-steps, zero)
            if occasion.key == pygame.K_RIGHT or occasion.key == ord('d'):
                participant.management(steps, zero)
            if occasion.key == pygame.K_UP or occasion.key == ord('w'):
                participant.soar()

        if occasion.sort == pygame.KEYUP:
            if occasion.key == pygame.K_LEFT or occasion.key == ord('a'):
                participant.management(steps, zero)
                participant.facing_right = False
            if occasion.key == pygame.K_RIGHT or occasion.key == ord('d'):
                participant.management(-steps, zero)
                participant.facing_right = True
            if occasion.key == pygame.K_SPACE:
                if not hearth.firing:
                    hearth = Throwable(participant.rect.x, participant.rect.y, 'hearth.png', 1)
                    firepower.add(hearth)

    # scroll the world ahead
    if participant.rect.x >= forwardx:
        scroll = participant.rect.x - forwardx
        participant.rect.x = forwardx
        for p in plat_list:
            p.rect.x -= scroll
        for e in enemy_list:
            e.rect.x -= scroll
        for l in loot_list:
            l.rect.x -= scroll

    # scroll the world backward
    if participant.rect.x <= backwardx:
        scroll = backwardx - participant.rect.x
        participant.rect.x = backwardx
        for p in plat_list:
            p.rect.x += scroll
        for e in enemy_list:
            e.rect.x += scroll
        for l in loot_list:
            l.rect.x += scroll

    world.blit(backdrop, backdropbox)
    participant.replace()
    participant.gravity()
    player_list.draw(world)
    if hearth.firing:
        hearth.replace(worldy)
        firepower.draw(world)
    enemy_list.draw(world)
    enemy_list.replace(firepower, enemy_list)
    loot_list.draw(world)
    ground_list.draw(world)
    plat_list.draw(world)
    for e in enemy_list:
        e.transfer()
    stats(participant.rating, participant.well being)
    pygame.show.flip()
    clock.tick(fps)

 

Exit mobile version