Unity学习笔记(五)——3DRPG游戏(2)

添加更多的敌人

编辑EnemyController,解决报错导致敌人无法注册观察者模式,从而无法执行敌人庆祝动画

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public enum EnemyStatus { GUARD,PATROL,CHASE,DEAD } //分别表示守卫(站桩)、巡逻(来回走动)、追逐、死亡状态
[RequireComponent(typeof(NavMeshAgent))]  //拖拽时自动添加component
public class EnemyController : MonoBehaviour,IEndGameObserver
{
    private EnemyStatus enemyStatus;
    ...

    void Start()
    {
        //在点击Play时才会执行,在Awake后执行
        if (isGuard)
        {
            //如果勾选了站桩,进入守卫模式
            enemyStatus = EnemyStatus.GUARD;
        }
        else 
        {
            //否则进入巡逻模式
            enemyStatus = EnemyStatus.PATROL;
            GetNewWayPoint();//获取一个巡逻路径点
        }
        //TODO: 场景切换后修改
        GameManager.Instance.AddOberver(this);
    }

    //启用时
    //void OnEnable() //在做场景加载时用到
    //{
    //    GameManager.Instance.AddOberver(this);//在执行OnEnable时没有找到GameManger
    //}

    //禁用时(与OnDestory区别:销毁完成后执行)
    void OnDisable()
    {
        //人物消失或游戏停止时执行
        if (GameManager.isInitialized) return; //如果GameManager没有生成时直接return
        GameManager.Instance.RemoveObserver(this);
    }

    ...
}

编辑PlayerController,避免Player死亡时仍然可以移动,添加对是否死亡的判断

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

public class PlayerController : MonoBehaviour
{
    private NavMeshAgent agent;
    ...

    public void MoveToTarget(Vector3 target) 
    {
        StopAllCoroutines(); //这个代码是用于:当人物向怪物移动攻击时,如果鼠标点击了其他地方可以打断向怪物移动的协程
        if (isDead) return; //如果已经死亡则return
        agent.isStopped = false;//还原非禁止状态,保证攻击后人物能继续移动
        agent.destination = target; //指定人物移动到的位置
    }

    private void EventAttack(GameObject target)
    {
        if (isDead) return; //如果已经死亡则return
        if (target != null) 
        {
            attackTarget = target;
            characterStatus.isCritical = UnityEngine.Random.value < characterStatus.attackData.criticalChance;//计算是否暴击
            StartCoroutine(MoveToAttackTarget());//执行携程函数 StartCoroutine:用于创建和启动协程(Coroutine)的核心函数,可以暂停执行并在之后的某个时间点继续执行
        }
    }

    ...
}

如果再复制一个Slime,当消灭掉一只时,另一只也死亡了,这是因为多个Slime用的同一个Slime Data,因此我们编辑CharacterStatus,创建模板并使用模板创建数据对象

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

public class CharacterStatus : MonoBehaviour
{
    public CharacterData_SO templateData;//模板数据
    public CharacterData_SO characterData;
    public AttackData_SO attackData;
    [HideInInspector] //不希望在界面中显示
    public bool isCritical;//是否暴击

    void Awake()
    {
        if (templateData != null) 
        {
            characterData = Instantiate(templateData);//Instantiate:借助已有的预制体(Prefab),能够创建新的游戏对象实例
        }
    }

    ...
}

然后修改两个Slime的配置如图

这样就不会互相影响了

添加新的怪物:将其拖拽到Hierarchy

删除其原有的Controller

编辑EnemyController,添加新的RequireComponent

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public enum EnemyStatus { GUARD,PATROL,CHASE,DEAD } //分别表示守卫(站桩)、巡逻(来回走动)、追逐、死亡状态
[RequireComponent(typeof(NavMeshAgent))]  //拖拽时自动添加component
[RequireComponent(typeof(CharacterStatus))]  //拖拽时自动添加component
public class EnemyController : MonoBehaviour,IEndGameObserver
{
    private EnemyStatus enemyStatus;
    private NavMeshAgent agent;
    ...
}

如图进行添加

然后将其Tag设为Enemy,并添加Box Collider组件

设置Agent属性

创建基础数值对象(Create->Character Status->Data)取名为TurtleShell Data,设置如图

创建攻击数据对象(Create->Attack->Attack Data)取名为TurtleShell BaseAttackData,设置如图

绑定攻击数据对象和基础数据对象

配置EnemyController属性

在Animator下创建Animator Override Controller,取名为Enemy_TurtleShell

在Enemy_TurtleShell属性界面中选择Enemy_Slime作为需要覆盖的原始类,下面的是原始类已有的动画,将需要的替换即可

ctrl+D复制Attack01和Attack02,然后拖拽到Enemy/TurtleShell(需新建此文件夹)下

绑定Enemy_TurtleShell

然后逐一去替换TurtleShell下动画

如果发现多了一个CharactorStatus一定要记得删掉

引入新的素材

Mini Legion Grunt PBR HP Polyart | Characters | Unity Asset Store

Mini Legion Rock Golem PBR HP Polyart | Characters | Unity Asset Store

将其添加到我的资源,然后进行Download、Import,然后将其放在Assets Packs下,如图

同样不要忘了选择Edit->Render Pipline,如图选择将所有素材都适配渲染组件,然后选择Proceed

然后我们将Prefab下的素材拖拽到Hierarchy创建两个敌人,将其摆放到想要的位置

将Hierarchy的TurtleShell拖拽到Prefebs/Characters下,选择Original Prefab

为GruntPolyart新建Box Collider组件,然后调整其范围(使用矩形面上的小点);设置Tag为Enemy;删掉原有的Controller;

另一个怪物同理,另外需要改一下它的名字

修改野人、石头人身体材质(修改后身体颜色会更亮、反光效果)

野人

为什么不用和乌龟一样的override:因为野人的重攻击动画有俩个动作(算一个技能攻击),需要单独弄

crtl+d复制Enemy_Slime,并重命名为Enemy_Grunt(复制出来的可更改条件或删除某一动画;override的时无法更改的一旦改了会影响原来的动画)

绑定对象

修改Animator中每个状态对应的动画(如图,其他略,共10个)

特别注意这个

修改转换参数

创建Enemy文件,在其下创建名为Grunt的C#脚本

编辑Grunt文件

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

public class Grunt : EnemyController
{
    
}

将文件拖拽到GruntPolyart对应的Inspector窗口内,然后就会自动添加组件

编辑agent属性

创建Grunt Data(Create->Character Status->Data)并配置基础属性

创建Grunt BaseAttackData(Create->Attack->Attack Data)并配置攻击属性

绑定属性

配置其他属性

添加特定帧下函数

Attack02同样(两个动作中,第二个挥砍动作触发函数)

编辑EnemyController,将attackTarget设置为protected,便于子类(Grunt)访问

public class EnemyController : MonoBehaviour,IEndGameObserver
{
    ...
    protected GameObject attackTarget; //攻击目标
    ...
}

编辑Grunt

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

public class Grunt : EnemyController
{
    [Header("Skill")]
    public float kickForce=10;//攻击力
    public void KickOff()
    {
        if (attackTarget != null)
        {
            transform.LookAt(attackTarget.transform);//攻击时面朝目标
        }
    }

}

绑定击飞时的函数

继续编辑Grunt 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI; //为了获取agent

public class Grunt : EnemyController
{
    [Header("Skill")]
    public float kickForce=10;//击飞时的力
    public void KickOff()
    {
        if (attackTarget != null)
        {
            transform.LookAt(attackTarget.transform);//攻击时面朝目标
            Vector3 direction = attackTarget.transform.position - transform.position; //被攻击对象的坐标-攻击对象坐标得到一个三维向量
            direction.Normalize();//将向量归一化
            attackTarget.GetComponent<NavMeshAgent>().isStopped = true;//被击飞时停止所有动作
            attackTarget.GetComponent<NavMeshAgent>().velocity = direction * kickForce;//给一个方向为direction,大小为kickForce的力
        }
    }

}

重新设置攻击属性

Kick Force决定了击飞的距离,越大击飞越远

通过演示,可以看到怪物会先先推一下Player然后再进行攻击

为Player添加熏晕效果:创建trigger命名为Dizzy,然后拖拽动画到如图位置,配置转换属性即可

调整眩晕动画播放速度(现在太慢了),如图修改Speed即可

发现受伤时、眩晕时Player仍然能鼠标移动,这肯定是不对的

选中GetHit状态,然后点击Add Behivior->New Script->输入StopAgent->Create And Add

新创建的脚本默认会放在Assets根目录下

将其拖拽到新创建的文件夹Animation Behavior内即可

编辑StopAgent

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

public class StopAgent : StateMachineBehaviour
{
    // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state 动画开始时执行
    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        animator.GetComponent<NavMeshAgent>().isStopped = true;
    }

    // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks 动画执行时一直执行
    override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        animator.GetComponent<NavMeshAgent>().isStopped = true; //在每次点击鼠标时都会把isStopped置为false,所以需要在这里一直把isStopped改为true
    }

    // OnStateExit is called when a transition ends and the state machine finishes evaluating this state 动画结束时执行
    override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        animator.GetComponent<NavMeshAgent>().isStopped = false;
    }

    // OnStateMove is called right after Animator.OnAnimatorMove()
    //override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    //{
    //    // Implement code that processes and affects root motion
    //}

    // OnStateIK is called right after Animator.OnAnimatorIK()
    //override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    //{
    //    // Implement code that sets up animation IK (inverse kinematics)
    //}
}

然后在眩晕状态也绑定刚才的脚本

让敌人在攻击时不移动

Slime如果设置了,TurtleShell就不用设置了

当敌人死亡时报错了,这是因为在敌人死亡时Agent找不到了(在死亡时我们将agent.enable=false了)

配置摄像机模式更舒服一些(无论怎么移动Player,摄像机都会相对与Player静止,保证游戏一开始,摄像机在人物正背面)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值