diff --git a/开发框架使用说明V1.0.doc b/开发框架使用说明V1.0.doc new file mode 100644 index 0000000..f6806ca --- /dev/null +++ b/开发框架使用说明V1.0.doc @@ -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(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();来获取一个存在的UI视图,进而 +对UI进行相关的逻辑操作。 +4.此类UI操作中,其他操作同理: +UIView.Show(); +UIView.Hide(); +UIView.Unload(); + + + + + + + + + + + + + + +ActionChain事件链 + +框架内置了八种类型的事件,分别是Simple普通事件、Delay延迟事件、Timer定时事件 +、Until条件事件、While条件事件、Tween动画事件、Animate动画事件、Timeline时间 +轴事件。也可以通过继承AbstractAction抽象事件类,重写OnInvoke和OnReset函数来自 +定义事件。下面是内置的八种事件的介绍: +1.Simple 普通事件 +普通事件是最简单的,可以理解为一个简单的Action回调函数。 + +2.Delay 延迟事件 +延迟事件需要指定一个时长,在经过该时长后执行指定的回调函数。 + +3.Timer 定时事件 +定时事件可以理解为定时器,分为正计时和倒计时,通过参数isReverse指定,事件为A +ction类型,通过已经计时的时长(正计时)或剩余的时长(倒计时)调用执行 +。 + +4.Until 条件事件 +Until条件事件需要指定一个Func条件,表示直到该条件成立时,调用回调函数, +事件结束。 + +5.While 条件事件 +While条件事件同样需要指定一个Func条件,与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("Example", SubscribeEvent); + + //发布消息主题为Example的消息 + //消息内容为一个int类型的数值50 + Messenger.Publish("Example", 50); + } + private void SubscribeEvent(int num) + { + Debug.Log(num); + } +注意:Publish的执行应该晚于消息订阅Subscribe +取消订阅消息主题为Example的消息 +取消后,消息主题为Example的消息被发布时,订阅事件SubscribeEvent不再会执行 +Messenger.Unsubscribe("Example", SubscribeEvent); + +SceneLoader 场景加载器 + +加载器支持通过场景名称和场景指针加载场景: +/// +/// 异步加载场景 +/// +/// 场景名称 +/// 激活延迟时长 +/// 场景加载方式 +/// +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、OnStay,OnClick分别为该交互物体设置瞄准进入 +事件、瞄准退出事件、瞄准停留,点击事件,也可以通过继承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的初始化 + +/// +/// 构造函数 +/// +/// 问题配置文件 +public QuestionsHandler(QuestionsProfile profile) +/// +/// 构造函数 +/// +/// 配置文件的路径 +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 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为分数项ID,Description为分数项描述信息,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; +} +/// +/// 任务开始时重置示例状态 +/// +private void OnTaskStarted(TaskData task) +{ + Debug.Log("任务开始,等待操作输入"); + +} +步骤 2:创建并添加 TaskStep,注:step生命周期中OnStepHint +事件只有当任务不为考核时才执行 +/// +/// 添加 Step +/// +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.ReportCurrentStepResult(false); +若当前步骤错误完成,则 + TaskManager.Instance.ReportCurrentStepResult(true); +→→→→→→设计原则错误为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.Instance; +a. myStr = string.Empty; +//单例释放 +Singleton.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.Instance.Func(); +单例释放:MonoSingleton.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 dic = new Dictionary() { { 5, "AAA" +}, { 10, "BBB" } }; + + //拷贝字典 + Dictionary copy = dic.Copy(); + //遍历字典 + dic.ForEach(m => Debug.Log(string.Format("Key{0} Value{1}", m.Key, +m.Value))); + + Dictionary target = new Dictionary() { { +11, "CCC" }, { 20, "DDD" } }; + //合并字典 + dic.AddRange(target); + + //将字典的所有值放入到一个列表中 + List 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("北京时间", 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("北京时间", 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,支持png,jpg,jpeg三种图片格式,返回Texture2D +[pic] +音频加载 +音频加载与图片加载类似,同样支持两种方式,当使用单个文件加载的时候,需以完整 +路径结束,音频加载支持mp3,ogg,wav三种格式,调用onComplete回调使用音频,方法调 +用以协程的方式调用: +[pic] + + +实用工具 + +Variables资源管理类 +资源管理类实现了一个方便资源进行分类管理的脚本,使用方法如下图: + public Variables variables; +[pic] + + +第一个参数为资源名称,第二个参数为类型,如下所示,第三个参数为需要指定的物体 +,其中component可以是该物体身上的任意可获取的组件: +[pic] +获取方法: public Button enterBtn; enterBtn = +variables.Get