修改luna奇幻之旅(学习笔记自用)

第一章 动态加载人物

        在根据chatgpt的指引下,我终于完成了从数据文件里调用模型加入我的BattleGo。当然一些脚本也是参考了以前的前辈(Unity通过协程实现回合制战斗 | Unity 中文课堂),然后我在上面添砖加瓦,缝缝补补。但他的这个教程是没有完成的,但我目前已经给这个教程添加了动画实现,还有人物动态加载(通过id)。

1.第一步:创建角色脚本

          首先我是创建了一个脚本。使用unity自带的ScriptTable来创建角色,代码如下:

using UnityEngine;
using System.Collections.Generic;
using UnityEditor.Experimental.GraphView;

[CreateAssetMenu(fileName = "CharacterName", menuName = "创建战斗角色1", order = 1)]
public class CharacterData : ScriptableObject
{
    public int id;
    /// <summary>
    /// 角色名
    /// </summary>
    public string characterName;

    /// <summary>
    /// 角色血量
    /// </summary>
    public int hp;

    /// <summary>
    /// 角色蓝量
    /// </summary>
    public int mp;

    /// <summary>
    /// 每回合回蓝量
    /// </summary>
    public int mpRecoverAmount;

    /// <summary>
    /// 角色攻击力
    /// </summary>
    public int atk;

    /// <summary>
    /// 暴击伤害(百分比)
    /// </summary>
    public int critDmg = 100;

    /// <summary>
    /// 暴击率
    /// </summary>
    [Range(0, 100)] public int critRate;

    /// <summary>
    /// 角色百分比防御力
    /// </summary>
    [Range(0, 100)] public int defRate;

    /// <summary>
    /// 角色百分比闪避率
    /// </summary>
    [Range(0, 100)] public int duckRate;

    /// <summary>
    /// 技能
    /// </summary>
    public Skill[] skills;

    /// <summary>
    /// 动画控制器路径
    /// </summary>
    public string aniContPath;

    /// <summary>
    /// 角色战斗物体加载
    /// </summary>
    public GameObject gameObjectBattle;
    /// <summary>
    /// 角色战斗物体加载路径
    /// </summary>
    public string gameObjectBattlePath;



    public Character ToCharacter()
    {
        return new Character()
        {
            Name = characterName,
            Hp = hp,
            InitialHp = hp,
            Mp = mp,
            InitialMp = mp,
            MpRecoverAmount = mpRecoverAmount,
            Atk = atk,
            CritDmg = critDmg,
            CritRate = critRate,
            DefRate = defRate,
            DuckRate = duckRate,
            Skills = skills,
            AniContPath = aniContPath,
            GameObjectBattle = gameObjectBattle,
            GameObjectBattlePath = gameObjectBattlePath
        };
    }
}

public class Character
{
    /// <summary>
    /// 角色名
    /// </summary>
    public string Name;

    /// <summary>
    /// 角色初始血量
    /// </summary>
    public int InitialHp;

    /// <summary>
    /// 角色血量
    /// </summary>
    public int Hp;

    /// <summary>
    /// 角色初始蓝量
    /// </summary>
    public int InitialMp;

    /// <summary>
    /// 角色蓝量
    /// </summary>
    public int Mp;

    /// <summary>
    /// 每回合回蓝量
    /// </summary>
    public int MpRecoverAmount;

    /// <summary>
    /// 角色攻击力
    /// </summary>
    public int Atk;

    /// <summary>
    /// 暴击伤害(百分比)
    /// </summary>
    public int CritDmg;

    /// <summary>
    /// 暴击率
    /// </summary>
    public int CritRate;

    /// <summary>
    /// 角色百分比防御力
    /// </summary>
    public int DefRate;

    /// <summary>
    /// 角色百分比闪避率
    /// </summary>
    public int DuckRate;

    /// <summary>
    /// 角色是否死亡
    /// </summary>
    public bool Dead => Hp <= 0;

    /// <summary>
    /// 技能
    /// </summary>
    public Skill[] Skills;

    /// <summary>
    /// 动画控制器路径
    /// </summary>
    public string AniContPath;

    /// <summary>
    /// 技能cd字典
    /// </summary>
    public Dictionary<Skill, int> SkillCountdown = new Dictionary<Skill, int>();
    /// <summary>
    /// 动画控制器
    /// </summary>
    public AnimationManager AnimationManager { get; set; }
    /// <summary>
    /// 角色战斗物体加载
    /// </summary>
    public GameObject GameObjectBattle;
    /// <summary>
    /// 角色战斗物体加载路径
    /// </summary>
    public string GameObjectBattlePath;
}

有了这个脚本,我就能在Resources文件夹中创建我需要的角色表格了。(记得一定得是Resources,但凡多一个少一个字母都不行。方便后面使用Resources.Load<>()方法来进行加载人物表),大概就是这样:

2.第二步:建立角色数组

          接着,我又创了个建立战斗人物表格数组的脚本:(本来想把这两个放一个脚本,结果发现会导致重启unity后,数组这个会丢失脚本。)

using UnityEngine;

[CreateAssetMenu(fileName = "CharacterDataTable", menuName = "创建战斗角色数组", order = 2)]
/// <summary>
/// 存储所有角色的数据表数组
/// </summary>
public class CharacterDataTable : ScriptableObject
{
    public CharacterData[] characterDataArray;
}

有了他,我们就能去创建数组来容纳,上面所创的人物表格了。大概样子是这样滴。

大概样子是这样滴。这个战斗角色数组建立后,还可以建立背包数组、道具数组等等各种,最重要的是可以根据不同任务建立不同的角色数组,去进行任务人物的加载。比如说我想要这个A任务有4、5、6人物去作为敌人,那么直接将数组A设定为[4,5,6]就好了,然后加载数组A。

 3.第三步:根据id获取角色数据

          去GameManager里面新写一个方法,根据id去获取这个人物。

/// <summary>
/// 根据id获取角色战斗数组中的角色数据///
/// </summary>
/// <param name="characterId"></param>
/// <returns></returns>
public CharacterDataTable characterDataTable;  // 指定ScriptableObject的表
public CharacterData GetCharacterDataById(int characterId)
{
    //characterDataTable = new List<CharacterDataTable>(Resources.LoadAll<CharacterDataTable>("CharacterDatas/战斗角色数组"));
    // 加载资源
    characterDataTable = Resources.Load<CharacterDataTable>("CharacterDatas/BattleCharacterArray");
    // 确保资源成功加载
    if (characterDataTable == null)
    {
        Debug.LogError("没找到战斗数组");
        return null;
    }
    foreach (var characterData in characterDataTable.characterDataArray)
    {
        if (characterData.id == characterId)
        {
            return characterData;
        }
    }
    return null;  // 如果找不到则返回null
}

记得路径得和自己的一样。

4.第四步:给角色赋值

然后去FightMgr(战斗逻辑脚本),给Players和Enemys赋值。

public CharacterDataTable characterDataTable;  // 指定ScriptableObject的表
/// <summary>
/// 根据id加载角色
/// </summary>
/// <param name="characterId"></param>
public CharacterData LoadCharacterById(int characterId)
{
    // 查找指定id的角色数据
    CharacterData characterData = GameManager.Instance.GetCharacterDataById(characterId);
    return characterData;
}

private IEnumerator Fight(Action sucCallback, Action lostCallback)
{
    int round = 1;//回合数
    bool playerTurn = true;//是否是玩家的回合

    for (int i = 0; i < playerData.Length; i++)
    {
        playerData[i] = LoadCharacterById(i);
    }
    for (int i = 0; i < enemyData.Length; i++)
    {
        enemyData[i] = LoadCharacterById(i+3);
    }

    //这里用到了Select语法,传了一个lambda匿名委托,
    //大概意思就是从playerData和enemyData数组中取每个元素的ToCharacter方法返回的对象,并转为新数组
    Character[] player = playerData.Select(d => d.ToCharacter()).ToArray();
    Character[] enemy = enemyData.Select(d => d.ToCharacter()).ToArray();

    省略了
}

这里后面可以去设置任务id赋值。目前是写了Players的队伍是0.1.2,然后Enemy的队伍是3.4.5

然后后面自己运行了游戏,发现可以加载成功。心情很不错 ,哈哈哈。

4.1遇到问题:

目前的问题是我发现游戏物体可以赋值,但是游戏ui的赋值好像是一步到位。

运行后就直接变成这样,后面经过一番苦战,问题研究出来了。原来是我为了想优化代码运行逻辑,想在FightMgr脚本里直接存储一个游戏内敌人和友军物体的数组,并把他放在Awake()里(之所以这么放,是因为好让PlayerUIMgr脚本去引用它)所以才出现了这个问题。

我的解决办法:

先写了个方法

/// <summary>
/// 玩家队伍和敌人队伍的游戏物体引用
/// </summary>
private void InitGameObj()
{
    for (int i = 0; i < gameObjectsP.Length; i++)
    {
        gameObjectsP[i] = GameObject.Find("Player/P" + (i + 1)+"(Clone)");
    }
    for (int i = 0; i < gameObjectsE.Length; i++)
    {
        gameObjectsE[i] = GameObject.Find("Enemy/E" + (i + 1)+"(Clone)");
    }
}

①用这个方法去寻找加载的预制体物体,并把这个方法放在Fight协程开始的上方

②修改PlayerUIMgr脚本中的方法,给他再加一个参数playersObjects,方便从FightMgr的参数可以直接实现,就不需要在PlayerUIMgr脚本中再赋值一遍了。只需要让InitGameObj()方法在协程里进行赋值就OK了。

/// <summary>
/// 更新队友血量
/// </summary>
/// <param name="players"></param>
public void UpdatePlayerHp(Character[] players,GameObject[] playersObjects)

{
    //for (int i = 0; i < gameObjectsP.Length; i++)
    //{
    //    gameObjectsP[i] = GameObject.Find("Player/P" + (i + 1));
    //}


    for (int i = 0; i < players.Length; i++)
    {
        if (i < playersObjects.Length)
        {
            GameObject gameObject = playersObjects[i];
            Transform hp = gameObject.transform.Find("Canvas/HP");
            Transform hpText = gameObject.transform.Find("Canvas/HPText");
            Slider hpSlider = hp.GetComponent<Slider>();
            Text text = hpText.GetComponent<Text>();
            hpSlider.maxValue = players[i].InitialHp;
            hpSlider.value = players[i].Hp;
            text.text = $"HP: {players[i].Hp}/{players[i].InitialHp}";
            Transform animator = gameObject.transform.Find("ani");
            Animator ani = animator.GetComponent<Animator>();
            
            //ani.SetBool("IsDead", players[i].Dead);
        }
    }
}

OK  吃饭去了。

5.第五步:LoadPrefb()

这中间我又做了很多事,因为一些事,没有记下来。不过此篇笔记也只是给自己一个学习的记忆。

我又在GameManager里创了两个int数组,让他们用来存放玩家队伍和敌人队伍,战斗的时候只需要调用这个int整数就能获得id,然后再获得游戏物体。这确实是一个很便捷的办法。

关于Fight脚本的Start()里我就写了一个方法:LoadPrefb();具体代码如下:

 /// <summary>
 /// 加入角色表里的预制体
 /// </summary>
 private void LoadPrefb()
 {
     GameObject battleGo = GameObject.Find("BattleGo");
     // 获取Player和Enemy物体
     GameObject player = battleGo.transform.Find("Player").gameObject;
     GameObject enemy = battleGo.transform.Find("Enemy").gameObject;

     // 将预制体实例化到P1, P2, P3以及E1, E2, E3
     for (int i = 0; i < playerData.Length; i++)
     {
         //加载角色战斗数据(因为我会把每个数组到时候初始化为-1,如果是-1说明该数组没有加载角色进来)
         if (GameManager.Instance.playersIdArray[i] != -1)
         {
             
             playerData[i] = GameManager.Instance.LoadCharacterById(GameManager.Instance.playersIdArray[i]);
             // 替换Player下的P1, P2, P3
             GameObject t = GameManager.Instance.LoadGameObjectById(GameManager.Instance.playersIdArray[i]);
             t.name = "P"+(i+1);
             ReplaceWithPrefab(player.transform.Find("P" + (i + 1)), t);
             gameObjectsP[i] = GameObject.Find("Player/P" + (i + 1) + "(Clone)");
             //绑定好物体和数据,为后面技能随机生成的数组好赋值
             playerData[i].gameObjId = i;
             Debug.Log(GameManager.Instance.playersIdArray[i]);
         }
         else
         {
             continue;
         }
     }
     for (int i = 0; i < enemyData.Length; i++)
     {
         //加载角色战斗数据(因为我会把每个数组到时候初始化为-1,如果是-1说明该数组没有加载角色进来)
         if (GameManager.Instance.enemyIdArray[i] != -1)
         {
             
             enemyData[i] = GameManager.Instance.LoadCharacterById(GameManager.Instance.enemyIdArray[i]);
             // 替换Enemy下的E1, E2, E3
             GameObject t = GameManager.Instance.LoadGameObjectById(GameManager.Instance.enemyIdArray[i]);
             t.name = "E" + (i + 1);
             //Debug.Log(t.name);
             ReplaceWithPrefab(enemy.transform.Find("E" + (i + 1)), t);
             gameObjectsE[i] = GameObject.Find("Enemy/E" + (i + 1) + "(Clone)");
             //绑定好物体和数据,为后面技能随机生成的数组好赋值
             enemyData[i].gameObjId = i;
             Debug.Log(GameManager.Instance.enemyIdArray[i]);
         }
         else
         {
             continue;
         }
         
         //gameObjectsE[i+3] = t;
     }
 void ReplaceWithPrefab(Transform targetTransform,GameObject gameObjectPrefab)
 {
     if (targetTransform != null)
     {
         // 获取原始物体
         GameObject originalObject = targetTransform.gameObject;

         // 记录原始物体的位置和父物体
         Vector3 position = originalObject.transform.position;
         Transform parent = originalObject.transform.parent;

         // 删除原物体
         Destroy(originalObject);

         // 实例化新的预制体
         Instantiate(gameObjectPrefab, position, Quaternion.identity, parent);
     }
     else
     {
         Debug.LogWarning("Target transform not found!");
     }
 }

OK,那么这样我就能很好的去更新血条和游戏物体了

因为我脚本里的技能释放目标是通过Random()随机生成的,所以opponent和gameObject并不能绑定到一起。当时就发现动画和血条物体等不匹配。我当时弄了一个小时左右吧(萌新的水平T_T),去问gpt,gpt叫我做个词典去映射,太扯了。我都是随机生成的,难道弄个动态映射。(可能是自己表达不清吧)在下去吃饭的时候突然想到,之前通过修改t.name来强制让他们变成P1、E1等,我是不是也可以在CharacterData里再添加一个属性GameObjId来锁定这个物体,在物体赋值时便进行修改。果不其然,这样的属性修改,比词典要好用的多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值