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.
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.
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.
# -*- 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)