ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Unity RPG 黑暗之光 问题记录 下(64-110 技能、装备商店、头像栏、快捷栏、敌人攻击系统、玩家攻击系统)

2021-06-26 20:03:30  阅读:337  来源: 互联网

标签:攻击 void float transform int Unity 64 skill public


64-73技能系统

在这里插入图片描述

数据读取

数据 表

黄色枚举,红色没有的
在这里插入图片描述

数据 文本

在这里插入图片描述

数据 读取

背包时是itemList,读取属性时各有不同,无法统一,
这种方式是为了方便读取,所有需要的数据表都可以在DataHub上读取到,不用写重复写名字各异的单例,比如ItemTextAssetToList._intanse.itemList,
SkillIextAssetToList._intanse.skillList
现在写
DataHub._instance.itemList
DataHub._instance.skillList
//后面想想也挺麻烦的,因为GetItemById的方法还在xxxTextAssetToList里面
在这里插入图片描述

不用数组,列表存储数据子节点,是因为前面的名字,只看索引很麻烦
skillList也有些,但不知道为什么没显示出来,public是为了让其他访问到(其实想编辑器看不到,但又让其他类访问到(HideInInspector)
在这里插入图片描述

(问题) VS语法"有毒糖"

VS支持,Unity不支持,所以不支持
当写了5段switch,回去unity一百多个红色

            skill.effectType = propertyArray[4] switch
            {
                "Passive" => EffectType.Passive,
                "Buff" => EffectType.Buff,
                "MultiTarget" => EffectType.MultiTarget,
                "SingleTarget" => EffectType.SingleTarget,
                _ => throw new System.NotImplementedException()
            };

读取成功

   public override string ToString()
    {
        string str = "";
        str += "\t" + id;
        str += "\t" + name;
        str += "\t" + icon_name;
        str += "\t" + description;
            
        str += "\t" + coolTime;
        str += "\t" + applyType;
        str += "\t" + lv;
        str += "\t" + roleLv;
        str += "\t" + costMp;
            
        str += "\t" + effectProperty;
        str += "\t" + effectTarget;
        str += "\t" + effectTime;
        str += "\t" + effectType;
        str += "\t" + effectValue;
        str += "\t" + releaseDistance;

        return str;     
    }
        foreach (Skill skill in skillList)
        {
            print(skill.ToString());
        }

在这里插入图片描述

UI

scrollView下方第一个Item的初始位置的空物体,作为所有技能的容器的父节点
在这里插入图片描述
在这里插入图片描述

(UI) 遮罩

就是一张图,表示未解锁
//需要加上触发器和DragScrollView,不然未解锁部分不能拖拽滚动
在这里插入图片描述
在这里插入图片描述

技能栏项目

SkillItem继承Skill(Skill继承MonoBehaviour,不然没事件机制,编辑器显示也有问题)
SetValue是将skill的值赋值给skillIItem
SetUI做UI显示

public class SkillItem : Skill
{
    public UISprite skillSprite;
    public UILabel nameLabel;
    public UILabel effectTypeLabel;
    public UILabel descriptionLabel;
    public UILabel costMpLabel;
    // Start is called before the first frame update
    void Start()
    {
        SetUI();
    }
    ......

技能栏项目的图片可以拖拽到快捷栏

盒子+UIDragAndDropItem(克隆拖拽)
Start的是克隆的那个,保存父节点的SkillItem后,把父节点移到根节点,让可以移到ScrollView之外
Release的tarnsfrom是克隆的那个,surface是鼠标移到的物体,移到格子,就是格子,根据Tag确定可以放置
Release调用surface(格子)的ShortItem方法,进行图片替换(默认是大小,位置设好的透明图)
在这里插入图片描述

public class SkillSprite : UIDragDropItem
{
    public SkillItem skillItem;
    protected override void OnDragDropStart()
    {
        base.OnDragDropStart();
        skillItem = transform.parent.GetComponent<SkillItem>();
        transform.parent = transform.root;//用回root方式跳出去
        GetComponent<UISprite>().depth = 5;//最高就行
        
    }

    protected override void OnDragDropRelease(GameObject surface)
    {
        base.OnDragDropRelease(surface);

        if (surface != null && surface.tag == "Shortcut")
        {
            surface.GetComponent<ShortcutItem>().SetSkill(skillItem);
        }
    }
}

(代码) 关联玩家等级和职业

视频是只盖住图片,我用的碰撞体盖住整个Item,后果是不可滑动未解锁的技能,scrollBar的depth保证在整个节点组的顶部,不然不能(偶尔可以)滑动
//SkillItem

    public void ShowSkillItem()//根据人物等级和职业显示技能
    {
        if (roleLv > Player._instance.level || applyType!=Player._instance.applyType)
        {
            maskSprite.gameObject.SetActive(true);
        }
        else
        {
            maskSprite.gameObject.SetActive(false);
        }       
    }

74-80 头像

头像框

增加图层,修改图层,调整相机
在这里插入图片描述
运行后,FaceCamrera
在这里插入图片描述
新建simpleTexture到场景,然后指定刚才的那张图
在这里插入图片描述

lv,name,exp,hp,mp

Player设int需要转float到value显示

exp,hp,mp用slider的value,视频是用exp+“/”+maxExp的格式

NPC在小地图上的UI标记 ,小地图缩放

3D/Quad(矩形),旋转到有显示的一面,加图层NpcSign,主相机不勾选该图层

没有圆形遮罩的素材,地图和头像都是方的
在这里插入图片描述

(问题)缩放失败

orthographicSize>0这样对应,小于0就反过来

    public void OnPlusClick()
    {
        miniMapCamera.orthographicSize--;
    }
    public void OnMinusClick()
    {
        miniMapCamera.orthographicSize++;
    }

上透视,下正交
越过0,按键作用相反,人物朝向由北向南(再底层的未了解)
在这里插入图片描述
改变的是这个
在这里插入图片描述

81 装备商店

(问题) unity完全不能点击,只能任务管理器结束进程

(问题) NGUI修改depth,子节点不随之变化

NGUI中更换Widget.depth时如何让子节点的depth也跟着变化
加的两句话的行数
1047行
1058行

Where需要引入using System.Linq;

NGUI ScrollView不能拖拽

在以下条件下,ScrollView里面的项目不能拖动:
ScrollView全打钩,Vertical移动,Vertical的Bar
Grid,Horizontal,1,调高度

(现象)运行后子节点Grid失效

项目,BoxCollider,UI Drag Scroll View。Grid是整理布局的·,运行后还是不激活,但可以拖拽了

在这里插入图片描述

(问题) 滚动滑轮时,画面不缩放,出现穿模

在这里插入图片描述

(问题) 物品的带信息提示框可以作为一个类

背包和商店的物品UI不同,但都可以带信息提示框。
现在是背包的带信息提示框,商店的继承背包的不合适,有很多UI不一样,会报错
所以商店的先继承Item

(了解) 类和属性的命名

可能复用的用最简洁的命名(方便以后合并复用),数据来源,类名用最骚最长的
比如weaponItem,potionItem,都统一用item,类名

(问题)描述与换行

        string str = "";
        if (attack > 0) str += "攻击:+" + attack+"\n";
        if (defense > 0) str += "防御:+" + defense + "\n";
        if (speed > 0) str += "移速:+" + speed + "\n";
        descriptionLabel.text =str;

(现象) NGUI和UGUI的层级

有NGUI的UIRoot后再加UGUI,UGUI会自动归于UIRoot下,手动移出也没用
在这里插入图片描述

(问题) NGUI的UIGrid无效

Unity3D学习笔记——NGUI之UIGrid

(问题) UIGrid的.Reposition()、Excute

导致Panel为空,但是一点击滑动块就正常
在这里插入图片描述
//正常
在这里插入图片描述
//没什么好说的,实例位置写错了。不过写错了,一点击,UIGrid还能正常排序,也挺新的
UIGrid组件的Excute可以代码调用,.Reposition()
在这里插入图片描述

(代码) 商店消费(一个)

在这里插入图片描述
//1 item

    public void OnBuyClick()
    {
        Transform panel = transform.parent.parent.parent.parent;
        panel.GetComponent<WeaponShopPannel>().Buy(price_buy);
    }

//
写在父类ShopPanel(Panel负责Tween动作、关闭;ShopPanel多了购买功能)
E-R的箭头是反着来的,但觉得这样视觉上更能体现“谁生谁”
在这里插入图片描述

    public void Buy(int price)
    {
        int coin = Player._instance.coin;
        //
        if (coin < price) return;//不够钱
        if (coin <= 0) return;//没有钱
        //           
        coin -=price;
        Player._instance.coin = coin;
        coinLabel.text = coin.ToString();
    }

(想法)买了应该销毁它,商店现有物品情况重新写入磁盘文本

(代码) 商店消费(带数量框)

在这里插入图片描述

item

    public void OnBuyClick_Amount()
    {
        //显示按钮
        buyGo.transform.position = transform.position + buyGoOffset;
        buyGo.SetActive(true);
    }
    public void OnBuyClick_Amount_OK()
    {
        Transform panel = transform.parent.parent.parent.parent;
        int amount = int.Parse(buyGo.GetComponentInChildren<UILabel>().text) ;
        int id = GetComponent<WeaponShopItem>().id;
        //
        buyGo.SetActive(false);
        //
        panel.GetComponent<WeaponShopPannel>().Buy(id, price_buy, amount);
    }

ShopPanel

    public void Buy(int id ,int price , int amount)
    {
        int coin = Player._instance.coin;
        //
        price *= amount;
        int remainCoin = coin - price * amount;
        //        
        if (remainCoin < 0) return;//不够钱
        //           
        coin = remainCoin;
        Player._instance.coin = coin;
        coinLabel.text = coin.ToString();
        //背包添加
        for (int i = 0; i < amount; i++)
        {
            BagPannel._instance.CreateNewItem(id);
        }        
    }

BagPanel

ShowWindow()打开不显示,所以在ShowWindow中调用显示item的方法

   public void CreateNewItem(int createNewItemId)//点击生成物品的测试函数,
    {
        //1 随机添加一个物品
          
        int itemGroupId ;

        if (itemGroupList.Count == 0)
            AddNewItem(createNewItemId);
        else if (itemGroupList.Count<=gridList.Count)
        {
            for (int i = 0; i < itemGroupList.Count; i++)//找到图片名相同的grid的索引
            {
                itemGroupId = itemGroupList[i].id;//取得格子里面的物品的id,moonobehavior排斥

                if (itemGroupId == createNewItemId)//相同,到对应的i加加
                {
                    AddExistingItem(createNewItemId);
                    break;
                }

                if (i == itemGroupList.Count - 1 && itemGroupId != createNewItemId)//到最后
                {
                    AddNewItem(createNewItemId);//用拿到的id去对应的i实例
                    break;
                }

                if (gridList.Count <= itemGroupList.Count)
                {
                    print("满了");
                    break;//不加报Error
                }
            }
        }   
    }
    ......
    public override void ShowWindow()
    {
        base.ShowWindow();
        DisplayItem(itemGroupList);
    }

(问题) Invalid editor window UIPrefabTool

Invalid editor window UnityEditor.FallbackEditorWindow UnityEditor.EditorApplicationLayout:SetPlaymo

86 快捷栏

(代码) 将物品拖到快捷栏

//item就是ItemGroup
在这里插入图片描述

在这里插入图片描述

从背包中拖出

写在MyDragAndDrop:UIDragDropItem

    protected override void OnDragDropRelease(GameObject surface)
    {
        base.OnDragDropRelease(surface);
		.......
        else if (surface.tag == "Shortcut")
        {
            ItemGroup item=GetComponent<ItemGroup>();
            //
            surface.GetComponent<ShortcutItem>().AddBagItem(item);
            //
            BagPannel._instance.RemoveItem(item.id);
        }
    }
}

拖进快捷栏

ShortcutItem,共6个格子

    public void AddBagItem(ItemGroup item)
    {
        GameObject go = Instantiate(BagPannel._instance.itemGroupPrefab);
        go.transform.position = transform.position;
        go.transform.parent = transform.GetChild(0);//父节点为背景图
        go.transform.localScale = Vector3.one;
        go.GetComponent<ItemGroup>().SetValue(item);
    }

(代码) 快捷栏使用药品

10/200血量,100血d的药,110/200=55%
在这里插入图片描述
在这里插入图片描述

回血回蓝

Player回复

    public bool Heal(int hp, int mp) //回血,还是不用item不然治愈技能不能复用
    {
        //
        if (mp==0 && hp > 0 && this.hp == maxHp) return false;//满血时不用纯血药
        if (hp==0 && mp > 0 && this.mp == maxMp) return false;//满蓝时不用纯蓝药
        if (this.hp == maxHp && this.mp == maxMp) return false;//都满不用药
        //
        int remainHp =this.hp + hp;
        int remainMp =this.mp + mp;
        //
        if (remainHp > maxHp) this.hp = maxHp;
        if (remainMp > maxMp) this.mp = maxMp;
        //
        this.hp = remainHp;
        this.mp = remainMp;

        return true;
    }

用药

ItemGroup(药物)
注意if (count <= 0)的位置,不要给显示0的机会
一开始用双击动作测试调用,所以这样命名


    public void OnButtonDoubleClick()
    {
  		......
        //药品
        if (itemType == ItemType.Potion)
        {
            bool isSuccese = Player._instance.Heal(hp, mp);

            if (isSuccese)
            {
                count--;
                if (count <= 0)
                {
                    Destroy(gameObject);
                }
                countLabel.text = count.ToString();
            }
       }     
    }

数字键调用

ShortItem调用它下面的ItemGroup

    void Update()
    {
        if(Input.GetKeyDown(keyCode))
        {
            if (transform.GetChild(0).childCount <= 0) return;
            //
            Transform itemGroup = transform.GetChild(0).GetChild(0);     
            itemGroup.GetComponent<ItemGroup>().OnButtonDoubleClick();//用双击测试故此命名
        }
    }

87 处理经验条和升级

经验条的UI在PlayerStatus部分

在这里插入图片描述

写在Player

    void Start()
    {
        //默认1级
        level =1;
        maxLevel = 6;                   //   0  1    2    3    4    5   6    7UI上每个等级对应的maxLevel
        maxExpLevelList = new List<int> {  100, 200, 300, 400, 500, 600,700 ,800};
    }
    public void AddExp(int exp)
    {
        int nextLevel = this.level + 1;
        //范围限制
        if (this.level >= maxLevel && this.exp >= maxExpLevelList[maxLevel])
        {
            this.level = maxLevel;
            this.exp = maxExpLevelList[maxLevel];
            return;
        }
               
        //
        int remainExp = this.exp + exp;
        while (remainExp >= maxExpLevelList[nextLevel])//考虑一次升多级的情况
        {
            remainExp -= maxExpLevelList[nextLevel];
            if (UpLevel())
            {
                continue;
            }
            else
            {
                break;
            }
        }
        //
        this.exp = remainExp;
    }
    public bool UpLevel()
    {
        int remainLevel = this.level +1;
        //范围限制
        //范围限制
        if (remainLevel > this.maxLevel)
        {
            return false;
        }
        this.level = remainLevel;
        
        return true;
    }

在Update测试效果

        if (Input.GetMouseButtonDown(0))
         {
            AddExp(100);
        }

089 开发敌人小狼

(问题) 贴图(还是说材质)丢失

记得之前做秘密行动(当时没有写下来的意识)是改Materials文件夹下的Stanard
在这里插入图片描述

在这里插入图片描述
//好像管点用
在这里插入图片描述
//全部改Standar
在这里插入图片描述

(问题) 模型变红

材质那里把贴图往“法线贴图”一贴,就变红,然后改不回来了
在这里插入图片描述
//贴图被修改为法线贴图导致的
在这里插入图片描述
//所以把贴图类型改回默认
在这里插入图片描述

添加动画

将Animations中的动画拖到NoAnimations里面的动画器上
在这里插入图片描述

(代码) 默认Idle

//根据名字索引动画
在这里插入图片描述

public class Wolf : MonoBehaviour
{
    public State state;
    //
    public string idleClip = "WolfBaby-Idle";
    private new Animation animation;

    // Start is called before the first frame update
    void Start()
    {
        state = State.Idle;
        animation = GetComponent<Animation>();
    }

    // Update is called once per frame
    void Update()
    {
        Aniamtor();
    }
    void Aniamtor()
    {
        if (state == State.Idle)
        {
            animation.CrossFade(idleClip);
        }
    }
}

90 控制小狼的随机移动巡逻

需求

随机的Idle、Walk
Idle,直接Walk
Walk,随机转弯,再Walk
计时循环
在这里插入图片描述

代码

    void Update()
    {
        ......
        Timer();
    }
        void Timer()
    {
        timer += Time.deltaTime;
        if (timer > time)
        {
            timer = 0f;
            Patrol();
        }
    }
    void Patrol()//巡逻
    {
        int rState = Random.Range(0,2);
        //
        if (rState == 0) //Idle
            state = State.Idle;
        else if (rState == 1)//Walk
        {
            if (state == State.Idle)//上个状态==Idle
                state = State.Walk;
            else if (state == State.Walk)//上个状态==Walk
            {
                int rRotate = Random.Range(0, 360);
                transform.Rotate(transform.up * rRotate);//y轴旋转
            }
            state = State.Walk;
        }
    }

//修改下Animator中的Walk,让它动

            case State.Walk:
                {
                    GetComponent<CharacterController>().SimpleMove(transform.forward * walkSpeed * Time.deltaTime);
                    PlayAniClip(walkClip); 
                }
                break;

在这里插入图片描述

91 控制小狼被打中的效果

需求

受击打
闪避率 分 打中,打不中
打中,锁定击中标志,掉血,身体协程变红1秒
//需要加标识符,防止协程变红未恢复,就进行下一次变色

在这里插入图片描述

    [Tooltip("闪避率")] public float missRate = 0.2f;
    [Tooltip("生命值")] public float hp=100f;
    [Tooltip("最大生命值")] public float maxHp=100f;
    [Tooltip("身体变红的锁,类似于计时器的效果")] public bool isAttacked = false;
    void Update()
    {
        //测试受伤动画
        if (Input.GetKeyDown(KeyCode.Space))
        {
            TakeDamage(20f);
        }
    }
    void TakeDamage(float attack)
    {
        float r = Random.Range(0f, 1f);
        if (r < missRate)
        {
            print("Miss");
            return;
        }
        else
        {
            float remainHp = hp - attack;
            if (remainHp > 0) hp = remainHp;
            else hp = 0f;
            //
            PlayAniClip(damage1Clip);

            //
            if (!isAttacked)//防止协程变红未恢复,就进行下一次变色
            {
                isAttacked = true;
                StartCoroutine(ShowBodyRed());
            }      
        }
    }
  
    IEnumerator  ShowBodyRed()
    {
        Material material= GetComponentInChildren<Renderer>().material;
        Color normalColor = material.color;
        material.color = Color.red;
        yield return new WaitForSeconds(1f);
        material.color = normalColor;

        isAttacked = false;
    }

(存疑) render与GetComponentInChildren()

//以前的render等价于以下?

GetComponentInChildren<Renderer>()

//(起码视频中没看到给render赋值)
在这里插入图片描述

//应该是等价的,下面导入NGUI HUDText解决报错时看到类似的light
在这里插入图片描述

92 显示Miss(闪避)效果

需求

UGUI HUDText与NGUI HUDText

HUDText插件,商店太慢.
//下面的插件看看区别
UGUI HUD Text 1.4.1 Unity角色显示文字与数值插件 免费版//有UGUI HUDText
NGUI的HUD Text的扩展插件学习–(HUDText)的使用//只有教程NGUI HUDText
NGUI HUD Text//有NGUI HUDText,22M,包括NGUI、HUDText
//示例
在这里插入图片描述
//视频中的示例
在这里插入图片描述

UGUI HUDText不能用

拖了预制体,是UGUI的。混用麻烦
在这里插入图片描述

(代码)

在这里插入图片描述
在这里插入图片描述

    [Tooltip("预制体,加了UIFollowTarget的新的预制体")] public GameObject hudTextPrefab;
    [Tooltip("预制体的实例,全局定义,方便调用")] public GameObject hudTextGo;
    [Tooltip("预制体生成对象上的组件。用来add文本")] private HUDText hudText;
    [Tooltip("HUDText位置")] public Transform hudTextTrans;
    //[Tooltip("就上面那个")] public GameObject hudTextFollow;
    [Tooltip("NGUI的下相机,直接赋值报空错误")] public Camera uiCamera; 
    [Tooltip("HUDText存在的时间")] public float stayDuration = 1f;
    
    // Start is called before the first frame update
    void Start()
    {
		......
        InitHudText();
    }

    void InitHudText()
    {
        //实例、父节点
        hudTextGo = NGUITools.AddChild(HudTextParent._instance.gameObject, hudTextPrefab);
        hudTextGo.transform.position = Vector3.zero;
        hudTextGo.transform.localScale = Vector3.one;
        //取得HUDText组件
        hudText = hudTextGo.GetComponent<HUDText>();
        //填UIFollowTarget的参数
        UIFollowTarget followTarget = hudTextGo.GetComponent<UIFollowTarget>();//预制体自带
        followTarget.target = hudTextTrans;
        followTarget.gameCamera = Camera.main;
        //followTarget.uiCamera = UICamera.current.GetComponent<Camera>();//报空指针错误
        followTarget.uiCamera = uiCamera;
    }

    void TakeDamage(float attack)
    {
        float r = Random.Range(0f, 1f);
        if (r < missRate)
        {
            AudioSource.PlayClipAtPoint(missAudioClip,transform.position);
            hudText.Add("Miss",Color.gray, stayDuration);
        }
        ......

在这里插入图片描述

093-094 敌人自动攻击部分-巡逻,追击,普攻暴击/受伤HUDText

(问题) 距离

//距离太远,所以放大1000倍,才发现位置的问题()
在这里插入图片描述
//3D视图发现父节点位置有问题,所以删掉新建父节点
在这里插入图片描述

(重构) 计时器委托(为了复用计时器的代码)

(问题) 原本的写法(返回不了 patrolTimer导致不变)

    void Update()
    {
        //巡逻
        Delegate patrol = Patrol;
        Timer(patrol, patrolTimer , patrolTime);
		......
    }
    private delegate void Delegate();//定义委托类型
    void Timer( Delegate func, float timer, float time )//定时巡逻
    { 
        if (timer > time)
        {
            timer = 0f;

            print("委托");
            func();
        }
        else
        {
            patrolTimer += Time.deltaTime;
        }
    }

符合需求(返回timer的值)

    void Update()
    {
 
        //巡逻
        Delegate patrol = Patrol;     
        patrolTimer = Timer(patrol, patrolTimer , patrolTime);
		......

    }
    private delegate void Delegate();//定义委托类型
    float Timer( Delegate func, float timer, float time )//定时巡逻
    { 
        if (timer > time)
        {
            timer = 0f;
            print("委托");
            func();
        }
        else
        {
            timer += Time.deltaTime;
        }
        return timer;
    }

(代码)

在这里插入图片描述

	......
    [Tooltip("普攻")] public int atk_normal = 10;
    [Tooltip("暴击")] public int atk_crazy = 15;
    [Tooltip("暴击率")] public float atk_crazy_rate = 0.1f;
    [Tooltip("攻速,一秒几下")] public int atk_rate = 1;

    [Tooltip("视野范围")] public float range_sight = 5f;
    [Tooltip("攻击范围")] public float range_atk = 1f;

    [Tooltip("追击速度")] public float chaseSpeed = 20f;
    void Update()
    {
        Aniamtor();
        //巡逻
        Delegate range = Range;     
        patrolTimer = Timer(range, patrolTimer , patrolTime);
		......
    }
   void Range()//视野
    {
        Transform target = Player._instance.transform;
        float distance = Vector3.Distance(target.position, transform.position);
        if (distance < range_atk)
        {
            Attack();
        }
        else if (distance < range_sight)
        {
            Chase();
        }
        else
        {
            Patrol();
        }

    }
    void Attack()//攻击
    {
        float r = UnityEngine.Random.Range(0f, 1f);
        //
        Transform target = Player._instance.transform;
        transform.LookAt(target);
        if (r < atk_crazy_rate)//暴击
        {
            //玩家受伤      
            state = State.Attack2;
        }
        else
        {
            state = State.Attack1;
        }
    }
    void Chase()//追击
    {
        Transform target = Player._instance.transform;
        transform.LookAt(target);
        GetComponent<CharacterController>().SimpleMove( transform.forward *Time.deltaTime *chaseSpeed);
        state = State.Walk;
    }
    void Patrol()//巡逻
    {
        int rState = UnityEngine.Random.Range(0, 2);
        //
        if (rState == 0) //Idle
        {
            state = State.Idle;

            print("1");
        }
        else if (rState == 1)//Walk
        {
            if (state == State.Idle)//上个状态==Idle
            {
                state = State.Walk;
                print("2");

            }
            else if (state == State.Walk)//上个状态==Walk
            {
                int rRotate = UnityEngine.Random.Range(0, 360);
                transform.Rotate(transform.up * rRotate);//y轴旋转
                print("3");
            }
            state = State.Walk;
        }
    }

(了解) 变量使用习惯(全局还是局部)

//个人喜欢用的时候直接 Transform target = Player._instance.transform;(在一个方法中就比较清楚(毕竟命名是件让人头疼的事))
//视频喜欢定义组件里面全局变量在Start()中target = Player._instance.transform;
//
暂时不能从经验上清楚各自特点优劣

(了解) region(折叠代码)

可能之前说了。但是看这坨东西和那小块,心情瞬间就不一样了
在这里插入图片描述

(代码) 敌人掉血HUDText

在Wolf的TakeDamage(float attack)加上,

hudText.Add("-"+attack.ToString(), Color.red, stayDuration);
    void Update()
    {
		......
        //测试受伤动画
        if (Input.GetKeyDown(KeyCode.Space))
        {
            TakeDamage(Player._instance.attack);
        }
    }
    void TakeDamage(float attack)
    {
		......
        else
        {
            float remainHp = hp - attack;
            if (remainHp > 0)
            {
                hp = remainHp;
                hudText.Add("-"+attack.ToString(), Color.red, stayDuration);
            } 
			......
        }

在这里插入图片描述

095-96 给主角添加战斗系统;主角攻击动画和自动攻击

(重构) 提取状态机那一部分到Player和Wolf的父类Human

反正能复用的,都尝试提取以一下

(代码) Human

//PlayAniClip(string clip)、Timer(Delegate func, float timer, float time)、Range(Transform target)有内容,其它都是下下图的虚函数

    public  void PlayAniClip(string clip)
    {
        animation.CrossFade(clip);
    }
    //
    public delegate void Delegate();//定义委托类型
    public float Timer(Delegate func, float timer, float time)//定时执行函数
    {
        if (timer > time)
        {
            timer = 0f;
            //print("Human计时器委托");
            func();
        }
        else
        {
            timer += Time.deltaTime;
        }
        return timer;
    }
    //
    public virtual void Range(Transform target)//视野
    {
        float distance = Vector3.Distance(target.position, transform.position);
        if (distance < range_atk)
        {
            Attack();
        }
        else if (distance < range_sight)
        {
            Chase();
        }
    }
    public virtual void Animator()
    {
        switch (state)
        {
            case State.Idle: PlayAniClip(idleClip); break;
            case State.Walk:  PlayAniClip(walkClip); break;
            case State.Dead: PlayAniClip(deathClip); break;
        }
    }

//其它都是虚函数
在这里插入图片描述

(了解) 父类的Start,Update

子类不继承父类的Start,Update
所以不能想在父类的Start中赋值,以后就不用赋值了
//正常是,声明,使用,但不赋值,子类实现时再赋值

    public new Animation animation;
	......
    // Start is called before the first frame update
    void Start()
    {
    }

    //
    public  void PlayAniClip(string clip)
    {
        animation.CrossFade(clip);
    }

(问题) animation过时联想不优

//这有点解释了过时的animation和现在的GetComponent< Animation >();就是以前unity帮我们在父类中多做了这一个。但又过时了。难道这是不优的?
在这里插入图片描述

(重构) 之前MoveByMouse也有射线检测的代码,并且都有state,冲突

MoveByMouse的speed、state用Player._instance.speed、state,来达到数据统一

射线检测

//主要是里面的if语句,去掉else(留条件空间给Player),否则会跟Player中的射线检测起冲突

    //判断 mouseDownPosition发出的射线是否撞到标签为tag的物体,是的返回hit的位置
    public Vector3 RayTesting()
    {

        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);//点生成射线
        RaycastHit hit;
        bool isCollided = Physics.Raycast(ray, out hit);//是否碰到
        if (isCollided && hit.collider.tag == Tags.ground)
        {
            targetPosition = hit.point;
            return hit.point;
        }
        return transform.position;//默认值
    }

state

//主要修改体现在MoveByMouse的Run()中
//主要关注原本的state都用GetComponent< Player >().state

    void Run()
    {    
        float stopDistanceNearTargetPosition = 0.2f;//离目标点方圆0.2米内就可以
        float distance = Vector3.Distance(transform.position, targetPosition);//检测现在pos与目标pos的距离
        if (targetPosition != Vector3.zero && distance > stopDistanceNearTargetPosition)//有目的地而未到达
        {
            GetComponent<Player>().state = State.Run;
            characterController.SimpleMove(transform.forward * Time.deltaTime * speed);
            transform.LookAt(new Vector3(targetPosition.x, transform.position.y, targetPosition.z));
        }
        else if(distance < stopDistanceNearTargetPosition && Player._instance.target ==null)
        {
            GetComponent<Player>().state = State.Idle;
        }
    }

(问题) 第二次点击才会在范围内攻击

调用问题
//之前Range(target);放在 RayTesting();里面,每次调用需要Input.GetMouseButton(0)
//所以拆出来

    void Update()
    {
        if (Input.GetMouseButton(0))
        {
            RayTesting();
        }
        if (target != null)
        {
            Range(target);
        }                
        Animator();    
    }

(问题) 飞起来攻击

敌人和玩家都加上碰撞器就不会;
只给一个加碰撞器也会
在这里插入图片描述

098 添加攻击的特效

//掉血上面做了
Attack()里面加
//暂时看不出视频所说的加标志符,防止实例的特效重复播

Instantiate(atk_prefab, target.transform.position, Quaternion.identity);

在这里插入图片描述

099 开发敌人的孵化器

需求

即时生成max_num的敌人,敌人死了,马上计时补充到max_num

Spawn

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Spawn : MonoBehaviour
{

    public float timer = 0f;
    public float time = 1f;
    public GameObject prefab;

    public List<Transform> wolfList;
    public int max_num=5;
    public int current_num;

   
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if (wolfList.Count < max_num)
        {
            timer += Time.deltaTime;
            if (timer > time)
            {
                timer = 0f;
                SpawnWolf();
            }
        }
    }
    void SpawnWolf()
    {
        Vector3 pos;
        float posX = transform.position.x + Random.Range(-5f, 5f); //不包括右边界
        float posZ = transform.position.z + Random.Range(-5f, 5f); //不包括右边界
        pos = new Vector3(posX, transform.position.y, posZ);
        //
        GameObject go = Instantiate(prefab, pos, Quaternion.identity);
        go.transform.parent = transform;
        //
        wolfList.Add(go.transform);
        current_num = wolfList.Count;
    }
    public void Remove(Transform wolf)//记录current_num,为了保持max_num的数量
    {
        if (wolfList.Contains(wolf))
        {
            wolfList.Remove(wolf);
            current_num = wolfList.Count;
        }  
    }
}

Wolf

//死亡的时候调用
//Spawn里设置了Spawn的节点是父节点

    [Tooltip("所属生成器")] public Spawn spawn;
  
    void Start()
    {
		......
        spawn = transform.parent.GetComponent<Spawn>();
    }
    public override void Dead()
    {
		......
        //
        spawn.Remove(transform);
    }

100 开发敌人死亡后经验值的获得和任务的奖励

升级

需求

杀死敌人,传自身的exp调用玩家的AddExp(int Exp)方法

Wolf

	[Tooltip("杀死可得经验值")] public int exp = 10;
    public override void Dead()
    {
   		......
        Player._instance.AddExp(exp);
    }

Player

    public void AddExp(int exp)
    {
        int nextLevel = this.level + 1;
        //范围限制
        if (this.level >= maxLevel && this.exp >= maxExpLevelList[maxLevel])
        {
            this.level = maxLevel;
            this.exp = maxExpLevelList[maxLevel];
            return;
        }

        //
        int remainExp = this.exp + exp;
        while (remainExp >= maxExpLevelList[nextLevel])//考虑一次升多级的情况
        {
            remainExp -= maxExpLevelList[nextLevel];
            if (UpLevel())
            {
                continue;
            }
            else
            {
                break;
            }
        }
        //
        this.exp = remainExp;
    }
        private bool UpLevel()
    {
        int remainLevel = this.level + 1;
        //范围限制
        if (remainLevel > this.maxLevel)
        {
            return false;
        }
        this.level = remainLevel;

        return true;
    }

任务奖励

(问题 ) 毛皮

//spawn这种引用在spawn.Remove(transform);报空指针错误。原因是“毛皮”,声明的spawn附在Wolf上,Wolf没了,spawn也没了
//做成单例,就不报错。逻辑上说,。也可以单例,但是这个单例是有一个spawn的列表,记录各个spawn中玩家杀死的敌人数

	[Tooltip("所属生成器")] public Spawn spawn;
    void Start()
    {
		......
        spawn = transform.parent.GetComponent<Spawn>(); 
    }
    public override void Dead()
    {
        PlayAniClip(deathClip);    
        //移除一个,(补充满员)
        spawn.Remove(transform);
        //升级
        Player._instance.AddExp(exp);
        //任务进度
        QuestPannel._instance.progress++;
        //
        Destroy(gameObject, 1f);

    }

(问题) Invalid editor window UnityEditor.FallbackEditorWindow

切换个布局,之前也发生过,不清楚原理

后面发现,调整分辨率运行后,出现这种错误

(想法) 任务系统

//TODO:这里应该架构任务系统,任务类的设计,因为不止一个任务
//TODO:受任务后,才会建立起一个任务类,监听敌人的死亡,每死一个,progress++

101-02 给环境添加中型狼、BOSS

(问题) 角色控制器、碰撞器的调整

//角色控制器、碰撞器的基准点是中心,但一般“人型”与地面接触,如果基准点设为“脚底”就方便得多。
//不是全部脚底,类似于。得到一个点的坐标,一种方式是,拖进去坐子节点,重置Transform,再拖出来,这样就得到该点坐标
在这里插入图片描述

(面板) 调整

动画名
角色控制器、碰撞器
因为体型,所以range_atk攻击范围要大点,不然敌人打不到玩家

在这里插入图片描述
在这里插入图片描述

(问题) uiCamera空指针

空指针

//视频的方式找不到NGUI的uiCamera(可能是版本,可能是我遗漏了什么)
//uiCamera是固定的,拖拽赋值也不好

uiCamera = UICamera.current.GetComponent<Camera>();//报空指针错误

在这里插入图片描述

NGUITools.Start()

// NGUITools.FindCameraForLayer的Start()
//主要关注uiCamera = NGUITools.FindCameraForLayer(gameObject.layer);。因为观察到:不给uiCamera手动赋值时,也会自动赋值,所以看源码,它是怎么给uiCamera赋值的

	void Start()
	{
		if (target)
		{
			if (gameCamera == null) gameCamera = NGUITools.FindCameraForLayer(target.gameObject.layer);
			if (uiCamera == null) uiCamera = NGUITools.FindCameraForLayer(gameObject.layer);
			Update();
		}
		else
		{
			if (destroyWithTarget) Destroy(gameObject);
			else enabled = false;
		}
	}

NGUITools.FindCameraForLayer(int layer)

//NGUITools的FindCameraForLayer()
//从Start(),注意到有个方法FindCameraForLayer(gameObject.layer);
//更底层的是UICamera.list.buffer[i].cachedCamera

	static public Camera FindCameraForLayer (int layer)
	{
		int layerMask = 1 << layer;

		Camera cam;

		for (int i = 0; i < UICamera.list.size; ++i)
		{
			cam = UICamera.list.buffer[i].cachedCamera;
			if (cam && (cam.cullingMask & layerMask) != 0)
				return cam;
		}
		......

解决方法

//然后看了源代码,干脆就用uiCamera = NGUITools.FindCameraForLayer(gameObject.layer);
//所以就是uiCamera = NGUITools.FindCameraForLayer(5);(这里可以做个int类型的枚举)
//或者uiCamera这一步不做了,反正会自动赋值
在这里插入图片描述

Spawn

//根据类型(枚举(枚举的那个下拉看起来很爽)),在InitType()中给实际要实例的预制体prefab赋值
//enum 公有还是私有,其他类都可以访问到;就是该类型的属性的访问级别不能比enum高。比如enum私有,该类型的属性就不能public

public enum WolfType
{
    WolfBaby,
    WolfNormal,
    WolfBoss
}

public class Spawn : MonoBehaviour
{
    [Tooltip("根据类型用到的prefab")] private GameObject prefab;
    [Tooltip("生成类型")] public WolfType wolfType;
    public GameObject wolfBabyPrefab;
    public GameObject wolfNormalPrefab;
    public GameObject wolfBossPrefab;
	......

    void Start()
    {
        InitType();
    }

    private void InitType()
    {
        switch (wolfType)
        {
            case WolfType.WolfBaby: prefab = wolfBabyPrefab;break;
            case WolfType.WolfNormal: prefab = wolfNormalPrefab;break;
            case WolfType.WolfBoss: prefab = wolfBossPrefab;break;
            default: prefab = wolfBabyPrefab; break;
        }
    }
    ......

在这里插入图片描述
//可以看到生效了,不过MainCamera距离最下面的那只狼太近,穿模了。(暂时未学到怎么叫解决)
在这里插入图片描述

103 控制主角的受攻击的效果(掉血)、死亡

(重构) 将Wolf受伤,身体变红的代码 提取到 父类Human

属性的就不写了,解释前面有写

父类Human

    public virtual void InitHudText()
    {
        //实例、父节点
        hudTextGo = NGUITools.AddChild(HudTextParent._instance.gameObject, hudTextPrefab);
        hudTextGo.transform.position = Vector3.zero;
        hudTextGo.transform.localScale = Vector3.one;
        //取得HUDText组件
        hudText = hudTextGo.GetComponent<HUDText>();
        //填UIFollowTarget的参数
        UIFollowTarget followTarget = hudTextGo.GetComponent<UIFollowTarget>();//预制体自带
        followTarget.target = hudTextTrans;
        followTarget.gameCamera = Camera.main;
        //followTarget.uiCamera = UICamera.current.GetComponent<Camera>();//报空指针错误
        followTarget.uiCamera = NGUITools.FindCameraForLayer(5);//5是UI层
    }
    public virtual void TakeDamage(int attack)
    {
        float r = UnityEngine.Random.Range(0f, 1f);
        if (r < missRate)
        {
            AudioSource.PlayClipAtPoint(missAudioClip, transform.position);
            hudText.Add("Miss", Color.gray, stayDuration);

            print("Miss");
        }
        else
        {
            int remainHp = hp - attack;
            if (remainHp > 0)
            {
                hp = remainHp;
                hudText.Add("-" + attack.ToString(), Color.red, stayDuration);
            }
            else
            {
                hp = 0;
                Dead();
            }
            //
            TakeDamageEffect();

        }
    }
    public virtual void TakeDamageEffect() { }
    public IEnumerator ShowBodyRed()
    {
        Material material = GetComponentInChildren<Renderer>().material;
        Color normalColor = material.color;
        material.color = Color.red;
        yield return new WaitForSeconds(1f);
        material.color = normalColor;

        isAttacked = false;
    }

子类Wolf

TakeDamage(int attack)用父类的就行

    public override void TakeDamageEffect()
    {
        PlayAniClip(damage1Clip);

        if (!isAttacked)//防止协程变红未恢复,就进行下一次变色
        {
            isAttacked = true;
            StartCoroutine(ShowBodyRed());
        }
    }
    IEnumerator  ShowBodyRed()
    {
        Material material= GetComponentInChildren<Renderer>().material;
        Color normalColor = material.color;
        material.color = Color.red;
        yield return new WaitForSeconds(1f);
        material.color = normalColor;

        isAttacked = false;
    }

子类Player

//子类在原本父类基础上加了一些。
//受伤方法跟同父类的Wolf一样

    public int Attack_Func(int attack)
    {  
        //具体公式看自己设计
        int remainAttack = attack - defense;
        if (remainAttack <= 0) remainAttack = 1;

        return remainAttack;
    }
    public override void TakeDamage(int attack)
    {
        if (Player._instance.state == State.Dead) return;
        //根据什么防御力整合最终所受攻击力
        attack = Attack_Func(attack);//仅仅为了方便从Wolf贴过来的代码
        //
        base.TakeDamage(attack); 
    }
    public override void Dead()
    {
        state = State.Dead;
        Destroy(gameObject,5f);
    }

(代码) 死亡销毁 return

对报错的地方,进行 stateState.Dead、Player._instacenull,的判断

(运行) 也是跑得通的

在这里插入图片描述

104 添加技能额外信息的存储

主要看流程,因为这时代码多了,不方便都放出来

(需求)

//后三项是特效名称、角色动画名称、角色动画时间
//战士没有写后三项,所以只做魔法师的
在这里插入图片描述

Skill

添加属性

//技能特效名称efx是什么中文的翻译?
在这里插入图片描述
添加

    [Tooltip("技能特效文件名称")] public string skill_efx_name;
    [Tooltip("玩家动画文件名称")] public string player_ani_name;
    [Tooltip("玩家动画时间")] public string player_ani_time;

ToString()重写(觉得贴一下,因为觉得有做成片段的需要)

    public override string ToString()
    {
        string str = "";
		......
        //
        str += "\t" + skill_efx_name;
        str += "\t" + player_ani_name;
        str += "\t" + player_ani_time;

        return str;     
    }

片段

	......
	<Code Language="csharp"><![CDATA[  public override string ToString()
    {
        string str = "";
        str += "\t" + id;      

        return str;     
    }]]>
    ......

SkillTextAssetToList

//在想只有魔法师有,要不要把代码到里面
//主要是觉得战士的后三项为空,会不会报错。视频是直接接在后面的
在这里插入图片描述

105 技能系统-使用增益技能

(需求)

//快捷栏需要的是Skill,而不是包含很多UI的SkillItem(因为Start()中就会初始化UI,快捷栏没有对应的UI,导致报空错误),如下图
//以上总而言之就是,就是避开SetUI、UnlockSkillItem
//01、SetUI、UnlockSkillItem的调用实际,在Start()去除,在ShowPanel加入(成功)
//02、所以SkillItem,不继承Skill,而是加属性。 public Skill skill;(没试成)
在这里插入图片描述

ShortItem

//我是放在Sprite下,Sprite是一张透明图,等着被换成技能或者物品

    void Start()
    {
        sprite = transform.GetChild(0);

    }
    void Update()
    {
        if (Input.GetKeyDown(keyCode))
            PlayerInput();
    }
    void PlayerInput()
    {      
        if (sprite.childCount <1) return;
        //
        item = sprite.GetChild(0);//挂在sprite下

        if (item.GetComponent<ItemGroup>() != null)//物品
        {
            item.GetComponent<ItemGroup>().OnButtonDoubleClick();//用双击测试故此命名
        }
        else if (item.GetComponent<SkillItem>() != null)//技能
        {
            print("发动技能");
            Player._instance.UseSkill(item.GetComponent<SkillItem>() );
        }
        
    }

在这里插入图片描述

SkillItem

    }
    public void SetValue(Skill skill)
    {
		......
        skill_efx_name = skill.skill_efx_name;
        player_ani_name = skill.player_ani_name; 
        player_ani_time = skill.player_ani_time;
    }

Player

拖技能预制体,做字典

    [Tooltip("技能预制体")] public List<GameObject> skillGoList;
    [Tooltip("根据名字找预制体的字典")] public Dictionary<string, GameObject> skillDictionary;
	......
    void Start()
    {
		......
        InitSkill();
    }
    void InitSkill()
    {
        foreach (GameObject go in skillGoList)
        {
            skillDictionary.Add(go.name, go);
        }
    }

在这里插入图片描述
在这里插入图片描述

//需求,扣篮,Fill图,根据技能类型,执行对应类型技能的代码

    public bool UseSkill(Skill skill)//使用技能看蓝够不够
    {
        int mp = skill.costMp;
        int remainMp = this.mp - mp;
        //
        if (remainMp < 0)//没蓝
        {
            return false;
        }
        else//成功
        {
            this.mp = remainMp;
            UseSkillByEffectType(skill);
            return true;
        }
    }
    void UseSkillByEffectType(Skill skill)
    {
        //清洗

        //增益(Hp、Mp) 增强(攻防) 单目标 多目标
        switch (skill.effectType)
        {
            case EffectType.Passive:
                {
                    UsePassiveSkill(skill);
                }
                break;
            case EffectType.Buff:
                {
                    UsePassiveSkill(skill);
                }
                break;
            case EffectType.SingleTarget:
                {
                    UsePassiveSkill(skill);
                }
                break;
            case EffectType.MultiTarget:
                {
                    UsePassiveSkill(skill);
                }
                break;
            default: break;
        }
    }
    void UsePassiveSkill(Skill skill)//增益
    {
        switch (skill.effectProperty)
        {
            case EffectProperty.HP: AddHp((int) skill.effectValue); break;
            case EffectProperty.MP: AddMp((int) skill.effectValue); break;
        }
    }
    void UseBuffSkill(Skill skill)//增强
    { }
    void UseSingleTargetSkill(Skill skill)//单目标
    { }
    void UseMultiTarget(Skill skill)//多目标
    { }

HP/MP生效

//一开始hp=50%
//按下后显示“发动技能”,回血到62%
在这里插入图片描述

(问题) 字典找不到值

//
ArgumentException: The Object you want to instantiate is null.
//
KeyNotFoundException: The given key was not present in the dictionary.

打印键值是否存在

//false没有键
print(skillDictionary.ContainsKey(“Effect-Heal”));
//true有值true
print(skillDictionary.ContainsValue(skillGoList[1]));

直接取预制体列表可以生效

模型是战士,但是类型已经改为魔法师
在这里插入图片描述

字典取值的两种方式

//所以是Key出现问题

        GameObject go = skillGoList[1];
        skillDictionary.Add("Effect-Heal",  go);
        
        GameObject go = null;
        //01 方式一
        //skillDictionary.TryGetValue("Effect-Heal", out go);
        //02 方式二
        go = skillDictionary["Effect-Heal"] ;
        //
        Instantiate(go, transform.position, Quaternion.identity);

结果

名字问题
//三个文件夹中有heal_Effect、Effect_Heal
在这里插入图片描述

(问题) 实例不出来

//前面强制命名测试没改回来
//字典取不到值,可以先强制命名Key,能不能拿到Value

(效果)

共三个文件,1一个Heal,3两个Heal。效果如下左三
在这里插入图片描述

(动画) 协程

在这里插入图片描述

(代码) Player新增度代码

    [Tooltip("技能预制体")] public List<GameObject> skillGoList;
    [Tooltip("根据名字找预制体的字典")] public Dictionary<string, GameObject> skillDictionary=new Dictionary<string, GameObject>();
    [Tooltip("技能实例的地方")] public Transform skillTrans;//可能没用到
    [Tooltip("粒子播放时间,用来控制动画的播放时间")] public float skillTime;
    void Start()
    {
		......
        InitSkill();
    }
	......

    void InitSkill()
    {
        skillDictionary.Clear();
        for (int i = 0; i < skillGoList.Count; i++)
        {
            GameObject go = skillGoList[i];
            skillDictionary.Add(go.name,go);
        }
    }
    public bool UseSkill(Skill skill)//使用技能看蓝够不够
    {
        int mp = skill.costMp;
        int remainMp = this.mp - mp;
        //
        if (remainMp < 0)//没蓝
        {
            return false;
        }
        else//成功
        {
            if (UseSkillByEffectType(skill))//防止满血加血等情况
            {
                this.mp = remainMp;
            }         
            return UseSkillByEffectType(skill);//比如回血,蓝是够了,但没必要
        }
    }
    bool UseSkillByEffectType(Skill skill)
    {
        //清洗

        //增益(Hp、Mp) 增强(攻防) 单目标 多目标
        switch (skill.effectType)
        {
            case EffectType.Passive:
                {
                    return UsePassiveSkill(skill);
                }
                break;
            case EffectType.Buff:
                {
                    UsePassiveSkill(skill);
                }
                break;
            case EffectType.SingleTarget:
                {
                    UsePassiveSkill(skill);
                }
                break;
            case EffectType.MultiTarget:
                {
                    UsePassiveSkill(skill);
                }
                break;
            default: break;
        }
        return false;
    }
    bool UsePassiveSkill(Skill skill)//增益
    {
        bool isSuccesed = false;
        switch (skill.effectProperty)
        {
            case EffectProperty.HP:
                {
                   isSuccesed= AddHp((int)skill.effectValue);//满血,没蓝就失败
                }  break;
            case EffectProperty.MP: 
                { 
                   isSuccesed= AddMp((int)skill.effectValue); 
                }  break;
        }
        if (isSuccesed)
        {
            //实例特效
            string key = skill.skill_efx_name;
            //
            if (skillDictionary.TryGetValue(key, out GameObject value))
            {
                Instantiate(value, transform.position, Quaternion.identity);
                //
                skillTime = value.GetComponent<DestroyForTime>().time;
                StartCoroutine(PlayAniClip());
            }
        }
        else
        {
            print("发动技能失败");  
        }
        return isSuccesed;
    }
    
    IEnumerator PlayAniClip()
    {
        isAttacked = true;
        state = State.Skill1;
        yield return new WaitForSeconds(skillTime);
        isAttacked = false;
        state = State.Idle;
    }
    ......

106 技能系统-使用增强技能

(代码) 动画速度

//改变动画速度我查到如下,但是只需要攻击状态变化速度,移速其他的保持原速。

            foreach (AnimationState state in anim)
            {
                state.speed = speed;
            }

所以实际加了状态判断,如下:
//问题是复用,aniState.name是写死的,暂不展开

    void ChangeAnimationSpeed(float speed)
    {
        Animation anim = GetComponent<Animation>();

        foreach (AnimationState aniState in anim)
        {
            print("输出"+aniState.name);
            if (aniState.name == "Sword-Attack1" || aniState.name == "Sword-Attack2")
            {
                aniState.speed = attackSpeed;
            }
        }
    }

(代码)

//skill.effectTime是buff存在时间

    IEnumerator UseBuffSkill(Skill skill)//增强
    {
        //属性
        switch (skill.effectProperty)
        {
            case EffectProperty.Attack: atk_normal*=skill.effectValue/100f;break;
            case EffectProperty.Defense:defense *= skill.effectValue / 100f; break;
            case EffectProperty.AttackSpeed:
                {
                    attackSpeed += skill.effectValue / 100f;
                    UpdateAttackSpeed(attackSpeed);
                }  break;
        }

        print("输出"+ skill.effectTime);
        //技能状态
        SkillState(skill);       
        //属性恢复
        yield return new WaitForSeconds(skill.effectTime);
        switch (skill.effectProperty)
        {
            case EffectProperty.Attack: atk_normal /= skill.effectValue / 100f; break;
            case EffectProperty.Defense: defense /= skill.effectValue / 100f; break;
            case EffectProperty.AttackSpeed:
                {
                    attackSpeed -= skill.effectValue / 100f;
                    UpdateAttackSpeed(attackSpeed);
                } break;
        }
    }
    void UpdateAttackSpeed(float attackSpeed)
    {
        Animation anim = GetComponent<Animation>();

        foreach (AnimationState aniState in anim)
        {
            print("输出"+aniState.name);
            if (aniState.name == "Sword-Attack1" || aniState.name == "Sword-Attack2")
            {
                aniState.speed = attackSpeed;
            }
        }
    }

在这里插入图片描述

107-108 技能系统-使用单个目标技能

(代码) GameSettings 设置单个目标攻击光标

//实际叫CursorController合理点
//前面(36 鼠标指针管理系统),有设置了所有的

public class GameSettings : MonoBehaviour
{
    public Texture2D lockTargetCursor;
	......

    public static GameSettings _instance;
    // Start is called before the first frame update
    void Awake()
    {
        _instance = this;
    }

	void SetLockTargetCursor()
    {
        Cursor.SetCursor(lockTargetCursor, Vector2.zero, CursorMode.Auto);//图形,焦点
    }
	......

}

(代码) Wolf 光标处理

//inChosingTarget,数字键按下技能,处于选择目标的时候

    private void onm ouseEnter()
    {
        if (Player._instance.inChosingTarget) //使用技能时
            GameSettings._instance.SetLockTargetCursor();
        else 
            GameSettings._instance.SetAttackCursor();
    }

    private void onm ouseExit()
    {
        GameSettings._instance.SetNormalCursor();
    }

(问题) 状态栏不刷新

//并且为了UI刷新, return这段语句要放在方面最后面,如下

    public void UpdatePlayerStatus()
    {
		......
        if (player.level > 6) return;//只做了6的经验表,但是为了解锁技能,我设100级,所以...
        float exp = (float)player.exp / (float)player.maxExpLevelList[player.level + 1];
        expBar.GetComponent<UISlider>().value = exp;
    }

(代码) Player

    [Tooltip("使用单体群体技能后为true,比如选择光标的优先级要大于普攻敌人时普攻光标")] public bool inChosingTarget = false;
    [Tooltip("当前处理那个技能的选择目标状态")] public Skill currentSkill;
    void Update()
    {
		......
        if (Input.GetMouseButton(0) && inChosingTarget==true && target != null && target.GetComponent<Wolf>() != null)//使用技能出现选择光标的情况
        {
            UseSingleTargetSkill(currentSkill);
        }
    }
     bool UseSkillByEffectType(Skill skill)
    {
        //清洗

        //增益(Hp、Mp) 增强(攻防) 单目标 多目标
        switch (skill.effectType)
        {
			......
            case EffectType.SingleTarget:
                {
                    inChosingTarget = true;
                    currentSkill = skill;
                    return true;                  
                }
    void UseSingleTargetSkill(Skill skill)//单目标
    {
        //
        Wolf enemy;
        inChosingTarget = true;//为了让普攻光标切换为选择光标
        enemy = target.GetComponent<Wolf>();
        //
        SkillState(skill, target);
        //
        //产生技能伤害(普通、暴击)
        float r = UnityEngine.Random.Range(0f, 1f);
        transform.LookAt(target);
        if (r < atk_crazy_rate)//暴击
        {
            print("暴击");
            //玩家受伤      
            state = State.Skill2;
            float attack = atk_crazy * skill.effectValue / 100f;
            enemy.TakeDamage(attack);
        }
        else
        {
            print("普攻");
            state = State.Skill1;
            float attack =atk_normal * skill.effectValue / 100f;
            enemy.TakeDamage(attack);
        }
        Instantiate(atk_prefab, target.transform.position, Quaternion.identity);//减血文字
        //看表知道没有能持续时间
        //
        inChosingTarget = false;
        currentSkill =null;
    }
            ......

(效果)

//有发生倒下的bug
//点击选择目标时,玩家移动
在这里插入图片描述

(代码) MoveBuMouse Run()

//防止点击选择敌人时移动

        if (Player._instance.inChosingTarget) return;

109 技能系统-使用群体攻击技能

target是ground

enemy是所有碰撞到的enemy

在这里插入图片描述

群体、单体的区别

//光标开始变化时间不同
//伤害计算方式不一样,
//射线检测的对象不一样,尤其将射线检测写成一个方法,返回Transform target,在群体中位置不对((问题) 点击地面,实例特效的位置)

(问题) 按住群体技能,角色不能动,技能特效没有出现

//重构射线检测代码时,target局部变量和全局变量的区别

(问题) Skipped updating the transform of this Rigidbody

Skipped updating the transform of this Rigidbody because its components are infinite. Could you have applied infinite forces, acceleration or set huge velocity?
重新运行自动好的

(问题) 点击地面,实例特效的位置

//地图很大,群体技能是射线检测为Ground就释放,hit.collider.transform是地面,坐标是原点,所以不行
//用Input.mousePosition,测试不行
//视频是射线检测的Vector3 hit.point,所以新建一个全局变量targetPos来接收它
//Ground的原点是000
在这里插入图片描述

(问题) 按两下数字键,玩家既不能移动也不能放技能

//点击鼠标,玩家没有目标,但处于群体攻击的选择目标状态
在这里插入图片描述
//update那里的问题,所以梳理下,并且运行通过

    void Update()
    {      
        Animator();
        if (Input.GetMouseButton(0))
        {
            if (RayTesting(Tags.Enemy) && inChosingTarget==false)//追击,攻击的状态
            {
                Range(target);
            }
            if (RayTesting(Tags.Enemy) && inChosingTarget == true)//单体技能
            {
                UseSingleTargetSkill(currentSkill);
            }
            else if (RayTesting(Tags.Ground)  && inChosingMultiTarget)//群体技能
            {
                UseMultiTarget(currentSkill);
            }
        }
    }

在这里插入图片描述

(问题) 没伤害HudText

//特效可以拿到触发列表
在这里插入图片描述
//玩家可以拿到触发列表
在这里插入图片描述
//HudText有生成伤害文字,3D视图发现位置不对
//所以试着跟视频一样,将伤害的代码放在 技能的脚本 中(触发检测中调用改方法),达到效果。详细见MagicSphere
在这里插入图片描述

(问题) 销毁后为空,报空指针

//Wolf加报空检测,有可能死了
//或者((问题) 多次计算伤害) 失效触发器,这样就不会多次走触发事件,从而再次执行(List takingDamageEnemyList, float attack)。第一次执行一定不为空,除非有多个伤害源

    void RangeSkillAttack(List<Transform> takingDamageEnemyList, float attack)
    {
        for (int i = 0; i < takingDamageEnemyList.Count; i++)
        {
            if (takingDamageEnemyList[i] != null)
            {
                takingDamageEnemyList[i].GetComponent<Wolf>().TakeDamage(attack);
                Instantiate(atk_prefab, transform.position, Quaternion.identity);//减血文字
            }
        }
    }

在这里插入图片描述

(问题) 多次计算伤害

//想过remove,或者标志,麻烦
//使用GetComponent().enabled = false;能达到效果

    void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag(Tags.Enemy))
        { 
             takingDamageEnemyList.Add(other.transform);       
        }
        RangeSkillAttack(takingDamageEnemyList, attack); //开打
        GetComponent<SphereCollider>().enabled = false;
    }

//下图就是多次运行了OnTriggerEnter(Collider other)的后果
在这里插入图片描述

(效果) 最终效果

//主要代码在UseMultiTarget(Skill skill)
//觉得写得有些地方不明白。
在这里插入图片描述

在这里插入图片描述

(代码) Player

	......
	[Tooltip("使用单体群体技能后为true,比如选择光标的优先级要大于普攻敌人时普攻光标")] public bool inChosingTarget = false;
    [Tooltip("使用群体群体技能后为true,比如选择光标的优先级要大于普攻敌人时普攻光标")] public bool inChosingMultiTarget = false;
    [Tooltip("当前处理那个技能的选择目标状态")] public Skill currentSkill;
    [Tooltip("群体技能认地面原点所以不能用Transform")] public Vector3 targetPos;
        void Update()
    {      
        Animator();
        if (Input.GetMouseButton(0))
        {
            if (RayTesting(Tags.Enemy) && inChosingTarget==false)//追击,攻击的状态
            {
                Range(target);
            }
            if (RayTesting(Tags.Enemy) && inChosingTarget == true)//单体技能
            {
                UseSingleTargetSkill(currentSkill);
            }
            else if (RayTesting(Tags.Ground)  && inChosingMultiTarget)//群体技能
            {
                UseMultiTarget(currentSkill);
            }
        }
    }
    public bool RayTesting(string tag)
    {
        Ray ray = Camera.main.ScreenPointToRay( Input.mousePosition );//从鼠标位置发出射线
        RaycastHit hit;
        bool isCollided = Physics.Raycast( ray, out hit );

        if (isCollided && hit.collider.CompareTag(tag) )
        {
            target = hit.collider.transform;
            targetPos = hit.point;//针对地形这种大物体获取位置,肯定不能用target.position(为原点)
            return  true;
        }
        else
        {
            target = null;
            return false;
        }      
    }
    bool UseSkillByEffectType(Skill skill)
    {
        //增益(Hp、Mp) 增强(攻防) 单目标 多目标
        switch (skill.effectType)
        {
      		......
            case EffectType.MultiTarget:
                {
                    inChosingMultiTarget = true;                      
                    currentSkill = skill;
                    GameSettings._instance.SetLockTargetCursor();//一使用就切选择光标,跟单体不同
                    return true;
                }
                break;
            ......
    }
    void UseMultiTarget(Skill skill)//多目标
    {
        inChosingMultiTarget = true ;//防止技能期间移动之类的

        //相对于单体的SkillState(Skill)重写
        string key = skill.skill_efx_name;
        
        if (skillDictionary.TryGetValue(key, out GameObject value))
        {                      
            GameObject go= Instantiate(value, targetPos + Vector3.up  , Quaternion.identity);//技能特效
            //伤害
            float attack = SkillAttack(skill);//综合攻击力    
            go.GetComponent<MagicSphere>().attack = attack;//因为调用伤害是触发器调用的,所以Player这边只设置好参数
            go.GetComponent<MagicSphere>().atk_prefab = atk_prefab;
            //
            skillTime = skill.player_ani_time;
            StartCoroutine(PlayAniClip());
            //                           
        }
        //看表知道没有能持续时间
        //
        inChosingMultiTarget = false;
        currentSkill = null;
        GameSettings._instance.SetNormalCursor();
    }
    float SkillAttack(Skill skill)//计算综合伤害
    {
       //产生技能伤害(普通、暴击)并且实例HudText
        float r = UnityEngine.Random.Range(0f, 1f);
        transform.LookAt(target);
        float attack;
        if (r < atk_crazy_rate)//暴击
        {
            print("暴击");
            //玩家受伤      
            state = State.Skill2;
            attack = atk_crazy * skill.effectValue / 100f;
        }
        else
        {
            print("普攻");
            state = State.Skill1;
            attack = atk_normal * skill.effectValue / 100f;
        }
        return attack;
    }

(代码) MoveByMouse

    void Run()
    {
        Player player = Player._instance;
		......
        if (player.inChosingMultiTarget) return;
        if (player.isAttacked) return;
        ......

(代码) MagicSphere(群体技能)

public class MagicSphere : MonoBehaviour
{
    [Tooltip("将被范围伤害的敌人")]    public List<Transform> takingDamageEnemyList;
    [Tooltip("角色传过来的攻击力")] public float attack;
    [Tooltip("角色传过来的对应的Hudtext")] public GameObject atk_prefab;
    // Start is called before the first frame update
    void Start()
    {
       
    }

    void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag(Tags.Enemy))
        { 
             takingDamageEnemyList.Add(other.transform);       
        }
        RangeSkillAttack(takingDamageEnemyList, attack); //开打
        GetComponent<SphereCollider>().enabled = false;
    }
    void RangeSkillAttack(List<Transform> takingDamageEnemyList, float attack)
    {
        for (int i = 0; i < takingDamageEnemyList.Count; i++)
        {
            if (takingDamageEnemyList[i] != null )
            {
                Transform enemy = takingDamageEnemyList[i];
                enemy.GetComponent<Wolf>().TakeDamage(attack);
                Instantiate(atk_prefab, enemy.position, Quaternion.identity);//减血文字
                //
            }
        }
    }
}

110 场景加载-游戏完结

(代码) LoadGame

public class LoadGame : MonoBehaviour
{
    public List<GameObject> playerList;
    public Vector3 spawnPos;
    public int index;
    public new string name;
    // Start is called before the first frame update
    void Awake()
    {
        if (PlayerPrefs.HasKey("SelectedPlayerName") )
        {
            name = PlayerPrefs.GetString("SelectedPlayerName"); 
            spawnPos = new Vector3(236.27f, 0f, 155.61f);//看到地图广场位置
            Instantiate(playerList[index], spawnPos, Quaternion.identity);
        }
        if (PlayerPrefs.HasKey("SelectedPlayerIndex"))
        {
            index = PlayerPrefs.GetInt("SelectedPlayerIndex"); 
            Player._instance.name = name;//PlayerStatus做了Update显示name,所以直接设
        }       
    }

(代码) Player

    void InitProperty() 
    {
        if(PlayerPrefs.HasKey("SelectedPlayerName")== false) name = "123";
        ......

(代码) 其他

绑定按钮事件…

(跑流程)

在这里插入图片描述
在这里插入图片描述
//之前模型是没有改回来,所以一直是魔法师类型的战士模型
在这里插入图片描述

标签:攻击,void,float,transform,int,Unity,64,skill,public
来源: https://blog.csdn.net/weixin_39538253/article/details/117318710

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有