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

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

目录

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

一、简单介绍

二、CutFruit (切水果(水果忍者)) 游戏内容与操作

三、相关说明

四、游戏代码框架

五、知识点

六、游戏效果预览​

七、实现步骤

八、工程源码地址

扫描二维码关注公众号,回复: 13721802 查看本文章

九、延伸扩展


一、简单介绍

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

本节介绍,CutFruit (切水果(水果忍者))休闲小游戏快速实现的方法,希望能帮到你,若有不对,请留言。

这是一个 3D 游戏,主要是使用精灵图(水果特效等)、3D 重力、3D 碰撞体,TrailRenderer (切刀效果) 实现。

二、CutFruit (切水果(水果忍者)) 游戏内容与操作

1、游戏开始,底部会自动生成向上运动的水果

2、按下鼠标,移动就会生成切水果的刀锋

3、鼠标刀锋碰到水果,水果就会被切开

4、松开鼠标,切割刀锋消失

5、当切到水果,会对应加分,切刀炸弹,会对应失去生命

6、生命没有了,则游戏结束

三、相关说明

1、这里底部生成水果位置,做了屏幕适配,x 方向是:宽度的20% - 80%,y 方向是:屏幕向下高度的 20 %

2、随机生成水果和炸弹的规则是:随机某个范围,指定某个数是炸弹,其他是随机水果

3、这里 3D 水果和炸弹,使用 Collider 3D 碰撞体,但是使用了的是 Trigger 效果,Knife 可以喝水果碰撞,触发事件,但是不会发生实际碰撞效果

4、这里水果或者水果碎片回收,使用的是 OnBecameInvisible() 函数(编辑状态下,包括编辑Scene 的窗口,不仅仅是游戏Game窗口),既是:水果出相机视野,则进行,对应的回收,对象池中继续使用,节约性能

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

6、这里值得注意的是,水果的枚举的作用:1)枚举值是随机生成水果的随机值对应;2)枚举是对应的预制体名称一致;3)枚举是对饮水果的脚本名称一致

四、游戏代码框架

五、知识点

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

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

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

4、简单的对象池管理

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

6、简单UGUI的使用

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

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、然后把对应的资源做成预制体

3、这个说明一下 Knife 的制作过程:1)图片是刀锋,可以替换图片更换刀锋;2) 创建一个拖尾的材质Trail,对应shader 为 Particles/Additive,并赋值对应刀锋图;3)创建一个 Sphere ,改名为Knife,添加 TrailRenderer 组件,并赋值 Trail 材质,TrailRenderer  Time 适当改小为 0.1,Knife scale 适当改小为 0.2(可参照实际水果大小调整)

4、取消 Knife 的MeshRenderer 渲染,保留 Collier ,用于与水果触发碰撞事件

5、其中水果预制体,添加 Rigibody,和 MeshCollider ,MeshCollider  勾选 Convex 和 Is Trigger(碰撞的时候只触发碰撞事件,不发生实际碰撞效果),Bomb 类似操作

6、BombEffect 和 Splash 是 Spriterenderer 图片动画来的

7、在场景中,创建一个 GameObject,改名为 World(pos(0,0,0)),把默认的Main Camera(位置修改为(0,0,0)) 和 Direction Light 至于World 下面,类似依次创建 GameObject,改名为 SpawnFruitPos、SpawnSplashPos、SpawnKnifePos、SpawnBombPos、SpawnBombEffectPos,位置都为(0,0,0),功能如其名,用来把对应生成的游戏物体统一管理

8、创建一个 GameObject,改名为UI(Pos(0,0,0)),添加一个Canvas (自动添加EventSystem),设置 Canvas Scaler 的 UI Scale Mode 为 Scale with Screen Size ,Reference Resolution 为  1080 x 1920 ,Match 滑动到 Height

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

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

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

12、修改屏幕为 1080x1920 ,最终 UI 效果如下

13、在工程中添加 Enum 脚本,管理所有枚举,目前只是水果类型,和水果破碎类型枚举

	/// <summary>
	/// 水果类型
	/// </summary>
	public enum FruitType { 
		Apple = 0,
		Lemon,
		Watermelon,

		SUM_COUNT, // 水果类型总数
	}

	/// <summary>
	/// 破碎水果类型
	/// </summary>
	public enum FruitBrokenType { 
		Apple_Broken = 0,
		Lemon_Broken,
		Watermelon_Broken,


		SUM_COUNT,  // 破碎水果类型总数
	}

14、在工程中添加 GameConfig 脚本,管理游戏中的一些数据

	/// <summary>
	/// 游戏配置数据
	/// </summary>
	public class GameConfig 
	{
		public const string NAME_SPACE_NAME = "MGP_005CutFruit.";

		// Knife 预制体名称(用于碰撞触发判断使用)
		public const string KNIFE_NAME = "Knife";

		// 水果、炸弹、Knife 等游戏生成的统一深度距离
		public const float GAME_OBJECT_Z_VALUE = 5;	

		// 底部间隔几秒生成物体
		public const float BOTTOM_SPAWN_INTERVAL_TIME = 2;	

		// 游戏生命(可切破几次炸弹)
		public const int GAME_LIFE_LENGTH = 3;
		// 游戏剩余几条命结束
		public const int REMAIN_LIFE_IS_GAME_OVER = 0;

		// 水果 炸弹生成向上的最小最大速度
		public const float FRUIT_UP_Y_VELOSITY_MIN_VALUE = 8;
		public const float FRUIT_UP_Y_VELOSITY_MAX_VALUE = 10;

		/// <summary>
		/// 随机生成水果炸弹的数值范围
		/// 随机数多少是炸弹
		/// </summary>
		public const int FRUIT_RANDOM_MIN_VALUE = 0;
		public const int FRUIT_RANDOM_MAX_VALUE = 15;
		public const int BOMB_RANDOM_VALUE = 10;

		/// <summary>
		/// 切刀不同水果的得分
		/// </summary>
		public const int FRUIT_APPLE_SCORE = 10;
		public const int FRUIT_LEMON_SCORE = 20;
		public const int FRUIT_WATERMELON_SCORE = 30;

		//切到炸弹失去的生命值
		public const int BOMB_REDUCE_LIFE = 1;
	}

15、在工程中添加 GameObjectPathInSceneDefine 脚本,定义场景中的游戏物体路径类,ResPathDefine 定义预制体路径类

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

		public const string SPAWN_FRUIT_POS_PATH = "SpawnFruitPos";
		public const string SPAWN_SPLASH_POS_PATH = "SpawnSplashPos";
		public const string SPAWN_KNIFE_POS_PATH = "SpawnKnifePos";
		public const string SPAWN_BMOB_POS_PATH = "SpawnBombPos";
		public const string SPAWN_BMOB_EFFECT_POS_PATH = "SpawnBombEffectPos";


		public const string UI_LIFE_TEXT_PATH = "Canvas/LifeText";
		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";


	}

	/// <summary>
	/// 定义预制体路径类
	/// </summary>
	public class ResPathDefine 
	{
		public const string FRUIT_PREFAB_BASE_PATH = "Prefabs/CutFruits/";

		public const string SPLASH_PREFAB_PATH = "Prefabs/Splash";
		public const string KNIFE_PREFAB_PATH = "Prefabs/Knife";
		public const string BOMB_PREFAB_PATH = "Prefabs/Bomb";
		public const string BOMB_EFFECT_PREFAB_PATH = "Prefabs/BombEffect";
	}

16、在工程中添加 BaseFruit 脚本,水果基类脚本,主要管理水果和 Knife 碰撞事件(加分,生成水果碎片,特效等),以及出相机视野外的回收处理,关键代码如下

        /// <summary>
        /// 碰撞事件
        /// </summary>
        /// <param name="other"></param>
        private void OnTriggerEnter(Collider other)
        {
            if (other.name.StartsWith( GameConfig.KNIFE_NAME))
            {
                //增加分数
                m_DataModelManager.Score.Value += Score;


                for (int i = 0; i < 2; i++)
                {
                    //产生破碎的水果
                    FruitBroken fruitBroken = SpawnBroken();
                    fruitBroken.Init(FruitBrokenType,m_FruitManager);
                    GameObject temp = fruitBroken.gameObject;
                    temp.SetActive(true);
                    temp.transform.position = transform.position;
                    temp.transform.eulerAngles = new Vector3(Random.Range(0, 360), Random.Range(0, 360), Random.Range(0, 360));
                    //-3 -2  2 3

                    // 随机水果碎片速度
                    float x, y;
                    int ranX = Random.Range(0, 2);
                    if (ranX == 0)
                    {
                        x = Random.Range(-3.0f, -2.0f);
                    }
                    else
                    {
                        x = Random.Range(2.0f, 3.0f);
                    }

                    int ranY = Random.Range(0, 2);
                    if (ranY == 0)
                    {
                        y = Random.Range(-3.0f, -2.0f);
                    }
                    else
                    {
                        y = Random.Range(2.0f, 3.0f);
                    }

                    temp.GetComponent<Rigidbody>().velocity = new Vector2(x, y);

                }

                //产生果汁特效
                Splash splash = SpawnSplash();
                splash.transform.position = transform.position;
                splash.transform.rotation = Quaternion.Euler(Vector3.forward * Random.Range(-180,180));
                splash.Show(FruitSplahColor, m_SplashManager.RecycleSplah);
                

                //隐藏水果物体
                m_FruitManager.RecycleFruit(FruitType,this);
                m_IsRecycle = false;
            }
            if (other.tag == "BottomCollider")
            {
                gameObject.SetActive(false);
            }
        }

        private void OnBecameVisible()
        {
            m_IsRecycle = true;
        }

        /// <summary>
        /// 相机视野外
        /// 回收水果
        /// </summary>
        private void OnBecameInvisible()
        {
            if (m_IsRecycle==true)
            {
                m_FruitManager.RecycleFruit(FruitType, this);
                m_IsRecycle = false;

            }
        }

17、在工程中添加Apple、Lemon、Watermelon 脚本,继承 BaseFruit ,实现抽象类,设置其对应水果类型,水果破碎类型,碎果Splash 颜色,和得分

	public class Apple : BaseFruit
	{
       

        public override FruitType FruitType => FruitType.Apple;

        public override FruitBrokenType FruitBrokenType => FruitBrokenType.Apple_Broken;

        public override Color FruitSplahColor => Color.red;

        public override int Score => GameConfig.FRUIT_APPLE_SCORE;
    }

18、在工程中添加 FruitManager 脚本,主要管理水果和水果碎片的预制体加载,以及水果和水果碎片对象池的初始化,并提供接口,从对象池中获取水果和水果碎片,和回收水果和水果碎片

        /// <summary>
        /// 获取水果碎片
        /// </summary>
        /// <param name="fruitBrokenType"></param>
        /// <returns></returns>
        public FruitBroken GetFruitBroken(FruitBrokenType fruitBrokenType)
        {
            Queue<FruitBroken> fruitBrokenQue = m_FruitBrokenObjectPoolDict[fruitBrokenType];
            if (fruitBrokenQue.Count > 0)
            {
                return fruitBrokenQue.Dequeue();
            }
            else
            {
                return InstantiateFruitBroken(fruitBrokenType);
            }
        }

        /// <summary>
        /// 回收水果
        /// </summary>
        /// <param name="fruitType"></param>
        /// <param name="baseFruit"></param>
        public void RecycleFruit(FruitType fruitType,BaseFruit baseFruit) {
            baseFruit.gameObject.SetActive(false);
            if (m_FruitObjectPoolDict!=null && m_FruitObjectPoolDict[fruitType]!=null)
            {
                m_FruitObjectPoolDict[fruitType].Enqueue(baseFruit);

            }
        }

        /// <summary>
        /// 回收水果碎片
        /// </summary>
        /// <param name="fruitBrokenType"></param>
        /// <param name="fruitBroken"></param>
        public void RecycleFruitBroken(FruitBrokenType fruitBrokenType, FruitBroken fruitBroken)
        {
            fruitBroken.gameObject.SetActive(false);
            m_FruitBrokenObjectPoolDict[fruitBrokenType].Enqueue(fruitBroken);
        }

        /// <summary>
        /// 加载预制体和预载水果到对象池中
        /// </summary>
        private void InitFruit() {

            // 加载预制体
            m_FruitPrefabsDict = new Dictionary<string, GameObject>();
            for (FruitType fruitType = FruitType.Apple; fruitType < FruitType.SUM_COUNT; fruitType++)
            {
                GameObject prefab = Resources.Load<GameObject>(ResPathDefine.FRUIT_PREFAB_BASE_PATH+fruitType.ToString());
                if (prefab == null)
                {
                    Debug.LogError(GetType() + "/InitFruit()/prefab is null, path = " + ResPathDefine.FRUIT_PREFAB_BASE_PATH + fruitType.ToString());
                }
                else {
                    m_FruitPrefabsDict.Add(fruitType.ToString(),prefab);
                }
            }

            // 加载预制体
            for (FruitBrokenType fruitBrokenType = FruitBrokenType.Apple_Broken; fruitBrokenType < FruitBrokenType.SUM_COUNT; fruitBrokenType++)
            {
                GameObject prefab = Resources.Load<GameObject>(ResPathDefine.FRUIT_PREFAB_BASE_PATH + fruitBrokenType.ToString());
                if (prefab == null)
                {
                    Debug.LogError(GetType() + "/InitFruit()/prefab is null, path = " + ResPathDefine.FRUIT_PREFAB_BASE_PATH + fruitBrokenType.ToString());
                }
                else
                {
                    m_FruitPrefabsDict.Add(fruitBrokenType.ToString(), prefab);
                }
            }

            // 预载水果
            m_FruitObjectPoolDict = new Dictionary<FruitType, Queue<BaseFruit>>();
            for (FruitType fruitType = FruitType.Apple; fruitType < FruitType.SUM_COUNT; fruitType++) {
                Queue<BaseFruit> fruits = new Queue<BaseFruit>();
                m_FruitObjectPoolDict.Add(fruitType, fruits);

                RecycleFruit(fruitType, InstantiateFruit(fruitType));
            }

            // 预载水果 Broken
            m_FruitBrokenObjectPoolDict = new Dictionary<FruitBrokenType, Queue<FruitBroken>>();
            for (FruitBrokenType fruitBrokenType = FruitBrokenType.Apple_Broken; fruitBrokenType < FruitBrokenType.SUM_COUNT; fruitBrokenType++)
            {
                Queue<FruitBroken> fruitBrokens = new Queue<FruitBroken>();
                m_FruitBrokenObjectPoolDict.Add(fruitBrokenType, fruitBrokens);

                RecycleFruitBroken(fruitBrokenType, InstantiateFruitBroken(fruitBrokenType));
            }
        }

        /// <summary>
        /// 生成指定水果对象
        /// </summary>
        /// <param name="fruitType"></param>
        /// <returns></returns>
        private BaseFruit InstantiateFruit(FruitType fruitType) {
            GameObject fruit = GameObject.Instantiate(m_FruitPrefabsDict[fruitType.ToString()], m_SpawnFruitPosTrans);
            // 反射获取脚本类型,添加脚本 (注意添加命名空间)
            Type type = Type.GetType( GameConfig.NAME_SPACE_NAME+ fruitType.ToString());
            return (fruit.AddComponent(type) as BaseFruit);
        }

        /// <summary>
        /// 生成破碎水果
        /// </summary>
        /// <param name="fruitBrokenType"></param>
        /// <returns></returns>
        private FruitBroken InstantiateFruitBroken(FruitBrokenType fruitBrokenType)
        {
            GameObject fruit = GameObject.Instantiate(m_FruitPrefabsDict[fruitBrokenType.ToString()], m_SpawnFruitPosTrans);
          
            return fruit.AddComponent<FruitBroken>();
        }

19、在工程中添加 Bomb 脚本,主要功能是 Bomb 与 Knife 的碰撞(减少生命值,以及生成特效),以及相机视野外后回收

        private void OnTriggerEnter(Collider other)
        {
            if (other.name.StartsWith( GameConfig.KNIFE_NAME))
            {
                //减少生命值
                m_DataModelManager.Life.Value -= GameConfig.BOMB_REDUCE_LIFE;

                //产生爆炸特效
                BombEffect bombEffect = m_BombEffectManager.GetBombEffect();
                bombEffect.Show(transform.position, m_BombEffectManager.RecycleBombEffect);

                //隐藏物体
                m_BmobManager.RecycleBomb(this);
            }
        }


        private void OnBecameVisible()
        {
            m_IsRecycle = true;
        }


        /// <summary>
        /// 相机是野外事件
        /// </summary>
        private void OnBecameInvisible()
        {
            if (m_IsRecycle == true)
            {
                m_BmobManager.RecycleBomb( this);
                m_IsRecycle = false;

            }
        }

20、在工程中添加 BombManager 脚本,主要管理 Bomb 预制体加载,对象池初始化,提供接口获取和回收 Bomb

        /// <summary>
        /// 获取 Bomb 
        /// </summary>
        /// <returns></returns>
        public Bomb GetBmob()
        {
            if (m_BombQueue.Count > 0)
            {
                Bomb bomb = m_BombQueue.Dequeue();
                bomb.gameObject.SetActive(true);
                return bomb;
            }
            else
            {
                return InstantiateBomb();
            }
        }

        /// <summary>
        /// 回收 Bomb 
        /// </summary>
        /// <param name="bomb"></param>
        public void RecycleBomb(Bomb bomb)
        {
            bomb.gameObject.SetActive(false);
            m_BombQueue.Enqueue(bomb);
        }

        /// <summary>
        /// 初始化
        /// </summary>
        private void InitBmob()
        {
            // 加载预制体
            m_BombPrefab = Resources.Load<GameObject>(ResPathDefine.BOMB_PREFAB_PATH);

            // 预载 Bomb
            m_BombQueue = new Queue<Bomb>();
            RecycleBomb(InstantiateBomb());
        }

        private Bomb InstantiateBomb()
        {
            GameObject go = GameObject.Instantiate(m_BombPrefab, m_SpawnBombPosTrans);

            return go.AddComponent<Bomb>();
        }

21、在工程中添加 BombEffect 脚本,主要功能是 显示特效,并且定时回收特效

		/// <summary>
		/// 显示特效
		/// </summary>
		/// <param name="pos"></param>
		/// <param name="showAnimationEndAction"></param>
		public void Show(Vector3 pos, Action<BombEffect> showAnimationEndAction)
		{
			// 赋值位置和随机旋转,并且定时回收
			transform.position = pos;
			transform.rotation = Quaternion.Euler(Vector3.forward * UnityEngine.Random.Range(-180, 180));
			StartCoroutine(Recycle(showAnimationEndAction));

		}

		/// <summary>
		/// 协程回收特效
		/// </summary>
		/// <param name="showAnimationEndAction"></param>
		/// <returns></returns>
		IEnumerator Recycle(Action<BombEffect> showAnimationEndAction) {
			yield return new WaitForSeconds(m_AnimationLength);
            if (showAnimationEndAction!=null)
            {
				showAnimationEndAction.Invoke(this);

			}
		}

22、在工程中添加 BombEffectManager 脚本,主要管理 Bomb 预制体加载,对象池初始化,提供接口获取和回收 BombEffect

        /// <summary>
        /// 获取爆炸特效
        /// </summary>
        /// <returns></returns>
        public BombEffect GetBombEffect()
        {
            if (m_BombEffectQueue.Count > 0)
            {
                BombEffect bombEffect = m_BombEffectQueue.Dequeue();
                bombEffect.gameObject.SetActive(true);
                return bombEffect;
            }
            else
            {
                return InstantiateBombEffect();
            }
        }

        /// <summary>
        /// 回收爆炸特效
        /// </summary>
        /// <param name="bombEffect"></param>
        public void RecycleBombEffect(BombEffect bombEffect)
        {
            bombEffect.gameObject.SetActive(false);
            m_BombEffectQueue.Enqueue(bombEffect);
        }

        /// <summary>
        /// 初始化特效
        /// </summary>
        private void InitBombEffect()
        {
            // 加载预制体
            m_BombEffectPrefab = Resources.Load<GameObject>(ResPathDefine.BOMB_EFFECT_PREFAB_PATH);

            // 预载 BombEffect
            m_BombEffectQueue = new Queue<BombEffect>();
            RecycleBombEffect(InstantiateBombEffect());
        }

        private BombEffect InstantiateBombEffect()
        {
            GameObject go = GameObject.Instantiate(m_BombEffectPrefab, m_SpawnBombEffectPosTrans);

            return go.AddComponent<BombEffect>();
        }

23、在工程中添加 Splash 脚本,主要功能是 显示 Splash ,并且定时回收 Splash

		/// <summary>
		/// 显示特效
		/// </summary>
		/// <param name="color"></param>
		/// <param name="showAnimationEndAction"></param>
		public void Show(Color32 color, Action<Splash> showAnimationEndAction)
		{
			StartCoroutine(EffectAnimation(color, showAnimationEndAction));
		}

		/// <summary>
		/// 特效动画
		/// </summary>
		/// <param name="color"></param>
		/// <param name="showAnimationEndAction"></param>
		/// <returns></returns>
		IEnumerator EffectAnimation(Color color, Action<Splash> showAnimationEndAction)
		{
			m_ColorValue = 0;
			color.a = 0;
			SpriteRenderer.color = color;
			while (true)
			{
				// lerp 匀速插值处理
				m_ColorValue += 1.0f / 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 / 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);
			}
		}

24、在工程中添加 SplashManager 脚本,主要管理 Bomb 预制体加载,对象池初始化,提供接口获取和回收 Splash

        /// <summary>
        /// 获取 Splash
        /// </summary>
        /// <returns></returns>
        public Splash GetSplash()
        {
            if (m_SplashQueue.Count > 0)
            {
                Splash splash = m_SplashQueue.Dequeue();
                splash.gameObject.SetActive(true);
                return splash;
            }
            else
            {
                return InstantiateSplash();
            }
        }

        /// <summary>
        /// 回收 Splash
        /// </summary>
        /// <param name="splash"></param>
        public void RecycleSplah(Splash splash)
        {
            splash.gameObject.SetActive(false);
            m_SplashQueue.Enqueue(splash);
        }

        /// <summary>
        /// 初始化 SPlash
        /// </summary>
        private void InitSplash()
        {
            // 加载预制体
            m_SplashPrefab = Resources.Load<GameObject>(ResPathDefine.SPLASH_PREFAB_PATH);

            // 预载Splash
            m_SplashQueue = new Queue<Splash>();
            RecycleSplah(InstantiateSplash());
        }

        private Splash InstantiateSplash()
        {
            GameObject go = GameObject.Instantiate(m_SplashPrefab, m_SpawnSplashPosTrans);

            return go.AddComponent<Splash>();
        }

25、在工程中添加 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);

		}
	}

26、在工程中添加 KnifeManager 脚本,主要管理 Knife 预制体加载,监控鼠标按下情况,进行刀锋切水果功能

        /// <summary>
        /// 加载预制体
        /// </summary>
        void LoadPrefab() {
            m_KnifePrefab = Resources.Load<GameObject>(ResPathDefine.KNIFE_PREFAB_PATH);
            if (m_KnifePrefab == null)
            {
                Debug.LogError(GetType() + "/LoadPrefab()/ prefab  is  null, path = " + ResPathDefine.KNIFE_PREFAB_PATH);
            }
            else {
                m_KnifeTrans = GameObject.Instantiate(m_KnifePrefab,m_SpawnKnifePosTrans).transform;
                m_KnifeTrans.position = Vector3.forward * GameConfig.GAME_OBJECT_Z_VALUE;
                m_KnifeTrans.gameObject.SetActive(false);
            }
        }

        /// <summary>
        /// 游戏未结束时,鼠标按下更新刀锋
        /// </summary>
        void UpdateShowKnife() {
            if (m_KnifeTrans!=null && m_IsGameOver==false)
            {
                if (Input.GetMouseButtonDown(0))
                {
                    m_KnifeTrans.position = Tools.ScreenPosToWorldPos(m_KnifeTrans, Camera.main, Input.mousePosition);
                    m_KnifeTrans.gameObject.SetActive(true);
                }
                else if (Input.GetMouseButton(0))
                {
                    m_KnifeTrans.position = Tools.ScreenPosToWorldPos(m_KnifeTrans, Camera.main, Input.mousePosition);
                }
                else if (Input.GetMouseButtonUp(0))
                {
                    m_KnifeTrans.gameObject.SetActive(false);
                }
            }
            
        }

27、在工程中添加 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;
	}

28、在工程中添加 DataModelManager 脚本,主要管理 分数Score 和 生命Life

        public Model Score => m_Scroe;
        public Model Life => m_Life;

        public void Init(Transform rootTrans, params object[] manager)
        {
            m_Scroe = new Model();
            m_Scroe.Value = 0;
            m_Life = new Model();
            m_Life.Value = GameConfig.GAME_LIFE_LENGTH;
        }

29、在工程中添加 UIManager 脚本,主要管理 UI 相关获取,以及数据显示,游戏结束,按钮事件重新加载游戏

        private Text m_LifeText;
        private Text m_ScoreText;
        private GameObject m_GameOverImageGo;
        private Button m_RestartGameButton;

        private DataModelManager m_DataModelManager;
        public void Init(Transform rootTrans, params object[] managers)
        {
            m_LifeText = rootTrans.Find(GameObjectPathInSceneDefine.UI_LIFE_TEXT_PATH).GetComponent<Text>();
            m_ScoreText = rootTrans.Find(GameObjectPathInSceneDefine.UI_SCORE_TEXT_PATH).GetComponent<Text>();
            m_GameOverImageGo = rootTrans.Find(GameObjectPathInSceneDefine.UI_GAME_OVER_IMAGE_PATH).gameObject;
            m_RestartGameButton = rootTrans.Find(GameObjectPathInSceneDefine.UI_RESTART_GAME_BUTTON_PATH).GetComponent<Button>();

            m_DataModelManager = managers[0] as DataModelManager;

            m_GameOverImageGo.SetActive(false);
            m_LifeText.text = m_DataModelManager.Life.Value.ToString();
            m_ScoreText.text = m_DataModelManager.Score.Value.ToString();
            m_DataModelManager.Life.OnValueChanged += OnLifeValueChanged;
            m_DataModelManager.Score.OnValueChanged += OnScroeValueChanged;
            m_RestartGameButton.onClick.AddListener(OnRestartButton);
        }

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

        private void OnLifeValueChanged(int life)
        {
            m_LifeText.text = life.ToString();
        }

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

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

30、在工程中添加 GameManager 脚本(单例脚本),主要功能:1)获取场景中相关游戏物体或者 UI,2)new 相关的 Manager 管理类,初始化Init,Update 、和Destroy;3)定时生成水果和炸弹;4)判断游戏是否结束;

        /// <summary>
        /// 底部生成
        /// </summary>
        private void BottomSpawnFruitAndBomb()
        {
            GameObject go = null;
            // 随机数,判断生成水果还是炸弹
            int ran = Random.Range(GameConfig.FRUIT_RANDOM_MIN_VALUE, GameConfig.FRUIT_RANDOM_MAX_VALUE);
            if (ran != GameConfig.BOMB_RANDOM_VALUE)
            {
                BaseFruit fruit = m_FruitManager.GetRandomFruit();
                fruit.Init(m_FruitManager, m_SplashManager, m_DataModelManager);
                go = fruit.gameObject;
            }
            else
            {
                Bomb bomb = m_BmobManager.GetBmob();
                bomb.Init(m_BmobManager,m_BombEffectManager,m_DataModelManager);
                go = bomb.gameObject;
            }

            
            // 生成物体的位置和,向上速度
            go.transform.position = new Vector3(Random.Range(m_Bottom_Spawn_X_Min, m_Bottom_Spawn_X_Max), m_Bottom_Spawn_Y, GameConfig.GAME_OBJECT_Z_VALUE);
            go.transform.rotation = Quaternion.identity;
            go.GetComponent<Rigidbody>().velocity = new Vector2(0, Random.Range(GameConfig.FRUIT_UP_Y_VELOSITY_MIN_VALUE,
                GameConfig.FRUIT_UP_Y_VELOSITY_MAX_VALUE));

        }

        /// <summary>
        /// 判断游戏是否结束
        /// </summary>
        /// <param name="life"></param>
        void JudageGameOverByLife(int life) {
            if (life == GameConfig.REMAIN_LIFE_IS_GAME_OVER)
            {
                OnGameOver();
            }
        }

        /// <summary>
        /// 结束的操作
        /// </summary>
        void OnGameOver() {
            m_IsGameOver = true;
            m_UIManager.OnGameOver();
            m_KnifeManager.OnGameOver();
        }

31、在工程中添加 GameStart 脚本,整个游戏的入口,管理对应 GameManager 的 Awake(),Start(),Update(),OnDestroy() 对应函数功能

    /// <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();
        }
     }

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

34、运行游戏,自动生水果,按下鼠标,既可以切割水果了

八、工程源码地址

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

的 MGP_005CutFruit 工程

九、延伸扩展

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

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

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

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

4、Effect 可以设置不同类型的的特效,或者3D 粒子特效

5、特殊水果特殊事件,冰冻,火烧等;

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

7、炸弹的特效,可以进行设置不同炸弹,不同特效;

8、水果生成只设置底部生成,可以添加两侧生成也生成水果炸弹等

9、可以是设置,不同波数的水果数量,和间歇频次,定时游戏时间

10、音乐音效也可以对应添加上,效果更好

11、等等
 

猜你喜欢

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