高阶中包含的内容(钩锁发射的扭曲动画、钩锁钩中物体后伸直的动画)
后附完整的两个脚本及本人的注释,便于有需者理解,欢迎随时交流
预览大致实现效果如下:
在场景中创建GameObject如下(由父对象到子对象一一进行讲解):
Player:(示例中是一个 圆形的sprite)对其添加RigidBody2D、Circle Collider2D 、Spring Joint2D组件(此时无需进行组件的设置,若有需要可以后续进行调整)
Gunpivot:钩锁枪的锚点,其为空对象,位置大概设置在Player的边缘(后续用于实现钩锁枪随着鼠标旋转的效果)
GrapplingGun:(示例中为一个长方形的sprite)钩锁枪,用于后期实现发射钩锁,仅添加Box Collider2D即可
FirePoint:钩锁的发射点,空对象,即钩锁发射的起始位置,设置在钩锁枪的边缘即可
Rope:在空对象上添加LineRenderer并适当改变宽度即可。
下有两个脚本分别添加给GrapplingGun和Rope即可
脚本1:Perfecter_Grapple添加给GrapplingGun
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Perfecter_Grapple : MonoBehaviour
{
[Header("Scripts Ref:")]
public Grappling_Rope grappleRope;
[Header("Layers Settings:")]
[SerializeField] private bool grappleToAll = false;
[SerializeField] private int grappableLayerNumber = 9;
[Header("Main Camera:")]
public Camera m_camera;
[Header("Transform Ref:")]
public Transform gunHolder;
public Transform gunPivot;
public Transform firePoint;
[Header("Physics Ref:")]
public SpringJoint2D m_springJoint2D;
public Rigidbody2D m_rigidbody;
[Header("Rotation:")]
[SerializeField] private bool rotateOverTime = true;
[Range(0, 60)] [SerializeField] private float rotationSpeed = 4;
[Header("Distance:")]
[SerializeField] private bool hasMaxDistance = false;
[SerializeField] private float maxDistnace = 20;
private enum LaunchType
{
Transform_Launch,
Physics_Launch
}
[Header("Launching:")]
[SerializeField] private bool launchToPoint = true;
[SerializeField] private LaunchType launchType = LaunchType.Physics_Launch;
[SerializeField] private float launchSpeed = 1;
[Header("No Launch To Point")]
[SerializeField] private bool autoConfigureDistance = false;
[SerializeField] private float targetDistance = 3;
[SerializeField] private float targetFrequncy = 1;
[HideInInspector] public Vector2 grapplePoint;
[HideInInspector] public Vector2 grappleDistanceVector;
private void Start()
{
grappleRope.enabled = false;
m_springJoint2D.enabled = false;
}
private void Update() //在update函数中控制输入
{
if (Input.GetKeyDown(KeyCode.Mouse0)) //通过Input的顺序设定函数的执行顺序,先进行钩爪选取点的定位
{
SetGrapplePoint();
}
else if (Input.GetKey(KeyCode.Mouse0)) //从上一步的定位中把grapplerope启用
{
if (grappleRope.enabled)
{
RotateGun(grapplePoint, false); //进行钩锁枪的旋转
}
else
{
Vector2 mousePos = m_camera.ScreenToWorldPoint(Input.mousePosition);
RotateGun(mousePos, true);
}
if (launchToPoint && grappleRope.isGrappling) //如果选择点对点发射且正在钩中目标
{
if (launchType == LaunchType.Transform_Launch) //如果发射类型是使用Transform类型发射
{
Vector2 firePointDistnace = firePoint.position - gunHolder.localPosition;
Vector2 targetPos = grapplePoint - firePointDistnace;
gunHolder.position = Vector2.Lerp(gunHolder.position, targetPos, Time.deltaTime * launchSpeed); //采用插值的形式,模拟绳索命中的物理效果
}
}
}
else if (Input.GetKeyUp(KeyCode.Mouse0)) //若抬起左键,则将一切启用的相关布尔置否,恢复原状
{
grappleRope.enabled = false;
m_springJoint2D.enabled = false;
m_rigidbody.gravityScale = 1;
}
else //时刻获取鼠标的屏幕信息位置
{
Vector2 mousePos = m_camera.ScreenToWorldPoint(Input.mousePosition);
RotateGun(mousePos, true);
}
}
void RotateGun(Vector3 lookPoint, bool allowRotationOverTime) //实现绳索枪根据鼠标进行旋转功能
{
Vector3 distanceVector = lookPoint - gunPivot.position; //定义三维距离向量=朝向点-枪锚点位置
float angle = Mathf.Atan2(distanceVector.y, distanceVector.x) * Mathf.Rad2Deg; //定义一个角度,其值等于距离向量tan所对应的弧度值*弧度值转化为角度值的常量
if (rotateOverTime && allowRotationOverTime) //当采用根据时间延迟旋转时,采用四元数的插值旋转,在原本的旋转角和获得的绕轴的新角度中进行随时间
{
gunPivot.rotation = Quaternion.Lerp(gunPivot.rotation, Quaternion.AngleAxis(angle, Vector3.forward), Time.deltaTime * rotationSpeed);
}
else
{
gunPivot.rotation = Quaternion.AngleAxis(angle, Vector3.forward); //不采用时间插值变化时时,直接让强旋转角角度等于计算出的角度绕轴的四元数即可
}
}
void SetGrapplePoint() //设定钩取点(主要是位置的计算和注意某些添加的限定条件)
{
Vector2 distanceVector = m_camera.ScreenToWorldPoint(Input.mousePosition) - gunPivot.position; //设置一个二维向量distance用于记录鼠标点击的点和枪锚点之间的距离
if (Physics2D.Raycast(firePoint.position, distanceVector.normalized)) //发射一条射线,起始点为开火点,方向为distance的方向向量
{
RaycastHit2D _hit = Physics2D.Raycast(firePoint.position, distanceVector.normalized); //保存刚才的射线为hit
if (_hit.transform.gameObject.layer == grappableLayerNumber || grappleToAll) //选择是否选中任意的可抓取图层或是某一指定图层
{
if (Vector2.Distance(_hit.point, firePoint.position) <= maxDistnace || !hasMaxDistance) //当命中点和开火电站之间的距离小于最大距离或者不限定最大距离时
{
grapplePoint = _hit.point; //将命中点位置赋予抓取点位置
grappleDistanceVector = grapplePoint - (Vector2)gunPivot.position; //抓钩的距离向量等于钩锁点减去钩锁枪的锚点位置
grappleRope.enabled = true; //打开绳索变量
}
}
}
}
public void Grapple() //钩锁执行(真正决定移动)
{
m_springJoint2D.autoConfigureDistance = false; //设定弹簧关节组建的自动计算距离属性为假
if (!launchToPoint && !autoConfigureDistance) //当对点发射和自动计算距离均为假时,将目标距离和目标频率赋给弹簧组件的属性
{
m_springJoint2D.distance = targetDistance;
m_springJoint2D.frequency = targetFrequncy;
}
if (!launchToPoint) //如果仅为不对点发射
{
if (autoConfigureDistance) //若自动计算距离
{
m_springJoint2D.autoConfigureDistance = true;
m_springJoint2D.frequency = 0; //弹簧组件频率属性为0,该值越大,弹簧越硬
}
m_springJoint2D.connectedAnchor = grapplePoint; //不自动计算距离且不对点发射时
m_springJoint2D.enabled = true;
}
else //对点发射时,选择发射类型,有物理类发射和Transform类发射
{
switch (launchType)
{
case LaunchType.Physics_Launch:
m_springJoint2D.connectedAnchor = grapplePoint; //当使用物理发射时,将钩取点赋予弹簧的连接锚点
Vector2 distanceVector = firePoint.position - gunHolder.position; //长度变量等于开火点距离减去持枪距离
m_springJoint2D.distance = distanceVector.magnitude; //将长度变量赋给弹簧组建的距离属性,保证钩爪拉到尽头时有一定的距离
m_springJoint2D.frequency = launchSpeed; //弹簧频率(强度)等于发射速度
m_springJoint2D.enabled = true; //打开弹簧组件,进行拉伸
break;
case LaunchType.Transform_Launch:
m_rigidbody.gravityScale = 0; //当使用Transform发射时,将物体的重力设置为0
m_rigidbody.velocity = Vector2.zero; //启动钩爪时,将物体速度清零
break;
}
}
}
private void OnDrawGizmosSelected() //始终在场景中绘制可视的Gizmo,On方法
{
if (firePoint != null && hasMaxDistance)
{
Gizmos.color = Color.green;
Gizmos.DrawWireSphere(firePoint.position, maxDistnace);
}
}
}
脚本2:Grappling_Rope 添加给Rope即可
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Grappling_Rope : MonoBehaviour
{
[Header("General Refernces:")]
public Perfecter_Grapple grapplingGun;
public LineRenderer m_lineRenderer;
[Header("General Settings:")]
[SerializeField] private int percision = 40;
[Range(0, 20)] [SerializeField] private float straightenLineSpeed = 5;
[Header("Rope Animation Settings:")]
public AnimationCurve ropeAnimationCurve;
[Range(0.01f, 4)] [SerializeField] private float StartWaveSize = 2;
float waveSize = 0;
[Header("Rope Progression:")]
public AnimationCurve ropeProgressionCurve; //保存动画关键帧
[SerializeField] [Range(1, 50)] private float ropeProgressionSpeed = 1;
float moveTime = 0;
[HideInInspector] public bool isGrappling = true;
bool strightLine = true;
private void OnEnable() //在Grapple中启用该函数
{
moveTime = 0;
m_lineRenderer.positionCount = percision;
waveSize = StartWaveSize;
strightLine = false;
LinePointsToFirePoint();
m_lineRenderer.enabled = true;
}
private void OnDisable()
{
m_lineRenderer.enabled = false;
isGrappling = false;
}
private void LinePointsToFirePoint()
{
for (int i = 0; i < percision; i++)
{
m_lineRenderer.SetPosition(i, grapplingGun.firePoint.position); //绘制连接钩取点和钩锁枪位置的绳子
}
}
private void Update()
{
moveTime += Time.deltaTime;
DrawRope();
}
void DrawRope() //绘制绳索曲线
{
if (!strightLine)
{
if (m_lineRenderer.GetPosition(percision - 1).x == grapplingGun.grapplePoint.x)
{
strightLine = true;
}
else
{
DrawRopeWaves();
}
}
else
{
if (!isGrappling)
{
grapplingGun.Grapple();
isGrappling = true;
}
if (waveSize > 0)
{
waveSize -= Time.deltaTime * straightenLineSpeed;
DrawRopeWaves();
}
else
{
waveSize = 0;
if (m_lineRenderer.positionCount != 2) { m_lineRenderer.positionCount = 2; }
DrawRopeNoWaves();
}
}
}
void DrawRopeWaves() ///绘制曲线
{
for (int i = 0; i < percision; i++)
{
float delta = (float)i / ((float)percision - 1f);
Vector2 offset = Vector2.Perpendicular(grapplingGun.grappleDistanceVector).normalized * ropeAnimationCurve.Evaluate(delta) * waveSize; //perpendicular:返回垂直于该 2D 向量的 2D 向量。对于正 Y 轴向上的 2D 坐标系来说,结果始终沿逆时针方向旋转 90 度。
Vector2 targetPosition = Vector2.Lerp(grapplingGun.firePoint.position, grapplingGun.grapplePoint, delta) + offset;
Vector2 currentPosition = Vector2.Lerp(grapplingGun.firePoint.position, targetPosition, ropeProgressionCurve.Evaluate(moveTime) * ropeProgressionSpeed);
m_lineRenderer.SetPosition(i, currentPosition);
}
}
void DrawRopeNoWaves() //无曲线绘制时
{
m_lineRenderer.SetPosition(0, grapplingGun.firePoint.position);
m_lineRenderer.SetPosition(1, grapplingGun.grapplePoint);
}
}
注意:脚本赋予对象后注意赋值,在Rope中的函数曲线绘制以及添加点时一定要注意不能逾越(0,0)以及(1,1)否则会出现钩锁无法发射的问题。
代码赋值部分参数可以参照:
绳索发射曲线样式参照:
有使用问题欢迎随时私信交流