NO.85——Unity官方案例nightmare从0开始实现(三)


接上一篇博客

玩家射击管理

基本配置
  • 在玩家预制体的第二层级,有一个GunBarrelEnd子物体,在其上管理我们的射击事件。
  • 首先,添加射击的声音。
  • 在资源包中找到Gunparticles这个粒子特效,拖到物体上,作为射击开火的特效。
  • 添加Light组件,设置颜色为黄色,意思是火线。
  • 添加LineRender组件,设置射线粗细以及材质
    在这里插入图片描述
添加射击脚步
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityProject;

public class PlayerShooting : MonoBehaviour
{
    public int damagePerShot = 20;
    public float timeBetweenBullets = 0.15f;
    public float range = 100f;

    float timer;
    Ray shootRay; //子弹飞行时的射线轨迹
    RaycastHit shootHit;
    int shootableMask;  //shootable层的掩码
    ParticleSystem gunParticles;
    LineRenderer gunLine;
    AudioSource gunAudio;
    Light gunLight;
    float effectsDisplayTime = 0.2f;

    void Awake()
    {
        shootableMask = LayerMask.GetMask("Shootable");
        gunParticles = GetComponent<ParticleSystem>();
        gunLine = GetComponent<LineRenderer>();
        gunAudio = GetComponent<AudioSource>();
        gunLight = GetComponent<Light>();
    }
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        timer += Time.deltaTime;
        //玩家按下射击键,射击
        if (Input.GetButton("Fire1") && timer >= timeBetweenBullets)
        {
            Shoot();
        }
        //玩家未按下射击键,且超过规定时间间隔
        if (timer >= timeBetweenBullets * effectsDisplayTime)
        {
            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;
        gunLine.SetPosition(0, transform.position);

        shootRay.origin = transform.position;
        shootRay.direction = transform.forward;

        //检测shootRay在指定方向(由transform.forward指定)上和指定层(shootableMask)上与怪物对象的碰撞情况
        if (Physics.Raycast(shootRay, out shootHit, range, shootableMask))
        {
            //如果shootRay和shootableMask层上的对象发生率碰撞,则调取碰撞对象上的EnemyHealth脚本
            EnemyHealth enemyHealth = shootHit.collider.GetComponent<EnemyHealth>();
            //如果组件不为空
            if (enemyHealth != null)
            {
                //对象的生命值减少damagePerShot,且在击中点shootHit.point播放击中的粒子特效
                //然后设置LineRender组件的终点为碰撞点。这样就会显示激光的效果
                enemyHealth.TakeDamage(damagePerShot, shootHit.point);
            }
            //终点序号为1,起点序号为0
            gunLine.SetPosition(1, shootHit.point);
        }
        else
        {
            //如果未击中对象,则在射击方向上显示长度为range的激光
            gunLine.SetPosition(1, shootRay.origin + shootRay.direction * range);
        }
    }
}

玩家生命值管理

建立玩家生命值UI元素
  • 首先,我们在Hierachy层级新建UI—Canvas,重命名为HUDCanvas。
  • 在其上建立HealthUI空物体,将其位置定位在左下角。
  • 在HealthUI子物体上建立一个Image物体和滑动条,自定义进行设置
  • 另外呢,一般咱玩游戏时,玩家受到伤害,在UI上会有交互,这里也样。在画布下新建Image物体重命名为DamageImage。设置它为平铺的效果,铺满整个屏幕。在脚本中来设置它的颜色,当玩家一受到伤害,这个图片就闪现一下。
    在这里插入图片描述
编写玩家生命值管理脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityProject;


public class PlayerHealth : MonoBehaviour
{
    public int startingHealth = 100;
    public int currentHealth;
    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;
    AudioSource playerAudio;
    PlayerMovement playerMovement;
    PlayerShooting playerShooting;

    bool isDead;
    bool damaged;

    void Awake()
    {
        anim = GetComponent<Animator>();
        playerAudio = GetComponent<AudioSource>();
        playerMovement = GetComponent<PlayerMovement>();
        playerShooting = GetComponentInChildren<PlayerShooting>();
        currentHealth = startingHealth;
    }
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // 更新damageImage的color属性
    void Update()
    {
        if(damaged)
        {
            damageImage.color = flashColor;
        }
        else
        {
            damageImage.color = Color.Lerp(damageImage.color, Color.clear, flashSpeed * Time.deltaTime);
        }
        damaged = false;
    }

    //public权限可以在其他脚本中调用该函数
    public void TakeDamage(int amount)
    {
        damaged = true;
        currentHealth -= amount;
        healthSlider.value = currentHealth;
        playerAudio.Play();
        if(currentHealth <= 0 && !isDead)
        {
            Death();
        }
    }

    void Death()
    {
        isDead = true;

        playerShooting.DisableEffects();

        anim.SetTrigger("Die");

        playerAudio.clip = deathClip;
        playerAudio.Play();

        playerMovement.enabled = false;
        playerShooting.enabled = false;
    }

}


敌人生命值管理

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

public class EnemyHealth : 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;

    void Awake()
    {
        anim = GetComponent<Animator>();
        enemyAudio = GetComponent<AudioSource>();
        hitParticles = GetComponentInChildren<ParticleSystem>();
        capsuleCollider = GetComponent<CapsuleCollider>();

        currentHealth = startingHealth;
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // 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;
        //先设定粒子系统的坐标为hitPoint,然后播放粒子特效
        hitParticles.transform.position = hitPoint;  
        hitParticles.Play();

        if (currentHealth <=0)
        {
            Death();
        }

    }

    void Death()
    {
        isDead = true;

        capsuleCollider.isTrigger = true;

        anim.SetTrigger("Dead");

        enemyAudio.clip = deathClip;

        enemyAudio.Play();
    }
    //怪物下沉
    public void StartSinking()
    {
        GetComponent<NavMeshAgent>().enabled = false;
        GetComponent<Rigidbody>().isKinematic = true;
        isSinking = true;
        Destroy(gameObject, 2f);  //2s后销毁
    }
}

敌人自动生成管理

  • 在这里我们把Hierarchy层级中我们做好的敌人拖到Project面板中做成预制体,将Hierarchy层级中的敌人删除。
  • 在Hierarchy层级中新建一个空物体重命名为EnemyManager。
  • 在Hierarchy层级中新建一个空物体重命名为SpawnPoints,然后新建两个子物体ZombunnySpanwPoint和HellephantSpawnPoint作为敌人的诞生点。

编写敌人自动生成脚本

把脚本挂载到EnemyManager上
在这里插入图片描述

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

public class EnemyManager : MonoBehaviour
{
    public PlayerHealth playerHealth;
    public GameObject enemy;
    public float spawnTime = 3f;
    public Transform[] spawnPoints;
    // Start is called before the first frame update
    void Start()
    {
        //用于在每间隔spawnTime时间之后,重复调用函数Spawn来不断实例化敌人对象
        InvokeRepeating("Spawn", spawnTime, spawnTime);
    }

    void Spawn()
    {
        if (playerHealth.currentHealth <= 0f)
        {
            return;
        }
        int spawnPointIndex = Random.Range(0, spawnPoints.Length);
        Instantiate(enemy, spawnPoints[spawnPointIndex].position, spawnPoints[spawnPointIndex].rotation);
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

游戏结束的管理

  • 在我们之前创建的画布下新建一个Image物体,叫ScreenFader。同样是要平铺整个屏幕。
  • 在画布下创建一个Text物体,叫GameOverText。
  • 然后呢,我们想实现一个效果,就是玩家死了,游戏结束,这个结束画面就弹出来,这个功能可以通过制作一个animation动画实现。
  • 我们在Project窗口中新建一个Animation动画重命名为GameOverClip。
  • 然后我们首先选中我们的画布,在Animation窗口中添加四个参数:GameOverText.Scale , GameOverText.Text.Color , ScoreText.Scale, ScreenFader.Image.Color
  • 我们把所有参数都拖到开始帧和结束帧上,然后在中间设置关键帧,比如设置ScoreText文字的大小。设置开始帧和结束帧不同的参数值,比如缩放比例,开始为0 ,结束为1,再比如图片的alpha值,这样就生成了渐变动画。
  • 最后我们在Project新建Animator Controller重命名为HUDCanvas,把我们刚才制作的GameOverClip拖到动画控制器窗口。左边添加一个Trigger属性的GameOver。
  • 在窗口中添加一个空白状态机,不然的话不管什么状况直接就从Entry进入到GameOverClip,这样显然不合理。设置这个空状态机为默认状态。设置空状态机到GameOverClip的连接,条件是GameOver。
  • 最后我们在这个画布上添加Animator组件,选择动画控制器。
    在这里插入图片描述

在这里插入图片描述

编写游戏结束脚本

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

public class GameOverManager : MonoBehaviour
{
    public PlayerHealth playerHealth;
    public float restartDelay = 5f;

    Animator anim;
    float restartTime;

    void Awake()
    {
        anim = GetComponent<Animator>();

    }
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if (playerHealth.currentHealth <= 0)
        {
            anim.SetTrigger("GameOver");
            restartTime += Time.deltaTime;
            if (restartTime >= restartDelay)
            {
                 SceneManager.LoadScene("Mainstage");
            }
        }

        
    }
}

嗯嗯基本大功告成啦!!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值