wxPython和pycairo练习记录5
先不管稀烂的坦克碰撞检测,开始添加武器系统。
5.1 武器类
想试试装载不同子弹效果,这里把武器系统设计成类似一个数据结构,主要负责装弹发射,数据刷新由所装载的子弹负责。同时需要给坦克类添加加载武器的方法,敌军坦克需要自动开火,暂时修改敌军坦克。
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 子弹类
之前设计图像容器不太合理,初始必须加载图片,如果想直接加载自己画的 surface ,继承重写要改很多。自己画 surface 输出图片再加载,还不如直接用 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
单帧的图像就继承 Sprite ,多帧的图像就继承 MovieClip 。找到的子弹素材是逐帧动画的,所以继承 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 玩家子弹和敌军坦克碰撞检测
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 效果和代码
把坦克也独立放到一个文件,不完善的地方先放着。
# -*- 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)