撰写了文章 发布于 2017-11-28 22:52:54
[Unity2D]童年回忆小蜜蜂开发实例

相信FC上的小蜜蜂大家都不陌生吧,伴随着阵型成群出没的敌人,屹立在屏幕底部的玩家,只能通过狭窄的水平移动迎击无穷无尽的敌人。今天这篇开发实例所记载的就是如何通过Unity来实现如此玩法。
我们会主要通过:创建玩家及敌人角色、控制敌人出场、创建发射物、敌人的回击、场景及一些美术元素的设计这几点来构建整个游戏。
你可以在GameBucket.io上在线体验到这款游戏 PlayDemoOnline
创建飞船并控制移动
创建飞船角色,添加RigidBody2D及PolygonCollider2D,BodyType为Kinematic,碰撞器为isTrigger,对应代码如下:
Void Start(){
float distance=transform.position.z-Camera.main.transform.position.z;
Vector3 leftMost=Camera.main.ViewPortToWorldPoint(new Vector3(0f,0f,distance));
Vector3 rightMost=Camera.main.ViewPortToWorldPoint(new Vector3(1f,0f,distance));
xMin=leftMost.x+0.5f;
xMax=rightMost.x-0.5f;
}
void Move(){
if(Input.GetKey(KeyCode.LeftArrow)){
transform.position+=Vector3.left*moveSpeed*Time.deltaTime;
}else if(Input.GetKey(KeyCode.RightArrow)){
transform.position+=Vector3.right*moveSpeed*Time.deltaTime;
}
float newX=Mathf.Clamp(transform.position.x,xMin,xMax);
transform.position=new Vector3(newX,transform.position.y,transform.position.z);
}创建敌人角色,同样添加对应的碰撞器及刚体,注意碰撞器勾选isTrigger。
通过将敌人角色部署在空物件上,并通过代码控制空物件的形式来控制整个飞船舰队的移动,并使用OnDrawGizmos()来实现场景中的阵型可视化。其中Position及EnemyFomation中的代码分别如下
public void OnDrawGizmos(){//该两条代码分别位于parent及child下方代码中
Gizmos.DrawWireCube(transform.position,new Vector2(gizmosWidth,gizmosHeight));
Gizmos.DrawWireSphere(transform.position,0.5f);
}

如图所示,即为最终的展示效果。继续在控制阵型脚本中添加相关移动如下:
void Update(){
if(!movingRight){//初始值为false
transform.position+=Vector3.right*formationSpeed*Time.deltaTime;
}else{
transform.position+=Vector3.left*formationSpeed*Time.deltaTime;
}
float rightEdgeOfFormation=transform.position.x+0.5f*gizmosWidth;//此处表示矩形框的右侧位置
float leftEdgeOfFormation=transform.position.x-0.5f*gizmosWidth;
if(rightEdgeOfFormation>xMax){//xMax及xMin分别为世界坐标系的(1,0)及(0,0)
movingRight=true;
}else if(leftEdgeOfFormation){
movingRight=false;
}
}){
创建Projectile发射物
分别为敌方和我方创建对应的Projectile prefabs
玩家发射
在Player脚本中继续更新,实现按空格键放射效果,并与Projectile脚本中互相调用,从而计算激光伤害
void Update(){
if(Input.GetKeyDown(KeyCode.Space)){
InvokeRepeating("Fire",0.000001f,fireRating);
}else if(Input.GetKeyUp(KeyCode.Space)){
CancelInvoke("Fire");
}
}
void Fire(){
GameObject beam=Instantiate(projectile,transform.position,Quaternion.identity) as GameObject;
beam.GetComponent<RigidBody2D>().velocity=new Vector3(0f,projectileSpeed,0f);
}
void OnTriggerEnter2D(Collider2D col){
PlayerLaser missile=col.gameObject.GetComponent<PlayerLaser>();//此处获取的是地方的发射激光脚本中的missile物件,可参考敌方发射部分
if(missile){
health-=missile.GetDamaged();
missile.Hit();
}if(health<=0){
Destroy(gameObject);
LevelManager levelManager=GameObject.Find("LevelManager").GetComponent<LevelManger>();
levelManger.LoadLevel("WinMenu");
}
}对应为PlayerProjectile中添加PlayerLaser脚本如下
public float damage;
public float GetDamaged(){return damage;}
public void Hit(){
Destroy(gameObject);//激光撞击到物体后消失
}
敌方发射
敌方的发射脚本基本与玩家功能一致,但是需要添加一段控制发射频率的代码,其原理为
p(fire frame)=time elapsed*frequency
void Update(){
float probability=shotsPerSeconds*Time.deltaTime;//e.g. 0.5f表示的是1秒钟平均射击2下
if(Random.value<probability){
Fire();
}
}
void Fire(){
GameObject missile=Instantiate(enemyProjectile,transform.position,Quaternion.identity) as GameObject;
missile.GetComponent<RigidBody2D>().velocity=Vector2.down*missileSpeed*Time.deltaTime;
}
void OnTriggerEnter2D(Collider2D col){
PlayerLaser missile=col.gameObject.GetComponent<PlayerLaser>();
if(missile){
health-=missile.GetDamaged();
missile.Hit();
}if(health<=0){
Die();
}
}
void Die(){
Destroy(gameObject);
}监测阵型以及使用递归方法重生
当我们操作地方飞机摧毁阵型中的敌机时,我们希望当整只敌军被摧毁后,会重新生成新的飞机并继续供玩家摧毁。为此,我们需要在阵型中添加脚本,负责检测敌方的生成和消失
Transfrom NextFreePosition(){//实现依次生成对应敌方位置
foreach(Transform childPosGameObject in transform){
if(childPosGameObject.childCount=0){
return childPosGameObject;
}
return null;
}
}
void SpawnUntilFull(){//Recursion
Transform freePos=NextFreePosition();
if(freePos){
GameObject enemy=Instantiate(enemyPlane,freePos.Position,Quaternion.identity) as GameObject;
enemy.transform.parent=freePos;
}
if(NextFreePosition()){
Invoke("SpawnUntilFull",spawnDelay);
}
}
bool AllMembersDead(){
foreach(Transform childPosGameObject in transform){
if(childPosGameObject.childCount>0){
return false;
}
}
return true;
}
void Update(){
if(AllMembersDead()){
SpawnUntilFull();
}
}另外,可通过Animation为每一个敌人添加对应的进场动画。这段内容比较简单,有兴趣但不知道如何实现的可以私信问我或通过Youtube解决。
Score系统
添加UI→Text,并挑选合适的位置展示分数。最终呈现的形式是在GameScene中展示分数,玩家死亡后进入WinMenu,并在WinMenu的ScoreUI中展示最终得分。需要涉及到在不同场景内的功能传递,实现起来也很容易有趣
首先在GameScene中的ScoreKeeper
public static int score=0;//使用静态函数实现调用
void Start(){
Text myText=GetComponent<Text>();
Reset();
}
public void Score(int point){
score+=point;
}
public static void Reset(){
score=0;
}
接下来是在WinMenu中的最终显示得分UI,ScoreDisplay
void Start{
Text myText =GetComponent<Text>();
myText.text=ScoreKeeper.score.ToString();
ScoreKeeper.Reset();
}为了使得敌人的死亡能够传递到ScoreKeeper中,需要在Enemy脚本中添加如下代码
void Start(){
scoreKeeper=GameObject.Find("Score").GetComponent<ScoreKeeper>();
}
void Die(){
scoreKeeper.Score(scoreValue);//每个敌军提供的击毁分数
}其他的一些诸如ParticleSystem,设计音效,图层碰撞的调控,动画系统,一些子弹的Shredder防止内存占用过多等小细节,就留待大家自己扩充。对应的Assets已上传网盘,欢迎大家提出指正~
链接: https://pan.baidu.com/s/1c1GHvyw 密码: sbsu

那么,加班继续干活咯~