1.什么是BulletML? BulletML是一个日本人的作品,是一个弹幕脚本语言,用于描述弹幕的特征,同时BulletML的作者还制作了一个针对C++的弹幕脚本的接口,以便于制作游戏之用。
详细请访问:
http://www.asahi-net.or.jp/~cs8k-cyu/bulletml/index_e.html 2.弹幕脚本简介: 其实这个作者本人以及介绍的比较详细了,语法说明文档链接:
http://www.asahi-net.or.jp/~cs8k-cyu/bulletml/bulletml_ref_e.html 不过还是简单介绍一下吧,并进行了一些补充的说明。
BulletML的脚本语言是基于XML的,只不过XML的键值描述的都是弹幕的信息而已,遵循作者描述的弹幕的基本语法规则就能制作出各种各样的弹幕了。
基本语法:
注意:游戏中的距离单位可以看做像素,时间是帧,方向是以角度的方式描述的,作者指定12点为0度角,顺时针。
于是速度单位就是(像素/帧),加速度(像素/2次方帧),时间(帧),距离(像素)
1.<bulletml> 定义脚本的本体
Attribute - type = (none | vertical | horizontal) 定义是纵版还是横板,注:针对不同的情况计算出的结果是不同的。
Example:<bulletml type="vertical" xmlns="AnythingIsOK"> 定义了一个针对纵版射击游戏的弹幕脚本。
2.<bullet> 定义一个子弹
Attribute - label = String 定义子弹的名字,当然可以不定义,定义的好处就在于方便弹幕脚本的其他部分的引用。
Content - direction?, speed?, (action | actionRef)* 定义子弹的方向,速度,动作,以及动作引用。
Example:
<bullet label="downAccel">
<direction>270</direction>
<speed>2</speed>
<action>
<accel>
<vertical>3</vertical>
<term>120</term>
</accel>
</action>
</bullet>
定义了一个叫做downAccel的子弹,初始角度270,速度2,并且该子弹有一个动作,这个动作会使子弹用120帧的时间在竖直的方向速度增加到3。
3.<action> - 定义子弹的动作
Attribute - label = STRING 定义动作的名称,方便脚本内引用。
Contents - (repeat | fire | fireRef | changeSpeed | changeDirection | accel | wait | vanish | action | actionRef)*
定义动作的类型,包括:重复,开火,开火引用,变换速度,变换方向,加速,等待,销毁,其他的动作,动作 的引用。
Example:
<action>
<changeSpeed>
<speed>0</speed>
<term>60</term>
</changeSpeed>
<wait>60</wait>
<fire><bullet/></fire>
<fire>
<direction type="absolute">330+$rand*25</direction>
<bulletRef label="downAccel"/>
</fire>
<vanish/>
</action>
定义一个动作,首先将执行该动作的子弹用60帧的时间将速度降为零,然后等待60帧,发射一个默认方向的子 弹,然后在330+$rand*25的绝对角度发射一个叫做downAccel的子弹,最后销毁执行该动作的子弹。
4.<fire> - 开火
Attribute - label = STRING 开火的名称,方便内部引用。
Contents - direction?, speed?, (bullet | bulletRef)
射击的方向,速度,子弹或者子弹的引用。
Example:
<fire>
<direction type="absolute">270</direction>
<speed>2</speed>
<bulletRef label="rocket"/>
</fire>
发射一个叫做rocket的子弹,该子弹初始角度270,速度2。
注:fire内必须有一个bullet或者bulletRef,没有子弹你射啥啊?
5.<changeDirection> - 变换子弹的方向。
Contents - direction, term 方向,时间限制。
在term(帧)的时间之内将子弹的方向变化至direction。
6.<changeSpeed> - 变换子弹的速度
Contents - speed, term 速度,时限
在term(帧)的时间之内将子弹的速度变化至speed。
7.<accel> - 加速子弹
Contents - horizontal?, vertical?, term 水平方向,竖直方向,时限
在term(帧)的时间之内在水平或者竖直的方向上加速子弹。
8.<wait> - 等待一段时间,也就是弹幕暂停一段时间。
Contents - NUMBER
等待NUMBER帧。
9.<vanish> - Vanishes a bullet
销毁子弹。
10.<repeat> - 重复一个动作
Contents - times, (action | actionRef) 次数,动作或者动作的引用。
Example
<repeat>
<times>100</times>
<action>
<fire>
<direction type="absolute">220+$rand*100</direction>
<bulletRef label="backBurst"/>
</fire>
<wait>6</wait>
</action>
</repeat>
重复一个开火的动作100遍。
注意:在这之前都是比较高级的XML节点的描述,下面是辅助高级节点作用的一些特征的注释。
11.<direction> - 定义一个方向
Attribute - type = (aim | absolute | relative | sequence)
方向的类型:指向型,绝对方向,后面两个都是关联型,但具体的关联方法不同。
Contents - NUMBER
Specifies the direction in degrees.
"aim" type means that NUMBER is relative to the direction to my ship (The direction to my ship is 0, clockwise).
指向一个目标(注:这里作者说的是指向自己的飞机,但是不一定是这样,关键看你的接口是如何实现的,咱们下回再说)
"absolute" type means that NUMBER is the absolute value (12 o'clock is 0, clockwise).
绝对的方向,12点是0度,顺时针。
"relative" type means that NUMBER is relative to the direction of this bullet (0 means that the direction of this fire and the direction of the bullet are the same).
关联到上一个子弹的角度
"sequence" type means that NUMBER is relative to the direction of the previous fire (0 means that the direction of this fire and the direction of the previous fire are the same).
关联上一次射击的角度
12.<speed> - 定义一个速度的信息
Attribute - type = (absolute | relative | sequence)
绝对的速度,相对的速度。
Contents - NUMBER
太长了,自己看吧 = =!
In case of the type is "relative", if this element is included in changeSpeed element, the speed is relative to the current speed of this bullet. If not, the speed is relative to the speed of this bullet.
In case of the type is "sequence", if this element is included in changeSpeed element, the speed is changing successively. If not, the speed is relative to the speed of the previous fire.
13.<horizontal> - 水平方向指定加速
Attribute - type = (absolute | relative | sequence)
Contents - NUMBER
If the type is "relative", the acceleration is relative to the acceleration of this bullet. If the type is "sequence", the acceleration is changing successively.
<vertical> - 竖直方向指定加速
Attribute - type = (absolute | relative | sequence)
Contents - NUMBER
14.<term> - 时限
Contents - NUMBER
15.<times> - 次数
Contents - NUMBER
<bulletRef> - 子弹引用
Attribute - label = STRING 引用的名称
Contents - param* 参数
使用Attribute - label来引用之前定义过的子弹引用。
如果引用的节点内部定义了参数,那么你可以使用<param>传递参数
例如:
之前定义了一个子弹
<bullet label="wave">
<speed>1</speed>
<action>
<fire>
<direction type="sequence">$1</direction>
<bullet/>
</fire>
<vanish/>
</action>
</bullet>
然后再某处引用该子弹
<bulletRef label="wave">
<param>-3</param>
</bulletRef>
这个时候之前定义的$1这个参数就会由于<param>-3</param>节点的作用被替换为-3。
Variables($1, $2, $3, ...) in the referred element are replaced with parameters in <param>. (First parameter replaces $1, second parameter replaces $2, ...)
16.<actionRef> - 动作引用。
Attribute - label = STRING 引用的名称
Contents - param* 参数
基本使用方法等同于子弹的引用。
17.<fireRef> - 开火引用
Attribute - label = STRING 引用名称
Contents - param* 参数
同上
18.<param> - 指定参数
Contents - NUMBER
3.弹幕脚本的补充说明:
(1)引用的说明:
假设你定义了一个十分复杂的弹幕的动作或者定义了一个有着十分复杂动作的子弹,
如果你想在一个脚本之中多次使用该动作或者子弹的时候,你可以通过子弹或者动作的名字直接引用它。
首先给定义一个子弹,例:
<bullet label="downAccel">
<direction>270</direction>
<speed>2</speed>
<action>
<accel>
<vertical>3</vertical>
<term>120</term>
</accel>
</action>
</bullet>
然后在其他的地方引用该子弹,例如:
<fire>
<direction type="absolute">330+$rand*25</direction>
<bulletRef label="downAccel"/>
</fire>
实际同
<fire>
<direction type="absolute">330+$rand*25</direction>
<bullet>
<direction>270</direction>
<speed>2</speed>
<action>
<accel>
<vertical>3</vertical>
<term>120</term>
</accel>
</action>
</bullet>
</fire>
的效果是一致的。
熟悉编程的同学应当很容易就明白啦~貌似不用我废那么多话~
(2)脚本的入口:
一个弹幕脚本的入口是一个action,action的名字叫做"top",记住这个是定死了的,C++对应的库中的代码是死的,我也没办法。
就是<action label="top">
注意在定义自己的action的时候不要将名字命名为top,有什么后果我不知道,有兴趣的同学可以自己测试下 。
(3)完整的例子:
BulletML的网页上有很多的例子,而且有演示。
详情:
http://www.asahi-net.or.jp/~cs8k-cyu/bulletml/bulletml_applet_e.html 刚打开时有些卡,请耐心等 待。
都是些不错的例子,可以用作基本的框架。
(4)rank参数:
脚本支持由应用程序的直接传参,但是只能传一个,一般用作弹幕的等级。
有的时候我们要制作不同难度下的弹幕,rank参数就可以解决这个问题。
脚本内定义为:
<repeat><times>8+$rank*10</times>
<action>
<fire>
<direction type="sequence">$1</direction>
<bulletRef label="nrm"/>
</fire>
<wait>3</wait>
</action>
</repeat>
由程序传入的rank越高,射击的次数越多。
4.C++程序接口: 可以参考BulletML库下载后作者的内部说明文档 README
看这篇文章前请确定你有了以下基础知识:熟悉C++,有一定面向对象编程的基础,知道如何在工程项目之中引入 三方库。
注意:BulletML不负责图形的效果,图形的接口需要自定义。
如果想要实现弹幕图形效果,还需掌握其他任意一项技术:DirectX,OpenGL,任何一款支持C++编程的游戏引擎或者图形引擎,Windows GDI编程。
Step1:下载BulletML库,From:
http://www.asahi-net.or.jp/~cs8k-cyu/bulletml/index_e.html Step2:以下选其一。
1.编译BulletML库,新建工程项目并将头文件以及库文件引入到你自己的项目之中。(VC 6.0, VS2008被证明编译 没有问题)
2.直接将BulletML的源码放到你的工程之中。
Step3:添加头文件。
#include "bulletml/bulletmlparser.h"
#include "bulletml/bulletmlparser-tinyxml.h"
#include "bulletml/bulletmlrunner.h"
Step4:写代码,这里详细说明一下。
首先,介绍一个十分重要的类,BulletMLRunner,可以将这个类理解为一个发射器(某人说的)。
BulletML分为两种子弹:
一种子弹本身包含Action,也就是发射之后需要控制的子弹(变向,加速,再发射),我们称其为Bullet。
另一种子弹不包含Action,发射之后速度以及方向都不会改变,我们称其为SimpleBullet。
BulletMLRunner本质上是控制一个(注意!一个!)包含有Action的子弹的类并负责发射部分没有Action的子弹的类。
使用方法如下:
自定义类继承自BulletMLRunner,作者的例子里写作BulletCommand,我们跟他一样。
class BulletCommand : public BulletMLRunner {
Bullet* bullet_;//当前被控制的子弹,
//注意:子弹类是自定义的,根据需求设置类的基本参数。
//例如:速度,加速度,方向,位置信息等等。
//同样子弹也可以包含图形接口的相关操作。
};
注意构造函数,至少要有这两个:
BulletCommand::BulletCommand(BulletMLParser* bp, Bullet* b = NULL)
: BulletMLRunner(bp), bullet_(b)
BulletCommand::BulletCommand(BulletMLState* bs, Bullet* b)
: BulletMLRunner(bs), bullet_(b)
然后在自定义的类之中实现父类的虚函数,下面对于每个待实现的函数进行一下解释:
1.void createSimpleBullet(double direction, double speed)函数:
这个函数负责创建一个简单的子弹(不包含Action,发射之后速度方向不会改变)。
可以这样写实现(伪码):
void BulletCommand::createSimpleBullet(double direction, double speed)
{
Bullet *pBullet = new Bullet(direction, speed);
//根据当前的发射器的位置设置子弹的位置。
pBullet->SetPosition(WhereTheEmitterIs);
//将子弹添加到显示的场景之中。
bulletList->AddBullet(pBullet);
}
2.void createBullet(BulletMLState* state, double direction, double speed)函数:
该函数负责创建一个拥有Action的子弹(发射之后方向以及速度会改变)
参考实现:
void BulletCommand::createBullet(BulletMLState* state, double direction, double speed)
{
Bullet *pBullet = new Bullet(direction, speed);
//根据发射器的位置设置子弹的位置。
pBullet->SetPosition(WhereTheEmitterIs);
//生成一个新的命令,实际上一个拥有Action的子弹也可以看成一个发射器。子弹发射子弹,然后再发射。
BulletCommand *pCmd = new BulletCommand(state, pBullet);
//将子弹添加到场景之中。
bulletList->AddBullet(pBullet);
//将BulletCommand添加到主循环的调用之中。
MainLoopConmandList->AddConmand(pCmd);
}
注:初始发射器的基础位置是由程序员指定的,当二次发射的时候发射器的位置应当就是bullet_(类内成员)的位置。
其实还有一种方法,就是一开始就将一个子弹关联到一个发射器,这样WhereTheEmitterIs的值应当一直就是
bullet_的位置。
3.double getBulletDirection()函数:
负责返回子弹的当前方向,这个是脚本在执行的时候需要获取的信息,如果获取的信息不对的话实际上脚本的
运行就会出问题。
double BulletCommand::getBulletDirection()
{
//返回当前拥有Action的子弹的方向。
return bullet_->GetDirection();
}
4.double getAimDirection()
这个函数就是用于辅助计算指向型弹幕的信息的,如果错了的话会出现奇怪的结果,具体指向什么目标自己定义,一般是自机。
double BulletCommand::getAimDirection()
{
//返回当前的子弹同目标的夹角。
return theAimValue;
}
5.double getBulletSpeed()
double BulletCommand::getBulletSpeed()
{
//返回速度,没啥说的。
return bullet_->GetSpeed();
}
6.double getDefaultSpeed()
double BulletCommand::getDefaultSpeed()
{
//获得默认的速度
return DefaultSpeed;
}
7.double getRank()
这个就是程序内部返回Rank参数的办法了,对应于弹幕脚本之中的Rank参数。
根据Rank的返回值的不同,弹幕的结果也会不同,一般用于弹幕的难度分级。
double BulletCommand::getRank()
{
//返回当前弹幕的等级
return betterBeAGlobleValue;
}
8.int getTurn()
这个函数我苦恼了很长的时间,结果问了一位之前做过弹幕游戏的同学才知道这个是什么。
在此感谢那位同学O(∩_∩)O。
返回当前的时间(帧数),我是使用主循环中的计数器作为返回时间的,应当只要提供信息让脚本运行时能获得正确的间隔就可以。
int BulletCommand::getTurn()
{
return GetGlobleTime();
}
上面是很多获得信息的函数,下面就是动作函数了,相当于You Should doSomething when the func was Called。
9.void doVanish()子弹销毁
void BulletCommand::doVanish()
{
bullet_->die();//这个其实挺形象的
}
10.void doChangeDirection(double dParam)变换方向
void BulletCommand::doChangeDirection(double dParam)
{
bullet_->ChangeDirection(dParam);
}
11.void doChangeSpeed(double dParam)变换速度
void BulletCommand::doChangeSpeed(double dParam)
{
bullet_->ChangeSpeed(dParam);
}
12.void doAccelX(double dParam) X方向加速
void BulletCommand::doAccelX(double dParam)
{
bullet_->ChangeAccelX(dParam);
}
13.void doAccelY(double dParam) Y方向加速
void BulletCommand::doAccelY(double dParam)
{
bullet_->ChangeAccelY(dParam);
}
14.double getBulletSpeedX() 获得子弹X方向的速度
double BulletCommand::getBulletSpeedX()
{
return bullet_->GetSpeedX();
}
15.double getBulletSpeedY() 获得Y方向的速度
double BulletCommand::getBulletSpeedY()
{
return bullet_->GetSpeedY();
}
定义完了类之后就是初始化的操作了。
BulletMLParser是解释器类,它负责脚本的加载与解释。
例:
BulletMLParser* bp = new BulletMLParserTinyXML("foo.xml");
bp->build();//这句话是真正的解释。
后面作者提示了一句,注意:
Because parsing BulletML is slow, all xml files should be loaded in
the initialization of the program.
之后将解释后的脚本关联到命令。
例:
BulletCommand* bc = new BulletCommand(bp)
创建完成命令之后放到主循环之中调用即可,当命令不断run的时候之前描述的n多函数
如:getBulletSpeedY, do...等会在适当的时机被调用。
场景之中有可能有很多的命令一起跑,所以最好维护一个列表一样的数据结构(按照需求定义),新的命令生成之后添加到列表即可。
void MainLoop()
{
bc->run();
}
5.一个完整的工程: 为了帮助大家更好理解,制作了一个开源的项目,如下~
工程以及源码(VC 6.0 VS2005 VS2008编译通过):
115:f1cd1a00bc 若想删除直接删除文件夹即可~
单纯的弹幕演示程序:
115:f13f47cbc5