飞机大战中,敌机的角色是必不可少的,其运动轨迹有很多种,简单点的,用初中、高中的几何知识,也能创建不少。
例如:圆圈
radius = 200
for angle in range(0,361,10):
a = angle*math.pi/180
pos_x = radius *math.sin(a)
pos_y = radius *math.cos(a)
稍微复杂点 sin函数,控制下中心点y坐标(300)和振幅(200),也能实现很多
for x in range(0,SCENEWIDTH):
pos_x = x
angle = x *math.pi/180
pos_y = 200*math.sin(angle)+300
再例如抛物线函数,螺旋曲线函数,都能实现飞机的各种花样轨迹。
调试下各个参数的数值,顺便温故下初中高中的知识,也是很有乐趣的。
但简单、易行且路径花样繁多的公式,还是得用到贝塞尔曲线方式。可以自行百度,太神奇了。
参考下阿呆的入门介绍。
http://www.cnblogs.com/wjtaigwh/p/6647114.html
看下n 阶贝塞尔曲线计算公式实现
https://www.jianshu.com/p/7c56103dcf63
编写了一个容易理解点的 python的实现方式。
用到了下面的知识点
----------------------------------------------------------------
将贝塞尔曲线一般参数公式中的表达式用如下方式表示:
设有常数 a,b 和 c,则该表达式可统一表示为如下形式:
a * (1 - t)^b * t^c * Pn;
根据上面的分析就可以总结出 a,b,c 对应的取值规则:
-------------------------------------------------------------------
理论基础有了,开始写代码
a 值用杨辉三角计算,b ,c 值在for 循环里计算,Pn从传入的点坐标读取。
二阶,三阶直接用展开后的公式简单。
对于n阶要用杨辉三角。杨辉三角网上的计算代码很简洁,高效,可阅读起来不直观,我简单的把C++的实现方式改了下,看起来容易理解多了。
"""
杨辉三角的数的规律
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
……
"""
def YangHui(n):
#初设化,并赋值为0,建立二维数组,res[i][i],i=0~n-1;
# 其实也可以直接建立 res[n][n]数组。
res = [[0 for i in range(j + 1)] for j in range(n)]
#左右两边赋值1
for i in range(0, n):
res[i][0] = 1
res[i][i] = 1
#从第二个数开始,等于前列的左边一个加上正上方一个
for row in range(1, n):
for col in range(1, row):
res[row][col] = res[row - 1][col - 1] + res[row - 1][col]
return res
返回的是n阶要用到的a值的一个二维数组。
下面开始进行贝塞尔的计算了。points为控制点的列表,格式为points = [(10, 40), (200, 400), (450, 200), (100, 30)]之类,包含两个坐标,t的值在[0,1]之间,含义和公式里的t一致。p[v][0] 公式里的第n个点(Pn)的x值
def bezier(points, t):
n = len(points)
pointx = 0
pointy = 0
nrow_a = YangHui(n)
for v in range(0, n):
c = v
b = n - 1 - c
a = nrow_a[n - 1][v]
# print('a:', a, "b:", b, "c:", c)
# print(points[v][0], points[v][1])
#a * (1 - t)^b * t^c * Pn
pointx += a * math.pow((1 - t), b) * math.pow(t, c) * points[v][0]
pointy += a * math.pow((1 - t), b) * math.pow(t, c) * points[v][1]
return pointx, pointy
返回的是坐标点的x,y值。
开始测试代码,其实pygame提供了贝塞尔曲线的绘制函数,源代码里也有(可惜我看不懂)。
用我们自己写的代码来绘制,比较下pygame提供的函数绘制,看看效果如何。为了体现贝塞尔方程是优势,也顺便画了直线方程。
def main():
surf = pygame.display.set_mode((600,800), 1, 32)
pygame.display.set_caption("贝塞尔曲线测试")
surf.fill((0, 0, 0))
#四个测试点
points = [(10, 40), (200, 400), (450, 200), (100, 30)]
#pygame提供的函数
col = (255, 0, 0)
pygame.gfxdraw.bezier(surf, points,10, col)
#画直线
col2 = (0, 255, 0)
pygame.draw.line(surf, col2, (10, 40), (200, 400), 10)
pygame.draw.line(surf, col2, ((200, 400)), (450, 200), 10)
pygame.draw.line(surf, col2, ((450, 200)), (100, 30), 10)
#自己编写的函数实现
col3 = (0, 0, 255)
point2 = []
for v in range(0, 101):
#0.01递增,共100个点
t = v / 100
point2.append(bezier(points, t))
for v in range(1, 101):
pygame.draw.line(surf, col3, point2[v - 1], point2[v], 1)
pygame.display.flip()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
main()
可以看到红蓝线是重合的,肉眼很难分辨坐标点差别。
pygame的实现方式在SDL_gfxPrimitives.c里,源代码如下:
/* ---- Bezier curve */
/*!
\brief Internal function to calculate bezier interpolator of data array with ndata values at position 't'.
\param data Array of values.
\param ndata Size of array.
\param t Position for which to calculate interpolated value. t should be between [0, ndata].
\returns Interpolated value at position t, value[0] when t<0, value[n-1] when t>n.
*/
double _evaluateBezier (double *data, int ndata, double t)
{
double mu, result;
int n,k,kn,nn,nkn;
double blend,muk,munk;
/* Sanity check bounds */
if (t<0.0) {
return(data[0]);
}
if (t>=(double)ndata) {
return(data[ndata-1]);
}
/* Adjust t to the range 0.0 to 1.0 */
mu=t/(double)ndata;
/* Calculate interpolate */
n=ndata-1;
result=0.0;
muk = 1;
munk = pow(1-mu,(double)n);
for (k=0;k<=n;k++) {
nn = n;
kn = k;
nkn = n - k;
blend = muk * munk;
muk *= mu;
munk /= (1-mu);
while (nn >= 1) {
blend *= nn;
nn--;
if (kn > 1) {
blend /= (double)kn;
kn--;
}
if (nkn > 1) {
blend /= (double)nkn;
nkn--;
}
}
result += data[k] * blend;
}
return (result);
}
公式是同一个公式,反正我没看懂。