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
13、Resources.Load
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, ActiononCompoundAction) { 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 /// /// 碰撞检测函数 /// /// private void OnCollisionEnter2D(Collision2D collision) { // 不是大西瓜,则合成 if (this.FruitType != FruitSeriesType.BigWatermelon) { // 判断与之相碰的也是水果 Fruit otherRuit = collision.gameObject.GetComponent(); 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()监控鼠标情况,进行水果移动和释放
////// /// 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(path); if (fruit == null) { Debug.LogError(GetType()+ "/LoadFruitsPrefab()/fruit is null, path = "+ path); } else { m_FuritPrefabList.Add(fruit); } } } /// /// 监控鼠标情况,进行水果移动和释放 /// 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() 真正生成水果的函数,这里没有做对象池的处理,大家其实可以根据需要进行生成水果和释放水果的对象池处理,可以作为性能优化
////// 写成生成水果 /// /// /// ///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; } } /// /// 生成水果(非合成水果) /// /// /// /// ///Fruit SpawnFruit(FruitSeriesType fruitSeriesType, Vector2 pos, Transform parent) { m_AudioManager.PlaySpawnSound(); return SpawnFruit(fruitSeriesType, pos, parent, false); } /// /// 生成水果(可合成水果) /// /// /// /// /// ///Fruit SpawnFruit(FruitSeriesType fruitSeriesType, Vector2 pos, Transform parent, bool isCompound) { GameObject go = GameObject.Instantiate(m_FuritPrefabList[(int)fruitSeriesType], parent); Fruit fruit = go.AddComponent (); 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)移动水果的时候,水平位置的限制
////// 鼠标操作时,实时更新对应的当前的水果位置 /// 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); } } ////// 释放水果的时候,水平位置的限制 /// /// ///限制后的水果位置 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) 销毁水果(这里可以和生成水果一起做一个对象池处理),合成新水果,,并进行特效、声音和分数的相关处理
////// 可合成水果的委托 /// /// /// 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); } } } ////// 满足合成条件的处理 /// /// /// 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
public void Show(Color32 color, ActionshowAnimationEndAction) { StartCoroutine(EffectAnimation(color, showAnimationEndAction)); } IEnumerator EffectAnimation(Color color, Action 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(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();
}
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);
}
///
/// 简单对象池获取特效
///
///
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();
}
}
///
/// 特效动画结束,回收特效到对象池
///
///
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(ResPathDefine.AUDIO_SPAWN_PATH);
m_BombSound = Resources.Load(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();
}
}
public void PlaySpawnSound()
{
m_AudioSource.PlayOneShot(m_SpawnSound);
}
public void PlayBombSound()
{
m_AudioSource.PlayOneShot(m_BombSound);
}
33、在工程中添加 LineManager脚本,主要功能是,进行边界的左右适应屏幕,瞄准线的显示隐藏、位置更新,和游戏结束线的显示和隐藏
34、LineManager脚本主要函数:BorderLineSimpleAdaptScreen()简单边界适配屏幕,UpdateWarninglineHandle()警告线的显示隐藏处理,UpdateAimlineHandle()瞄准线的显示隐藏,移动位置处理
////// 简单边界适配屏幕 /// 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); } ////// 警告线的显示隐藏处理 /// void UpdateWarninglineHandle() { m_Warningline.gameObject.SetActive(GameManager.Instance.GameOverWarning); } ////// 瞄准线的显示隐藏,移动处理 /// 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); } } ////// 瞄准线的移动处理 /// 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();
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();
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
{
///
/// 把屏幕坐标转为世界坐标
///
/// 对应参照对象
/// 对应参照相机
/// 屏幕位置
/// 屏幕位置的世界位置
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 管理各种枚举,这里主要时水果类型的枚举
////// 水果类型 /// 注意:其中顺序是合成的顺序 /// 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 场景中游戏物体路径定义类,统一管理景中游戏物体路径
////// 场景中游戏物体路径定义类,统一管理景中游戏物体路径 /// 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 预制体路径定义类,统一管理预制体路径
////// 预制体路径定义类,统一管理预制体路径 /// 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() 对应函数功能
////// 整个游戏入口 /// 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、等等



