欢迎光临散文网 会员登陆 & 注册

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

2023-04-08 21:35 作者:_KomeijiKoishi  | 我要投稿

本教程是基于umbrella丶2修改果引擎制作的耐久引擎(稍加改动)的教程,该引擎gm版本为Gamemaker8。 


此章提及一些数学方法在弹幕设计中的应用,有些弹幕事实上使用频率并不高,编写此章的原因仅仅是希望能为读者带来一些启发

一.均分直线与多边形

在第(3)章中,我们介绍了以极坐标的方式生成正方形的方法,效果如下

极坐标版本正方形

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

n边形的极坐标公式

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

边长版本正方形

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

1.均分线段

假设有一条线段,其上可以分布有任意数量的点,其中,线段两端分别记为A0与A1,随着线段上的点由A0向A1靠近,An的下标“匀速”增大,那么要如何确定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.多边形

为图简便,我们将上述的创建均分直线的代码改写为脚本

其中,argumentX代表使用脚本时的第X个变量

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

因此,创建多边形的代码可以写作类似如下形式:

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=100*power(random(1),-0.5):在圆外渐稀
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函数来表示

sigmoid函数:y=1/(1+e^(-x))

如:

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中绘制函数的一些应用(不包括表面)

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

分享到微博请遵守国家法律