RPG游戏《黑暗之光》流程介绍与代码分析之(十二):怪物系统的实现

第十二章:怪物系统

怪物功能是交互的重要部分,涉及到任务系统、人物状态系统等等,设计起来也较为复杂。
首先将Model中的小狼拖入场景,命名为WolfBaby,并添加动画信息

12.1 小狼的状态切换和移动

为其添加一个脚本WolfBaby以控制其行为
using UnityEngine;
using System.Collections;

public enum WolfBabyState{    //几种枚举状态,对应怪物的不同形态
    Idle,
    Walk,
    Attack,
    Death
}

public class WolfBaby : MonoBehaviour {
    

    public WolfBabyState state = WolfBabyState.Idle;    //默认动画为站立
    public string aniName_death;    //对应不同的动画
    public string aniName_walk;
    public string aniName_idle;
    public string currentAniName;    //当前播放的动画
    public float changeTime = 3;    //动画改变的时间间隔
    public float timer = 0;    //定时器
    void Awake()
    {
        currentAniName = aniName_idle;
    }

    void Update()
    {
        if (state == WolfBabyState.Death)    //根据state判断当前的状态,并做出相应操作
        {
            animation.CrossFade(aniName_death);
        }
        else if(state == WolfBabyState.Attack)    //为攻击状态
        {
                //todo,对应下文的AutoAttack
        }
        else    //巡逻状态
        {
            animation.CrossFade(currentAniName);    
            timer += Time.deltaTime;    定时器开始工作
            if(timer >= changeTime)    //当定时器大于3秒时
            {
                timer = 0;
                RandomState();    //随机生成一种动画
                animation.CrossFade(currentAniName);
            }
        }
    }

    void RandomState()
    {
        int value = Random.Range (0, 2);
        if (value == 0)
        {
            currentAniName = aniName_idle;
                
        } else
        {
            currentAniName = aniName_walk;    
        }
    }

}
可以看到小狼有两种不同的动画,每隔3秒改变一次,但在Walk状态下的小狼无法移动,需要改进
因此添加一个Character Controller,用以控制小狼的移动,并在state == WolfBabyState.Walk的时候调用SimpleMove()控制移动。
    private CharacterController cc;
    public float speed = 0.5f;
    void Awake()
    {
        cc = this.GetComponent<CharacterController> ();
    }

            if(currentAniName == aniName_walk)
            {
                cc.SimpleMove(transform.forward * speed);
            }
此时的小狼只会朝一个方向进行移动,因此我们修改RandomState()中的设置,在切换到Walk状态时给小狼一个随机的方向。
    void RandomState()
    {
        int value = Random.Range (0, 2);
        if (value == 0)
        {
            currentAniName = aniName_idle;
                
        } else
        {
            if(currentAniName != aniName_walk)    //当value为1时,即下一个播放状态为Walk,但当前状态仍为Idle的时候,改变小狼朝向的角度
            {
                transform.Rotate(transform.up * Random.Range(0,361));    //随机一个角度,改变朝向
            }
            currentAniName = aniName_walk;    
        }
    }

12.2 小狼遭受攻击

处理完移动行为后,接下来还有自动攻击,被攻击等功能,先处理被攻击功能。为小狼创建一个血量。
    public int hp = 100;    //怪物血量
    public float missRate = 0.2;    //怪物闪避率
    public void BeDamaged(int attackValue)
    {
        int value = Random.Range (0f, 1f);    //生成一个0~1之间的随机数,与missRate比较,若小于,则产生miss
        if (value > this.missRate)
        {
            this.hp -= attackValue;    //扣除血量
            if(hp <= 0)
            {
                state = WolfBabyState.Death;    //播放死亡动画,2秒后销毁
                Destroy(this.gameObject,2);
            }
        }
    }
这样达到了扣血的目的,但被攻击时的效果需要直观地显示出来,以更好地提示用户,因此我们通过Skinned Mesh Renderer组建控制怪物颜色的改变,在WolfBaby中定义两种颜色,对应普通状态和受击时的颜色。
我们定义一般状态的颜色normalColor,并通过协程控制颜色的改变,起到伤害的颜色效果
    private Color normalColor;    //存储原始的颜色,当被击效果结束后可以返回原样
    private GameObject wolfBody;
    void Awake()
    {
        wolfBody = transform.Find ("Wolf_Baby").gameObject;    //访问到控制颜色的子物体Wolf_Baby
        normalColor = wolfBody.renderer.material.color;
    }
    IEnumerator ShowWolfRed()    //通过协程代替计时器,更加简单,协程的概念见https://blog.csdn.net/jasonwang18/article/details/55519165
    {
        wolfBody.renderer.material.color = Color.red;
        yield return new WaitForSeconds (1f);
        wolfBody.renderer.material.color = normalColor;
    }
并在受到伤害时调用StartCoroutine(ShowWolfRed())即可

12.3 MISS效果

攻击怪物时加入一个闪避效果可以提高游戏体验,因此我们为Miss效果添加一个AudioClip,实现提示效果,在if (value > this.missRate)时,添加
AudioSource.PlayClipAtPoint(missSound,transform.position);
导入HUD Text创建Miss效果,将HUD Text放到UI root下,

它包含的UIFollow Target用以跟随主角或怪物,文本HUD Text用以显示Miss或扣血效果。在UI root下创建一个Invisible Widget,命名为HUDTextParent,并为其创建一个脚本HUDTextParent,并设置为单例模式。在每个物体创建时新增一个HUD Text
之后在WolfBaby中添加一个Empty物体,用以存放HUD Text,命名为WolfHUDtext,并将WolfBaby做成一个Prefab

    private GameObject wolfHUDTextGO;    //WolfBaby下的HUD Text
    private GameObject HUDTextGO;    //UI root下的HUDTextParent下的HUD Text
    public GameObject HUDTextPrefab;    //HUD Text的prefab,直接导入即可
    private HUDText showText;    //HUDTextGO下的Text信息,控制显示
    private UIFollowTarget followTarget;    //HUDTextGO下的位置信息,控制位置
    void Awake()
    {
        wolfHUDTextGO = transform.Find ("WolfHUDText").gameObject;
    }

    void Start()
    {
        HUDTextGO = GameObject.Instantiate (HUDTextPrefab, Vector3.zero, Quaternion.identity) as GameObject;    //HUDTextParent下的HUD Text由Prefab得到
        HUDTextGO.transform.parent = HUDTextParent._instance.gameObject.transform;    //并将这一Prefab作为HUDTextParent的子类
        showText = HUDTextGO.GetComponent<HUDText> ();    //取得文本和位置信息
        followTarget = HUDTextGO.GetComponent<UIFollowTarget> ();
        followTarget.target = wolfHUDTextGO.transform;    //followTarget中的位置跟随小狼的移动
        followTarget.gameCamera = Camera.main;    //followTarget中的Camera为main Camera
        followTarget.uiCamera = UICamera.current.GetComponent<Camera> ();    //followTarget中的UICamera为current Camera
    }
初始化完成后,在Miss时进行测试
    public void BeDamaged(int attackValue)
    {
        if (value > this.missRate)
        {   
        }
        else
        {
            AudioSource.PlayClipAtPoint(missSound,transform.position);
            showText.Add("Miss",Color.gray,1);    //添加显示的文本、颜色和时间
        }
    }
我们添加一个方法,作为模拟攻击测试。在Update()中,通过按下“A”键起到模拟的作用
        if (Input.GetKeyDown (KeyCode.A))
        {
            BeDamaged(1);    
        }


被攻击时,小狼会变为红色,但miss文字的显示有问题(上图灰色部分所示,字体过于巨大)
问题出现在
        HUDTextGO = GameObject.Instantiate (HUDTextPrefab, Vector3.zero, Quaternion.identity) as GameObject;    
        HUDTextGO.transform.parent = HUDTextParent._instance.gameObject.transform;    
即创建HUDText物体时的问题,我们用NGUITool创建可以避免这一情况。
HUDTextGO = NGUITools.AddChild (HUDTextParent._instance.gameObject, HUDTextPrefab);
结果如下。

在怪物被杀死时,我们要销毁WolfBaby,并且销毁HUDText,在WolfBaby脚本中添加
      if (hp <= 0)
            {
                state = WolfBabyState.Death;
                Destroy (this.gameObject, 2);
                GameObject.Destroy(HUDTextGO);
                        
            }

12.4 敌人的自动攻击部分

自动攻击的设计关系到AI的智商,这里只涉及基本的AI操作,一些高端的“拉怪”操作不在考虑范围内。。。
12.4.1 自动攻击逻辑
当state == WolfBabyState.Attack时,我们需要让小狼自动攻击。攻击包括下面几个属性
    public int attackValue;    //攻击伤害
    public string aniName_normalAttack;    //正常攻击
    public string aniName_crazyAttack;    //疯狂攻击,提升attackRate,即攻击速率
    public string aniName_nowAttack;    //当前攻击的种类
    public float normalAttackTime;    //普通攻击消耗时间
    public float crazyAttackTime;    //疯狂攻击消耗时间
    public float crazyAttackRate;    //疯狂攻击触发的概率
    public int attackRate = 1;    //攻击速率,默认为1秒1次
    public float attackTimer = 0;    //攻击的计时器,决定attackRate
    public Transform target;    //攻击目标,当触发BeDamage函数时获取目标
属性如下

自动攻击的逻辑为
  • 当人物与小狼的距离小于可攻击距离时,进行攻击    (distance < acceptAttackDistance ,攻击)
  • 当人物与小狼的距离大于可攻击距离并且小于最大攻击距离时,移动到最小距离之内,再攻击    (acceptAttackDistance < distance < maxAcceptAttackDistance,,移动再攻击)
  • 当人物与小狼距离大于最大攻击距离时,返回巡逻状态 (distance > maxAcceptAttackDistance ,取消攻击状态)
因此对函数AutoAttack()地设置如下
    public float minAttackDistance = 2f;
    public float maxAttackDistance = 5f;

    void AutoAttack()
    {
        if (target != null)    //取得目标
        {
            float distance = Vector3.Distance(target.position,transform.position);    //计算距离
            if(distance > maxAttackDistance)    //大于最大攻击距离时,切换到巡逻状态
            {
                target = null;
                state = WolfBabyState.Idle;    
            }
            else if(distance <= minAttackDistance)    //小于最小攻击距离时,攻击
            {

            }
            else    //介于最小与最大攻击距离时,移动到攻击距离内再攻击
            {
                transform.LookAt(target);
                cc.SimpleMove(transform.forward * speed);
                animation.CrossFade(aniName_walk);
            }
        }
        else
        {
            state = WolfBabyState.Idle;    
        }
    }
12.4.2 攻击行为的切换与播放
攻击行为可以拆分成两部分:攻击和距离下一次攻击开始的休息时间。因此我们将攻击状态分为3种:普通攻击、疯狂攻击以及攻击的休息间隔

先考虑distance <= minAttackDistance的情况
else if(distance <= minAttackDistance)
            {
                attackTimer += Time.deltaTime;    //计时器开启
                animation.CrossFade(aniName_nowAttack);    //先播放当前攻击动画
                if(aniName_nowAttack == aniName_normalAttack)    //判断当前攻击动画的种类
                {
                    if(attackTimer >= normalAttackTime)    //大于播放时间后,造成伤害
                    {    
                        //todo,造成伤害
                        animation.CrossFade(aniName_idle);
                    }
                }
                else if(aniName_nowAttack == aniName_crazyAttack)
                {
                    if(attackTimer >= crazyAttackTime)
                    {
                        //todo
                        animation.CrossFade(aniName_idle);
                    }
                }
                if(attackTimer > (1f/attackRate))    //如果大于攻击休息间隔时,随机一种攻击动画并重置计时器,实现一个攻击的循环
                {
                    RandomAttack();   
                    attackTimer = 0;
                }
            }

    void RandomAttack()
    {
        float value = Random.Range (0f, 1f);
        if (value > crazyAttackRate)
        {
            aniName_nowAttack = aniName_normalAttack;        
        } else
        {
            aniName_nowAttack = aniName_crazyAttack;    
        }
    }
这样就实现了攻击动画和攻击行为的循环,但暂时没有取得target目标,target要在角色对小狼造成伤害后将主角信息传递给小狼,之后进行补充。

ps:(5月3日补充)中型狼和Boss狼的创建。

中型狼和大型BOSS狼
中型狼和大型狼的素材都在RPG——>Model——>Model Enemy之中,拖入场景之中

与小狼类似,我们为其添加Animation动画和角色控制器,我们使用WolfBaby的脚本,稍作修改即可。
主要涉及修改部分:Animation动画、自身gameObject的指定、属性值以及攻击距离(大狼体积较大,攻击距离过小会导致无法正常攻击)


  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值