wxPython and pycairo practice record 5

wxPython and pycairo practice record 5

Leaving aside the crappy tank collision detection, start adding weapon systems.

5.1 Weapons

I want to try the effect of loading different bullets. Here, the weapon system is designed to be similar to a data structure, which is mainly responsible for loading and launching, and the data refresh is responsible for the loaded bullets. At the same time, it is necessary to add a method of loading weapons to the tank class. The enemy tank needs to fire automatically, and temporarily modify the enemy tank.

class Weapon:
    def __init__(self, capacity=10, speed=20):
        self._clip = [] # 弹夹
        self._capacity = capacity # 弹夹容量
        self._speed = speed # 发射出的子弹速度
        self._isFire = False # 开火状态
        self._tank = None # 被装载对象
        self._currentBullet = None # 当前要发射的子弹

    def GetCapacity(self):
        return self._capacity

    def GetSpeed(self):
        return self._speed

    def GetBulletNum(self):
        return len(self._clip)

    def isFire(self):
        return self._isFire

    def Loaded(self, tank):
        # 被装载
        self._tank = tank

    def AddBullet(self, bullet):
        # 装弹并返回装弹结果
        if len(self._clip) < self._capacity:
            self._clip.append(bullet)
            return True
        else:
            return False

    def Fire(self, state=True):
        # 开火或停火
        self._isFire = state
        if state and len(self._clip) > 0:
            self._currentBullet = self._clip.pop()
            # 根据坦克坐标和尺寸计算子弹起始位置
            direction = self._tank.GetDirection()
            x, y, w, h = self._tank.GetRect()
            _, _, bw, bh = self._currentBullet.GetRect()
            if direction == wx.WXK_UP:
                bx = x + w / 2 - bw / 2
                by = y - bh
            elif direction == wx.WXK_DOWN:
                bx = x + w / 2 - bw / 2
                by = y + h
            elif direction == wx.WXK_LEFT:
                bx = x - bw
                by = y + h / 2 - bh / 2
            elif direction == wx.WXK_RIGHT:
                bx = x + w
                by = y + h / 2 - bh / 2

            # 设置子弹方向
            self._currentBullet.SetDirection(direction)
            # 设置发射初始坐标
            self._currentBullet.SetX(bx)
            self._currentBullet.SetY(by)
            # 设置子弹速度
            self._currentBullet.SetSpeed(self._speed)
            # 发射
            self._currentBullet.Fired()

5.2 Bullets

The previous design of the image container was unreasonable, and the image had to be loaded initially. If you want to directly load the surface you draw, you need to change a lot in inheritance and rewriting. Draw the surface by yourself and output the picture and then load it, it is better to draw it directly with AI.
insert image description here

    def InitSceneObjects(self):
        super().InitSceneObjects()

        self.angle = math.pi / 10
        self.x = 0
        self.y = 100
        self.width = 100
        self.height = 20

    def DrawSceneObjects(self, ctx):
        # 矩形中心坐标
        centerX = self.x + self.width / 2
        centerY = self.y + self.height / 2

        ctx.translate(centerX, centerY) # 移动坐标原点到矩形中心
        ctx.rotate(self.angle) # 旋转弧度,pi 为180度
        ctx.translate(-centerX, -centerY) # 旋转后还原原点坐标,不影响后缀图像绘制

        ctx.rectangle(self.x, self.y, self.width, self.height)
        ctx.set_source_rgb(1, 0, 0)
        ctx.fill()

        # 每次刷新更新数据
        self.angle += math.pi / 10
        self.x += 10

Single-frame images inherit Sprite, and multi-frame images inherit MovieClip. The found bullet material is animated frame by frame, so it inherits from MovieClip.
insert image description here

class Bullet(MovieClip):
    def __init__(self, x=-100, y=0, path="bullets.png", rect=(0, 0, 16, 16), fps=100, type=0):
        super(Bullet, self).__init__(x, y, path, rect, fps)
        self._speed = 0 # 子弹初始速度
        self._type = type
        self._direction = None

    def SetDirection(self, direction):
        self._direction = direction

    def SetSpeed(self, speed):
        self._speed = speed

    def Fired(self):
        # 尽量只用中心对称的子弹,避免换方向发射还要做旋转处理
        self.GotoAndPlay(0, self._type)

    def Fly(self):
        if not self._destroyed and self._speed > 0:
            if self._direction == wx.WXK_UP:
                self._y -= self._speed
            elif self._direction == wx.WXK_DOWN:
                self._y += self._speed
            elif self._direction == wx.WXK_LEFT:
                self._x -= self._speed
            elif self._direction == wx.WXK_RIGHT:
                self._x += self._speed

    def Update(self, times, speed):
        self.Fly()
        super().Update(times, speed)

5.3 Collision detection between player bullets and enemy tanks

    def CheckCollisions(self):
        # 检测子弹和敌军坦克碰撞
        for bullet in self.bullets[:]:
            for tank in self.enemies[:]:
                if bullet.GetRect().Intersects(tank.GetRect()):
                    bullet.Destroy()
                    self.bullets.remove(bullet)
                    self.sceneObjects.remove(bullet)

                    tank.Destroy()
                    self.enemies.remove(tank)
                    self.sceneObjects.remove(tank)

                    break

5.4 Effects and codes

Put the tank into a file independently, and put the imperfect parts first.
insert image description here

# -*- coding: utf-8 -*-
# weapon.py

import wx
import wx.lib.wxcairo
import cairo
from display import MovieClip


class Weapon:
    def __init__(self, capacity=10, speed=20):
        self._clip = [] # 弹夹
        self._capacity = capacity # 弹夹容量
        self._speed = speed # 发射出的子弹速度
        self._isFire = False # 开火状态
        self._tank = None # 被装载对象
        self._currentBullet = None # 当前要发射的子弹

    def GetCapacity(self):
        return self._capacity

    def GetSpeed(self):
        return self._speed

    def GetBulletNum(self):
        return len(self._clip)

    def isFire(self):
        return self._isFire

    def Loaded(self, tank):
        # 被装载
        self._tank = tank

    def AddBullet(self, bullet):
        # 装弹并返回装弹结果
        if len(self._clip) < self._capacity:
            self._clip.append(bullet)
            return True
        else:
            return False

    def Fire(self, state=True):
        # 开火或停火
        self._isFire = state
        if state and len(self._clip) > 0:
            self._currentBullet = self._clip.pop()
            # 根据坦克坐标和尺寸计算子弹起始位置
            direction = self._tank.GetDirection()
            x, y, w, h = self._tank.GetRect()
            _, _, bw, bh = self._currentBullet.GetRect()
            if direction == wx.WXK_UP:
                bx = x + w / 2 - bw / 2
                by = y - bh
            elif direction == wx.WXK_DOWN:
                bx = x + w / 2 - bw / 2
                by = y + h
            elif direction == wx.WXK_LEFT:
                bx = x - bw
                by = y + h / 2 - bh / 2
            elif direction == wx.WXK_RIGHT:
                bx = x + w
                by = y + h / 2 - bh / 2

            # 设置子弹方向
            self._currentBullet.SetDirection(direction)
            # 设置发射初始坐标
            self._currentBullet.SetX(bx)
            self._currentBullet.SetY(by)
            # 设置子弹速度
            self._currentBullet.SetSpeed(self._speed)
            # 发射
            self._currentBullet.Fired()


class Bullet(MovieClip):
    def __init__(self, x=-100, y=0, path="bullets.png", rect=(0, 0, 16, 16), fps=100, type=0):
        super(Bullet, self).__init__(x, y, path, rect, fps)
        self._speed = 0 # 子弹初始速度
        self._type = type
        self._direction = None

    def SetDirection(self, direction):
        self._direction = direction

    def SetSpeed(self, speed):
        self._speed = speed

    def Fired(self):
        # 尽量只用中心对称的子弹,避免换方向发射还要做旋转处理
        self.GotoAndPlay(0, self._type)

    def Fly(self):
        if not self._destroyed and self._speed > 0:
            if self._direction == wx.WXK_UP:
                self._y -= self._speed
            elif self._direction == wx.WXK_DOWN:
                self._y += self._speed
            elif self._direction == wx.WXK_LEFT:
                self._x -= self._speed
            elif self._direction == wx.WXK_RIGHT:
                self._x += self._speed

    def Update(self, times, speed):
        self.Fly()
        super().Update(times, speed)
        
# -*- coding: utf-8 -*-
# tank.py

import random
import wx
import wx.lib.wxcairo
import cairo
from display import MovieClip
import board


class cv(board.cv):
    """
    常量,用类属性避免使用全局变量
    """
    # 面板尺寸
    BOARD_WIDTH = 600
    BOARD_HEIGHT = 400


class Tank(MovieClip):
    def __init__(self, *args, **kwargs):
        super(Tank, self).__init__(*args, **kwargs)
        self._speed = 10 # 移动速度
        self._dx = 0 # x 轴方向
        self._dy = 0 # y 轴方向
        self._weapon = None # 武器

    def Up(self):
        self._dy = -1
        self._dx = 0
        # 同一方向时需要需要排除,不然动画一直停留在第1帧
        if self._currentScene != 0:
            self.GotoAndPlay(0, 0)

    def Down(self):
        self._dy = 1
        self._dx = 0

        if self._currentScene != 1:
            self.GotoAndPlay(0, 1)

    def Left(self):
        self._dx = -1
        self._dy = 0

        if self._currentScene != 2:
            self.GotoAndPlay(0, 2)

    def Right(self):
        self._dx = 1
        self._dy = 0

        if self._currentScene != 3:
            self.GotoAndPlay(0, 3)

    def LoadWeapon(self, weapon):
        weapon.Loaded(self)
        self._weapon = weapon

    def GetWeapon(self):
        return self._weapon


class Player(Tank):
    def __init__(self, x, y, path="tank_T1_0.png", rect=(0, 0, 48, 48), fps=100):
        super(Player, self).__init__(x, y, path, rect, fps)
        self._direction = wx.WXK_UP

    def OnKeyDown(self, e):
        key = e.GetKeyCode()

        if key == wx.WXK_UP:
            self.Up()
            self._direction = wx.WXK_UP
        if key == wx.WXK_DOWN:
            self.Down()
            self._direction = wx.WXK_DOWN
        if key == wx.WXK_LEFT:
            self.Left()
            self._direction = wx.WXK_LEFT
        if key == wx.WXK_RIGHT:
            self.Right()
            self._direction = wx.WXK_RIGHT

        self._x += self._dx * self._speed
        self._y += self._dy * self._speed

        # 开火
        if key == wx.WXK_SPACE and self._weapon:
            self._weapon.Fire(True)

    def OnKeyUp(self, e):
        key = e.GetKeyCode()

        if key == wx.WXK_UP or key == wx.WXK_DOWN:
            self._dy = 0
        if key == wx.WXK_LEFT or key == wx.WXK_RIGHT:
            self._dx = 0
        if self._dx == 0 and self._dy == 0:
            self.Stop()

        # 停火
        if key == wx.WXK_SPACE and self._weapon:
            self._weapon.Fire(False)

    def GetDirection(self):
        return self._direction

class Enemy(Tank):
    def __init__(self, x, y, path="tank_T1_0.png", rect=(0, 0, 48, 48), fps=100, mixColor=(0, 1, 1, 0.8)):
        self._mixColor = mixColor # 自定义混合颜色
        super(Enemy, self).__init__(x, y, path, rect, fps)
        # 初始随机方向
        self._direction = random.choice([wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT, wx.WXK_RIGHT])
        self._speed = 1
        self._isPlaying = True

    def LoadFrames(self, rect):
        # 将图像载为帧前,先改变图像的色相
        surface = cairo.ImageSurface.create_from_png("tank_T1_0.png")
        sw = surface.get_width()
        sh = surface.get_height()

        # 创建只显示坦克的 surface 作为混合层
        surface2 = cairo.ImageSurface(cairo.FORMAT_ARGB32, sw, sh)
        ctx = cairo.Context(surface2)
        ctx.set_source_rgba(*self._mixColor)
        ctx.mask_surface(surface)

        # 与目标图像混合
        ctx = cairo.Context(surface)
        ctx.set_source_surface(surface2)
        ctx.set_operator(cairo.Operator.HSL_HUE) # 将色相和目标图像色相混合
        ctx.paint()

        self._surface = surface
        super().LoadFrames(rect)

    def GetDirection(self):
        return self._direction

    def SetDirection(self, direction):
        self._direction = direction

    def ChangeDirection(self, direction):
        # 方向转换为除指定方向以外的方向,总觉得这样写不太好 加个
        # TODO
        directions = [wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT, wx.WXK_RIGHT]
        directions.remove(direction)
        self._direction = random.choice(directions)

    def AutoWalk(self):
        if self._direction == wx.WXK_UP:
            self.Up()
        elif self._direction == wx.WXK_DOWN:
            self.Down()
        elif self._direction == wx.WXK_LEFT:
            self.Left()
        elif self._direction == wx.WXK_RIGHT:
            self.Right()

        # 遇边界转向,坦克宽高明明是48,可是按48处理显示不对
        # TODO
        if self._x < 0:
            self._x = 0
            self.ChangeDirection(wx.WXK_LEFT)
        elif self._x > cv.BOARD_WIDTH - 68:
            self._x = cv.BOARD_WIDTH - 68
            self.ChangeDirection(wx.WXK_RIGHT)
        else:
            self._x += self._dx * self._speed

        if self._y < 0:
            self._y = 0
            self.ChangeDirection(wx.WXK_UP)
        elif self._y > cv.BOARD_HEIGHT - 96:
            self._y = cv.BOARD_HEIGHT - 96
            self.ChangeDirection(wx.WXK_DOWN)
        else:
            self._y += self._dy * self._speed

    def Update(self, times, speed):
        # 由主程序刷新时更新
        self.AutoWalk()
        super().Update(times, speed)

# -*- coding: utf-8 -*-
# example2.py

import random
import math
import wx
import wx.lib.wxcairo
import cairo
import board
from display import MovieClip
from tank import Player, Enemy
from weapon import Weapon, Bullet

class cv(board.cv):
    """
    常量,用类属性避免使用全局变量
    """
    # 面板尺寸
    BOARD_WIDTH = 600
    BOARD_HEIGHT = 400


class Board(board.Board):
    def DrawBackground(self, ctx):
        super().DrawBackground(ctx)

        text = "SmileBasic"
        ctx.set_font_size(40)
        _, _, w, h, _, _ = ctx.text_extents(text)
        x = (cv.BOARD_WIDTH - w) // 2
        y = (cv.BOARD_HEIGHT - h) // 2

        # 文字是以首个字左下角坐标定位,而矩形是以左上角定位,y轴相差文字的高度,不需要考虑线条宽度。
        # 另外PaintDC是不含标题栏的。
        ctx.rectangle(x - 10, y - 10 - h, w + 20, h + 20)
        ctx.set_source_rgb(1, 0, 0)
        ctx.stroke()

        ctx.move_to(x, y)
        ctx.set_source_rgb(1, 1, 1)
        ctx.show_text(text)

    def InitSceneObjects(self):
        super().InitSceneObjects()
        self.enemies = []

        self.player = Player(50, 50) # 实例化一个玩家坦克
        self.sceneObjects.append(self.player)

        # 坦克在四个角的坐标
        coordinates = [(0, 0),
                       (cv.BOARD_WIDTH - 48, 0),
                       (0, cv.BOARD_HEIGHT - 48),
                       (cv.BOARD_WIDTH - 48, cv.BOARD_HEIGHT - 48)]
        # 生成10个敌军坦克
        for i in range(10):
            coord = random.choice(coordinates)
            enemy = Enemy(x=coord[0], y=coord[1], mixColor=(random.random(), random.random(), random.random(), 0.8))
            self.enemies.append(enemy)
            self.sceneObjects.append(enemy)

        # 初始武器系统,并装弹
        self.bullets = []
        weapon = Weapon(capacity=100, speed=2)
        for i in range(50):
            bullet = Bullet(type=3) # 装载第4行子弹
            weapon.AddBullet(bullet)
            self.bullets.append(bullet)
            self.sceneObjects.append(bullet)

            bullet = Bullet(type=6) # 装载第7行子弹
            weapon.AddBullet(bullet)
            self.bullets.append(bullet)
            self.sceneObjects.append(bullet)
        self.player.LoadWeapon(weapon) # 玩家坦克加载武器系统

    def DrawSceneObjects(self, ctx):
        for so in self.sceneObjects:
            if not so.IsDestroyed():
                ctx.set_source_surface(so.GetSurface(), so.GetX(), so.GetY())
                ctx.paint()

    def OnKeyDown(self, e):
        self.player.OnKeyDown(e)
        print("剩余子弹", self.player.GetWeapon().GetBulletNum())
        self.Refresh()

    def OnKeyUp(self, e):
        self.player.OnKeyUp(e)

    def CheckStrategies(self):
        self.CheckCollisions()

    def CheckCollisions(self):
        # 检测子弹和敌军坦克碰撞
        for bullet in self.bullets[:]:
            for tank in self.enemies[:]:
                if bullet.GetRect().Intersects(tank.GetRect()):
                    bullet.Destroy()
                    self.bullets.remove(bullet)
                    self.sceneObjects.remove(bullet)

                    tank.Destroy()
                    self.enemies.remove(tank)
                    self.sceneObjects.remove(tank)
                    
                    break

        # checked = [] # 存储已检测过的坦克对象
        for tank1 in self.enemies:
            for tank2 in self.enemies:
                # 忽略自身和已检测过的坦克
                if tank1 is tank2:
                # if tank1 is tank2 or tank2 in checked:
                    continue

                rect1 = tank1.GetRect()
                rect2 = tank2.GetRect()
                x1, y1, w1, w2 = rect1
                x2, y2, w2, h2 = rect2
                direction1 = tank1.GetDirection()
                direction2 = tank2.GetDirection()

                if rect1.Intersects(rect2):
                    # 同方向相撞>>,位置在后的转向。
                    # 不同轴方向相撞>^或^>,撞向侧面的那个转向。
                    # print(wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT, wx.WXK_RIGHT)
                    # 315 317 314 316
                    if abs(direction1 - direction2) != 2:
                    # if direction1 == direction2 or abs(direction1 - direction2) == 1 or abs(direction1 - direction2) == 3:
                        if direction1 == wx.WXK_UP and y1 >= y2:
                            tank1.SetDirection(wx.WXK_DOWN)
                        elif direction1 == wx.WXK_DOWN and y1 <= y2:
                            tank1.SetDirection(wx.WXK_UP)
                        elif direction1 == wx.WXK_LEFT and x1 >= x2:
                            tank1.SetDirection(wx.WXK_RIGHT)
                        elif direction1 == wx.WXK_RIGHT and x1 <= x2:
                            tank1.SetDirection(wx.WXK_LEFT)
                        else:
                            direction2 = (direction2 <= 315) and (direction2 + 2) or (direction2 - 2)
                            tank2.SetDirection(direction2)
                    # 相对方向相撞><,都转向。
                    else:
                    # elif abs(direction1 - direction2) == 2:
                        # TODO: 哪里错了?
                        # tank1.SetDirection(direction2)
                        # tank2.SetDirection(direction1)
                        tank1.ChangeDirection(direction1)
                        tank2.ChangeDirection(direction2)
            # checked.append(tank1)



Guess you like

Origin blog.csdn.net/SmileBasic/article/details/126364644