目录
(一)玩家数据保存
(二)制作主菜单
(三)制作NewGame场景转换
(四)制作场景渐入渐出
(五)玩家死亡返回Menu
(一)玩家数据保存
创建新的脚本SaveManager专门负责保存数据。为了能够随时调用并保存数据,所以使用泛型单例与设置DontDestroyOnLoad。玩家数据的保存则使用JsonUtility+PlayerPrefs的形式存储,PlayerPrefs提供给玩家int,float,string三个类型进行储存与修改,以键值对的形式存储;而JsonUtility可以对ScriptableObject等类进行存储。存储时先将数据转为json格式,再使用PlayerPrefs进行存储,读取同理。
保存的数据需要在转换场景时仍然保存,在SceneController的传送协程中,切换场景之前和之后分别进行Save和Load。(顺便把其他场景的UI补上)
SaveManager:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SaveManager : Singleton<SaveManager>
{
protected override void Awake()
{
base.Awake();
DontDestroyOnLoad(this);//防止Manager被销毁
}
private void Update()
{
if(Input.GetKeyDown(KeyCode.S))
{
SavePlayerData();
}
if (Input.GetKeyDown(KeyCode.L))
{
LoadPlayerData();
}
}
public void SavePlayerData()//存储玩家数据
{
Save(GameManager.Instance.playerStats.characterData, GameManager.Instance.playerStats.characterData.name);
}
public void LoadPlayerData()
{
Load(GameManager.Instance.playerStats.characterData, GameManager.Instance.playerStats.characterData.name);
}
public void Save(object data, string key)
{
var jsonData = JsonUtility.ToJson(data,true);//数据格式转换为json
PlayerPrefs.SetString(key, jsonData);//构造键值对与存储
PlayerPrefs.Save();
}
public void Load(object data, string key)
{
if(PlayerPrefs.HasKey(key))//找到key则读取
{
JsonUtility.FromJsonOverwrite(PlayerPrefs.GetString(key), data);
}
}
}
(二)制作主菜单
首先进行主菜单场景的布置,注意当利用player等prefab进行布置时,最好解包并关闭一些组件。
创建菜单Canvas,调整比例并添加配件。当UI字体模糊时,尝试提高字号的同时将Scale调小会使字体清晰。将Canvas的RenderMode调整为Camera,这样UI界面就在所设定的摄像机的视野中进行布局,再改成WorldSpace模式就可以以合适的初始位置来调整UI的空间视觉效果(自己的作业本来不想改,但是后面做转换效果更好所以改了。)。
下面编写主菜单的脚本并挂载到主菜单Canvas上。主菜单的功能按钮在脚本中以事件的形式产生作用,可以采用button.onClick.AddListener(xxx)来为按钮事件添加xxx方法。新游戏和继续游戏都需要转换到对应场景并加载Player,这需要在SceneController中再写一个新的协程,在GameManager中完成获得场景入口的方法:
public Transform GetEntrance()
{
foreach(var i in FindObjectsOfType<TransitionDestination>())
{
if(i.destinationTag== TransitionDestination.DestinationTag.Enter)
{
return i.transform;
}
}
return null;
}
这样在协程中调用此方法来作为player生成的位置。完成场景加载和玩家生成后,保存数据并结束协程。协程在方法中调用,而方法与事件绑定。(泛型单例在重复时会删除到只剩一个,所以不用担心Manager重复)。传送方法:
//给NewGame使用跳转到第一个场景
public void TransitionToFirstScene()
{
StartCoroutine(LoadLevel("Level1"));
}
IEnumerator LoadLevel(string sceneName)
{
if(sceneName!="")//找到场景,跳转并生成player
{
yield return SceneManager.LoadSceneAsync(sceneName);
yield return player = Instantiate(PlayerPrefab, GameManager.Instance.GetEntrance().position, GameManager.Instance.GetEntrance().rotation);
//保存数据以便下次继续游戏
SaveManager.Instance.SavePlayerData();
yield break;
}
}
最后在按钮处调用即可。
读档需要让玩家也能回到原本的场景,因此需要在Save同时保存所在场景到PlayerPrefs中,并在SceneController实现读取游戏的传送方法:
public void TransitionToLoadScene()
{
StartCoroutine(LoadLevel(SaveManager.Instance.SceneName));
}
玩家数据的读取则应当在player开始时获取,所以写在PlayerController的start中,且玩家注册到游戏中的步骤则应该调整到OnEnable中。可以在Manager的Update中判断按下Input.GetKeyDown(KeyCode.Escape),是则跳转回主界面,跳转本身的实现则需要像上面一样在SceneController中再写一次方法和协程:
public void TransitionToMainScene()
{
StartCoroutine(LoadMain());
}
IEnumerator LoadMain()
{
yield return SceneManager.LoadSceneAsync("Main");
yield break;
}
这样主菜单的基本功能就完成了,此时的存档会在按下对应按键(Update中设置)、开始与加载游戏以及跨场景传送时触发。
(三)制作NewGame场景转换
单纯的卡顿不利于场景转换的表现,下面需要制作场景的转换效果。Timeline是一种针对多对象的动画制作功能,一般用于过场动画的制作,其实际操作类似于PR,将需要控制的物体拖入左侧可以创建track,然后通过录制关键帧来控制动画的播放。比如在相机的Transform position上右键添加Key,就可以在相机的track上添加一个关键帧,两个关键帧之间则会自动产生动画效果。录制希望添加效果的对象的track。对于可以使用Animation的对象,Timeline可以直接将动画拖到时间轴上来播放。对于播放动画时的位置偏移,可以选择Add override track,在一条附属轨道上录制正确的位置动画。
制作的动画希望在点击New Game时播放,所以要取消director的Awake时播放,在MainMenuUI脚本中控制director,director的stopped是动画结束时的事件,所以可以将NewGame注册到这一事件中来,修改一下参数即可。
对于动画播放中仍然可以用键盘控制按钮的问题,可以在Timeline中创建EventSystem的Activation track,删掉active段。
(四)制作场景渐入渐出
使用Canvas Group制作SceneFader效果。创建Canvas并令其image覆盖屏幕,然后添加组件Canvas Group,通过调整其Alpha值来实现渐入渐出的视觉效果,使用脚本SceneFader 控制。
由于透明度的调整与场景加载同步进行,所以采用协程的形式实现,协程的嵌套则可以在yield return中进行:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SceneFader : MonoBehaviour
{
CanvasGroup canvasGroup;
public float fadeInDuration;//渐入时间
public float fadeoutDuration;//渐出时间
private void Awake()
{
canvasGroup = GetComponent<CanvasGroup>();
DontDestroyOnLoad(this);
}
public IEnumerator FadeOutToIn()//完整渐入渐出
{
yield return FadeOut(fadeoutDuration);
yield return FadeIn(fadeInDuration);
}
public IEnumerator FadeOut(float time)//alpha 0-1,渐出
{
while (canvasGroup.alpha < 1)
{
canvasGroup.alpha += Time.deltaTime / time;//???
yield return null;
}
}
public IEnumerator FadeIn(float time)//alpha 1-0,渐入
{
while (canvasGroup.alpha !=0)
{
canvasGroup.alpha -= Time.deltaTime / time;//???
yield return null;
}
}
}
剩下只需要在转换场景前后调用协程即可。
(五)玩家死亡返回Menu
玩家死亡时GameManager发出广播,可让SceneController也监听并执行返回菜单操作。加入监听操作见敌人制作部分(继承与注册)。收到广播方法后启动返回主菜单协程即可。
为了防止GameManager持续广播导致SceneController执行过多游戏崩溃,要在SceneController设置bool flag进行判断,并在每次新游戏开始时重置flag保证其功能正常
(六)打包与运行
将初步完成的项目打包,从而能够脱离unity编辑器来运行。
打包前需要调整Build Settings,需要把涉及到的场景都添加上,并设置运行的平台环境。名字和图标等设置则在左下角的PlayerSettings中。需要多平台支持的话,可以事先在UnityHub中为编辑器添加模块,就可以在PlayerSettings中设置了。更改打包方式为IL2CPP可以比Mono减少游戏大小,且更难反编译。文章来源:https://www.toymoban.com/news/detail-773055.html
打包前要在Edit菜单中清除所有PlayerPrefs,防止将测试存档带入打包文件。文章来源地址https://www.toymoban.com/news/detail-773055.html
到了这里,关于3D RPG Course | Core | Unity学习笔记(九)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!