关于弹幕射击游戏的基本实现(论文格式) ,文章为了紧凑删除了没用了一些缩进空格和回车。文章附带一些C#代码,使用unity引擎实现了游戏功能,只是提供思路,其他的引擎和编程语言应该也可以实现。弹幕各式各样,文章提供了简单,复杂,复合型等弹幕的基础思路。由于是我学习时期写的,所以难免有瑕疵或者错误,仅供参考。

 

  

 

《东方Project》是日本同人游戏社团上海爱丽丝幻乐团所制作的一系列同人游戏、相关作品。其中以STG游戏为主,东方的弹幕非常华丽,很有光赏性。我非常喜欢东方系列的游戏及其相关作品,所以针对STG游戏的核心弹幕实现方法来进行研究。我决定针对弹幕的实现方法使用Unity3D进行研究制作一个东方的同人STG作品《幻想乡》。STG的乐趣是独特的,只有正真喜欢STG的人们才能感受到,流行是会轮回的,沉寂一段时间不代表消亡,STG的独有魅力如果能和现在的游戏模式做到完美结合那必将会给那款游戏添彩。相信有一天人们玩累了现在的快餐游戏后必定回想起当年自己在街机上打STG的场景,STG总有一天会再次崛起的。比起需要投入大量精力来练级打装备的游戏来讲,STG更体现了游戏的本质,游戏本来就是人们用来放松和消磨时间的一种娱乐方式。希望更多人再次关注到STG。也希望我的研究能给STG增加新鲜血液。

 

关键词:射击游戏(STG);弹幕设计;数学函数;

 

ABSTRACT

 

 "Touhou Project" is a series of games and related works produced by Alice Magic Orchestra in Shanghai. Among them, the STG game as the main, the eastern Barrage is very gorgeous, very bright reward. I really like the Oriental series of games and related works, so the core barrage for STG game implementation method to study. I decided to use the Unity3D to study the barrage method, and create an oriental STG work fantasy village. STG fun is unique, only really love people can STG feel, is the reincarnation of the popular will, a period of silence does not mean the demise of STG, the unique charm of the game now and if we can do that will be the perfect combination mode to the game. I believe that one day, people tired of playing, now the fast food game must recall their own playing STG scene in the arcade, STG will someday rise again. Compared with the game that requires a lot of energy to equip the equipment, STG embodies the essence of the game, and the game is originally a recreational way for people to relax and kill time. I hope more people will pay attention to STG again. I hope my research will add fresh blood to STG.

Keywordsshooting game(STG);barrage design;math function;

 

  

 

摘要…………………………………………………………………………………………I

ABSTRACT …………………………………………………………………………………II

第一部分:作品介绍…………………………………………………………………………2

第二部分:作品阐释………………………………………………………………………… 3

一、自机的基本操作………………………………………………………………3

(一)自机的动画效果…………………………………………………………………3

(二)自机的移动及其范围控制………………………………………………………3

(三)自机的射击及其切换…………………………………………………………3

二、道中怪物基础弹幕实现………………………………………………………………4

(一)道中怪物实例化及其控制……………………………………………………4

(二)怪物的弹幕实现…………………………………………………………………4

1.假追踪弹幕…………………………………………………………………………4

2.圆形弹幕…………………………………………………………………………5

3.扇形弹幕…………………………………………………………………………5

三、BOSS的复杂符卡弹幕实现………………………………………………………………6

(一)螺旋弹幕……………………………………………………………………6

(二)变速旋转弹幕……………………………………………………………………7

1.匀加速旋转弹幕……………………………………………………………………8

2.波与粒子的境界………………………………………………………………9

(三)基础弹幕和旋转弹幕共同构造花型弹幕……………………………………10

结论………………………………………………………………………………………11

参考文献……………………………………………………………………………………12

后记………………………………………………………………………………………13

 

 

 

第一部分:作品介绍

 

我的作品是利用Unity3D游戏引擎制作的同人STG项目《幻想乡》。玩家控制一位自机进行飞行射击,自机有不同的不同的射击方式和移动方式,移动分为高速和低速两种,低速飞行中射击方式变成集中射击。击杀小怪可以掉落增加自机子弹伤害的P,当P达到一定数值,自机的子弹效果会改变,也会开启特殊的射击方式。boss有血条和时间条,击破血条或者时间条到0都会使boss切换符卡,当击破最后一张符卡的时候则为通过本关。因为STG游戏的核心在于弹幕的设计,华丽的弹幕才是我研究的主要任务,所以我设计自机是无敌状态,为了能够顺利通关观赏道中小怪的弹幕和boss的特殊符卡弹幕。

 

 

第二部分:作品阐释

 

一、自机的基本操作

因为STG需要一个玩家控制的人物也就是自机,所以自机的基本操作也算是STG中基本的设计,为了增加项目的可玩性所以我将自机设计的更加多功能化,其中包括射击方式和移动方式的切换和控制最为重要。

(一)自机的动画效果

因为项目是2D的所以自机只要选择图即可,建立一个空物体GameObject改名为character用来挂自机需要的控制脚本,然后在建立一个characterSprite类型的子物体我使用Sprite来实现自机的动画效果,如果自机在没有左右移动的情况下就循环切换提前准备好的图片stand来替换Sprite来实现动画效果。如果向左或者向右移动则是关闭stand开启循环切换移动的方法,就完成了standleftright时的动画效果。

 

()自机的移动及其范围控制

character上添加控制代码character_ctrl实现自机的控制。使用gameObject.transform.Translate(new Vector3(0, speed, 0) * Time.deltaTime, Space.World);方法按方向键上下左右实现自机的上下左右移动,并且分别添加bool条件cannot_up,cannot_down,cannot_left,cannot_right.使用空物体围成游戏区域,如果自机达到边界就使bool条件开启达到不能继续移动的效果。speed初始化为0.6f为正常移动速度,因为STG中有的弹幕十分密集这时候就需要低速移动才能精确的躲避开,按住空格键的时候自机将进入低速移动状态,这时候只要把speed的值改成0.3f即可,撒开空格键的时候则把speed初始化成0.6f变回正常移动速度。

(三)自机的射击及其切换

自机的移动实现之后就是自机的射击实现。将character上添加射击代码character_shoot实现自机的射击及其射击方式切换。自机射击方式分为自身基础射击和阴阳玉射击,阴阳玉是当P达到10及其以上时出现在自机身边进行协助射击的道具,而且当P更高的时候基础射击和阴阳玉射击回有所变化。按z键开始射击,按x键切换阴阳玉的射击状态,阴阳玉分为两种状态分别是梦想封印和封魔阵,梦想封印的子弹对boss是追踪弹而封魔阵是更为直线的集中射击。

z键进行子弹实例化并且添加标签bullet,并且在子弹的母体上添加脚本bullet_move,如果gameobject的标签是bullet则让gameobject移动,gameObject.transform.Translate(Vector2.up * Time.deltaTime*0.5f);基础射击实现很简单,关键在于其中切换射击状态包括低速移动之间的切换,bool变量的开关是这一块的应该注意的地方。当子弹被摧毁子弹就变为空的了,这时候有些脚本应用到子弹时就会报空,只要在语句上添加一条ifgameobject=null)即可。

二、道中怪物弹幕实现

我们知道,现在的效果仅仅是自机可以移动和射击,而且P没有达到一定数值也没有特殊的射击方式,所以这时候需要实例化道中的怪物并且赋予其弹幕。击杀道中怪物就会掉落P来进行自机火力的提升。

(一)道中怪物实例化及其控制

我们知道STG是有规律的,所以道中的怪物不能随便乱出现和乱发射弹幕,所以我们需要控制这些因素。我们可以在游戏开始的时候实例化第一波怪物然后发射一波弹幕,在第一波怪物全部死亡的时候再出现第二波怪物以此类推,那么如果我们不打死第一波怪物呢?那也没问题,只要我们设定怪物碰到游戏边框就会自己死亡即可,比如实例化5个怪物,每死亡一个deadNum++这样deadNum==5的时候就代表我们全部消灭的这一波的怪物了。

(二)怪物的弹幕实现

因为道中的怪物是相对弱小的怪物所以就采用基础弹幕然后再修改一下子弹速度,角度等参数来控制强弱即可。

1.假追踪弹幕

所谓假追踪弹幕就是如果自机不动的话怪物的子弹就会朝自机的方向射过来但是如果自机移动则不会改变子弹的方向。即发射时记录了自机的坐标再利用点到点方法实现子弹的移动。如果期间自机改变了自身坐标那么子弹并不会改变目标点,这是一种非常基础的怪物弹幕。

当怪物射击调教开启后进行实例化子弹并且赋予标签,然后实现点到点移动。难点在于以怪物为中心画坐标系,自机可以在第一二三四个象限的任何一个,因为子弹还是朝着一个轴移动,这时就需要改变子弹的角度,那么就需要直角坐标和极坐标之间的转换。转换公式θ=arctany/x)。

 if (new_bullet.transform.position.x >= character.transform.position.x){

if (new_bullet.transform.position.y >= character.transform.position.y)//第一象限(+,+)

 {new_bullet.transform.rotation = Quaternion.AngleAxis(Mathf.Atan(Mathf.Abs(new_bullet.transform.position.y - character.transform.position.y) / Mathf.Abs(new_bullet.transform.position.x - character.transform.position.x)) / Mathf.Deg2Rad+90, Vector3.forward);//θ=arctany/x}

else//第四象限(+,-){new_bullet.transform.rotation = Quaternion.AngleAxis(-Mathf.Atan(Mathf.Abs(new_bullet.transform.position.y - character.transform.position.y) / Mathf.Abs(new_bullet.transform.position.x - character.transform.position.x)) / Mathf.Deg2Rad+90, Vector3.forward);}}else{

if (new_bullet.transform.position.y >= character.transform.position.y)//第二象限(-,+)

{new_bullet.transform.rotation = Quaternion.AngleAxis(-Mathf.Atan(Mathf.Abs(new_bullet.transform.position.y - character.transform.position.y) / Mathf.Abs(new_bullet.transform.position.x - character.transform.position.x)) / Mathf.Deg2Rad+270, Vector3.forward);//弹幕射击character 的方向}else//第三象限(-,-){new_bullet.transform.rotation = Quaternion.AngleAxis(Mathf.Atan(Mathf.Abs(new_bullet.transform.position.y - character.transform.position.y) / Mathf.Abs(new_bullet.transform.position.x - character.transform.position.x)) / Mathf.Deg2Rad+270, Vector3.forward);}}

2.圆形弹幕

圆形弹幕是道中怪物常用的弹幕,即以怪物为中心一瞬间向周围固定方向发射一圈弹幕,弹幕虽然快但是因为并不是360全覆盖所以安定点有很多,不过如果一下出来一队的怪物全部发射圆形弹幕那就要另外寻找安定点的交集了。

怪物开始射击。利用for循环进行子弹的实例化,并且在实例化的同时直接改变子弹在实例化出来的角度,这样子弹实例化一圈时其实他们的轴向就是固定好的一圈,就会直接向各自的方向移动实现圆形弹幕射击。

for (int i = 0; i < 360 / 10;i++ ){

new_bullet = (GameObject)Instantiate(bullet,gameObject.transform.position, Quaternion.AngleAxis(10 * i, Vector3.forward));

new_bullet.tag = "monster2_bullet";}

其中10代表子弹与子弹间的差角,360/10表示一圈正好实例化多少的子弹,而10*i表示第几个子弹应该旋转多少度,比如第一个子弹移动轴向变成10度的方向,第二个变成20度方向,以此类推形成一圈。

3.扇形弹幕

扇形弹幕其实就是圆形弹幕的一部分,即怪物以自己为中心向固定扇形角度发射扇形弹幕,弹幕是从n度到m度之间的一个扇形区域,所以只要局限一下实例化子弹的度数就可以得到扇形弹幕。

if(i>90/10&&i<270/10){;}

扇形弹幕更多的是应用在特殊符卡对于安定点的调控,使得自机可以找到某一个角度是安定点这样才能体现出弹幕的规律性和可通过性,这需要设计者有着很丰富的想象力,不过我的项目中主要体现的是弹幕的实现方法,后续的复杂符卡会有更多的体现。

三、BOSS的复杂符卡弹幕

如果都是基础的弹幕并不能很好的体现STG中弹幕的华丽,复杂的弹幕需要更加复杂的数学模型和函数实现,这里我选择一些STG中肯定会出现的有代表性的复杂弹幕进行数学模型构建和算法的实现。

(一)螺旋弹幕

说道了螺旋弹幕那就必须先要介绍一些数学知识了,阿基米德螺旋线。

 

阿基米德螺旋线(等速螺旋):阿基米德螺线是一个点匀速离开或者靠近一个固定点的同时又以固定的角速度绕该固定点转动而产生的轨迹。

阿基米德螺旋线的极坐标方程:

其中ab均为实数。当θ=0时,a为起点到极坐标原点的距离。dr/dθ=bb为螺旋线旋转的角速度,改变参数a相当于旋转螺线,参数b则是控制着相邻曲线之间的距离。

从直角坐标到极坐标变换方程:

r=sqrt(x*x+y*y)

θ=arctany/x

从极坐标到直角坐标表换方程:

x=rcos(θ)

y=rsin(θ)

阿基米德螺旋线的几何画法:

以适当长度(OA)为半径,画一圆O;作一射线OA;作一点P于射线OA上;模拟点A沿圆O移动点P沿射线OA移动;画出点P的轨迹;隐藏圆O、射线OA&P;即可得到螺旋线。

阿基米德螺旋线的简单画法:

两只铅笔用细绳连接好,竖直按住一只铅笔,另一只拉到最紧竖直围绕另一只笔旋转。

了解了阿基米德螺旋线之后就要开始构建一个数学模型,我们能看出其实ab两个参数才是最重要的,所以我们构建数学模型需要两步:

1.P围绕一固定点O匀速旋转。

2.P向着点O做匀速直线运动。

两步必须同时进行即可得到螺旋线。

利用构建好的数学模型就可以选择合适的方法来进行算法实现了。点P我们可以看成是boss子弹发射口即实例化子弹的点,点Oboss的坐标点。

PO点旋转可以选择

C# => void RotateAround(Vector3 point, Vector3 axis, float angle);

P向点O移动可以选择

C# => public static Vector2 Lerp(Vector2 a, Vector2 b, float t);

同时在P点实例化子弹并且给予子弹速度即可得到螺旋弹幕。

bullet_position.RotateAround(gameObject.transform.position, Vector3.forward, 5);bullet_position_out.RotateAround(gameObject.transform.position,Vector3.forward, 5);bullet_position.position = Vector2.Lerp(bullet_position.position, bullet_position_out.position,Time.deltaTime/3.0f);

其中再次利用到了圆形弹幕中子弹在不同象限旋转角度的问题。具体实现方法和圆形弹幕类似。

实现效果如下:

 

(二)变速旋转弹幕

我们能看出之前的弹幕全部都是有固定安定点的,因为实例化子弹结束后子弹就会朝着固定方向移动,这里说的变速不是子弹的移动速度在改变而是弹幕整体在旋转,这个旋转有一个旋转角速度,变速指的是弹幕的旋转角速度speed的变化。如果speed不变那就是匀速旋转,如果speed慢慢变大那就是加速旋转运动,减速同理。可以看出这个speed的改变可以影响到整个旋转弹幕。我们以两种匀加速旋转弹幕来对比一下。

1.匀加速旋转弹幕

不是boss本身0点作为发射口,而是另外一个发射口P点,P点直接实例化子弹并且直接改变所在象限的子弹移动轴向的角度。这样更能体现出效果。

if(Time.time-create_time>0.05f){create_time = Time.time;

new_bullet = (GameObject)Instantiate(bullet, bullet_pos.position, Quaternion.identity);new_bullet.tag = "wp_bullet";

change_bullet_look_at();//改变子弹沿着固定轴向移动所旋转的角度的方法

}

不过这样仅仅是子弹在向一个方向一直发射一条直线弹幕而已,就和自机的射击没什么区别了,所以我们需要另一部操作也就是旋转,这里的旋转指的是发射口绕着boss点旋转即绕轴心旋转。这一步实例化其实就是第一步,不必构建数学模型而是直接给出了实现方法。因为绕轴心旋转是旋转弹幕的核心,先构建数学模型:

第一步我们把boss点作为轴心O,发射口点作为旋转点P,让点P以速度speed匀速绕点O顺时针或者逆时针旋转;第二步给speed一个匀加速,实现旋转角速度均匀增加。

通过数学模型我们可以选择合适的方法来进行代码实现:

bullet_pos.transform.RotateAround(character.transform.position,Vector3.forward, speed);//绕轴心旋转方法

我们可以写一个计时器进行speed的自加操作。

 if (Time.time - test_time > 0.1f){test_time = Time.time;speed += 0.5f;}

这样就实现了加速旋转弹幕,不过这只是一个发射口,我们可以以相同方法做四个发射口,那么就会得到以下效果:

 

1. 波与粒子的境界

影响变速旋转弹幕效果的最重要参数有speed,还有就是实例化子弹的间隔,如果我们将弹幕1计时器去除就会得到新的变速旋转弹幕效果如下:

 

通过简单观察(观察动图)可以了解到,这个弹幕的特点是 只有一种弹型,并且所有子弹一经发射保持弹速和方向不变。那么变化的只有子弹的初始发射方向。继续观察下去,弹幕的变化很微妙,发射方向的转速不断变化,似乎是一个不断加快又减慢,而后又反向的一个过程,子弹时聚时散,链条数也随之不断改变。这样一个变化复杂的弹幕看上去无从下手,实际上了解了公式之后非常简单,这个弹幕的刚一开始是一个转速匀速加快的过程,只要实现出这个转速匀速加快的过程,后面的弹幕就会自动转出这些复杂的图形,所以这个弹幕的变化是很微妙的。环形弹幕和旋转弹幕是会经常用到的两种弹幕,波与粒子的境界几乎成了必了解弹幕之一。

(三)基础弹幕和旋转弹幕共同构造花型弹幕

我们通过基础弹幕的实现知道了子弹的移动在四个不同象限的移动轴向不同,又从螺旋弹幕和旋转角度中知道了角速度的变化会很大程度影响弹幕的变化,甚至实例化子弹的频率也会影响弹幕的效果,那么我们把这些因素结合在一起再经过参数调整会是什么样的效果呢?答案是弹幕更加漂亮。当掌握了基础弹幕,螺旋弹幕和变速旋转弹幕的时候,就能够设计出更多漂亮的弹幕。

实例化一条直线弹幕,2S后子弹自转同时绕轴心旋转。

 if(BOSS_come.skill3==true&&gameObject!=null){

if (count < 200)if (Time.time - test_time >0.1f)

{new_bullet = (GameObject)Instantiate(bullet, gameObject.transform.position, Quaternion.identity);test_time = Time.time;count++;

if (gameObject.name == "up_bullet"){new_bullet.tag = "skill3_bullet_up";}

if (gameObject.name == "down_bullet"){new_bullet.tag = "skill3_bullet_down";}

if (gameObject.name == "left_bullet"){new_bullet.tag = "skill3_bullet_left";}

if (gameObject.name == "right_bullet"){new_bullet.tag = "skill3_bullet_right";

}}}

if(BOSS_come.skill3==true){

if (gameObject.tag == "skill3_bullet_down" || gameObject.tag == "skill3_bullet_right" || gameObject.tag == "skill3_bullet_left" || gameObject.tag == "skill3_bullet_up"){ if (Time.time - test_time > 2.0f){transform.Rotate(Vector3.back * Time.deltaTime * 45.0f);//最外圈半径transform.RotateAround(new Vector2(circle_O.position.x, circle_O.position.y), Vector3.back,30* Time.deltaTime); //转圈速度}}}

if (gameObject.tag == "spiral_bullet" && enemy_bullet.skill01==false){

gameObject.transform.Translate(Vector2.up * Time.deltaTime * 0.5f);}

if (gameObject.tag == "wp_bullet"){gameObject.transform.Translate(Vector2.up * Time.deltaTime * 0.25f);}

if(BOSS_come.skill3==true)//技能三 花型弹幕{

if (gameObject.tag == "skill3_bullet_down")

{gameObject.transform.Translate(Vector2.right * Time.deltaTime * 0.5f);}

if (gameObject.tag == "skill3_bullet_up"){

gameObject.transform.Translate(Vector2.left * Time.deltaTime * 0.5f);}

if (gameObject.tag == "skill3_bullet_left"){

gameObject.transform.Translate(Vector2.down * Time.deltaTime * 0.5f);}

if (gameObject.tag == "skill3_bullet_right"){

gameObject.transform.Translate(Vector2.up * Time.deltaTime * 0.5f);}}

这样就会得到花型弹幕,如果再次改变参数还会得到新的弹幕。

 

 

结论 

通过以上的内容对STG大体有了了解,不过要想真的做出华丽的弹幕则需要设计者更加丰富的想象力,利用基础弹幕,螺旋弹幕,变速旋转弹幕,及其花型弹幕等进行更多的设计才能得到更加漂亮的弹幕,那时就能更深的感受到弹幕的魅力。

 

参考文献

 

[1]太田顺也.The Grimoire of Marisa グリモワール オブ マリサ(1996-2009)[Z].日本:一迅社,2009.

[2]孙家广.计算机图形学[M].北京:清华大学出版社,1998.

[3]李春葆.C#程序设计教程(第2版)[M].北京:清华大学出版社,2013.

[4]王红梅,胡明,王涛编著.数据结构(C++)[M].北京:清华大学出版社,2011.6.

[5]王红梅编著, 胡明.算法设计与分析(2)[M].北京.清华大学出版社,2013.3.

[6]谭浩强编著.C++程序设计(2)[M].北京:清华大学出版社,2011.8.

[7]谭浩强编著.C程序设计(第四版)[M].北京:清华大学出版社,2010.

[8]谭浩强编著.C++面向对象程序设计[M].北京:清华大学出版社,2006.

 

 

  

 

本着对《东方project》的喜爱,但是因为我不是很擅长玩STG所以一直没有更深入的研究过STG,借此机会通过《幻想乡》这个项目使我更深入的了解了STG中弹幕的魅力也使我对STG的感情更深,我以后也会投入更多热情到STG中,毕竟STG的时代过去了很久,不过我相信真正的好游戏早晚是要再次被人们想起的,我就是其中一员而已,希望我以后的研究能让更多人再次关注到STG,希望更多人能够被弹幕的魅力所吸引。