[python3] tank battle

Hits: 0

Tank Battle Full Version [Python3]

English Annotated Version

Reprinted from /lwj-jz/articles/TankWarGame.html

[Write on the front]

I still remember the tank battle I played when I was a child~ I think that when a few friends were crowded together, I can’t wait to divide the keyboard into several petals…

Well, without further ado, let’s get straight to the point. Today we are going to basically implement the mini-game of tank battle, mainly using [third-party libraries]pygame . Friends who don’t know it can refer to the official documentation. Since http://www.pygame.org has become such a ghost recently

Harm, it’s annoying, but fortunately I downloaded the document before, the link is here pygame official document

If you don’t want to follow along and want the complete code , please like and chat with me privately; if you want to follow along, I believe that you can do it without private chatting, and you will give me a small like~

【operation result】

Results first! ! !

【Preparation】

Not satisfied with the result~

If you are satisfied, just give a small like, no money.

It doesn’t matter if you’re not satisfied, hey, see clearly, it’s just Tank War 1.0oh, what does it mean? Yes, upgrade update. However, I believe that these are enough for you. If you want to upgrade, please chat with me privately.

  1. Editing environment: Pycharm.
  2. Interpreter: Python3.
  3. You need to import the relevant modules according to the steps, be sure to check them.
  4. Related material resources: I will give the link directly, don’t look back and say I’m not kind… [Tank war material]

【Project requirements】

First of all, we have to analyze what is needed to make this “Tank Wars” mini game.

"""
Here are the project requirements for the Tank Wars game.

1. What classes are in the project?
    a) MainGame
    b) Tank(OurTank, EnemyTank)
    c) Bullet
    d) Wall
    e) Explosion
    f) Music
    g) BaseItem --> Sprite class

2. What are the methods in each class?
    a) methods in MainGame class
        1. startGame
        2. gameOver
        3. getEvent
        4. setTextSurface
    b) methods in Tank class
        1. shooting
        2. moveTank
        3. displayTank
        4. isCollide
    c) methods in Bullet class
        1. moveBullet
        2. displayBllet
        3. isCollide
    d) methods in Wall class
        1. displayWall
    e) methods in Explosion class
        1. displayExplosion
    f) methods in Music class
        1. playMusic
    g) methods in BaseItem class
"""

ah? Can’t understand English? No, I’m already very Chinese. If you really don’t want to read it or don’t understand it, then translate it. Ma Li will give you a translation URL, lest you mutter me… Google Translate

【Build the frame】

With the project requirements, then the next step is to build the framework. According to the project requirements, we set up the framework.

class  BaseItem (Sprite) : 
    pass


class MainGame:
    pass


class Tank(BaseItem):
    pass


class  OurTank (Tank) : 
    pass


class EnemyTank(Tank):
    pass


class  Bullet (BaseItem) : 
    pass


class Wall(BaseItem):
    pass


class Explosion:
    pass


class Music:
    pass

【Improve the MainGame class】

The main functions of this class are:

  • Launch game window
  • Display some text information
  • Detect keystrokes in real time and respond
  • End Game

The way to import these modules first:

from time import sleep
from pygame import display
from pygame import Color

First set the window size and background color:

SCREEN_WIDTH = 700
SCREEN_HEIGHT = 500
BG_COLOR = Color(0, 0, 0)

Perfect MainGameclass:

class MainGame:
    window = None

    def __init__(self):
        pass

    def startGame(self):
        display.init()
        MainGame.window = display.set_mode(size=[SCREEN_WIDTH, SCREEN_HEIGHT])
        display.set_caption("Tank War 1.0")
        while True:
            sleep(0.002)
            MainGame.window.fill(color=BG_COLOR)
            display.update()

Add at the bottom of all code:

if __name__ == "__main__":
    my_game = MainGame()
    my_game.startGame()

Run the screenshot:

what! Clicking to close doesn’t work, and it’s still stuck… Don’t panic, it’s not over yet.

Proceed to import the following module’s method:

from sys import exit
from pygame import event
from pygame import QUIT
from pygame import quit

MainGameAdd the gameOvermethod to the class:

@staticmethod
def gameOver():
    quit()
    exit()

MainGameAdd the getEventmethod to the class:

def getEvent(self):
    for e in event.get():
        if e.type == QUIT:
            self.gameOver()

Add a sentence to the loop in the method of the class MainGame:startGame

self.getEvent()

So the MainGameclass becomes:

class MainGame:
    window = None

    def __init__(self):
        pass

    def startGame(self):
        display.init()
        MainGame.window = display.set_mode(size=[SCREEN_WIDTH, SCREEN_HEIGHT])
        display.set_caption("Tank War 1.0")
        while True:
            sleep(0.002)
            MainGame.window.fill(color=BG_COLOR)
            self.getEvent()
            display.update()

    @staticmethod
    def gameOver():
        quit()
        exit()

    def getEvent(self):
        for e in event.get():
            if e.type == QUIT:
                self.gameOver()

Run the screenshot:

It can be closed now~ Hey, it’s just so skinny…

However, it’s dark, and it’s nothing… Then go down.

[Improve the BaseItem class]

The question is, what is this class used for? Hey, let me tell you, this class is for detecting collisions (tank vs tank, tank vs bullet, tank vs wall). What is the principle? Please move to the pygameofficial documentation to view pygame.spritethe relevant content. I will not expand it here.

Proceed to import the following module’s method:

from pygame.sprite import Sprite
from pygame.sprite import AbstractGroup

Supplementary BaseItemclass:

class BaseItem(Sprite):
    def __init__(self, *groups: AbstractGroup):
        super().__init__(*groups)

【Improve the Tank class】

The tank has four directions: up and down, left and right

UP = 1
DOWN = 2
LEFT = 3
RIGHT = 4

And speed:

SPEED = 1

Since the Tankclass is the parent class of the OurTankclass and the class, the specific construction method will be explained later.EnemyTank

TankAdd the displayTankmethod to the class:

def displayTank(self):
    self.my_image = self.images.get(self.direction, None)
    MainGame.window.blit(source=self.my_image, dest=self.rect)

At this point Tankthe class is:

class Tank(BaseItem):
        UP = 1
        DOWN = 2
        LEFT = 3
        RIGHT = 4
        SPEED = 1

        def __init__(self, *groups: AbstractGroup):
            super().__init__(*groups)
            self.images = {}
            self.direction = Tank.UP
            self.my_image = None
            self.rect = None
            self.move_switch = None
            self.bullets = []

        def displayTank(self):
            self.my_image = self.images.get(self.direction, None)
            MainGame.window.blit(source=self.my_image, dest=self.rect)

Don’t run it yet, the class hasn’t been perfected OurTankyet.

【Improve OurTank class】

Import the methods of the following modules:

from pygame import image

Add attributes to the OurTankclass, our tank camp and its number:

tanks = []
count = 0

Let’s make it simple first, and set our tank camp to have only one tank. Considering the infinite resurrection of our tanks, the singleton mode should be set. The introduction of singleton mode and the implementation of python3 will not be expanded here, but a reference article for learning singleton mode is provided: N implementations of Python singleton mode (Singleton)

OurTankAdd properties to the class:

__obj = None
__init_flag = True

OurTankAdd the __new__method to the class:

def __new__(cls, *args, **kwargs):
    if cls.__obj is None:
        cls.__obj = object.__new__(cls)
    return cls.__obj

OurTankAdd the __init__method to the class:

def __init__(self, left, top):
    super().__init__()
    if OurTank.__init_flag:
        OurTank.__init_flag = False

Load the pictures of the tanks of our camp. Since there are four directions, there are four corresponding pictures. In OurTankthe method in the class __init__add:

Note :

  1. The img folder is extracted from the tank war material, pay attention to your path problem, I use a relative path, it is recommended that you use an absolute path.

  2. You can also use your own material if you want, but the image suffix must be .gif, otherwise there will be problems.

self.images = {
    Tank.UP: image.load(r"img/p1tankU.gif"),
    Tank.DOWN: image.load(r"img/p1tankD.gif"),
    Tank.LEFT: image.load(r"img/p1tankL.gif"),
    Tank.RIGHT: image.load(r"img/p1tankR.gif"),
}

OurTankContinue to add in the init method in the class:</p> <pre><code>self.my_image = self.images[self.direction] self.rect = self.my_image.get_rect() self.rect.left = left self.rect.top = top OurTank.count += 1 OurTank.tanks.append(self) </code></pre> <p>From top to bottom, respectively:</p> <ul> <li>Load the image corresponding to the default orientation</li> <li>Get image <code>rect</code>properties</li> <li>Set the position of the image relative to the left end of the window</li> <li>Set the position of the image relative to the top of the window</li> <li>Increase the number of our tank factions by one</li> <li>The object list of our tank camp is increased by one</li> </ul> <p>At this point, <code>OurTank</code>the method of the class <code>__init__</code>is:</p> <pre><code>def __init__(self, left, top): super().__init__() if OurTank.__init_flag: OurTank.__init_flag = False self.images = { Tank.UP: image.load(r"img/p1tankU.gif"), Tank.DOWN: image.load(r"img/p1tankD.gif"), Tank.LEFT: image.load(r"img/p1tankL.gif"), Tank.RIGHT: image.load(r"img/p1tankR.gif"), } self.my_image = self.images[self.direction] self.rect = self.my_image.get_rect() self.rect.left = left self.rect.top = top OurTank.count += 1 OurTank.tanks.append(self) </code></pre> <h2>【Improve the Tank class】</h2> <p>Next, to consider spawning our camp's tanks in the game window, we use random locations to spawn tanks. Since the positions are random, there is no guarantee that the pictures of multiple tanks do not overlap. Therefore, it is necessary to detect whether the tanks collide. (We think here that coincidence is collision)</p> <p>Import the methods of the following modules:</p> <pre><code>from inspect import getmro from pygame.sprite import collide_rect </code></pre> <p><code>Tank</code>Add the <code>isCollide</code>method to the class:</p> <pre><code>def isCollide(self, collided_type): if "Tank" in [i.__name__ for i in getmro(cls=collided_type)]: for tank in collided_type.tanks: if tank != self and collide_rect(tank, self): return True return False else: return False </code></pre> <h2>【Improve the MainGame class】</h2> <p>Import the methods of the following modules:</p> <pre><code>from random import randint </code></pre> <p>In <code>MainGame</code>the method in the class, add on the next<code>startGame</code> line:<code>display.set_caption("Tank War 1.0")</code></p> <pre><code>while OurTank.count < 1: OurTank(randint(60, 640), randint(60, 440)) if OurTank.tanks[-1].isCollide(OurTank): OurTank.tanks.pop() OurTank.count -= 1 </code></pre> <p>In <code>MainGame</code>the method in the class, add on the next<code>startGame</code> line:<code>MainGame.window.fill(color=BG_COLOR)</code></p> <pre><code>for tank in OurTank.tanks: tank.displayTank() </code></pre> <p>At this point, <code>MainGame</code>the methods in the class are <code>startGame</code>:</p> <pre><code>def startGame(self): display.init() MainGame.window = display.set_mode(size=[SCREEN_WIDTH, SCREEN_HEIGHT]) display.set_caption("Tank War 1.0") while OurTank.count < 1: OurTank(randint(60, 640), randint(60, 440)) if OurTank.tanks[-1].isCollide(OurTank): OurTank.tanks.pop() OurTank.count -= 1 while True: sleep(0.002) MainGame.window.fill(color=BG_COLOR) for tank in OurTank.tanks: tank.displayTank() self.getEvent() display.update() </code></pre> <p>Run the screenshot:</p> <p>Maybe your tank's position is different, it doesn't matter, because it's a random position~ and after each run, the position will change randomly...</p> <h2>【Improve the MainGame class】</h2> <p>In other words, there is nothing wrong with pictures, you have to be able to move...</p> <p>When we play this game, we must use the keyboard. We are used to the "up, down, left and right" keys, that is <code>Up</code>, <code>Down</code>, , <code>Left</code>, <code>Right</code>keys, so we need to go back to <code>MainGame</code>the class. why? We press the key and hope to get the corresponding response, who will detect it? Still our <code>getEvent</code>method, back to the <code>MainGame</code>class.</p> <p>Import the methods of the following modules:</p> <pre><code>from pygame import KEYDOWN from pygame import K_LEFT from pygame import K_RIGHT from pygame import K_UP from pygame import K_DOWN from pygame import KEYUP </code></pre> <p>In <code>MainGame</code>the method in the class, on the next<code>getEvent</code> line (in parallel with) add:<code>if e.type == QUIT: self.gameOver()if e.type == QUIT</code></p> <pre><code>if e.type == KEYDOWN: if (e.key == K_UP or e.key == K_DOWN or e.key == K_LEFT or e.key == K_RIGHT): for tank in OurTank.tanks: tank.move_switch = e.key if e.type == KEYUP: if (e.key == K_UP or e.key == K_DOWN or e.key == K_LEFT or e.key == K_RIGHT): for tank in OurTank.tanks: tank.move_switch = None </code></pre> <p>At this point, our keys are set up, and then the corresponding responses to the keys are arranged.</p> <h2>【Improve the Tank class】</h2> <p><code>Tank</code>Add methods to the class <code>moveTank</code>and consider four directions:</p> <pre><code>def moveTank(self, my_key): if my_key == K_UP: self.direction = self.UP if self.rect.top > 0: self.rect.top -= 1 if not self.isCollide(OurTank): self.rect.top += 1 self.rect.top -= self.SPEED else: self.rect.top += 1 elif my_key == K_DOWN: self.direction = self.DOWN if self.rect.top < SCREEN_HEIGHT - self.rect.height: self.rect.top += 1 if not self.isCollide(OurTank): self.rect.top -= 1 self.rect.top += self.SPEED else: self.rect.top -= 1 elif my_key == K_LEFT: self.direction = self.LEFT if self.rect.left > 0: self.rect.left -= 1 if not self.isCollide(OurTank): self.rect.left += 1 self.rect.left -= self.SPEED else: self.rect.left += 1 elif my_key == K_RIGHT: self.direction = self.RIGHT if self.rect.left < SCREEN_WIDTH - self.rect.width: self.rect.left += 1 if not self.isCollide(OurTank): self.rect.left -= 1 self.rect.left += self.SPEED else: self.rect.left -= 1 </code></pre> <p>What are so many <code>if</code>for? In fact, it is to ensure that the tank does not come out of the window, and does not collide with other tanks of our camp (if any).</p> <h2>【Improve the MainGame class】</h2> <p>The button response is also done, then you can add it to the main window.</p> <p>In the method in the <code>MainGame</code>class, on the next line in the loop add:<code>startGamewhile Trueself.getEvent()</code></p> <pre><code>for tank in OurTank.tanks: tank.moveTank(tank.move_switch) </code></pre> <p>Run the screenshot:</p> <p>I won't post the gif, but, hey, your tank must be running around under your control, aren't you happy? Give a like first~</p> <h2>[Improve the EnemyTank class]</h2> <p>A tank is always alone... Then add a few more~</p> <p>Let's add a tank of the enemy camp to it, 6, let's be more lively.</p> <p>Coming to our <code>EnemyTank</code>class, add a few properties:</p> <pre><code>tanks = [] count = 0 </code></pre> <p>Do you look familiar? Yes, when I wrote the class earlier <code>OurTank</code>, these two attributes were also added, which means the same thing, but the camp is different.</p> <p><code>EnemyTank</code>Add the <code>__init__</code>method to the class:</p> <pre><code>def __init__(self, left, top): super().__init__() self.images = { Tank.UP: image.load(r"img/enemy1U.gif"), Tank.DOWN: image.load(r"img/enemy1D.gif"), Tank.LEFT: image.load(r"img/enemy1L.gif"), Tank.RIGHT: image.load(r"img/enemy1R.gif"), } self.direction = randint(1, 4) self.my_image = self.images[self.direction] self.rect = self.my_image.get_rect() self.rect.left = left self.rect.top = top EnemyTank.count += 1 EnemyTank.tanks.append(self) </code></pre> <p>Similar <code>OurTank</code>to the method of the class <code>__init__</code>, only <code>self.direction = randint(1, 4)</code>here is the difference, the initial direction of the enemy tank is randomly generated.</p> <p>Then we can add tanks from the enemy camp in the game window~</p> <h2>【Improve the MainGame class】</h2> <p>In the method in the <code>MainGame</code>class, on the line above add:<code>startGamewhile True</code></p> <pre><code>while EnemyTank.count < 6: EnemyTank(randint(60, 640), randint(60, 220)) if EnemyTank.tanks[-1].isCollide(EnemyTank): EnemyTank.tanks.pop() EnemyTank.count -= 1 </code></pre> <p>Aha, a random <code>6</code>enemy faction tank is generated and needs to be displayed. In the <code>while True</code>loop, on <code>self.getEvent()</code>the previous line add:</p> <pre><code>for tank in EnemyTank.tanks: tank.displayTank() </code></pre> <p>Run the screenshot:</p> <p>Hey, here it comes. The tanks of the enemy camp are not moving? Don't worry, just look down...</p> <h2>[Improve the EnemyTank class]</h2> <p>If you want to make the tanks of the enemy camp move by themselves, or move randomly, you need to <code>moveTank</code>overload the method.</p> <p><code>EnemyTank</code>Add properties to the class:</p> <pre><code>turn_flag = 0 </code></pre> <p><code>EnemyTank</code>Add the <code>moveTank</code>method to the class:</p> <pre><code>def moveTank(self, **kwargs): self.turn_flag += 1 if self.turn_flag == 150: self.direction = randint(1, 4) self.turn_flag = 0 </code></pre> <p>What is this <code>if</code>for? This is the frequency of the turning of the tanks that control the enemy camp (the update frequency of the game window is faster, that is, the <code>display.update()</code>frequency is faster), neither too fast nor too slow, and then give it a random direction.</p> <p>Next, add four directions of "up, down, left and right" to it, and continue to add in the methods in the <code>EnemyTank</code>class :<code>moveTank</code></p> <pre><code>if self.direction == self.UP: if self.rect.top > 0: self.rect.top -= 1 if not ( self.isCollide(OurTank) or self.isCollide(EnemyTank) ): self.rect.top += 1 self.rect.top -= self.SPEED else: self.rect.top += 1 elif self.direction == self.DOWN: if self.rect.top < SCREEN_HEIGHT - self.rect.height: self.rect.top += 1 if not ( self.isCollide(OurTank) or self.isCollide(EnemyTank) ): self.rect.top -= 1 self.rect.top += self.SPEED else: self.rect.top -= 1 elif self.direction == self.LEFT: if self.rect.left > 0: self.rect.left -= 1 if not ( self.isCollide(OurTank) or self.isCollide(EnemyTank) ): self.rect.left += 1 self.rect.left -= self.SPEED else: self.rect.left += 1 elif self.direction == self.RIGHT: if self.rect.left < SCREEN_WIDTH - self.rect.width: self.rect.left += 1 if not ( self.isCollide(OurTank) or self.isCollide(EnemyTank) ): self.rect.left -= 1 self.rect.left += self.SPEED else: self.rect.left -= 1 </code></pre> <p>The <code>if</code>statement here is to ensure that the tank does not collide with other tanks or run out of the screen.</p> <h2>【Improve the MainGame class】</h2> <p>The movement of the enemy tank camp is set, now let them move in the game window. In the <code>MainGame</code>class, on the previous line <code>startGame</code>in the <code>while True</code>loop <code>display.update()</code>add:</p> <pre><code>for tank in EnemyTank.tanks: tank.moveTank() </code></pre> <p>Run the screenshot:</p> <p>Great, all the tanks are moving! However, there is still a bug - in the picture above, the tanks of our camp can actively overlap with the enemy tanks. We hope that all tanks will not overlap, so we need to modify the next <code>Tank</code>class.</p> <h2>【Improve the Tank class】</h2> <p>Find <code>Tank</code>the method in the class and modify <code>moveTank</code>all the collision detection statements <code>if not self.isCollide(OurTank):</code>to:</p> <pre><code>if not ( self.isCollide(OurTank) or self.isCollide(EnemyTank) ): </code></pre> <p>It's not over yet, don't run it yet.</p> <h2>【Improve the MainGame class】</h2> <p>In <code>MainGame</code>the method in the class <code>startGame</code>, modify</p> <pre><code>while OurTank.count < 1: OurTank(randint(60, 640), randint(60, 440)) if OurTank.tanks[-1].isCollide(OurTank) : OurTank.tanks.pop() OurTank.count -= 1 </code></pre> <p>for:</p> <pre><code>while OurTank.count < 1: OurTank(randint( 60 , 640 ), randint( 60 , 440 )) if ( OurTank.tanks[-1].isCollide(OurTank) or OurTank.tanks[-1].isCollide(EnemyTank) ): OurTank.tanks.pop() OurTank.count -= 1 </code></pre> <p>Revise</p> <pre><code>while EnemyTank.count < 6: EnemyTank(randint(60, 640), randint(60, 220)) if EnemyTank.tanks[-1].isCollide(EnemyTank): EnemyTank.tanks.pop() EnemyTank.count -= 1 </code></pre> <p>for:</p> <pre><code>while EnemyTank.count < 6: EnemyTank(randint( 60 , 640 ), randint( 60 , 220 )) if ( EnemyTank.tanks[-1].isCollide(OurTank) or EnemyTank.tanks[-1].isCollide(EnemyTank) ): EnemyTank.tanks.pop() EnemyTank.count -= 1 </code></pre> <p>Then run:</p> <p>Alright, there's nothing wrong now, no one will overlap...</p> <h2>【Improve the Wall class】</h2> <p>Tanks alone are a bit monotonous, so add some walls.</p> <p><code>Wall</code>Add properties to the class:</p> <pre><code>walls = [] count = 0 </code></pre> <p><code>Wall</code>Add the <code>__init__</code>method to the class:</p> <pre><code>def __init__(self, left, top, *groups: AbstractGroup): super().__init__(*groups) self.image = image.load(r"img/walls.gif") self.rect = self.image.get_rect() self.rect.left = left self.rect.top = top self.hp = 4 Wall.walls.append(self) Wall.count += 1 </code></pre> <p>If you are careful, you will definitely find that, hey, there seems to be something more. Yes, we added a health value to the wall <code>self.hp</code>, that is to say, when the bullet hits the wall, the health value of the wall will be reduced, and it will be destroyed in the end~</p> <p><code>Wall</code>Add a method to the class <code>displayWall</code>:</p> <pre><code>def displayWall(self): MainGame.window.blit(source=self.image, dest=self.rect) </code></pre> <p><code>6</code>Then, let's randomly generate a wall in the game window .</p> <h2>【Improve the MainGame class】</h2> <p>In the method in the <code>MainGame</code>class , in the loop, add on the next line:<code>startGamewhile TrueMainGame.window.fill(color=BG_COLOR)</code></p> <pre><code>while Wall.count < 6: Wall(randint(60, 640), randint(60, 440)) for tank in EnemyTank.tanks: if Wall.walls and collide_rect(tank, Wall.walls[-1]): Wall.walls.pop() Wall.count -= 1 for tank in OurTank.tanks: if Wall.walls and collide_rect(tank, Wall.walls[-1]): Wall.walls.pop() Wall.count -= 1 for wall in Wall.walls[:-1]: if Wall.walls and collide_rect(wall, Wall.walls[-1]): Wall.walls.pop() Wall.count -= 1 </code></pre> <p>This <code>while</code>cycle ensures that every time a wall disappears, a wall is generated at a random location. Of course, the three loops inside <code>for</code>are to ensure that the generated walls do not overlap with other walls and tanks.</p> <p>Then show the generated wall in the game window, <code>while Wall.count < 6:</code>add on the next line (and side by side):</p> <pre><code>for wall in Wall.walls: wall.displayWall() </code></pre> <p>Run the screenshot:</p> <p>Hey, there are walls. But the problem comes again, the tank can walk over and overlap the wall... Don't panic, just modify it.</p> <h2>【Improve the Tank class】</h2> <p>Find <code>Tank</code>the <code>isCollide</code>method of the class and add it on the <code>else: return False</code>previous line (in <code>if "Tank" in [i.__name__ for i in getmro(cls=collided_type)]</code>parallel with):</p> <pre><code>elif "Wall" in [i.__name__ for i in getmro(cls=collided_type)]: for wall in collided_type.walls: if collide_rect(wall, self): return True return False </code></pre> <p>In <code>Tank</code>the methods in the class <code>moveTank</code>, modify all</p> <pre><code>if not ( self.isCollide(OurTank) or self.isCollide(EnemyTank) ): </code></pre> <p>for:</p> <pre><code>if not ( self.isCollide(Wall) or self.isCollide(OurTank) or self.isCollide(EnemyTank) ): </code></pre> <p>In the same way, also modify the movement of the enemy camp tanks.</p> <h2>[Improve the EnemyTank class]</h2> <p>In <code>EnemyTank</code>the methods in the class <code>moveTank</code>, modify all</p> <pre><code>if not ( self.isCollide(OurTank) or self.isCollide(EnemyTank) ): </code></pre> <p>for:</p> <pre><code>if not ( self.isCollide(Wall) or self.isCollide(OurTank) or self.isCollide(EnemyTank) ): </code></pre> <p>Run the screenshot:</p> <p>It's finally good, this time the tank can't go through the wall...</p> <p>Bullets! ! ! We're going to kill him without leaving a piece of armor! ! !</p> <h2>【Improve the Bullet class】</h2> <p><code>Bullet</code>Add the <code>__init__</code>method to the class:</p> <pre><code>def __init__(self, tank, *groups: AbstractGroup): super().__init__(*groups) self.my_image = image.load(r"img/enemymissile.gif") self.direction = tank.direction self.rect = self.my_image.get_rect() </code></pre> <p>The initial position of the bullet is worth thinking about, we must fire the bullet directly in front of the tank. Considering that the tank has four directions, therefore, the position of the bullet is divided into four cases, and continue to add in the method in the <code>Bullet</code>class :<code>__init__</code></p> <pre><code>if self.direction == Tank.UP: self.rect.left = tank.rect.left + (tank.rect.width / 2) - (self.rect.width / 2) self.rect.top = tank.rect.top - self.rect.height elif self.direction == Tank.DOWN: self.rect.left = tank.rect.left + (tank.rect.width / 2) - (self.rect.width / 2) self.rect.top = tank.rect.top + tank.rect.height elif self.direction == Tank.LEFT: self.rect.left = tank.rect.left - self.rect.width self.rect.top = tank.rect.top + (tank.rect.height / 2) - (self.rect.height / 2) elif self.direction == Tank.RIGHT: self.rect.left = tank.rect.left + tank.rect.width self.rect.top = tank.rect.top + (tank.rect.height / 2) - (self.rect.height / 2) </code></pre> <p>Then <code>Bullet</code>add the <code>displayBullet</code>method to the class:</p> <pre><code>def displayBullet(self): MainGame.window.blit(source=self.my_image, dest=self.rect) </code></pre> <p>We hope that in the game, when the space is pressed, the tanks of our camp will fire bullets.</p> <h2>【Improve the Tank class】</h2> <p><code>Tank</code>Add the <code>shooting</code>method to the class:</p> <pre><code>def shooting(self): self.bullets.append(Bullet(self)) </code></pre> <p>Once called <code>shooting</code>, it will add a bullet to its own bullet list.</p> <p>Then, we do a button processing.</p> <h2>【Improve the MainGame class】</h2> <p>Import the methods of the following modules:</p> <pre><code>from pygame import K_SPACE </code></pre> <p>in a method in <code>MainGame</code>a class , in<code>getEvent</code></p> <pre><code>if (e.key == K_UP or e.key == K_DOWN or e.key == K_LEFT or e.key == K_RIGHT): for tank in OurTank.tanks: tank.move_switch = e.key </code></pre> <p>The next line (in <code>if</code>parallel with) adds:</p> <pre><code>if e.key == K_SPACE: for tank in OurTank.tanks: tank.shooting() </code></pre> <p>In the method in the <code>MainGame</code>class , in the loop, on the previous line add:<code>startGamewhile True:display.update()</code></p> <pre><code>for tank in OurTank.tanks: for bullet in tank.bullets: bullet.displayBullet() </code></pre> <p>Run the screenshot:</p> <p>Hey, every time you press space, our tank will fire a bullet.</p> <p>But why don't the bullets move? ? ?</p> <p>Oh yes, the bullet should move in a straight line along the initial direction ha...</p> <h2>【Improve the Bullet class】</h2> <p><code>Bullet</code>Add a method to the class <code>moveBullet</code>:</p> <pre><code>if (0 <= self.rect.left <= (SCREEN_WIDTH - self.rect.width) and 0 <= self.rect.top <= (SCREEN_HEIGHT - self.rect.height)): if self.direction == Tank.UP: self.rect.top -= Tank.SPEED * 2 elif self.direction == Tank.DOWN: self.rect.top += Tank.SPEED * 2 elif self.direction == Tank.LEFT: self.rect.left -= Tank.SPEED * 2 elif self.direction == Tank.RIGHT: self.rect.left += Tank.SPEED * 2 </code></pre> <p>It means that as long as the bullet is fired, it will always <code>2</code>move in a straight line at times the speed of the tank.</p> <p>Then in the method of the <code>Bullet</code>class , on the previous line add:<code>displayBulletMainGame.window.blit(source=self.my_image, dest=self.rect)</code></p> <pre><code>self.moveBullet() </code></pre> <p>Run the screenshot:</p> <p>Damn, the bullet is fine, it's fired...</p> <p>There are three more small problems:</p> <ul> <li>Does not disappear at the game boundary</li> <li>Can't beat enemy tanks</li> <li>through the wall</li> </ul> <p>Let's solve it one by one...</p> <h2>【Improve the MainGame class】</h2> <p>If the bullet disappears at the border, you need to traverse all the bullets of our camp tanks, and delete the bullet as long as it reaches the border.</p> <p>In <code>MainGame</code>the <code>startGame</code>method in, in the <code>while True:</code>loop, on the <code>display.update()</code>previous line add:</p> <pre><code>for tank in OurTank.tanks: if tank.bullets: if not (0 <= tank.bullets[0].rect.left <= (SCREEN_WIDTH - tank.bullets[0].rect.width) and 0 <= tank.bullets[0].rect.top <= (SCREEN_HEIGHT - tank.bullets[0].rect.height)): tank.bullets = tank.bullets[1:] </code></pre> <p>Run the screenshot:</p> <p>Now you can see that the bullet disappears when it reaches the boundary...</p> <p>Then solve the two problems of bullets destroying enemies and breaking walls.</p> <h2>【Improve the Bullet class】</h2> <p><code>Bullet</code>Add a method to the class <code>isCollide</code>, the idea is similar <code>Tank</code>to the <code>isCollide</code>method in the class:</p> <pre><code>def isCollide(self, collided_type): if "Tank" in [i.__name__ for i in getmro(cls=collided_type)]: for tank in collided_type.tanks: if collide_rect(tank, self): collided_type.tanks.remove(tank) del tank collided_type.count -= 1 return True return False elif "Wall" in [i.__name__ for i in getmro(cls=collided_type)]: for wall in collided_type.walls: if collide_rect(wall, self): if wall.hp > 1: wall.hp -= 1 return True else: collided_type.walls.remove(wall) del wall collided_type.count -= 1 return True return False else: return False </code></pre> <p>After setting, you need to detect whether a collision occurs in real time in the game window.</p> <h2>【Improve the MainGame class】</h2> <p>in a method in <code>MainGame</code>a class , in a loop, in<code>startGamewhile True:</code></p> <pre><code>for tank in OurTank.tanks: for bullet in tank.bullets: bullet.displayBullet() </code></pre> <p>The next line (in <code>bullet.displayBullet()</code>parallel with) adds:</p> <pre><code>if bullet.isCollide(EnemyTank) or bullet.isCollide(Wall): tank.bullets.remove(bullet) </code></pre> <p>Run the screenshot:</p> <p>I won't post the animation, I believe you can find that when the bullet hits the tank, the tank disappears; the bullet hits the wall four times, and the wall disappears. Not happy, here, you are not far from success! ! !</p> <p>Then reload the tanks of the enemy camp and let them fire random bullets.</p> <p>in a method in <code>MainGame</code>a class , in a loop, in<code>startGamewhile True:</code></p> <pre><code>for tank in EnemyTank.tanks: tank.moveTank() </code></pre> <p>The next line ( <code>for</code>side by side with the loop) adds:</p> <pre><code>for enemy_tank in EnemyTank.tanks: if randint(1, 2000) <= 10: enemy_tank.shooting() </code></pre> <p>Used to fire random enemy bullets. Then in the method in the <code>MainGame</code>class , in the loop, in<code>startGamewhile True:</code></p> <pre><code>for tank in OurTank.tanks: for bullet in tank.bullets: bullet.displayBullet() if bullet.isCollide(EnemyTank) or bullet.isCollide(Wall): tank.bullets.remove(bullet) </code></pre> <p>The next line (in <code>for tank in</code>parallel with) adds:</p> <pre><code>for tank in EnemyTank.tanks: for bullet in tank.bullets: bullet.displayBullet() if bullet.isCollide(OurTank) or bullet.isCollide(Wall): tank.bullets.remove(bullet) </code></pre> <p>At this point, the bullets of the enemy tanks are displayed.</p> <p>Run the screenshot:</p> <p>That's right, hahahaha, it wiped out our faction...</p> <p>However, the bullet did not disappear when it reached the boundary. It doesn't matter. It is similar to dealing with the bullet of our camp. In the method in the <code>MainGame</code>class , in the loop, add on the previous line:<code>startGamewhile True:display.update()</code></p> <pre><code>for tank in EnemyTank.tanks: if tank.bullets: if not (0 <= tank.bullets[0].rect.left <= (SCREEN_WIDTH - tank.bullets[0].rect.width) and 0 <= tank.bullets[0].rect.top <= (SCREEN_HEIGHT - tank.bullets[0].rect.height)): tank.bullets = tank.bullets[1:] </code></pre> <p>Run the screenshot:</p> <p>The bullets are all right now!</p> <p>However, woo woo woo, our faction died too badly... We were beaten to death as soon as we came out... I want to be resurrected infinitely! ! ! Infinite resurrection! ! !</p> <p>Arrangement, generally press <code>r</code>the button to revive, then you need to add something.</p> <p>Import the methods of the following modules:</p> <pre><code>from pygame import K_r </code></pre> <p>in a method in <code>MainGame</code>a class , in<code>getEvent</code></p> <pre><code>if e.key == K_SPACE: for tank in OurTank.tanks: tank.shooting() </code></pre> <p>The next line (in <code>if</code>parallel with) adds:</p> <pre><code>if OurTank.count == 0 and e.key == K_r: OurTank(randint( 60 , 640 ), randint( 60 , 220 )) while ( OurTank.tanks[-1].isCollide(Wall) or OurTank.tanks[-1].isCollide(EnemyTank) ): OurTank.tanks.pop() OurTank.count -= 1 OurTank(randint(60, 640), randint(60, 220)) </code></pre> <p>Run the screenshot:</p> <p>Hehe, infinite resurrection, now you are not afraid of death! ! !</p> <p>However, I now want to display the current number of tanks of the enemy faction on the screen, what should I do?</p> <p>come on, look down...</p> <p>Add on the <code>class BaseItem(Sprite):</code>previous line:</p> <pre><code>FONT_COLOR = Color(255, 255, 0) </code></pre> <p>Import the methods of the following modules:</p> <pre><code>from pygame import font </code></pre> <p><code>MainGame</code>Add the <code>setTextSurface</code>method to the class:</p> <pre><code>@staticmethod def setTextSurface(text): font.init() my_font = font.SysFont(, size=18) text_surface = my_font.render(text, True, FONT_COLOR) return text_surface </code></pre> <p><code>MainGamestartGame</code>In the method in the class , in the <code>while True:</code>loop, add <code>MainGame.window.fill(color=BG_COLOR)</code>on the next line:</p> <pre><code>MainGame.window.blit( source=self.setTextSurface("Enemy tanks: {0}.".format( EnemyTank.count)), dest=(10, 10) ) </code></pre> <p>Run the screenshot:</p> <p>haha, perfect! ! !</p> <p>Are you done? No, this should have an explosion effect... Come on, continue to add explosion effects.</p> <h2>[Improve the Explosion class]</h2> <p><code>Explosion</code>Add properties to the class:</p> <pre><code>explosion = [] </code></pre> <p><code>Explosion</code>Add the <code>__init__</code>method to the class:</p> <pre><code>def __init__(self, bullet): self.rect = bullet.rect self.rect.left -= 40 self.rect.top -= 40 del bullet self.images = [ image.load(r"img/blast1.gif"), image.load(r"img/blast2.gif"), image.load(r"img/blast3.gif"), image.load(r"img/blast4.gif"), image.load(r"img/blast5.gif"), image.load(r"img/blast6.gif"), image.load(r"img/blast7.gif"), image.load(r"img/blast8.gif"), ] self.step = 0 Explosion.explosion.append(self) </code></pre> <p>The explosion is switched from the small picture to the large picture, so there are many pictures. <code>Explosion</code>Add the <code>displayExplosion</code>method to the class:</p> <pre><code>def displayExplosion(self): if self.step < 8: MainGame.window.blit(source=self.images[self.step], dest=self.rect) self.step += 1 return True else: return False </code></pre> <p>Then it needs to explode when the bullet collides with the wall and the tank, so...</p> <h2>【Improve the MainGame class】</h2> <p>in a method in <code>MainGame</code>a class , in a loop, in<code>startGamewhile True:</code></p> <pre><code>for tank in OurTank.tanks: for bullet in tank.bullets: bullet.displayBullet() if bullet.isCollide(EnemyTank) or bullet.isCollide(Wall): tank.bullets.remove(bullet) </code></pre> <p>The next line (in <code>tank.bullets.remove(bullet)</code>parallel with) adds:</p> <pre><code>Explosion(bullet=bullet) </code></pre> <p>Similarly, in methods in <code>MainGame</code>classes , in loops, in<code>startGamewhile True:</code></p> <pre><code>for tank in EnemyTank.tanks: for bullet in tank.bullets: bullet.displayBullet() if bullet.isCollide(OurTank) or bullet.isCollide(Wall): tank.bullets.remove(bullet) </code></pre> <p>The next line (in <code>tank.bullets.remove(bullet)</code>parallel with) adds:</p> <pre><code>Explosion(bullet=bullet) </code></pre> <p>On the next line (in <code>for tank in EnemyTank.tanks:</code>parallel with) add:</p> <pre><code>for explosion in Explosion.explosion: if not explosion.displayExplosion(): Explosion.explosion.remove(explosion) of the explosion </code></pre> <p>Run the screenshot:</p> <p>Finally got the picture of the explosion... I'm so hard! ! !</p> <p>Well, it's basically done here, but there's still one less music effect, right? It's simple, come on.</p> <h2>【Improve the Music class】</h2> <p>Import the methods of the following modules:</p> <pre><code>from pygame.mixer import init from pygame.mixer import music </code></pre> <p><code>Music</code>Add the <code>__init__</code>method to the class:</p> <pre><code>def __init__(self, filename): self.filename = filename init() music.load(self.filename) </code></pre> <p><code>Music</code>Add the <code>playMusic</code>method to the class:</p> <pre><code>@staticmethod def playMusic(): music.play() </code></pre> <p>After thinking about it, there are several sounds that need to be added:</p> <ul> <li>game start sound</li> <li>bullet firing sound</li> <li>bullet explosion sound</li> <li>Our camp tank resurrection sound</li> </ul> <p>Well, add one by one...</p> <h2>[Improve the Explosion class]</h2> <p>In the method in the <code>Explosion</code>class, add in the first line:<code>displayExplosionelse</code></p> <pre><code>Music(r"img/blast.wav").playMusic() </code></pre> <h2>【Improve the MainGame class】</h2> <p>in a method in <code>MainGame</code>a class , in<code>getEvent</code></p> <pre><code>if OurTank.count == 0 and e.key == K_r: OurTank(randint( 60 , 640 ), randint( 60 , 220 )) while ( OurTank.tanks[-1].isCollide(Wall) or OurTank.tanks[-1].isCollide(EnemyTank) ): OurTank.tanks.pop() OurTank.count -= 1 OurTank(randint(60, 640), randint(60, 220)) </code></pre> <p>The next line (in <code>while</code>parallel with) add:</p> <pre><code>Music(r"img/add.wav").playMusic() </code></pre> <p>in a method in <code>MainGame</code>a class , in<code>getEvent</code></p> <pre><code>if e.key == K_SPACE: for tank in OurTank.tanks: tank.shooting() </code></pre> <p>The next line (in <code>tank.shooting()</code>parallel with) add:</p> <pre><code>Music(r"img/fire.wav").playMusic() </code></pre> <p>In <code>MainGame</code>the method in the class, add on the next<code>startGame</code> line:<code>display.set_caption("Tank War 1.0")</code></p> <pre><code>Music(r"img/start.wav").playMusic() </code></pre> <p>Run and you're done! ! ! ! ! ! !</p> <p>so excited! ! ! ! ! ! Congratulations, you learned how to reproduce tank battles! ! ! !</p> <p>It's not easy to write a blog, I hope you who are interested, give a like~~~</p> <p>Don't praise me in the comments, after all, I'm a novice, and we really don't have the qualifications.</p> <p>If you have any questions, remember to private message me, I will try my best to help you.</p> <p>usic.load(self.filename)</p> <p><code>Music</code>Add the <code>playMusic</code>method to the class:</p> <pre><code>@staticmethod def playMusic(): music.play() </code></pre> <p>After thinking about it, there are several sounds that need to be added:</p> <ul> <li>game start sound</li> <li>bullet firing sound</li> <li>bullet explosion sound</li> <li>Our camp tank resurrection sound</li> </ul> <p>Well, add one by one...</p> <h2>[Improve the Explosion class]</h2> <p>In the method in the <code>Explosion</code>class, add in the first line:<code>displayExplosion`else

Music(r"img/blast.wav").playMusic()

【Improve the MainGame class】

in a method in MainGamea class , ingetEvent

if OurTank.count == 0 and e.key == K_r:
    OurTank(randint( 60 , 640 ), randint( 60 , 220 ))
     while (
            OurTank.tanks[-1].isCollide(Wall) or
            OurTank.tanks[-1].isCollide(EnemyTank)
    ):
        OurTank.tanks.pop()
        OurTank.count -= 1
        OurTank(randint(60, 640), randint(60, 220))

The next line (in whileparallel with) add:

Music(r"img/add.wav").playMusic()

in a method in MainGamea class , ingetEvent

if e.key == K_SPACE:
    for tank in OurTank.tanks:
        tank.shooting()

The next line (in tank.shooting()parallel with) add:

Music(r"img/fire.wav").playMusic()

In MainGamethe method in the class, add on the nextstartGame line:display.set_caption("Tank War 1.0")

Music(r"img/start.wav").playMusic()

Run and you’re done! ! ! ! ! ! !

[External link image dumping…(img-hqFAgASO-1647272391768)]

so excited! ! ! ! ! ! Congratulations, you learned how to reproduce tank battles! ! ! !

It’s not easy to write a blog, I hope you who are interested, give a like~~~

Don’t praise me in the comments, after all, I’m a novice, and we really don’t have the qualifications.

If you have any questions, remember to private message me, I will try my best to help you.

Note: The complete code can be privately chatted with me.

You may also like...

Leave a Reply

Your email address will not be published.