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

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

目录

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

一、简单介绍

二、CompoundBigWatermelon (简单合成一个大西瓜) 游戏内容与操作

三、注意事项

四、游戏代码框架

五、知识点

六、游戏效果预览

七、实现步骤

八、工程源码地址

九、延伸扩展


一、简单介绍

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

本节介绍,CompoundBigWatermelon (简单合成一个大西瓜)休闲小游戏快速实现的方法,希望能帮到你,若有不对,请留言。

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

水果的合成线路如下:

山竹 -> 苹果 -> 橙子-> 柠檬-> 猕猴桃-> 番茄-> 水蜜桃-> 菠萝-> 椰子-> 西瓜-> 大西瓜

二、CompoundBigWatermelon (简单合成一个大西瓜) 游戏内容与操作

1、游戏开始,鼠标按下,移动水果,会有瞄准线对准落下方向

2、鼠标松开,水果落下

3、相同2个水果相碰,会触发合成新水果,并增加对应分数

4、水果等级一定高度会触发,警告线

5、水果接近或者超过警告线持续若干秒,则游戏结束

三、注意事项

1、水果的图片的大小直接影响游戏精灵图大小,同一屏幕尺寸下,图片越大,其实合成一个大西瓜的难度越大,可以在游戏配置统一改生成的水果精灵图大小

2、随机生成水果的规则是,随机数是(0,水果路线总数的一半),需要更改,可以在游戏配置中修改

3、两个相碰撞的水果合成规则是:1)相碰撞的水果,在下面的(Y值较小的)触发合成事件;2)如果相碰撞的两个水果同一水平线(Y值相等),则左边的(X值较小的)触发合成事件;3)合成的水果在碰撞点合成,且带有两相撞水果中某个水果的角速度

4、水果的碰撞体大小是默认的,如果觉得碰撞不是很好,可以手动调整一下水果的碰撞体尺寸

5、水果的重力是默认设置,如果想修改不同重力效果,可以手动调整水果的下落重力

6、由于2D 游戏(元素都在同一平面),设置 SpriteRenderer 的 Order in Layer 来控制显示先后,Order in Layer 越大,显示就在前面,所以各个 Order in Layer 定义显示规则为 :水果预制体为 0,BorderLine 边界为 2,WarningLine 为 5,Effect 为 8,AimLine 为 -2

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

四、游戏代码框架

五、知识点

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

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

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

4、简单的对象池管理

5、Rigidbody2D 重力效果,添加 CirecleCollider2D ,进行 碰撞检测

6、简单UGUI的使用

7、Vector3.Lerp 位移向量插值使用,Vector3.Distance 位置距离函数的使用

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

9、Transform.Rotate 旋转使用

10、IEnumerator 协程 , StartCoroutine 开始协程 和 StopAllCoroutines 停止所有协程的使用

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

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

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

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

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

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

17、等等
 

六、游戏效果预览

七、实现步骤

1、打开 Unity,导入水果、音频的资源

2、因为是 2D 游戏,修改MainCamera 位置为(0,0,-10),Camera 中的 Clear Flag 为 Solid Color ,设置 Background 做为游戏背景颜色,Projection 设置为 Orthographic 正交模式,然后设置屏幕为竖屏 1080x1920 作为游戏基础屏,效果如下

3、把导入进来的图片,全部设置为 Sprite 精灵图

4、把转换为精灵图的水果拖入场景中(系统会自动添加SpriteRenderer 精灵图渲染),然后添加 Rigidbody2D(2D重力) 和 CircleCollider2D(2D圆形碰撞体)

5、给水果的 Rigidbody 2D 添加 FruitPhysicsMaterial2D (2D 物理材质),以增加水果落地的稍稍的回弹效果,如图

6、FruitPhysicsMaterial2D (2D 物理材质) 创建如图,Bounciness 设置为 0.25

 

7、根据上面的方法,把所有水果都做成预制体

8、effect 拖入场景,然后拖回工程文件夹中,也作为预制体

9、把 BorderLine 拖到场景中,并添加一个 2D 碰撞体 (BoxCollider2D),拷贝两份,作为边界,如图 ,BorderLine_Down 的 pos(0,-5,0),BorderLine_Left 的 pos(-2.8,0,0) Rotation(0,0,90),BorderLine_Right 的 pos(2.8,0,0) Rotation(0,0,90)

 

10、把 WarningLine 拖到场景中,设置 Position(0,3,0)

11、把 WarningLine 拖到场景中,设置 Position(0,0,0)

12、创建一个GameObject 空物体,改名为 SpawnFruitPos ,设置 Position(0,4,0) 刚好在 AimLine 上端点

13、由于2D 游戏(元素都在同一平面),设置SpriteRenderer 的 Order in Layer 来控制显示先后,Order in Layer 越大,显示就在前面,所以各个 Order in Layer 定义显示为 :水果预制体为 0,BorderLine 边界为 2,WarningLine 为 5,Effect 为 8,AimLine 为 -2

14、创建一个 GameObject 空物体,改名为 World,把场景中的所有物体,都作为其子物体

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

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

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

 

18、创建Game Object空物体,改名为 UI ,把 Canvas 相关的都拖到 其下, UI Canvas 效果如图

19、在工程中添加 Fruit 脚本,主要功能是,进行水果类型的标记,以及水果碰撞的监测

20、Fruit 脚本主要函数:Init() 定义水果类型,和统一水果碰撞合成的事件,EnableFruit()/DisableFruit() 是设置该水果是否有重力和碰撞效果,OnCollisionEnter2D() 该水果的碰撞检测,相同水果且不是大西瓜,则触发水果合成委托

       public void Init(FruitSeriesType fruitSeriesType, Action<Collision2D, Fruit> onCompoundAction)
        {
            DisableFruit();
            m_FruitSeriesType = fruitSeriesType;
            m_OnCompoundAction = onCompoundAction;
        }

        public void EnableFruit()
        {
            Rigidbody2D.bodyType = RigidbodyType2D.Dynamic;
            CircleCollider2D.enabled = true;
        }

        public void DisableFruit()
        {
            Rigidbody2D.bodyType = RigidbodyType2D.Static;
            CircleCollider2D.enabled = false;
        }

        public void Fall()
        {
            EnableFruit();

        }

        public float GetRadius()
        {
            return CircleCollider2D.radius;
        }

        #region Unity Function

        /// <summary>
        /// 碰撞检测函数
        /// </summary>
        /// <param name="collision"></param>
        private void OnCollisionEnter2D(Collision2D collision)
        {
            // 不是大西瓜,则合成
            if (this.FruitType != FruitSeriesType.BigWatermelon)
            {
                // 判断与之相碰的也是水果
                Fruit otherRuit = collision.gameObject.GetComponent<Fruit>();
                if (otherRuit != null)
                {
                    // 水果类型相同,才触发合成新瓜
                    if (otherRuit.FruitType == this.FruitType)
                    {
                        if (m_OnCompoundAction != null)
                        {
                            m_OnCompoundAction.Invoke(collision, this);
                        }

                    }
                }
            }
        }

        #endregion

21、在工程中添加 FruitManager 脚本,主要功能是:1)水果的预制体加载;2)水果的生成;3)水果未释放前的位置移动;4)相同合成水果时新水果的生成事件;

22、FruitManager 脚本主要函数:LoadFruitsPrefab() 加载水果预制体,UpdateFruitOperation()监控鼠标情况,进行水果移动和释放

		/// <summary>
		/// 
		/// </summary>
		void LoadFruitsPrefab() {
			m_FuritPrefabList.Clear();
			// FruitSeriesType 枚举的名字和预制体名字一致
			for (FruitSeriesType i = 0; i < FruitSeriesType.SUM_COUNT; i++)
			{
				Debug.Log(GetType() + "/LoadFruitsPrefab()/ " + i);
				string path = ResPathDefine.FRUITS_PREFAB_BASE_PATH + i;
				GameObject fruit = Resources.Load<GameObject>(path);
				if (fruit == null)
				{
					Debug.LogError(GetType()+ "/LoadFruitsPrefab()/fruit is null, path =  "+ path);
				}
				else {
					m_FuritPrefabList.Add(fruit);
				}
			}
		}

		/// <summary>
		/// 监控鼠标情况,进行水果移动和释放
		/// </summary>
		void UpdateFruitOperation()
		{


			if (Input.GetMouseButtonDown(0))
			{

			}
			else if (Input.GetMouseButton(0))
			{
				UpdateCurFuritPos();
			}
			else if (Input.GetMouseButtonUp(0))
			{
				m_IsFalled = true;
				if (m_CurFruit != null)
				{
					m_CurFruit.Fall();

				}

				if (m_IsCanSpawn == true)
				{
					m_Mono.StartCoroutine(SpawnRandomFruit(m_SpawnFruitPosTrans.position, m_SpawnFruitPosTrans));

				}
			}

		}

23、FruitManager 脚本主要函数:IEnumerator SpawnRandomFruit(Vector2 pos, Transform parent) 协程释放水果后,随机生成水果(规则时:随机的上限是水果系列的总数的一半Random.Range(0, (int)((int)FruitSeriesType.SUM_COUNT / 2));),SpawnFruit() 真正生成水果的函数,这里没有做对象池的处理,大家其实可以根据需要进行生成水果和释放水果的对象池处理,可以作为性能优化

		/// <summary>
		/// 写成生成水果
		/// </summary>
		/// <param name="pos"></param>
		/// <param name="parent"></param>
		/// <returns></returns>
		IEnumerator SpawnRandomFruit(Vector2 pos, Transform parent)
		{
			m_IsCanSpawn = false;
			yield return new WaitForSeconds(GameConfig.FRUIT_SPAWN_INTERVAL_TIME);
			if (GameManager.Instance.GameOver == false)
			{
				int random = Random.Range(0, (int)((int)FruitSeriesType.SUM_COUNT / 2));
				m_CurFruit = SpawnFruit((FruitSeriesType)random, pos, parent);
				m_IsFalled = false;
				m_IsCanSpawn = true;
			}

		}

		/// <summary>
		/// 生成水果(非合成水果)
		/// </summary>
		/// <param name="fruitSeriesType"></param>
		/// <param name="pos"></param>
		/// <param name="parent"></param>
		/// <returns></returns>
		Fruit SpawnFruit(FruitSeriesType fruitSeriesType, Vector2 pos, Transform parent)
		{
			m_AudioManager.PlaySpawnSound();
			return SpawnFruit(fruitSeriesType, pos, parent, false);
		}

		/// <summary>
		/// 生成水果(可合成水果)
		/// </summary>
		/// <param name="fruitSeriesType"></param>
		/// <param name="pos"></param>
		/// <param name="parent"></param>
		/// <param name="isCompound"></param>
		/// <returns></returns>
		Fruit SpawnFruit(FruitSeriesType fruitSeriesType, Vector2 pos, Transform parent, bool isCompound)
		{

			GameObject go = GameObject.Instantiate(m_FuritPrefabList[(int)fruitSeriesType], parent);
			Fruit fruit = go.AddComponent<Fruit>();
			fruit.Init(fruitSeriesType, OnCompoundAction);
			if (isCompound == true)
			{
				fruit.EnableFruit();
			}

			fruit.transform.position = pos;
			fruit.transform.localScale *= GameConfig.FRUIT_SCALE;

			return fruit;
		}

24、FruitManager 脚本主要函数:UpdateCurFuritPos()鼠标操作时,实时更新对应的当前的水果位置,ClampFruitPos(Vector3 pos)移动水果的时候,水平位置的限制

		/// <summary>
		/// 鼠标操作时,实时更新对应的当前的水果位置
		/// </summary>
		private void UpdateCurFuritPos()
		{
			if (m_CurFruit != null && m_IsFalled == false)
			{
				Vector3 pos = Tools.ScreenPosToWorldPos(m_CurFruit.transform, m_MainCamera, Input.mousePosition);
				m_CurFruit.transform.position = ClampFruitPos(pos);

				
			}
		}

		/// <summary>
		/// 释放水果的时候,水平位置的限制
		/// </summary>
		/// <param name="pos"></param>
		/// <returns>限制后的水果位置</returns>
		private Vector3 ClampFruitPos(Vector3 pos)
		{
			if (m_CurFruit != null)
			{
				if (pos.x - m_CurFruit.GetRadius() < m_FruitXLeftLimitValue)
				{
					pos.x = m_FruitXLeftLimitValue + m_CurFruit.GetRadius();

				}

				if (pos.x + m_CurFruit.GetRadius() > m_FruitXRightLimitValue)
				{
					pos.x = m_FruitXRightLimitValue - m_CurFruit.GetRadius();

				}

				pos.y = m_CurFruit.transform.position.y;
			}

			return pos;
		}

25、FruitManager 脚本主要函数:OnCompoundAction(Collision2D col, Fruit toColFruit)判断合成水果的时候条件进行哪个水果作为事件触发,CompoundHandle(Collision2D col, Fruit toColFruit) 销毁水果(这里可以和生成水果一起做一个对象池处理),合成新水果,,并进行特效、声音和分数的相关处理

		/// <summary>
		/// 可合成水果的委托
		/// </summary>
		/// <param name="col"></param>
		/// <param name="toColFruit"></param>
		void OnCompoundAction(Collision2D col, Fruit toColFruit)
		{

			if (col.transform.position.y < toColFruit.transform.position.y)  // 同一水平位置Y比较难判断,分开判断
			{
				CompoundHandle(col, toColFruit);
			}
			else if (col.transform.position.y == toColFruit.transform.position.y)
			{
				if (col.transform.position.x < toColFruit.transform.position.x)
				{
					CompoundHandle(col, toColFruit);
				}

			}

		}

		/// <summary>
		/// 满足合成条件的处理
		/// </summary>
		/// <param name="col"></param>
		/// <param name="toColFruit"></param>
		void CompoundHandle(Collision2D col, Fruit toColFruit)
		{
			Debug.Log($"融合 {toColFruit.FruitType.ToString()} ============");
			int colFruitId = (int)toColFruit.FruitType;
			FruitSeriesType compoundFruitType = (FruitSeriesType)(colFruitId + 1);

			SpawnFruit(compoundFruitType,
				col.contacts[0].point, // 碰撞点
				m_SpawnFruitPosTrans,
				true).Rigidbody2D.angularVelocity = toColFruit.Rigidbody2D.angularVelocity; // 附上角速度

			// 当前是直接销毁,好一点的方式的建立对象池,循环利用
			GameObject.Destroy(col.gameObject);
			GameObject.Destroy(toColFruit.gameObject);


			m_EffectManager.ShowEffect(new Color(Random.Range(0, 1.0f), Random.Range(0, 1.0f), Random.Range(0, 1.0f), 1),
				col.contacts[0].point);// 碰撞点

			m_AudioManager.PlayBombSound();

			m_ScoreManager.Score += colFruitId * GameConfig.COMPOUND_FRUIT_BASE_SCORE;

		}

26、在工程中添加脚本 Effect ,主要功能是:水果合成的特效动画(这里只做了特效颜色透明度的处理,大家可以根据需要设置大小等的动画变化)

27、Effect脚本主要函数: Show(Color32 color, Action<Effect> showAnimationEndAction)设置特效颜色和特效结束事件委托,EffectAnimation(Color color, Action<Effect> showAnimationEndAction) 协程实现特效动画,并且触发特效结束委托事件

		public void Show(Color32 color, Action<Effect> showAnimationEndAction)
		{
			StartCoroutine(EffectAnimation(color, showAnimationEndAction));
		}

		IEnumerator EffectAnimation(Color color, Action<Effect> showAnimationEndAction)
		{
			m_ColorValue = 0;
			color.a = 0;
			SpriteRenderer.color = color;
			while (true)
			{
				// lerp 匀速插值处理
				m_ColorValue += 1.0f / GameConfig.EFFECT_ANIMATION_SPEED * Time.deltaTime;
				color.a = Mathf.Lerp(color.a, 1, m_ColorValue);
				SpriteRenderer.color = color;
				if ((1 - color.a <= 0.05f))
				{
					color.a = 1;
					SpriteRenderer.color = color;

					break;
				}


				yield return new WaitForEndOfFrame();
			}
			m_ColorValue = 0;
			while (true)
			{
				// lerp 匀速插值处理
				m_ColorValue += 1.0f / GameConfig.EFFECT_ANIMATION_SPEED * Time.deltaTime;
				color.a = Mathf.Lerp(color.a, 0, m_ColorValue);
				SpriteRenderer.color = color;
				if ((color.a - 0) <= 0.05f)
				{
					color.a =0;
					SpriteRenderer.color = color;
					break;
				}


				yield return new WaitForEndOfFrame();
			}


            if (showAnimationEndAction!=null)
            {
				showAnimationEndAction.Invoke(this);
            }
		}

28、在工程中添加 EffectManager 脚本,主要功能是:特效预制体的获取,和特效的显示,这里特效的生成使用了简单的对象池作为处理

29、EffectManager 脚本主要函数:Init() 初始化获取Effect预制体,ShowEffect(Color32 color, Vector3 pos) 设置位置和颜色,显示特效,GetEffect() 使用简单对象池获取特效,OnEffectShowEndAction(Effect effect)特效动画结束回收对象到对象池

       public void Init(Transform worldTrans, Transform uiTrans, params object[] manager)
        {
            m_EffectPrefab = Resources.Load<GameObject>(ResPathDefine.EFFECT_PREFAB_PATH);
            if (m_EffectPrefab==null)
            {
                Debug.LogError(GetType()+ "/Init()/m_EffectPrefab Loaded is null, path = " + ResPathDefine.EFFECT_PREFAB_PATH);
            }

            m_IdleEffectQueue = new Queue<Effect>();
        }


        public void ShowEffect(Color32 color, Vector3 pos)
        {
            Effect effect = GetEffect();
            effect.transform.position = pos;
            effect.transform.rotation = Quaternion.Euler(Vector3.forward * Random.Range(-180, 180));
            effect.Show(color, OnEffectShowEndAction);
        }

        /// <summary>
        /// 简单对象池获取特效
        /// </summary>
        /// <returns></returns>
        private Effect GetEffect() {
            if (m_IdleEffectQueue.Count > 0)
            {
                Effect effect = m_IdleEffectQueue.Dequeue();
                effect.gameObject.SetActive(true);
                return effect;
            }
            else {
                GameObject go = GameObject.Instantiate(m_EffectPrefab);
                return go.AddComponent<Effect>();
                 
            }
        }

        /// <summary>
        /// 特效动画结束,回收特效到对象池
        /// </summary>
        /// <param name="effect"></param>
        private void OnEffectShowEndAction(Effect effect) {
            effect.gameObject.SetActive(false);
            m_IdleEffectQueue.Enqueue(effect);
        }

30、这里简单说一下 IManager 接口类,是为了统一各个管理类中函数的定义规范

public interface IManager 
	{
		// 初始化
		void Init(Transform worldTrans, Transform uiTrans, params object[] manager);
		// 帧更新
		void Update();
		// 销毁时调用(主要是释放数据等使用)
		void Destroy();
	}

31、在工程中添加 AudioManager 脚本,主要功能是:加载音频,添加AudioSource(播放声音必须的),和播放对应音效

32、AudioManager 脚本主要函数:Init() 加载需要的音频,添加AudioSource ,PlaySpawnSound()/PlayBombSound() 播放水果生成/合成音效

        public void Init(Transform worldTrans, Transform uiTrans, params object[] manager)
        {
            m_SpawnSound = Resources.Load<AudioClip>(ResPathDefine.AUDIO_SPAWN_PATH);
            m_BombSound = Resources.Load<AudioClip>(ResPathDefine.AUDIO_BOMB_PATH);

            GameObject audiosSourceGO = worldTrans.Find(GameObjectPathInSceneDefine.AUDIO_SOURCE_PATH).gameObject;
            if (audiosSourceGO == null)
            {
                Debug.LogError(GetType() + "/AudioSource()/ audiosSourceGO is null , path = " + GameObjectPathInSceneDefine.AUDIO_SOURCE_PATH);
            }
            else
            {
                m_AudioSource = audiosSourceGO.AddComponent<AudioSource>();

            }
        }


        public void PlaySpawnSound()
        {
            m_AudioSource.PlayOneShot(m_SpawnSound);
        }

        public void PlayBombSound()
        {
            m_AudioSource.PlayOneShot(m_BombSound);
        }

33、在工程中添加 LineManager脚本,主要功能是,进行边界的左右适应屏幕,瞄准线的显示隐藏、位置更新,和游戏结束线的显示和隐藏

34、LineManager脚本主要函数:BorderLineSimpleAdaptScreen()简单边界适配屏幕,UpdateWarninglineHandle()警告线的显示隐藏处理,UpdateAimlineHandle()瞄准线的显示隐藏,移动位置处理

        /// <summary>
        /// 简单边界适配屏幕
        /// </summary>
        void BorderLineSimpleAdaptScreen()
        {
            // 左边缘屏幕适配
            float left_X = Tools.ScreenPosToWorldPos(m_BorderLine_Left, m_MainCamera, Vector2.zero).x; 
            m_BorderLine_Left.position = new Vector3(left_X, m_BorderLine_Left.position.y, m_BorderLine_Left.position.z);

            // 右边缘屏幕适配
            float Right_X = Tools.ScreenPosToWorldPos(m_BorderLine_Right, m_MainCamera, Vector2.right * Screen.width).x;
            m_BorderLine_Right.position = new Vector3(Right_X, m_BorderLine_Right.position.y, m_BorderLine_Right.position.z);

        }

        /// <summary>
        /// 警告线的显示隐藏处理
        /// </summary>
        void UpdateWarninglineHandle() {
            m_Warningline.gameObject.SetActive(GameManager.Instance.GameOverWarning);
        }

        /// <summary>
        /// 瞄准线的显示隐藏,移动处理
        /// </summary>
        void UpdateAimlineHandle() {
            if (Input.GetMouseButtonDown(0))
            {
                if (m_FruitManager != null && m_FruitManager.CurFruit != null)
                {
                    m_Aimline.gameObject.SetActive(true);

                }

            }
            else if (Input.GetMouseButton(0))
            {
                UpdateAimlinePos();
            }
            else if (Input.GetMouseButtonUp(0))
            {
                m_Aimline.gameObject.SetActive(false);

            }
        }

        /// <summary>
        /// 瞄准线的移动处理
        /// </summary>
        void UpdateAimlinePos() {
            if (m_FruitManager!=null && m_FruitManager.CurFruit!=null)
            {
                m_Aimline.transform.position = new Vector3(m_FruitManager.CurFruit.transform.position.x,
               m_Aimline.transform.position.y,
               m_Aimline.transform.position.z);
            }
           
        }

35、在工程中添加 ScoreManager 脚本,主要功能是分数的增加统计,和分数变化的事件处理

36、在工程中添加 UIManager 脚本,主要的功能是:实时显示分数,游戏结束的时候显示结束面板,并且添加重新开始游戏的按钮事件,重新加载场景,重新开始游戏

37、UIManager 脚本主要函数:OnGameOver() 游戏结束时,显示结束面板,OnScroeValueChanged(int score) 更新游戏得分,OnRestartButton()游戏重新开始

        public void OnGameOver() {
            m_GameOverImageGo.SetActive(true);
        }

        private void OnScroeValueChanged(int score) {
            m_ScoreText.text = score.ToString();
        }

        private void OnRestartButton() {
            SceneManager.LoadScene(SceneManager.GetActiveScene().name);
        }

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

39、GameManager 脚本主要函数:Awake(MonoBehaviour mono)/Start()/Init()/Update()/Destroy() 对应各个Manager 的 new 对象,以及初始化Init,Update 、和Destroy,FindGameObjectInScene()获取场景中相关游戏物体或者 UI

        public void Awake(MonoBehaviour mono) {
            m_Mono = mono;
            m_FruitManager = new FruitManager();
            m_LineManager = new LineManager();
            m_EffectManager = new EffectManager();
            m_AudioManager = new AudioManager();
            m_ScoreManager = new ScoreManager();
            m_UIManager = new UIManager();
        }

		public void Start()
		{
            FindGameObjectInScene();
            Init(m_WorldTrans, m_UITrans,this);

        }

        public void Init(Transform worldTrans, Transform uiTrans, params object[] manager)
        {
            m_IsGameOverWarning = false;
            m_IsGameOVer = false;
            m_ScoreManager.Init(worldTrans, uiTrans);
            m_EffectManager.Init(worldTrans, uiTrans);
            m_AudioManager.Init(worldTrans, uiTrans);
            m_FruitManager.Init(worldTrans, uiTrans, m_Mono, m_EffectManager,m_AudioManager, m_ScoreManager);
            m_LineManager.Init(worldTrans, uiTrans, m_FruitManager);            
            m_UIManager.Init(worldTrans, uiTrans, m_ScoreManager);
        }

        public void Update()
        {
            if (m_IsGameOVer==true)
            {
                return;
            }
            m_FruitManager.Update();
            m_LineManager.Update();
            m_EffectManager.Update();
            m_AudioManager.Update();
            m_ScoreManager.Update();
            m_UIManager.Update();

            UpdateJudgeGaveOverAndWarning();
        }

        public void Destroy()
        {
            m_FruitManager.Destroy();
            m_LineManager.Destroy();
            m_EffectManager.Destroy();
            m_AudioManager.Destroy();
            m_ScoreManager.Destroy();
            m_UIManager.Destroy();

            m_WorldTrans = null;
            m_UITrans = null;

            m_Warningline = null;
            m_SpawnFruitPosTrans = null;

            m_IsGameOverWarning = false;
            m_IsGameOVer = false;
        }

        void FindGameObjectInScene() {
            m_WorldTrans = GameObject.Find(GameObjectPathInSceneDefine.WORLD_PATH).transform;
            m_UITrans = GameObject.Find(GameObjectPathInSceneDefine.UI_PATH).transform;

            m_Warningline = m_WorldTrans.Find(GameObjectPathInSceneDefine.WARNING_LINE_PATH);
            m_SpawnFruitPosTrans = m_WorldTrans.Find(GameObjectPathInSceneDefine.SPAWN_FRUIT_POS_TRANS_PATH);
        }

40、GameManager 脚本主要函数:UpdateJudgeGaveOverAndWarning()/IsGameOverWarning()/IsJudgeGameOver() 判断游戏是否发出警告,或者游戏结束,OnGameOver() 游戏结束事件

        void UpdateJudgeGaveOverAndWarning() {
            if (IsGameOverWarning() == true)
            {
                m_WarningTimer += Time.deltaTime;
                if (m_WarningTimer >= GameConfig.JUDGE_GAME_OVER_WARNING_TIME_LENGHT)
                {
                    m_Warningline.gameObject.SetActive(true);

                }

                if (IsJudgeGameOver() == true)
                {
                    m_OverTimer += Time.deltaTime;
                    if (m_OverTimer >= GameConfig.JUDGE_GAME_OVER_TIME_LENGHT)
                    {
                        m_IsGameOVer = true;

                        if (m_FruitManager.CurFruit != null)
                        {
                            m_FruitManager.CurFruit.DisableFruit();
                        }

                        OnGameOver();
                    }
                }
                else
                {
                    m_OverTimer = 0;
                }
            }
            else
            {
                m_Warningline.gameObject.SetActive(false);
                m_OverTimer = 0;
                m_WarningTimer = 0;
            }
        }

        bool IsGameOverWarning()
        {
            Fruit fruit;
            foreach (Transform item in m_SpawnFruitPosTrans)
            {
                if (item.gameObject.activeSelf == true)
                {
                    fruit = item.GetComponent<Fruit>();
                    if (fruit != null)
                    {
                        if (fruit.CircleCollider2D.enabled == true && fruit != m_FruitManager.CurFruit)
                        {
                            if (m_Warningline.transform.position.y - (fruit.transform.position.y + fruit.CircleCollider2D.radius) < GameConfig.GAME_OVER_WARNING_LINE_DISTANCE)
                            {
                                return true;
                            }
                        }
                    }
                }

            }

            return false;
        }

        bool IsJudgeGameOver()
        {
            Fruit fruit;
            foreach (Transform item in m_SpawnFruitPosTrans)
            {
                if (item.gameObject.activeSelf == true)
                {
                    fruit = item.GetComponent<Fruit>();
                    if (fruit != null)
                    {
                        if (fruit.CircleCollider2D.enabled == true)
                        {
                            if (m_Warningline.transform.position.y - (fruit.transform.position.y + fruit.CircleCollider2D.radius) <= 0)
                            {
                                return true;
                            }
                        }
                    }
                }

            }

            return false;
        }

        void OnGameOver() {
            m_FruitManager.OnGameOver();
            m_UIManager.OnGameOver();
        }

41、Tools 工具类,把屏幕坐标转为世界坐标

	public class 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);

		}
	}

42、Enum 管理各种枚举,这里主要时水果类型的枚举

	/// <summary>
	/// 水果类型
	/// 注意:其中顺序是合成的顺序
	/// </summary>
	public enum FruitSeriesType
	{

		Mangosteen = 0,		// 山竹
		Apple = 1,			// 苹果
		Orange = 2,			// 桔子
		Lemon = 3,			// 柠檬
		Kiwi = 4,           // 猕猴桃
		Tomato = 5,			// 西红柿
		Peach = 6,			// 桃子
		Pineapple = 7,		// 菠萝
		Coco = 8,           // 椰子
		Watermelon = 9,		// 西瓜
		BigWatermelon = 10,	// 大西瓜

		SUM_COUNT = 11,		//总数(计数使用)
	}

43、GameConfig游戏配置类,游戏不同配置,直接影响游戏的不同体验

	public class GameConfig 
	{
		// 生成水果的比例(用来控制游戏中生成水果的整体比例)
		public const float FRUIT_SCALE = 0.80f;

		// 每次生成水果的间隔时间
		public const float FRUIT_SPAWN_INTERVAL_TIME = 0.5f;

		// 水果触及警告线持续多少秒,算游戏结束
		public const float JUDGE_GAME_OVER_TIME_LENGHT = 3;

		// 水果满足条件差不多到警告线前,持续多少秒,发出警告信息
		public const float JUDGE_GAME_OVER_WARNING_TIME_LENGHT = 0.5f;

		// 某个水果距离警告线多远,触发警告条件
		public const float GAME_OVER_WARNING_LINE_DISTANCE = 1.5f;

		// 特效的动画速度
		public const float EFFECT_ANIMATION_SPEED = 5f;

		// 水果合成的基础分 (实际计算规则是 : 水果的类型 * 该 base score)
		public const int COMPOUND_FRUIT_BASE_SCORE = 10;

	}

44、GameObjectPathInSceneDefine 场景中游戏物体路径定义类,统一管理景中游戏物体路径

	/// <summary>
	/// 场景中游戏物体路径定义类,统一管理景中游戏物体路径
	/// </summary>
	public class GameObjectPathInSceneDefine 
	{
		public const string WORLD_PATH = "World";
		public const string UI_PATH = "UI";

		public const string UI_SCORE_TEXT_PATH = "Canvas/ScoreText";
		public const string UI_GAME_OVER_IMAGE_PATH = "Canvas/GameOverImage";
		public const string UI_RESTART_GAME_BUTTON_PATH = "Canvas/GameOverImage/RestartGameButton";

		public const string BORDERLINE_DOWN_PATH = "BorderLine_Down";
		public const string BORDERLINE_LEFT_PATH = "BorderLine_Left";
		public const string BORDERLINE_RIGHT_PATH = "BorderLine_Right";
		public const string AIM_LINE_PATH = "AimLine";
		public const string WARNING_LINE_PATH = "Warningline";

		public const string SPAWN_FRUIT_POS_TRANS_PATH = "SpawnFruitPos";
		public const string Main_Camera_TRANS_PATH = "Main Camera";

		public const string AUDIO_SOURCE_PATH = "AudioSource";
	}

45、ResPathDefine 预制体路径定义类,统一管理预制体路径

	/// <summary>
	/// 预制体路径定义类,统一管理预制体路径
	/// </summary>
	public class ResPathDefine 
	{
		// 水果预制体文件夹路径
		public const string FRUITS_PREFAB_BASE_PATH = "Prefabs/Fruits/";

		public const string EFFECT_PREFAB_PATH = "Prefabs/Effect";


		public const string AUDIO_BOMB_PATH = "Audios/bomb";
		public const string AUDIO_SPAWN_PATH = "Audios/spawn";
	}

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

    /// <summary>
    /// 整个游戏入口
    /// </summary>
	public class GameStart : MonoBehaviour
	{
        private void Awake()
        {
            GameManager.Instance.Awake(this);
        }

        // Start is called before the first frame update
        void Start()
		{
            GameManager.Instance.Start();
        }

		// Update is called once per frame
		void Update()
		{
            GameManager.Instance.Update();
        }

        private void OnDestroy()
        {
            GameManager.Instance.Destroy();
        }

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

48、运行场景,就会自动随机生成水果,并更新分数

八、工程源码地址

github 地址:GitHub - XANkui/UnityMiniGameParadise: Unity 游戏开发集合代码集

的 MGP_004CompoundBigWatermelon 工程

九、延伸扩展

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

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

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

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

4、Effect 可以设置一个World 游戏父物体,便于管理,游戏场景显得不是那么乱

5、可以重构水果生成,水果生成和销毁没有进行对象池管理,这里可以做性能优化;

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

7、这里的特效Effect 颜色是随机的,可以对应添加合成水果颜色更新Effect 颜色和大小,或者设置不同特效Effect;

8、游戏判断结束的条件,可能游戏存在bug,大家实际使用中,可以优化一下有些结束条件的判断

9、等等

猜你喜欢

转载自blog.csdn.net/u014361280/article/details/122519355