随便写的做耐久小教程——(3)基础弹幕种类制作

本教程是基于umbrella丶2修改果引擎制作的耐久引擎(稍加改动)的教程,该引擎gm版本为Gamemaker8。
一.单一弹幕Object制作
1.自机狙
自机狙的特征是朝向kid飞行,因此需要用到该函数:

因此,要实现该效果,可以通过如下方式:
在弹幕obj的create代码中输入:
direction=point_direction(x,y,player.x,player.y)
那么这个弹幕的速度方向就会朝向kid
2.随机弹
随机弹需要有一些随机的参数,因此需要用到以下函数

这些随机函数的使用方法众多,最简单的方法如:
在弹幕obj的create代码中输入:
direction=random(360)//随机角度
或者
speed=random_range(5,15)//随机速度
等等
3.图形弹幕
大多数简单的图形弹幕都可以通过函数或者函数的叠加来生成
A.极坐标函数
通过极坐标函数创建弹幕时,通常需要以下函数

其中,dir遵循角度制,逆时针增大
注:在Gamemaker8中,y坐标由上至下增大

使用此函数,我们可以通过形如
R=……//以dir为变量的函数
instance_create(X+lengthdir_x(R,dir),Y+lengthdir_y(R,dir),obj)
的代码来创建一个以(X,Y)为中心,形状与极坐标函数图像相同的图形弹幕
例如
①.圆形弹幕
我们知道,圆形在极坐标下可以用R(θ)=C的形式表示。因此,创建圆形弹幕,可以通过如下方式:
for(i=0;i<60;i+=1){
dir=i*6
R=100
instance_create(300+lengthdir_x(R,dir),300+lengthdir_y(R,dir),obj)
}
这段代码能够创建一个以300,300为圆心,半径为100,平均分布有60个obj的圆
②.方形弹幕
同样,正方形在极坐标下可以用

的形式表示。

因此,创建方形弹幕,可以通过如下方式:
for(i=0;i<60;i+=1){
dir=i*6
R=100/sqrt(1+abs(cos(2*(degtorad(dir)+pi/10)))
instance_create(300+lengthdir_x(R,dir),300+lengthdir_y(R,dir),obj)
}
这段代码能够创建一个以300,300为中心,对角线长度为200,每6度方向上有一个obj,顺时针旋转π/10的正方形
注:在GML中,cos和sin是弧度制,如果要将角度转为弧度,应使用degtorad()代码
B.参数方程函数
假设要创建一个以参数方程决定的曲线弹幕,可以通过如下方式:

例如:
①.直线
for(i=0;i<41;i+=1){
X=100+i*10
Y=100+i*10
instance_create(X,Y,obj)
}
②.正弦函数波
for(i=0;i<81;i+=1){
X=i*10
Y=304+sin(i/10)*30
instance_create(X,Y,obj)
}
具体效果可在游戏中测试
*C.创建后的行为分化
①.弹幕自主控制
假设,我们要让这个圆上的弹幕,一段时间后,上半部分变成随机弹,下半部分变成自机狙,怎么办呢?
简单来说,就是在创建时根据循环变量i为各个obj赋予一个特殊的变量,如:
for(i=0;i<60;i+=1){
dir=i*6+3
R=100
inst=instance_create(300+lengthdir_x(R,dir),300+lengthdir_y(R,dir),obj)
if i<30 inst.Num=1
else inst.Num=2
}
然后在这个弹幕obj的step代码中输入:
Time+=1
if Time=100{
if Num=1{
direction=random(360)
speed=random_range(5,10)
}
else{
direction=point_direction(x,y,player.x,player.y)
speed=15
}
}
即可实现所需效果
②.单一obj控制
假设,你想要一个obj创建出的所有弹幕继续保持着圆的形状,并且以这个obj为圆的中心旋转,可是你又不想要用弹幕来执行代码怎么办呢?
用单一obj控制所有弹幕的方式需要让这个obj记录下所有obj的id,以上述圆形弹幕为例,将其更改为:
for(i=0;i<60;i+=1){
dir=i*6
R=100
inst[i]=instance_create(x+lengthdir_x(R,dir),y+lengthdir_y(R,dir),obj)
}
之后,在这个obj的step中执行:
Time+=1
for(i=0;i<60;i+=1){
dir=i*6+Time
R=100
inst[i].x=x+lengthdir_x(R,dir)
inst[i].y=y+lengthdir_y(R,dir)
}
那么这个圆就会不断绕着这个obj旋转
4.变速弹幕
A.通过重力实现变速
最简单的使弹幕变速的方法是通过设定obj的重力值

以kid本身为例,player的gravity=0.4,gravity_direction=270
如果要创建一个向着左上角不断加速的弹幕,可以这样实现:
gravity=0.75
gravity_direction=135
B.通过step实现变速
而更多情况下,变速可以通过step函数进行,如:
①.随着时间缓慢减速
speed-=(speed>0)*0.25
if speed<0 speed=0
或通过阻力实现

friction=0.25
②.在某一时刻突然变速
Time+=1
if Time=10 speed=20
③.抛物线(效果相当于gravity)
hspeed+=xgrav
vspeed+=ygrav
其中xgrav、ygrav是每秒x、y方向上的速度增量
*④e^(-x)型缓动

有时候,我们希望让弹幕先快后慢的靠近空间中的某一个点,于是我们发现,如果将t作为自变量,e^(-t)+C作为因变量,那么这一函数正好具有此良好的性质,最终它的值会逐渐趋近于C。
我们知道,s=e^(-kt)+C的微分形式是ds=-ke^(-kt)dt,令dt为1,将s代入微分式,我们能够得到:
ds=-k(s-C)
也就是说,如果我们要让某个值逐渐靠近C,可以通过在step事件中添加如下代码实现:
s-=k(s-C)
其中,k代表收敛的速度(k的取值为0~1,否则会导致s超出C而产生bug)
于是,我们可以通过如下形式实现弹幕的变速:
speed-=0.1*(speed-1)
此时弹幕的速度会逐渐趋近于1
5.变角度弹幕
①角度增减
GML中没有类似于重力的特定的改变角度的函数,一般情况下,改变角度的方式和speed类似,如:
在step中输入:
direction+=3
*②角度趋近
在GML中,实现类似于缓动的角度趋近是困难的,因为角度的范围只能是0~360,当角度超过360后,它会自动减去360。因此,如果用与上述缓动形式相同的代码,当角度要从359变为0时,它不会选择最快速的顺时针旋转,而是会额外旋转将近一圈。
于是,如果要进行角度旋转,需要进行额外的分类讨论,以下是一种实现该效果的方式:
为图方便,首先,创建一个名为directionapproach的Script,输入以下代码:
//directionapproach(dirfrom,dirto,sca)
//example:dir-=directionapproach(dir,dirto,0.1)
if abs(argument0-argument1)<=180{
return argument2*(argument0-argument1)
}
if abs(argument0-argument1)>180{
return argument2*(sign(argument1-argument0)*(360-abs(argument0-argument1)))
}
然后,在需要进行角度趋近的弹幕的step事件中输入:
direction-=directionapproach(direction,dirto,sca)
其中,dirto表示要趋近于的角度值,sca相当于上文的k,表示趋近的速度
6.弹幕源
多数情况下,在时间轴中过多的创建时间节点较为麻烦,有时每步间隔又较长,不能满足密弹幕的需求,此时需要独立创建一个弹幕源,例如:创建一个obj并在step中输入以下代码:
Time+=1
if Time<100{
instance_create(200,304,Bullet)
}
这样就能创建一个100帧内每帧在(200,304)创建一个Bullet的弹幕源,并且在100帧时销毁
二.基础组合弹幕示例

上图为i wanna be the locus的弹幕,截图于2:06处
首先,我们对其行为进行分析,将其转化为几种基础弹幕的组合
我们可以将其行为分成
①.生成一个不断创建竖向自机狙的弹幕源
②.直线自机狙形状为圆形,同时是自机狙的图形弹幕,并且自身销毁
③.圆形弹幕离开房间后删除
在分析完行为之后,我们逐个实现它的行为
首先,创建一个obj,此处名为ObjCreator,将其作为弹幕源,并在时间轴中选择一个时刻创建该obj,然后,还需要两种弹幕obj,一种作为直线自机狙,一种作为圆形图形弹幕,此处将其命名为Bullet1和Bullet2
第一个行为要求该弹幕源一段时间创建一个竖向自机狙,因此,可以在ObjCreator的step事件中输入:
Time+=1
if (Time mod 100=0){
inst=instance_create(player.x,-50,Bullet1)
inst.direction=90
inst.speed=10
}
if (Time mod 100=50){
inst=instance_create(player.x,658,Bullet1)
inst.direction=270
inst.speed=10
}
然后,这个直线自机狙将在一段时间后销毁并创建图形自机狙弹幕,可以在Bullet1的step中输入:
Time+=1
if Time=30{
instance_destroy()
for(i=0;i<10;i+=1){
inst=instance_create(x,y,Bullet2)
inst.direction=i*36+point_direction(x,y,player.x,player.y)
}
}
最后,Bullet2要在离开房间后销毁以释放空间,因此可在其step中输入:
if bbox_right<0 or bbox_left>800 or bbox_bottom<0 or bbox_top>608{
instance_destroy()
}
或
if x<-20 or x>820 or y<-20 or y>628{
instance_destroy()
}

上图为被 ☆耐久の神☆「狗日的拉德」 薄纱的马云牧师的弹幕,截图于0:15处
同样,我们对其行为进行分析,将其转化为几种基础弹幕的组合
我们可以将其行为分成
①.创建单个苹果在kid的坐标
②.该苹果透明度增大至1
③.该苹果作为弹幕源创建圆形变速弹幕
④.圆形弹幕在回到中心的时刻删除
该项留作习题,代码可见显然,读者自撰不难
下一章将介绍如何进行debug操作