Unity - 祖玛游戏
GitHub
用QF一个场景就够了,在UIRoot下切换预制体达到面板切换。
但测试中当然要有一个直接跳到测试面板的 测试脚本,保留测试Scene(不然初学者也不知道怎么恢复测试Scene),所以全文按Scene划分
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()
{
}
}
}
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;
}
}
}
}
/****************************************************
文件: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 生命
/// 首次载入
void Awake()
{
ResKit.Init();
UIKit.OpenPanel<StartGamePanel>();
GameObject.DontDestroyOnLoad(gameObject);
}
#endregion
}
}
unity中,Plugins文件夹下,会被变成firstpass程序集
对初学者有好点。但实际Unity的内置脚本,有枚举写在类内部的
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()
{
}
}
}
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()
{
}
}
}
new ResLoader()这种过时了
爆炸特效
球的预制体
//在扫雷案例中测试的,用到WhiteChess
ResKit.Init();
ResLoader loader = ResLoader.Allocate();
GameObject prefab = loader.LoadSync<GameObject>("WhiteChess");
Instantiate(prefab , transform);
01 首先将子节点的所有脚本上提到父节点 GamePanel
02 将脚本中引用的节点 进行提取,重命名(和节点名字相同),添加Bind脚本。到脚本最上面,方便看
03 因为用到UI,SpriteRenderer该改成Image
明明有,重新打
SpriteRenderer转Image后(想要用QF的调用面板的方式),球移动很小。
方法 生成地图文件之前用同样用UIRoot,也就是UGUI,而不是原来的世界坐标。此时小心注意父节点的Scale、预制体小球Ball的RectTranfrom的Scale都调为1,不然球与球之间的距离会有问题(问题就是要么球之间的距离有问题,要么轨道不重合)
3000是球沿着曲线移动的平滑度
0.3也相当于球的直径,这个直径就是你要实例的那个球预制体的直径
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;
}
}
就是把Canvas上的 球 ,拖到Canvas的边界,看坐标
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);
}
}
后面又自动好了,可能改掉了循环调用打开GamePanel,导致存在多个GamePanel?
不放Resources,又会
GameManager.Instance.mapConfig运行后为空。这是因为
public class GameSceneConfig : MonoSingleton中的mapconfigArr有的为空
小球后回退需要
小球后还能存在继续消球的情况在
看了示例,不需要类似于UIkit的ResKit.Init()的初始化
AudioKit.PlaySound(“resources://Sound/”+clipName );
AudioKit.PlayMusic(“resources://Sound/”+name,volume:volume);
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);
}
}
发生里面的Vector3丢失的情况,所以用了以下。暂时不知道管不管用
EditorUtility.SetDirty(fromAsset);
原本我命名AB为 0_mapconfig,打包出也有这个ABbao1
但是右面自动出现了 0_asset,并且自动把 文件标记为 0)_asset,导致后面打包出现 0_asset
这是开始初始化的一段小球,不动是因为此时的GmaeState==Succ。重新进入游戏,需要重置 GameState
也是采用QF的UI脚本自动生成,往里面填代码
。。。。
复活时回退得太多了(3D转UGUI的原因),需要调数值(里面有个回退时间是3秒)
。。。。
两个面板的效果在文章最后
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()
{
}
}
}
主要是复活,不能Close,所以失败后先Hide,后面
01 复活,就Show
02 再来一次,就Close,在Open
03 回主页
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()
{
}
}
}
制作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文件夹下也保存了一份坐标数据
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;
/// 连线上求和球之间的举例,也就是比直径大一点
public float BallAndBallDis = 0.3f;
public bool Debug = true;
public GameObject ballPrefab;
/// 贝塞尔曲线的节点。控制弯曲度的白球
public List<GameObject> ControlPointList = new List<GameObject>();
/// 贝塞尔曲线的的线段。连线的蓝球的坐标
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 辅助
/// 贝塞尔线段
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;
}
///
/// 贝塞尔曲线的三次方公式
///
///
/// 起点
/// 一侧的平滑度调节点
/// 另一侧的平滑度调节点
/// 终点
///
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
///
/// pathPointList写入"Assets/Map/map.asset"
/// 但没有覆盖功能,删掉再创建就看得见效果了
///
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
保存位置放在Resources会报错
文件命名改为gameObject.name,不写死
string assetPath =String.Format( “Assets/Map/{0}.asset”, gameObject.name); //写这Vector3数据的
Transform 内部实现了迭代器,所以就可以这样写
//节点
ControlPointList.Clear();
foreach (Transform item in transform)//没错,就是遍历子节点
{
ControlPointList.Add(item.gameObject);
}
GameMapConfig来控制,里面有个Vector3数组
/****************************************************
文件: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
///
/// pathPointList写入"Assets/Map/map.asset"
/// 但没有覆盖功能,删掉再创建就看得见效果了
///
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)键,快速失败,进行测试
地图数据只做了三关,所以最后报空错误了