Unity教程(二十四)技能系统 投剑技能(中)技能变种实现

Unity开发2D类银河恶魔城游戏学习笔记


Unity开发2D类银河恶魔城游戏学习笔记目录

技能系统

Unity教程(二十一)技能系统 基础部分
Unity教程(二十二)技能系统 分身技能
Unity教程(二十三)技能系统 掷剑技能(上)基础实现
Unity教程(二十四)技能系统 掷剑技能(中)技能变种实现
Unity教程(二十五)技能系统 掷剑技能(下)冻结时间实现


如果你更习惯用知乎
Unity开发2D类银河恶魔城游戏学习笔记目录


文章目录

  • Unity开发2D类银河恶魔城游戏学习笔记
    • 技能系统
  • 前言
  • 一、概述
  • 二、类型设置
  • 三、弹射类型的实现
    • (1)创建参数
    • (2)弹射的实现
    • (3)弹射伤害的实现
    • (3)问题改进
  • 三、穿刺类型的实现
    • (1)创建参数
    • (2)穿刺的实现
    • (3)穿刺伤害的实现
    • (4)问题改进
  • 四、盘旋类型的实现
    • (1)创建参数
    • (2)盘旋的实现
    • (3)盘旋伤害的实现
  • 总结 完整代码
    • SwordSkill.cs
    • Sword_Skill_Controller.cs


前言

注意:Udemy上更新了课程,原来的版本删掉了。新版教程与旧版顺序相差较大,并且改用Unity6。但这个笔记已经写到一半了,所以还是按照旧版来。

本文为Udemy课程The Ultimate Guide to Creating an RPG Game in Unity学习笔记,如有错误,欢迎指正。

本节实现角色投剑技能的变种。

Udemy课程地址

对应视频:
Bouncy sword
Setting sword type
Pierce sword
Saw spin sword


一、概述

本节实现投剑技能的几种变种类型,包括常规、弹射、穿刺和盘旋,并实现对敌人的伤害。
在这里插入图片描述

二、类型设置

在Sword_Skill脚本中创建枚举类型SwordType表示四种投掷类型。
添加变量Sword_Skill类中添加投剑类型变量,默认为常规。

public enum SwordType
{Regular,Bounce,Pierce,Spin
}
public class Sword_Skill : Skill
{public SwordType swordType = SwordType.Regular;
}

三、弹射类型的实现

弹射要实现的效果是在击中一个敌人后,在附近敌人身上弹射攻击几次,弹射次数归零后返回。
因此,实现时要在击中某个敌人后,检测以剑为中心一定半径范围内的敌人并记录。依据列表让剑在敌人之间移动,同时设置好动画,击中时取消嵌入,弹射次数归零后返回。

(1)创建参数

首先在Sword_Skill中添加弹射类型需要更改的变量,包括弹射的重力、次数、速度。
在创建剑时判断剑的类型,给剑的重力赋值,将弹跳相关参数传入控制器。这部分要写在设置Sword的参数之前,因为要先给剑的重力赋值再通过SetupSword传入。

    [Header("Bounce Info")][SerializeField] private float bounceGravity;[SerializeField] private int bounceAmount;[SerializeField] private float bounceSpeed;public void CreateSword(){GameObject newSword = Instantiate(swordPrefab, player.transform.position, transform.rotation);Sword_Skill_Controller newSwordScript = newSword.GetComponent<Sword_Skill_Controller>();if(swordType == SwordType.Bounce){swordGravity = bounceGravity;newSwordScript.SetupBounce(true, bounceAmount, bounceSpeed);}newSwordScript.SetupSword(finalDir, swordGravity, player, returnSpeed);player.AssignNewSword(newSword);DotsActive(false);}

在Sword_Skill_Controller中添加弹射所需参数
添加SetupBounce函数设置参数

    [Header("Bounce Info")]private bool isBouncing;private int amountOfBounce;private float bounceSpeed;public void SetupBounce(bool _isBouncing, int _amountOfBounces , float _bounceSpeed){isBouncing = _isBouncing;amountOfBounce = _amountOfBounces;bounceSpeed = _bounceSpeed;}

(2)弹射的实现

在弹射类型时让剑一直保持旋转动画的状态,并且不挂载到角色下随角色移动。

    private void StuckInto(Collider2D collision){canRotate = false;cd.enabled = false;rb.isKinematic = true;rb.constraints = RigidbodyConstraints2D.FreezeAll;if(isBouncing)return;anim.SetBool("Rotation", false);transform.parent = collision.transform;}

在这里插入图片描述

在Sword_Skill_Controller中添加列表存储范围内敌人,添加当前索引变量。设置弹射参数时将列表初始化。
在玩家击中第一个敌人时,获取一定范围内的敌人,并将它们存在列表中。
将这部分提取出来,重命名为SetupTargetsForBounce函数。

    [Header("Bounce Info")]private bool isBouncing;private int amountOfBounce;private float bounceSpeed;private List<Transform> enemyTarget;private int targetIndex;public void SetupBounce(bool _isBouncing, int _amountOfBounces , float _bounce){isBouncing = _isBouncing;amountOfBounce = _amountOfBounces;enemyTarget = new List<Transform>();}private void OnTriggerEnter2D(Collider2D collision){if (isReturning)return;SetupTargetsForBounce(collision);StuckInto(collision);}private void SetupTargetsForBounce(Collider2D collision){if (collision.GetComponent<Enemy>() != null){if (isBouncing && enemyTarget.Count <= 0){Collider2D[] colliders = Physics2D.OverlapCircleAll(transform.position, 10);foreach (var hit in colliders){if (hit.GetComponent<Enemy>() != null)enemyTarget.Add(hit.transform);}}}}

在Update中实现剑在敌人中弹射移动。当处于弹射类型且在上面步骤中能获取到敌人时,让剑以一定速度从现在的位置向当前索引敌人位置移动。移动到剑与敌人间距很小时,将索引增加,换向下一个目标弹射。
由于敌人数目可能小于弹射次数,可以进行取模操作,当弹射完整个列表的敌人时,从列表第一个敌人再次开始弹射。
每次弹射时,将弹射次数减一,弹射次数小于等于零时,结束弹射并让剑返回。
将弹射的实现部分提取成函数BounceLogic。

    private void Update(){if (canRotate){transform.right = rb.velocity;}if (isReturning){transform.position = Vector2.MoveTowards(transform.position, player.transform.position, returnSpeed * Time.deltaTime);if (Vector2.Distance(transform.position, player.transform.position) < 1)player.CatchTheSword();}BounceLogic();}private void BounceLogic(){if (isBouncing && enemyTarget.Count > 0){transform.position = Vector2.MoveTowards(transform.position, enemyTarget[targetIndex].position, bounceSpeed * Time.deltaTime);if (Vector2.Distance(transform.position, enemyTarget[targetIndex].position) < 0.1f){targetIndex = (targetIndex + 1) % enemyTarget.Count;bounceAmount--;if (bounceAmount <= 0){isBouncing = false;isReturning = true;}}}}

将骷髅复制一个方便测试。
在这里插入图片描述
在技能管理器中给弹射参数赋上合适的值。
在这里插入图片描述

效果如下:
在这里插入图片描述

(3)弹射伤害的实现

每次距离接近时调用当前敌人的Damage函数即可。

    private void BounceLogic(){if (isBouncing && enemyTarget.Count > 0){transform.position = Vector2.MoveTowards(transform.position, enemyTarget[targetIndex].position, bounceSpeed * Time.deltaTime);if (Vector2.Distance(transform.position, enemyTarget[targetIndex].position) < 0.1f){enemyTarget[targetIndex].GetComponent<Enemy>().Damage();targetIndex = (targetIndex + 1) % enemyTarget.Count;bounceAmount--;if (bounceAmount <= 0){isBouncing = false;isReturning = true;}}}}

在这里插入图片描述

(3)问题改进

这时存在一个问题,剑如果在碰撞在地面和墙体上会一直空转。
在这里插入图片描述
解决方式很简单,在实现嵌入墙体功能的StuckInto函数中,修改弹射类型返回的条件。当处于弹射类型且击中了其他东西时,由于没有与敌人发生碰撞,因此获取不到敌人的列表,只需添加条件获取到的敌人数量大于0时才可返回即可。

    private void StuckInto(Collider2D collision){canRotate = false;cd.enabled = false;rb.isKinematic = true;rb.constraints = RigidbodyConstraints2D.FreezeAll;if(isBouncing && enemyTarget.Count > 0)return;anim.SetBool("Rotation", false);transform.parent = collision.transform;}

在这里插入图片描述
由于剑的碰撞面积过大,容易出现卡在边角的情况,修改碰撞盒大小,还可以增加一点偏移,调一下碰撞盒位置。
在这里插入图片描述
在这里插入图片描述

这里发现一个小问题,敌人和敌人之间不应该碰撞,改变碰撞矩阵。
在这里插入图片描述

三、穿刺类型的实现

穿刺要实现的效果是根据穿刺次数,直线穿过所有敌人造成伤害。
实现直线穿刺可以将剑的重力设置小一些,就会产生直线的轨迹。穿刺时关闭旋转动画取消嵌入,检测敌人并造成伤害。

(1)创建参数

首先在Sword_Skill中添加穿刺类型需要更改的变量,包括穿刺的重力、次数。
由于每个类型都要设置重力,就直接将设置重力提出来作为一个函数,为每个类型设置不同重力,在Start中调用函数。
CreateSword中依旧调用控制器中的设置函数,设置不同类型的参数,将穿刺次数传入。

    [Header("Pierce Info")][SerializeField] private int pierceAmount;[SerializeField] private float pierceGravity;protected override void Start(){base.Start();GenerateDots();SetupGravity();}private void SetupGravity(){if (swordType == SwordType.Bounce)swordGravity = bounceGravity;else if (swordType == SwordType.Pierce)swordGravity = pierceGravity;}public void CreateSword(){GameObject newSword = Instantiate(swordPrefab, player.transform.position, transform.rotation);Sword_Skill_Controller newSwordScript = newSword.GetComponent<Sword_Skill_Controller>();if (swordType == SwordType.Bounce)newSwordScript.SetupBounce(true, bounceAmount, bounceSpeed);else if (swordType == SwordType.Pierce)newSwordScript.SetupPierce(pierceAmount);newSwordScript.SetupSword(finalDir, swordGravity, player, returnSpeed);player.AssignNewSword(newSword);DotsActive(false);}

在Sword_Skill_Controller中添加穿刺所需参数
添加SetupPierce函数设置参数

    [Header("Pierce Info")]private float pierceAmount;public void SetupPierce(int _pierceAmount){pierceAmount = _pierceAmount;}

(2)穿刺的实现

在穿刺类型时让剑在穿刺次数不为零时,不打开旋转动画,且穿过敌人不嵌入其中。
在Sword_Skill_Controller中添加

    public void SetupSword(Vector2 _dir, float _gravityScale, Player _player, float _returnSpeed){player = _player;rb.velocity = _dir;rb.gravityScale = _gravityScale;returnSpeed = _returnSpeed;if(pierceAmount<=0)anim.SetBool("Rotation", true);}private void StuckInto(Collider2D collision){if(pierceAmount >0 && collision.GetComponent<Enemy>() != null){pierceAmount--;return;}canRotate = false;cd.enabled = false;rb.isKinematic = true;rb.constraints = RigidbodyConstraints2D.FreezeAll;if(isBouncing && enemyTarget.Count > 0)return;anim.SetBool("Rotation", false);transform.parent = collision.transform;}

在技能管理器中选择穿刺类型,并给穿刺的参数赋上合适的值。
在这里插入图片描述
在这里插入图片描述

(3)穿刺伤害的实现

在触发器函数中调用,当剑的触发器检测到敌人时,敌人调用Damage函数。
教程里用了C#中的 ?. Null条件运算符。详情请见C#官方文档
在这里的作用是判断获取的敌人是否为空,不为空则调用Damage函数。

    private void OnTriggerEnter2D(Collider2D collision){if (isReturning)return;collision.GetComponent<Enemy>()?.Damage();SetupTargetsForBounce(collision);StuckInto(collision);}private void SetupTargetsForBounce(Collider2D collision){if (collision.GetComponent<Enemy>() != null){if (isBouncing && enemyTarget.Count <= 0){Collider2D[] colliders = Physics2D.OverlapCircleAll(transform.position, 10);foreach (var hit in colliders){if (hit.GetComponent<Enemy>() != null)enemyTarget.Add(hit.transform);}}}}

在这里插入图片描述

(4)问题改进

现在创建的剑会一直存在,在某些角度下会飞出去很远很难收回,因此需要添加剑在一定时间后自动销毁。
创建销毁剑的函数DestroyMe。使用Invoke在一定时间后调用。

    private void DestroyMe(){Destroy(gameObject);}public void SetupSword(Vector2 _dir, float _gravityScale, Player _player, float _returnSpeed){player = _player;rb.velocity = _dir;rb.gravityScale = _gravityScale;returnSpeed = _returnSpeed;if(pierceAmount<=0)anim.SetBool("Rotation", true);Invoke("DestroyMe", 7);}

为了方便观察效果,我先把销毁时间调一个很小的数。
在这里插入图片描述

四、盘旋类型的实现

盘旋要实现的效果是扔出剑后,剑命中敌人或达到最远距离后,悬停对敌人造成伤害,持续时间结束后返回。
盘旋状态分为两段,前半段剑正常移动,判断最远距离和击中敌人两个条件,达成时切换到悬停状态。悬停状态每隔一段冷却时间检测敌人对敌人造成伤害,悬停持续时长结束后返回。

(1)创建参数

首先在Sword_Skill中添加盘旋类型需要更改的变量,包括盘旋的最远距离、持续时长、冷却时间等。设置重力,调用SetupSpin设置参数。
这里if-else如果觉得别扭,也可Alt+Enter进入子菜单,转换为switch语句。
在这里插入图片描述

    [Header("Spin Info")][SerializeField] private float maxTravelDistance;[SerializeField] private float spinDuration;[SerializeField] private float spinGravity;private void SetupGravity(){if (swordType == SwordType.Bounce)swordGravity = bounceGravity;else if (swordType == SwordType.Pierce)swordGravity = pierceGravity;else if(swordType == SwordType.Spin)swordGravity = spinGravity;}public void CreateSword(){GameObject newSword = Instantiate(swordPrefab, player.transform.position, transform.rotation);Sword_Skill_Controller newSwordScript = newSword.GetComponent<Sword_Skill_Controller>();if (swordType == SwordType.Bounce)newSwordScript.SetupBounce(true, bounceAmount, bounceSpeed);else if (swordType == SwordType.Pierce)newSwordScript.SetupPierce(pierceAmount);else if (swordType== SwordType.Spin)newSwordScript.SetupSpin(true, maxTravelDistance, spinDuration);newSwordScript.SetupSword(finalDir, swordGravity, player, returnSpeed);player.AssignNewSword(newSword);DotsActive(false);}

在Sword_Skill_Controller中添加盘旋所需参数
添加SetupSpin函数设置参数

    [Header("Spin Info")]private bool isSpinning;private float maxTravelDistance;private float spinDuration;private float spinTimer;private bool wasStopped;public void SetupSpin(bool _isSpinning, float _maxTravelDistance,float _spinDuration){isSpinning = _isSpinning;maxTravelDistance = _maxTravelDistance;spinDuration = _spinDuration;}

(2)盘旋的实现

创建函数SpinLogic进行盘旋的实现,在Update函数中调用它,具体实现如下:

创建函数StopWhenSpinning,进行悬停的操作,将wasStopped设置为true并冻结剑的位置。此时开始悬停,设置盘旋的计时器。

处于盘旋状态时,要判断剑飞出去的距离有没有到达最大距离限制,在到达最大距离时让剑悬停。在StuckInto函数中添加盘旋状态时返回,剑不嵌入物体中。

开始悬停后让计时器递减,直到时间归零让剑停止盘旋返回。

    private void Update(){if (canRotate){transform.right = rb.velocity;}if (isReturning){transform.position = Vector2.MoveTowards(transform.position, player.transform.position, returnSpeed * Time.deltaTime);if (Vector2.Distance(transform.position, player.transform.position) < 1)player.CatchTheSword();}BounceLogic();SpinLogic();}private void SpinLogic(){ if(isSpinning){if(Vector2.Distance(player.transform.position, transform.position) > maxTravelDistance && !wasStopped)StopWhenSpinning();}if(wasStopped){spinTimer  -= Time.deltaTime;if(spinTimer < 0){isReturning = true;isSpinning = false;}}}   private void StopWhenSpinning(){wasStopped = true;rb.constraints = RigidbodyConstraints2D.FreezePosition;spinTimer = spinDuration;}private void StuckInto(Collider2D collision){if(pierceAmount >0 && collision.GetComponent<Enemy>() != null){pierceAmount--;return;}if (isSpinning)return;canRotate = false;cd.enabled = false;rb.isKinematic = true;rb.constraints = RigidbodyConstraints2D.FreezeAll;if(isBouncing && enemyTarget.Count > 0)return;anim.SetBool("Rotation", false);transform.parent = collision.transform;}

在技能管理器中设置参数
在这里插入图片描述

效果如下:
在这里插入图片描述
下边这一一小块选做:

依据教程还有一部分修改,在击中第一个敌人后不管有没有达到最远距离直接进入悬停,只需在StuckInto函数中调用StopWhenSpinning函数。这里根据自己想法设置即可。

注意: 这种做法会产生一个问题,每次和敌人产生碰撞时都会刷新一次悬停持续时间,敌人数量很多时持续时间会多次刷新。但是剑的触发器范围并不大,敌人进入的时间差别也不大,所以影响还可以接受。
如果想改进,可以考虑不将将计时器重置写在StopWhenSpinning中,而是只在超出距离和第一次有敌人进入触发器时重置。或者干脆将spinDuration改成整个盘旋类型的技能持续时长,而不是悬停那一小段的。在这里就不详细实现了。

   private void StuckInto(Collider2D collision){if(pierceAmount >0 && collision.GetComponent<Enemy>() != null){pierceAmount--;return;}if (isSpinning){StopWhenSpinning();return;}canRotate = false;cd.enabled = false;rb.isKinematic = true;rb.constraints = RigidbodyConstraints2D.FreezeAll;if(isBouncing && enemyTarget.Count > 0)return;anim.SetBool("Rotation", false);transform.parent = collision.transform;}

在这里插入图片描述

(3)盘旋伤害的实现

在Sword_Skill中添加参数hitCoolDown,表示每隔多久攻击一次。将它传入控制器中。

    [Header("Spin Info")][SerializeField] private float maxTravelDistance;[SerializeField] private float spinDuration;[SerializeField] private float spinGravity;[SerializeField] private float hitCooldown;public void CreateSword(){GameObject newSword = Instantiate(swordPrefab, player.transform.position, transform.rotation);Sword_Skill_Controller newSwordScript = newSword.GetComponent<Sword_Skill_Controller>();if (swordType == SwordType.Bounce)newSwordScript.SetupBounce(true, bounceAmount, bounceSpeed);else if (swordType == SwordType.Pierce)newSwordScript.SetupPierce(pierceAmount);else if (swordType== SwordType.Spin)newSwordScript.SetupSpin(true, maxTravelDistance, spinDuration, hitCooldown);newSwordScript.SetupSword(finalDir, swordGravity, player, returnSpeed);player.AssignNewSword(newSword);DotsActive(false);}

在Sword_Skill_Controller中,添加冷却时间和计时器。
处于盘旋悬停状态时,每隔一个冷却时间进行一次攻击,由计时器控制。每次计时器归零时,检测周围的敌人,对敌人产生一次伤害,并重置计时器。

    private float hitTimer;private float hitCooldown;public void SetupSpin(bool _isSpinning, float _maxTravelDistance,float _spinDuration, float _hitCooldown){isSpinning = _isSpinning;maxTravelDistance = _maxTravelDistance;spinDuration = _spinDuration;hitCooldown = _hitCooldown;}private void Update(){if (canRotate){transform.right = rb.velocity;}if (isReturning){transform.position = Vector2.MoveTowards(transform.position, player.transform.position, returnSpeed * Time.deltaTime);if (Vector2.Distance(transform.position, player.transform.position) < 1)player.CatchTheSword();}BounceLogic();SpinLogic();}private void SpinLogic(){if (isSpinning){if (Vector2.Distance(player.transform.position, transform.position) > maxTravelDistance && !wasStopped){StopWhenSpinning();}}if (wasStopped){spinTimer -= Time.deltaTime;if (spinTimer < 0){isReturning = true;isSpinning = false;}hitTimer -= Time.deltaTime;if (hitTimer < 0){hitTimer = hitCooldown;Collider2D[] colliders = Physics2D.OverlapCircleAll(transform.position, 1);foreach (var hit in colliders){if (hit.GetComponent<Enemy>() != null)hit.GetComponent<Enemy>().Damage();}}}}

设置伤害间隔时间。
在这里插入图片描述
效果如下:
在这里插入图片描述

总结 完整代码

SwordSkill.cs

添加枚举类型,表示四种投掷方式。
添加三种类型投掷方式的相关参数,并传递到控制器。
添加设置重力的函数。

//Sword_Skill:掷剑技能
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public enum SwordType
{Regular,Bounce,Pierce,Spin
}public class Sword_Skill : Skill
{public SwordType swordType = SwordType.Regular;[Header("Skill Info")][SerializeField] private GameObject swordPrefab;[SerializeField] private Vector2 launchForce;[SerializeField] private float swordGravity;[SerializeField] private float returnSpeed;[Header("Aim dots")][SerializeField] private int numberOfDots;[SerializeField] private float spaceBetweenDots;[SerializeField] private GameObject dotPrefab;[SerializeField] private Transform dotsParent;private GameObject[] dots;private Vector2 finalDir;[Header("Bounce Info")][SerializeField] private float bounceGravity;[SerializeField] private int bounceAmount;[SerializeField] private float bounceSpeed;[Header("Pierce Info")][SerializeField] private int pierceAmount;[SerializeField] private float pierceGravity;[Header("Spin Info")][SerializeField] private float maxTravelDistance;[SerializeField] private float spinDuration;[SerializeField] private float spinGravity;[SerializeField] private float hitCooldown;protected override void Start(){base.Start();GenerateDots();SetupGravity();}protected override void Update(){if(Input.GetKeyUp(KeyCode.Mouse1))finalDir = new Vector2(AimDirection().normalized.x * launchForce.x , AimDirection().normalized.y * launchForce.y);if(Input.GetKey(KeyCode.Mouse1)){for(int i = 0; i < dots.Length; i++){dots[i].transform.position = DotsPosition(i * spaceBetweenDots);}}}private void SetupGravity(){if (swordType == SwordType.Bounce)swordGravity = bounceGravity;else if (swordType == SwordType.Pierce)swordGravity = pierceGravity;else if(swordType == SwordType.Spin)swordGravity = spinGravity;}public void CreateSword(){GameObject newSword = Instantiate(swordPrefab, player.transform.position, transform.rotation);Sword_Skill_Controller newSwordScript = newSword.GetComponent<Sword_Skill_Controller>();if (swordType == SwordType.Bounce)newSwordScript.SetupBounce(true, bounceAmount, bounceSpeed);else if (swordType == SwordType.Pierce)newSwordScript.SetupPierce(pierceAmount);else if (swordType== SwordType.Spin)newSwordScript.SetupSpin(true, maxTravelDistance, spinDuration, hitCooldown);newSwordScript.SetupSword(finalDir, swordGravity, player, returnSpeed);player.AssignNewSword(newSword);DotsActive(false);}#region 瞄准public Vector2 AimDirection(){Vector2 playerPosition = player.transform.position;Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);Vector2 direction = mousePosition - playerPosition;return direction;}private void GenerateDots(){dots = new GameObject[numberOfDots];for (int i = 0; i < numberOfDots; i++){dots[i] = Instantiate(dotPrefab, player.transform.position, Quaternion.identity, dotsParent);dots[i].SetActive(false);}}public void DotsActive(bool _isActive){for (int i = 0; i < dots.Length; i++){dots[i].SetActive(_isActive);}}private Vector2 DotsPosition(float t){Vector2 position = (Vector2)player.transform.position +new Vector2(AimDirection().normalized.x * launchForce.x, AimDirection().normalized.y * launchForce.y) * t +0.5f * (Physics2D.gravity * swordGravity) * (t * t);return position;}#endregion
}

Sword_Skill_Controller.cs

实现三种投掷类型的具体逻辑,添加对敌人的伤害。

//Sword_Skill_Controller:掷剑技能控制器
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Sword_Skill_Controller : MonoBehaviour
{private Animator anim;private Rigidbody2D rb;private CircleCollider2D cd;private Player player;private bool canRotate = true;private bool isReturning;private float returnSpeed = 12;private float freezeTimeDuration;[Header("Bounce Info")]private bool isBouncing;private int bounceAmount;private float bounceSpeed;private List<Transform> enemyTarget;private int targetIndex;[Header("Pierce Info")]private float pierceAmount;[Header("Spin Info")]private bool isSpinning;private float maxTravelDistance;private float spinDuration;private float spinTimer;private bool wasStopped;private float hitTimer;private float hitCooldown;private void Awake(){anim = GetComponentInChildren<Animator>();rb = GetComponent<Rigidbody2D>();cd = GetComponent<CircleCollider2D>();}private void DestroyMe(){Destroy(gameObject);}public void SetupSword(Vector2 _dir, float _gravityScale, Player _player, float _returnSpeed, float _freezeTimeDuration){player = _player;rb.velocity = _dir;rb.gravityScale = _gravityScale;returnSpeed = _returnSpeed;freezeTimeDuration = _freezeTimeDuration;if (pierceAmount<=0)anim.SetBool("Rotation", true);Invoke("DestroyMe", 7.0f);}public void SetupBounce(bool _isBouncing, int _bounceAmount , float _bounceSpeed){isBouncing = _isBouncing;bounceAmount = _bounceAmount;bounceSpeed = _bounceSpeed;enemyTarget = new List<Transform>();}public void SetupPierce(int _pierceAmount){pierceAmount = _pierceAmount;}public void SetupSpin(bool _isSpinning, float _maxTravelDistance,float _spinDuration, float _hitCooldown){isSpinning = _isSpinning;maxTravelDistance = _maxTravelDistance;spinDuration = _spinDuration;hitCooldown = _hitCooldown;}public void ReturnSword(){rb.constraints = RigidbodyConstraints2D.FreezeAll;transform .parent = null;isReturning = true;}private void Update(){if (canRotate){transform.right = rb.velocity;}if (isReturning){transform.position = Vector2.MoveTowards(transform.position, player.transform.position, returnSpeed * Time.deltaTime);if (Vector2.Distance(transform.position, player.transform.position) < 1)player.CatchTheSword();}BounceLogic();SpinLogic();}private void SpinLogic(){if (isSpinning){if (Vector2.Distance(player.transform.position, transform.position) > maxTravelDistance && !wasStopped){StopWhenSpinning();}}if (wasStopped){spinTimer -= Time.deltaTime;if (spinTimer < 0){isReturning = true;isSpinning = false;}hitTimer -= Time.deltaTime;if (hitTimer < 0){hitTimer = hitCooldown;Collider2D[] colliders = Physics2D.OverlapCircleAll(transform.position, 1);foreach (var hit in colliders){if (hit.GetComponent<Enemy>() != null)hit.GetComponent<Enemy>().Damage();}}}}private void BounceLogic(){if (isBouncing && enemyTarget.Count > 0){transform.position = Vector2.MoveTowards(transform.position, enemyTarget[targetIndex].position, bounceSpeed * Time.deltaTime);if (Vector2.Distance(transform.position, enemyTarget[targetIndex].position) < 0.1f){enemyTarget[targetIndex].GetComponent<Enemy>().Damage();targetIndex = (targetIndex + 1) % enemyTarget.Count;bounceAmount--;if (bounceAmount <= 0){isBouncing = false;isReturning = true;}}}}private void OnTriggerEnter2D(Collider2D collision){if (isReturning)return;if(collision.GetComponent<Enemy>()!=null){Enemy enemy = collision.GetComponent<Enemy>();enemy.Damage();if (!isBouncing && !isSpinning)enemy.StartCoroutine("FreezeTimeFor", freezeTimeDuration);}SetupTargetsForBounce(collision);StuckInto(collision);}private void SetupTargetsForBounce(Collider2D collision){if (collision.GetComponent<Enemy>() != null){if (isBouncing && enemyTarget.Count <= 0){Collider2D[] colliders = Physics2D.OverlapCircleAll(transform.position, 10);foreach (var hit in colliders){if (hit.GetComponent<Enemy>() != null)enemyTarget.Add(hit.transform);}}}}private void StopWhenSpinning(){wasStopped = true;rb.constraints = RigidbodyConstraints2D.FreezePosition;spinTimer = spinDuration;}private void StuckInto(Collider2D collision){if(pierceAmount >0 && collision.GetComponent<Enemy>() != null){pierceAmount--;return;}if (isSpinning){//StopWhenSpinning();return;}canRotate = false;cd.enabled = false;rb.isKinematic = true;rb.constraints = RigidbodyConstraints2D.FreezeAll;if(isBouncing && enemyTarget.Count > 0)return;anim.SetBool("Rotation", false);transform.parent = collision.transform;}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/90986.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/90986.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

局域网TCP通过组播放地址rtp推流和拉流实现实时喊话

应用场景&#xff0c;安卓端局域网不用ip通过组播放地址实现实时对讲功能发送端: ffmpeg -f alsa -i hw:1 -acodec aac -ab 64k -ac 2 -ar 16000 -frtp -sdp file stream.sdp rtp://224.0.0.1:14556接收端: ffmpeg -protocol whitelist file,udp,rtp -i stream.sdp -acodec pcm…

基于深度学习的医学图像分析:使用YOLOv5实现细胞检测

最近研学过程中发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击链接跳转到网站人工智能及编程语言学习教程。读者们可以通过里面的文章详细了解一下人工智能及其编程等教程和学习方法。下面开始对正文内容的…

32.768KHZ 3215晶振CM315D与NX3215SA应用全场景

在现代电子设备中&#xff0c;一粒米大小的晶振&#xff0c;却是掌控时间精度的“心脏”。CITIZEN的CM315D系列与NDK的NX3215SA系列晶振便是其中的佼佼者&#xff0c;它们以 3.2 1.5 mm 的小尺寸”(厚度不足1mm)&#xff0c;成为智能设备中隐形的节奏大师。精准计时的奥秘这两…

嵌软面试——ARM Cortex-M寄存器组

Cortex-M内存架构包含16个通用寄存器&#xff0c;其中R0-R12是13个32位的通用寄存器&#xff0c;另外三个寄存器是特殊用途&#xff0c;分别是R13&#xff08;栈指针&#xff09;,R14&#xff08;链接寄存器&#xff09;,R15&#xff08;程序计数器&#xff09;。对于处理器来说…

7.DRF 过滤、排序、分页

过滤Filtering 对于列表数据可能需要根据字段进行过滤&#xff0c;我们可以通过添加django-fitlter扩展来增强支持。 pip install django-filter在配置文件中增加过滤器类的全局设置&#xff1a; """drf配置信息必须全部写在REST_FRAMEWORK配置项中""…

二、CUDA、Pytorch与依赖的工具包

CUDA Compute Unified Device Architecture&#xff08;统一计算架构&#xff09;。专门用于 GPU 通用计算 的平台 编程接口。CUDA可以使你的程序&#xff08;比如矩阵、神经网络&#xff09;由 GPU 执行&#xff0c;这比CPU能快几十甚至上百倍。 PyTorch 是一个深度学习框架…

SpringCloude快速入门

近期简单了解一下SpringCloude微服务,本文主要就是我学习中所记录的笔记,然后具体原理可能等以后再来深究,本文可能有些地方用词不专业还望包容一下,感兴趣可以参考官方文档来深入学习一下微服务,然后我的下一步学习就是docker和linux了。 nacos: Nacos 快速开始 | Nacos 官网…

GPT Agent与Comet AI Aent浏览器对比横评

1. 架构设计差异GPT Agent的双浏览器架构&#xff1a;文本浏览器&#xff1a;专门用于高效处理大量文本内容&#xff0c;适合深度信息检索和文献追踪&#xff0c;相当于Deep Research的延续可视化浏览器&#xff1a;具备界面识别与交互能力&#xff0c;可以点击网页按钮、识别图…

应用信息更新至1.18.0

增加DO权限 增加权限管理&#xff08;需DO支持&#xff09; 增加应用冻结隐藏&#xff08;需DO支持&#xff09; 增加权限委托&#xff08;需DO支持&#xff09; 增加特殊组件 ...

常用git命令集锦

git init 初始化 将当前目录初始化为 git 本地仓库&#xff0c;此时会在本地创建一个 .git 的文件夹 git init -q 静默执行&#xff0c;就是在后台执行 git init --bare –bare 参数,一般用来初始化一个空的目录&#xff0c;作为远程存储仓库 git init --template dir –templa…

skywalking安装

一、简介 SkyWalking是一款用于分布式系统跟踪和性能监控的开源工具。它可以帮助开发人员了解分布式系统中不同组件之间的调用关系和性能指标&#xff0c;从而进行故障排查和性能优化。 它支持多种语言和框架&#xff0c;包括Java、.NET、Node.js等。它通过在应用程序中插入代…

利用DataStream和TrafficPeak实现大数据可观察性

可观察性工作流对于深入了解应用程序的健康状况、客户流量和整体性能至关重要。然而&#xff0c;要实现真正的可观察性还面临一些挑战&#xff0c;包括海量的流量数据、数据保留、实施时间以及各项成本等。TrafficPeak是一款为Akamai云平台打造&#xff0c;简单易用、可快速部署…

jQuery 最新语法大全详解(2025版)

引言 jQuery 作为轻量级 JavaScript 库&#xff0c;核心价值在于 简化 DOM 操作、跨浏览器兼容性和高效开发。尽管现代框架崛起&#xff0c;jQuery 仍在遗留系统维护、快速原型开发中广泛应用。本文涵盖 jQuery 3.6 核心语法&#xff0c;重点解析高效用法与最佳实践。 一、jQu…

Android 15 修改截图默认音量大小

概述 在 Android 15 中,截图音效的默认音量可能过大,影响用户体验。本文将介绍如何通过修改系统源码来调整截图音效的默认音量大小。 修改位置 需要修改的文件路径: frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundProvider.kt…

Python爬虫实战:快速采集教育政策数据(附官网工具库API)

解锁教育政策研究的数据金矿&#xff0c;用技术提升学术效率 在教育政策研究领域&#xff0c;获取最新、最全面的政策文本是学术工作的基础。传统手动收集方式效率低下且容易遗漏关键政策&#xff0c;而Python爬虫技术为教育研究者提供了高效的数据采集解决方案。本文将系统介…

验证回文串-leetcode

如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后&#xff0c;短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。 字母和数字都属于字母数字字符。 给你一个字符串 s&#xff0c;如果它是 回文串 &#xff0c;返回 true &#xff1b;否则&#xf…

嵌入式学习日志(十)

10 学习指针1 指针核心定义与本质1.1 指针与指针变量1、指针即地址&#xff0c;指针变量是存放地址的变量&#xff0c;其大小与操作系统位数相关&#xff1a;64 位系统中占 8 字节&#xff0c;32 位系统中占 4 字节。2、指针的核心功能是通过地址间接访问目标变量&#xff0…

Anaconda创建环境报错:CondaHTTPEFTOT: HTTP 403 FORBIDDEN for url

一、快速解决方案这类报错的原因通常是由于 conda 无法访问镜像源或权限被服务器拒绝&#xff0c;以下是常见原因和对应的解决方案&#xff1a;检查镜像源拼写是否正确conda config --show channels清华源镜像示例如果不正确&#xff0c;先清除旧配置del %USERPROFILE%\.condar…

亚马逊地址关联暴雷:新算法下的账号安全保卫战

2025年Q3&#xff0c;上千个店铺因共享税代地址、海外仓信息重叠等问题被批量冻结&#xff0c;为行业敲响了“精细化合规”的警钟。事件复盘&#xff1a;地址成为关联风控的“致命开关”税代机构违规引发“多米诺效应”事件的导火索指向税代机构“saqibil”&#xff0c;其为降低…

在本地环境中运行 ‘dom-distiller‘ GitHub 库的完整指南

在本地环境中运行 ‘dom-distiller’ GitHub 库的完整指南 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff0c;觉得好请收藏。点击跳转到网站。 1. 项目概述 ‘dom-distiller’ 是一个用于将网页…