【四圣龙神录的编程教室】第24章、来制作boss吧

原文地址:
http://dixq.net/rp/24.html

那么接下来、终于要进入弹幕的制作了。
首先,要制作BOSS的弹幕的话,就需要有个BOSS。
BOSS的身体的制作的,要注意很多地方,比较麻烦,大家努力学习下吧。

在这里,稍微说点物理相关的话题吧。
龙神录里,BOSS看起来都是“咻”,“咻”的运动的。
虽然想按照“从这里到那里,移动这么长的时间”这样的指令来执行某个动作,但以速度匀速移动过去,到制定地点突然就停止了,这样看起来感觉不大好。
如果没有加速,减速,再停下来这样的过程的话,运动看起来就不漂亮。

于是,以“在这么长的距离,这么长的时间内,漂亮的移动过去”作为我们动作指令的目标,我们来考虑下怎么用算式来实现这个吧。

对数学学的不好的人可能会讨厌看到计算公式,我们就用很简单的计算来表示下看看吧。

物理的力学的匀加速运动中,有三个大公式。
就是下面3条公式。v是速度,a是加速度,y是距离,t是时间。

现在,我们考虑某个时间发射出去,在指定距离y里,用了一定的时间ty,做减速运动正好停止的这样一个问题。

v=v0at...
y=v0t12at2...
v2v20=2ay...

v0为初速度飞出去的物体,在时间ty内前进了ymax的距离后停下来时,最后的速度是0。所以把v=0 代入①, 得到
v0=aty
a=v0ty … ⑤

v=0再代入③,得到
v20=2aymax

把⑤代入上面这个式子
v20=2v0tyymax …④
两边都约去v0
v0=2tyymax … ⑥

把⑥代入⑤,得到
a=2t2yymax …⑦

把⑥,⑦都代入②
y=2ymaxtytymaxt2yt2 …A

这样,我们就推导出了式子A啦。
ty是一段时间,ymax是移动距离。把计数器代入t的话,y就可以算出来了。
这个在水平方向,竖直方向的分量都计算了的话,就可以实现漂亮的移动啦。
以下是使用了那个计算公式的地方,
input_phy(登陆)
calc_phy(计算)
我们看一下注释吧。



---- boss_shot.cpp 的改动 ----

#include "../include/GV.h"
#include "../include/func.h"

#define V0 10.0

int serch_boss_shot(){//返回空的编号
        for(int i=0;i<BOSS_BULLET_MAX;i++){
                if(boss_shot.bullet[i].flag==0)
                        return i;
        }
        return -1;
}
double bossatan2(){//自机和敌机形成的角度
        return atan2(ch.y-boss.y,ch.x-boss.x);
}
//进行物理运算的实现(在指定时间t内返回到指定位置)
void input_phy(int t){//t=移动消耗的时间
        doubley max_x,ymax_y;
        if(t==0)
            t=1;
        boss.phy.flag=1;//设置为启用
        boss.phy.cnt=0;//初始化时间计数
        boss.phy.set_t=t;//设置移动所需的时间
        ymax_x=boss.x-BOSS_POS_X;//准备移动的水平距离
        boss.phy.v0x=2*ymax_x/t;//水平方向的初速度
        boss.phy.ax =2*ymax_x/(t*t);//水平方向的加速度
        boss.phy.prex=boss.x;//初始的x坐标
        ymax_y=boss.y-BOSS_POS_Y;//准备移动的竖直距离
        boss.phy.v0y=2*ymax_y/t;//竖直方向的初速度
        boss.phy.ay =2*ymax_y/(t*t);//竖直方向的加速度
        boss.phy.prey=boss.y;//初始的y坐标
}
//角色的物理移动计算
void calc_phy(){
        double t=boss.phy.cnt;
        boss.x=boss.phy.prex-((boss.phy.v0x*t)-0.5*boss.phy.ax*t*t);//现在应该在的x坐标计算
        boss.y=boss.phy.prey-((boss.phy.v0y*t)-0.5*boss.phy.ay*t*t);//现在应该在的y坐标计算
        boss.phy.cnt++;
        if(boss.phy.cnt>=boss.phy.set_t)//移动的时间到了的话
                boss.phy.flag=0;//设置为关闭
}
//计算boss的弹幕
void boss_shot_calc(){
        int i;
        boss.endtime--;
        if(boss.endtime<0)
                boss.hp=0;
        for(i=0;i<BOSS_BULLET_MAX;i++){
                if(boss_shot.bullet[i].flag>0){
                        boss_shot.bullet[i].x+=cos(boss_shot.bullet[i].angle)*boss_shot.bullet[i].spd;
                        boss_shot.bullet[i].y+=sin(boss_shot.bullet[i].angle)*boss_shot.bullet[i].spd;
                        boss_shot.bullet[i].cnt++;
                        if(boss_shot.bullet[i].cnt>boss_shot.bullet[i].till){
                                if(boss_shot.bullet[i].x<-50 || boss_shot.bullet[i].x>FIELD_MAX_X+50 ||
                                        boss_shot.bullet[i].y<-50 || boss_shot.bullet[i].y>FIELD_MAX_Y+50)
                                        boss_shot.bullet[i].flag=0;
                        }
                }
        }
        boss_shot.cnt++;
}
//设置弹幕
void enter_boss_shot(){
        memset(&boss_shot , 0, sizeof(boss_shot_t));//初始化弹幕的信息
        boss_shot.flag=1;
        boss.wtime=0;//待机时间0
        boss.state=2;//在弹幕中的状态
        boss.hp=boss.set_hp[boss.knd];//设置血量
        boss.hp_max=boss.hp;
}
//设置boss
void enter_boss(int num){
        if(num==0){//中间的boss开始的时候
                memset(enemy,0,sizeof(enemy_t)*ENEMY_MAX);//杂兵消除
                memset(shot,0,sizeof(shot_t)*SHOT_MAX);//弹幕消除
                boss.x=FIELD_MAX_X/2;//boss的初始坐标
                boss.y=-30;
                boss.knd=-1;//弹幕的种类
        }
        boss.flag=1;
        boss.hagoromo=0;//是否扇形散开的标志
        boss.endtime=99*60;//剩余时间
        boss.state=1;//设置为待机状态
        boss.cnt=0;
        boss.graph_flag=0;//重设绘制的标志开关
        boss.knd++;
        boss.wtime=0;//初始化待机的时间
        memset(&boss_shot,0,sizeof(boss_shot_t));//初始化boss的弹幕情况
        input_phy(60);//经过60个计数后,物理计算运动的位置
}
//boss的待机处理
void waitandenter(){
        int t=140;
        boss.wtime++;
        if(boss.wtime>t)//待机了140个计数后,设置弹幕
                enter_boss_shot();
}
//boss的弹幕主方法
void boss_shot_main(){
        if(stage_count==boss.appear_count[0] && boss.flag==0)//开始时间到了的话
                enter_boss(0);//开始
        if(boss.flag==0)//boss没有设置为启动的话,就返回
                return;
        if(boss.phy.flag==1)//物理演算移动开启的话
                calc_phy();//进行物理计算
        if(boss.state==2 && (boss.hp<=0 || boss.endtime<=0)){//弹幕过程中,体力没了的话
                enter_boss(1);//启动下一个弹幕
        }
        if(boss.state==1){//弹幕中间的待机时间
                waitandenter();
        }
        if(boss.state==2){//如果在弹幕过程中
                boss_shot_bullet[boss.knd]();//弹幕函数
                boss_shot_calc();//弹幕计算
        }
        boss.cnt++;
}

哎呀~boss的控制看起来很麻烦呢。
首先,stage_count代表在游戏里面的计时,如果它和表示boss什么时候出现的boss.appear_count 一致的话,就让boss出现。
我们就用boss这个变量来表示boss,这里先给他设置一个标志。
设置 boss.flag=1
boss的状态等于1时,是弹幕和弹幕之间的休息时间。敌机处于待机状态。
boss的状态等于2时,正处于弹幕发射状态中。
传递给弹幕函数的方法,是之前射击里用的函数指针。

这里从最上面开始看具体实现。

这里调用 double bossatan2() 函数,可以很方便的返回自机和敌机所形成的角度。弹幕制作时一定要用到这个函数哦。

boss什么时候出现,体力为多少,等这些信息,都通过 ini函数设置预先设置好了适当的值。


---- ini.cpp ini()的改动 ----

//游戏初始化
void ini(){
        stage_count=1;
        memset(&ch,0,sizeof(ch_t));
        memset(enemy,0,sizeof(enemy_t)*ENEMY_MAX);
        memset(enemy_order,0,sizeof(enemy_order_t)*ENEMY_ORDER_MAX);
        memset(shot,0,sizeof(shot_t)*SHOT_MAX);
        memset(cshot,0,sizeof(cshot_t)*CSHOT_MAX);
        memset(effect,0,sizeof(effect_t)*EFFECT_MAX);
        memset(del_effect,0,sizeof(del_effect_t)*DEL_EFFECT_MAX);
        memset(&bom,0,sizeof(bom_t));
        memset(&bright_set,0,sizeof(bright_set_t));
        memset(&dn,0,sizeof(dn_t));
        memset(&boss,0,sizeof(boss_t));

        ch.x=FIELD_MAX_X/2;
        ch.y=FIELD_MAX_Y*3/4;
        ch.power=500;
        boss.appear_count[0]=100;
        for(int i=0;i<DANMAKU_MAX;i++)
                boss.set_hp[i]=5000;
        bright_set.brt=255;
}

变量和构造题的声明,也准备一下。


---- struct.h に追加 ----

//boss弹幕相关的构造体
typedef struct{
//标志,种类,计数,从哪个敌机发射的编号,颜色
        int flag,knd,cnt,num;
//基础角度,基础速度
        double base_angle[1],base_spd[1];
        bullet_t bullet[BOSS_BULLET_MAX];
}boss_shot_t;

//物理计算要用到的构造体
typedef struct{
        int flag,cnt,set_t;
        double ax,v0x,ay,v0y,vx,vy,prex,prey;
}phy_t;

//boss的信息
typedef struct{
        int flag,cnt,knd,wtime,state,endtime,hagoromo,graph_flag;
        int hp,hp_max;
        int appear_count[2],set_hp[DANMAKU_MAX];
        double x,y,ang,spd;
        phy_t phy;
}boss_t;


---- define.h 里添加 ----

//boss的指定位置
#define BOSS_POS_X (FIELD_MAX_X/2)
#define BOSS_POS_Y 100.0
//boss拥有的子弹最大数量
#define BOSS_BULLET_MAX 3000
//弹幕的最大数量
#define DANMAKU_MAX 50


---- GV.h 里添加 ----

GLOBAL int img_dot_riria[8];//莉莉娅的点阵图像
GLOBAL boss_shot_t boss_shot;//boss弹幕信息
GLOBAL boss_t boss;          //boss信息

---- main.cpp 的main函数的以下部分进行改动 ----

            case 100://通常处理
                calc_ch();   //角色计算
                ch_move();   //角色的移动操作
                cshot_main();//自机射击主函数
                enemy_main();//敌机处理主函数
                shot_main(); //射击主函数
                boss_shot_main();
                out_main();  //中弹检测
                effect_main();//特效主函数
                graph_main();//绘制主函数
                if(boss.flag==0)
                        stage_count++;


---- load.cpp load()函数里添加 ----

LoadDivGraph( "../dat/img/char/riria.png" , 8 , 8 , 1 , 100 , 100 , img_dot_riria ) ;


---- enemy.cpp enemy_enter()函数里添加 ----

void enemy_enter(){//敌机的行动开关,控制的函数
    int i,j,t;
    if(boss.flag!=0)return;// 这是新加的
    for(t=0;t<ENEMY_ORDER_MAX;t++){


---- graph.cpp 的一下函数的改动和添加 ----

void graph_boss(){
        if(boss.flag==0)return;
        DrawRotaGraphF(boss.x+FIELD_X+dn.x,boss.y+FIELD_Y+dn.y,1.0f,0.0f,img_dot_riria[0],TRUE);
}

//子弹的绘制
void graph_bullet(){
        int i,j;
        SetDrawMode( DX_DRAWMODE_BILINEAR ) ;//线性插值绘图
        for(i=0;i<SHOT_MAX;i++){//循环敌人的弹幕数
                if(shot[i].flag>0){//如果弹幕信息要显示的话
                        for(j=0;j<SHOT_BULLET_MAX;j++){//循环那个弹幕的最大子弹数目
                                if(shot[i].bullet[j].flag!=0){//子弹信息要显示的话
                                        if(shot[i].bullet[j].eff==1)
                                                SetDrawBlendMode( DX_BLENDMODE_ADD, 255) ;

                                        DrawRotaGraphF(
                                                shot[i].bullet[j].x+FIELD_X+dn.x, shot[i].bullet[j].y+FIELD_Y+dn.y,
                                                1.0, shot[i].bullet[j].angle+PI/2,
                                                img_bullet[shot[i].bullet[j].knd][shot[i].bullet[j].col],TRUE);

                                        if(shot[i].bullet[j].eff==1)
                                                SetDrawBlendMode( DX_BLENDMODE_NOBLEND, 0) ;
                                }
                        }
                }
        }
        //boss
        if(boss_shot.flag>0){//弹幕信息要显示的话
                for(j=0;j<BOSS_BULLET_MAX;j++){//循环那个弹幕的最大子弹数目
                        if(boss_shot.bullet[j].flag!=0){//子弹信息要显示的话
                                if(boss_shot.bullet[j].eff==1)
                                        SetDrawBlendMode( DX_BLENDMODE_ADD, 255) ;

                                DrawRotaGraphF(
                                        boss_shot.bullet[j].x+FIELD_X+dn.x, boss_shot.bullet[j].y+FIELD_Y+dn.y,
                                        1.0, boss_shot.bullet[j].angle+PI/2,
                                        img_bullet[boss_shot.bullet[j].knd][boss_shot.bullet[j].col],TRUE);

                                if(boss_shot.bullet[j].eff==1)
                                        SetDrawBlendMode( DX_BLENDMODE_NOBLEND, 0) ;
                        }
                }
        }
        SetDrawMode(DX_DRAWMODE_NEAREST);//返回绘图格式
}

void graph_develop(){
        DrawFormatString(0,0,GetColor(255,255,255),"%d",stage_count);
}

void graph_main(){
        if(bright_set.brt!=255)SetDrawBright(bright_set.brt,bright_set.brt,bright_set.brt);

        graph_back_main();//背景绘制主函数
        graph_effect(0);//敌机死亡特效

        if(bright_set.brt!=255)SetDrawBright(255,255,255);

        graph_effect(4);//决死炸弹的特效

        if(bright_set.brt!=255)SetDrawBright(bright_set.brt,bright_set.brt,bright_set.brt);

        graph_boss();
        graph_enemy();//敌机的绘制
        graph_cshot();//自机子弹的绘制

        if(bright_set.brt!=255)SetDrawBright(255,255,255);

        graph_ch();//自机的绘制

        if(bright_set.brt!=255)SetDrawBright(bright_set.brt,bright_set.brt,bright_set.brt);

        graph_bullet();//子弹的绘制

        if(bright_set.brt!=255)SetDrawBright(255,255,255);

        graph_effect(1);//炸弹的特效
        graph_effect(2);//炸弹线条的特效
        graph_effect(3);//炸弹角色的特效
        graph_board();//版面的绘制

        graph_develop();
}


---- function.h 里添加 ----

//boss_shot.cpp
        GLOBAL void boss_shot_main();

弹幕函数是使用杂兵射击时,同样的函数指针。
这里,include文件夹里的fun.h文件里也要添加一下。


---- func.cpp 的改动 ----

extern void boss_shot_bulletH000();

void (*boss_shot_bullet[DANMAKU_MAX])() =
{
        boss_shot_bulletH000,
};

我们准备一个弹幕来试试看吧。


---- boss_shotH.cpp 的改动 ----

#include "../include/GV.h"

extern int serch_boss_shot();
extern double bossatan2();

void boss_shot_bulletH000(){
#define TM000 120
        int i,k,t=boss_shot.cnt%TM000;
        double angle;
        if(t<60 && t%10==0){
                angle=bossatan2();
                for(i=
                        0;i<30;i++){if((k=serch_boss_shot())!=-1){
                                boss_shot.bullet[k].col   = 0;
                                boss_shot.bullet[k].x     = boss.x;
                                boss_shot.bullet[k].y     = boss.y;
                                boss_shot.bullet[k].knd   = 8;
                                boss_shot.bullet[k].angle = angle+PI2/30*i;
                                boss_shot.bullet[k].flag  = 1;
                                boss_shot.bullet[k].cnt   = 0;
                                boss_shot.bullet[k].spd   = 3;
                                se_flag[0]=1;
                        }
                }
        }
        for(i=0;i<BOSS_BULLET_MAX;i++){
                if(boss_shot.bullet[i].flag>0){

                }
        }
}

虽然中弹检测都还没有,我们先让弹幕运行起来吧。

运行结果

http://dixq.net/rp/swf/24.swf

本人CSDN博客目录:
http://blog.csdn.net/tidus5

本人github博客:
http://tidus5.github.io

展开阅读全文

Git 实用技巧

11-24
这几年越来越多的开发团队使用了Git,掌握Git的使用已经越来越重要,已经是一个开发者必备的一项技能;但很多人在刚开始学习Git的时候会遇到很多疑问,比如之前使用过SVN的开发者想不通Git提交代码为什么需要先commit然后再去push,而不是一条命令一次性搞定; 更多的开发者对Git已经入门,不过在遇到一些代码冲突、需要恢复Git代码时候就不知所措,这个时候哪些对 Git掌握得比较好的少数人,就像团队中的神一样,在队友遇到 Git 相关的问题的时候用各种流利的操作来帮助队友于水火。 我去年刚加入新团队,发现一些同事对Git的常规操作没太大问题,但对Git的理解还是比较生疏,比如说分支和分支之间的关联关系、合并代码时候的冲突解决、提交代码前未拉取新代码导致冲突问题的处理等,我在协助处理这些问题的时候也记各种问题的解决办法,希望整理后通过教程帮助到更多对Git操作进阶的开发者。 本期教程学习方法分为“掌握基础——稳步进阶——熟悉协作”三个层次。从掌握基础的 Git的推送和拉取开始,以案例进行演示,分析每一个步骤的操作方式和原理,从理解Git 工具的操作到学会代码存储结构、演示不同场景下Git遇到问题的不同处理方案。循序渐进让同学们掌握Git工具在团队协作中的整体协作流程。 在教程中会通过大量案例进行分析,案例会模拟在工作中遇到的问题,从最基础的代码提交和拉取、代码冲突解决、代码仓库的数据维护、Git服务端搭建等。为了让同学们容易理解,对Git简单易懂,文中详细记了详细的操作步骤,提供大量演示截图和解析。在教程的最后部分,会从提升团队整体效率的角度对Git工具进行讲解,包括规范操作、Gitlab的搭建、钩子事件的应用等。 为了让同学们可以利用碎片化时间来灵活学习,在教程文中大程度降低了上下文的依赖,让大家可以在工作之余进行学习与实战,并同时掌握里面涉及的Git不常见操作的相关知识,理解Git工具在工作遇到的问题解决思路和方法,相信一定会对大家的前端技能进阶大有帮助。
©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值