框架说明上传

This commit is contained in:
黄嘉宇 2026-02-02 16:44:43 +08:00
parent d72dbca0a6
commit fa059e8d5b
1 changed files with 0 additions and 0 deletions

View File

@ -0,0 +1,796 @@
文档说明
文档编写目的
此文档编写目的为讲解unity开发框架的使用。
2. 更新版本记录
本文档的更新版本信息如下表所示:
|版本号 |更新日期 |更新内容摘要 |更新人 |
|V1.0 |2026/01/28 |初始版本,对初 |司忠占 |
| | |始版本框架做了 | |
| | |基本解释 | |
框架内容讲解-UI框架
1.本UI框架是一个基础的UI框架包括UI加载、卸载、显示、隐藏等外部方法还包括
一些子类的重写方法包括OnInit、OnShow、OnUnload三种使用者可根据项目功能自
行调用具体的使用方法为创建一个名为xxxView建议写法的物体再创建一个同
名的脚本脚本继承UIView后在脚本中实现逻辑然后把物体制作成预制体存放于Re
sources文件夹下或者脚本所在的同级resources文件夹下通过UIView.Load<XXXXView
>(ViewLevel.TOP)加载预制体(显示隐藏卸载同理,无需传入层级);ViewLevel为物体加
载的层级具体层级可见代码注释。详情可见图1
[pic]
图1
脚本示例using SK.Framework;(命名空间)
public class HomeView : UIView
{
protected override void OnInit(IViewData data)
{
base.OnInit(data);
}
}
UI框架的核心属性
UI画布Canvas uiCanvas = UI.Canvas;
UI相机Camera uiCamera = UI.Camera;
UI Resolution 分辨率Vector2 uiResolution = UI.Resolution;
获取UI视图
当UI视图被加载时可以通过UIView.Get<HomeView>();来获取一个存在的UI视图进而
对UI进行相关的逻辑操作。
4.此类UI操作中其他操作同理
UIView.Show<HomeView>();
UIView.Hide<HomeView>();
UIView.Unload<HomeView>();
ActionChain事件链
框架内置了八种类型的事件分别是Simple普通事件、Delay延迟事件、Timer定时事件
、Until条件事件、While条件事件、Tween动画事件、Animate动画事件、Timeline时间
轴事件。也可以通过继承AbstractAction抽象事件类重写OnInvoke和OnReset函数来自
定义事件。下面是内置的八种事件的介绍:
1.Simple 普通事件
普通事件是最简单的可以理解为一个简单的Action回调函数。
2.Delay 延迟事件
延迟事件需要指定一个时长,在经过该时长后执行指定的回调函数。
3.Timer 定时事件
定时事件可以理解为定时器分为正计时和倒计时通过参数isReverse指定事件为A
ction<float>类型,通过已经计时的时长(正计时)或剩余的时长(倒计时)调用执行
4.Until 条件事件
Until条件事件需要指定一个Func<bool>条件,表示直到该条件成立时,调用回调函数,
事件结束。
5.While 条件事件
While条件事件同样需要指定一个Func<bool>条件与Until条件事件不同的是While条
件事件中设置的回调函数在条件成立时一直被调用,当条件不再成立时,事件结束。
6.Tween 动画事件
框架中集成了DoTween插件Tween事件表示的是播放一个DoTween动画动画播放完后
事件结束。
7.Animate 动画事件
Animate动画事件指的是通过Animator播放动画需要指定Animator组件和Animator
Controller中动画状态State的名称动画播放完后事件结束。
8.Timeline 时间轴事件
时间轴事件需要指定事件开始的时间节点和事件执行的时长需要配合Timeline
ActionChain时间轴事件链使用。
ActionChain详解
事件链包含三种类型分别是Timeline时间轴事件链、Sequence序列事件链和Concurre
nt并发事件链均继承自IActionChain接口包含Begin启动、Pause暂停、Resume恢复
、Stop终止函数。
Timeline时间轴事件链示例代码
[SerializeField] private GameObject cube;
[SerializeField] private GameObject sphere;
private TimelineActionChain timeline;
private void Start()
{
timeline = this.Timeline()
//通过Append添加时间轴事件
//第一个参数表示该事件开始的时间节点
//第二个参数表示该事件的时长
.Append(0f, 5f, s => cube.transform.position =
Vector3.Lerp(Vector3.zero, new Vector3(0, 0, 5f), s))
.Append(2f, 4f, s => sphere.transform.position =
Vector3.Lerp(Vector3.zero, Vector3.up * 2f, s))
.Begin() as TimelineActionChain;
//2倍速
timeline.Speed = 2f;
}
用法可做情景演绎此事件链可以获取当前timeline.CurrentTime可读可写
以理解成一个可编程的视频播放器。
Sequence 序列事件链
序列事件链中的事件是依次执行的,即只有上一个事件执行结束后,才会开始执行下一
个事件,适用于固定操作的业务流程编写。举例如下:
this.Sequence()
//普通事件
.Event(() => Debug.Log("Begin"))
//延迟2秒
.Delay(2f)
//普通事件
.Event(() => Debug.Log("2f"))
//直到按下键盘A键
.Until(() => Input.GetKeyDown(KeyCode.A))
//普通事件
.Event(() => Debug.Log("A Pressed."))
//DoTween动画事件
.Tween(() => transform.DOMove(new Vector3(0f, 0f, 1f), 2f))
//定时事件
.Timer(3f, false, s => Debug.Log(s))
.Begin()
.OnStop(() => Debug.Log("Completed"));
也可使用private IActionChain ac; ac= this.Sequence();
ac.Pause();使当前事件链暂停(停止,开始等同理,详见代码)
Concurrent 并发事件链
并发事件链中的事件是并发执行的,在事件链启动时同时开启执行,在所有的事件都执
行完成后,事件链终止,适用于一些同时进行的逻辑处理。示例如下:
this.Concurrent()
.Event(() => Debug.Log("Begin"))
.Delay(1f, () => Debug.Log("1f"))
.Delay(2f, () => Debug.Log("2f"))
.Delay(3f, () => Debug.Log("3f"))
.Until(() => Input.GetKeyDown(KeyCode.A))
.OnStop(() => Debug.Log("Completed"));
.Begin()
事件链嵌套
事件链之间支持互相嵌套,示例代码:
this.Sequence()
.Event(() => Debug.Log("Begin"))
//嵌套一个并发事件链
.Append(new ConcurrentActionChain()
.Delay(1f, () => Debug.Log("1f"))
.Delay(2f, () => Debug.Log("2f"))
.Delay(3f, () => Debug.Log("3f"))
as IAction)
//并发事件链执行完成后 继续执行序列事件链
.Until(() => Input.GetKeyDown(KeyCode.A))
.Event(() => Debug.Log("A Pressed."))
.Timer(3f, false, s => Debug.Log(s))
.Begin()
.OnStop(() => Debug.Log("Completed."));
Timer时间工具类
Timer模块实现了一系列计时工具包括定时器倒计时、计时器、秒表、闹钟等
们均继承自接口ITimer支持启动、暂停、恢复、停止计时等行为。
Countdown 定时器(倒计时)
获取一个定时器可以通过如下方式计时类工具的运行依赖于携程通过this获取定时
器表示使用当前的MonoBehaviour开启携程使用Timer获取定时器表示使用计时模块管
理器的MonoBehaviour开启携程。
Countdown countdown1 = this.Countdown(5f);
Countdown countdown2 = Timer.Countdown(10f, true);
第一个参数为float类型表示定时的时长第二个参数为bool类型表示计时是否忽略
时间的缩放默认为false。通过如下方式设置定时器的启动、执行、暂停、恢复、停止
事件:
//定时器
private Countdown countdown;
countdown = Timer.Countdown(5f)
.OnLaunch(() => Debug.Log("定时器启动"))
.OnExecute(s => Debug.Log(string.Format("剩余时间{0}", s)))
.OnPause(() => Debug.Log("定时器暂停"))
.OnResume(() => Debug.Log("定时器恢复"))
.OnStop(() => Debug.Log("定时器停止"));
可以通过Launch、Pause、Resume、Stop方法来控制countdown。
Clock 计时器(正计时)
计时器与定时器具有相同的事件不同的是定时器为倒计时例如定时5秒其值将会
从5逐渐到0到0后自动停止计时器为正向计时需要调用Stop手动终止可以通过S
topWhen为其设置停止的条件当条件满足时计时器将自动停止。脚本示例
private Clock clock;
clock=Timer.Clock()
.OnExecute(s => Debug.Log(string.Format("已经计时{0}", s)))
//设置停止条件 当键盘A按下时 计时器停止
.StopWhen(() => Input.GetKeyDown(KeyCode.A))
.Launch();
可以通过clock.ElapsedTime来获取已经计时的时间
TimeUtility 功用(时间格式化工具)
[pic]
Messenger 消息中心.
Messenger消息中心包含两部分内容一部分是消息的发布、订阅系统另一部分是消息
的打包、拆包系统。它们是用于脚本之间解耦的利器。
private void Start()
{
//订阅消息主题为Example的消息
//订阅后,当该主题的消息发布时,订阅事件将会被执行
Messenger.Subscribe<int>("Example", SubscribeEvent);
//发布消息主题为Example的消息
//消息内容为一个int类型的数值50
Messenger.Publish("Example", 50);
}
private void SubscribeEvent(int num)
{
Debug.Log(num);
}
注意Publish的执行应该晚于消息订阅Subscribe
取消订阅消息主题为Example的消息
取消后消息主题为Example的消息被发布时订阅事件SubscribeEvent不再会执行
Messenger.Unsubscribe<int>("Example", SubscribeEvent);
SceneLoader 场景加载器
加载器支持通过场景名称和场景指针加载场景:
/// <summary>
/// 异步加载场景
/// </summary>
/// <param name="sceneName">场景名称</param>
/// <param name="sceneActivationDelay">激活延迟时长</param>
/// <param name="loadSceneMode">场景加载方式</param>
/// <returns></returns>
public static SceneLoader LoadAsync(string sceneName, float
sceneActivationDelay = 3f, LoadSceneMode loadSceneMode =
LoadSceneMode.Single)
其中sceneActivationDelay参数表示当场景在内存中加载完成时需要延迟该时长才允
许场景激活原理是首先将异步操作AsyncOperation中的allowSceneActivation设为fa
lse在场景加载完成并延迟后再将其设为true该参数默认值为3。
代码示例如下:
//加载名为Example的场景
SceneLoader.LoadAsync("Example", 5f)
.OnBegan(() => Debug.Log("开始加载"))
.OnLoading(progress => Debug.Log(string.Format("加载进度 {0}",
progress)))
.OnCompleted(() => Debug.Log("加载完成"));
AimableObject 物体交互系统
使用功能前需要将脚本Aim System挂载在场景物体上
再把AimableObject脚本挂在需要被操作的物体上或者是继承此脚本重写需要挂载碰撞
Description属性表示该物体的描述信息AimableDistance属性表示该物体可被瞄准检
测到的距离通过OnEnter、OnExit、OnStayOnClick分别为该交互物体设置瞄准进入
事件、瞄准退出事件、瞄准停留,点击事件也可以通过继承AimableObject类来重写
这些事件,如下所示
public class Example : AimableObject
{
protected override void OnEnter()
{
//TODO
}
protected override void OnStay()
{
//TODO
}
protected override void OnExit()
{
//TODO
}
}
[pic]
Aim System
[pic]
Toggle表示整个系统的开关将其设为false后系统将不再进行瞄准检测MainCamer
a是用于瞄准检测的相机AimLayer表示检测的层级AimMaxDistance表示检测的最大距
AimMode表示瞄准的方式包含两种一种是Mouse使用鼠标的位置发出射线进行
瞄准检测当我们的相机是自由视角、上帝视角时通常使用这种方式另一种则是Vi
ew
Centre通过屏幕的中心点发出射线进行瞄准检测当我们的相机是第一人称或第三人
称视角时通常使用这种方式。需要注意当AimMode为ViewCentre模式时AimMaxDis
tance属性才起作用AimableObject的OnClick事件只有为鼠标输入的时候才会生效V
iewCentre模式需自行实现类似效果。
除此之外AimSystem中的核心属性还包括CurrentAimableObject它记录了当前所瞄准
的物体
Question 问题模块
创建问题配置文件,在文件夹右键鼠标,选择创建问题配置文件
[pic]
模块中内置了五种题型,可以在配置文件中进行配置,分别是判断题、单选题、多选题
、填空题、论述题它们均继承自Question Base基类。
使用方法1依托QuestionsHandler进行问题管理需先实现QuestionsHandler的初始化
/// <summary>
/// 构造函数
/// </summary>
/// <param name="profile">问题配置文件</param>
public QuestionsHandler(QuestionsProfile profile)
/// <summary>
/// 构造函数
/// </summary>
/// <param name="resourcesPath">配置文件的路径</param>
public QuestionsHandler(string resourcesPath)
初始化操作:
public QuestionsProfile questionsProfile;
public QuestionsHandler questionsHandler;
private QuestionBase currectQuestion;
// Start is called before the first frame update
void Start()
{
//初始化操作
questionsHandler = new QuestionsHandler(questionsProfile);
//获取选择内容
currectQuestion = questionsHandler.Switch(1);
//获取问题
Debug.Log(currectQuestion.Question);
}
初始化完成后可以通过questionsHandler进行问题管理
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
//上一题
currectQuestion = questionsHandler.Last();
Debug.Log(currectQuestion.Question);
}
if (Input.GetKeyDown(KeyCode.D))
{
//下一题
currectQuestion = questionsHandler.Next();
Debug.Log(currectQuestion.Question);
}
if (Input.GetKeyDown(KeyCode.S))
{
//根据题目ID跳转到指定题目
currectQuestion = questionsHandler.Switch(5);
Debug.Log(currectQuestion.Question);
}
}
使用方法2获取配置文件的问题列表自行管理
public QuestionsProfile questionsProfile;
void Start()
{
List<SingleChoiceQuestion> mySingles = questionsProfile.SingleChoices;
SingleChoiceQuestion single = mySingles[0];
//获取问题,同理获取题解,选项,是否正确
string que = single.Question;
bool isRight = single.IsCorrect(0);
}
判定当前问题的对错currectQuestion.IsCorrect(0);其中IsCorrect方法传递的参数
一般为当前选项的序号例如ABCD四个选项选则A则为0选择B则为1如多选的情况
选择AB则为0,1
因为选项内容不一致所以在方法1中无法获取每个题型的选项内容如在UI中自行搭建
了答题界面且编辑好问题内容则方法1更为便捷如需动态生成问题内容则方法2更
便捷。
Score 计分模块
通过右键/Create/Score
Profile创建一个分数配置文件首先在ScoreIDConstant脚本中编写代表分数项ID的in
t常量
[pic]
然后进行分数的编辑
[pic]
ID为分数项IDDescription为分数项描述信息Value为分数项分值模块内默认的配
置文件加载路径为Resources/Score Profile也可以在代码中进行更改。
创建单项分数项操作:
Score.Create(ScoreIDConstant.QUESTION_1);Create方法在调用的时候会生成并返回
一个唯一值,此值是后续进行一系列操作的唯一凭证,包括赋分,删除,取消得分等操
作。
[pic]
创建一个得分组合:
有些场景的一个步骤会包含多个得分点,此时我们就需要多个得分点组成一个得分组,
方法如下:
string[] flags = Score.CreateGroup("选择题", ValueMode.Additive,
ScoreIDConstant.QUESTION_1,
ScoreIDConstant.QUESTION_2
);
赋分操作:
单项得分: string score1= Score.Create(ScoreIDConstant.QUESTION_1);
Score.Obtain(score1);
分数组合得分: Score.Obtain("选择题",
flags[0]);此时第一个得分点得分,取消同理
获取分值:
获取总分:
Score.GetSum();
获取分数组合的分值:
Score.GetGroupSum("选择题");
通过Score.GetSum(); 获取的总分值为所有得分点的得分,包括组合中的分数。
Task任务系统
TaskManager负责任务调度与模式管理
TaskData负责一个任务的流程推进
TaskStep仅负责记录单步状态不控制流程
TaskManager有三种模式
Teach, // 教学模式(强提示)
Practice, // 练习模式(弱提示)
Exam // 考试模式(无提示)
任务由多个step组成需按顺序完成步骤支持完成失败跳步的操作。每个步骤最
终只会产生一种结果:
public enum StepResult
{
None, //初始
Correct,//正确
Error,//错误
Skipped// 跳过
}
使用流程:
步骤 1创建 TaskData 并配置基本信息
private TaskData _task;
void Start()
{
_task = new TaskData
{
TaskId = "Task_01",
TaskName = "状态驱动示例任务",
TaskDescription = "按下数字键1-5依次完成五个操作步骤"
};
AddStep(1, "步骤1 down");
AddStep(2, "步骤2 down");
AddStep(3, "步骤3 down");
AddStep(4, "步骤4 down");
AddStep(5, "步骤5 down");
//设置任务开始事件内容
_task.OnTaskStarted += OnTaskStarted;
}
/// <summary>
/// 任务开始时重置示例状态
/// </summary>
private void OnTaskStarted(TaskData task)
{
Debug.Log("任务开始,等待操作输入");
}
步骤 2创建并添加 TaskStepstep生命周期中OnStepHint
事件只有当任务不为考核时才执行
/// <summary>
/// 添加 Step
/// </summary>
private void AddStep(int stepId, string desc)
{
var step = new TaskStep
{
StepId = stepId,
Description = desc
};
step.OnStepStarted += s =>
{
Debug.Log($"【Step 开始】{s.Description}");
};
step.OnStepHint += s =>
{
Debug.Log($"【提示】{s.Description}");
};
step.OnStepCompleted += s =>
{
Debug.Log($"【Step 正确完成】{s.Description}");
};
step.OnStepError += s =>
{
Debug.LogWarning($"【Step 错误完成】{s.Description}");
};
step.OnStepSkipped += s =>
{
Debug.LogWarning($"【Step 跳过】{s.Description}");
};
//添加步骤
_task.Steps.Add(step);
}
步骤3设置任务模式并启动
// 设置任务模式
TaskManager.Instance.SetMode(TaskMode.Exam);
//添加taskdata
TaskManager.Instance.AddTask(_task);
//启动任务
TaskManager.Instance.Start();
所有的任务步骤只调用一个推进接口:
//发送当前 Step 结果(并自动进入下一个)
TaskManager.Instance.ReportCurrentStepResult(isError);
若当前步骤正确完成,则
TaskManager.Instance.ReportCurrentStepResultfalse
若当前步骤错误完成,则
TaskManager.Instance.ReportCurrentStepResulttrue
→→→→→→设计原则错误为true正确为false请勿混淆←←←←←←←
跳步操作通过指定step的id来跳转向指定步骤代码如下
TaskManager.Instance.JumpToStepById(1);
单例管理器
普通单例类示例:
using SK.Framework;
public class TestA : ISingleton
{
public string myStr;
public void OnInit()
{
myStr = "Test";
}
public void Func()
{
Debug.Log("Singleton Example.");
}
}
使用示例: TestA a = Singleton<TestA>.Instance;
a. myStr = string.Empty;
//单例释放
Singleton<TestA>.Dispose();
Mono单例类示例
Mono类型的单例通过继承IMonoSingleton接口来实现IsDontDestroyOnLoad属性用于指
定该单例物体是否作不销毁处理。
public class A : MonoBehaviour, IMonoSingleton
{
public string myStr { get; private set; }
public bool IsDontDestroyOnLoad { get { return true; } }
public void OnInit()
{
myStr = "Test";
}
public void Func()
{
Debug.Log("Singleton Example.");
}
}
使用示例MonoSingleton<A>.Instance.Func();
单例释放MonoSingleton<A>.Dispose();
场景相机管理器
场景相机管理器位置如下图:
[pic]
FreeCameraController实现了与Unity编辑器Scene视图中同样的相机操作包括WASD移
QE上升下降鼠标控制方向。界面如图
[pic]
RoamCameraController为一个物体观察相机可以基于参数围绕物体运动
[pic]
调用该脚本中SetTarget方法可动态切换观察对象上述组件可针对具体项目进行修改
Extension扩展函数
此类扩展函数内容较多,大多数都是项目中实用的快速方法,此处只列举几个示例,需
自行阅读代码:
private GameObject test;
test.Activate();//开启物体
test.Deactivate();关闭物体
test.transform.SetEulerAngles(0, 0, 0);基于Transform的设置欧拉角
//Array合并
string[] exampleArray = new string[] { "AAA", "BBB" };
string[] target = new string[] { "CCC", "DDD" };
string[] merge = exampleArray.Merge(target);
//字典操作
Dictionary<int, string> dic = new Dictionary<int, string>() { { 5, "AAA"
}, { 10, "BBB" } };
//拷贝字典
Dictionary<int, string> copy = dic.Copy();
//遍历字典
dic.ForEach(m => Debug.Log(string.Format("Key{0} Value{1}", m.Key,
m.Value)));
Dictionary<int, string> target = new Dictionary<int, string>() { {
11, "CCC" }, { 20, "DDD" } };
//合并字典
dic.AddRange(target);
//将字典的所有值放入到一个列表中
List<string> list = dic.Value2List();
//将字典的所有值放入到一个Array中
string[] array = dic.Value2Array();
//尝试添加
if (dic.TryAdd(20, "DDD")) Debug.Log("添加成功");
WebRequester网络请求器
创建WebInterface Profile 网络接口配置文件:
[pic]
配置一个获取时间的免费接口:
[pic]
Name表示该接口的命名Url为接口的地址Method表示该接口的请求方式该接口以G
ET方式调用Args为string类型数组表示接口的参数名称该接口不包含任何参数
所以不需要添加。
接口的注册与回调:
//注册接口
WebRequester.RegisterInterface<TextResponseWebInterface>("北京时间", out
var hourStatistics);
//设置回调函数
hourStatistics.OnCompleted(response => Debug.Log(response));
hourStatistics.SendWebRequest();
//注销接口
WebRequester.UnregisterInterface("北京时间");
更多用法说明:
1.GET
上例中我们以GET的方式发起网络请求调用一个接口并且没有任何参数假如接口包含
参数arg1和arg2我们需要在配置文件中进行设置
[pic]
代码中: hourStatistics.SendWebRequest("value1","value2");
此时系统会自动将Url进行参数拼接最终结果为:
https://apps.game.qq.com/CommArticle/app/reg/gdate.php?arg1=value1&arg2=valu
e2
2.Post
倘若以POST方式发起网络请求调用接口传入的第一个参数是POST的数据后面的参数
表示请求头,为可选参数,假如以"Content-Type"
"application/json"为请求头,则代码为:
//注册接口
WebRequester.RegisterInterface<TextResponseWebInterface>("北京时间", out
var hourStatistics);
string s = this.ToJson();
//设置回调函数
hourStatistics.OnCompleted(response => Debug.Log(response));
hourStatistics.SendWebRequest(s, "Content-Type=application/json");
为了更灵活的进行网络请求,可以自行定义请求内容,代码如下:
[pic]
[pic]
本地文件加载器
[pic]
本地文件加载器提供三种本地文件加载:
1.文本加载
[pic]
图片加载,图片加载有两种方式,一种加载固定路径的单张图片,单张图片的路径需以
文件格式结束(完整路径)一种传入文件夹路径,将加载文件夹下的所有图片并返回一
个list,支持pngjpgjpeg三种图片格式,返回Texture2D
[pic]
音频加载
音频加载与图片加载类似,同样支持两种方式,当使用单个文件加载的时候,需以完整
路径结束音频加载支持mp3,ogg,wav三种格式调用onComplete回调使用音频,方法调
用以协程的方式调用:
[pic]
实用工具
Variables资源管理类
资源管理类实现了一个方便资源进行分类管理的脚本,使用方法如下图:
public Variables variables;
[pic]
第一个参数为资源名称,第二个参数为类型,如下所示,第三个参数为需要指定的物体
其中component可以是该物体身上的任意可获取的组件
[pic]
获取方法: public Button enterBtn; enterBtn =
variables.Get<Button>("enterBtn");
Get填入的需要是对应的类型例如上述资源中绑定了一个名为enterBtn类型为Butto
n的EnterBtn的物体则获取的时候需要传入对应的类型与名称。
同理,可以动态的将已经设置好的物体进行变更,注意:变更只能是同类型物体进行变
更,不同类型无法进行:
[pic]
public Variables Variables;
public Transform obj1;
private void Awake()
{
Variables.Set<Transform>("Obj2", obj1);
}
运行时则变成
[pic]
此工具可将资源集中存放,集中管理,方便使用和阅读。
HighlightOutline物体高亮类
此物体高亮效果比较简单且只支持标准渲染器,后续考虑融合高亮专用插件,在此不做
其他讲解:
[pic]