using asap.core; using cfg; using DG.Tweening; using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; using Unity.Mathematics; using UnityEngine; using UnityEngine.AddressableAssets; public class FishMovement : AquariumFishBase { [SerializeField] private Rigidbody rb; private Vector3 Orientation => transform.rotation * Vector3.forward; public int index = 0, groupId = -1; public Rigidbody Rb => rb; FishManager fishManager; GameObject goPrefab; float startSizeZ = 0; float avoidRadius = 0; float maxPitchAngle = 30f; GameObject leftEye; GameObject rightEye; public float AvoidRadius => avoidRadius * transform.localScale.x; public Vector3 targetPos; public Transform fishPos; GameObject fishUnDownloadAlter; public FishData fishData; public Fish fish; List colliders = new List(); //public int triggerCount; public bool isResult; public float mutantScale; float loadTime; float startLoadTime; public void Init(FishManager fishManager, AquariumSlotData slotData, float startSizeZ, FishData fishData, float mutantScale) { this.mutantScale = mutantScale; this.slotData = slotData; this.fishData = fishData; this.fishManager = fishManager; this.startSizeZ = startSizeZ; rb.velocity = rb.rotation * Vector3.forward; LoadFishUnDownloadAlter(fishData.Quality - 1); startLoadTime = Time.time; } public void RemoveEye() { if (leftEye != null) { DestroyImmediate(leftEye); } if (rightEye != null) { DestroyImmediate(rightEye); } } void LoadFishUnDownloadAlter(int quality) { if (fishManager.fishUnDownloadAlter != null && fishManager.fishUnDownloadAlter.ContainsKey(quality)) { fishUnDownloadAlter = Instantiate(fishManager.fishUnDownloadAlter[quality], transform); fish = fishUnDownloadAlter.GetComponent(); fish.animator.transform.localPosition = Vector3.zero; fish.EnableCollider(); avoidRadius = fish.GetColliderSize(fishManager.avoidRadius); fishUnDownloadAlter.transform.localPosition = Vector3.zero; fishUnDownloadAlter.transform.localEulerAngles = Vector3.up * 180; fishUnDownloadAlter.transform.localScale = Vector3.one; fishUnDownloadAlter.name = $"{fishUnDownloadAlter.name}_{index}"; } } public async Task LoadFish(Material mutantMaterial, GameObject fxLeftEye, GameObject fxRightEye) { goPrefab = await Addressables.InstantiateAsync(fishData.Fbx, transform).Task; loadTime = Time.time - startLoadTime; if (rb == null || isResult || loadTime > fishManager.MaxLoadTime) { Debug.LogWarning($"LoadFish TimeOut:{loadTime} {fishData.Fbx}"); Addressables.ReleaseInstance(goPrefab); goPrefab = null; } else if (goPrefab != null) { //Destroy(fishUnDownloadAlter); //fishUnDownloadAlter = null; if (fishUnDownloadAlter != null) { fishUnDownloadAlter.SetActive(false); } fish = goPrefab.GetComponent(); fish.animator.transform.localPosition = Vector3.zero; fish.EnableCollider(); fish.animator.Play("Swim01", 0, UnityEngine.Random.Range(0, 0.8f)); avoidRadius = fish.GetColliderSize(fishManager.avoidRadius); goPrefab.transform.localPosition = Vector3.zero; goPrefab.transform.localEulerAngles = Vector3.up * 180; goPrefab.transform.localScale = Vector3.one; goPrefab.name = $"{fishData.Fbx}_{index}"; if (slotData.mutants <= index) { return; } var _tables = GContext.container.Resolve(); float eyeScale = _tables.TbFishMutant.FxAquariumScale[fishData.Quality - 1]; if (fish.LeftEye != null && fxLeftEye != null) { leftEye = Instantiate(fxLeftEye); leftEye.transform.localScale = Vector3.one * eyeScale; leftEye.transform.parent = fish.LeftEye; leftEye.transform.localPosition = Vector3.zero; leftEye.transform.localRotation = Quaternion.identity; } if (fish.RightEye != null && fxRightEye != null) { rightEye = Instantiate(fxRightEye); rightEye.transform.localScale = Vector3.one * eyeScale; rightEye.transform.parent = fish.RightEye; rightEye.transform.localPosition = Vector3.zero; rightEye.transform.localRotation = Quaternion.identity; } Renderer[] meshRenderer = goPrefab.GetComponentsInChildren(); if (meshRenderer != null && meshRenderer.Length > 0) { var render = meshRenderer[0]; var materials = render.materials; for (int i = 0; i < materials.Length; i++) { var material = materials[i]; var material_P = new Material(mutantMaterial); material_P.SetTexture("_BaseMap", material.GetTexture("_BaseMap")); material_P.SetTexture("_BumpMap", material.GetTexture("_BumpMap")); materials[i] = material_P; } render.materials = materials; } } } public void OnShoot(bool isRob, float speed, float time, Vector3 reasonableCenter, Vector3 reasonableLocation, float reasonableDistance) { rb.isKinematic = true; isResult = true; var pos = transform.position; var startPos = Vector3.zero; startPos.z = UnityEngine.Random.Range(-reasonableLocation.z, reasonableLocation.z); if (pos.x > fishManager.tankCenter.x) { startPos.x = UnityEngine.Random.Range(0, reasonableLocation.x); } else { startPos.x = UnityEngine.Random.Range(-reasonableLocation.x, 0); } if (pos.y > fishManager.tankCenter.y) { startPos.y = UnityEngine.Random.Range(0, reasonableLocation.y); } else { startPos.y = UnityEngine.Random.Range(-reasonableLocation.y, 0); } transform.position = startPos + reasonableCenter; targetPos = startPos + reasonableCenter + rb.rotation * Vector3.forward * reasonableDistance; float l = speed * time; float t = l / (targetPos - transform.position).magnitude; if (t < 1) { targetPos = Vector3.Lerp(transform.position, targetPos, t); } //转弯 //Quaternion rotation1 = Quaternion.LookRotation(targetPos - transform.position); //transform.DORotateQuaternion(rotation1, time * 0.8f); // Move the fish along the path transform.DOMove(targetPos, time).SetEase(Ease.Linear); SetTargetPos(isRob); } void SetTargetPos(bool isRob) { fishPos = fish.mouthPos; if (!isRob) { int i = UnityEngine.Random.Range(0, 3); if (i == 0) { fishPos = fish.Fin; } else if (i == 1) { fishPos = fish.Neck; } else if (i == 2) { fishPos = fish.Tail; } } if (fishPos != null) { targetPos += fishPos.position - transform.position; } } public void AllEscape() { if (!isResult) { rb.isKinematic = true; isResult = true; var ta = transform.position + rb.rotation * Vector3.forward * 50; transform.DOMove(ta, 3).OnComplete(() => fish.gameObject.SetActive(false)).SetEase(Ease.Linear); } } public void SetFishSpeed(float speed, float time) { if (fish != null) { float curSpeed = speed; DOTween.To(() => speed, x => curSpeed = x, 1f, time).OnUpdate(() => { fish.FishAnimSpeed = curSpeed; }); } } public void Shock(float distance, float time, AnimationCurve shockCurve) { transform.DOMoveZ(transform.position.z + distance, time).SetEase(shockCurve); } public void Capture(float minSpeed, float maxSpeed, float addRotation, float addRotationPeriod) { fish.FishAnimSpeed = UnityEngine.Random.Range(minSpeed, maxSpeed); //鱼的朝向y轴 增加 rotation float add = UnityEngine.Random.Range(-addRotation, addRotation); Quaternion rotation = Quaternion.AngleAxis(add, Vector3.up); rb.rotation = rb.rotation * rotation; //rb.transform.forward; StartCoroutine(AddRotation(addRotation, addRotationPeriod)); } IEnumerator AddRotation(float addRotation, float addRotationPeriod) { Quaternion startRotation = rb.rotation; while (enabled) { float add = UnityEngine.Random.Range(-addRotation, addRotation); Quaternion rotation = Quaternion.AngleAxis(add, Vector3.up); //rb.rotation = startRotation * rotation; //rb.transform.forward; transform.DORotateQuaternion(startRotation * rotation, addRotationPeriod); yield return new WaitForSeconds(addRotationPeriod); } } public void Escape(float distance, float duration) { transform.DOKill(); rb.isKinematic = true; isResult = true; var ta = transform.position + rb.rotation * Vector3.forward * distance; transform.DOMove(ta, duration).OnComplete(() => fish.gameObject.SetActive(false)).SetEase(Ease.InSine); } public void OnShooted(bool isRob) { if (fish != null) { fish.animator.SetInteger("HurtIndex", isRob ? 2 : 1); fish.animator.SetTrigger("Hurt"); } rb.isKinematic = true; isResult = true; } private void OnTriggerEnter(Collider other) { colliders.Add(other); } private void OnTriggerExit(Collider other) { colliders.Remove(other); } private Vector3 _dv = Vector3.zero; private Vector3 _lastDv = Vector3.zero; private void FixedUpdate() { if (fishManager == null || isResult) { return; } var s = fishManager.tankSize / 2; float offsetX = fishManager.tankCenter.x; float offsetY = fishManager.tankCenter.y; float offsetZ = fishManager.tankCenter.z + startSizeZ; //区域外 _dv = Vector3.zero; // Area Constraint if (rb.position.x > s.x + offsetX) { _dv += Vector3.left * (rb.position.x - s.x - offsetX); } else if (rb.position.x < -s.x + offsetX) { _dv += Vector3.right * (-s.x + offsetX - rb.position.x); } if (rb.position.y > s.y + offsetY) { _dv += Vector3.down * (rb.position.y - s.y - offsetY); } else if (rb.position.y < -s.y + offsetY) { _dv += Vector3.up * (-s.y + offsetY - rb.position.y); } if (rb.position.z > s.z + offsetZ) { _dv += Vector3.back * (rb.position.z - s.z - offsetZ); } else if (rb.position.z < -s.z + offsetZ) { _dv += Vector3.forward * (-s.z + offsetZ - rb.position.z); } _dv *= fishManager.areaConstrain; // Boids Constraint var colliderCount = fishManager.allFish.Count; // var flockMates = new List(); var alignVelocity = Vector3.zero; var avoidPosition = Vector3.zero; var coherePosition = Vector3.zero; int alignBoidCount = 0, avoidBoidCount = 0, cohereBoidCount = 0; for (int i = colliders.Count - 1; i >= 0; i--) { if (colliders[i] == null) { colliders.RemoveAt(i); continue; } Transform obstacleTransform = colliders[i].transform; var fromToVector = obstacleTransform.position - transform.position; var distance = fromToVector.magnitude; _dv -= fromToVector.normalized / distance * fishManager.trigger; } for (int i = 0; i < fishManager.obstacleGo.Count; i++) { Transform obstacleTransform = fishManager.obstacleGo[i].transform; Vector3 localScale = obstacleTransform.localScale / 2; Vector3 localPos = obstacleTransform.position; if (transform.position.x < localPos.x - localScale.x) { localPos.x -= localScale.x; } else if (transform.position.x > localPos.x + localScale.x) { localPos.x += localScale.x; } if (transform.position.y < localPos.y - localScale.y) { localPos.y -= localScale.y; } else if (transform.position.y > localPos.y + localScale.y) { localPos.y += localScale.y; } if (transform.position.z < localPos.z - localScale.z) { localPos.z -= localScale.z; } else if (transform.position.z > localPos.z + localScale.z) { localPos.z += localScale.z; } var fromToVector = localPos - transform.position; var distance = fromToVector.magnitude; // Obstacle has a higher weight than other boids if (distance <= fishManager.obstacleDetectionRadius) { _dv -= fromToVector.normalized / distance * fishManager.obstacle; } } for (int i = 0; i < colliderCount; i++) { var flockMate = fishManager.allFish[i]; var fromToVector = fishManager.allFish[i].transform.position - transform.position; var distance = fromToVector.magnitude; if (fishManager.allFish[i].gameObject.name == gameObject.name || flockMate is null) continue; if (flockMate.groupId == groupId && distance <= fishManager.alignRadius) { alignBoidCount++; alignVelocity += flockMate.Rb.velocity; } if (flockMate.fishData.Quality >= fishData.Quality && distance <= (AvoidRadius + flockMate.AvoidRadius)) // && ((triggerCount > 0 && flockMate.groupId == groupId) || flockMate.groupId != groupId) //) { avoidBoidCount++; avoidPosition += flockMate.Rb.position; } if (flockMate.groupId == groupId) { cohereBoidCount++; coherePosition += flockMate.Rb.position; } } if (avoidBoidCount > 0) _dv += (rb.position - avoidPosition / avoidBoidCount) * fishManager.avoidance; if (alignBoidCount > 0) _dv += (alignVelocity / alignBoidCount - rb.velocity) * fishManager.alignment; if (cohereBoidCount > 0) _dv += (coherePosition / cohereBoidCount - rb.position) * fishManager.cohesion; _dv = Vector3.Lerp(_lastDv, _dv, math.saturate(fishManager.dvLerp)); _lastDv = _dv; var targetVelocity = rb.velocity + _dv * Time.fixedDeltaTime; var newVelocity = Vector3.RotateTowards(rb.transform.forward * rb.velocity.magnitude, targetVelocity, fishManager.angularAcc, fishManager.magnitudeAcc); //仰角限制 float maxPitchAngle = 30f; float maxY = Mathf.Sin(maxPitchAngle * Mathf.Deg2Rad) * newVelocity.magnitude; newVelocity.y = Mathf.Clamp(newVelocity.y, -maxY, maxY); if (newVelocity.magnitude < 0.001) { return; } rb.rotation = Quaternion.LookRotation(newVelocity); newVelocity = Vector3.RotateTowards(newVelocity, targetVelocity, fishManager.angularAcc, fishManager.magnitudeAcc); maxY = Mathf.Sin(maxPitchAngle * Mathf.Deg2Rad) * newVelocity.magnitude; newVelocity.y = Mathf.Clamp(newVelocity.y, -maxY, maxY); float angle = Vector3.Angle(rb.velocity, targetVelocity); newVelocity *= 1 - (angle / 185); rb.velocity = newVelocity; //Max-Min Constraint if (rb.velocity.magnitude > fishManager.maxVel) rb.velocity = rb.velocity.normalized * fishManager.maxVel; else if (rb.velocity.magnitude < fishManager.minVel) rb.velocity = rb.velocity.normalized * fishManager.minVel; } private void OnDestroy() { if (goPrefab != null) { Addressables.ReleaseInstance(goPrefab); goPrefab = null; } } private void OnDrawGizmosSelected() { // Display the explosion radius when selected Gizmos.color = Color.red; Gizmos.DrawWireSphere(transform.position, AvoidRadius); Gizmos.color = Color.blue; Gizmos.DrawLine(transform.position, transform.position + _dv * (5 * Time.fixedDeltaTime)); } }