主要技术:Scripts Object 数值文件,角色升级系统,JSON数据保存

Scripts Object 数值文件

CharacterData 人物数值

生命数值

  • 在菜单中创建数值文件类型

    [CreateAssetMenu(fileName ="New Data", menuName = "Character Stats/Data")]

    QQ截图20220905134242

  • 生命基础数值类型(玩家和敌人各一个)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # CharacterData_SO
    [CreateAssetMenu(fileName ="New Data", menuName = "Character Stats/Data")]
    public class CharacterData_SO : ScriptableObject
    {
    [Header("Stats Info")]
    // 最大血量
    public int maxHealth;
    // 当前血量
    public int currentHealth;
    // 基础防御
    public int baseDefence;
    // 当前防御
    public int currentDefence;
    [Header("Kill")]
    public int killPoint;
    }

    QQ截图20220905134655

  • 读取数据类型:用属性的方法读写给GameObject(挂载该脚本给玩家和敌人)

    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
    # CharacterStates
    public class CharacterStates : MonoBehaviour
    {
    public CharacterData_SO characterData;
    public AttackData_SO attackData;
    // 可读 可写
    // 最大生命值
    public int MaxHealth
    {
    get { if (characterData != null) return characterData.maxHealth; else return 0; }
    set { characterData.maxHealth = value; }
    }
    // 当前生命值
    public int CurrentHealth
    {
    get{ if (characterData != null) return characterData.currentHealth; else return 0; }
    set{ characterData.currentHealth = value; }
    }
    // 基础防御力
    public int BaseDefence
    {
    get{if (characterData != null) return characterData.baseDefence; else return 0; }
    set{ characterData.baseDefence = value; }
    }
    // 当前防御力
    public int CurrentDefence
    {
    get{ if (characterData != null) return characterData.currentDefence; else return 0; }
    set{ characterData.currentDefence = value; }
    }
    }
  • 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # PlayerController
    public class PlayerController : MonoBehaviour
    {
    private CharacterStates characterStates;
    void Awake()
    {
    characterStates = GetComponent<CharacterStates>();
    }
    void Start()
    {
    characterStates.MaxHealth = 2;
    }

攻击数值

  • 在菜单创建攻击数值模板

    [CreateAssetMenu(fileName = "New Attack", menuName = "Attack/Attack Data")]

    QQ截图20220905141050

  • 攻击数值类型(玩家和敌人各一个)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    [CreateAssetMenu(fileName = "New Attack", menuName = "Attack/Attack Data")]
    public class AttackData_SO : ScriptableObject
    {
    //攻击距离
    public float attackRange;
    //远程距离
    public float skillRange;
    //冷却时间
    public float coolDown;
    //最小攻击值
    public float minDange;
    // 最大攻击值
    public float maxDamage;
    // 暴击
    public float criticalMultiplier;
    //暴击率
    public float criticalChance;
    }
  • 使用 characterStates.attackData.attackRange

    1
    2
    3
    4
    5
    6
    7
    # Player Controller
    //修改攻击范围参数,持续不断循环>攻击范围,武器长度
    while (Vector3.Distance(attackTarget.transform.position, transform.position) > characterStates.attackData.attackRange)
    {
    agent.destination = attackTarget.transform.position; // 点击目标
    yield return null;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # Enemy Controller
    // 近战
    bool TargetInAttackRange()
    {
    if (attackTarget != null)
    return Vector3.Distance(attackTarget.transform.position, transform.position) <= characterStates.attackData.attackRange;
    else
    return false;
    }
    // 远程
    bool TargetInSkillRange()
    {
    if (attackTarget != null)
    return Vector3.Distance(attackTarget.transform.position, transform.position) <= characterStates.attackData.skillRange;
    else
    return false;
    }

攻击效果计算

  • 伤害计算:攻击 - 防御 出现暴击就 *暴击百分比

    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
    # CharacterStates   
    // 受伤计算方式
    public void TakeDamage(CharacterStates attacker, CharacterStates defener) // 两个Character类型的参数
    {
    // 攻击者的攻击-受攻击的防御 防御可能高于其攻击,最小为0
    int damage = Math.Max(attacker.CurrentDamage() - defener.CurrentDefence, 0);
    // 血量最低为0
    CurrentHealth = Math.Max(CurrentHealth - damage, 0);
    // 暴击
    if (attacker.isCritical)
    {
    defener.GetComponent<Animator>().SetTrigger("Hit");
    }
    }
    // 函数重载 直接写即可
    public void TakeDamage(int damage, CharacterStates defener)
    {
    int currentdamage = Mathf.Max(damage - defener.CurrentDefence, 0);
    CurrentHealth = Mathf.Max(CurrentHealth - currentdamage, 0);
    }

    private int CurrentDamage()
    {
    float coreDamage = UnityEngine.Random.Range(attackData.minDange, attackData.maxDamage);
    if (isCritical)
    {
    coreDamage *= attackData.criticalMultiplier;
    Debug.Log("暴击!" + coreDamage);
    }
    return (int)coreDamage;
    }

动画关键帧添加事件

即动画在执行该关键帧时触发事件,事件中的n个函数执行,产生效果

1
2
3
4
5
6
void Hit()
{
// 获取目标数据
var targetStates = attackTarget.GetComponent<CharacterStates>();
targetStates.TakeDamage(characterStates, targetStates);
}

升级系统

  • 数值参数里面添加
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
# CharacterData_SO

[Header("Kill")]
public int killPoint;

[Header("Level")]
public int currentLevel; // 当前等级
public int maxLevel; // 最高等级
public int baseExp; // 基础经验
public int currentExp; // 当前经验
public float levelBuff; // 升级加成
// 每次升级要增加的经验:2级 2-1=1级需要的经验*buffer
public float LevelMultiplier{ get { return 1 + (currentLevel - 1) * levelBuff; } }
// 小怪死亡加的经验
public void UpdateExp(int point)
{
currentExp += point;

if (currentExp >= baseExp)
LevelUp();
}
// 升级
private void LevelUp()
{
// 提升方法
// 返回最大值,不会超过最高等级
currentLevel = Mathf.Clamp(currentLevel + 1, 0, maxLevel);
// 升级下阶段需要
baseExp += (int)(baseExp * LevelMultiplier);
// 升级血量上升
maxHealth = (int)(maxHealth * levelBuff);
currentHealth = maxHealth;
Debug.Log("Level Up!" + currentLevel + "Max Health: " + maxHealth);
}
  • 调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# CharacterStates

// 受伤计算方式, 两个CharacterStates类型的参数,传进来两个数据
public void TakeDamage(CharacterStates attacker, CharacterStates defener)
{
// 攻击者的攻击-受攻击的防御 防御可能高于其攻击,最小为0
int damage = Math.Max(attacker.CurrentDamage() - defener.CurrentDefence, 0);
CurrentHealth = Math.Max(CurrentHealth - damage, 0);
// 暴击
if (attacker.isCritical)
{
defener.GetComponent<Animator>().SetTrigger("Hit");
}
// 血条UI和任务升级属性等
UpdateHealthBarOnAttack?.Invoke(CurrentHealth, MaxHealth);
// attacker添加经验值
if (CurrentHealth <= 0)
attacker.characterData.UpdateExp(characterData.killPoint); // 死了就自身killPoint加到攻击者
}

保存数据

JSON 类型变成 String 保存 SetString

  • object 基类
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
# SaveManager
public class SaveManager : Singleton<SaveManager>
{
protected override void Awake()
{
base.Awake();
DontDestroyOnLoad(this);
}

void Update()
{
// S存档
if (Input.GetKeyDown(KeyCode.S))
{
SavePlayerData();
}
// l读档
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为名字
{
// 转换为JSON
var jsonData = JsonUtility.ToJson(data, true);
// 保存至系统磁盘
PlayerPrefs.SetString(Key, jsonData);
PlayerPrefs.Save();
}
// 读档
public void Load(Object data, string key)
{
// HashKey 判断是否存有数据
if (PlayerPrefs.HasKey(key))
{
JsonUtility.FromJsonOverwrite(PlayerPrefs.GetString(key), data);
}
}
}