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、等等