随便写的做耐久小教程——(5)基于数学的一些弹幕举例

本教程是基于umbrella丶2修改果引擎制作的耐久引擎(稍加改动)的教程,该引擎gm版本为Gamemaker8。
此章提及一些数学方法在弹幕设计中的应用,有些弹幕事实上使用频率并不高,编写此章的原因仅仅是希望能为读者带来一些启发
一.均分直线与多边形
在第(3)章中,我们介绍了以极坐标的方式生成正方形的方法,效果如下

同样,用极坐标的方式,可以通过如下函数创建多边形:

但是,通过这种方式创建的多边形中,弹幕以角度为单位平均分布。而有时,我们希望达到的是下图效果:

此时,我们就需要通过创建均分直线来实现目的
1.均分线段

假设有一条线段,其上可以分布有任意数量的点,其中,线段两端分别记为A0与A1,随着线段上的点由A0向A1靠近,An的下标“匀速”增大,那么要如何确定An的坐标?

总而言之,假设A0的坐标为(x0,y0),A1的坐标为(x1,y1),那么可以通过以下代码确定An的坐标:
xn=n*x1+(1-n)*x0
yn=n*y1+(1-n)*y0
通过这一形式,我们可以创建一条均分直线。比如,若我们要将一条线段平均分为十段,每段的端点上有一个弹幕,我们可以通过如下的代码形式实现:
for(i=0;i<=10;i+=1){
n=i/10
xn=n*x1+(1-n)*x0
yn=n*y1+(1-n)*y0
instance_create(xn,yn,obj)
}
2.多边形
为图简便,我们将上述的创建均分直线的代码改写为脚本

又因为,一个正多边形的边可以由圆上平均分布的数点确定

因此,创建多边形的代码可以写作类似如下形式:
R=150
dir=15
n=5
Nums=6
for(i=0;i<n;i+=1){
xA=lengthdir_x(R,360*i/n+dir)
yA=lengthdir_y(R,360*i/n+dir)
xB=lengthdir_x(R,360*(i+1)/n+dir)
yB=lengthdir_y(R,360*(i+1)/n+dir)
Line(xA,yA,xB,yB,Nums)
}
同样的,除了多边形以外,通过参数方程确定某些点,再连接这些点,可以生成众多不同的图像。
下列图形留作习题:


二.扩散
假设我们想要创建一个从点向外扩散的圆,显然可以通过以下代码实现:
for(i=0;i<60;i+=1){
inst=instance_create(400,304,obj)
inst.direction=i*6
inst.speed=3
}
但是,如果换作正方形等各种不规则图形,要怎么确定速度呢?
可以证明,当所有弹幕由同一点匀速射出时,它的形状将与速度矢量所形成的形状保持相似
因此,假设我们要创建一个扩散的多边形弹幕,可以通过更改上文所述的创建多边形代码实现:
R=10
dir=15
n=5
Nums=6
for(i=0;i<n;i+=1){
xA=lengthdir_x(R,360*i/n+dir)
yA=lengthdir_y(R,360*i/n+dir)
xB=lengthdir_x(R,360*(i+1)/n+dir)
yB=lengthdir_y(R,360*(i+1)/n+dir)
for(i=0;i<=10;i+=1){
n=i/10
xn=n*xB+(1-n)*xA
yn=n*yB+(1-n)*yA
inst=instance_create(400,304,obj)
inst.direction=point_direction(0,0,xn,yn)
inst.speed=point_direction(0,0,xn,yn)
}
}
三.正则变换与圆形区域随机弹
若我们要在一个半径为200的圆内创建200个随机弹,显然我们很容易想到如下方式:
repeat(200){
Dir=random(360)
Len=random(200)
instance_create(320+lengthdir_x(Len,Dir),240+lengthdir_y(Len,Dir),objCherry)
}

这种方式创建出的弹幕如图所示。
然而,由于这种方法创建出的弹幕,圆心位置的弹幕会比周围更加密集。在某些特定情况下(比如kid有可能位于圆心)会产生一些不合理的配置,有没有办法让所有位置的弹幕平均分布呢?
首先,我们将Dir与Len视作直角坐标系上的随机变量,如下图所示,显然,Dir与Len将在区域内平均分布,因此,在每块小正方形区域内的弹幕数量可视作相等

之后,我们将其投影到极坐标上,显然,如果要使弹幕保持平均分布不变,则每块投影区域的面积相等

作正则变换,有

将结果进一步简化,可得以下代码:
repeat(200){
Dir=random(360)
Len=200*sqrt(random(1))
instance_create(320+lengthdir_x(Len,Dir),240+lengthdir_y(Len,Dir),objCherry)
}
结果如下:

此外,若对参数稍加改动,还可能出现如下结果

Len=200*power(random(1),0.25) :中间稀,外部密
Len=200*sqrt(random(1)+0.5) :均匀分布圆环
Len=200*power(random(1),4) :内部密,外部稀
四.图形过渡与sigmoid函数
在上文中,我们提到了确定线段上An坐标的方法,那么,假设有两个图形,我想要让弹幕从图形1变为图形2,是否也可以用这种方法实现?
事实上,假设图形S0上每个点(xi,yi)都能对应到另一个图形S1上的点(x'i,y'i),那么就可以通过x‘’i=n*x'i+(1-n)*xi,y‘’i=n*y'i+(1-n)*yi的方式实现
例如:
Time+=1
if Time=1{
for(i=0;i<60;i+=1){
inst[i]=instance_create(200+lengthdir_x(100,i*6),304+lengthdir_y(100,i*6),obj)
}
}
if Time>=40 && Time<=50{
n=(Time-40)/10
for(i=0;i<60;i+=1){
x1=200+lengthdir_x(100,i*6)
y1=304+lengthdir_y(100,i*6)
x2=600+lengthdir_x(100,90-i*6)
y2=304+lengthdir_y(100,90-i*6)
inst[i].x=n*x2+(1-n)*x1
inst[i].y=n*y2+(1-n)*y1
}
}
这样,弹幕就能从一个圆变换成另一个圆
不过,这种变换并不平滑,为使变换更为流畅,我们可以将n用sigmoid函数来表示

如:
Time+=1
if Time=1{
for(i=0;i<60;i+=1){
inst[i]=instance_create(200+lengthdir_x(100,i*6),304+lengthdir_y(100,i*6),obj)
}
}
if Time>=40 && Time<=80{
n=1/(1+exp(-(Time-60)/2))
for(i=0;i<60;i+=1){
x1=200+lengthdir_x(100,i*6)
y1=304+lengthdir_y(100,i*6)
x2=600+lengthdir_x(100,90-i*6)
y2=304+lengthdir_y(100,90-i*6)
inst[i].x=n*x2+(1-n)*x1
inst[i].y=n*y2+(1-n)*y1
}
}
这种方式不仅可以在静态图形间进行变换,也可以在动态弹幕间进行变换,只需要弹幕的坐标变化可以通过代码来表示,例如:
Time+=1
if Time=1{
for(i=0;i<60;i+=1){
inst[i]=instance_create(200,304,obj)
spe[i]=random_range(10,20)
dir[i]=random(360)
x1[i]=200
y1[i]=304
}
}
for(i=0;i<60;i+=1){
x1[i]+=lengthdir_x(spe[i],dir[i])
y1[i]+=lengthdir_y(spe[i],dir[i])
}
if Time<40{
for(i=0;i<60;i+=1){
inst[i].x=x1[i]
inst[i].y=y1[i]
}
}
if Time>=40 && Time<=80{
n=1/(1+exp(-(Time-60)/2))
for(i=0;i<60;i+=1){
x2=600+lengthdir_x(100,90-i*6)
y2=304+lengthdir_y(100,90-i*6)
inst[i].x=n*x2+(1-n)*x1[i]
inst[i].y=n*y2+(1-n)*y1[i]
}
}
其中,黑体部分是模拟随机弹的x,y坐标变化的代码
Time分成<40和40<<80两种条件是为了防止e的次方过大导致数据溢出

附.一些常见的简单弹幕

for(i=0;i<=120;i+=1){
inst=instance_create(i*10-200,304,objCherry)
inst.direction=i*10
inst.speed=1
}
即:角度随着与直线端点的距离增大而增大

for(i=0;i<=60;i+=1){
dir=i*6
inst=instance_create(400+lengthdir_x(100,dir),304+lengthdir_y(100,dir),objCherry)
inst.direction=-i*6
inst.speed=1
}
即:速度角度与速度角度改变量相反

friction=0.5
hspeed+=lengthdir_x(1,point_direction(x,y,mouse_x,mouse_y))
vspeed+=lengthdir_y(1,point_direction(x,y,mouse_x,mouse_y))
将mouse_x,mouse_y改成跟踪对象的x,y坐标即可食用

若还有其他可能比较有意思的弹幕,可能会在教程(5.X)中补充,也欢迎在评论区或者加入我的群提问,群号726054484。
下一章将简要讲一讲关于GML中绘制函数的一些应用(不包括表面)