The Python Game Book

code games. learn Python.

User Tools

Site Tools


Sidebar

Github:

en:pygame:step020

Step 020 - Shooting bullets from the end of a cannon barrel

video

description

screenshot of tankdemo In this example, two tanks can be controlled by the players (using both hands), moving forward and backward and rotating. Additionally, the turrets can rotate also. The turrets can shoot bullets out of the tanks's main cannon (please admire the recoil effect) and the tanks can fire tracer rounds from machine guns. Each gun has a machine gun at the turret and at its bow (see graphic at the right).
This source code examples teaches nothing new but demonstrate how to solve a specific problem: Creating bullet-sprites not at the center of it's launcher, but at the end (and some space away) from it. The most obvious solution for such a problem would be to create the bullet at the center of it's launcher (the Tank) and use the Layer system to make sure the Tank is drawn on top of the bullet.
But if you have a fine eye you will notice some ugly effects: if you rotate a cannon fast enough, it will look like the bullet exits at the side of the cannon instead of at it's end. Also creating one Sprite at the exact position of another sprite will trigger a collision detection, needing more code to make sure that a tank cannot shoot itself.

source code discussion

To deal with the problem of creating a bullet sprite at the exact end of a rotated cannon sprite, see the source code below. All you need is a little knowledge of the math.sin and math.cos function (remember to transform grad into radiant), like explained in step017 - rotating, shooting, inheritance.

If you like complicated explanations: What you do here is creating a vector and rotating it to find the coordinate of a point (the musszle). This is done in the methods calculate_origin of the Bullet and the Tracer class:

Bullet

For the Bullet, shooting out of the muzzle of the tanks main gun the problem is this: It's boss sprite, the Tank turret, is rotated by turretAngle. Also the cannon is very long, nearly as long as the side of the Tank.

   def calculate_origin(self):
        # - spawn bullet at end of turret barrel instead tank center -
        # cannon is around Tank.side long, calculatet from Tank center
        # later subtracted 20 pixel from this distance
        # so that bullet spawns closer to tank muzzle
        self.pos[0] +=  math.cos(degrees_to_radians(self.boss.turretAngle)) * (Tank.side-20)
        self.pos[1] +=  math.sin(degrees_to_radians(-self.boss.turretAngle)) * (Tank.side-20)
        

Tracer

For the Tracer (shooting out of the red bow rectangle of the tank) the point of launching is the little red rectangle in the front of the Tank. Because the Tank can rotate it's turret independent of the tank's own rotation, the important variable here is tankangle. The bow machine gun rectangle is not so much distanced from the Tank's center (side/2), but it is a bit on the left side. De facto i created here an 2D-Vector, 30° from the Tank's center and with the lenght of Tank.side/2. This vector is rotated with the tankAngle to find the coordinates of the point of origin for the Tracer round.

    def calculate_origin(self):
        """overwriting because another point of origin is needed"""
        # - spawn bullet at end of bow rect (and some extra distance)
        # the bow rect is in the middle -left from the tank center
        # calculatet by going -30° from the Tank center for the half tank side
        self.pos[0] +=  math.cos(degrees_to_radians(30+self.boss.tankAngle)) * (Tank.side/2)
        self.pos[1] +=  math.sin(degrees_to_radians(-30-self.boss.tankAngle)) * (Tank.side/2)

keyboard overflow problem

While playing you will notice that sometimes some keyboard command seem to be ignored when you press several keys at once. It also can happen that if you press some combinations of more than 2 keys at the same time, it seems like a different key was pressed (ghost)
To test it out:

  • run example tankdemo03.py from below.
  • press <key>w</key> and <key>s</key> to move the tank forward and back (works)
  • press <key>w</key> and <key>s</key> at the same time, to stop the tank movement (works also)
  • if you press <key>d</key> and <key>a</key> at the same time, the tank does not rotate (correctly)
  • while pressing <key>d</key> and <key>a</key>, also press <key>w</key> or <key>s</key>. This time, it works (the tank does not rotate, but moves forward & back). In this case, 3 keys are pressed simultaneously and interpreted correctly.
  • the combinations <key>w</key> and <key>a</key> , <key>s</key> and <key>a</key>, <key>w</key> and <key>d</key>, <key>s</key> and <key>d</key> all work, letting the tank rotate and move at the same time
  • while pressing <key>w</key> and <key>s</key> at the same time (no movement), also press <key>d</key> to rotate the tank. This does not work ! Worse, the bow mg start firing, but you have not pressed <key>LCTRL</key> ! (ghost key effect)

This phenomenon is well known among game designer. Ultimately it is caused by the way how keyboards are constructed.

A possible solution is to design games with fewer keys to be pressed and use keys like SHIFT, ALT and CTRL because those keys are better recognized by design of the hardware. Also think about accepting input from Mouse and Joysticks (see pygame documentation) or writing network-games where each player has his own keyboard.

See the glossary entry keyboard for more information. You will find there a very cool program1) to test out how many keys you can press at the same time without confusing your keyboard.

additional resources

no additional resources necessary.

classchart

classchart020 for tankdemo02.py

ideas

<note tip>Can you add sound effects to the Tankdemo ? See step010 - using sound and music</note> <note tip>Can you create moving or stationary practice targets for the Tankdemo ? See step017 - rotating, shooting...</note>

source code on github

To run this example you need:

file in folder download
020_shooting_from_tank.py pygame Download the whole Archive with all files from Github:
https://github.com/horstjens/ThePythonGameBook/archives/master

View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/020_shooting_from_tank.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
020_shooting_from_tank.py
demo of tank game with rotating turrets
url: http://thepythongamebook.com/en:part2:pygame:step020
author: horst.jens@spielend-programmieren.at
licence: gpl, see http://www.gnu.org/licenses/gpl.html

demo of 2 tanks shooting bullets at the end of it's cannon
and shooting tracers at the end of it's bow Machine Gun
and from the turret-machine gun (co-axial with main gun)

works with pyhton3.4 and python2.7
"""

#the next line is only needed for python2.x and not necessary for python3.x
from __future__ import print_function, division
 
import pygame
import random
import math
GRAD = math.pi / 180 # 2 * pi / 360   # math module needs Radiant instead of Grad
 
class Config(object):
    """ a class to hold all game constants that may be modded by the user"""
    fullscreen = False
    width = 640
    height = 480
    fps = 100
    xtiles = 30 # how many grid tiles for x axis
    ytiles = 20 # how many grid tiles for y axis
 
class Text(pygame.sprite.Sprite):
    """ a helper class to write text on the screen """
    number = 0 
    book = {}
    def __init__(self, pos, msg):
        self.number = Text.number # get a unique number
        Text.number += 1 # prepare number for next Textsprite
        Text.book[self.number] = self # store myself into the book
        pygame.sprite.Sprite.__init__(self, self.groups)
        self.pos = [0.0,0.0]
        self.pos[0] = pos[0]
        self.pos[1] = pos[1]
        self.msg = msg
        self.changemsg(msg)
 
    def update(self, seconds):        
        pass
 
    def changemsg(self,msg):
        self.msg = msg
        self.image = write(self.msg)
        self.rect = self.image.get_rect()
        self.rect.centerx = self.pos[0]
        self.rect.centery = self.pos[1]
 
class Bullet(pygame.sprite.Sprite):
    """ a big projectile fired by the tank's main cannon"""
    side = 7 # small side of bullet rectangle
    vel = 180 # velocity
    mass = 50
    maxlifetime = 10.0 # seconds
    def __init__(self, boss):
        pygame.sprite.Sprite.__init__(self, self.groups) # THE most important line !
        self.boss = boss
        self.dx = 0
        self.dy = 0
        self.angle = 0
        self.lifetime = 0.0
        self.color = self.boss.color
        self.calculate_heading() # !!!!!!!!!!!!!!!!!!!
        self.dx += self.boss.dx
        self.dy += self.boss.dy # add boss movement
        self.pos = self.boss.pos[:] # copy (!!!) of boss position 
        #self.pos = self.boss.pos   # uncomment this linefor fun effect
        self.calculate_origin()
        self.update() # to avoid ghost sprite in upper left corner, 
                      # force position calculation.
 
    def calculate_heading(self):
        """ drawing the bullet and rotating it according to it's launcher"""
        self.radius = Bullet.side # for collision detection
        self.angle += self.boss.turretAngle
        self.mass = Bullet.mass
        self.vel = Bullet.vel
        image = pygame.Surface((Bullet.side * 2, Bullet.side)) # rect 2 x 1
        image.fill((128,128,128)) # fill grey
        pygame.draw.rect(image, self.color, (0,0,int(Bullet.side * 1.5), Bullet.side)) # rectangle 1.5 length
        pygame.draw.circle(image, self.color, (int(self.side *1.5) ,self.side//2), self.side//2) #  circle
        image.set_colorkey((128,128,128)) # grey transparent
        self.image0 = image.convert_alpha()
        self.image = pygame.transform.rotate(self.image0, self.angle)
        self.rect = self.image.get_rect()
        self.dx = math.cos(degrees_to_radians(self.boss.turretAngle)) * self.vel
        self.dy = math.sin(degrees_to_radians(-self.boss.turretAngle)) * self.vel
 
    def calculate_origin(self):
        # - spawn bullet at end of turret barrel instead tank center -
        # cannon is around Tank.side long, calculatet from Tank center
        # later subtracted 20 pixel from this distance
        # so that bullet spawns closer to tank muzzle
        self.pos[0] +=  math.cos(degrees_to_radians(self.boss.turretAngle)) * (Tank.side-20)
        self.pos[1] +=  math.sin(degrees_to_radians(-self.boss.turretAngle)) * (Tank.side-20)
 
    def update(self, seconds=0.0):
        # ---- kill if too old ---
        self.lifetime += seconds
        if self.lifetime > Bullet.maxlifetime:
            self.kill()
        # ------ calculate movement --------
        self.pos[0] += self.dx * seconds
        self.pos[1] += self.dy * seconds
        # ----- kill if out of screen
        if self.pos[0] < 0:
            self.kill()
        elif self.pos[0] > Config.width:
            self.kill()
        if self.pos[1] < 0:
            self.kill()
        elif self.pos[1] > Config.height:
            self.kill()
        #------- move -------
        self.rect.centerx = round(self.pos[0],0)
        self.rect.centery = round(self.pos[1],0)
 
class Tracer(Bullet):
    """Tracer is nearly the same as Bullet, but smaller
       and with another origin (bow MG rect instead cannon.
       Tracer inherits all methods of Bullet, but i overwrite
       calculate_heading and calculate_origin"""
    side = 15 # long side of bullet rectangle
    vel = 200 # velocity
    mass = 10
    color = (200,0,100)
    maxlifetime = 10.0 # seconds
    def __init__(self, boss, turret=False):
        self.turret = turret
        Bullet.__init__(self,boss ) # this line is important 
 
    def calculate_heading(self):
        """overwriting the method because there are some differences 
           between a tracer and a main gun bullet"""
        self.radius = Tracer.side # for collision detection
        self.angle = 0
        self.angle += self.boss.tankAngle
        if self.turret:
            self.angle = self.boss.turretAngle
        self.mass = Tracer.mass
        self.vel = Tracer.vel
        image = pygame.Surface((Tracer.side, Tracer.side / 4)) # a line 
        image.fill(self.boss.color) # fill yellow ? 
        pygame.draw.rect(image, (0,0,0), (Tracer.side * .75, 0, Tracer.side, Tracer.side / 4)) # red dot at front
        image.set_colorkey((128,128,128)) # grey transparent
        self.image0 = image.convert_alpha()
        self.image = pygame.transform.rotate(self.image0, self.angle)
        self.rect = self.image.get_rect()
        if self.turret:
            # turret mg
            self.dx = math.cos(degrees_to_radians(self.boss.turretAngle)) * self.vel
            self.dy = math.sin(degrees_to_radians(-self.boss.turretAngle)) * self.vel
        else:
            # bow mg
            self.dx = math.cos(degrees_to_radians(self.boss.tankAngle)) * self.vel
            self.dy = math.sin(degrees_to_radians(-self.boss.tankAngle)) * self.vel
 
    def calculate_origin(self):
        """overwriting because another point of origin is needed"""
        # - spawn bullet at end of machine gun muzzle (bow or turret)
        if self.turret:
            self.pos[0] +=  math.cos(degrees_to_radians(-90+self.boss.turretAngle)) * 15
            self.pos[1] +=  math.sin(degrees_to_radians(90-self.boss.turretAngle)) * 15
        else:
            self.pos[0] +=  math.cos(degrees_to_radians(30+self.boss.tankAngle)) * (Tank.side/2)
            self.pos[1] +=  math.sin(degrees_to_radians(-30-self.boss.tankAngle)) * (Tank.side/2)
 
class Tank(pygame.sprite.Sprite):
    """ A Tank, controlled by the Player with Keyboard commands.
    This Tank draw it's own Turret (including the main gun) 
    and it's bow rectangle (slit for Tracer Machine Gun"""
    side = 100 # side of the quadratic tank sprite
    recoiltime = 0.75 # how many seconds  the cannon is busy after firing one time
    mgrecoiltime = 0.2 # how many seconds the bow mg (machine gun) is idle
    turretTurnSpeed = 25 # turret
    tankTurnSpeed = 8 # tank
    movespeed = 25
    maxrotate = 360 # maximum amount of degree the turret is allowed to rotate
    book = {} # a book of tanks to store all tanks
    number = 0 # each tank gets his own number
    # keys for tank control, expand if you need more tanks
    #          player1,        player2    etc
    firekey = (pygame.K_k, pygame.K_DOWN)
    mgfirekey = (pygame.K_LCTRL, pygame.K_KP_ENTER)
    mg2firekey = (pygame.K_i, pygame.K_UP)
    turretLeftkey = (pygame.K_j, pygame.K_LEFT)
    turretRightkey = (pygame.K_l, pygame.K_RIGHT)
    forwardkey = (pygame.K_w, pygame.K_KP8)
    backwardkey = (pygame.K_s, pygame.K_KP5)
    tankLeftkey = (pygame.K_a, pygame.K_KP4)
    tankRightkey = (pygame.K_d, pygame.K_KP6)
    color = ((200,200,0), (0,0,200))
    msg = ["wasd LCTRL, ijkl", "Keypad: 4852, ENTER, cursor"]
 
    def __init__(self, startpos = (150,150), angle=0):
        self.number = Tank.number # now i have a unique tank number
        Tank.number += 1 # prepare number for next tank
        Tank.book[self.number] = self # store myself into the tank book
        pygame.sprite.Sprite.__init__(self, self.groups) # THE most important line !
        self.pos = [startpos[0], startpos[1]] # x,y
        self.dx = 0
        self.dy = 0
        self.ammo = 30 # main gun
        self.mgammo = 500 # machinge gun
        self.msg =  "player%i: ammo: %i/%i keys: %s" % (self.number+1, self.ammo, self.mgammo, Tank.msg[self.number])
        Text((Config.width/2, 30+20*self.number), self.msg) # create status line text sprite
        self.color = Tank.color[self.number]
        self.turretAngle = angle #turret facing
        self.tankAngle = angle # tank facing
        self.firekey = Tank.firekey[self.number] # main gun
        self.mgfirekey = Tank.mgfirekey[self.number] # bow mg
        self.mg2firekey = Tank.mg2firekey[self.number] # turret mg
        self.turretLeftkey = Tank.turretLeftkey[self.number] # turret
        self.turretRightkey = Tank.turretRightkey[self.number] # turret
        self.forwardkey = Tank.forwardkey[self.number] # move tank
        self.backwardkey = Tank.backwardkey[self.number] # reverse tank
        self.tankLeftkey = Tank.tankLeftkey[self.number] # rotate tank
        self.tankRightkey = Tank.tankRightkey[self.number] # rotat tank
        # painting facing north, have to rotate 90° later
        image = pygame.Surface((Tank.side,Tank.side)) # created on the fly
        image.fill((128,128,128)) # fill grey
        if self.side > 10:
             pygame.draw.rect(image, self.color, (5,5,self.side-10, self.side-10)) #tank body, margin 5
             pygame.draw.rect(image, (90,90,90), (0,0,self.side//6, self.side)) # track left
             pygame.draw.rect(image, (90,90,90), (self.side-self.side//6, 0, self.side,self.side)) # right track
             pygame.draw.rect(image, (255,0,0), (self.side//6+5 , 10, 10, 5)) # red bow rect left
             #pygame.draw.rect(image, (255,0,0), (self.side/2 - 5, 10, 10, 5)) # red bow rect middle
        pygame.draw.circle(image, (255,0,0), (self.side//2,self.side//2), self.side//3 , 2) # red circle for turret
        image = pygame.transform.rotate(image,-90) # rotate so to look east
        self.image0 = image.convert_alpha()
        self.image = image.convert_alpha()
        self.rect = self.image0.get_rect()
        #---------- turret ------------------
        self.firestatus = 0.0 # time left until cannon can fire again
        self.mgfirestatus = 0.0 # time until mg can fire again
        self.mg2firestatus = 0.0 # time until turret mg can fire again
        self.turndirection = 0    # for turret
        self.tankturndirection = 0
        self.movespeed = Tank.movespeed
        self.turretTurnSpeed = Tank.turretTurnSpeed
        self.tankTurnSpeed = Tank.tankTurnSpeed
        Turret(self) # create a Turret for this tank
 
    def update(self, seconds):
        # no need for seconds but the other sprites need it
        #-------- reloading, firestatus----------
        if self.firestatus > 0:
            self.firestatus -= seconds # cannon will soon be ready again
            if self.firestatus <0:
                self.firestatus = 0 #avoid negative numbers
        if self.mgfirestatus > 0:
            self.mgfirestatus -= seconds # bow mg will soon be ready again
            if self.mgfirestatus <0:
                self.mgfirestatus = 0 #avoid negative numbers
        if self.mg2firestatus > 0:
            self.mg2firestatus -= seconds # turret mg will soon be ready again
            if self.mg2firestatus <0:
                self.mg2firestatus = 0 #avoid negative numbers
 
        # ------------ keyboard --------------
        pressedkeys = pygame.key.get_pressed()
        # -------- turret manual rotate ----------
        self.turndirection = 0    #  left / right turret rotation
        if pressedkeys[self.turretLeftkey]:
            self.turndirection += 1
        if pressedkeys[self.turretRightkey]:
            self.turndirection -= 1
 
        #---------- tank rotation ---------
        self.tankturndirection = 0 # reset left/right rotation
        if pressedkeys[self.tankLeftkey]:
            self.tankturndirection += 1
        if pressedkeys[self.tankRightkey]:
            self.tankturndirection -= 1
 
        # ---------------- rotate tank ---------------
        self.tankAngle += self.tankturndirection * self.tankTurnSpeed * seconds # time-based turning of tank
        # angle etc from Tank (boss)
        oldcenter = self.rect.center
        oldrect = self.image.get_rect() # store current surface rect
        self.image  = pygame.transform.rotate(self.image0, self.tankAngle) 
        self.rect = self.image.get_rect()
        self.rect.center = oldcenter 
        # if tank is rotating, turret is also rotating with tank !
        # -------- turret autorotate ----------
        self.turretAngle += self.tankturndirection * self.tankTurnSpeed * seconds  + self.turndirection * self.turretTurnSpeed * seconds # time-based turning
        # ---------- fire cannon -----------
        if (self.firestatus ==0) and (self.ammo > 0):
            if pressedkeys[self.firekey]:
                self.firestatus = Tank.recoiltime # seconds until tank can fire again
                Bullet(self)    
                self.ammo -= 1
                self.msg =  "player%i: ammo: %i/%i keys: %s" % (self.number+1, self.ammo, self.mgammo, Tank.msg[self.number])
                Text.book[self.number].changemsg(self.msg)
        # -------- fire bow mg ---------------
        if (self.mgfirestatus ==0) and (self.mgammo >0):
            if pressedkeys[self.mgfirekey]:
                self.mgfirestatus = Tank.mgrecoiltime
                Tracer(self, False) # turret mg = False
                self.mgammo -= 1
                self.msg = "player%i: ammo: %i/%i keys: %s" % (self.number+1, self.ammo, self.mgammo, Tank.msg[self.number])
                Text.book[self.number].changemsg(self.msg)
        # -------- fire turret mg ---------------
        if (self.mg2firestatus ==0) and (self.mgammo >0):
            if pressedkeys[self.mg2firekey]:
                self.mg2firestatus = Tank.mgrecoiltime # same recoiltime for both mg's
                Tracer(self, True) # turret mg = True
                self.mgammo -= 1
                self.msg =  "player%i: ammo: %i/%i keys: %s" % (self.number+1, self.ammo, self.mgammo, Tank.msg[self.number])
                Text.book[self.number].changemsg(self.msg)
        # ---------- movement ------------
        self.dx = 0
        self.dy = 0
        self.forward = 0 # movement calculator
        if pressedkeys[self.forwardkey]:
            self.forward += 1
        if pressedkeys[self.backwardkey]:
            self.forward -= 1
        # if both are pressed togehter, self.forward becomes 0
        if self.forward == 1:
            self.dx =  math.cos(degrees_to_radians(self.tankAngle)) * self.movespeed
            self.dy =  -math.sin(degrees_to_radians(self.tankAngle)) * self.movespeed
        if self.forward == -1:
            self.dx =  -math.cos(degrees_to_radians(self.tankAngle)) * self.movespeed
            self.dy =  math.sin(degrees_to_radians(self.tankAngle)) * self.movespeed
        # ------------- check border collision ---------------------
        self.pos[0] += self.dx * seconds
        self.pos[1] += self.dy * seconds
        if self.pos[1] + self.side//2 >= Config.height:
            self.pos[1] = Config.height - self.side//2
            self.dy = 0 # crash into border
        elif self.pos[1] -self.side/2 <= 0:
            self.pos[1] = 0 + self.side//2
            self.dy = 0
        # ---------- paint sprite at correct position ---------
        self.rect.centerx = round(self.pos[0], 0) #x
        self.rect.centery = round(self.pos[1], 0) #y    
 
class Turret(pygame.sprite.Sprite):
    """turret on top of tank"""
    def __init__(self, boss):
        pygame.sprite.Sprite.__init__(self, self.groups) # THE most important line !
        self.boss = boss
        self.side = self.boss.side        
        self.images = {} # how much recoil after shooting, reverse order of apperance
        self.images[0] = self.draw_cannon(0)  # idle position
        self.images[1] = self.draw_cannon(1)
        self.images[2] = self.draw_cannon(2)
        self.images[3] = self.draw_cannon(3)
        self.images[4] = self.draw_cannon(4)
        self.images[5] = self.draw_cannon(5)
        self.images[6] = self.draw_cannon(6)
        self.images[7] = self.draw_cannon(7)
        self.images[8] = self.draw_cannon(8)  # position of max recoil
        self.images[9] = self.draw_cannon(4)
        self.images[10] = self.draw_cannon(0) # idle position
 
    def update(self, seconds):        
        # painting the correct image of cannon
        if self.boss.firestatus > 0:
            self.image = self.images[int(self.boss.firestatus // (Tank.recoiltime / 10.0))]
        else:
            self.image = self.images[0]
        # --------- rotating -------------
        # angle etc from Tank (boss)
        oldrect = self.image.get_rect() # store current surface rect
        self.image  = pygame.transform.rotate(self.image, self.boss.turretAngle) 
        self.rect = self.image.get_rect()
        # ---------- move with boss ---------
        self.rect = self.image.get_rect()
        self.rect.center = self.boss.rect.center
 
    def draw_cannon(self, offset):
         # painting facing right, offset is the recoil
         image = pygame.Surface((self.boss.side * 2,self.boss.side * 2)) # created on the fly
         image.fill((128,128,128)) # fill grey
         pygame.draw.circle(image, (255,0,0), (self.side,self.side), 22, 0) # red circle
         pygame.draw.circle(image, (0,255,0), (self.side,self.side), 18, 0) # green circle
         pygame.draw.rect(image, (255,0,0), (self.side-10, self.side + 10, 15,2)) # turret mg rectangle
         pygame.draw.rect(image, (0,255,0), (self.side-20 - offset,self.side - 5, self.side - offset,10)) # green cannon
         pygame.draw.rect(image, (255,0,0), (self.side-20 - offset,self.side - 5, self.side - offset,10),1) # red rect 
         image.set_colorkey((128,128,128))
         return image
# ---------------- End of classes --------------------
#------------ defs ------------------
def radians_to_degrees(radians):
    return (radians / math.pi) * 180.0
 
def degrees_to_radians(degrees):
    return degrees * (math.pi / 180.0)
 
def write(msg="pygame is cool"):
    """helper function for the Text sprite"""
    myfont = pygame.font.SysFont("None", 28)
    mytext = myfont.render(msg, True, (255,0,0))
    mytext = mytext.convert_alpha()
    return mytext        
 
def pressedKeysString():
    """returns the pressed keys (for the player1 tank) to be displayd in the status line"""
    pressedkeys = pygame.key.get_pressed()
    line = ""
    if pressedkeys[pygame.K_a]:
        line += "a "
    if pressedkeys[pygame.K_d]:
        line += "d "
    if pressedkeys[pygame.K_w]:
        line += "w "
    if pressedkeys[pygame.K_s]:
        line += "s "
    if pressedkeys[pygame.K_LCTRL]:
        line += "LCTRL"
    return line
 
def main():
    """the game itself"""
    pygame.init()
    screen=pygame.display.set_mode((Config.width,Config.height)) 
    background = pygame.Surface((screen.get_size()))
    background.fill((128,128,255)) # fill grey light blue:(128,128,255) 
    # paint a grid of white lines
    for x in range(0,Config.width,Config.width//Config.xtiles): #start, stop, step
        pygame.draw.line(background, (255,255,255), (x,0), (x,Config.height))
    for y in range(0,Config.height,Config.height//Config.ytiles): #start, stop, step
        pygame.draw.line(background, (255,255,255), (0,y), (Config.width,y))
    # paint upper rectangle to have background for text
    pygame.draw.rect(background, (128,128,255), (0,0,Config.width, 70))
 
    background = background.convert()
    screen.blit(background, (0,0)) # delete all
    clock = pygame.time.Clock()    # create pygame clock object
    FPS = Config.fps               # desired max. framerate 
    playtime = 0
 
    tankgroup = pygame.sprite.Group()
    bulletgroup = pygame.sprite.Group()
    allgroup = pygame.sprite.LayeredUpdates()
 
    Tank._layer = 4   # base layer
    Bullet._layer = 7 # to prove that Bullet is in top-layer
    Tracer._layer = 5 # above Tank, but below Turret
    Turret._layer = 6 # above Tank & Tracer
    Text._layer = 3   # below Tank
 
    #assign default groups to each sprite class
    Tank.groups = tankgroup, allgroup
    Turret.groups = allgroup
    Bullet.groups = bulletgroup, allgroup
    Text.groups = allgroup
    player1 = Tank((150,250), 90) # create  first tank, looking north
    player2 = Tank((450,250), -90) # create second tank, looking south
    status3 = Text((Config.width//2, 10), "Tank Demo. Press ESC to quit")
    mainloop = True           
    while mainloop:
        milliseconds = clock.tick(Config.fps)  # milliseconds passed since last frame
        seconds = milliseconds / 1000.0 # seconds passed since last frame (float)
        playtime += seconds
 
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                # pygame window closed by user
                mainloop = False 
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    mainloop = False # exit game
 
        pygame.display.set_caption("FPS: %.2f keys: %s" % ( clock.get_fps(), pressedKeysString()))
        #screen.blit(background, (0,0)) # delete all
        allgroup.clear(screen, background) # funny effect if you outcomment this line
        allgroup.update(seconds)
        allgroup.draw(screen)
        pygame.display.flip() # flip the screen 30 times a second
    return 0
 
if __name__ == '__main__':
    main()

1)
from yipyip
en/pygame/step020.txt · Last modified: 2020/05/15 22:53 by horst