主要技术:UI,Cavens,血条显示,玩家信息UI,主菜单UI

血条显示UI

  • 新建UI -> Cavens(UI挂载在上面) -> image(血条)

QQ截图20220811191225

  • 2D Sprite:2D图形插件

QQ截图20220811191804

  • 血量显示:设置两层血条,上层按血量比例显示

QQ截图20220811192434

  • Action 方法
1
2
3
4
5
using System;
// Action方法 攻击的时候更新参数
public event Action<int, int> UpdateHealthBarOnAttack;
// 血条UI和任务升级属性等
UpdateHealthBarOnAttack?.Invoke(CurrentHealth, MaxHealth);

取子物体,按子物体顺序 0 1 2 ,子物体的子物体 0 0, 01……

1
healthSlider = UIbar.GetChild(0).GetComponent<Image>();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# HeathBarUI
public class HeathBarUI : MonoBehaviour
{
public GameObject healthbarPrefab;
public Transform barpoint; // 血条位置
public bool alwaysVisible; // 是否长久可见

public float visibleTime; // 可视化时间
public float timeLeft; // 剩余可显示时间

Image healthSlider; // 血条滑动
Transform UIbar;
Transform cam; // 血条与摄像机平行
CharacterStates currentStates; // 传入数值

void Awake()
{
currentStates = GetComponent<CharacterStates>();
// 受到伤害时启用
currentStates.UpdateHealthBarOnAttack += UpdataHealthBar;
}
// 人物启用就调用 OnEnable
void OnEnable()
{
// 相机位置
cam = Camera.main.transform;
// 世界空间生成HealthBar
foreach (Canvas canvas in FindObjectsOfType<Canvas>()) // 遍历UI
{
if (canvas.renderMode == RenderMode.WorldSpace) // 世界坐标
{
// 生成底部血条
UIbar = Instantiate(healthbarPrefab, canvas.transform).transform;
// 上方滑动血条
healthSlider = UIbar.GetChild(0).GetComponent<Image>();
UIbar.gameObject.SetActive(alwaysVisible);
}
}
}
// 更新血条
private void UpdataHealthBar(int currentHealth, int maxHealth)
{
if (currentHealth <= 0)
Destroy(UIbar.gameObject);
// 被攻击时必然显示
UIbar.gameObject.SetActive(true);
timeLeft = visibleTime;
// 血条比例
float sliderPercent = (float)currentHealth / maxHealth;
healthSlider.fillAmount = sliderPercent; 】
}
// 上一帧渲染之后在执行
void LateUpdate()
{
if (UIbar != null)
{
UIbar.position = barpoint.position; // 跟随角色
UIbar.forward = -cam.forward; // 血条正对镜头
// 计时器
if (timeLeft <= 0 && !alwaysVisible)
UIbar.gameObject.SetActive(false);
else
timeLeft -= Time.deltaTime;
}
}
}


玩家信息显示UI

  • 显示内容:血量 经验值 等级

    血条:GameManager.Instance.playStats.CurrentHealth / GameManager.Instance.playStats.MaxHealth

    经验值:GameManager.Instance.playStats.characterData.currentExp / GameManager.Instance.playStats.characterData.baseExp

QQ截图20220812020618

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# PlayerHealthUI
public class PlayerHealthUI : MonoBehaviour
{
// 等级文本
Text levelText;
// 血条图片
Image healthSlider;
// 经验值图片
Image expSlider;

void Awake()
{
levelText = transform.GetChild(2).GetComponent<Text>();
healthSlider = transform.GetChild(0).GetChild(0).GetComponent<Image>();
expSlider = transform.GetChild(1).GetChild(0).GetComponent<Image>();
}

// 同步血量需要 update 每帧检查
void Update()
{
// 显示为两位 00
levelText.text = "Level " + GameManager.Instance.playStats.characterData.currentLevel.ToString("00");
UpdateHealth();
UpdateExp();
}

// 血量
void UpdateHealth()
{
float sliderPrecent = (float)GameManager.Instance.playStats.CurrentHealth / GameManager.Instance.playStats.MaxHealth;
healthSlider.fillAmount = sliderPrecent;
}

// 经验值
void UpdateExp()
{
float sliderPrecent = (float)GameManager.Instance.playStats.characterData.currentExp / GameManager.Instance.playStats.characterData.baseExp;
expSlider.fillAmount = sliderPrecent;
}
}

  • 调用
1
2
3
4
5
# CharacterStates TakeDamage
// 死了就自身killPoint加到攻击者
if (CurrentHealth <= 0)
attacker.characterData.UpdateExp(characterData.killPoint);
GameManager.Instance.playStats.characterData.UpdateExp(characterData.killPoint);

主菜单UI

  • UI 视觉差调整

QQ截图20220815180749

  1. 设置 Pender Mode 为Screen Space - Camera,选择 Main Camera
  2. 调整位置QQ截图20220815180921

  3. 设置 Pender Mode 为 World Space 产生视觉差

MenuCanvas 挂载 MainMenu,设置每个按钮的功能,如何触发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# MainMenu
public class MainMenu : MonoBehaviour
{
Button newGameButton;
Button continueButton;
Button quitButton;

private void Awake()
{
// 三个菜单挂载的按键
newGameButton = transform.GetChild(1).GetComponent<Button>();
continueButton = transform.GetChild(2).GetComponent<Button>();
quitButton = transform.GetChild(3).GetComponent<Button>();
newGameButton.onClick.AddListener(NewGame);
continueButton.onClick.AddListener(ContinueGame);
// 单击退出
quitButton.onClick.AddListener(QuitGame);
}

void NewGame()
{
// 清除所有记录
PlayerPrefs.DeleteAll();
// 转换场景
SceneControllor.Instance.TransitionToFirstLevel();
}

void ContinueGame()
{
// 转换场景
// 读取进度
SceneControllor.Instance.TransitionToLoadGame();
}

void QuitGame()
{
Application.Quit();
Debug.Log("退出游戏");
}
}

空Object 挂载 SceneControllor

通过传送门 Destination 位置切换场景生成人物

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# SceneControllor 
public void TransitionToMain()
{
StartCoroutine(LoadMain());
}

public void TransitionToLoadGame()
{
StartCoroutine(LoadLevel(SaveManager.Instance.SceneName));
}

public void TransitionToFirstLevel()
{
StartCoroutine(LoadLevel("Sky1"));
}
// 协程保存等级
IEnumerator LoadLevel(string scene)
{
if (scene != "")
{
yield return SceneManager.LoadSceneAsync(scene);
yield return player = Instantiate(playerPrefab, GameManager.Instance.GetEntrance().position, GameManager.Instance.GetEntrance().rotation);
// 保存数据
SaveManager.Instance.SavePlayerData();
yield break;
}
}
// 学成加载主菜单
IEnumerator LoadMain()
{
yield return SceneManager.LoadSceneAsync("Menu");
yield break;
}

生成人物先读取存档

1
2
3
4
5
# PlayerController
void Start()
{
SaveManager.Instance.LoadPlayerData();
}

保存数据 读档当时场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# SaveManager
public class SaveManager : Singleton<SaveManager>
{
string sceneName = "level";

public string SceneName { get { return PlayerPrefs.GetString(sceneName); } }

protected override void Awake()
{
base.Awake();
DontDestroyOnLoad(this);
}

void Update()
{
if (Input.GetKeyDown(KeyCode.Escape)) // ESC返回Main界面
{
SceneControllor.Instance.TransitionToMain();
}

if (Input.GetKeyDown(KeyCode.S))
{
SavePlayerData();
}

if (Input.GetKeyDown(KeyCode.L))
{
LoadPlayerData();
}
}

public void SavePlayerData()
{
Save(GameManager.Instance.playStats.characterData, GameManager.Instance.playStats.characterData.name);
}

public void LoadPlayerData()
{
Load(GameManager.Instance.playStats.characterData, GameManager.Instance.playStats.characterData.name);
}

// 存档(在注册表)
public void Save(Object data, string Key) // Object最大基类 Key为名字
{
var jsonData = JsonUtility.ToJson(data, true); // 转换为JSON
PlayerPrefs.SetString(Key, jsonData); // 保存至系统磁盘
PlayerPrefs.SetString(sceneName, SceneManager.GetActiveScene().name); // 保存场景名,用 SceneManager.GetActiveScene().name 返回到sceneName
PlayerPrefs.Save();
}

// 读档
public void Load(Object data, string key)
{
// HashKey 判断是否存有数据
if (PlayerPrefs.HasKey(key))
{
JsonUtility.FromJsonOverwrite(PlayerPrefs.GetString(key), data);
}
}
}