本文中Flappy Bird基于Unity2019.4.7f1完成,源工程已部分代码改为适配安卓

在这里插入图片描述

flappy bird:一夜爆红的胖鸟

这是一款简单又困难的手机游戏,游戏中玩家必须控制一只胖乎乎的小鸟,跨越由各种不同长度水管所组成的障碍。上手容易,但是想通关可不简单。Flappy bird 于2013年5月在苹果App Store上线,2014年2月份在100多个国家/地区的榜单一跃登顶,尽管没有精细的动画效果,没有有趣的游戏规则,没有众多的关卡,却突然大火了一把,下载量突破5000万次。

一、工程创建和素材导入

在Assets中创建不同的文件夹,存储声音、图片、脚本、材质素材。

二、添加背景

创建Quad,创建Material,bg,back,pipe,bird,整个场景设置图1所示。其中材质的创建得选择
在这里插入图片描述
图 1 场景制作
Unlit Shader(无光照着色器):它是一个不包含光照(但包含雾效)的基本顶点/片元着色器

三、脚本使小鸟飞起来

关键技术:
小鸟飞行动画由三幅图组成,连续播放三幅图可以看出小鸟的翅膀在动。需要修改offset参数实现。
图 2 小鸟
0为第一个图,0.333为第二个图,0.666为第三个图。因此,每隔0.1s播放一个图片。
比如:0.1s播放offset为0,0.2s播放offset为0.333,0.3s播放offset为0.666
所以调用SetTextureOffset函数
this.GetComponent().velocity 意思是给小鸟一个初始速度,方向为x方向。所以前提是给小鸟一个rigidbody组件。

public float timer=0;
public int frameNumber=10;
public int frameCount=0;//统计第几帧

void Start()
{
	this.GetComponent<Rigidbody>().velocity=new Vector3(1,0,0)
}
void Update(){
	timer+=Time.deltaTime;
	if(timer>=1.0f/frameNumber){
	frameCount++;
	timer-=Time.deltaTime;
	//修改材质的偏移值
	int frameIndex=frameCount%3;
	this.GetComponent<Renderer>().material.SetTextureOffset("_MainTex",Vector2(0.333f*frameIndex,0));
}
}

"_MainTex"是主要的漫反射纹理;
"_BumpMap"是法线贴图
"_Cube"是反射cubemap.(立方体贴图)

四、随机生成管道的位置

修改管道的y值
随机值范围 Random.Range(a,b);//也就是说保持x和z不变,y是一个上下变化的值

public class pipe : MonoBehaviour{
	void Start(){
	RandomGeneratePosition();
}
public void RandomGeneratePosition(){
	float pos_y=Random.Range(-0.2f,0.2f);
	this.transform.localPosition=new Vector3(this.transform.localPosition.x,pos_y,this.tranform.localPosition.z);
}
}

五、添加小鸟、管道碰撞器

小鸟的碰撞器
图 3 小鸟的碰撞器
图 4 碰撞器的限制
锁定x,y方向的旋转,z方向的移动
图 5 小鸟的球型碰撞器
管道的碰撞器
图 6 管道的碰撞器

地面碰撞器
图 7 地面碰撞器
图 8 地面的碰撞器设置

3个背景循环展示

当小鸟走到bg1时,修改bg的位置,放置在bg2前面,依次循环。创建bg为一个预制体,根据这个预制体创建bg1,bg2.修改坐标。创建游戏管理器存放共享的变量、分值、ui等信息。新建一个脚本GameManage,挂在摄像头上。在这个脚本中,定义了一个共享的第一个bg位置,也就是最前面的bg的位置,同时,创建了一个共享单例。
在这里插入图片描述
创建一个MoveTrigger触发器。
图 9 MoveTrigger触发器的设置
图 10 MoveTrigger触发器的设置
图 11
其中,MoveTrigger属于bg下属子物体,当小鸟走到差不多第2个背景时候,触发MoveTrigger,讲左侧的这个bg移动到右侧的bg2的右侧。设置的触发器的z值,保证小鸟可以触发到。
这里要存储2个背景的位置。也就是说。当小鸟触发到了触发器,将第一个背景移动到右侧的第三个背景的右侧。他们的坐标值在x方向上相差20.

public class MoveTrigger : MonoBehaviour{
	public Transform currentBG;
	public pipe p1;
	public pipe p2;
	void OnTriggerEnter(Collider other){
		Transform firstBG = GameManage._instance.FirstBG;
		currentBG.position=new Vector3(firstBG.position.x+10,currentBG.position.y,curremtBG.position.z);
		p1.RamdomGeneratePosition();
		p2.RamdomGeneratePosition();
	}
}
代码的意思是:
currentBG为左侧的第一个bg的值。如果碰撞到是小鸟,firstBG为右侧的bg2的值,用bg2的x+10(当前的案例中应该是x+20),y和z不变。而后,currentBG赋值为FirstBG的值。

六、小鸟的跳跃

在小鸟的脚本中,鼠标左键按下后,有一个向上的速度。小鸟的重力必须加上。
在这里插入图片描述

七、相机的跟随

先试一下,将相机放在bird的子物体下。试一下看看,发现一个结果:bird飞后,相机会跟着,但是如果bird被撞飞后,相机会旋转。
解决如下:
创建一个相机跟随脚本。
手动给出 camera和bird的一个位置坐标差。

在这里插入图片描述

八、计分功能:

在每根管道中间添加一个触发器,选择一个pipe1,添加box collider

图 12
图 13
图 14图 15
到pipe2上粘贴collider。
图 16
在GameManage中,定义一个变量存储分数的。
在这里插入图片描述
在pipe脚本中,添加以下代码:
在这里插入图片描述

九、游戏状态控制

在游戏开始时,小鸟没有速度,没有重力。是停止的,在鼠标右键点击后,给小鸟添加上速度和重力,在飞行过程中,若小鸟撞到管子,就掉落,鼠标左键按下去应该没有作用,游戏结束。所以,需要给游戏设定几种游戏状态。
在这里插入图片描述
初始情况下,取消小鸟的速度和重力。并在小鸟的飞行和鼠标左键按下的左右是在playing状态下

if(GameManage._instance.GameState==GameMabage.GAMESTATE_PLAYING){
	timer+=Time.deltaTime;
	if(timer>=1.0f/frameNumber){
		frameCount++;
		timer-=Time.deltaTime;
		//修改材质的偏移值
		int frameIndex=frameCount%3;
		this.GetComponent<Renderer>().material.SetTextureOffser("_MainTex",new Vector2(0.333f*frameIndex,0));
	}
	if(Input.GetMouseButton(0)){
		Vector3 vel=this.GetComponent<Rigidbody>().velocity;
		this.GetComponent<Rigidbody>().velocity=new Vector3(vel.x,2,vel.z);
	}
}

设定函数,让其满足设置小鸟的速度和重力。
在这里插入图片描述
在GameMange中,从menu状态转变成Playing状态。在playing状态下,调用上述函数,使用方法是SendMessage。在一个脚本中向另一个脚本发送方法。
在这里插入图片描述
此时小鸟可以飞行了,可是,小鸟掉落后,再通过鼠标点击时,还可以点击后飞起。游戏状态没有改变。
于是,我们需要在小鸟撞击掉落后,修改游戏状态,同时,不让小鸟再次飞起。
注意:
不能在pipe中再写OnCollionEnter有关的函数,因为本代码又有触发器,两者是冲突的,要么触发器要么碰撞器。所以,我们在pipeup和pipedown中使用碰撞器。
在这里插入图片描述

十、添加声音

在Main Camera上,添加audio sourse 组件,添加声音文件sfx_swooshing,取消Loop
在这里插入图片描述
每次飞行都有翅膀煽动的声音,所以在bird上添加audiosourse,添加声音文件sfx_wing。取消play on awake。在bird的脚本中,播放声音。

if(input.GetMouseButton(0)){
	audio.Play();
}

得分的声音。在MoveTrigger的时候,才会有声音。于是在Pipe1和pipe2上添加audio sourse声音文件sfx_point.同样在脚本中添加

audio.Play();

撞到柱子的时候,会有声音,于是在pipe_up,pipe_down都添加audio sourse,添加sfx_hit声音。取消play on awake。在pipeupordown脚本中,trigger过程中,audio.Play()
死亡的声音
在这里插入图片描述

public class PipeUpOrDown:MonoBehaviour{
	public AudioSource hitMusic;
	public AudioSource dieMusic;
void OnCollisionEnter(Collision other){
	if(other.gameObject.tag=="Player"){
		hitMusic.Play();
		dieMusic.Play();
		GameManager._instance.GameState=GameManager.GAMESTATE_END;
}
}
}

十一、失败后的界面

在GameManage脚本,对菜单显示及更新分数
在这里插入图片描述

更新最高分值

在这里插入图片描述
同时加上HighScoreText.text = preHighScore + " ";
finalScoreText.text = Score + " ";
//加上空串可以将int转换成字符串
在结束状态
在这里插入图片描述

游戏的退出

在这里插入图片描述
不过Application.Quit();的代码在Unity3D运行中是不好使的,此代码是在打包之后运行才能好使!

游戏的重新开始

在这里插入图片描述

额外的知识

1.PlayerPrefs
PlayerPrefs简单来说就是unity提供的一种本地存储数据的方式。
目前提供存储,int/float/string三种类型的数据。
存储结构类似于字典,一个key值对应一个value。

分别使用

PlayerPrefs.GetInt(key);
PlayerPrefs.GetFloat(key);
PlayerPrefs.GetString(key); 获取键值对应的数值。
PlayerPrefs.SetInt(key,value);
PlayerPrefs.SetFloat(key,value);
PlayerPrefs.SetString(key,value); 设置键值对应的数值。
PlayerPrefs.DeleteKey(key); 删除键值。
PlayerPrefs.HasKey(key); 判断键值是否存在。
值得注意的是,键值对应的数据的默认值为数据类型的默认值。
比如当我们尝试获取一个未曾使用的Key,其返回值可能是:0,0F 或者 "",而不是null。
但是unity却允许存入默认数值,所以在读取数值的时候,返回默认数值的情况有两种,一种是因为不存在该Key值,另一种是因为你存入了默认值。
PlayerPrefs的储存位置

在Mac OS X上PlayerPrefs存储在~/Library/PlayerPrefs文件夹,名为unity.[company name].[product name].plist,这里company和product名是在Project Setting中设置的,相同的plist用于在编辑器中运行的工程和独立模式 (打开Find,按住Option键,点击“前往 →“资源库”,就可以找到Preferences文件夹。).
在Windows独立模式下,PlayerPrefs被存储在注册表的 HKCU\Software[company name][product name]键下(打开“运行”输入regedit打开注册表),这里company和product名是在Project Setting中设置的.
是持久本地化存储

using UnityEngine; 
using System.Colliections;

public class PlayerPerfsExample:MonoBehaviour
{ 
void Example() 
{ 
//设置键值和值 
PlayerPerfs.SetInt(“keyInt”,10); 
PlayerPerfs.SetFloat(“keyFloat”,10.2f); 
PlayerPerfs.SetString(“keyString”,”lmzqm”);

 //获取键值如果不存在该键值就返回默认值0
 PlayerPerfs.GetInt("keyInt",0);
 PlayerPerfs.GetFloat("keyFloat",0);
 PlayerPerfs.GetString("keyString","0");
  
 PlayerPerfs.DeleteAll();//删除所有键值
 PlayerPerfs.DeleteKey("keyInt");//删除某个键值

  bool exist = PlayerPerfs.HasKey("keyInt");//判断是否存在键值
}
3.发布到安卓时需要修改的地方

将脚本中的:

if(Input.GetMouseButton(0))

改为:

if(Input.touchCount==1)//表示手指单点触摸屏幕

在这里插入图片描述

源工程

下载链接:flappybird

Logo

苏州本地的技术开发者社区,在这里可以交流本地的好吃好玩的,可以交流技术,可以交流招聘等等,没啥限制。

更多推荐