Unity 游戏实例开发集合 之 FlappyBird (像素鸟) 休闲小游戏快速实现

目录

Unity 游戏实例开发集合 之 FlappyBird (像素鸟) 休闲小游戏快速实现

一、简单介绍

二、FlappyBird (像素鸟) 游戏内容与操作

三、相关说明

四、游戏代码框架

五、知识点

六、游戏效果预览

七、实现步骤

八、工程源码地址

九、延伸扩展


一、简单介绍

Unity 游戏实例开发集合,使用简单易懂的方式,讲解常见游戏的开发实现过程,方便后期类似游戏开发的借鉴和复用。

本节介绍,FlappyBird (像素鸟) 休闲小游戏快速实现的方法,希望能帮到你,若有不对,请留言。

这是一个 2D 游戏,主要是使用精灵图、2D 重力、2D 碰撞体,实现,游戏实现原理:

1、鸟 x 方向位置保持不变,背景天空草地,管道等在从右向左不动移动,从而实现鸟向前飞行效果

2、鸟有重力效果,通过 GetComponent<Rigidbody2D>().Velocity 的 y 方向添加速度,从而实现鸟向上飞的效果

3、背景天空草地,管道 也有Rigidbody2D,但是 Body Type 是 Kinematic ,没有重力向下的效果,但是可以 设置 GetComponent<Rigidbody2D>().Velocity 向左的速度,实现匀速向左运动的效果

4、游戏暂停实现原理是:1)暂停效果:把背景天空草地,管道 也有Rigidbody2D的Velocity 设置为0 ,暂停时记录鸟的Rigidbody2D的Velocity ,把 Rigidbody2D的 Body Type 是 Static,从而实现停止效果;2)继续游戏效果:重新设置背景天空草地,管道 也有Rigidbody2D的Velocity 设置为0之前的原值,把鸟Rigidbody2D的 Body Type 是 Dynamics,Rigidbody2D的Velocity设置为之前记录的值即可,这样就恢复现场,继续游戏了

5、无限背景实现:把背景天空草地设置两份(可以根据需要设置多份),1) 两份左右拼接

2)先是左边的背景显示,背景一起向左边运动到左边看不见,左边的看不到的背景,重新移动到右边背景的右边,这样,就实现无限循环背景了

二、FlappyBird (像素鸟) 游戏内容与操作

1、游戏开始,背景背景开始向左移动,鸟会受重力向下坠落

2、点击鼠标左键,鸟就会添加一个向上的力,避免坠落地面

3、鸟每过一个管子,就会有对应的分数增加

4、鸟撞到水管或者碰到地面,游戏则结束

三、相关说明

1、音频的枚举命名对应实际音频的名字,方便加载和指定播放音频

2、由于2D 游戏(元素都在同一平面),设置 SpriteRenderer 的 Order in Layer 来控制显示先后,Order in Layer 越大,显示就在前面,所以各个 Order in Layer 定义显示规则为 :天空预制体为 -5,草地 为 5,Bird 为 0,Pipe 下的上下管子为 2

3、Pipe 、GrassTile、SkyTile 的 Rigidbody2D, Body Type 是 Kinematic ,没有重力向下的效果

4、脚本复刻这块建议:先复刻 Common 和 Tools 文件夹的脚本,然后 各个实体 文件夹的脚本,接着 Manager 的各个脚本(顺序可按实现步骤的顺序来),最后 GameManager 和 GameStart 即可

四、游戏代码框架

(其实可以把Manager 拆分 Server 和 Data,或者更好)

五、知识点

1、MonoBehaviour 生命周期函数:Awake,Start,Update,Destroy

2、Input 按键的监控鼠标按键的状态

3、GameObject.Instantiate 物体的生成,GameObject.Destroy 物体的销毁

4、简单的对象池管理

5、Rigidbody 重力效果,添加 EdgeCollider2D ,进行 碰撞检测(Trigger)

6、简单UGUI的使用

7、简单屏幕适配(主要是UI和 Pipe生成位置)

8、一些数据,路径等的统一常量管理

9、Animation、Animator 的简单使用

10、游戏中 Tag 的使用

11、IManager 简单的接口规范 Manager 类函数

12、Action<int> OnChangeValue 属性变化中委托的使用

13、Resources.Load<GameObject>() 代码加载预制体的使用

14、简单的屏幕坐标转为世界坐标的工具类

15、 SceneManager.LoadScene 加载,和 SceneManager.GetActiveScene() 当前场景的获取

16、游戏开发的资源脚本文件夹分类管理

17、等等

六、游戏效果预览

七、实现步骤

1、打开 Unity,导入相关资源

2、因为是 2D 游戏,场景中设置MainCamera 的Clear Flags 为 Solid Color,Projection 为 Orthographic ,Pos(0,0,-10),大家根据需要设置即可

3、把导入的图片设置为 Sprite 精灵图

4、把两张天空图拖入场景,左右拼接两张图,一个pos(0,0,0),另一个pos(20.25,0,0),20.25很关键,将作为循环背景移动的关键参考数据

5、把屏幕设置为 1920x1080 作为参考大小,天空显示如图

6、同理,拖入草地的背景图,左右拼接,一个为pos(0,-2.5,0),另一个为pos(20.25,-2.5,0),效果如下

7、把天空和草地作为预制体,其中图片显示层级做修改,SkyTile 的 Order in Layer 为 -5(显示在后面),GrassTile 的 Order in Layer 为 5(显示在前面面),两个添加 Rigidbody2D ,其中 Rigidbody2D 的 Body Type 设置为 Kinematic(不会有向下的重力,但是可以施加 Rigidbody2D.velocity 的速度效果),然后 拖到 Resources 作为预制体

8、把两张飞行的鸟图拖入场景中,然后系统会自动生成一个动画和对应动画机挂载在图片上,运行场景,鸟动画大致如下

9、把BirdHero_01 改名为 Bird,选中 Bird,在菜单栏 Window - Animation - Animation,打开动画编辑,添加 BirdDie 鸟死亡动画,选择 Add Property ,添加SpriteRenderer 的 Sprite ,然后在 0.00 和 0.01 添加死亡图片,完成BirdDie 动画

10、选中Bird ,在Windows菜单中,选择 Animation - Animator,进入动画机,添加 Parameters参数来控制动画播放,这里添加Bool 的 IsDie 和 IsFly

11、在空白处右键,添加一个空动画,作为Idle 动画,选中 Idle ,右键 设置为默认动画,选中Idle 右键,点击 Make Transition ,拖到 BirdFly,形成一个动画过渡条件,同理,添加各个动画过渡条件线如图

12、其中 Idle 到 BirdFly 的过渡线的条件,选择为 IsFly 为true,BirdFly 到 Idle 的过渡线为 IsFly 为 flase

13、其中 BirdFly 到 BirdDie 的过渡线的条件,选择为 IsDie 为true,BirdDie 到 BirdFly 的过渡线为 IsDie 为 flase (或者可以不要这根过渡线也可以),Idle 到 BirdDie 的过渡线的条件,选择为 IsDie 为true

14、这样 Bird 的动画机完成了,整理一下相关资源,在 Bird 上,添加 Rigidbody ,和 BoxCollider2D,并整理BoxCollider2D大小适配鸟实际大小,然后把 Bird 拖入 Resources 作为预制体资源

15、把管子拖入场景,把 Bird 拖入场景中,为了适配鸟过去,添加一个空物体GameObject,改名为 Pipe(添加 Rigidbody2D,BodyType 设置为 Kinematic),把管子图片置于其下,上下一个,位置如下,DownColumnSprite 的 Pos(0,-6,0),BoxCollider2D 适配精灵图实际大小,UpColumnSprite的 Pos(0,6,0),BoxCollider2D 适配精灵图实际大小,ScoreEdgeCollider2D 位置和方向如图,刚好鸟在中间触发加分,并勾选 IsTrigger (可以碰撞触发,但没有实际碰撞效果)

16、把设置好的Pipe 作为预制体,拖入 Resources 文件夹下

17、在场景中添加一个空物体GameObject,改名为 World (pos(0,0,0)),然后把 Main Camera 等拖到其下,新建多个 空物体GameObject,依次命名为 SpawnSkyTilePos(天空背景的生成位置和父物体),SpawnGrassTilePos(草地背景的生成位置和父物体),SpawnBirdPos(Bird的生成位置和父物体),SpawnPipePos(管子的生成位置和父物体),AudioSourceTrans(音频播放源的挂载体),位置 都为 Pos(0,0,0)

18、  在 World 下添加一个 GameObject ,添加 EdgeCollider2D ,改名为 GroundEdgeCollider2D ,位置搞好放置到草地上(拖入草地预制体作为参考),如图,最后记得把参考的参照物删除就好

19、  在 World 下添加一个 GameObject ,添加 EdgeCollider2D ,改名为 SkyEdgeCollider2D ,添加一个Cube 作为可视化参考,位置搞好放置到天空上(拖入天空预制体作为参考),如图

20、新建一个 GameObject,改名为UI,用来当作 UI相关部分的父物体,pos 为 (0,0,0)

21、在UI下面,添加一个Canvas (自动添加EventSystem),设置 Canvas Scaler 的 UI Scale Mode 为 Scale with Screen Size ,Reference Resolution 为  1920 x 1080  ,Match 滑动到 Width

22、PauseGameButton 放置在左边上,改变锚点,文字加粗居中显示,可以根据实际情况自行调整

23、ScoreText 放置在右边上,改变锚点,文字加粗居中显示,可以根据实际情况自行调整

24、ResumeGameImage 图片铺满屏幕,设置为黑色,增加透明度,ResumeGameText 文字加粗居中显示,ResumeGameButton 放大,文字调大,可以根据实际情况自行调整,最终如图

25、GameOverImage 图片铺满屏幕,设置为黑色,增加透明度,GameOverText 文字加粗居中显示,RestartGameButton 放大,文字调大,可以根据实际情况自行调整,最终如图

26、在工程中添加 Common (一般可以游戏开发最后写,脚本中抽出来,整理统一管理),管理游戏中的一些常量,AnimatorParametersDefine 动画机的参数定义类,Enum 管理所有枚举,GameConfig 一些游戏配置,GameObjectPathInSceneDefine 场景中的游戏物体路径定义,ResPathDefine 预制体路径定义类,TagDefine Tag 定义类

27、 TagDefine Tag 定义类,其中 Tag 定义方式如下,选择一个游戏物体,选择 Tag 下拉菜单,点击 Add Tag ... 添加 Tag ,然后选择 + ,命名一个名称,save 即可,这里,我们把预制体的 Pipe的  Tag 设置为 Pipe (后面碰撞体触发用到)

28、在工程中添加 IManager 脚本,主要功能是定义管理类的一些基本接口

 public interface IManager{// 初始化void Init(Transform rootTrans, params object[] managers);// 帧更新void Update();// 销毁时调用(主要是释放数据等使用)void Destroy();// 游戏结束void GameOver();}

29、在工程中添加 ResLoadManager 脚本,主要功能是,加载 Resources 文件加载的预制体,这里主要涉及加载 GameObject 和 AudioClip

        /// <summary>/// 加载预制体 GameObject /// </summary>/// <param name="path"></param>/// <returns></returns>public GameObject LoadPrefab(string path) {if (m_PrefabsDict.ContainsKey(path) == true){return m_PrefabsDict[path];}else {GameObject prefab = Load<GameObject>(path);if (prefab!=null){m_PrefabsDict.Add(path, prefab);}return prefab;}}/// <summary>/// 加载预制体 AudioClip /// </summary>/// <param name="path"></param>/// <returns></returns>public AudioClip LoadAudioClip(string path){if (m_AudioClipsDict.ContainsKey(path) == true){return m_AudioClipsDict[path];}else{AudioClip prefab = Load<AudioClip>(path);if (prefab != null){m_AudioClipsDict.Add(path, prefab);}return prefab;}}/// <summary>/// 泛型加载预制体/// </summary>/// <typeparam name="T"></typeparam>/// <param name="path"></param>/// <returns></returns>private T Load<T>(string path) where T:Object{T prefab = Resources.Load<T>(path);if (prefab == null){Debug.LogError(GetType() + "/Load()/prefab is null,path = " + path);}return prefab;}

30、在工程中添加 Model 脚本,主要功能是定义了一个基本的数据模型,包含数值,和数值变化触发的事件

 /// <summary>/// 数据模型/// </summary>public class Model{private int m_Value;public int Value{get { return m_Value; }set{if (m_Value != value){m_Value = value;if (OnValueChanged != null){OnValueChanged.Invoke(value);}}}}/// <summary>/// 数值变化事件/// </summary>public Action<int> OnValueChanged;

31、在工程中添加 DataModelManager 脚本,主要功能是定义游戏中涉及到的数据,这里定义了 Score (既是 Model 类类型参数)的数据

        private Model m_Scroe;public Model Score => m_Scroe;public void Init(Transform rootTrans, params object[] manager){m_Scroe = new Model();m_Scroe.Value = 0;}public void Destroy(){m_Scroe.OnValueChanged = null;m_Scroe.Value = 0;m_Scroe = null;}

32、在工程中添加 SkyTile脚本,主要功能是 利用 Rigidbody2D.velocity,更新天空背景位置,当位置到达指定位置,进行位置左移,从而实现无限循环

     /// <summary>/// 初始化/// </summary>/// <param name="index">第几块天空,从 0 开始</param>public void Init(int index) {m_TargetPosX = -1 * GameConfig.BACKGROUND_SPRITE_INTERVAL_X;m_Velocity = Vector2.left * GameConfig.BACKGROUND_MOVE_LEFT_X;Vector3 curPos = transform.position;transform.position = new Vector3(curPos.x+(index)* GameConfig.BACKGROUND_SPRITE_INTERVAL_X,curPos.y, curPos.z);m_IsPause = false;Move();}public void Resume() {m_IsPause = false;Move();}public void Pause(){m_IsPause = true;Rigidbody2D.velocity = Vector2.zero;}public void GaomeOver(){Rigidbody2D.velocity = Vector2.zero;}private void Move(){Rigidbody2D.velocity = m_Velocity;}/// <summary>/// 更新天空背景位置/// 当位置到达指定位置,进行位置左移,从而实现无限循环/// </summary>private void UpdatePosOperation() {Vector3 curPos = transform.position;if (curPos.x <= m_TargetPosX){// 移动到右边(以为走了,右边的右边,所以增加 2 * BACKGROUND_SPRITE_INTERVAL_X )curPos = new Vector3((curPos.x + 2* GameConfig.BACKGROUND_SPRITE_INTERVAL_X), curPos.y, curPos.z);transform.position = curPos;}}

33、在工程中添加 SkyTileManager 脚本,主要功能是加载 SkyTile 预制体,生成到指定位置,然后在游戏暂停、继续、结束控制 SkyTile 是否移动

        /// <summary>/// 初始化/// </summary>/// <param name="rootTrans"></param>/// <param name="managers"></param>public void Init(Transform rootTrans, params object[] managers){m_SpawnSkyTilePosTrans = rootTrans.Find(GameObjectPathInSceneDefine.SPAWN_SKY_TILE_POS_PATH);m_ResLoadManager = managers[0] as ResLoadManager;m_SkyTileList = new List<GameObject>();LoadPrefab();}/// <summary>/// 加载预制体/// </summary>private void LoadPrefab() {GameObject prefab = m_ResLoadManager.LoadPrefab(ResPathDefine.PREFAB_SKY_TILE_PATH);for (int i = 0; i < GameConfig.BACKGROUND_TILE_COUNT; i++){GameObject skyTile = GameObject.Instantiate(prefab,m_SpawnSkyTilePosTrans);skyTile.AddComponent<SkyTile>().Init(i);m_SkyTileList.Add(skyTile);}}public void GamePause(){foreach (GameObject item in m_SkyTileList){item.GetComponent<SkyTile>().Pause();}}public void GameResume(){foreach (GameObject item in m_SkyTileList){item.GetComponent<SkyTile>().Resume();}}public void GameOver(){foreach (GameObject item in m_SkyTileList){item.GetComponent<SkyTile>().GaomeOver();}}

34、在工程中添加 GrassTile 脚本,主要功能是 利用 Rigidbody2D.velocity,更新草地背景位置,当位置到达指定位置,进行位置左移,从而实现无限循环

     /// <summary>/// 初始化/// </summary>/// <param name="index">第几块天空,从 0 开始</param>public void Init(int index){m_TargetPosX = -1 * GameConfig.BACKGROUND_SPRITE_INTERVAL_X;m_Velocity = Vector2.left * GameConfig.BACKGROUND_MOVE_LEFT_X;Vector3 curPos = transform.position;transform.position = new Vector3(curPos.x + (index) * GameConfig.BACKGROUND_SPRITE_INTERVAL_X,curPos.y, curPos.z);m_IsPause = false;Move();}/// <summary>/// 更新草地背景位置/// 当位置到达指定位置,进行位置左移,从而实现无限循环/// </summary>private void UpdatePosOperation(){Vector3 curPos = transform.position;if (curPos.x <= m_TargetPosX){// 移动到右边(以为走了,右边的右边,所以增加 2 * BACKGROUND_SPRITE_INTERVAL_X )curPos = new Vector3((curPos.x + 2 * GameConfig.BACKGROUND_SPRITE_INTERVAL_X), curPos.y, curPos.z);transform.position = curPos;}}

35、在工程中添加 GrassTileManager 脚本,主要功能是加载 GrassTile 预制体,生成到指定位置,然后在游戏暂停、继续、结束控制 GrassTile 是否移动

        /// <summary>/// 初始化/// </summary>/// <param name="rootTrans"></param>/// <param name="managers"></param>public void Init(Transform rootTrans, params object[] managers){m_SpawnGrassTilePosTrans = rootTrans.Find(GameObjectPathInSceneDefine.SPAWN_GRASS_TILE_POS_PATH);m_ResLoadManager = managers[0] as ResLoadManager;m_GrassTileList = new List<GrassTile>();LoadPrefab();}public void GamePause(){foreach (GrassTile item in m_GrassTileList){item.Pause();}}public void GameResume(){foreach (GrassTile item in m_GrassTileList){item.Resume();}}public void GameOver(){foreach (GrassTile item in m_GrassTileList){item.GaomeOver();}}/// <summary>/// 加载实例化预制体/// </summary>private void LoadPrefab(){GameObject prefab = m_ResLoadManager.LoadPrefab(ResPathDefine.PREFAB_GRASS_TILE_PATH);for (int i = 0; i < GameConfig.BACKGROUND_TILE_COUNT; i++){GameObject tile = GameObject.Instantiate(prefab, m_SpawnGrassTilePosTrans);GrassTile grassTile = tile.AddComponent<GrassTile>();grassTile.Init(i);m_GrassTileList.Add(grassTile);}}

36、在工程中添加 ObjectPool 脚本,主要功能是 泛型对象池,继承即可使用,包含 对象获取,预载、回收、以及清空等功能

    /// <summary>/// 泛型对象池/// </summary>/// <typeparam name="T"></typeparam>public class ObjectPool<T>  where T : MonoBehaviour{private Queue<T> m_TQueue;/// <summary>/// 获取 T/// </summary>/// <returns></returns>public T Get(GameObject prefab,Transform parent){if (m_TQueue!=null && m_TQueue.Count > 0){T t = m_TQueue.Dequeue();t.gameObject.SetActive(true);return t;}else{return InstantiateT(prefab, parent);}}/// <summary>/// 回收 T/// </summary>/// <param name="t"></param>public void Recycle(T t){t.gameObject.SetActive(false);if (m_TQueue==null){m_TQueue = new Queue<T>();}m_TQueue.Enqueue(t);}/// <summary>/// 预载/// </summary>/// <param name="prefab"></param>/// <param name="parent"></param>/// <param name="preloadCount"></param>public void PreloadT(GameObject prefab, Transform parent,int preloadCount=1){// 预载Splashif (m_TQueue==null){m_TQueue = new Queue<T>();}for (int i = 0; i < preloadCount; i++){Recycle(InstantiateT(prefab, parent));}}/// <summary>/// 清空对象池/// </summary>public void ClearPool() {if (m_TQueue!=null){while (m_TQueue.Count > 0){GameObject.Destroy(m_TQueue.Dequeue().gameObject);}}m_TQueue = null;}/// <summary>/// 生成实例/// </summary>/// <param name="prefab"></param>/// <param name="parent"></param>/// <returns></returns>private T InstantiateT(GameObject prefab,Transform parent){GameObject go = GameObject.Instantiate(prefab, parent);return go.AddComponent<T>();}}

37、在工程中添加 Bird 脚本,主要功能是 Rigidbody2D.velocity 实现鸟飞行,鸟的动画切换,以及触发碰撞体相关事件(死亡和得分事件)

     /// <summary>/// 初始化/// </summary>public void Init(Action onGroundCollisionEnter2D, Action onScoreCollisionEnter2D){m_OnGroundCollisionEnter2D = onGroundCollisionEnter2D;m_OnScoreCollisionEnter2D = onScoreCollisionEnter2D;m_UpVelocity = Vector2.up * GameConfig.BIRD_MOVE_UP_Y;PlayFlyAnimation();}public void Fly() {Rigidbody2D.velocity = m_UpVelocity;}public void Resume(){Rigidbody2D.bodyType = RigidbodyType2D.Dynamic;Move(m_CurVelocity);PlayFlyAnimation();}public void Pause(){m_CurVelocity = Rigidbody2D.velocity;Rigidbody2D.bodyType = RigidbodyType2D.Static;PlayIdleAnimation();}public void GameOver() {//Rigidbody2D.bodyType = RigidbodyType2D.Static;PlayDieAnimation();}private void Move(Vector2 velocity){Rigidbody2D.velocity = velocity;}private void PlayIdleAnimation() {Animator.SetBool(AnimatorParametersDefine.IS_FLY, false);Animator.SetBool(AnimatorParametersDefine.IS_DIE, false);}private void PlayFlyAnimation(){Animator.SetBool(AnimatorParametersDefine.IS_FLY, true);Animator.SetBool(AnimatorParametersDefine.IS_DIE, false);}private void PlayDieAnimation(){Animator.SetBool(AnimatorParametersDefine.IS_FLY, false);Animator.SetBool(AnimatorParametersDefine.IS_DIE, true);}/// <summary>/// 触发死亡碰撞/// </summary>/// <param name="collision"></param>private void OnCollisionEnter2D(Collision2D collision){if (collision.collider.name.StartsWith(GameConfig.GROUND_EDGE_COLLIDER2D_NAME) || collision.collider.CompareTag(TagDefine.PIPE)){if (m_OnGroundCollisionEnter2D!=null){m_OnGroundCollisionEnter2D.Invoke();}}}/// <summary>/// 触发加分碰撞/// </summary>/// <param name="collision"></param>private void OnTriggerEnter2D(Collider2D collision){if (collision.name.StartsWith(GameConfig.SCORE_EDGE_COLLIDER2D_NAME)){if (m_OnScoreCollisionEnter2D != null){m_OnScoreCollisionEnter2D.Invoke();}}}

38、在工程中添加 BirdManager脚本,主要功能是加载 BIrd预制体,生成到指定位置,然后在游戏暂停、继续、结束控制 Bird 是否可出发飞行等事件和动画,以及游戏结束和游戏得分事件

        /// <summary>/// 初始化/// </summary>/// <param name="rootTrans"></param>/// <param name="managers"></param>public void Init(Transform rootTrans, params object[] managers){m_SpawnBirdPosTrans = rootTrans.Find(GameObjectPathInSceneDefine.SPAWN_BIRD_POS_PATH);m_ResLoadManager = managers[0] as ResLoadManager;m_DataModelManager = managers[1] as DataModelManager;m_AudioManager = managers[2] as AudioManager;m_IsPause = false;m_IsGameOver = false;LoadPrefab();}public void Update(){if (m_IsPause == true || m_IsGameOver == true){return;}UpdatePosOperation();}public void Destroy(){m_SpawnBirdPosTrans = null;m_ResLoadManager = null;m_Bird = null;}public void GameResume() {m_IsPause = false;m_Bird.Resume();}public void GamePause() {m_IsPause = true;m_Bird.Pause();}public void GameOver(){m_IsGameOver = true;m_Bird.GameOver();}/// <summary>/// 加载实例化鸟/// </summary>private void LoadPrefab(){GameObject prefab = m_ResLoadManager.LoadPrefab(ResPathDefine.PREFAB_BIRD_PATH);GameObject bird = GameObject.Instantiate(prefab, m_SpawnBirdPosTrans);m_Bird =  bird.AddComponent<Bird>();m_Bird.Init(OnBirdGroundCollisionEnter, OnBirdScoreCollisionEnter);}/// <summary>/// 监听是否鼠标按下,向上飞/// </summary>private void UpdatePosOperation(){if (Input.GetMouseButtonDown(0) == true && EventSystem.current.IsPointerOverGameObject() ==false) // 鼠标点击在 UI 上不触发{m_AudioManager.PlayAudio(AudioClipSet.Fly);m_Bird.Fly();}}/// <summary>/// 游戏结束事件/// </summary>private void OnBirdGroundCollisionEnter() {m_AudioManager.PlayAudio(AudioClipSet.Collider);m_IsGameOver = true;}/// <summary>/// 游戏加分事件/// </summary>private void OnBirdScoreCollisionEnter(){m_AudioManager.PlayAudio(AudioClipSet.Tip);m_DataModelManager.Score.Value += GameConfig.PASS_PIPE_GET_SCORE;}

39、在工程中添加 Tools 脚本,主要功能是 把屏幕坐标转为世界坐标

     /// <summary>/// 把屏幕坐标转为世界坐标/// </summary>/// <param name="refTran">对应参照对象</param>/// <param name="refCamera">对应参照相机</param>/// <param name="screenPos">屏幕位置</param>/// <returns>屏幕位置的世界位置</returns>public static Vector3 ScreenPosToWorldPos(Transform refTran, Camera refCamera, Vector2 screenPos){//将对象坐标换成屏幕坐标Vector3 pos = refCamera.WorldToScreenPoint(refTran.position);//让鼠标的屏幕坐标与对象坐标一致Vector3 mousePos = new Vector3(screenPos.x, screenPos.y, pos.z);//将正确的鼠标屏幕坐标换成世界坐标交给物体return refCamera.ScreenToWorldPoint(mousePos);}

40、在工程中添加 Pipe 脚本,主要功能是 Rigidbody2D.velocity 实现 管子移动,和到达指定位置,进行对象回收

     /// <summary>/// 初始化/// </summary>/// <param name="spawnPosX"></param>/// <param name="spawnPosY"></param>/// <param name="movePosTargetPosX"></param>/// <param name="onRecycleSelfAction"></param>public void Init(float spawnPosX, float spawnPosY, float movePosTargetPosX, Action<Pipe> onRecycleSelfAction){m_TargetPosX = movePosTargetPosX;m_Velocity = Vector2.left * GameConfig.BACKGROUND_MOVE_LEFT_X;Vector3 curPos = transform.position;transform.position = new Vector3(spawnPosX, spawnPosY, curPos.z);m_OnRecycleSelfAction = onRecycleSelfAction;m_IsPause = false;Move();}private void Move(){Rigidbody2D.velocity = m_Velocity;}/// <summary>/// 位置更新/// 判断位置是否到达指定位置,进行对象回收/// </summary>private void UpdatePosOperation(){Vector3 curPos = transform.position;if (curPos.x <= m_TargetPosX){if (m_OnRecycleSelfAction != null){m_OnRecycleSelfAction.Invoke(this);}}}

41、在工程中添加 PipeManager脚本(继承 ObjectPool 实现 Bird 对象池功能),主要功能是加载 Pipe 预制体,生成到指定位置(管子的Y的位置是随机的,但范围是如图确认),然后在游戏暂停、继续、结束控制 Pipe 是否运动,定时生成管子

        /// <summary>/// 初始化/// </summary>/// <param name="rootTrans"></param>/// <param name="managers"></param>public void Init(Transform rootTrans, params object[] managers){m_SpawnPipePosTrans = rootTrans.Find(GameObjectPathInSceneDefine.SPAWN_PIPE_POS_PATH);m_ResLoadManager = managers[0] as ResLoadManager;m_PipeList = new List<Pipe>();m_IsPause = false;m_IsGameOver = false;m_SpawnTimer = GameConfig.PIPE_SPAWN_TIME_INTERVAL;m_SpawnPosX = Tools.ScreenPosToWorldPos(m_SpawnPipePosTrans,Camera.main,Vector2.right*(Screen.width *(1+0.1f))).x;m_TargetMovePosX = Tools.ScreenPosToWorldPos(m_SpawnPipePosTrans,Camera.main,Vector2.left*(Screen.width *(0.1f))).x;m_PipePrefab = m_ResLoadManager.LoadPrefab(ResPathDefine.PREFAB_Pipe_PATH);LoadPrefab(m_PipePrefab, m_SpawnPipePosTrans);}public void GamePause(){m_IsPause = true;if (m_PipeList != null && m_PipeList.Count > 0){foreach (var item in m_PipeList){item.Pause();}}}public void GameResume(){m_IsPause = false;if (m_PipeList != null && m_PipeList.Count > 0){foreach (var item in m_PipeList){item.Resume();}}}public void GameOver(){m_IsGameOver = true;if (m_PipeList!=null && m_PipeList.Count>0){foreach (var item in m_PipeList){item.GaomeOver();}}}/// <summary>/// 预载实例化对象/// </summary>/// <param name="prefab"></param>/// <param name="parent"></param>private void LoadPrefab(GameObject prefab,Transform parent){PreloadT(prefab, parent);}/// <summary>/// 计时生成管子,以及初始化管子和设置回收管子事件/// </summary>void UpdateSpawnPipe() {m_SpawnTimer += Time.deltaTime;if (m_SpawnTimer>=GameConfig.PIPE_SPAWN_TIME_INTERVAL){m_SpawnTimer -= GameConfig.PIPE_SPAWN_TIME_INTERVAL;Pipe pipe = Get(m_PipePrefab, m_SpawnPipePosTrans);float spawnPosY = Random.Range(GameConfig.PIPE_SPAWN_POS_Y_LIMIT_MIN,GameConfig.PIPE_SPAWN_POS_Y_LIMIT_MAX);pipe.Init(m_SpawnPosX, spawnPosY,m_TargetMovePosX, (p)=> {m_PipeList.Remove(p);Recycle(p);});m_PipeList.Add(pipe);}}

42、在工程中添加 AudioManager 脚本,主要功能是加载指定音频,以及添加AudioSource ,播放指定音频(Sound 拖到 Resources 文件夹下,枚举命名上注意与音频文件对应)

        /// <summary>/// 初始化/// </summary>/// <param name="rootTrans"></param>/// <param name="managers"></param>public void Init(Transform rootTrans, params object[] managers){m_AudioSourceTrans = rootTrans.Find(GameObjectPathInSceneDefine.AUDIO_SOURCE_TRANS_PATH);m_ResLoadManager = managers[0] as ResLoadManager;m_AudioClipDict = new Dictionary<AudioClipSet, AudioClip>();m_AudioSource = m_AudioSourceTrans.gameObject.AddComponent<AudioSource>();Load();}/// <summary>/// 播放指定音频/// </summary>/// <param name="audioName"></param>public void PlayAudio(AudioClipSet audioName) {if (m_AudioClipDict.ContainsKey(audioName) == true){m_AudioSource.PlayOneShot(m_AudioClipDict[audioName]);}else {Debug.LogError(GetType()+ "/PlayAudio()/ audio clip is null,audioName = "+ audioName);}}/// <summary>/// 加载音频/// </summary>private void Load() {for (AudioClipSet clipPath = AudioClipSet.Collider; clipPath < AudioClipSet.SUM_COUNT; clipPath++){AudioClip audioClip = m_ResLoadManager.LoadAudioClip(ResPathDefine.AUDIO_CLIP_BASE_PATH+ clipPath.ToString());m_AudioClipDict.Add(clipPath, audioClip);}}

43、在工程中添加 UIManager 脚本,主要功能是获取 UI 元素,对应和 Score 添加对应事件,以及 游戏暂停,继续、结束按钮的事件添加

        public void Init(Transform rootTrans, params object[] managers){m_ScoreText = rootTrans.Find(GameObjectPathInSceneDefine.UI_SCORE_TEXT_PATH).GetComponent<Text>();m_ResumeGameImageGo = rootTrans.Find(GameObjectPathInSceneDefine.UI_RESUME_GAME_IMAGE_PATH).gameObject;m_GameOverImageGo = rootTrans.Find(GameObjectPathInSceneDefine.UI_GAME_OVER_IMAGE_PATH).gameObject;m_ResumeGameButton = rootTrans.Find(GameObjectPathInSceneDefine.UI_RESUME_GAME_BUTTON_PATH).GetComponent<Button>();m_PauseGameButton = rootTrans.Find(GameObjectPathInSceneDefine.UI_PAUSE_GAME_BUTTON_PATH).GetComponent<Button>();m_RestartGameButton = rootTrans.Find(GameObjectPathInSceneDefine.UI_RESTART_GAME_BUTTON_PATH).GetComponent<Button>();m_DataModelManager = managers[0] as DataModelManager;m_GameOverImageGo.SetActive(false);m_ScoreText.text = m_DataModelManager.Score.Value.ToString();m_DataModelManager.Score.OnValueChanged += OnScroeValueChanged;m_ResumeGameButton.onClick.AddListener(OnResumeGameButton);m_PauseGameButton.onClick.AddListener(OnPauseGameButton);m_RestartGameButton.onClick.AddListener(OnRestartButton);m_ResumeGameImageGo.SetActive(true);m_IsPause = true;}public void Update(){}public void Destroy(){m_RestartGameButton.onClick.RemoveAllListeners();m_ScoreText = null;m_GameOverImageGo = null;m_RestartGameButton = null;m_DataModelManager = null;}public void GameOver(){m_GameOverImageGo.SetActive(true);}private void OnScroeValueChanged(int score){m_ScoreText.text = score.ToString();}private void OnPauseGameButton(){m_IsPause = true;m_ResumeGameImageGo.SetActive(true);}private void OnResumeGameButton(){m_IsPause = false;m_ResumeGameImageGo.SetActive(false);}private void OnRestartButton(){SceneManager.LoadScene(SceneManager.GetActiveScene().name);}

44、在工程中添加 GameManager 脚本(单例),主要功能:1)获取场景中相关游戏物体或者 UI根物体,2)new 相关的 Manager 管理类,初始化Init,Update 、和Destroy,3)判断游戏状态,是否暂停、继续、结束

        public void Awake() {m_ResLoadManager = new ResLoadManager();m_AduioManager = new AudioManager();m_SkyTileManager = new SkyTileManager();m_GrassTileManager = new GrassTileManager();m_BirdManager = new BirdManager();m_PipeManager = new PipeManager();m_DataModelManager = new DataModelManager();m_UIManager = new UIManager();}public void Start() {m_WorldTrans = GameObject.Find(GameObjectPathInSceneDefine.WORLD_PATH).transform;m_UITrans = GameObject.Find(GameObjectPathInSceneDefine.UI_PATH).transform;Init(null);m_IsGameOver = false;}public void Init(Transform rootTrans, params object[] managers){m_DataModelManager.Init(null);m_ResLoadManager.Init(null);m_AduioManager.Init(m_WorldTrans, m_ResLoadManager);m_SkyTileManager.Init(m_WorldTrans, m_ResLoadManager);m_GrassTileManager.Init(m_WorldTrans, m_ResLoadManager);m_BirdManager.Init(m_WorldTrans, m_ResLoadManager, m_DataModelManager, m_AduioManager);m_PipeManager.Init(m_WorldTrans, m_ResLoadManager);m_UIManager.Init(m_UITrans, m_DataModelManager);}public void Update(){JudgeGamePauseOrResume();JudgeGameOver();m_DataModelManager.Update();m_ResLoadManager.Update();m_SkyTileManager.Update();m_GrassTileManager.Update();m_BirdManager.Update();m_PipeManager.Update();m_UIManager.Update();m_AduioManager.Update();}public void Destroy(){m_DataModelManager.Destroy();m_ResLoadManager.Destroy();m_SkyTileManager.Destroy();m_GrassTileManager.Destroy();m_BirdManager.Destroy();m_PipeManager.Destroy();m_UIManager.Destroy();m_AduioManager.Destroy();}

45、GameStart 脚本,整个游戏的入口,管理对应 GameManager 的 Awake(),Start(),Update(),OnDestroy() ,OnGUI() 对应函数功能

 public class GameStart : MonoBehaviour{private void Awake(){GameManager.Instance.Awake();}// Start is called before the first frame updatevoid Start(){GameManager.Instance.Start();}// Update is called once per framevoid Update(){GameManager.Instance.Update();}private void OnDestroy(){GameManager.Instance.Destroy();}}

46、在场景中添加 GameObject 空物体,改名为 GameStart,并且挂载 GameStart 脚本

47、运行场景,就会自动生成场景,点击 Game ,开始游戏,Pause 暂停游戏,Bird 撞地面和管子都会游戏结束,每过一个管子都会增加分数

八、工程源码地址

github 地址:https://github.com/XANkui/UnityMiniGameParadise

的 MGP_006FlappyBird 工程

九、延伸扩展

游戏的好不好玩,趣味性,视觉化等诸多因素影响,下面简单介绍几个方面拓展游戏的方向,仅做参考

1、可以根据自己需要修改游戏资源,换肤什么的等

2、可以根据需要添加加分特效,音效,背景更多的细节变化等等

3、添加 UI 面板等,美化游戏

4、Bird 得分特效添加

5、Bird 不同分数下的不同状态或者皮肤,抑或速度;

6、添加最高分数保留,和游戏排行榜等;

7、管子可以出来一些什么东西,或者也有上下动的管子;

8、天空和草地的不同组别,比如春夏秋冬,中国日本埃及不同风格,以增加新奇性

9、等等

Unity 游戏实例开发集合 之 FlappyBird (像素鸟) 休闲小游戏快速实现相关推荐

  1. Unity 游戏实例开发集合 之 CompoundBigWatermelon (简单合成一个大西瓜) 休闲小游戏快速实现

    Unity 游戏实例开发集合 之 CompoundBigWatermelon (简单合成一个大西瓜) 休闲小游戏快速实现 目录 Unity 游戏实例开发集合 之 CompoundBigWatermel ...

  2. Unity 游戏实例开发集合 之 FlyPin (见缝插针) 休闲小游戏快速实现

    Unity 游戏实例开发集合 之 FlyPin (见缝插针) 休闲小游戏快速实现 目录 Unity 游戏实例开发集合 之 FlyPin (见缝插针) 休闲小游戏快速实现 一.简单介绍 二.FlyPin ...

  3. Unity 游戏实例开发集合 之 Car Racing 2D (2D赛车) 休闲小游戏快速实现

    Unity 游戏实例开发集合 之 Car Racing 2D (2D赛车) 休闲小游戏快速实现 目录 Unity 游戏实例开发集合 之 Car Racing 2D (2D赛车) 休闲小游戏快速实现 一 ...

  4. Unity 游戏实例开发集合 之 CutFruit (切水果(水果忍者)) 休闲小游戏快速实现

    Unity 游戏实例开发集合 之 CutFruit (切水果(水果忍者)) 休闲小游戏快速实现 目录 Unity 游戏实例开发集合 之 CutFruit (切水果(水果忍者)) 休闲小游戏快速实现 一 ...

  5. Unity 游戏实例开发集合 之 Circus (马戏团) 休闲小游戏快速实现

    Unity 游戏实例开发集合 之 Circus (马戏团) 休闲小游戏快速实现 目录 Unity 游戏实例开发集合 之 Circus (马戏团) 休闲小游戏快速实现 一.简单介绍 二.Circus ( ...

  6. Unity 游戏实例开发集合 之 打砖块 休闲小游戏快速实现

    Unity 游戏实例开发集合 之 打砖块 休闲小游戏快速实现 目录 Unity 游戏实例开发集合 之 打砖块 休闲小游戏快速实现 一.简单介绍 二.打砖块游戏内容与操作 三.游戏代码框架 四.知识点 ...

  7. c语言像素鸟游戏,掌控板制作Flappy bird(像素鸟)小游戏

    掌控板制作Flappy bird小游戏 大学时期火爆的手机游戏,记得那时候我在做unity开发,还用C#仿照写过这个游戏,用"空格键"操作. 先说一下这游戏的操作:游戏开始,点击屏 ...

  8. html5实现像素鸟,[Swift]SpriteKit实现类似像素鸟的小游戏 - Crashy Plane

    像素鸟曾经非常火爆,游戏简单,很有趣味性,仿写一个叫 crashy plane 的游戏,它的原理跟像素鸟是一样的,接下来用 SpriteKit 来实现它 同时推荐一个不错的学习 Swift 的网站,这 ...

  9. 原生JS:100行js代码带你实现【像素鸟】小游戏(完整代码+素材图片)

    系列文章目录 JS:经典小游戏:像素鸟 JS:经典小游戏:贪吃蛇 JS:经典小游戏:扫雷 目录 系列文章目录 像素鸟 1.游戏介绍 2.代码分析 3.代码实现 3.1 随机生成水管 3.2 当水管超过 ...

  10. 【Pygame小游戏】一发超人:弓箭手佛系射鸟休闲小游戏,你能射中几只?(源码白嫖)

    前言 嘿,有没有想我? 栗子驾着七彩祥云来给大家敲代码啦! 所有文章完整的素材+源码都在

最新文章

  1. spark streaming 入门例子
  2. 如何去除Windows8测试版界面“水印”
  3. C++学习笔记:(四)运算符重载 类型转换
  4. 串口MSComm控件五种不同校验方式对数据收发的影响
  5. Linux-2.6.25 TCPIP函数调用大致流程
  6. Map 四种获取 key 和 value 值的方法,以及对 map 中的元素排序
  7. 首次!华人获世界科学奖,其海洋蓝色能源技术或解决世界能源需求
  8. MySQL基础篇(06):事务管理,锁机制案例详解
  9. MySQL check the manual that corresponds to your MySQL server version for the right syntax错误
  10. 物联网市场潜力巨大,中国移动加强布局
  11. Socket开发探秘--基于Json格式的数据协议收发
  12. 区块链:深入浅出零知识证明
  13. 深度强化Q学习-基于微软AirSim仿真环境的自动驾驶案例(原理代码详解简易可行)
  14. 误差修正ECM模型怎么分析?
  15. 屏幕录像软件使用教程?
  16. 肿瘤全外显子--记录
  17. 路径详解(绝对路径,相对路径,根相对路径)
  18. 一连串数字怎么转换成二维码?数字生成二维码如何制作?
  19. IDEA相对路径系统找不到指定的文件问题
  20. 加勒比海盗——最优装载问题

热门文章

  1. 错误: 找不到或无法加载主类 org.sang.BlogserverApplication
  2. 名利如过往云烟,知足就好
  3. Java并发编程系列(4)-线程安全及synchronized
  4. pyd文件逆向(二)
  5. 统计并输出数字、大写字母、小写字母
  6. php中怎么批量修改图片大小,怎么批量修改图片大小 光影魔术手批量处理图片...
  7. DataGear 制作Excel动态数据可视化图表
  8. 凝思系统激活序列号_安装凝思linux操作系统.doc
  9. 中国有哪些优秀的民谣音乐人?
  10. 计算机十年歌曲,抖音还有多少个十年原唱是谁 歌词一览