unity实现回旋镖函数
最近学习unity2D,想实现一个回旋镖武器,发出后就可以在角色周围回旋。
一、目标
1.不是一次性的,扔出去、返回、没有了;而是扔出去,返回到角色后方相同距离,再次返回;再次返回,永远不停止。
2.销毁的时机,是这个武器与角色再次重合的时候,相当于回收回旋镖;如果不回收(例如跳跃躲开),那就一直转。
3.角色的位置会移动,回旋镖得多次判断下一个目标点的位置。
4.回旋镖的速度,需要从最大速度开始,到接近目标点的时候逐渐减速;返回时也要从最大速度开始,到接近目标点的时候减速。
总的来说,就是类似《忍者龙剑传3》里,那个十字回旋镖武器的效果,如果跳跃躲开,就会一直在身边转。
二、问题
实际写代码实现的时候,才发现有一堆坑。
1.回旋镖的坐标,不能完全到达目标点,因为unity是按帧算的,每帧移动多少速度的话,可能会超过目标点,或者没有到达目标点,有误差。
2.如果只以接近目标点判断的话,会丢失距离,每次回旋的距离逐渐变近,最后停留原地不动了。
3.如果角色坐标移动,回旋镖计算目标点并重新移动时,经常速度变为0,卡在原地不动(不确定哪里出bug了)。
三、代码部分
//这部分代码,在武器代码cs部分
float weaponRange = 3;
float weaponSpeed = 15;
//当前已飞行的距离
float distanceTravelled = 0f;
//当前武器位置到目标点的总距离
float distanceToTarget;
//目标点在哪边
bool targetIsRight = false;
[SerializeField] Transform weaponPos;
Func<Vector2> weaponPosFunc;
Vector2 targetPos;
Vector2 nowSpeed;
Animator animator;
Rigidbody2D rb2d;
SpriteRenderer sprite;
CircleCollider2D cc2d;
void Awake()
{
animator = GetComponent<Animator>();
cc2d = GetComponent<CircleCollider2D>();
rb2d = GetComponent<Rigidbody2D>();
sprite = GetComponent<SpriteRenderer>();
}
//发射武器方法,其他类调用
public void Shoot(bool isFaceingRight) {
Vector2 newTarget;
//其他类传来的,一开始武器起点坐标
Vector2 v = weaponPosFunc();
//bool类型,武器图片,如果角色面朝右边就是true,就翻转图片,否则不翻转
sprite.flipX = isFaceingRight;
//bool类型,武器的目标点方向,后面会用
targetIsRight = isFaceingRight;
//赋值,刚开始武器到目标点的距离
distanceToTarget = Vector2.Distance(weaponPos.position, targetPos);
//首次武器目标点,直线,看是角色面前还是背后
//targetPos就是武器目标点
if (!isFaceingRight) {
newTarget = new Vector2(v.x - weaponRange, v.y);
targetPos = newTarget;
}
else
{
newTarget = new Vector2(v.x + weaponRange, v.y);
targetPos = newTarget;
}
}
//更新方法,系统会每帧调用
void Update()
{
// 向目标位置飞行,并逐步减速,到达目标点要掉头,反复重复
MoveTowardsTarget();
}
void MoveTowardsTarget()
{
//已经走过的距离/物体到目标的距离
float dPercent = distanceTravelled / distanceToTarget;
if(dPercent < 0){
dPercent = -dPercent;
}
if(dPercent > 1){
dPercent = 1;
}
// 计算当前的速度因子,使物体从快到慢;weaponSpeed是初始武器速度
//Lerp方法,就是dPercent为0时,返回第一个参数;为1时返回第二个参数;0-1之间时就返回第一个参数到第二个参数之间的值
float speedFactor = Mathf.Lerp(weaponSpeed, 0f, dPercent);
//确定一个最慢速度,否则速度因子接近0,越来越慢,很难到目标
if(speedFactor < 2f)
{
speedFactor = 2f;
}
// 计算朝目标位置的方向,方向向量
Vector2 direction = (targetPos - (Vector2)weaponPos.position).normalized;
//如果方向向量变为0,就没办法移动了,此时要指定一个默认方向向量
if(direction.x == 0 && direction.y == 0)
{
//如果目标点在右边,方向向量就是右边;否则左边
if (targetIsRight)
{
direction = new Vector2(1,0);
}
else
{
direction = new Vector2(-1, 0);
}
}
//设置物体的速度,方向向量*速度因子
//设置了这个,武器就会开始移动
rb2d.velocity = direction*speedFactor;
//保存当前速度,后续可能用
nowSpeed = direction * speedFactor;
// 更新已飞行的距离,刚开始是0;每一帧移动速度*每一帧时间=路程,然后累加
distanceTravelled += rb2d.velocity.magnitude * Time.deltaTime;
//打印日志,武器到目标点的距离
//Debug.Log("distanceToTarget"+ distanceToTarget);
// 如果 (超过目标点 或者 接近目标点) 并且 至少走够了武器路程
//需要分左右,才能知道是没到目标点还是超过目标点,需要超过目标点;是否超过目标点就先用x判断了;y又复杂了,还得分上下
//防止没到目标点就卡住,又加了接近目标点
if (targetIsRight)
{
if ( ((targetPos.x <= weaponPos.position.x) || distanceToTarget<=0.1) && distanceTravelled >= weaponRange)
{
//重置目标点
FreshTargetPos();
}
}
else
{
if ( ((targetPos.x >= weaponPos.position.x) || distanceToTarget<=0.1) && distanceTravelled >= weaponRange)
{
//重置目标点
FreshTargetPos();
}
}
}
void FreshTargetPos() {
//玩家当前坐标,实时获取(玩家的武器发射点的坐标)
Vector2 p = weaponPosFunc();
//武器当前坐标
Vector2 w = weaponPos.position;
//w点关于p点的对称点
//targetPos = new Vector2( 2*p.x -w.x , 2*p.y -w.y );
// 计算朝目标位置的方向,方向向量,扩大武器范围倍数
Vector2 direction = (p - (Vector2)w).normalized * weaponRange;
//算出下一个目标点
targetPos = new Vector2(direction.x+p.x, direction.y+p.y);
//看目标点到底在哪里,然后判断有没有超过目标点
if (targetPos.x > weaponPos.position.x)
{
targetIsRight = true;
}
else
{
targetIsRight = false;
}
//已走过的路程归零
distanceTravelled = 0;
// 重新计算物体到目标的距离,weaponPos是当前武器坐标,targetPos是目标点坐标;得到的是距离
distanceToTarget = Vector2.Distance(weaponPos.position, targetPos);
}
四、备注
unity代码比较多,就贴重要部分了,关于坐标的计算;
目前代码就是这个版本了,大概实现了需求;
武器当前速度处理还可能有些问题,但是可以在玩家周围回旋了;
如果玩家静止或者直线移动,武器就在x轴上回旋;
如果玩家跳跃,武器就会斜方向回旋到玩家背后;
感觉情况有些复杂,可能哪里还有bug,欢迎留言指正。
五、效果图片
如图,可以在角色周围水平回旋,斜方向也可以。