最近,儿子一直缠着让我把之前给他编写的游戏重做一下,要加一些功能.但是因为之前写代码的时候刚学会python,当时的想法就是能跑就行,现在回头看来,代码的可维护性几乎为零.所以没办法只能冲头再来,重构了几乎所有代码.在编写的时候遇到了一个有意思的问题,儿子让我给游戏添加一种带追踪能力的导弹.导弹不仅要能追踪目标不断修正轨迹向着目标前进,同时导弹的弹头要一直朝向目标,我一听,这不就是弹道导弹嘛,有意思,整!
当时我想,这还不简单,在追踪的时候顺手把本体和目标之间连线的斜率计算出来不久完了吗?于是说干就干,但是当我到了真正编写的时候还是发现了一些问题.今天就来和大家分享一下.
首先呢,路径追踪很简单,我之前写过一篇相关的文章可以参考这个http://t.csdnimg.cn/soVGy,所以路径这里咱们就跳过.
这篇文章主要就聊聊如何实现导弹通过旋转始终指向目标.
先来看一张示意图
如图,当A点向B移动时是沿着AB移动,所以只要计算出AB的斜率时就会得到第一个旋转角度,当A移动到A`点时追踪目标变为了C,这时算出A`C的斜率就能得到第二个旋转角度,如此循环往复,无论被追踪的目标如何变化,我们只要算出当前目标和本身的斜率就能得出要旋转的角度了.明白了实现旋转的方法以后,我们就开始写代码.这里多说一句,其实一开始我在这里思考了很长时间,主要的问题是我的一个闪念,既然正切函数能够得到斜率,那么正弦余弦甚至正割余割,也应该能到倾斜角度(其实是废话,利用推导公式本来就可以推导出的,但我也不知道为什么非要跟自己较劲,花了一整天时间去复习初中数学),但是,正弦余弦有一个问题,就是要判断象限.我甚至写了一大段"if"函数去判断象限.知道我发现math模块里的atan2()这个方法,我才明白,我之前想多了.这个方法不用考虑象限,只需要输入对边和临边的值就会自动判断象限,从而得出当前的弧度.
代码如下:
import math
import pygame,sys
from math import *
pygame.init()
screen=pygame.display.set_mode((800,700),0,32)
pygame.display.set_caption("弹道导弹")
clock = pygame.time.Clock()
class Sprite1(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.image.load('2.png')
self.rect = self.image.get_rect()
def update(self, *args, **kwargs):
self.rect.center = pygame.mouse.get_pos()
class Sprite2(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.mumimage = pygame.image.load('1.png')
self.image = self.mumimage
# print(id(self.mumimage), id(self.image))
self.rect = self.image.get_rect()
self.speed = 3
def update(self, target):
deltax = target.rect.centerx - self.rect.midtop[0]
deltay = target.rect.centery - self.rect.midtop[1]
distance = math.sqrt(deltax ** 2 + deltay ** 2)
if distance != 0:
angle = math.degrees(math.atan2(-deltay, deltax))
self.image = pygame.transform.rotate(self.mumimage, angle - 90)
# print(id(self.mumimage), id(self.image))
stepx = self.speed * deltax / distance
stepy = self.speed * deltay / distance
self.rect.centerx += stepx
self.rect.centery += stepy
sprite1 = Sprite1()
sprite2 = Sprite2()
sprites = pygame.sprite.Group()
sprites.add(sprite1,sprite2)
while True:
for event in pygame.event.get():
if event.type==pygame.QUIT:
sys.exit()
screen.fill((0,0,0))
sprites.update(sprite1)
sprites.draw(screen)
pygame.draw.rect(screen, (0,255,0), sprite2.rect, 2)
pygame.display.update()
clock.tick(60)
运行效果如下
其实写到这里,我要的目的目的其实已经达到了.但是,在写代码的时候我发现一个问题.
可以看到,我在编写sprite2的时候,定义了一个self.mumimage对象,然后又创建了一个self.image对象指向self.mumimage.这样做,主要是因为python的变量赋值其实是将变量指向了对象的内存地址,也就是说python里的变量都是指针变量.
pygame.transform.rotate()这个方法会生成一个新的对象,同时在内存中开辟一个新的地址.
如果我们直接使用self.image = pygame.transform.rotate(self.mumimage, angle - 90),那么就会产生递归,self.image 会不停的迭代self.image会不停的指向新地址而pygame.transform.rotate()方法会使用self.image迭代的新对象.这就产生了无限递归,直至内存泄漏,程序崩溃.下面举个例子.当我们删掉self.mumimage这个变量,直接定义self.image = pygame.image.load('1.png'),那么运行会得到以下结果.
所以,在这里我们必须要先声明一个变量self.mumimage,然后用self.image捕获self.mumimage迭代后的对象.这样才能保证pygame.transformer.rotate()这个方法一直作用与self.mumimage而不会产生无限的递归导致程序崩溃.
最后是文章中用到的素材