官方案例--Survival Shoot(二)

四、添加敌人

1、Models--->Characters---->Zombunny,拖拽到scene场景中,会自动贴合地面,放到玩家附近。

2、为了实现击中的效果,要在敌人身上挂载粒子特效,在Perfabs--->HitParticles,拖拽它到Zombunny身上。将Layer层变成Shootable(提示,环境Environment也是shootable层),添加rigidbody组件,设置和玩家Player一样,再添加capsule collider(设置center(0,0.8,0),Height(1.5)),,之后添加sphere collider组件(勾选Is Trigger,centerY=0.8,Radius=0.8),球碰撞体比胶囊碰撞体大,用于检测是否接触玩家。

3、添加Audio Source组件,受伤是发出声音。添加声音,取消勾选play on awake。

4、追赶玩家,window--->AI--->Navigation,给zombunny添加Nav Mesh Agent组件。speed=3,radius = 0.3, height = 1.1 , stopping distance = 1.3,

5、Navigation--->Bake,调整参数,之后点击右下角的Bake,之后场景(Environment已经勾选了static)上就会出现蓝色区域,是可以行走的区域

6、创建动画控制器Animator Controller,重命名EnemyAC,拖拽给ZomBunny,添加动画Idie,Move,Death,创建两个Trigger类型参数PlayerDead,Dead,设置状态变化条件。

   7、创建脚本EnemyMovement1,添加到敌人上。  

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

public class EnemyMovement1 : MonoBehaviour
{
    //跟随的玩家
    Transform Player;
    // NavMeshAgent组件
    NavMeshAgent nav;
    private void Awake()
    {
        Player = GameObject.FindGameObjectWithTag("Player").transform;
        nav = GetComponent<NavMeshAgent>();
    }

    // Update is called once per frame
    void Update()
    {
        //朝着玩家方向移动,多个敌人不会互相碰撞、不会相互相互穿过,自动寻路找玩家
        nav.SetDestination(Player.position);
    }
} 

五、生命值UI

1、创建Canvas,重命名为HUDCanvas添加Canvas Group组件,没有交互,取消勾选Interactable;由于用到了屏幕到地面的射线,不能让画布阻挡住射线,取消勾选Blocks RayCasts;

   

  2、在HUDCanvas下创建一个空对象Empty Object重命名为HealthUI,放到左上角,设置对齐点屏幕为左上角,设置Width=75,Height=60

   

  3、在HealthUI下创建Image重命名为Heart,设置宽高=30,Source Image选择Heart

  

  4、在HealthUI下创建Slider重命名为HealthSlider,因为不需要交互,删除Handle Slide Area.设置PosX=95,Transition=None,MaxValue=100;

   

  

  5、模拟玩家受到伤害时,屏幕闪一下,在HUDCanvas下创建Image,重命名为DamageImage,使其铺满整个屏幕,将透明度设置成0

六、玩家生命系统

  1、创建脚本PlayerHealth1 ,拖拽到Player上,并将变量赋值

  

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

public class PlayerHealth1 : MonoBehaviour
{
    // 设置最开始的血量为100
    public int startingHealth = 100;
    // 现在的血量
    public int currentHealth;
    // slider组件
    public Slider healthSlider;
    // 玩家受伤时显示的图片
    public Image damageImage;
    // 死亡时的音效
    public AudioClip deathClip;
    // 过渡受伤-不受伤颜色时的速度
    public float flashSpeed = 5f;
    // 受伤时的颜色
    public Color flashColor = new Color(1f, 0f, 0f, 0.1f);

    // 动画控制器组件
    Animator anim;
    // AudioSouce组件
    AudioSource playerAudio;
    // 玩家移动的组件
    PlayerMovement1 playerMovement1;
    // 是否死亡
    bool isDead;
    // 是否受伤
    bool isDamaged;
    // Start is called before the first frame update

    private void Awake()
    {
        // 获取组件
        anim = GetComponent<Animator>();
        playerAudio = GetComponent<AudioSource>();
        playerMovement1 = GetComponent<PlayerMovement1>();
        // 设置当前血量
        currentHealth = startingHealth;
    }

    // Update is called once per frame
    void Update()
    {
        // 如果受伤,设置damageImage的颜色和透明度
        if (isDamaged)
        {
            damageImage.color = flashColor;
        }
        else
        {
            damageImage.color = Color.Lerp(damageImage.color, Color.clear, flashSpeed * Time.deltaTime);
        }
        // 受伤完了之后就将isDamage设置成false
        isDamaged = false;
    }

    // 受伤的方法,不是在这个脚本中调用的,参数是伤害值
    public void TakeDamage(int amount)
    {
        // 受伤
        isDamaged = true;
        // 当前血量减去伤害值,设置slider的值,受伤音效
        currentHealth -= amount;
        healthSlider.value = currentHealth;
        playerAudio.Play();
        // 如果当前血量小于等于0并且不是死亡状态,才会调用死亡方法
        // 如果已经是死亡状态,血量小于0,也不会调用Death方法
        if(currentHealth <=0 && !isDead)
        {
            Death();
        }
    }

    // 死亡方法
    void Death()
    {
        // 死亡状态是true;播放死亡动画;播放死亡音效;
        isDead = true;
        anim.SetTrigger("Die");
        playerAudio.clip = deathClip;
        playerAudio.Play();
        // 死亡之后就不能移动了;
        playerMovement1.enabled = false;
    }
}

  2、创建脚本EnemyAttack1 ,拖拽到ZomBunny敌人身上

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

public class EnemyAttack1 : MonoBehaviour
{
    // 攻击间隔、攻击力
    public float timeBetweenAttacks = 0.5f;
    public int attackDamage = 10;
    // 组件
    Animator anim;
    GameObject player;
    PlayerHealth1 playerHealth1;
    // 是否在攻击范围
    bool isPlayerIn;
    float timer;

    private void Awake()
    {
        player = GameObject.FindGameObjectWithTag("Player");
        playerHealth1 = player.GetComponent<PlayerHealth1>();
        anim = GetComponent<Animator>();
    }

    // 当进入zomBunny的sphere collider自动触发 
    private void OnTriggerEnter(Collider other)
    {
        // 判断进入的是不是玩家
        if(other.gameObject == player)
        {
            isPlayerIn = true;
        }
    }

    private void OnTriggerExit(Collider other)
    {
        // 判断离开的是不是玩家
        if (other.gameObject == player)
        {
            isPlayerIn = false;
        }
    }

    // Update is called once per frame
    void Update()
    {
        // 计时,time.deltatime每祯时间
        timer += Time.deltaTime;
        // 如果时间大于攻击间隔并且玩家在攻击范围
        if(timer >= timeBetweenAttacks && isPlayerIn)
        {
            Attack();
        }
        // 如果玩家的生命值为0
        if(playerHealth1.currentHealth <= 0)
        {
            anim.SetTrigger("PlayerDead");
        }
    }
    void Attack()
    {
        // 计时器清零
        timer = 0f;
        // 如果玩家生命值大于0,就调用playerHealth1脚本中的TakeDamage方法减血
        if (playerHealth1.currentHealth > 0)
        {
            playerHealth1.TakeDamage(attackDamage);
        }
    }
}

七、射击,敌人生命值

  1、敌人的生命值,填充变量

  

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

public class EnemyHealth1 : MonoBehaviour
{
    // 开始血量,当前血量   
    public int startingHealth = 100;
    public int currentHealth;
    // 敌人死亡后,不会直接消失,会下沉,下沉速度
    public float sinkSpeed = 2.5f;
    // 分数
    public int scoreValue = 10;
    // 死亡动画
    public AudioClip deathClip;

    Animator anim;
    AudioSource enemyAudio;
    ParticleSystem hitParticles;
    CapsuleCollider capsuleCollider;
    bool isDead;
    bool isSinking;

    private void Awake()
    {
        anim = GetComponent<Animator>();
        enemyAudio = GetComponent<AudioSource>();
        // 遍历所有的子对象,寻找组件
        hitParticles = GetComponentInChildren<ParticleSystem>();
        capsuleCollider = GetComponent<CapsuleCollider>();
        currentHealth = startingHealth;
    }

    // Update is called once per frame
    void Update()
    {
        // 是否要下沉
        if (isSinking)
        {
            transform.Translate(-Vector3.up * sinkSpeed * Time.deltaTime);
        }
    }

    // 敌人受伤 参数:伤害值,击中位置
    public void TakeDamage(int amount, Vector3 hitPoint)
    {
        // 如果死亡,就结束不执行下面的代码
        if (isDead) return;
        // 播放敌人受伤音效
        enemyAudio.Play();
        // 减血
        currentHealth -= amount;
        // 设置粒子位置就是击中位置
        hitParticles.transform.position = hitPoint;
        hitParticles.Play();
        // 如果生命值小于等于0,调用死亡方法
        if (currentHealth <= 0) Death();
    }

    // 死亡方法
    void Death()
    {
        
        isDead = true;
        // 因为要让敌人死后下沉,所以需要将胶囊碰撞器的Trigger变成true,否则下沉不了
        capsuleCollider.isTrigger = true;
        // 死亡动画
        anim.SetTrigger("Dead");
        enemyAudio.clip = deathClip;
        enemyAudio.Play();
    }

    // 下沉方法。这个是在死亡动画播完之后自己调用,动画上已经绑定好下沉事件了
    public void StartSinking()
    {
        // 禁用这个组件,不需要寻路了
        GetComponent<NavMeshAgent>().enabled = false;

         // 下沉的时候EnemyMove1中还会执行 nav.SetDestination()方法,把这个脚本也禁用,不然会报错。
          GetComponent<EnemyMovement1>().enabled = false;

// 设置成true,不受物理力控制,unity也不会计算了
        GetComponent<Rigidbody>().isKinematic = true;
        isSinking = true;
        Destroy(gameObject, 2f); 
    }
}

  2、敌人攻击脚本也需要修改下,在EnemyAttack1脚本中要用到EnemyHeath1脚本,因为敌人攻击有3个条件

  •     大于攻击时间间隔
  •     玩家在攻击范围
  •     自己还有血量

  

   3、PerFabs--->GunParticles,复制其Particle System组件到player子对象的GunBarrelEnd上,作为新组件

  4、在GunBarrelEnd上添加Line Renderer组件,设置其材质为LineRenderMaterial(包中自带),设置Width为0.05,先将这个组件取消勾选,刚开始不需要显示

  5、在GunBarrelEnd上添加Lights组件,调整灯光的颜色为黄色,先将这个组件取消勾选,刚开始不需要显示

  6、在GunBarrelEnd上添加Audio Source组件,添加Player GunShot音效。

  7、创建Player Shooting1脚本,放在GunBarrelEnd上,

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

public class PlayerShooting1 : MonoBehaviour
{
    // 每发子弹的伤害
    public int damagePerShot = 20;
    // 射击的时间间隔
    public float timeBetweenBullets = 0.15f;
    // 设置攻击范围
    public float range = 100f;

    // 计时
    float timer;
    // 射线
    Ray shootRay;
    // 存储击中物体的信息
    RaycastHit shootHit;
    // 可射击层(环境、敌人)
    int shootableMask;
    ParticleSystem gunParticles;
    LineRenderer gunLine;
    AudioSource gunAudio;
    Light gunlight;
    // 效果展示时间
    float effectDisplayTime = 0.2f;

    private void Awake()
    {
        shootableMask = LayerMask.GetMask("Shootable");
        gunParticles = GetComponent<ParticleSystem>();
        gunlight = GetComponent<Light>();
        gunLine = GetComponent<LineRenderer>();
        gunAudio = GetComponent<AudioSource>();
    }

    // Update is called once per frame
    void Update()
    {
        timer += Time.deltaTime;
        if (Input.GetButton("Fire1") && timer >= timeBetweenBullets)
        {
            Shoot();
        }

        if (timer >= timeBetweenBullets * effectDisplayTime)
        {
            DisableEffects();
        }
    }

    public void DisableEffects()
    {
        gunLine.enabled = false;
        gunlight.enabled = false;
    }

    void Shoot()
    {
        // 计时清零
        timer = 0f;
        gunAudio.Play();
        gunlight.enabled = true;

        // 如果射击粒子还在播放,需要先停止
        gunParticles.Stop();
        gunParticles.Play();

        // 
        gunLine.enabled = true;
        // line Renderer 划线第一个点为枪
        gunLine.SetPosition(0, transform.position);

        // 射线开始位置,枪管,方向:z轴方向
        shootRay.origin = transform.position;
        shootRay.direction = transform.forward;

        // 物理系统发射射线
        // 如果再射击范围内,射击到指定层的射击物体
        if (Physics.Raycast(shootRay, out shootHit, range, shootableMask))
        {
            EnemyHealth1 enemyHealth = shootHit.collider.GetComponent<EnemyHealth1>();
            if (enemyHealth != null)
            {
                enemyHealth.TakeDamage(damagePerShot, shootHit.point);
            }
            //line Renderer 划线第二个点为射击到的点
            gunLine.SetPosition(1, shootHit.point);
        }
        else
        {   //line Renderer 划线第二个点为射线起始点+射击范围
            gunLine.SetPosition(1, shootRay.origin + shootRay.direction * range);
        }

    }
}

  8、当玩家死亡后,就不能射击了。所以在PlayerHealth1中禁用PlayerShooting1组件;

   

八、计分

  1、在HUDCanvas下创建一个Text,重命名为ScoreText,放到右上角,改变字体、大小、颜色,对齐方式;添加Shadow(阴影)组件,调整值。

  

   2、创建ScoreManager1脚本,放到ScoreText身上。  

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

public class ScoreManager1 : MonoBehaviour
{
    // 将这个变量设置为实例变量,这样就不需要GetComponent获取组件了
    // 因为这是单人游戏,只有一个score,多人游戏有多个score就不行了
    public static int score;

    Text scoreText;
    private void Awake()
    {
        scoreText = GetComponent<Text>();
        score = 0;
    }

    // Update is called once per frame
    void Update()
    {
        scoreText.text = "Score: " + score;
    }
}

   3、在脚本EnemyHealth1中的StartSinking方法中添加加分的代码。

  

  4、将Zombunny变成预制体,之后从Hierarchy中删除

九、生成敌人

  1、将Hellephant、ZomBear也做成预制体,挂载的脚本和Zombunny一样,修改一下参数,添加自己想要的效果,比如Nav Mesh Agent脚本中的speed(寻路的速度);EnemyHealth1中脚本中生命值、分数;Enemy Attack1脚本中中的攻击间隔、伤害值等等

  

  2、修改Animator组件中的动画控制器,ZomBear可以用ZomBunny的,但是Hellephant不行,创建Animator Override Controller,重命名HellephantAC,将EnemyAC拖拽进去,然后将Hellephant自己的动画对应的放进去。

   

   3、创建一个空物体重命名EnemyManager,创建脚本EnemyManager1,挂载到EnemyManager上,挂3个,分别对于3个敌人。

  

using UnityEngine;

namespace CompleteProject
{
    public class EnemyManager1 : MonoBehaviour
    {
        // 玩家的血量
        public PlayerHealth playerHealth;  
        //生成的敌人
        public GameObject enemy;       
        // 生成的时间间隔
        public float spawnTime = 3f;   
        // 生成点
        public Transform[] spawnPoints; 


        void Start()
        {
            // 延迟spawnTime秒后调用Spawn方法,之后每隔spawnTime秒重复调用Spawn方法
            InvokeRepeating("Spawn", spawnTime, spawnTime);
        }


        void Spawn()
        {
            // 如果玩家生命值小于0,退出
            if (playerHealth.currentHealth <= 0f)
            {
                return;
            }

            // 随机索引,0,生成点列表的长度
            int spawnPointIndex = Random.Range(0, spawnPoints.Length);

            // 用Instantiate方法生成敌人
            Instantiate(enemy, spawnPoints[spawnPointIndex].position, spawnPoints[spawnPointIndex].rotation);
        }
    }
}

  4、创建生成点,用3个人空对象表示,修改位置角度。

  

    

十、游戏结束

  

 

posted on 2021-12-02 17:49  酱紫安  阅读(134)  评论(0)    收藏  举报

导航