This is a component 12 in an ongoing sequence about creating video video games in Python 3 utilizing the Pygame module. Previous articles are:
- Learn how to program in Python by building a simple dice game
- Build a game framework with Python using the Pygame module
- How to add a player to your Python game
- Using Pygame to move your game character around
- What’s a hero without a villain? How to add one to your Python game
- Put platforms in a Python game with Pygame
- Simulate gravity in your Python game
- Add jumping to your Python platformer game
- Enable your Python game player to run forward and backward
- Put some loot in your Python platformer game
- 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() # contextif 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 = 120BLUE = (80, 80, 155)
BLACK = (23, 23, 23)
WHITE = (254, 254, 254)
ALPHA = (zero, 255, zero)tx = 64
ty = 64font_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 = throwdef 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 = xlocclass 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.2def management(self, x, y):
"""
management participant motion
"""
self.movex += xdef soar(self):
if self.is_jumping is False:
self.is_falling = False
self.is_jumping = Truedef 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 hpground_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 = typlat_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.2if self.is_jumping and self.is_falling is False:
self.is_falling = True
self.movey -= 33 # how excessive to leaploot_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.moveyclass 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 = zerodef transfer(self):
"""
enemy motion
"""
distance = 80
velocity = eightif 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 = zeroself.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 + 1if 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 + 1if 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 = Trueparticipant = 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 + 1ground_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 = Falseif 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 += scrollworld.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)