>>点此回到教程目录
弹幕:“深弹幕结界”(「深弾幕結界」)仿制
制作难易度:无法测定 (非常困难/10)
运行结果
↑点击这里观看包含音乐的高画质版本↑ (译者注:这是niconico动画)
首先呢,我并不准备为大家介绍这个弹幕的制作方法。(诶?)
请认为本章仅仅是向大家说明“这样子的弹幕只要努力的话就能做出来哦”这种程度的介绍而已。m( )m
为了非常贴近地完成这个弹幕,由于无法进行完全相同的动作,因此我不断地截屏重播动画、停止动画、再截屏,然后在画板之类的工具里面确认子弹的坐标、求角度、推算计算式以求与原作中基本相同……一直这样重复工作。具体要我说明的话那就太麻烦了orz。
我大致说明一下计算式的制作方法。
首先,反复多次重播动画,然后确认这些动作是怎么进行的。
这样的话可以发现,魔法阵在以区域的中心为基准进行旋转,从那里(译者注:指魔法阵的外围)一个个地发出朝中心方向发射出去的子弹。 这个弹幕的第1发子弹朝着中心飞去,然后稍微改变一下子弹的前进角度,最后发射出去的子弹以在某种程度上稍微偏离中心的“不同角度”飞去。(译者注:如果您还记得高中物理电学部分的话,可以考虑这样一个类似的模型:带正电的运动粒子朝同样带正电的固定粒子飞去,一开始是朝着固定粒子的中心飞去,但是由于电场力的作用使运动粒子的运动方向逐渐偏离固定粒子中心,然后远离固定粒子飞走。)
至于这个“不同角度”是多少,需要像之前那样使用利用像素坐标求角度的方法来计算。
举个例子,最后发射的子弹会从某个坐标开始朝着某个方向飞去。
首先,从截图中我们确认一下坐标。我确认的坐标是[87,78]。
而中心坐标是[192,215]。
而子弹实际上射出去的目标坐标为[185,389]。
现在,通过这3个点,我们就可以解出这样两条直线了:最后发射的子弹和中心连线的直线、最后发射的子弹的轨迹的直线。
当我们计算出两条直线后,也就能够知道两条直线的夹角了。
由于已经知道了中心的坐标,接下来只要我们知道了需要变化几次(译者注:从源代码可以看出,这个变化次数事实上由您自己利用时间来确定,当这个时间值越小则轨道越精确,但是计算量也越大),也就知道要以多大角度移动了。
因此我们试着写这样一个工具程序:输入4个点返回两条直线所成的角度。
#include <stdio.h> #include <math.h> #define PI 3.141592653589793238462643383279 typedef struct{ double x,y; }p_t; int main(){ p_t p[4]; char st[2][5]={{"始点"},{"终点"}}; double angle0,angle1; for(int i=0;i<4;i++){ printf("输入第%d个的%s坐标(x,y)\n",i/2+1,st[i%2]); scanf(" %lf %lf",&p[i].x,&p[i].y); } angle0=atan2(p[1].y-p[0].y,p[1].x-p[0].x); angle1=atan2(p[3].y-p[2].y,p[3].x-p[2].x); printf("\n角度 = %.8f[rad]\n角度 = %.2f[°]\n" ,angle1-angle0,(angle1-angle0)/PI*180); return 0; }
角度 = 0.34866626[rad]角度 = 19.98[°] 输入第1个始点的坐标(x,y)87 78输入第1个终点的坐标( (x,y)192 215输入第2个始点的坐标( (x,y)87 78输入第2个终点的坐标( (x,y)185 389 角度 = 0.34866626[rad]角度 = 19.98[°]
角度 = 0.34866626[rad]角度 = 19.98[°]
输入第1个始点的坐标(x,y)87 78输入第1个终点的坐标( (x,y)192 215输入第2个始点的坐标( (x,y)87 78输入第2个终点的坐标( (x,y)185 389
使用这个程序试着计算角度之后,得知结果为19.98°。
我们干脆忽略小于1°的误差,直接考虑使用20°。
如果是60fps的话1帧就是16.66ms,我们通过距离和时间来求速度,然后写出符合原作中轨道的计算式。
这些的内容非常麻烦,弹幕也只做到2周目为止了,请见谅。m( ;)m
然后,将设定子弹暂时停止的时间的cnt_till、设定子弹开始运动的时间的cnt_stt追加到子弹的结构体中,不过这只是本章才会使用的变量,也就在本章的工程中追加了。
另外,我们还要让作为子弹的发射位置的魔法阵一遍旋转一边移动呢。
我们通过child这个结构体来完成这个工作。
由于这个机能我想在稍微后面再介绍,因此在这里我就不多说明了。
这种事情也是能实现的,请抱着这样的想法来阅读代码。
//深弹幕结界 void boss_shot_bulletH010(){ #define TM010 9000 //最大周期(并没有特别设定的意义) #define DIST010 (FMX/2*1.18) // childl在1周目圆周运动的大小 #define DIST0101 (FMX/2*0.95) // child在2周目圆周运动的大小 #define HANSHU 120 // child在1周目半圆周运动的时间 #define HANSHU1 180 // child在2周目半圆周运动的时间 #define GOOUT010 90 //child从中央往外移动的时间 #define KAISHI010 (HANSHU) //child开始发射的时间 #define KAISHI010_1 (HANSHU1*0.6) #define CHILD_TIME (HANSHU*5+HANSHU/3) //child的存在时间 #define CHILD_TIME1 (HANSHU1*3+HANSHU1*0.4) //child的存在时间 #define CHILD_SHOT_TIME (CHILD_TIME-KAISHI010)//child实际发射中的时间 #define CHILD_SHOT_TIME1 (CHILD_TIME1-KAISHI010_1)// child实际发射中的时间 #define ANG0 PI/9 //1周目的角度 #define ANG1 PI/6 //2周目的角度 #define TIME1 900 //1周目结束的时间 #define RAG 20 //往外发射后直到往内发射之间的延时 #define TERM0 20 //全部发射后直到开始运动的时间 #define ST_ED 130 //最初开始运动后直到最后的子弹开始运动的时间 #define ST0 (CHILD_SHOT_TIME+TERM0) //最开始子弹运动的时间 #define ED0 (CHILD_SHOT_TIME+TERM0+ST_ED) //最后的子弹运动的时间 #define TERM1 47 #define ST_ED1 105 #define ST1 (CHILD_SHOT_TIME1+TERM1) #define ED1 (CHILD_SHOT_TIME1+TERM1+ST_ED1) int i,j,k,t=boss_shot.cnt%TM010,t2=boss_shot.cnt; int tt1=boss_shot.cnt-TIME1; static int num,flag,knum; static double child_dist,child_angle,child_dist2,child_angle2; if(t2==0){//如果是最开始 input_phy_pos(FMX/2,FMY/2, 50); num=-1; flag=0; } //周期的最开始 if(t==0 || t2==TIME1){ num++; //child数据的初始化 child_dist=0; child_angle=0; child_dist2=0; child_angle2=0; knum=0; } //child的登录 if(t2==GOOUT010 || t2==TIME1){ int j=2; for(i=j-2;i<j;i++){ child[i].flag =1; child[i].x =boss.x; child[i].y =boss.y; child[i].range=0.5; child[i].spd =1; child[i].angle=0; child[i].knd =0; child[i].col =0; child[i].cnt =0; child[i].state=i; } } //1周目 if(num==0){ if(GOOUT010<=t){ //出现后直到将要往外扩展为止 if(GOOUT010<=t && t<GOOUT010+KAISHI010) child_dist+=DIST010/KAISHI010; //一般地旋转 child_angle+=PI/HANSHU; } } //2周目 if(num==1){ //出现后直到将要往外扩展为止 if(child[0].cnt<KAISHI010_1) child_dist+=DIST0101/KAISHI010_1; //一般地旋转 child_angle-=PI/HANSHU1; } //child数据计算 for(i=0;i<CHILD_MAX;i++){ if(child[i].flag>0){//如果已经登录的话 //轨道计算 child[i].x=boss.x+cos(child_angle+PI*child[i].state)*child_dist; child[i].y=boss.y+sin(child_angle+PI*child[i].state)*child_dist; //第一次 if(num==0){ if(KAISHI010<child[i].cnt){//如果超过发射开始的计数 if(((t+6)%36)/4<=5 && t%4==0){//这个时候登录子弹 for(j=0;j<3;j++){//往外发射3路 k=knum++; boss_shot.bullet[k].col = child[i].state;//子弹的颜色 boss_shot.bullet[k].x = child[i].x;//坐标 boss_shot.bullet[k].y = child[i].y; boss_shot.bullet[k].knd = 6;//子弹的种类 boss_shot.bullet[k].angle = bossatan3(k,boss.x,boss.y)+PI-PI/6+PI/6*j;//角度 boss_shot.bullet[k].flag = 1; boss_shot.bullet[k].cnt = 0; boss_shot.bullet[k].state = 10+j+10*child[i].state;//状态 boss_shot.bullet[k].spd = 0.4;//速度 se_flag[0]=1; } } else if(t%4==0){//除此之外,每4次执行1次 for(j=0;j<3;j++){ k=knum++; boss_shot.bullet[k].col = child[i].state;//子弹的颜色 boss_shot.bullet[k].x = child[i].x;//坐标 boss_shot.bullet[k].y = child[i].y; boss_shot.bullet[k].knd = 6;//子弹的种类 boss_shot.bullet[k].angle = bossatan3(k,boss.x,boss.y)+PI-PI/6+PI/6*j;//角度 boss_shot.bullet[k].flag = 1; boss_shot.bullet[k].cnt = 0; boss_shot.bullet[k].state = 30; boss_shot.bullet[k].spd = 0.4;//速度 } } } //如果到了销毁的时间则销毁之 if(child[i].cnt>CHILD_TIME) child[i].flag=0; } //第1次end //第二次 if(num==1){ //如果超过发射计数 if(KAISHI010_1<child[i].cnt){ if((tt1-55)%(3*22)<(3*15) && t%3==0){//这个时候登录 for(j=0;j<3;j++){//往外发射 k=knum++; boss_shot.bullet[k].col = child[i].state;//子弹的颜色 boss_shot.bullet[k].x = child[i].x;//坐标 boss_shot.bullet[k].y = child[i].y; boss_shot.bullet[k].knd = 6;//子弹的种类 boss_shot.bullet[k].angle = bossatan3(k,boss.x,boss.y)+PI-PI/6+PI/6*j;//角度 boss_shot.bullet[k].flag = 1; boss_shot.bullet[k].cnt = 0; boss_shot.bullet[k].state = 10+j+10*child[i].state; boss_shot.bullet[k].spd = 0.4;//速度 se_flag[0]=1; } } else if(t%3==0){//除此之外每3次执行一次 for(j=0;j<3;j++){//向外发射3路 k=knum++; boss_shot.bullet[k].col = child[i].state;//子弹的颜色 boss_shot.bullet[k].x = child[i].x;//坐标 boss_shot.bullet[k].y = child[i].y; boss_shot.bullet[k].knd = 6;//子弹的种类 boss_shot.bullet[k].angle = bossatan3(k,boss.x,boss.y)+PI-PI/6+PI/6*j;//角度 boss_shot.bullet[k].flag = 1; boss_shot.bullet[k].cnt = 0; boss_shot.bullet[k].state = 30; boss_shot.bullet[k].spd = 0.4;//速度 } } } //如果到了销毁的时间就销毁之 if(child[i].cnt>CHILD_TIME1) child[i].flag=0; } } } for(i=0;i<BOSS_BULLET_MAX;i++){ if(boss_shot.bullet[i].flag>0){ int cnt=boss_shot.bullet[i].cnt; int state=boss_shot.bullet[i].state; //0的话是第1周目内侧发射弹,100的话是2周目内侧发射弹 if(state==0 || state==100){ //如果到了停止的时间的话则停止并修改状态 if(boss_shot.bullet[i].cnt_till==cnt){ boss_shot.bullet[i].spd=0; boss_shot.bullet[i].state=1; if(state==100) boss_shot.bullet[i].state+=100; } } //1,101是在上面状态下停止后的状态 if(state==1 || state==101){ //如果到了开始运动的时间的话运动开始并改变状态 if(boss_shot.bullet[i].cnt_stt==cnt){ boss_shot.bullet[i].state++; if(flag==0) flag=1; boss_shot.bullet[i].cnt=0; } } //2,102是在上面状态下开始加速后(开始移动)的状态 if(state==2 || state==102){ if(flag==1) boss_shot.bullet[i].spd+=0.05; if(boss_shot.bullet[i].spd>2.0 && flag==1) flag=2; if(flag==2){ if(state==2) if(boss_shot.bullet[i].spd<2.0) boss_shot.bullet[i].spd+=0.05; if(state==102) if(boss_shot.bullet[i].spd<2.7) boss_shot.bullet[i].spd+=0.05; } } //11,21是各种1周目、2周目的外侧发射弹 if(state==11 || state==21){ if(cnt==RAG){ double zero_one; double ang,spd; if(num==0){ zero_one=(double)(t-GOOUT010-RAG-HANSHU)/CHILD_SHOT_TIME; ang=ANG0*zero_one; spd=2.3; } if(num==1){ zero_one=(double)(tt1-RAG-KAISHI010_1)/CHILD_SHOT_TIME1; ang=PI/6-(PI/6+PI/4)*zero_one; if(tt1==143) ang=PI/3; spd=2.3*0.55; } k=knum++; boss_shot.bullet[k].col = boss_shot.bullet[i].state==11 ? 0 : 1;//子弹的颜色 boss_shot.bullet[k].x = boss_shot.bullet[i].x;//坐标 boss_shot.bullet[k].y = boss_shot.bullet[i].y; boss_shot.bullet[k].knd = 6;//子弹的种类 boss_shot.bullet[k].angle = bossatan3(k,boss.x,boss.y)+ang;//角度 boss_shot.bullet[k].flag = 1; boss_shot.bullet[k].cnt = 0; if(num==0){ boss_shot.bullet[k].state = 0; boss_shot.bullet[k].cnt_till= (int)(150*zero_one+10); boss_shot.bullet[k].cnt_stt = (int)((ST0-(ST_ED+TERM0))*(1-zero_one)+(ST_ED+TERM0)+10); } if(num==1){ boss_shot.bullet[k].state = 100; boss_shot.bullet[k].cnt_till= (int)(150*zero_one); boss_shot.bullet[k].cnt_stt = (int)((ST1-(ST_ED1+TERM1))*(1-zero_one)+(ST_ED1+TERM1)); } boss_shot.bullet[k].spd = spd; } } } } }
谢辞:
36章承蒙array的鼓励与建议才顺利完成了。非常感谢。
请让我介绍一下array所制作的以编写36章为契机而制作的动画。