效果
https://www.bilibili.com/video/BV13r4y1q7vE
实现思路
钓鱼的核心玩法是:
甩钩,等待鱼咬钩后,再给予鱼线适当的力度把鱼给拉上来。
鱼的拉力用随机值模拟。
人的拉力则用按键来控制。如果不增加拉力,人对鱼竿的拉力会随着时间减小至0。按下L键时,就会在原拉力的基础上再给鱼竿增加一定的拉力。
如果人与鱼的拉力受力平衡时,可以看作是合适的拉力。
在合适的拉力范围内,积累一定的时间,即可将鱼钓上来。
鱼竿脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;
public class Fishing : MonoBehaviour
{
/*--------------核心玩法----------------*/
public float lineTension = 0.5f;
public float deviation = 0.1f; //偏差范围
public float resultantForce = 0; //合力
public float fishForce = 0;
public float fishingRodForce = 0;
public float fishingRodForceDeclinePerSec = 0.15f; //人的拉力每秒减少量
public float targetAccumulationTime = 5f;
public float currentAccumulationTime = 0;
public float accumulationRate = 0; //积累进度
/*--------------UI----------------*/
public GameObject canvas;
public GameObject TensionPanel;
public Image curTensionBar; //力度条
public Image acRateBar; //时间条
public Text status; //状态提示文本
public FishingStatus fishingStatus = FishingStatus.notAtFishing;
public enum FishingStatus
{
wait,
hit,
caught,
notAtFishing,
}
void Update()
{
AdjustLength();
if (fishingStatus == FishingStatus.caught)
MoveWithHook();
if (fishingStatus == FishingStatus.hit)
{
AdjustTension();
}
}
void AdjustTension()
{
fishForce = Mathf.Lerp(fishForce, Random.Range(0, 1f), 0.2f);
float fishingRodForceIncrease = (Input.GetKeyDown(KeyCode.L) ? 1 : 0) * 0.05f;
float fishingRodForceDecrease = fishingRodForce - fishingRodForceDeclinePerSec * Time.deltaTime >= 0 ? fishingRodForceDeclinePerSec * Time.deltaTime : 0;
fishingRodForce = fishingRodForce + fishingRodForceIncrease - fishingRodForceDecrease;
fishingRodForce = Mathf.Clamp(fishingRodForce, 0, 1f);
resultantForce = fishingRodForce - fishForce;
lineTension = resultantForce + 0.5f;
lineTension = Mathf.Clamp(lineTension, 0, 1f);
if (lineTension <= (0.5f + deviation) && lineTension >= (0.5f - deviation))
{
currentAccumulationTime += Time.deltaTime;
}
else
{
currentAccumulationTime -= Time.deltaTime;
}
currentAccumulationTime = Mathf.Clamp(currentAccumulationTime, 0, targetAccumulationTime);
accumulationRate = currentAccumulationTime / targetAccumulationTime;
curTensionBar.fillAmount = lineTension;
acRateBar.fillAmount = accumulationRate;
if(accumulationRate == 1)
{
OnCaught();
}
}
void MoveWithHook()
{
if (fish)
fish.transform.position = transform.position;
}
IEnumerator CloseFishingUI() //延迟关闭UI
{
float timer = 0;
while (true)
{
timer += Time.deltaTime;
yield return null;
if (timer > 5)
{
canvas.gameObject.SetActive(false);
StopCoroutine("CloseFishingUI");
}
}
}
private GameObject fish;
private void OnTriggerEnter(Collider other) //将钩子作为触发器,则必须将该脚本挂在钩子上
{
if (other.gameObject.name == "fish")
{
fish = other.gameObject;
if(fish.GetComponent<Fish>().hookStatus == Fish.HookStatus.focusOnHook)
OnEatHook();
}
}
public GameObject fishingLine;
public float fishingLineLength = 1;
void AdjustLength()
{
if(Input.GetMouseButton(0))
fishingLineLength += Input.GetAxis("Mouse ScrollWheel");
fishingLine.transform.localScale = new Vector3(fishingLine.transform.localScale.x,
fishingLine.transform.localScale.y, Mathf.Clamp(fishingLineLength, 1f,7f));
}
public void OnUnhook()
{
status.text = "鱼脱钩了";
fishingStatus = FishingStatus.notAtFishing;
TensionPanel.gameObject.SetActive(false);
StartCoroutine("CloseFishingUI");
}
public void OnCaught()
{
status.text = "钓上来了";
fishingStatus = FishingStatus.caught;
fish.GetComponent<Fish>().hookStatus = Fish.HookStatus.caught;
TensionPanel.gameObject.SetActive(false);
StartCoroutine("CloseFishingUI");
}
public void OnEatHook()
{
status.text = "鱼吃钩了..";
fishingStatus = FishingStatus.hit;
canvas.gameObject.SetActive(true);
TensionPanel.gameObject.SetActive(true);
}
public void OnSwingHook()
{
status.text = "静静等待";
fishingStatus = FishingStatus.wait;
canvas.gameObject.SetActive(true);
TensionPanel.gameObject.SetActive(false);
StartCoroutine("SwingHookAnimation");
}
public GameObject fishingRod;
IEnumerator SwingHookAnimation() //延迟甩钩动画
{
float timer = 0;
while(true)
{
timer += Time.deltaTime;
yield return null;
if(timer >= 0.5f && timer <=1f)
{
fishingRod.transform.DOLocalRotate(new Vector3(-60,0,90),0.5f);
}
if (timer >= 1f && timer <= 1.5f)
{
fishingRod.transform.DOLocalRotate(new Vector3(0,0,90),0.5f);
}
if (timer > 1.5f)
StopCoroutine("SwingHookAnimation");
}
}
}
鱼的脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
public class Fish : MonoBehaviour
{
public GameObject hook; //鱼钩
public float eatHookDistance = 10f; //吃钩距离
public HookStatus hookStatus = HookStatus.normal;
public enum HookStatus
{
normal,
focusOnHook,
caught,
}
void Update()
{
if (Input.GetKeyDown(KeyCode.K) && (hook.GetComponent<Fishing>().fishingStatus == Fishing.FishingStatus.notAtFishing
|| hook.GetComponent<Fishing>().fishingStatus == Fishing.FishingStatus.wait))
{
hook.GetComponent<Fishing>().OnSwingHook();
StartCoroutine("FindFood");
}
if(hookStatus == HookStatus.focusOnHook)
ChaseHook();
}
IEnumerator FindFood() //延迟吃钩
{
float timer = 0;
while (true)
{
timer += Time.deltaTime;
yield return null;
if (timer > 10)
{
//注意到钩子
hookStatus = HookStatus.focusOnHook;
GetComponent<DOTweenPath>().DOPause();
fish_y = transform.position.y;
lastHookPosition = new Vector3(hook.transform.position.x, fish_y, hook.transform.position.z);
StopCoroutine("FindFood");
}
}
}
public Vector3 lastHookPosition;
private float fish_y; //鱼受到浮力作用,纵坐标是时刻变化的
void ChaseHook()
{
//随意移动鱼钩位置,鱼跑了
if (lastHookPosition != new Vector3(hook.transform.position.x, fish_y, hook.transform.position.z) && lastHookPosition != Vector3.zero)
{
hookStatus = HookStatus.normal;
lastHookPosition = Vector3.zero;
GetComponent<DOTweenPath>().DORestart(); //改变了位置之后不可继续播放,只能重新播放
hook.GetComponent<Fishing>().OnUnhook();
return;
}
if (Vector3.Distance(hook.transform.position, transform.position) < eatHookDistance) //向钩子移动
{
if (Vector3.Distance(hook.transform.position, transform.position) > 0.5f)
transform.position = Vector3.Lerp(transform.position, lastHookPosition,0.05f);
transform.LookAt(hook.transform);
}
}
}