Unity教程(二十三)技能系统 投剑技能(上)基础实现

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

Unity教程(零)Unity和VS的使用相关内容
Unity教程(一)开始学习状态机
Unity教程(二)角色移动的实现
Unity教程(三)角色跳跃的实现
Unity教程(四)碰撞检测
Unity教程(五)角色冲刺的实现
Unity教程(六)角色滑墙的实现
Unity教程(七)角色蹬墙跳的实现
Unity教程(八)角色攻击的基本实现
Unity教程(九)角色攻击的改进

Unity教程(十)Tile Palette搭建平台关卡
Unity教程(十一)相机
Unity教程(十二)视差背景

Unity教程(十三)敌人状态机
Unity教程(十四)敌人空闲和移动的实现
Unity教程(十五)敌人战斗状态的实现
Unity教程(十六)敌人攻击状态的实现
Unity教程(十七)敌人战斗状态的完善

Unity教程(十八)战斗系统 攻击逻辑
Unity教程(十九)战斗系统 受击反馈
Unity教程(二十)战斗系统 角色反击

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


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


文章目录

  • Unity开发2D类银河恶魔城游戏学习笔记
  • 前言
  • 一、概述
  • 二、投剑相关状态的创建与实现
    • (1)创建PlayerAimSwordState和PlayerCatchSwordState
    • (2)创建瞄准与接剑的动画
    • (3)实现瞄准投掷的状态转换
  • 三、投剑技能的创建与实现
    • (1)创建Sword物体
    • (2)创建Sword闲置和旋转动画
    • (3)创建投剑技能及控制器脚本
    • (4)投掷剑的实现
  • 四、瞄准的实现
    • (1)瞄准的实现
    • (2)显示瞄准点和路径
    • (3)剑的旋转与嵌入
    • (4)解决投剑方向的问题
    • (5)瞄准投掷时滑动问题的解决
  • 五、接剑的实现
    • (1)剑的回收
    • (2)改进接剑方向
    • (3)接剑状态的转换
  • 总结 完整代码
    • Player.cs
    • PlayerAimSwordState.cs
    • PlayerCatchSwordState.cs
    • PlayerGroundedState.cs
    • PlayerAnimationTriggers.cs
    • Sword_Skill.cs
    • Sword_Skill_Controller.cs
    • SkillManager.cs


前言

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

本节实现角色投剑技能。

Udemy课程地址

对应视频:
Sword Throw Skill State
Setting up details of the sword
Setting up sword’s aim
Improving sword’s behaviour
Improving sword throwing state


一、概述

本节实现投剑技能。
实现投剑的基本功能部分,包括创建动画、状态转换和剑的预制体创建和投出。
实现瞄准的基本功能,包括瞄准状态转换、瞄准轨迹的显示、剑的旋转和嵌入、瞄准方向等。
实现接剑的基本功能,包括保证剑的唯一性、接剑状态转换和接剑的反馈效果等。
新创建瞄准投剑和接剑两个状态,具体状态转换如下:
在这里插入图片描述
主要具体实现如下:
在这里插入图片描述

二、投剑相关状态的创建与实现

(1)创建PlayerAimSwordState和PlayerCatchSwordState

创建脚本瞄准状态PlayerAimSwordState和接剑状态PlayerCatchSwordState,它们继承自PlayerState。

在这里插入图片描述
Alt+Enter生成构造函数和重写。

在这里插入图片描述
在Player中声明两个状态。

    #region 状态public PlayerStateMachine StateMachine { get; private set; }public PlayerIdleState idleState { get; private set; }public PlayerMoveState moveState { get; private set; }public PlayerJumpState jumpState { get; private set; }public PlayerAirState airState { get; private set; }public PlayerDashState dashState { get; private set; }public PlayerWallSlideState wallSlideState { get; private set; }public PlayerWallJumpState wallJumpState { get; private set; }public PlayerPrimaryAttackState primaryAttack { get; private set; }public PlayerCounterAttackState counterAttack { get; private set; }public PlayerAimSwordState aimSword { get; private set; }public PlayerCatchSwordState catchSword {  get; private set; }#endregion//创建对象protected override void Awake(){base.Awake();StateMachine = new PlayerStateMachine();idleState = new PlayerIdleState(StateMachine, this, "Idle");moveState = new PlayerMoveState(StateMachine, this, "Move");jumpState = new PlayerJumpState(StateMachine, this, "Jump");airState = new PlayerAirState(StateMachine, this, "Jump");dashState = new PlayerDashState(StateMachine, this, "Dash");wallSlideState = new PlayerWallSlideState(StateMachine, this, "WallSlide");wallJumpState = new PlayerWallJumpState(StateMachine, this, "Jump");primaryAttack = new PlayerPrimaryAttackState(StateMachine, this, "Attack");counterAttack = new PlayerCounterAttackState(StateMachine, this, "CounterAttack");aimSword = new PlayerAimSwordState(StateMachine, this, "AimSword");catchSword = new PlayerCatchSwordState(StateMachine, this, "CatchSword");}

(2)创建瞄准与接剑的动画

瞄准与接剑相关动画在Sword+Aim+Throw这张精灵表中
在这里插入图片描述
层次面板中选中Animator,在Animation面板中创建动画playerAimSword
playerAimSword精灵表标号14-16,采样率改为15
具体讲解见Unity教程(零)Unity和VS的使用相关内容

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

同理,playerThrowSword,精灵表标号16、17、21、22,采样率改为14

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

同理,playerCatchSword,精灵表标号17、16、15、14,采样率改为14
在这里插入图片描述
在这里插入图片描述
取消三个动画的循环时间
在这里插入图片描述
连接状态机。
连接playerAimSword和PlayerThrowSword
添加过渡条件AimSword,并修改过渡设置
在这里插入图片描述
Entry->playerAimSword的过渡,加条件变量
在这里插入图片描述
playerAimSword->playerThrowSword的过渡,加条件变量
在这里插入图片描述
playerThrowSword->Exit的过渡,修改退出时间和持续时间
在这里插入图片描述
连接playerCatchSword
添加过渡条件CatchSword,并修改过渡设置
在这里插入图片描述
Entry->playerCatchSword的过渡,加条件变量
在这里插入图片描述
playerCatchSword->Exit的过渡,加条件变量
在这里插入图片描述

(3)实现瞄准投掷的状态转换

要实现的功能为按下鼠标右键玩家进入瞄准状态,松开鼠标右键玩家恢复空闲状态。
在playerGroundedState玩家接地状态中添加转换到playerAimSwordState的代码。

    public override void Update(){base.Update();if (Input.GetKeyDown(KeyCode.Mouse1))stateMachine.ChangeState(player.aimSword);if (Input.GetKeyDown(KeyCode.Q))stateMachine.ChangeState(player.counterAttack);if (Input.GetKeyDown(KeyCode.Mouse0))stateMachine.ChangeState(player.primaryAttack);if(!player.isGroundDetected())stateMachine.ChangeState(player.airState);if (Input.GetKeyDown(KeyCode.Space)&& player.isGroundDetected())stateMachine.ChangeState(player.jumpState);}

在playerAimSwordState中退出状态。

    public override void Update(){base.Update();if(Input.GetKeyUp(KeyCode.Mouse1))stateMachine.ChangeState(player.idleState);}

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

三、投剑技能的创建与实现

(1)创建Sword物体

剑的精灵表是SwordSpin这一张。
在这里插入图片描述

我们拖一张出来到层次面板中重命名为Sword。
修改剑的层次。
在这里插入图片描述
我们还是把它挂在一个空物体下面,方便添加组件。
右击->Create Empty Parent->重命名为Sword
在这里插入图片描述

在父物体上添加刚体和圆形碰撞器
在这里插入图片描述

(2)创建Sword闲置和旋转动画

创建动画控制器Sword_AC,把它挂在子物体Sword上。

在这里插入图片描述
在这里插入图片描述
在Animations文件夹中,新建Sword文件夹存放剑的闲置动画和旋转动画。
在Animation面板中创建动画SwordIdle
SwordIdle精灵表标号23
在这里插入图片描述
在这里插入图片描述
在Animation面板中创建动画SwordFlip
SwordFlip精灵表标号3、5、9、12,采样率改为20
在这里插入图片描述
在这里插入图片描述
把swordFlip设置为默认状态
在这里插入图片描述
连接状态机。
连接SwordFlip和SwordIdle。
添加过渡条件Rotation,并修改过渡设置。
在这里插入图片描述
SwordFlip->SwordIdle的过渡,加条件变量
在这里插入图片描述
SwordIdle->SwordFlip的过渡,加条件变量
在这里插入图片描述

(3)创建投剑技能及控制器脚本

创建投剑技能脚本Sword_Skill,它继承自Skill技能基类。
在这里插入图片描述
在投剑技能脚本中添加经常修改的技能信息。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Sword_Skill : Skill
{[Header("Skill Info")][SerializeField] private GameObject swordPrefab;[SerializeField] private Vector2 launchForce;[SerializeField] private float swordGravity;
}

在SkillManager中创建技能。

    public Dash_Skill dash { get; private set; }public Clone_Skill clone { get; private set; }public Sword_Skill sword { get; private set; }private void Start(){dash = GetComponent<Dash_Skill>();clone = GetComponent<Clone_Skill>();sword = GetComponent<Sword_Skill>();}

创建控制器脚本Sword_Skill_Controller。
在这里插入图片描述
在Sword_Skill_Controller中添加变量获取要用的组件。

//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 void Awake(){anim = GetComponentInChildren<Animator>();rb = GetComponent<Rigidbody2D>();cd = GetComponent<CircleCollider2D>();}
}

技能实现依然类似上节的分身技能,预制体的创建与设置分别在技能脚本和控制器脚本中实现。

在控制器脚本中添加参数设置。

    public void SetupSword(Vector2 _dir, float _gravityScale, Player _player){player = _player;rb.velocity = _dir;rb.gravityScale = _gravityScale;}

在技能脚本中实例化Sword预制体到角色现在的位置。
这要用到player的位置参数,先在Skill基类中创建player变量并赋值,方便后续书写。

    protected Player player;protected virtual void Start(){player = PlayerManager.instance.player;}

在Sword_Skill中添加函数CreateSword,实例化预制体,并调用控制器中的函数SetupSword设置参数。

    public void CreateSword(){GameObject newSword = Instantiate(swordPrefab, player.transform.position, transform.rotation);Sword_Skill_Controller newSwordScript = newSword.GetComponent<Sword_Skill_Controller>();newSwordScript.SetupSword(launchForce, swordGravity,player);   }

将Sword_Skill挂在SkillManager下。
在这里插入图片描述

将Sword_Skill_Controller挂到创建的物体Sword下。
在这里插入图片描述

将Sword拉成预制体。
为Sword_Skill的参数赋值,从预制体文件夹中拖入剑的预制体,为参数赋一个合适的值
在这里插入图片描述

(4)投掷剑的实现

在使用技能时,我们要让角色扔出剑,这里要用Animator的事件实现。
在玩家触发器脚本PlayerAnimationTriggers中添加调用CreateSword的函数。

    private void ThrowSword(){SkillManager.instance.sword.CreateSword();}

在playerThrowSword动画中添加事件,触发事件时创建Sword。
在这里插入图片描述
在这里插入图片描述
效果如下:
在这里插入图片描述

四、瞄准的实现

(1)瞄准的实现

现在已经实现了将剑投掷出去,但作为技能使用,我们希望剑沿着鼠标所指的方向投掷。

实现这一部分需要获取鼠标的世界坐标。Input.mousePosition可以获得鼠标的屏幕坐标,我们将它转换到世界坐标后计算方向。最终的投掷方向由以玩家位置为起点、鼠标位置为终点的向量和投掷力度计算得到。
在Sword_Skill中添加函数AimDirection获取瞄准的方向。

    public Vector2 AimDirection(){Vector2 playerPosition = player.transform.position;Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);Vector2 direction = mousePosition - playerPosition;return direction;}

这里用到了函数Camera.ScreenToWorldPoint,它可以把点从屏幕空间变换到三维空间。
它的介绍见 Unity官方手册


创建变量finalDir存放最终的方向,并在Update函数中不断计算更新方向。计算时,将瞄准方向归一化后,乘该方向的投掷力度获得最终方向。将CreateSword中的投掷方向改为finalDir。

    private Vector2 finalDir;protected override void Update(){if(Input.GetKeyUp(KeyCode.Mouse1))finalDir = new Vector2(AimDirection().normalized.x * launchForce.x , AimDirection().normalized.y * launchForce.y);}public void CreateSword(){GameObject newSword = Instantiate(swordPrefab, player.transform.position, transform.rotation);Sword_Skill_Controller newSwordScript = newSword.GetComponent<Sword_Skill_Controller>();newSwordScript.SetupSword(finalDir, swordGravity, player);   }

(2)显示瞄准点和路径

先把瞄准点都生成在玩家位置,但是先不激活不显示。直到按下鼠标右键进入瞄准状态,再激活瞄准点并计算修改点的位置。在将剑实例化后就可以取消点的显示,再次设置为未激活状态了。

剑被抛出去后运动路线是个抛物线,通过运动公式计算间隔一定时刻剑的位移来确定位置。设置点的个数和间隔控制瞄准点的显示。

在Sword_Skill脚本中添加瞄准点相关变量。

    [Header("Aim dots")][SerializeField] private int numberOfDots;[SerializeField] private float spaceBetweenDots;[SerializeField] private GameObject dotPrefab;[SerializeField] private Transform dotsParent;private GameObject[] dots;

先创建生成瞄准点的函数,实例化预制体存储在数组中,将瞄准点改为未激活状态。
创建激活瞄准点的函数方便使用。
生成与激活瞄准点的函数如下:

    protected override void Start(){base.Start();GenerateDots();}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);}}

显示瞄准点要根据时间变化计算轨迹上点的位置,将t作为参数传入,采用简单的运动公式计算轨迹上的点
{ x = v x t , y = v y t + 1 2 g t 2 \begin{cases} x = v_x t, \\ y = v_y t + \frac{1}{2} g t^2 \end{cases} {x=vxt,y=vyt+21gt2
调用计算函数,每隔固定时刻取一个点,显示整条轨迹。函数将在Update函数中调用,在鼠标右键被按住时,随鼠标运动改变瞄准点显示。
注意:这里是按着鼠标时,要用GetKey()

代码如下:

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

在PlayerAimSwordState的Enter函数中添加瞄准点的激活

    public override void Enter(){base.Enter();player.skill.sword.DotsActive(true);}

在SwordSkill的CreateSword函数中设置完参数剑要丢出了,这时就可以关闭瞄准点把它改为未激活了。

    public void CreateSword(){GameObject newSword = Instantiate(swordPrefab, player.transform.position, transform.rotation);Sword_Skill_Controller newSwordScript = newSword.GetComponent<Sword_Skill_Controller>();newSwordScript.SetupSword(finalDir, swordGravity, player);   DotsActive(false);}

创建瞄准点预制体。
创建一个圆形精灵,改变它的层次为地面层 -2,使它在地里面不可见。
右键 -> 2D Object -> Sprite ->Circle ->重命名为AimDot
在这里插入图片描述
在这里插入图片描述

修改它的大小、颜色到合适的值
在这里插入图片描述
拉成预制体
在这里插入图片描述
在Player上创建空物体AimDotParent作为瞄准点的父物体。
在这里插入图片描述

在技能管理器上给Sword_Skill参数赋值
在这里插入图片描述

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

(3)剑的旋转与嵌入

可以看到,现在在投掷出去后,剑是完全不动的,而且会砸在骷髅身上弹回来,我们需要把它改的更符合规律一点。

让剑尖顺着投掷的轨迹旋转直到击中骷髅。我们可以通过修改坐标轴的方向实现,这里选取了x轴,让坐标轴与运动方向一致,剑也会随之旋转。
首先要把剑调整为与x轴平齐,剑尖朝向x轴正方向,剑才能沿着轨迹运动。调整时旋转子物体,把它朝向右,到以下程度:
在这里插入图片描述
在这里插入图片描述
注意:记得应用预制体的更改

在这里插入图片描述
在Sword_Skill_Controller中添加update函数,根据速度实时改变坐标轴方向

    private void Update(){transform.right = rb.velocity;}

在这里插入图片描述

接下来实现让剑嵌入敌人和地面,在预制体面板中进行更改。
在碰撞盒上勾选触发器。
在这里插入图片描述

我们要让剑在插入物体后静止,要将刚体类型改为Kinematic,Kinematic 2D 刚体不受重力和作用力影响且开销较小。
更详细的介绍请见 Unity官方手册 2D刚体
同时锁定XYZ三个轴的旋转和移动避免剑产生多余的运动,关闭碰撞器避免产生连续碰撞。

在Sword_Skill_Controller中添加参数canRotate来表示是否处于旋转,默认值设为true。只有处于旋转时需要改变剑的坐标轴。
创建触发器函数,在有物体进入剑的触发器时,将canRotate改为false,并实行以上操作让剑静止插入物体,同时将剑作为子物体挂在插入的物体上,让剑后续跟随物体一起移动。

    private bool canRotate = true;private void Update(){if (canRotate){transform.right = rb.velocity;}}private void OnTriggerEnter2D(Collider2D collision){canRotate = false;cd.enabled = false;rb.isKinematic = true;rb.constraints = RigidbodyConstraints2D.FreezeAll;transform.parent = collision.transform;}

现在投掷剑,剑会与玩家碰撞,粘在玩家身上,我们要更改剑的图层来解决这一问题。
添加Sword图层,将剑及其子物体都改为Sword层
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

修改碰撞矩阵
Edit -> Project Setting -> Physics 2D -> Layer Collision Matrix -> 仅保留Sword与Ground和Enemy的碰撞

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

(4)解决投剑方向的问题

在这里插入图片描述

角色不会随投掷方向的变化随之改变面朝的方向,要修改代码使角色随鼠标位置改变面向。
改变朝向的条件如下:

鼠标在角色左侧,角色面向右
鼠标在角色右侧,角色面向左

在PlayerAimSwordState中添加:

    public override void Update(){base.Update();if(Input.GetKeyUp(KeyCode.Mouse1))stateMachine.ChangeState(player.idleState);Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);if (player.transform.position.x > mousePosition.x && player.facingDir == 1)player.Flip();else if(player.transform.position.x < mousePosition.x && player.facingDir == -1)player.Flip();}

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

(5)瞄准投掷时滑动问题的解决

在这里插入图片描述
我们要参照之前空闲状态写的,在瞄准和接剑状态退出时要调用协程使它处于"忙的状态"
在这里插入图片描述
玩家从移动状态转换到瞄准状态时也依然会滑动,因此在瞄准状态中将速度设置为0。
在瞄准和接剑状态里分别添加

//PlayerAimSwordState: 玩家瞄准状态public override void Exit(){base.Exit();player.StartCoroutine("BusyFor", 0.2f);}public override void Update(){base.Update();player.ZeroVelocity();if (Input.GetKeyUp(KeyCode.Mouse1))stateMachine.ChangeState(player.idleState);Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);if (player.transform.position.x > mousePosition.x && player.facingDir == 1)player.Flip();else if(player.transform.position.x < mousePosition.x && player.facingDir == -1)player.Flip();}
//PlayerCatchSwordState: 玩家接剑状态public override void Exit(){base.Exit();player.StartCoroutine("BusyFor", 0.1f);}

五、接剑的实现

(1)剑的回收

现在可以同时存在多把剑,于是可能出现下面把骷髅扎成刺猬的场景,我们需要改进这一点,让场景中只能同时存在一把剑,剑击中敌人后可以回收。
在这里插入图片描述
添加变量sword,每次创建后都分配新的剑给此变量
在player中添加变量sword,并添加分配和接住sword的函数。接剑时将转到PlayerCatchSwordState。

public SkillManager skill{get; private set;}public GameObject sword { get; private set; }public void AssignNewSword(GameObject _newSword){sword = _newSword;}public void ClearTheSword(){Destroy(sword);}

在创建剑后,将剑分配给sword,在Sword_Skill中调用函数

    public void CreateSword(){GameObject newSword = Instantiate(swordPrefab, player.transform.position, transform.rotation);Sword_Skill_Controller newSwordScript = newSword.GetComponent<Sword_Skill_Controller>();newSwordScript.SetupSword(finalDir, swordGravity, player);player.AssignNewSword(newSword);DotsActive(false);}

接下来实现剑的返回。
在Sword_Skill中添加返回速度returnSpeed,传递到控制器中。

    [Header("Skill Info")][SerializeField] private GameObject swordPrefab;[SerializeField] private Vector2 launchForce;[SerializeField] private float swordGravity;[SerializeField] private float returnSpeed;public void CreateSword(){GameObject newSword = Instantiate(swordPrefab, player.transform.position, transform.rotation);Sword_Skill_Controller newSwordScript = newSword.GetComponent<Sword_Skill_Controller>();newSwordScript.SetupSword(finalDir, swordGravity, player, returnSpeed);player.AssignNewSword(newSword);DotsActive(false);}

在Sword_Skill_Controller中添加返回相关变量,isReturning和returnSpeed,分别表示是否返回中和返回速度。
创建ReturnSword函数,让剑不再旋转并且不再跟随插入的物体移动,同时把isReturning改为true,表示正在收回剑。这里不使用改为Kinematic刚体的方式,因为这会使剑无法中途收回。
当剑处于收回状态时,让它以设定好的速度由当前位置向着玩家位置移动,当距离小于某一值时,销毁剑,这一部分写在Update中随时间更新位置。

    private bool isReturning;private float returnSpeed = 12;public void SetupSword(Vector2 _dir, float _gravityScale, Player _player, float _returnSpeed){player = _player;rb.velocity = _dir;rb.gravityScale = _gravityScale;returnSpeed = _returnSpeed;}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.ClearTheSword();}}

修改进入瞄准状态的条件,将它改为瞄准投剑和收回剑共用的操作。
按下鼠标右键且当player没有被分配剑时转换到瞄准状态;如果有分配剑,则调用Sword_Skill_Controller中让剑返回的函数ReturnSword()。写个函数HasNoSword判断并实现

    //更新public override void Update(){base.Update();if (Input.GetKeyDown(KeyCode.Mouse1) && HasNoSword())stateMachine.ChangeState(player.aimSword);if (Input.GetKeyDown(KeyCode.Q))stateMachine.ChangeState(player.counterAttack);if (Input.GetKeyDown(KeyCode.Mouse0))stateMachine.ChangeState(player.primaryAttack);if(!player.isGroundDetected())stateMachine.ChangeState(player.airState);if (Input.GetKeyDown(KeyCode.Space)&& player.isGroundDetected())stateMachine.ChangeState(player.jumpState);}private bool HasNoSword(){if (!player.sword){return true;}player.sword.GetComponent<Sword_Skill_Controller>().ReturnSword();return false;}

给回收速度赋一个合适的值
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
为了提升技能效果,在扔出剑时,播放剑的旋转动画;在剑接触到物体碰撞时停止播放动画。
在设置剑和触发器函数中分别添加改变动画变量Rotation的语句。

    public void SetupSword(Vector2 _dir, float _gravityScale, Player _player, float _returnSpeed){player = _player;rb.velocity = _dir;rb.gravityScale = _gravityScale;returnSpeed = _returnSpeed;anim.SetBool("Rotation", true);}private void OnTriggerEnter2D(Collider2D collision){anim.SetBool("Rotation", false);canRotate = false;cd.enabled = false;rb.isKinematic = true;rb.constraints = RigidbodyConstraints2D.FreezeAll;transform.parent = collision.transform;}}

在这里插入图片描述

(2)改进接剑方向

接剑的方向和投掷剑的方向问题一样,在PlayerCatchSwordState中获取剑的位置,比较角色位置与剑的位置确定是否应该翻转。

    public override void Enter(){base.Enter();sword = player.sword.transform;if (player.transform.position.x > sword.position.x && player.facingDir == 1)player.Flip();else if (player.transform.position.x < sword.position.x && player.facingDir == -1)player.Flip();}

(3)接剑状态的转换

前面在接剑时,我们直接调用ClearSword函数销毁剑。除此之外,还会播放接剑动画,让我们添加这部分。
首先,Ctrl+R快捷键,将Player中的函数ClearSword改名为CatchSword。在函数中增加到PlayerCatchSwordState的状态转换。

    public void CatchTheSword(){StateMachine.ChangeState(catchSword);Destroy(sword);}

退出接剑状态,要在playerCatchSword中触发事件实现。
在动画的最后一帧添加动画事件,当触发器被触发时,转换为空闲状态。
在这里插入图片描述
在这里插入图片描述
在playerCatchSwordState中添加

    public override void Update(){base.Update();if (triggerCalled)stateMachine.ChangeState(player.idleState);}

在这里插入图片描述
接剑的时候让角色后退一点,显示剑有一个小的冲击力。
在Player中添加变量swordReturnImpact。

    [Header("Move Info")]public float moveSpeed = 8f;public float jumpForce = 12f;public float swordReturnImpact = 7f;

在接剑状态中添加接剑后改变速度。

    public override void Enter(){base.Enter();sword = player.sword.transform;if (player.transform.position.x > sword.position.x && player.facingDir == 1)player.Flip();else if (player.transform.position.x < sword.position.x && player.facingDir == -1)player.Flip();rb.velocity = new Vector2 (player.swordReturnImpact * -player.facingDir,rb.velocity.y);}

赋上合适的值
在这里插入图片描述
在这里插入图片描述

总结 完整代码

Player.cs

创建PlayerAimSwordState和PlayerCatchSwordState两个状态并赋值。
添加接剑时后退的相关参数swordReturnImpact。
添加参数sword获取投掷的剑,并判断现在是否有剑被创建分配。

//Player:玩家
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Player : Entity
{[Header("Attack details")]public Vector2[] attackMovement;public float counterAttackDuration = 0.2f;public bool isBusy { get; private set; }[Header("Move Info")]public float moveSpeed = 8f;public float jumpForce = 12f;public float swordReturnImpact = 7f;[Header("Dash Info")]public float dashSpeed=25f;public float dashDuration=0.2f;public float dashDir { get; private set; }public SkillManager skill{get; private set;}public GameObject sword { get; private set; }#region 状态public PlayerStateMachine StateMachine { get; private set; }public PlayerIdleState idleState { get; private set; }public PlayerMoveState moveState { get; private set; }public PlayerJumpState jumpState { get; private set; }public PlayerAirState airState { get; private set; }public PlayerDashState dashState { get; private set; }public PlayerWallSlideState wallSlideState { get; private set; }public PlayerWallJumpState wallJumpState { get; private set; }public PlayerPrimaryAttackState primaryAttack { get; private set; }public PlayerCounterAttackState counterAttack { get; private set; }public PlayerAimSwordState aimSword { get; private set; }public PlayerCatchSwordState catchSword {  get; private set; }#endregion//创建对象protected override void Awake(){base.Awake();StateMachine = new PlayerStateMachine();idleState = new PlayerIdleState(StateMachine, this, "Idle");moveState = new PlayerMoveState(StateMachine, this, "Move");jumpState = new PlayerJumpState(StateMachine, this, "Jump");airState = new PlayerAirState(StateMachine, this, "Jump");dashState = new PlayerDashState(StateMachine, this, "Dash");wallSlideState = new PlayerWallSlideState(StateMachine, this, "WallSlide");wallJumpState = new PlayerWallJumpState(StateMachine, this, "Jump");primaryAttack = new PlayerPrimaryAttackState(StateMachine, this, "Attack");counterAttack = new PlayerCounterAttackState(StateMachine, this, "CounterAttack");aimSword = new PlayerAimSwordState(StateMachine, this, "AimSword");catchSword = new PlayerCatchSwordState(StateMachine, this, "CatchSword");}// 设置初始状态protected override void Start(){base.Start();skill = SkillManager.instance;StateMachine.Initialize(idleState);}// 更新protected override void Update(){base.Update();StateMachine.currentState.Update();CheckForDashInput();}public void AssignNewSword(GameObject _newSword){sword = _newSword;}public void CatchTheSword(){StateMachine.ChangeState(catchSword);Destroy(sword);}public IEnumerator BusyFor(float _seconds){isBusy = true;yield return new WaitForSeconds(_seconds);isBusy = false;}//设置触发器public void AnimationTrigger() => StateMachine.currentState.AnimationFinishTrigger();//检查冲刺输入public void CheckForDashInput(){if (Input.GetKeyDown(KeyCode.LeftShift) && SkillManager.instance.dash.CanUseSkill()){dashDir = Input.GetAxisRaw("Horizontal");if (dashDir == 0)dashDir = facingDir;StateMachine.ChangeState(dashState);}}}

PlayerAimSwordState.cs

激活瞄准点。
实现瞄准状态到空闲状态的转换。
实现瞄准时根据鼠标位置左右翻转。
设置处于忙碌中,防止滑动。

//PlayerAimSwordState: 玩家瞄准状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class PlayerAimSwordState : PlayerState
{public PlayerAimSwordState(PlayerStateMachine _stateMachine, Player _player, string _animBoolName) : base(_stateMachine, _player, _animBoolName){}public override void Enter(){base.Enter();player.skill.sword.DotsActive(true);}public override void Exit(){base.Exit();player.StartCoroutine("BusyFor", 0.2f);}public override void Update(){base.Update();player.ZeroVelocity();if (Input.GetKeyUp(KeyCode.Mouse1))stateMachine.ChangeState(player.idleState);Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);if (player.transform.position.x > mousePosition.x && player.facingDir == 1)player.Flip();else if(player.transform.position.x < mousePosition.x && player.facingDir == -1)player.Flip();}
}

PlayerCatchSwordState.cs

实现接剑状态到空闲状态的转换。
实现接剑时根据剑的位置左右翻转和后退效果。
设置处于忙碌中,防止滑动。

//PlayerCatchSwordState: 玩家接剑状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class PlayerCatchSwordState : PlayerState
{private Transform sword;public PlayerCatchSwordState(PlayerStateMachine _stateMachine, Player _player, string _animBoolName) : base(_stateMachine, _player, _animBoolName){}public override void Enter(){base.Enter();sword = player.sword.transform;if (player.transform.position.x > sword.position.x && player.facingDir == 1)player.Flip();else if (player.transform.position.x < sword.position.x && player.facingDir == -1)player.Flip();rb.velocity = new Vector2 (player.swordReturnImpact * -player.facingDir,rb.velocity.y);}public override void Exit(){base.Exit();player.StartCoroutine("BusyFor", 0.1f);}public override void Update(){base.Update();if (triggerCalled)stateMachine.ChangeState(player.idleState);}
}

PlayerGroundedState.cs

添加player是否被分配剑的判断,根据判断转到瞄准状态或回收剑。

//超级状态PlayerGroundedState:接地状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class PlayerGroundedState : PlayerState
{//构造函数public PlayerGroundedState(PlayerStateMachine _stateMachine, Player _player, string _animBoolName) : base(_stateMachine, _player, _animBoolName){}//进入public override void Enter(){base.Enter();}//退出public override void Exit(){base.Exit();}//更新public override void Update(){base.Update();if (Input.GetKeyDown(KeyCode.Mouse1) && HasNoSword())stateMachine.ChangeState(player.aimSword);if (Input.GetKeyDown(KeyCode.Q))stateMachine.ChangeState(player.counterAttack);if (Input.GetKeyDown(KeyCode.Mouse0))stateMachine.ChangeState(player.primaryAttack);if(!player.isGroundDetected())stateMachine.ChangeState(player.airState);if (Input.GetKeyDown(KeyCode.Space)&& player.isGroundDetected())stateMachine.ChangeState(player.jumpState);}private bool HasNoSword(){if (!player.sword){return true;}player.sword.GetComponent<Sword_Skill_Controller>().ReturnSword();return false;}
}

PlayerAnimationTriggers.cs

实现在动画播完触发事件时扔出剑。

//PlayerAnimationTriggers:触发器组件
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class PlayerAnimationTriggers : MonoBehaviour
{private Player player => GetComponentInParent<Player>();private void AnimationTrigger(){player.AnimationTrigger();}private void AttackTrigger(){Collider2D[] colliders = Physics2D.OverlapCircleAll(player.attackCheck.position, player.attackCheckRadius);foreach(var hit in colliders){if (hit.GetComponent<Enemy>() != null)hit.GetComponent<Enemy>().Damage();}}private void ThrowSword(){SkillManager.instance.sword.CreateSword();}
}

Sword_Skill.cs

创建Skill相关信息,和瞄准点所需信息。
创造剑的函数CreateSword,实例化剑并调用函数设置剑的相关变量,将剑分配给player,关闭瞄准点显示。
实现瞄准点相关函数,包括计算瞄准方向、生成点瞄准点、激活/关闭瞄准点、计算运动轨迹。

//Sword_Skill:掷剑技能
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Sword_Skill : Skill
{[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;protected override void Start(){base.Start();GenerateDots();}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);}}}public void CreateSword(){GameObject newSword = Instantiate(swordPrefab, player.transform.position, transform.rotation);Sword_Skill_Controller newSwordScript = newSword.GetComponent<Sword_Skill_Controller>();newSwordScript.SetupSword(finalDir, swordGravity, player, returnSpeed);player.AssignNewSword(newSword);DotsActive(false);}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;}
}

Sword_Skill_Controller.cs

获取相关组件信息,创建相关变量并赋值。
根据Sword_Skill传值,设置剑的相关参数,设置剑的动画。
实现包围盒触发器函数,设置剑的相关参数,设置剑的动画。
实现到接剑状态的转换,让剑飞回玩家位置。实现剑返回的参数设置。

//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 void Awake(){anim = GetComponentInChildren<Animator>();rb = GetComponent<Rigidbody2D>();cd = GetComponent<CircleCollider2D>();}public void SetupSword(Vector2 _dir, float _gravityScale, Player _player, float _returnSpeed){player = _player;rb.velocity = _dir;rb.gravityScale = _gravityScale;returnSpeed = _returnSpeed;anim.SetBool("Rotation", true);}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();}}private void OnTriggerEnter2D(Collider2D collision){if (isReturning)return;StuckInto(collision);}private void StuckInto(Collider2D collision){anim.SetBool("Rotation", false);canRotate = false;cd.enabled = false;rb.isKinematic = true;rb.constraints = RigidbodyConstraints2D.FreezeAll;transform.parent = collision.transform;}}

SkillManager.cs

创建投剑技能。

//SkillManager:玩家管理器
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SkillManager : MonoBehaviour
{public static SkillManager instance;public Dash_Skill dash { get; private set; }public Clone_Skill clone { get; private set; }public Sword_Skill sword { get; private set; }private void Awake(){if (instance != null && instance != this){Destroy(this.gameObject);}else{instance = this;}}private void Start(){dash = GetComponent<Dash_Skill>();clone = GetComponent<Clone_Skill>();sword = GetComponent<Sword_Skill>();}
}

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

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

相关文章

Oracle01-入门

零、文章目录 Oracle01-入门 1、Oracle简介 &#xff08;1&#xff09;数据库基础 数据库基础请参考&#xff1a;https://blog.csdn.net/liyou123456789/article/details/131207068 &#xff08;2&#xff09;Oracle是什么 ORACLE 数据库系统是美国 ORACLE 公司&#xff…

springboot集成mybatis-plus详细使用

以下是 Spring Boot 集成 MyBatis-Plus 的详细步骤&#xff1a; 创建 Spring Boot 项目 可使用 Spring Initializr 快速创建项目&#xff0c;添加相关依赖。 引入依赖 在项目 pom.xml 文件中添加以下依赖&#xff1a; <!-- MyBatis-Plus 启动依赖 --> <dependenc…

【wsl】命令说明,wsl的虚拟机ubuntu十分好用

wsl官方说明地址 wsl虚拟机安装简单方便&#xff0c;十分推荐。 安装 在上方的官网链接有安装办法和各种操作指南&#xff0c;在此不再赘述。 安装wsl后从微软商店搜索ubuntu点击后就能直接安装虚拟机镜像&#xff0c;多快好省。 wsl命令 阅读官网文档时发现wsl十分强大 …

Open CASCADE学习|判断一点与圆弧的位置关系

一、引言 在计算机辅助设计&#xff08;CAD&#xff09;、计算机图形学以及机械制造等众多领域中&#xff0c;经常需要处理几何图形之间的位置关系判断问题。其中&#xff0c;判断一个点与圆弧的位置关系是一个基础且重要的任务。Open CASCADE 作为一个强大的开源几何建模内核…

<论文>(字节跳动)使用大语言模型进行时间序列理解和推理

一、摘要 本文介绍2024年12月字节跳动牵头发表的大模型论文《ChatTS: Aligning Time Series with LLMs via Synthetic Data for Enhanced Understanding and Reasoning》。论文提出了 ChatTS 模型&#xff0c;用合成数据提升对时间序列的理解和推理能力。作者在纽约出租车乘客数…

大数据应用开发和项目实战-电商双11美妆数据分析

数据初步了解 &#xff08;head出现&#xff0c;意味着只出现前5行&#xff0c;如果只出现后面几行就是tail&#xff09; info shape describe 数据清洗 重复值处理 这个重复值是否去掉要看实际情况&#xff0c;比如说&#xff1a;昨天卖了5瓶七喜&#xff0c;今天卖了5瓶七…

Vi/Vim 编辑器详细指南

Vi/Vim 编辑器详细指南 简介一、模式详解1. 命令模式(Normal Mode)2. 插入模式(Insert Mode)3. 可视模式(Visual Mode)4. 命令行模式(Ex Mode)二、核心操作1. 保存与退出2. 导航与移动3. 编辑与文本操作4. 搜索与替换三、高级技巧1. 多文件与窗口操作2. 宏录制3. 寄存器…

kotlin 01flow-StateFlow 完整教程

一 Android StateFlow 完整教程&#xff1a;从入门到实战 StateFlow 是 Kotlin 协程库中用于状态管理的响应式流&#xff0c;特别适合在 Android 应用开发中管理 UI 状态。本教程将带全面了解 StateFlow 的使用方法。 1. StateFlow 基础概念 1.1 什么是 StateFlow? StateF…

开发搭载OneNet平台的物联网数据收发APP的设计与实现

一、开发环境与工具准备 工具安装 下载HBuilderX开发版(推荐使用开发版以避免插件兼容性问题)安装Node.js和npm(用于依赖管理及打包)配置Android Studio(本地打包需集成离线SDK)项目初始化 创建uni-app项目,选择“默认模板”或“空白模板”安装必要的UI库(如uView或Van…

HHsuite3 的 HHblits 和 HHsearch比较

HHblits 与 HHsearch 的核心区别及远源同源检测能力对比 一、核心功能与定位差异 特征HHblitsHHsearch核心目标快速迭代搜索,构建高质量多序列比对(MSA)和 Profile HMM,用于大规模序列聚类与初步同源筛选。高精度 Profile HMM-HMM 比对,用于深度同源检测与结构 / 功能预测…

【从零开始学习RabbitMQ | 第二篇】生成交换机到MQ的可靠性保障

目录 ​编辑前言 交换机 Direct交换机与Fanout交换机的差异 Topic交换机 Topic交换机相比Direct交换机的差异 生成我们的交换机&#xff0c;队列&#xff0c;以及绑定关系 基于代码去生成交换机和队列 基于注解去声明队列和交换机 消息转换器 消息队列的高可靠性 发送…

LeetCode 热题 100 22. 括号生成

LeetCode 热题 100 | 22. 括号生成 大家好&#xff0c;今天我们来解决一道经典的算法题——括号生成。这道题在 LeetCode 上被标记为中等难度&#xff0c;要求生成所有可能的并且有效的括号组合。这是一道非常经典的回溯法题目&#xff0c;非常适合用来练习递归和回溯的技巧。…

TestStand API 简介

TestStand API 简介 在自动化测试领域&#xff0c;TestStand 凭借其灵活的架构和强大的功能&#xff0c;成为众多开发者的首选工具。而 TestStand API&#xff08;Application Programming Interface&#xff0c;应用程序编程接口&#xff09;则是打开 TestStand 强大功能的 “…

如何修改 JAR 包中的源码

如何修改 JAR 包中的源码 前言一、准备工作二、将 JAR 当作 ZIP 打开并提取三、重写 Java 类方法 A&#xff1a;直接替换已编译的 .class方法 B&#xff1a;运行时类路径优先加载 四、修改 MyBatis&#xff08;或其他&#xff09;XML 资源五、重新打包 JAR&#xff08;命令行&a…

存算一体架构下的新型AI加速范式:从Samsung HBM-PIM看近内存计算趋势

引言&#xff1a;突破"内存墙"的物理革命 冯诺依曼架构的"存储-计算分离"设计正面临根本性挑战——在GPT-4等万亿参数模型中&#xff0c;数据搬运能耗已达计算本身的200倍。存算一体&#xff08;Processing-In-Memory, PIM&#xff09;技术通过‌在存储介…

蓝桥杯15届国赛 合法密码

问题描述 小蓝正在开发自己的 OJ 网站。他要求网站用户的密码必须符合以下条件&#xff1a; 长度大于等于 8 个字符&#xff0c;小于等于 16 个字符。必须包含至少 1 个数字字符和至少 1 个符号字符。 例如 **lanqiao2024!、-*/0601、8((>w<))8** 都是合法的密码。 而…

Jenkins忘记admin密码后的恢复步骤

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 时间较长没有使用…

C++ - 仿 RabbitMQ 实现消息队列(1)(环境搭建)

C - 仿 RabbitMQ 实现消息队列&#xff08;1&#xff09;&#xff08;环境搭建&#xff09; 什么是消息队列核心特点核心组件工作原理常见消息队列实现应用场景优缺点 项目配置开发环境技术选型 更换软件源安装一些工具安装epel 软件源安装 lrzsz 传输工具安装git安装 cmake安装…

简单面试提问

Nosql非关系型数据库&#xff1a; Mongodb&#xff1a;开源、json形式储存、c编写 Redis&#xff1a;key-value形式储存&#xff0c;储存在内存&#xff0c;c编写 关系型数据库&#xff1a; sqlite;&#xff1a;轻量型、0配置、磁盘存储、支持多种语言 mysql&#xff1a;开源…

油气地震资料信号处理中的NMO(正常时差校正)

油气地震资料信号处理中的NMO&#xff08;正常时差校正&#xff09;介绍与应用 NMO基本概念 **正常时差校正&#xff08;Normal Moveout Correction&#xff0c;NMO&#xff09;**是地震资料处理中的一项关键技术&#xff0c;主要用于消除由于炮检距&#xff08;source-recei…