当前位置:   article > 正文

【用unity实现100个游戏之15】开发一个类保卫萝卜的Unity2D塔防游戏2(附项目源码)_unity 塔防源码

unity 塔防源码

先看本次实现的最终效果

在这里插入图片描述

前言

本期紧接着上一篇,本期主要内容是实现敌人血条、动画和行为逻辑。

敌人生命值

绘制血条UI
在这里插入图片描述

新建

public class EnemyHealth : MonoBehaviour
{
    [SerializeField, Header("血条预制体")] private GameObject healthBarPrefab;
    [SerializeField, Header("血条位置")] private Transform barPosition;
    [SerializeField, Header("初始生命值")] private float initialHealth = 10f;
    [SerializeField, Header("最大生命值")] private float maxHealth = 10f;

    private void Start()
    {
        CreateHealthBar();
    }

    // 创建血条
    private void CreateHealthBar()
    {
        // 实例化血条预制体并设置位置为敌人的位置
        GameObject newBar = Instantiate(healthBarPrefab, barPosition.position, Quaternion.identity);
        
        // 设置血条的父物体为当前敌人,使血条随着敌人移动
        newBar.transform.SetParent(transform);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

配置
在这里插入图片描述

效果
在这里插入图片描述

扣血测试,

新增

public class EnemyHealthContainer : MonoBehaviour {
    [SerializeField]private Image fillAmountImage;
    public Image FillAmountImage => fillAmountImage;
}
  • 1
  • 2
  • 3
  • 4

修改EnemyHealth

public class EnemyHealth : MonoBehaviour
{
    [SerializeField, Header("血条预制体")] private GameObject healthBarPrefab;
    [SerializeField, Header("血条位置")] private Transform barPosition;
    [SerializeField, Header("初始生命值")] private float initialHealth = 10f;
    [SerializeField, Header("最大生命值")] private float maxHealth = 10f;

    public float CurrentHealth { get; set; }
    private Image _healthBar;
    private void Start()
    {
        CreateHealthBar();
        CurrentHealth = initialHealth;
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.P))
        {
            DealDamage(5f);
        }
        _healthBar.fillAmount = Mathf.Lerp(_healthBar.fillAmount, CurrentHealth / maxHealth, Time.deltaTime * 10f);
    }

    // 创建血条
    private void CreateHealthBar()
    {
        // 实例化血条预制体并设置位置为敌人的位置
        GameObject newBar = Instantiate(healthBarPrefab, barPosition.position, Quaternion.identity);

        // 设置血条的父物体为当前敌人,使血条随着敌人移动
        newBar.transform.SetParent(transform);

        EnemyHealthContainer container = newBar.GetComponent<EnemyHealthContainer>();

        _healthBar = container.FillAmountImage;
    }

    public void DealDamage(float damage)
    {
        CurrentHealth -= damage;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

效果
在这里插入图片描述

敌人死亡

敌人死亡送回池内,并重置健康值,修改EnemyHealth

public static Action OnEnemyKilled;//定义敌人被杀死委托事件

    public void ResetHealth(){
        CurrentHealth = initialHealth;
        _healthBar.fillAmount = 1f;
    }

    public void DealDamage(float damage)
    {
        CurrentHealth -= damage;
        if (CurrentHealth <= 0)
        {
            CurrentHealth = 0;
            Die();
        }
    }

    private void Die()
    {
        ResetHealth();
        OnEnemyKilled?.Invoke();
        ObjectPooler.ReturnToPool(gameObject);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

修改Spawner,调用敌人死亡委托

    private void RecordEnemy()
    {
        _enemiesRamaining--;
        if (_enemiesRamaining <= 0) StartCoroutine(NextWave());
    }

    private void OnEnable()
    {
        Enemy.OnEndReached += RecordEnemy;
        EnemyHealth.OnEnemyKilled += RecordEnemy;
    }

    private void OnDisable()
    {
        Enemy.OnEndReached -= RecordEnemy;
        EnemyHealth.OnEnemyKilled -= RecordEnemy;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

敌人扣除部分生命值,到达终点也要重置生命值

修改Enemy

private EnemyHealth _enemyHealth;

_enemyHealth = GetComponent<EnemyHealth>();

//回收敌人
private void ReturnEnemyToPool()
{
    OnEndReached?.Invoke();
    _enemyHealth.ResetHealth();
    ObjectPooler.ReturnToPool(gameObject);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

效果
在这里插入图片描述

控制敌人动画

添加一个敌人动画控制器
在这里插入图片描述

因为敌人的逻辑都一样,除了动画不一样,所以其他敌人我们可以创建动画覆盖控制器实现,修改对应的动画即可
在这里插入图片描述

在这里插入图片描述

修改EnemyHealth,定义敌人受伤委托

public static Action<Enemy> OnEnemyHit;//定义敌人受伤委托事件
private Enemy _enemy;

_enemy = GetComponent<Enemy>();

public void DealDamage(float damage)
    {
        CurrentHealth -= damage;
        if (CurrentHealth <= 0)
        {
            //。。。
        }
        else
        {
            OnEnemyHit?.Invoke(_enemy);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

新增EnemyAnimations类用于控制敌人的动画播放。PlayHurt方法是一个协程,用于播放受伤动画并暂停敌人的移动一段时间后恢复移动。EnemyHit方法是处理敌人被击中事件的方法,通过判断传入的敌人和当前敌人是否匹配来决定是否播放受伤动画。在OnEnable方法中注册了对EnemyHealth.OnEnemyHit事件的监听,在OnDisable方法中取消了对该事件的监听。

public class EnemyAnimations : MonoBehaviour
{
    private Animator _animator; // 敌人动画控制组件
    private Enemy _enemy; // 敌人组件

    private void Start()
    {
        _animator = GetComponent<Animator>(); // 获取敌人动画控制组件
        _enemy = GetComponent<Enemy>(); // 获取敌人组件
    }

    // 播放受伤动画
    private void PlayHurtAnimation()
    {
        _animator.SetTrigger("Hurt"); // 设置触发器播放受伤动画
    }

    // 播放死亡动画
    private void PlayDieAnimation()
    {
        _animator.SetTrigger("Die"); // 设置触发器播放死亡动画
    }

    // 播放受伤动画的协程
    private IEnumerator PlayHurt()
    {
        _enemy.StopMovement(); // 停止敌人移动
        PlayHurtAnimation(); // 播放受伤动画
        yield return new WaitForSeconds(GetCurrentAnimationLength() + 0.3f); // 等待当前动画长度+0.3秒
        _enemy.ResumeMovement(); // 恢复敌人移动
    }

    // 敌人被击中的事件处理方法
    private void EnemyHit(Enemy enemy)
    {
        if (_enemy == enemy)
        {
            StartCoroutine(PlayHurt()); // 开始播放受伤动画的协程
        }
    }

    private void OnEnable()
    {
        EnemyHealth.OnEnemyHit += EnemyHit; // 注册敌人被击中的事件监听
    }

    private void OnDisable()
    {
        EnemyHealth.OnEnemyHit -= EnemyHit; // 取消敌人被击中的事件监听
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

修改Enemy

public float MoveSpeed;//保存移速

public void StopMovement()
{
    moveSpeed = 0f;
}

public void ResumeMovement()
{

    moveSpeed = MoveSpeed;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

效果
在这里插入图片描述

敌人死亡动画

订阅敌人死亡事件
修改EnemyHealth

public static Action<Enemy> OnEnemyKilled;//定义敌人被杀死委托事件

private void Die()
{
    // ResetHealth();
    OnEnemyKilled?.Invoke(_enemy);
    // ObjectPooler.ReturnToPool(gameObject);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

修改EnemyAnimations

private EnemyHealth _enemyHealth; // 敌人组件

_enemyHealth = GetComponent<EnemyHealth>();

private IEnumerator PlayDead()
{
    _enemy.StopMovement();
    PlayDieAnimation();
    yield return new WaitForSeconds(GetCurrentAnimationLength());
    _enemy.ResumeMovement();
    _enemyHealth.ResetHealth();
    ObjectPooler.ReturnToPool(_enemy.gameObject);
}

private void EnemyDead(Enemy enemy)
{
    if (_enemy == enemy)
    {
        StartCoroutine(PlayDead());
    }
}

private void OnEnable()
{
    EnemyHealth.OnEnemyHit += EnemyHit; // 注册敌人被击中的事件监听
    EnemyHealth.OnEnemyKilled += EnemyDead;
}

private void OnDisable()
{
    EnemyHealth.OnEnemyHit -= EnemyHit; // 取消敌人被击中的事件监听
    EnemyHealth.OnEnemyKilled -= EnemyDead;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

修改死亡动画为不循环
在这里插入图片描述

效果
在这里插入图片描述

敌人转向问题

修改Enemy

private Vector3 _lastPointPosition;
private SpriteRenderer _spriteRenderer;

private void Start()
{
     //...
     _spriteRenderer = GetComponent<SpriteRenderer>();

     _lastPointPosition = transform.position;
 }
private void Update()
{
	//...
    Rotate();
}
 
//控制转向
private void Rotate()
{
    if (CurrentPointPosition.x > _lastPointPosition.x)
    {
        _spriteRenderer.flipX = false;
    }
    else
    {
        _spriteRenderer.flipX = true;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

效果
在这里插入图片描述

源码

见本项目最后一节

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,于是最近才开始自习unity。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号