当前位置: 首页 > news >正文

Unity教程(二十五)技能系统 掷剑技能(下)冻结时间实现

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


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

技能系统

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


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


文章目录

  • Unity开发2D类银河恶魔城游戏学习笔记
    • 技能系统
  • 前言
  • 一、概述
  • 二、冻结时间的实现
  • 总结 完整代码
    • Enemy.cs
    • Sword_Skill.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


一、概述

本节给投剑技能添加冻结时间的功能。
在Enemy类中添加敌人冻结后的反馈,并创建协程。
设置时间冻结的持续时长,由Sword_Skill传入控制器。
在Sword_Skill_Controller中检测敌人,并在击中敌人时调用冻结敌人的协程实现时间冻结。
在这里插入图片描述

在这里插入图片描述

二、冻结时间的实现

首先在Enemy中添加敌人被冻结后应有的反应。敌人被冻结时,速度归零,动画停止播放;冻结结束后,恢复原速度,恢复动画播放速度。
添加变量 defaultMoveSpeed记录敌人原有的速度。
创建协程,实现将敌人冻结一段时间。
代码如下:

    [Header("Move Info")]public float moveSpeed = 1.5f;public float idleTime = 2.0f;public float battleTime = 4.0f;private float defaultMoveSpeed;protected override void Awake(){base.Awake();stateMachine = new EnemyStateMachine();defaultMoveSpeed = moveSpeed;}public virtual void FreezeTime(bool _timeFrozen){if(_timeFrozen){moveSpeed = 0;anim.speed = 0;}else{moveSpeed = defaultMoveSpeed;anim.speed = 1;}}protected virtual IEnumerator FreezeTimeFor(float _seconds){FreezeTime(true);yield return new WaitForSeconds(_seconds);FreezeTime(false);}

在Sword_Skill中添加冻结时间持续时长,并传至控制器

    [Header("Skill Info")][SerializeField] private GameObject swordPrefab;[SerializeField] private Vector2 launchForce;[SerializeField] private float swordGravity;[SerializeField] private float returnSpeed;[SerializeField] private float freezeTimeDuration;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,freezeTimeDuration);player.AssignNewSword(newSword);DotsActive(false);}

在Sword_Skill_Controller中添加变量freezeTimeDuration,在SetupSword函数中接收传来的参数。
在触发器函数OnTriggerEnter2D中检测敌人,并调用协程FreezeTimeFor冻结敌人。

    private float freezeTimeDuration;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);}private void OnTriggerEnter2D(Collider2D collision){if (isReturning)return;if(collision.GetComponent<Enemy>()!=null){Enemy enemy = collision.GetComponent<Enemy>();enemy.Damage();enemy.StartCoroutine("FreezeTimeFor",freezeTimeDuration);}SetupTargetsForBounce(collision);StuckInto(collision);}

在SkillManager中设置FreezeTimeDuration。
在这里插入图片描述

这样可能会遇到一点问题,骷髅被击中时会转身,冻结期间会抖动。
在这里插入图片描述
骷髅转身问题,只需要延长骷髅的地面检测距离就可以。因为骷髅被击退时会向上移动一点,地面检测过短会导致问题。
在这里插入图片描述

在这里插入图片描述
抖动是因为骷髅碰撞盒过大,不断与剑碰撞,修改到合适大小即可

在这里插入图片描述
修改后效果如下:
在这里插入图片描述
教程里给弹跳和盘旋类型也加了时间冻结,但我觉得这两个连续攻击的不加会流畅一些,这里就不添加了。而且我不想这两种类型第一次攻击时冻结敌人,于是自己改了一下:

    {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);}

总结 完整代码

Enemy.cs

添加敌人被冻结时的反馈。

//Enemy:敌人基类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Enemy : Entity
{[SerializeField] protected LayerMask WhatIsPlayer;[Header("Stunned Info")]public float stunDuration = 1.0f;public Vector2 stunDirection;protected bool canBeStunned;[SerializeField] protected GameObject counterImage;[Header("Move Info")]public float moveSpeed = 1.5f;public float idleTime = 2.0f;public float battleTime = 4.0f;private float defaultMoveSpeed;[Header("Attack Info")]public float attackDistance;public float attackCoolDown;[HideInInspector] public float lastTimeAttacked;public EnemyStateMachine stateMachine;protected override void Awake(){base.Awake();stateMachine = new EnemyStateMachine();defaultMoveSpeed = moveSpeed;}protected override void Update(){base.Update();stateMachine.currentState.Update();}public virtual void FreezeTime(bool _timeFrozen){if(_timeFrozen){moveSpeed = 0;anim.speed = 0;}else{moveSpeed = defaultMoveSpeed;anim.speed = 1;}}protected virtual IEnumerator FreezeTimeFor(float _seconds){FreezeTime(true);yield return new WaitForSeconds(_seconds);FreezeTime(false);}#region Counter Attack Window//打开反击窗口public virtual void OpenCounterAttackWindow(){canBeStunned = true;counterImage.SetActive(true);}//关闭反击窗口public virtual void CloseCounterAttackWindow(){canBeStunned = false;counterImage.SetActive(false);}#endregion//检查击晕条件,关闭反击窗口public virtual bool CheckCanBeStunned(){if(canBeStunned){CloseCounterAttackWindow();return true;}return false;}//设置触发器public virtual void AnimationTrigger() => stateMachine.currentState.AnimationFinishTrigger();public virtual RaycastHit2D IsPlayerDetected()=>Physics2D.Raycast(transform.position, Vector2.right * facingDir, 50 ,WhatIsPlayer);protected override void OnDrawGizmos(){base.OnDrawGizmos();Gizmos.color = Color.yellow;Gizmos.DrawLine(transform.position, new Vector3(transform.position.x + attackDistance * facingDir, transform.position.y));}
}

Sword_Skill.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;[SerializeField] private float freezeTimeDuration;[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,freezeTimeDuration);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.dtcms.com/a/303516.html

相关文章:

  • PostgreSQL 详解
  • java每日精进 7.28【流程设计6.0(泳池和泳道)】
  • V-Ray 7.00.08 for 3ds Max 2021-2026 安装与配置教程(含语言补丁)
  • HTML5 `<figure>` 标签:提升网页语义化与可访问性的利器
  • 【2025/07/28】GitHub 今日热门项目
  • Solidity基础(教程①-简单数字存储)
  • 第二十一章:AI的“视觉压缩引擎”与“想象力温床”
  • AIBOX硬件设计概述
  • 什么是 LoRA 学习笔记
  • 项目执行标准流程是什么样的,如何制定
  • Java 接口入门学习笔记:从概念到简单实践
  • ts学习3
  • Microsoft 365中的Compromised User Detection功能深度解析:智能识别与防护用户账户安全的利器
  • 极速保鲜+ERP数字化,深圳“荔枝出海”驶入外贸订单管理快车道
  • 2023.2.2版IDEA安装教程(ideaIU-2023.2.2.win.exe详细步骤)Windows电脑一键安装指南
  • 二层环路与三层环路:原理、区别与解决方案全解析
  • MacBook IOS操作系统格式化U盘FAT32
  • 铜金矿数据分组优化系统设计与实现
  • 前端基础之《Vue(25)—Vue3简介》
  • Go 原理之 GMP 并发调度模型
  • it is not annotated with @ClientEndpoint“
  • 【学习路线】Android开发2025:从入门到高级架构师
  • 拓扑排序算法
  • LeetCode 85. 最大矩形
  • Nginx 四层(stream)反向代理 + DNS 负载均衡
  • 回滚日志-undo log
  • Resilience4j 实战—使用方式及配置详解
  • 如何利用机器学习分析筛选生物标记物
  • 【机器学习】第八章 模型评估及改进
  • C++入门自学Day2-- c++类与对象(初识)