Skip to content

Commit

Permalink
Added Comments
Browse files Browse the repository at this point in the history
  • Loading branch information
chungchunwang committed Dec 27, 2023
1 parent 12646ec commit ea23243
Show file tree
Hide file tree
Showing 4 changed files with 372 additions and 342 deletions.
225 changes: 122 additions & 103 deletions project/Assets/Sword/Sliceable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,121 +154,140 @@ private void OnEnable()
{
sliced = false;
}
public void slice(Vector3 enterHandle, Vector3 enterTip, Vector3 exitTip, Vector3[] pastPositions, Func<Task<Vector3[]>> getPositionsFromNow, Vector3[] collisionEnterPoints, Vector3[] collisionExitPoints, string otherTag){
if (sliced) return;
sliced = true;
sfxSystem.playSliceAudio();
//Maintain world space copies of variables.
Vector3 worldSpaceEnterHandle = enterHandle;
Vector3 worldSpaceEnterTip = enterTip;
Vector3 worldSpaceExitTip = exitTip;

//Transform all inputs from world space to local space.
enterHandle = collisionEnterMatrix.inverse.MultiplyPoint3x4(enterHandle);
enterTip = collisionEnterMatrix.inverse.MultiplyPoint3x4(enterTip);
exitTip = transform.InverseTransformPoint(exitTip);
for (int i = 0; i < pastPositions.Length; i++) {
pastPositions[i] = transform.InverseTransformPoint(pastPositions[i]);
}
for (int i = 0; i < collisionEnterPoints.Length; i++)
{
collisionEnterPoints[i] = transform.InverseTransformPoint(collisionEnterPoints[i]);
}
for (int i = 0; i < collisionExitPoints.Length; i++)
{
collisionExitPoints[i] = transform.InverseTransformPoint(collisionExitPoints[i]);
}
//Calculate normal of slice.
Vector3 normal = Vector3.Cross(enterHandle - enterTip, enterHandle - exitTip).normalized;
//Vector3 worldSpaceNormal = Vector3.Cross(worldSpaceEnterHandle - worldSpaceEnterTip, worldSpaceEnterHandle - worldSpaceExitTip).normalized;

//Check if slice is valid.
//** Note that in object space a correct slice is downwards **
bool isValid = true;

float xOffset = exitTip.x - enterTip.x;
float yOffset = exitTip.y - enterTip.y;
if (Mathf.Abs(yOffset) < 0.01f || Mathf.Abs(xOffset) < 0.01f)
return;
if (!isAll && (yOffset > 0.6f || (Mathf.Abs(xOffset) -1f) > Mathf.Abs(yOffset)))
isValid = false;
if (noteType == MapSystem.NoteType.LeftNote && otherTag == "Right Sword") isValid = false;
if (noteType == MapSystem.NoteType.RightNote && otherTag == "Left Sword") isValid = false;
if (noteType == MapSystem.NoteType.Bomb) isValid = false;
//Calculate point score.
if (isValid) {
int addedPoints = 0;

//Add slice accuracy points.
float averageDistanceFromCenter = 0;
foreach (Vector3 point in collisionEnterPoints) {
averageDistanceFromCenter += Mathf.Abs(point.x);
}
foreach (Vector3 point in collisionExitPoints)
{
averageDistanceFromCenter += Mathf.Abs(point.x);
}
averageDistanceFromCenter /= (collisionEnterPoints.Length + collisionExitPoints.Length);

averageDistanceFromCenter = averageDistanceFromCenter * Mathf.Sqrt(averageDistanceFromCenter); //Creating a curved falloff curve + making getting points easier

addedPoints += Mathf.RoundToInt(((Mathf.Clamp01(averageDistanceFromCenter/ accuracyZoneRadius)-1)*-1)*15);
//Add enter and exit angle points.
UnityEngine.Plane plane = new UnityEngine.Plane();
plane.SetNormalAndPosition(normal, enterHandle);
addedPoints += CalculateMaxPastPosAngleOnPlane(plane,pastPositions,enterHandle, exitTip, 100, 70);
getPositionsFromNow.Invoke().ContinueWith((getPosTask) => {
UnityMainThreadDispatcher.Instance().Enqueue(() => {
Vector3[] futurePositions = getPosTask.Result;
addedPoints += CalculateMaxPastPosAngleOnPlane(plane, pastPositions, enterHandle, exitTip, 60, 30);
pointSystem.logPoints(addedPoints, note);
});
});
}
else
{
if (noteType == MapSystem.NoteType.Bomb) pointSystem.logBombHit();
else pointSystem.logBadCut();
sfxSystem.playBadCutAudio();
}
public void slice(Vector3 enterHandle, Vector3 enterTip, Vector3 exitTip, Vector3[] pastPositions, Func<Task<Vector3[]>> getPositionsFromNow, Vector3[] collisionEnterPoints, Vector3[] collisionExitPoints, string otherTag)
{
if (sliced) return; //If the object was sliced already, we do not slice it again.
sliced = true; //Set sliced to true so that we do not slice it again.
sfxSystem.playSliceAudio(); //Play slice sound effect.

//Maintain world space copies of sword position variables.
Vector3 worldSpaceEnterHandle = enterHandle;
Vector3 worldSpaceEnterTip = enterTip;
Vector3 worldSpaceExitTip = exitTip;

//Transform all inputs from world space to local space (relative to the object being sliced).
enterHandle = collisionEnterMatrix.inverse.MultiplyPoint3x4(enterHandle); //Transforms the enterHandle to the local space (relative to the position of the object when the collision first occured). Why? Because the object will have moved since the collision occured.
enterTip = collisionEnterMatrix.inverse.MultiplyPoint3x4(enterTip); //Same thing here.
exitTip = transform.InverseTransformPoint(exitTip); //Transforms the exitTip to the local space (relative to the position of the object currently). Why? Because this method is called when collision ends with the slicing object.
for (int i = 0; i < pastPositions.Length; i++)
{
pastPositions[i] = transform.InverseTransformPoint(pastPositions[i]); //Transforms the pastPositions to the local space (relative to the position of the object currently).
}
for (int i = 0; i < collisionEnterPoints.Length; i++)
{
collisionEnterPoints[i] = collisionEnterMatrix.inverse.MultiplyPoint3x4(collisionEnterPoints[i]); //Transforms the collisionEnterPoints to the local space (relative to the position of the object when the collision first occured).
}
for (int i = 0; i < collisionExitPoints.Length; i++)
{
collisionExitPoints[i] = transform.InverseTransformPoint(collisionExitPoints[i]); //Transforms the collisionExitPoints to the local space (relative to the position of the object currently).
}
//Calculate normal of slice (cross product). This vector is perpendicular to the slice plane (and thus is representative of it).
Vector3 normal = Vector3.Cross(enterHandle - enterTip, enterHandle - exitTip).normalized;


//Check if slice is valid.
//** Note that in object space a correct slice is downwards - horizontal slices, etc. are all just rotated. **
bool isValid = true; //Assume slice is valid.

float xOffset = exitTip.x - enterTip.x; //Calculate how much the sword moved horizontally.
float yOffset = exitTip.y - enterTip.y; //Calculate how much the sword moved vertically.
if (Mathf.Abs(yOffset) < 0.01f || Mathf.Abs(xOffset) < 0.01f) //If the sword barely moved horizontally or vertically, this slice is like a false trigger. We exit here.
return;
if (!isAll && (yOffset > 0.6f || (Mathf.Abs(xOffset) - 1f) > Mathf.Abs(yOffset))) //If the sword moved upwards (by a margin) or moved more horizontally than vertically (by a margin), this slice is invalid.
isValid = false;
if (noteType == MapSystem.NoteType.LeftNote && otherTag == "Right Sword") isValid = false; //If the note is a left note and the sword is a right sword, this slice is invalid.
if (noteType == MapSystem.NoteType.RightNote && otherTag == "Left Sword") isValid = false; //If the note is a right note and the sword is a left sword, this slice is invalid.
if (noteType == MapSystem.NoteType.Bomb) isValid = false; //If the note is a bomb, this slice is invalid.

if (isValid)
{ //If the slice is valid, we calculate the points earned for the slice.
int addedPoints = 0; //The points earned for the slice.

//Add slice accuracy points. This is based on how close the slice was to the center of the object. We do this based on the points where the sword entered and exited the object.
float averageDistanceFromCenter = 0; //The average distance from the center of the object.
foreach (Vector3 point in collisionEnterPoints)
{
averageDistanceFromCenter += Mathf.Abs(point.x); //Add the distance from the center of the object of all of the points in collisionEnterPoints.
}
foreach (Vector3 point in collisionExitPoints)
{
averageDistanceFromCenter += Mathf.Abs(point.x); //Add the distance from the center of the object of all of the points in collisionExitPoints.
}
averageDistanceFromCenter /= (collisionEnterPoints.Length + collisionExitPoints.Length); //Divide by the total number of points to get the average distance of each point from the center of the object.
averageDistanceFromCenter = averageDistanceFromCenter * Mathf.Sqrt(averageDistanceFromCenter); //Creating a curved falloff curve for the distance, making getting points easier.

//Get a value between 0 and 1 based on how close the slice was to the center of the object. We then invert this value so that 1 means a closer slice (this is the -1 then *-1 thing). Then, multiply by 15 to get the points earned for the slice. 15 is the maximum number of points that can be earned for slice accuracy.
addedPoints += Mathf.RoundToInt(((Mathf.Clamp01(averageDistanceFromCenter / accuracyZoneRadius) - 1) * -1) * 15);



//Add enter and exit angle points.
UnityEngine.Plane plane = new UnityEngine.Plane();
plane.SetNormalAndPosition(normal, enterHandle); //Create a plane that is representative of the slice.
//CalculateMaxPastPsAngleOnPlane: Calculate and give points based on the magnitude of the angle swung by the sword up to the collision where the sword was in line with the slice plane.
//If other words, how much of an angle did the sword swing for this particular cut.
//100 is the maximum angle that can be earned points for. 70 is the maximum number of points that can be earned for angle.
//I wrote the function, and it features some calculations - you can check it out in the source if you want, but im omitting as it will make this snippet too long.
addedPoints += CalculateMaxPastPosAngleOnPlane(plane, pastPositions, enterHandle, exitTip, 100, 70);
getPositionsFromNow.Invoke().ContinueWith((getPosTask) => { //Additional points can be earned if the sword continues along the slice plane after the slice.
UnityMainThreadDispatcher.Instance().Enqueue(() => {
Vector3[] futurePositions = getPosTask.Result; //Get the future positions of the sword.
for (int i = 0; i < futurePositions.Length; i++)
{
futurePositions[i] = transform.InverseTransformPoint(futurePositions[i]); //Transforms the furturePositions to the local space
}
addedPoints += CalculateMaxPastPosAngleOnPlane(plane, futurePositions, enterHandle, exitTip, 60, 30); //The same points calculation is done here, but with a different angle and point cap.
pointSystem.logPoints(addedPoints, note); //Log the points earned.
});
});
}
else //If the slice is invalid, we log the appropriate point deficit and play the appropriate sound effect.
{
if (noteType == MapSystem.NoteType.Bomb) pointSystem.logBombHit();
else pointSystem.logBadCut();
sfxSystem.playBadCutAudio();
}

//Slice mesh and add physics to fragments.
//Ezy-Slice uses world space for slice planes!
GameObject[] fragments = SliceObjectRecursive(worldSpaceExitTip, normal, sliceableMesh, crossSectionMaterial);
if (fragments == null) {
fragments = SliceObjectRecursive(transform.position, normal, sliceableMesh, crossSectionMaterial);
//Slice mesh and add physics to fragments.
//We use the package Ezy-Slice in SliceObjectRecursive to slice the meshes. However, I wrote the recursive portion (basically also slices children of the object being sliced).
//Note that Ezy-Slice uses world space for slice planes!
GameObject[] fragments = SliceObjectRecursive(worldSpaceExitTip, normal, sliceableMesh, crossSectionMaterial); //Slice the mesh alongside the slice plane recursively. This function is pretty cool, but I feel would make this snippet too long.
if (fragments == null)
{
fragments = SliceObjectRecursive(transform.position, normal, sliceableMesh, crossSectionMaterial); //If the slice failed along the slice plane, we try again along the center of the object.
if (fragments == null)
{
Destroy(gameObject);
Destroy(gameObject); //If the slice failed again, we just destroy the object.
return;
}
}

Vector3 centerPosition = Vector3.zero;
foreach(GameObject fragment in fragments){
fragment.transform.SetParent(transform, false); //readjust position
fragment.transform.SetParent(debrisParent);
fragment.layer = ghostLayer;
foreach (GameObject fragment in fragments)
{
fragment.transform.SetParent(transform, false); //Re-adjust the position of the fragments to be on the block.
fragment.transform.SetParent(debrisParent); //Set the parent of the fragments to the debris parent (just an empty object that holds all the debris pieces).
fragment.layer = ghostLayer; //Set the layer of the fragments to the ghost layer (this is so that the fragments do not collide with the sword).

fragment.AddComponent<FragmentManager>().startFadeWithLifespanRecursive(fragmentFade);
fragment.AddComponent<FragmentManager>().startFadeWithLifespanRecursive(fragmentFade); //Add a FragmentManager to the fragments. This is a script that causes the fragments to disintegrate with a shader and eventually destroys the GameObject.

MeshCollider fragmentCollider = fragment.AddComponent<MeshCollider>();
fragmentCollider.convex = true;
centerPosition += fragment.transform.position;
MeshCollider fragmentCollider = fragment.AddComponent<MeshCollider>(); //Add a mesh collider to the fragments.
fragmentCollider.convex = true; //Set the mesh collider to be convex (this is required for physics).
centerPosition += fragment.transform.position; //Add the position of the fragment to the centerPosition (this is used to calculate the approx. center of mass of the fragments).
}
centerPosition /= fragments.Length;
foreach(GameObject fragment in fragments){
fragment.AddComponent<Rigidbody>();
Rigidbody rb = fragment.GetComponent<Rigidbody>();
rb.angularDrag = fragmentAngularDrag;
rb.AddForce(0, 0, rb.mass * mover.currentVelocity.z * fragmentVelocityTransfer, ForceMode.Impulse);
rb.AddExplosionForce(sliceExplosionForce, centerPosition, sliceExplosionRadius,upwardsModifier);
rb.AddForce((exitTip - enterTip).normalized * slashForceMultiplier);
//StartCoroutine(forceAxisVelocityAfterForces(Axis.Z, mover.currentVelocity.z, rb));
centerPosition /= fragments.Length; //Divide the centerPosition by the number of fragments to get the approx. center of mass of the fragments.
foreach (GameObject fragment in fragments)
{
fragment.AddComponent<Rigidbody>(); //For each fragment, we give it a rigidbody, which enables physics.
Rigidbody rb = fragment.GetComponent<Rigidbody>(); //Get the rigidbody of the fragment.
rb.angularDrag = fragmentAngularDrag; //Set the angular drag of the fragment.
rb.AddForce(0, 0, rb.mass * mover.currentVelocity.z * fragmentVelocityTransfer, ForceMode.Impulse); //Adds an force to the fragment in the z direction of the velocity of the original object. This is so that the fragments maintain part of the momentum of the original object. Should probably allow for other axes if I want Sliceable to be generalizable, but this is ok for now.
rb.AddExplosionForce(sliceExplosionForce, centerPosition, sliceExplosionRadius, upwardsModifier); //Adds an explosion force at the approx. center of mass to the fragment. This is so that the fragments fly apart from each other.
rb.AddForce((exitTip - enterTip).normalized * slashForceMultiplier); //Adds a force in the direction of the slice. This is so that the fragments fly in the direction of the slice (to give the illusion that the blade had friction).
}
LeanPool.Despawn(gameObject);
LeanPool.Despawn(gameObject); //Despawn the original (unsliced) object. We use a pool here as the blocks that are sliced are loaded multiple times throughout a game. LeanPool hides them instead of deleting them so that we don't have to instantiate and destroy blocks all the time (this saves the garbage collector from having to do work, which is good for performance).
}
/*
/*
* Ignore, this is if you want the blocks' forward movement to be not affected by slices
IEnumerator forceAxisVelocityAfterForces(Axis axis, float velocity, Rigidbody rb)
{
Expand Down
Loading

0 comments on commit ea23243

Please sign in to comment.