赞
踩
这是一款简单又困难的手机游戏,游戏中玩家必须控制一只胖乎乎的小鸟,跨越由各种不同长度水管所组成的障碍。上手容易,但是想通关可不简单。Flappy bird 于2013年5月在苹果App Store上线,2014年2月份在100多个国家/地区的榜单一跃登顶,尽管没有精细的动画效果,没有有趣的游戏规则,没有众多的关卡,却突然大火了一把,下载量突破5000万次。
在Assets中创建不同的文件夹,存储声音、图片、脚本、材质素材。
创建Quad,创建Material,bg,back,pipe,bird,整个场景设置图1所示。其中材质的创建得选择
Unlit Shader(无光照着色器):它是一个不包含光照(但包含雾效)的基本顶点/片元着色器
关键技术:
小鸟飞行动画由三幅图组成,连续播放三幅图可以看出小鸟的翅膀在动。需要修改offset参数实现。
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);
}
}
小鸟的碰撞器
锁定x,y方向的旋转,z方向的移动
管道的碰撞器
地面碰撞器
当小鸟走到bg1时,修改bg的位置,放置在bg2前面,依次循环。创建bg为一个预制体,根据这个预制体创建bg1,bg2.修改坐标。创建游戏管理器存放共享的变量、分值、ui等信息。新建一个脚本GameManage,挂在摄像头上。在这个脚本中,定义了一个共享的第一个bg位置,也就是最前面的bg的位置,同时,创建了一个共享单例。
创建一个MoveTrigger触发器。
其中,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
到pipe2上粘贴collider。
在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运行中是不好使的,此代码是在打包之后运行才能好使!
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值,另一种是因为你存入了默认值。
在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");//判断是否存在键值 }
将脚本中的:
if(Input.GetMouseButton(0))
改为:
if(Input.touchCount==1)//表示手指单点触摸屏幕
下载链接:flappybird
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。