有些游戏中比如一些肉鸽游戏,击杀怪物会掉落经验或者装备。如果手动控制角色“吃”这些奖励的话,有可能会对不准,这让人感到恼火,所以我们需要奖励自动被我们吃掉。
当然这里考虑的是我们需要走到奖励一定范围内才能自动吃到奖励,否则直接击杀怪物后更新奖励的数值就好了。所以为了实现这个目标,我们可以把它分解成三个小任务来完成:
第一,判断人物走到奖励附近。
第二,奖励自动找寻目标并向目标移动。
第三,奖励触碰到人物后消失。
那么我按照这三个步骤分别展示。
这里我们的奖励是一个紫色的小立方块,人物是一个黑色的胶囊体。在奖励上面挂载上一个脚本命名为RewardMovement,并且挂载rigbody。
1.判断人物在奖励附近
有两种实现方法,一种是直接计算奖励和人物的距离,规定这个距离小于一定值时触发后面的事件。第二种是在奖励上挂载一个球形碰撞器并设置好半径,那么当人物与这个球形碰撞器发生碰撞时也就意味着人物已经到达奖励的附近可以触发后续事件。
第一种方法比较简单直接,我就只介绍第一种。下面是判断条件:
(player.transform.position - transform.position).magnitude <= 3
Vector3.magitude表示向量的模长。所以上段代码表示奖励到人物的距离,如果这个距离小于3则触发后续事件。
因为人物的位置是一直在动的,所以我们需要实时查看以上条件是否满足,满足的话触发事件,那么我们需要在生命周期函数Update里执行以上操作,我们可以按照这个思路写一下代码
//判断距离条件初代版本
private void Update()
{
if ((player.transform.position - transform.position).magnitude <= 3)
{
gameObject.GetComponent<Rigidbody>().useGravity = false;
MoveToPlayer();
}
}
这里加了一个满足条件时奖励的重力为零,因为我想要达到的效果是奖励飘着朝人物移动。如果保留重力的话,奖励会飘不起来。MoveToPlayer是奖励朝人物移动的函数,后面会说到。
这个版本可以实现人物在奖励附近的时候自动吸引奖励,但是当人物离开奖励范围的时候,奖励就会停止自动追踪目标,这不是我想要的效果。我需要的效果是,一旦人物走进奖励的启动范围,奖励就会一直追踪人物直到奖励被吸收掉,所以我们需要改进一下。
public bool isCalled = false;
private void Update()
{
if ((player.transform.position - transform.position).magnitude <= judgeRange && !isCalled)
{
gameObject.GetComponent<Rigidbody>().useGravity = false;
StartCoroutine(MoveToPlayer());
isCalled = true;
}
}
这里我们把奖励运动的代码打包放到一个协程函数MoveToPlayer里,这里声明了一个布尔变量isCalled目的是保证MoveToPlayer只会被调用一次。原理很简单,在判断条件里加入条件!isCalled,也就是说在函数没有被调用的情况下才能调用,调用过MoveToPlayer后将isCalled设置为true,这样MoveToPlayer就不会再被调用了。当我们遇到只想执行一次函数的情况时也可以用类似的方法。
2.奖励自动找寻目标并向目标移动
实现这个很简单,我们只需知道奖励到人物的方向,并向那个方向移动即可。移动方式有两种,第一种是更新位置:
IEnumerator MoveToPlayer()
{
while (true)
{
yield return new WaitForSeconds(0.01f);
if (moveSpeed <= 80)
{
moveSpeed += 0.05f;
}
Vector3 newPos = transform.position + (player.transform.position - transform.position).normalized * moveSpeed * 0.01f;
transform.position = newPos;
}
}
这里做了一个加速度处理(视觉效果好看,并且保证奖励可以追上人物),给了一个最大速度。
第二种移动方式velocity:
IEnumerator MoveToPlayer()
{
while (true)
{
yield return new WaitForSeconds(0.01f);
if (moveSpeed <= 80)
{
moveSpeed += 0.05f;
}
rig.velocity = (player.transform.position - transform.position).normalized * moveSpeed;
}
}
第三中移动方式是addforce
IEnumerator MoveToPlayer()
{
while (true)
{
yield return new WaitForSeconds(0.01f);
if (moveSpeed <= 80)
{
moveSpeed += 0.05f;
}
Vector3 direction = (player.transform.position - transform.position).normalized * moveSpeed;
rig.AddForce(direction);
}
}
通过对比三种移动方式,我最后选择第二种。第一种和第二种移动方式类似,第二种更丝滑。第三种通过力的方式移动不适合这种情况,原因是力的方向不能及时改变速度方向,也就是说力的方向朝向人物,但是由于惯性,物体运动方向是合速度方向与力的方向不一致,往往做向心运动。
3.奖励触碰到人物后消失
这部分代码很简单,只需要用到生命周期函数OntriggerEnter即可,逻辑就是奖励接触人物时执行删除操作。
private void OnTriggerEnter(Collider other)
{
print(other.gameObject.name);
Destroy(gameObject);
}
4.完整代码
using System.Collections;
using UnityEngine;
public class RewardMovement : MonoBehaviour
{
Rigidbody rig;
float moveSpeed = 4;
GameObject player;
bool isCalled = false;
public float judgeRange;
private void Start()
{
rig = GetComponent<Rigidbody>();
player = GameObject.Find("player");
}
private void Update()
{
if ((player.transform.position - transform.position).magnitude <= judgeRange && !isCalled)
{
gameObject.GetComponent<Rigidbody>().useGravity = false;
StartCoroutine(MoveToPlayer());
isCalled = true;
}
}
private void OnTriggerEnter(Collider other)
{
print(other.gameObject.name);
Destroy(gameObject);
}
IEnumerator MoveToPlayer()
{
while (true)
{
yield return new WaitForSeconds(0.01f);
if (moveSpeed <= 80)
{
moveSpeed += 0.05f;
}
rig.velocity = (player.transform.position - transform.position).normalized * moveSpeed;
}
}