Unity游戏实战项目-英雄无敌

在这里插入图片描述

敌人模块

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

策划

在这里插入图片描述

需求分析

在这里插入图片描述

EnemyMotor.cs

在这里插入图片描述

EnemyMotor.cs
注意:
1.transform.LookAt(targetPos) 表示当该物体设置了Lookat并指定了目标物体时,该物体的z轴将始终指向目标物体,所以MovementForward() 函数只用改变Z坐标即可,而且此处使用Time.deltaTime,是由于MovementForward()会在Pathfinding()中调用,而Pathfinding()将会在EnemyAI中的Update中调用,这使得敌人运动可以保证是恒速。
2.由于前期没有写WayLIne类,所以需要测试代码,测试子功能是否正确,注释掉部分是测试该功能的代码。

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

/// <summary>
/// 敌人马达 提供移动、旋转、寻路的功能
/// </summary>
public class EnemyMotor : MonoBehaviour 
{

    //临时创建路点 用于测试
    //public Transform[] points;

    //创建线路
    public WayLine line;
    //速度
    public float Speed = 5;
    //移动
    public void MovementForward()
    {
        transform.Translate(0, 0, Speed * Time.deltaTime);
    }

    //朝着路点旋转
    public void LookRotation(Vector3 targetPos)
    {
        transform.LookAt(targetPos);
    }

    private int currentIndex;
    public bool Pathfinding()
    {
        //寻路结束
        if (currentIndex >= line.Points.Length || line.Points == null)
            return false;

        LookRotation(line.Points[currentIndex]);
        MovementForward();
        //到达路点后,索引值++
        if (Vector3.Distance(this.transform.position, line.Points[currentIndex]) <= 0.1f)
            currentIndex++;

        //临时创建路点 用于测试
        //if (currentIndex >= points.Length || points == null)
        //    return false;

        //LookRotation(points[currentIndex].position);
        //MovementForward();
        //if(Vector3.Distance(this.transform.position, points[currentIndex].position) <= 0.1f)
        //currentIndex++;


        return true;

    }

}

EnemyStatusInfo.cs

在这里插入图片描述

EnemyStatusInfo
注意:
public EnemySpawn spawn; 是为了EnemySpwn类中的CreatEnemy()函数中产生一个敌人后,将生成该敌人的生成器赋给该spawn变量,便于在EnemyStatusInfo类中Death()函数中完成一个敌人死后,可以用该生成器生成下一个敌人,避免使用到其他生成器,造成同一路线生成多个敌人。

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

/// <summary>
// 敌人的状态信息
/// </summary>
public class EnemyStatusInfo : MonoBehaviour
{
    public EnemySpawn spawn;

    //当前血量
    public float currentHP = 80;

    private void Demage(float amount)
    {
        currentHP -= amount;
        if (currentHP <= 0)
            //死亡
            Death();
            

    }

    private EnemyAnimation anim;

    //死亡延迟时间
    public float deathDelay = 5;
    private void Death()
    {
        //销毁当前游戏对象
        Destroy(gameObject, deathDelay);
        //播放死亡动画
        anim = GetComponent<EnemyAnimation>();
        anim.Play(anim.deatName);

        //生成下一个敌人
        spawn.GenerateEnemy();
        
    }
}

EnemyAnimation.cs

在这里插入图片描述

EnemyAnimation
注意:
1.定义各种动画名称的变量的原因
EnemyAnmation脚本挂在Solier物体上,而其子物体Model上有Animation脚本,由于不同Model的Animation中动作的名称不同,所以我们要定义各种动画名称的变量,且是Public的,这样就可以对照着不同Model的动画名称在界面中填入相对应的名称,这样的话就算Soldier的子物体Model修改了,我们也只用在EnemyAnmation脚本的界面中做出相应的修改即可,不用改变EnemyAnmation代码内容。
2. anim = GetComponentInChildren< Animation>(); 因为EnemyAnmation脚本挂在父物体Solier物体上,而我们需要寻找子物体Model上的Animation组件,所以此处使用GetComponentInChildren。
3. Play和CrossFade的区别:
Play:直接切换动画,如果人物之前处于倾斜跑步状态,则会立即变成站立状态,表现上比较不真实,特别是当两个动画姿势差别较大时。
CrossFade:通过动画融合来切换动画,第二个参数可以指定融合的时间,如果人物之前处于倾斜跑步状态,则会在指定的融合时间内逐渐变成站立状态,表现上接近真实的人物动作切换效果。
在这里插入图片描述
EnemyAnimation

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

/// <summary>
/// 敌人动画
/// </summary>
public class EnemyAnimation : MonoBehaviour 
{
    public string runName = "run";
    //攻击动画
    public string atkName = "shooting";

    //死亡动画
    public string deatName = "death";

    //闲置动画
    public string idleName = "idleWgun";


    private Animation anim;
    private void Awake()
    {
        anim = GetComponentInChildren<Animation>();
    }

    //播放动画
    public void Play(string name)
    {
        anim.CrossFade(name);    
    }
	
    //判断动画是否播放
    public bool IsPlaying(string name)
    {
        return anim.IsPlaying(name);
    }

}

EnemyAI.cs

在这里插入图片描述

EnemyAI
1.快捷键:选中内容,按control+R+M键,创建新方法。
2.Update中执行游戏逻辑,根据敌人状态,执行寻路或攻击。

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

/// <summary>
/// 敌人 人工智能
/// </summary>
public class EnemyAI : MonoBehaviour 
{
    //获取[敌人马达、敌人动画]脚本对象引用
    private EnemyMotor motor;
    private EnemyAnimation anim;

    //枚举敌人状态
    public enum State
    {
        //攻击
        Attack,
        //寻路
        Pathfinding
    }
    private void Start()
    {
        motor = GetComponent<EnemyMotor>();
        anim = GetComponent<EnemyAnimation>();
    }

    private State state = State.Pathfinding;

    
    private float atkTimer;
    //攻击间隔
    public float atkInterval = 0.5f;
    //Update中执行的是游戏逻辑
    private void Update()
    {
        switch (state)
        {
            case State.Attack:
                Attack();
                break;
            case State.Pathfinding:
                Pathfinding();
                break;
            default:
                break;
        }
    }

    private void Attack()
    {
        //当攻击动画未播放时,播放闲置动画
        if (!anim.IsPlaying(anim.atkName))
        {
            anim.Play(anim.idleName);
        }
        if (atkTimer <= Time.time)
        {
            anim.Play(anim.atkName);
            atkTimer = Time.time + atkInterval;
        }
    }

    private void Pathfinding()
    {
        //执行寻路
        //如果寻路结束,修改状态为攻击
        anim.Play(anim.runName);
        if (!motor.Pathfinding())
            state = State.Attack;
    }

}

敌人生成器

在这里插入图片描述

策划

在这里插入图片描述

需求分析

在这里插入图片描述

创建根路线

创建根路线:
1.调试阶段让路点坐标的标签显示出来,方便观察。
2.敌人生成器EnemySpawn脚本应该挂在WayLineRoot这个父物体身上,这样就可以选择下面不同的子路线来创建不同的敌人。
在这里插入图片描述

WayLine.cs

WayLine
1.Points这个数组存放的是三维向量的引用,所以默认是null,该Point变量的属性是可读可写。
2.当创建一条线路时,需要初始化线路,所以创建了构造函数。
3.当创建的数组内部存放的是引用类型的时候,我们往往需要New 引用类型来初始化。

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

/// <summary>
/// 路线类
/// </summary>
public class WayLine
{
    //坐标数组 装的是三维向量的引用 默认是null 
    public Vector3[] Points { get; set; }

    //线路是否可用 默认是 false
    public bool IsUseAble { get; set; }

    //构造函数,初始化属性
    public WayLine(int countPoints)
    {
        this.Points = new Vector3[countPoints];
        this.IsUseAble = true;
    }
     
}

EnemySpawn

代码实现

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

注意事项

EnemySpawn
重点:1.lines这数组用于储存所有的路线,也是引用类型,所以需要New 引用类型来初始化,New完以后lines成为一个有数组大小的数组,且lines,IsUseable=true,但是每个数组内位置仍然为null,接下来就是遍历子物体,即界面中WayLIneRoot下面的子路线Wayline01、Wayline02、Wayline03,由于每条子路线为null,所以将每条子路线都New 引用类型,这样就初始化了每条子路线,但现在每条子路线中的Points是(0,0,0),所以遍历每条子路线的路点,将每个路点都赋值。
在这里插入图片描述2.由于有的线路如果当前产生了敌人,有敌人占据,则该线路不可用 IsUseable = false,所以需要SelectWayLines()方法将所有的线路进行筛选,选出所有的有用路线,且将该路线放在List<>集合里面,由于该方法的返回值是WayLine[]数组,所以需要ToArray操作,将集合转为数组。
3.在所选择路线的起始点创建一个敌人以后,需要注意需要配置敌人的信息,这个敌人创建好以后,上面应该自动挂着EnemyAI、EnemyAnimation、EnemyStatusInfo、EnemyMotor脚本(由于创建预制体时候已经设置好了哦),由于寻路需要知道线路,所以找到所创建的敌人的EnemyMotorjiao组件的引用,将路线line传给它,此外该线路被这个敌人占据,所以当前不可用。
4.由于后面敌人可能会死亡,所以每次创建敌人时,将产生该敌人的生成器保留,为了下次可能会使用该生成器来创建这条线路的敌人,避免产生一条线路产生多个敌人。

EnemySpawn.cs

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

/// <summary>
/// 敌人生成器
/// </summary>
public class EnemySpawn : MonoBehaviour 
{
    
    //存储所有路线
    private WayLine[] lines;

    //记录敌人预制体
    public GameObject[] enemyTypes;

    //开始需要创建的敌人数目
    public int startCount = 2;

    //记录已经产生的敌人数量
    private int spawnedCount;

    //记录产生敌人数量的上限
    public int maxCount;

    //最大延迟时间
    public float maxDelay = 10;
   
    //计算所有路线以及坐标
    private void CalculateWayLines()
    {
        lines = new WayLine[transform.childCount];
        for (int i = 0; i < transform.childCount; i++)
        {
            Transform WaylinesTF = transform.GetChild(i);
            lines[i] = new WayLine(WaylinesTF.childCount);

            for (int pointsIndex = 0; pointsIndex < WaylinesTF.childCount; pointsIndex++)
            {
                lines[i].Points[pointsIndex] = WaylinesTF.GetChild(pointsIndex).position;
            }

        }
        
    }

    //产生敌人
    private void CreatEnemy()
    {
        //获取所有有用路线
        WayLine[] useableLines = SelectWayLines();
        //随机选择其中一条路线
        WayLine wayLine = useableLines[Random.Range(0, useableLines.Length)];

        //创建一个敌人
        int enemyTypeIndex = Random.Range(0, enemyTypes.Length);
        GameObject enemyGO = Instantiate(enemyTypes[enemyTypeIndex], wayLine.Points[0], Quaternion.identity) as GameObject;

        //给敌人配置信息
        EnemyMotor motor = enemyGO.GetComponent<EnemyMotor>();
        motor.line = wayLine;
        //wayLine.IsUseAble = false;//产生了敌人,该路线已经被占用
        motor.line.IsUseAble = false;
        enemyGO.GetComponent<EnemyStatusInfo>().spawn = this;

    }

    //计算出所有的路线,并且产生初敌人 
    private void Start()
    {
        CalculateWayLines();
        for (int i = 0; i < startCount; i++)
        { 
            GenerateEnemy();
        }
       
       
    }

    public void GenerateEnemy()
    {
        spawnedCount++;
        if (spawnedCount >= maxCount) return;

        float delay = Random.Range(0, maxDelay);
        //延迟产生敌人
        Invoke("CreatEnemy", delay);
    }

    //获取所有有用路线
    private WayLine[] SelectWayLines()
    {
        List<WayLine> WayLines = new List<WayLine>(lines.Length);
        foreach (var item in lines)
        {
            if (item.IsUseAble) WayLines.Add(item);
        }

        return WayLines.ToArray();
    }
}

敌人模块代码的逻辑顺序:
EnemySpawn(挂在WayLineRoot产生敌人) --> WayLine --> EnemyAI --> EnemyMotor --> EnemyAnimation --> EnemStausInfo(如果敌人受伤或死亡)

注意代码的执行顺序:由于Sodiler物体上挂有四个脚本,需要注意在同一物体上脚本的执行,关注下Awake、Start、Update的执行顺序。

武器模块

枪口火光特效

在这里插入图片描述
在这里插入图片描述
注意:
1.Gun应该放在FistPersonCharacter下面,而不是FPSController下面,因为Camera组件在FistPersonCharacter里面,这样Gun才会随着视角移动旋转,
2.因为可能有多把枪,所以创建空物体Gun,再做一个空物体HandGun作为手枪分类,该处手枪预制体放在HangGun下面。

  • 8
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 24
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值