用QFramework来重构 祖玛游戏

资料

Unity - 祖玛游戏
GitHub

说明

用QF一个场景就够了,在UIRoot下切换预制体达到面板切换。

但测试中当然要有一个直接跳到测试面板的 测试脚本,保留测试Scene(不然初学者也不知道怎么恢复测试Scene),所以全文按Scene划分

----------------------------------------------------

01 Scene 开始进入(面板脚本,主要是 动(功能)静(UI引用)分离)

01 先创建一个UIRoot(QF里面有的)

02 做好UI,给需要引用的UI加 Bind脚本(默认设置看图)

03 拖出来做预制体,并且 CreateUICode,会生成两个脚本(自动生成位置是上一级目录的Scripts/UI),一个管UI引用(xxx.Designer),一个管功能(xxx),管功能的会自动加到预制体上

xxxPanelData 、xxxPanel(不叫Panel,叫xxxWindow,xxxUI随便)

csharp 复制代码
using UnityEngine;
using UnityEngine.UI;
using QFramework;
using UnityEngine.SceneManagement;

namespace QFramework.Example
{
	public class StartGamePanelData : UIPanelData
	{
	}
	public partial class StartGamePanel : UIPanel
	{
		protected override void OnInit(IUIData uiData = null)
		{
			mData = uiData as StartGamePanelData ?? new StartGamePanelData();
			// please add init code here

			Screen.SetResolution(640, 1136, false);//宽,高,不可修改
			BtnStart.onClick.AddListener(() => {


				Debug.Log("StartGamePanel");
                SceneManager.LoadScene("01 SelectLevel");
            });
		}
		
		protected override void OnOpen(IUIData uiData = null)
		{
		}
		
		protected override void OnShow()
		{
		}
		
		protected override void OnHide()
		{
		}
		
		protected override void OnClose()
		{
		}
	}
}

xxxPanel.Designer

csharp 复制代码
using System;
using UnityEngine;
using UnityEngine.UI;
using QFramework;

namespace QFramework.Example
{
	// Generate Id:29cfe4a1-ad1e-4350-a490-3bdf8cf34278
	public partial class StartGamePanel
	{
		public const string Name = "StartGamePanel";
		
		[SerializeField]
		public UnityEngine.UI.Button BtnStart;
		
		private StartGamePanelData mPrivateData = null;
		
		protected override void ClearUIComponents()
		{
			BtnStart = null;
			
			mData = null;
		}
		
		public StartGamePanelData Data
		{
			get
			{
				return mData;
			}
		}
		
		StartGamePanelData mData
		{
			get
			{
				return mPrivateData ?? (mPrivateData = new StartGamePanelData());
			}
			set
			{
				mUIData = value;
				mPrivateData = value;
			}
		}
	}
}

04 调用,RrsKit.Init();必须调用

csharp 复制代码
/****************************************************
    文件:GameStart.cs
	作者:lenovo
    邮箱: 
    日期:2023/7/2 22:59:41
	功能:
*****************************************************/

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;


namespace QFramework.Example
{
    public class GameStart : MonoBehaviour
    {

        #region 生命

        /// <summary>首次载入</summary>
        void Awake()
        {
            ResKit.Init();
            UIKit.OpenPanel<StartGamePanel>();
            GameObject.DontDestroyOnLoad(gameObject);
        }
        

        #endregion 


    }

}

06 写包名,标记(ResKit才有它在打包的列表中),做AB包(QF有个相关示例中不打包运行不了;剪辑了,实际没那么快)



06 效果

---------------------------------------------------------------

02 Scene 选择

世界选择

关卡选择

stars Plugins文件夹名

unity中,Plugins文件夹下,会被变成firstpass程序集

--------------------------------------------------------

03 Scene 游戏界面

modify 拆分Enum

对初学者有好点。但实际Unity的内置脚本,有枚举写在类内部的

GameUI拆成 Pass面板、Fail面板

Pass面板

csharp 复制代码
using UnityEngine;
using UnityEngine.UI;
using QFramework;
using UnityEngine.SceneManagement;

namespace QFramework.Example
{
	public class SuccPanelData : UIPanelData
	{
	}
	public partial class SuccPanel : UIPanel
	{
		protected override void OnInit(IUIData uiData = null)
		{
			mData = uiData as SuccPanelData ?? new SuccPanelData();
			// please add init code here


			BtnNext.onClick.AddListener(() => { 
                GameData.LevelIndex++;
                SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);			
			});
        }
		
		protected override void OnOpen(IUIData uiData = null)
		{
		}
		
		protected override void OnShow()
		{
		}
		
		protected override void OnHide()
		{
		}
		
		protected override void OnClose()
		{
		}
	}
}

Fail面板

csharp 复制代码
using UnityEngine;
using UnityEngine.UI;
using QFramework;
using UnityEngine.SceneManagement;

namespace QFramework.Example
{
	public class GameOverPanelData : UIPanelData
	{
	}
	public partial class GameOverPanel : UIPanel
	{
		protected override void OnInit(IUIData uiData = null)
		{
			mData = uiData as GameOverPanelData ?? new GameOverPanelData();
			// please add init code here

			BtnReset.onClick.AddListener(()=>{
                GameManager.Instance.StartBack();
				CloseSelf();
            });

            BtnReplay.onClick.AddListener(() => {
                SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
            });

			BtnHome.onClick.AddListener(() => {
				UIKit.OpenPanel<StartGamePanel>();
                CloseSelf();
            });
        }
		
		protected override void OnOpen(IUIData uiData = null)
		{
		}
		
		protected override void OnShow()
		{
		}
		
		protected override void OnHide()
		{
		}
		
		protected override void OnClose()
		{
		}
	}
}

watch QF中获取一个资源

new ResLoader()这种过时了

爆炸特效

球的预制体

csharp 复制代码
			//在扫雷案例中测试的,用到WhiteChess
			ResKit.Init();
            ResLoader loader = ResLoader.Allocate();
			GameObject prefab = loader.LoadSync<GameObject>("WhiteChess");
			Instantiate(prefab , transform);

处理GameManager成GamePanel

01 首先将子节点的所有脚本上提到父节点 GamePanel

02 将脚本中引用的节点 进行提取,重命名(和节点名字相同),添加Bind脚本。到脚本最上面,方便看

03 因为用到UI,SpriteRenderer该改成Image

watch Awake的特点

脚本中只用Awake方法,前面就不会有勾选选项

bug AB资源不存在

明明有,重新打

bug 3D转UGUI

SpriteRenderer转Image后(想要用QF的调用面板的方式),球移动很小。

方法 生成地图文件之前用同样用UIRoot,也就是UGUI,而不是原来的世界坐标。此时小心注意父节点的Scale、预制体小球Ball的RectTranfrom的Scale都调为1,不然球与球之间的距离会有问题(问题就是要么球之间的距离有问题,要么轨道不重合)

modify 转UGUI Image

3000是球沿着曲线移动的平滑度

0.3也相当于球的直径,这个直径就是你要实例的那个球预制体的直径

方法 生成地图文件之前用同样用UIRoot,也就是UGUI,而不是原来的世界坐标。同时需要乘以倍率(试230合适)

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MapConfig : ScriptableObject
{
    public float EndPoint { get; private set; }

    public List<Vector3> pathPointList = new List<Vector3>();

    public void InitMapConfig()
    {
        EndPoint = pathPointList.Count - 2;
    }

    public Vector3 GetPosition(float progress)
    {
        Camera ui=Camera.main.gameObject.FindComponentWithTag<Camera>("UI");
        int index = Mathf.FloorToInt(progress);
         //return Vector3.Lerp(pathPointList[index], pathPointList[index + 1], progress - index);

        Vector3 v1 = Vector3.Lerp(pathPointList[index], pathPointList[index + 1], progress - index);

        return v1*230f;
    }

}

效果 轨道球

-------------------------------------

modify 发射球的发射位置、速度和终点超界判定

发射球的位置



速度

超界的判定

就是把Canvas上的 球 ,拖到Canvas的边界,看坐标

效果

-------------------------------------------

bug 销毁特效的大小和位置

csharp 复制代码
using QFramework;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using ResLoader = QFramework.ResLoader;

public class FXManager :MonoSingleton<FXManager>
{
       Transform FXs;
   //
    GameObject destroyFXPrefab;
    ObjectPool<GameObject> destroyFXPool;
 

                                                                
    public void Init(Transform FXs)
    {
        this.FXs = FXs;
        //
        ResKit.Init();
        QFramework.ResLoader loader =  QFramework.ResLoader.Allocate();
        destroyFXPrefab = loader.LoadSync<GameObject>("DestroyFX"); 
        destroyFXPool = new ObjectPool<GameObject>(InstantiateFX, 10);
    }


    private GameObject InstantiateFX()
    {
        GameObject go = Instantiate(destroyFXPrefab, FXs);
        go.Hide();
        return go;
    }


    public void ShowDestroyFX(Vector3 pos)
    {
        GameObject go = destroyFXPool.GetObject();
        go.Show();
        go.transform.localPosition = pos;

        //延时0.5f执行回收操作
        ScheduleOnce.Start(this, () =>
         {
             go.Hide();
             destroyFXPool.AddObject(go);
         }, 0.5f);
    }
}

bug 发射器旋转一次就固定朝向左下方

后面又自动好了,可能改掉了循环调用打开GamePanel,导致存在多个GamePanel?

bug 自动刷新

点了几下没反应,因为原来没加自动刷新

bug 三个mapConfig不能放Resources,生成时空白

不放Resources,又会

GameManager.Instance.mapConfig运行后为空。这是因为

public class GameSceneConfig : MonoSingleton中的mapconfigArr有的为空

bug 抬起发射时不灵敏

关掉这个

watch 消球后不回退

小球后回退需要

小球后还能存在继续消球的情况在

bug 复活后回退太狠

modify SoundManager

看了示例,不需要类似于UIkit的ResKit.Init()的初始化

AudioKit.PlaySound("resources://Sound/"+clipName );

AudioKit.PlayMusic("resources://Sound/"+name,volume:volume);

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using QFramework;

public class SoundManager : MonoSingleton<SoundManager>
{
    static AudioSource bgAudio;


    public void Init()
    {
        bgAudio = gameObject.GetOrAddComponent<AudioSource>();
    }


    private static void PlaySound(string clipName)
    {
      //  AudioSource.PlayClipAtPoint(GetAudioClip(clipName), Vector3.zero);
        AudioKit.PlaySound("resources://Sound/"+clipName );
    }


    public static AudioClip GetAudioClip(string clipName)
    {
        return Resources.Load("Sound/" + clipName, typeof(AudioClip)) as AudioClip;
    }


    public static void PlayDestroy() { PlaySound("Eliminate");  }
    public static void PlayShoot()   { PlaySound("Shoot"); }
    public static void PlayInsert()  { PlaySound("BallEnter"); }
    public static void PlayBomb()    { PlaySound("Bomb"); }
    public static void PlayFail()    { PlaySound("Fail"); }
    public static void PlayFastMove(){ PlaySound("FastMove"); }
    public static void PlayMusic(string name,float volume=0.3f) 
    {                                                   
        //bgAudio.clip = SoundManager.GetAudioClip(name);//*-
        //bgAudio.volume = volume;
        //bgAudio.loop = true;
        //bgAudio.Play();
        AudioKit.PlayMusic("resources://Sound/"+name,volume:volume);
    }



}

bug shooterSO数据丢失

发生里面的Vector3丢失的情况,所以用了以下。暂时不知道管不管用

csharp 复制代码
        EditorUtility.SetDirty(fromAsset);

bug Object的AB包命名

原本我命名AB为 0_mapconfig,打包出也有这个ABbao1

但是右面自动出现了 0_asset,并且自动把 文件标记为 0)_asset,导致后面打包出现 0_asset

bug 通关下一关球没被销毁,只是progress==0

这是开始初始化的一段小球,不动是因为此时的GmaeState==Succ。重新进入游戏,需要重置 GameState

modify Manager拆分

将Manager从GamePanel中拆分出来

--------------------------------------------------------

Panel两个面板说明

也是采用QF的UI脚本自动生成,往里面填代码

。。。。

复活时回退得太多了(3D转UGUI的原因),需要调数值(里面有个回退时间是3秒)

。。。。

两个面板的效果在文章最后

Panel 成功

csharp 复制代码
using UnityEngine;
using UnityEngine.UI;
using QFramework;
using UnityEngine.SceneManagement;
using QFramework.PointGame;


namespace QFramework.Example
{
	public class SuccPanelData : UIPanelData
	{
	}
	public partial class SuccPanel : UIPanel
	{
		protected override void OnInit(IUIData uiData = null)
		{
			mData = uiData as SuccPanelData ?? new SuccPanelData();
			// please add init code here


			BtnNext.onClick.AddListener(() => {
				
				UIKit.OpenPanel<GamePanel>(
					new GamePanelData() { LevelCount=GameData.GetLevelIndex() }
				);
				CloseSelf();
			});

			BtnHome.onClick.AddListener(() => {
                UIKit.OpenPanel<StartGamePanel>();
                CloseSelf();
            });
        }
		
		protected override void OnOpen(IUIData uiData = null)
		{
		}
		
		protected override void OnShow()
		{
		}
		
		protected override void OnHide()
		{
		}
		
		protected override void OnClose()
		{
		}
	}
}

Panel 失败

主要是复活,不能Close,所以失败后先Hide,后面

01 复活,就Show

02 再来一次,就Close,在Open

03 回主页

csharp 复制代码
using UnityEngine;
using UnityEngine.UI;
using QFramework;
using UnityEngine.SceneManagement;

namespace QFramework.Example
{
	public class GameOverPanelData : UIPanelData
	{
	}


	public partial class GameOverPanel : UIPanel
	{
		protected override void OnInit(IUIData uiData = null)
		{
			mData = uiData as GameOverPanelData ?? new GameOverPanelData();
			// please add init code here

			BtnReset.onClick.AddListener(()=>{ //复活
                GameManager.Instance.GameRevive();
                CloseSelf();

            });

            BtnReplay.onClick.AddListener(() => { //再来一次
                UIKit.ClosePanel<GamePanel>();
                UIKit.OpenPanel<GamePanel>( new GamePanelData() { LevelCount=GameData.GetLevelIndex() });
                CloseSelf();
            });

			BtnHome.onClick.AddListener(() => {	 //主页
                UIKit.ClosePanel<GamePanel>();
                UIKit.OpenPanel<StartGamePanel>();
                CloseSelf();
            });
        }
		
		protected override void OnOpen(IUIData uiData = null)
		{
		}
		
		protected override void OnShow()
		{
		}
		
		protected override void OnHide()
		{
		}
		
		protected override void OnClose()
		{
		}
	}
}

---------------------------------------------------------

Tool 轨道制作

modify BezierPathController

制作Ball预制体(我用了红球(可以用别的)的sprite(UGUI),原本的是MeshRender(3D世界中))

节点"Map"是BezierPathController.Awake()中用到的,是演示蓝色球坐标的,可以注释掉

01 拖一个到"1"节点(带有BezierPathController脚本)下

01 不断Ctrl+D复制节点下的预制体(每4个(基本2个控制弯曲度,2个在轨道上)红球预制体就有生成一段蓝色球)

02 Control Point List里面就是红球的坐标数据(世界坐标)

06 每4个红色球后,Scene中会生成一段蓝色球

03 04 全部做完,点击"生成地图文件","Path Point List"里面就是蓝色球的位置(世界坐标)

05 Map文件夹下也保存了一份坐标数据

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using UnityEditor;
using System;
using UnityEngine.UI;

public class BezierPathController : MonoBehaviour
{

    #region 字属



    public int segmentsPerCurve = 3000;
    /// <summary>连线上求和球之间的举例,也就是比直径大一点</summary>
    public float BallAndBallDis = 0.3f;
    public bool Debug = true;
    public GameObject ballPrefab;

    /// <summary>贝塞尔曲线的节点。控制弯曲度的白球</summary>
    public List<GameObject> ControlPointList = new List<GameObject>();
    /// <summary>贝塞尔曲线的的线段。连线的蓝球的坐标</summary>
    public List<Vector3> pathPointList = new List<Vector3>();
    #endregion


    //private void Awake()
    //{
    //    Debug = true;
    //    foreach (var item in pathPointList)
    //    {
    //        GameObject ball = Instantiate(ballPrefab, GameObject.Find("Map").transform);
    //        ball.transform.position = item;
    //    }
    //}


    private void OnDrawGizmos()
    {
        //节点
        ControlPointList.Clear();
        foreach (Transform item in transform)//没错,就是遍历子节点
        {
            ControlPointList.Add(item.gameObject);
        }


        //线段
        List<Vector3> controlPointPos 
            = ControlPointList.Select(point => point.transform.position).ToList();
        var points = GetDrawingPoints(controlPointPos, segmentsPerCurve);

        Vector3 startPos = points[0];
        pathPointList.Clear();
        pathPointList.Add(startPos);
        for (int i = 1; i < points.Count; i++)
        {
            if (Vector3.Distance(startPos, points[i]) >= BallAndBallDis)
            {
                startPos = points[i];
                pathPointList.Add(startPos);
            }
        }

        foreach (var item in ControlPointList)
        {
            item.GetComponent<Image>().enabled = Debug;//相当于将物体隐身,并不会影响物体的脚本运行,物体的碰撞体也依然存在。
        }

        if (Debug == false)
        { 
            return;
        } 


        //01 画连线球的球
        Gizmos.color = Color.blue;
        foreach (var pos in pathPointList)
        {
            Gizmos.DrawSphere(pos, BallAndBallDis / 2);
        }

        //02 画连线球的线
        Gizmos.color = Color.yellow;
        for (int i = 0; i < points.Count - 1; i++)
        {
            Gizmos.DrawLine(points[i], points[i + 1]);
        }

        //03 画连线球的的弯曲度控制线
        //绘制贝塞尔曲线控制点连线,红,色
        Gizmos.color = Color.red;
        for (int i = 0; i < controlPointPos.Count - 1; i++)
        {
            Gizmos.DrawLine(controlPointPos[i], controlPointPos[i + 1]);
        }

    }



    #region 辅助


    /// <summary>贝塞尔线段</summary>
    List<Vector3> GetDrawingPoints(List<Vector3> controlPoints, int segmentsPerCurve)
    {
        List<Vector3> points = new List<Vector3>();
        for (int i = 0; i < controlPoints.Count - 3; i += 3)
        {
            var p0 = controlPoints[i];
            var p1 = controlPoints[i + 1];
            var p2 = controlPoints[i + 2];
            var p3 = controlPoints[i + 3];

            for (int j = 0; j <= segmentsPerCurve; j++)
            {
                var t = j / (float)segmentsPerCurve;
                points.Add(CalculateBezierPoint(t, p0, p1, p2, p3));
            }
        }
        return points;
    }

    /// <summary>
    /// <summary>贝塞尔曲线的三次方公式</summary>
    /// </summary>
    /// <param name="t"></param>
    /// <param name="p0">起点</param>
    /// <param name="p1">一侧的平滑度调节点</param>
    /// <param name="p2">另一侧的平滑度调节点</param>
    /// <param name="p3">终点</param>
    /// <returns></returns>
    Vector3 CalculateBezierPoint(float t
        , Vector3 p0
        , Vector3 p1, Vector3 p2
        , Vector3 p3)
    {
        var x   = 1 - t;
        var xx  = x * x;
        var xxx = x * x * x;
        var tt  = t * t;
        var ttt = t * t * t;
        return p0 * xxx 
            +   3 * p1 * t * xx 
            +   3 * p2 * tt * x 
            +  p3 * ttt;
    }


#if UNITY_EDITOR
    /// <summary>
    /// pathPointList写入"Assets/Map/map.asset"
    /// 但没有覆盖功能,删掉再创建就看得见效果了
    /// </summary> 
    public void CreateMapAsset()
    {
        string assetPath =String.Format(  "Assets/Map/{0}.asset",gameObject.name);  //写这Vector3数据的
        MapConfig mapConfig = new MapConfig();
        foreach (Vector3 item in pathPointList)
        {
            mapConfig.pathPointList.Add(item);
        }
        AssetDatabase.CreateAsset(mapConfig, assetPath);
        AssetDatabase.SaveAssets();
    }
#endif


    #endregion


}


#if UNITY_EDITOR
[CustomEditor(typeof(BezierPathController))]
public class BezierEditor : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        if (GUILayout.Button("生成地图文件"))//详情面板下的按钮
        {
            (target as BezierPathController).CreateMapAsset();
        }
        AssetDatabase.Refresh();
    }
}
#endif

modify 保存位置,文件命名

保存位置放在Resources会报错

文件命名改为gameObject.name,不写死

string assetPath =String.Format( "Assets/Map/{0}.asset", gameObject.name); //写这Vector3数据的

watch 遍历子节点

Transform 内部实现了迭代器,所以就可以这样写

csharp 复制代码
        //节点
        ControlPointList.Clear();
        foreach (Transform item in transform)//没错,就是遍历子节点
        {
            ControlPointList.Add(item.gameObject);
        }

Tool 青蛙Shooter的位置

GameMapConfig来控制,里面有个Vector3数组

modify 修改图片的名字(-1),方便对应

modify 制作Shooter位置的一个工具

csharp 复制代码
/****************************************************
    文件:MakeShooterPos.cs
	作者:lenovo
    邮箱: 
    日期:2023/7/19 15:37:17
	功能:
*****************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using Random = UnityEngine.Random;
 

public class MakeShooterPos : MonoBehaviour
{
#if UNITY_EDITOR
    /// <summary>
    /// pathPointList写入"Assets/Map/map.asset"
    /// 但没有覆盖功能,删掉再创建就看得见效果了
    /// </summary> 
    public void RecordeShooterPos()
    {
        MapConfig fromAsset= AssetDatabase.LoadAssetAtPath<MapConfig>("Assets/Map/shooter.asset");
        int idx = int.Parse( gameObject.name);
        Transform shooter = GameObject.Find("ShooterTrans").transform;
        fromAsset.pathPointList[idx] = shooter.localPosition;
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
    }
#endif

}

#if UNITY_EDITOR
[CustomEditor(typeof(MakeShooterPos))]
public class MakeShooterPosEditor : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        if (GUILayout.Button("生成Shooter位置"))//详情面板下的按钮
        {
            (target as MakeShooterPos).RecordeShooterPos();
        }
        AssetDatabase.Refresh();
    }
}
#endif

效果

---------------------------------------------------------

总的效果

01 主要按 S(Success)键,快速通关,进行测试。

02 主要按F(Fail)键,快速失败,进行测试

地图数据只做了三关,所以最后报空错误了

相关推荐
躺下睡觉~9 小时前
Unity-Transform类-父子关系
java·unity·游戏引擎
躺下睡觉~10 小时前
Unity-Transform类-缩放和看向
unity·游戏引擎
君莫愁。12 小时前
【Unity】检测鼠标点击位置是否有2D对象
unity·c#·游戏引擎
咩咩觉主13 小时前
Unity实战案例全解析:PVZ 植物卡片状态分析
unity·c#·游戏引擎
蓝裕安16 小时前
伪工厂模式制造敌人
开发语言·unity·游戏引擎
谢泽浩20 小时前
Unity 给模型贴上照片
unity·游戏引擎
z2014z20 小时前
Unity Resource System 优化笔记
unity·游戏引擎
王维志20 小时前
Unity 高亮插件HighlightPlus介绍
unity·游戏引擎
DisonTangor20 小时前
Ruffle 继续在开源软件中支持 Adobe Flash Player
游戏·adobe
zaizai100721 小时前
我的demo保卫萝卜中的技术要点
unity