RPG游戏《黑暗之光》流程介绍与代码分析之(十三):角色攻击系统的实现

十三章:角色攻击系统

角色攻击是杀怪时的核心功能,攻击模式又可细分为普通攻击和技能攻击,其中技能攻击的信息存储在SkillInfoInList,本章节只涉及普通攻击部分。
为Magician添加一个PlayerAttack脚本,控制攻击
public enum PlayerState{
    normalWalk,
    normalAttack,
    skillAttack
}

    PlayerState state = PlayerState.normalWalk;    //默认为Walk状态,在PlayerMove脚本中也要通过Walk状态控制普通状态下的移动,当状态为Walk时,才能进行移动。
    public string aniName_normalAttack;    //攻击动画,这些属性与小狼类似
    public float normalPlayerAttackTime;
    private float playerAttackTimer = 0;
    public float minPlayerAttackDistance = 5;
    private Transform normalAttackTarget;    //攻击目标

    void Update()
    {
        if (Input.GetMouseButtonDown (1))    //点击右键攻击
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);    //与主角移动类似,参考(链接)
            RaycastHit hitInfo;
            bool isCollider = Physics.Raycast(ray,out hitInfo);
            if(isCollider && hitInfo.collider.tag == Tags.enemy)    //目标为enemy
            {
                normalAttackTarget = hitInfo.collider.transform;    //传递目标
                state = PlayerState.normalAttack;    //切换攻击状态
            }
            else    //若点击别的地方,则切换为Walk状态
            {
                state = PlayerState.normalWalk;    
                normalAttackTarget = null;
            }
        }

    }

13.1 普通攻击状态

清楚了基本的角色攻击状态后,本节实现当state == PlayerState.normalAttack时的攻击状态。
首先当主角进行攻击时,要判断主角和怪物的距离。若超出攻击距离,主角进行移动并攻击;若未超出,直接攻击。在PlayerAttack脚本中
        if(normalAttackTarget != null)    //选定目标后,判断距离
        {
            float distance = Vector3.Distance(transform.position,normalAttackTarget);
            if(distance > minPlayerAttackDistance)
            {
                 PlayerMove._instance.SimpleMove(normalAttackTarget.position);    //从PlayerMove中调用移动方法

            }
            else
            {
                  //直接攻击
            }
        }
其中SimpleMove()函数实现如下
    public void SimpleMove(Vector3 target)
    {
        transform.LookAt (target);
        controller.SimpleMove (transform.forward * speed);
    }
在攻击时,我们也有不同的动画,包括 处在攻击状态下的移动、攻击以及攻击休息时间的动画。因此需要对PlayerAttack脚本再添加AttackState状态,这三种状态对应三种不同的动画。
public enum AttackState{
    Moving,
    Idle,
    Attack
}
并在控制角色动画的PlayerAnimation脚本中通过当前AttackState的状态,分配不同的动画
 
 
        if (attack.state == PlayerState.normalWalk)
        {
            if (move.state == PlayerWalkState.Moving)    
            {
                PlayAnim ("Run");    //普通状态下的"Run"
            }
            else if (move.state == PlayerWalkState.Idle)
            {
                PlayAnim ("Idle");
                        
            }
        }
        else if (attack.state == PlayerState.normalAttack)
        {
            if(attack.atkState == AttackState.Moving)
            {
                PlayAnim("Run");     //攻击状态下的"Run"
            }
        }
在PlayerAttack下的if(distance > minPlayerAttackDistance)中加入攻击状态下的移动状态即可
if(distance > minPlayerAttackDistance)
            {
                atkState = AttackState.Moving;    //移动的过程中播放动画
                PlayerMove._instance.SimpleMove(normalAttackTarget.position);
            }
就实现了攻击状态下移动时的动画播放。

接下来实现攻击时的休息状态,添加两个string,用以切换攻击状态和休息状态
    public string aniName_idleOfRest;    //攻击间隔的休息动画
    public string aniName_now;    //当前攻击状态下的动画
当distance <= minPlayerAttackDistance,即进入攻击范围时,我们添加代码
                if(distance <= minPlayerAttackDistance)
                {
                transform.LookAt(normalAttackTarget);
                atkState = AttackState.Attack;    //切换到攻击状态下的攻击模式
                playerAttackTimer += Time.deltaTime;    //计时器开启
                animation.CrossFade(aniName_now);    //播放当前动画,默认为aniName_normalAttack
                if(playerAttackTimer >= normalPlayerAttackTime)
                {
                    aniName_now = aniName_idleOfRest;    //攻击效果结束后,当前状态为aniName_idleOfRest
                }
                if(playerAttackTimer >= 1f/attackRate)    //攻击间隔达到后,进入下一次攻击
                {
                    playerAttackTimer = 0;
                    aniName_now = aniName_normalAttack;
                }
            }
即可实现AttackState下的三种状态。

13.2 攻击指针与攻击特效

13.2.1 攻击指针

在攻击时,我们需要改变鼠标指针,并且在攻击动画播放完成后显示攻击特效。
我们参照4.3节( 链接)使用继承的方法,在鼠标放在小狼身上时出现不同的效果,有攻击图标和技能图标。
新建一个AttackCursor脚本,输入
    void OnMouseEnter()
    {
        MouseSetting._instance.SetNormalAttackCursor ();
    }
    
    void OnMouseExit()
    {
        MouseSetting._instance.SetNormalCursor ();
    }
并在MouseSetting中设置SetNormalAttackCursor(),如下
    public Texture2D cursor_attack;
    public void SetNormalAttackCursor()
    {
        Cursor.SetCursor (cursor_attack, hotspot, mode);
    }
之后让WolfBaby继承自AttackCursor脚本,即可实现鼠标的变换功能。

补充:考虑到使用技能时的攻击图标与普通攻击冲突,我们可以不用继承的方式,在WolfBaby上用
    void OnMouseEnter()
    {
        MouseSetting._instance.SetNormalAttackCursor ();
    }
    
    void OnMouseExit()
    {
        MouseSetting._instance.SetNormalCursor ();
    }
直接替换。

13.2.2 攻击特效

我们使用RPG——>Effect——>Prefabs中的Effect_Slash作为攻击时的闪光特效,在PlayerAttack中创建一个GameObject并导入特效的Prefab

并在攻击结束时创建
    private bool effectFlag = false;
                if(playerAttackTimer >= normalPlayerAttackTime)
                {
                    aniName_now = aniName_idleOfRest;
                    if(effectFlag == false)
                    {
                        effectFlag = true;
                        GameObject.Instantiate(effect,normalAttackTarget.position,Quaternion.identity);    //添加特效
                    }
                }
即可显示攻击闪光,如下图所示

在显示特效的同时造成伤害,需要访问PlayerStatus的人物攻击属性和Equipment中的装备加成属性。
    private PlayerStatus ps;
    void Awake()
    {
        ps = this.GetComponent<PlayerStatus> ();
    }
    public int GetAttack()
    {
        return Equipment._instance.attack + ps.attack + ps.attack_plus;    //装备、基础和加点攻击力
    }
之后在产生特效之后加入如下代码即可
normalAttackTarget.GetComponent<WolfBaby>().BeDamaged(GetAttack());
即可看到效果

13.3 野怪的自动生成

我们在Hierarchy中新建一个空的游戏物体,用作小狼的巢穴,命名为BabySpawn,为其添加脚本BabySpawn,源源不断地孵化小狼。
public static BabySpawn _instance;
    private int maxNumber = 5;    //最大数量与当前数量,控制是否继续添加小狼
    public int currentNumber = 0;
    private float createTimer = 3;    //创建的时间间隔
    private float timer = 0;
    public GameObject prefab;    //小狼的prefab

    void Awake()
    {
        _instance = this;
    }

    void Update()
    {
        if (currentNumber < maxNumber)    //满足创建条件
        {
            timer += Time.deltaTime;
            if(timer >= createTimer)
            {
                Vector3 pos = transform.position;    //创建时随机生成一个位置。
                pos.x += Random.Range(-3,3);
                pos.z += Random.Range(-5,5);
                GameObject.Instantiate(prefab,pos,Quaternion.identity);
                timer = 0;
                ++currentNumber;    
            }
        }
    }
当小狼死亡时,即WolfBaby中hp<0时,调节currentNumber,即
BabySpawn._instance.currentNumber -= 1;
即可实现野怪的刷新功能。

13.4 怪物与状态的交互

杀死小狼时,我们要添加经验值等信息,并更新任务信息
我们调用PlayerStatus中的GetExp()函数和BarNPC中的PlusWolfKilledNumber()函数,实现如下
    public void GetExp(float exp)
    {
        this.expCurrent += exp;
        int totalExp = 100 + this.level * 30;
        while (this.expCurrent >= totalExp)
        {
            ++level;
            expCurrent -= totalExp;
            point_remain += 5;
            totalExp = 100 + this.level * 30;
        }
        EXPBar._instance.SetValue (this.expCurrent / totalExp);
    }

    public void PlusWolfKilledNumber()
    {
        if (isOnTask)
        {
            killWolfNumber += 1;    
        }
    }
并在WolfBaby脚本中进行调用
if (hp <= 0)
            {
                UpdatePara();    //更新这些数据参数
                state = WolfBabyState.Death;
                Destroy (this.gameObject, 2);
                GameObject.Destroy(HUDTextGO);
                        
            }
其中 UpdatePara()实现如下
    void UpdatePara()
    {
        BabySpawn._instance.currentNumber -= 1;
        ps.GetExp(exp);
        BarNPC._instance.PlusWolfKilledNumber();
    }
当任务完成后,我们需要获得奖励,如上右图所示,即点击上左图的OK按钮,提交任务,领取奖励。
    public void OnAcceptQuest()
    {
        Inventory._instance.EarnMoney (1000);
        isOnTask = true;
        ShowTaskProgress ();
    }
其中EarnMoney()函数实现如下
    public void EarnMoney(int money)
    {
        coinCount += money;
        coinNumberLabel.text = coinCount.ToString();    //更新显示
    }
至此,角色攻击系统以及与任务系统的交互就大体实现了。

猜你喜欢

转载自blog.csdn.net/s1314_jhc/article/details/80158994
今日推荐