_xiaofang/xiaofang/Assets/Obi/Samples/RopeAndRod/SampleResources/Scripts/TangledRopesGameController.cs
杨号敬 bcc74f0465 add
2024-12-18 02:18:45 +08:00

137 lines
4.5 KiB
C#

using UnityEngine;
using UnityEngine.Events;
using Obi;
[RequireComponent(typeof(ObiSolver))]
public class TangledRopesGameController : MonoBehaviour
{
public TangledPegSlot[] pegSlots;
public float pegHoverHeight = 1;
public float maxPegDistanceFromSlot = 1.5f;
public int framesWithoutContactsToWin = 30;
public UnityEvent onFinish = new UnityEvent();
private TangledPeg selectedPeg;
private Plane floor = new Plane(Vector3.up, 0);
private int framesSinceLastContact = 0;
void OnEnable()
{
GetComponent<ObiSolver>().OnParticleCollision += Solver_OnParticleCollision;
}
private void OnDisable()
{
GetComponent<ObiSolver>().OnParticleCollision -= Solver_OnParticleCollision;
}
private TangledPegSlot FindCandidateSlot(TangledPeg peg)
{
// Go over all slots, find the closest one to the peg that's closer than
// maxPegDistanceFromSlot:
TangledPegSlot closest = null;
float closestDistance = float.MaxValue;
foreach (TangledPegSlot slot in pegSlots)
{
// reset slot color, to make sure it looks normal if it's not a candidate for our peg.
slot.ResetColor();
// ignore occupied slots:
if (slot.currentPeg != null)
continue;
Vector3 slotOnFloor = floor.ClosestPointOnPlane(slot.transform.position);
Vector3 pegOnFloor = floor.ClosestPointOnPlane(peg.transform.position);
float distance = Vector3.Distance(slotOnFloor, pegOnFloor);
if (distance < closestDistance && distance < maxPegDistanceFromSlot)
{
closest = slot;
closestDistance = distance;
}
}
return closest;
}
private void Update()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// User clicks, cast a ray towards the floor, see if it hits any peg.
if (Input.GetMouseButtonDown(0))
{
if (Physics.Raycast(ray, out RaycastHit hit))
{
// if the ray hit a peg, store it as the selected peg and lift it off from its current slot.
if (hit.transform.TryGetComponent(out TangledPeg peg) && peg.currentSlot != null)
{
selectedPeg = peg;
selectedPeg.UndockFromCurrentSlot();
}
}
}
if (selectedPeg != null)
{
// Make selected peg follow the mouse cursor:
if (floor.Raycast(ray, out float enter))
selectedPeg.MoveTowards(ray.GetPoint(enter) + Vector3.up * pegHoverHeight);
// Try to find a suitable slot to drop the peg:
TangledPegSlot closest = FindCandidateSlot(selectedPeg);
// If we found a candidate slot, tint it to give the player some visual feedback:
if (closest != null)
closest.Tint();
// Drop selected peg:
if (Input.GetMouseButtonUp(0))
{
// If we could find a free slot nearby, connect to it:
if (closest != null)
{
selectedPeg.currentSlot = null;
selectedPeg.DockInSlot(closest);
closest.ResetColor();
}
else // If we couldn't find one or if it's too far, return to current slot.
{
selectedPeg.DockInSlot(selectedPeg.currentSlot);
}
selectedPeg = null;
}
}
// If all ropes have been contact-free for a certain amount of frames, trigger finish event.
if (framesSinceLastContact >= framesWithoutContactsToWin && onFinish != null)
onFinish.Invoke();
}
private void Solver_OnParticleCollision(ObiSolver s, ObiNativeContactList e)
{
// Count contacts between different ropes (that is, exclude self-contacts):
int contactsBetweenRopes = 0;
for (int i = 0; i < e.count; ++i)
{
var ropeA = s.particleToActor[s.simplices[e[i].bodyA]].actor;
var ropeB = s.particleToActor[s.simplices[e[i].bodyB]].actor;
if (ropeA != ropeB)
contactsBetweenRopes++;
}
// If there's no contacts, bump the amount of frames we've been contact-free.
// Otherwise reset the amount of frames to zero.
if (contactsBetweenRopes == 0)
framesSinceLastContact++;
else
framesSinceLastContact = 0;
}
}