Unity 游戏实例开发集合 之 CutFruit (切水果(水果忍者)) 休闲小游戏快速实现
作者:互联网
Unity 游戏实例开发集合 之 CutFruit (切水果(水果忍者)) 休闲小游戏快速实现
目录
Unity 游戏实例开发集合 之 CutFruit (切水果(水果忍者)) 休闲小游戏快速实现
二、CutFruit (切水果(水果忍者)) 游戏内容与操作
一、简单介绍
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、等等
标签:忍者,水果,const,void,private,Unity,PATH,public 来源: https://blog.csdn.net/u014361280/article/details/122616242