/// <summary> /// Changes the theme of all child IChangeTheme components that were attached at the start of the game to the players theme. /// </summary> /// <param name="user">The character that caused the hit.</param> /// <param name="hitPlanet">The planet that was hit.</param> /// <param name="hitPoint">The world coordinates of the hit.</param> /// <param name="hitNormal">The world normal of the hit.</param> public void OnHit(PlayerStats stats, PlanetStats hitPlanet, Vector3 hitPoint, Vector3 hitNormal) { if (stats != null) { SetTheme(stats.theme); } }
/// <summary> /// Finds the attached planet stats. Planet Stats are not required for use but is highly recommended and may /// become a future requirement. /// </summary> protected void Awake() { myPlanet = GetComponentInParent <PlanetStats>(); if (!myPlanet) { Debug.LogWarning("Warning: Destroyer script not associated with a planet."); } }
/// <summary> /// Update players current state variables. /// </summary> protected void Update() { isGrounded = Physics.CheckBox(groundCheck.position - transform.up * groundCheckDistance / 2.0f, Vector3.one * groundCheckDistance / 2.0f, groundCheck.rotation, groundMask, QueryTriggerInteraction.Ignore); if (isGrounded) { RaycastHit downHit; if (Physics.Raycast(groundCheck.position, gravity.GravityDirection, out downHit, groundCheckDistance, groundMask, QueryTriggerInteraction.Ignore)) { transform.parent = downHit.transform; if (isUsingGroundPound) { PlanetStats hitPlanet = downHit.transform.GetComponentInParent <PlanetStats>(); IOnHitBehaviour[] hitBehaviours = downHit.transform.GetComponentsInParent <IOnHitBehaviour>(); foreach (IOnHitBehaviour onHitBehaviour in hitBehaviours) { onHitBehaviour.OnHit(stats, hitPlanet, downHit.point, downHit.normal); } groundPoundRecoveryTimer = groundPoundRecoveryTime; } isUsingGroundPound = false; // ground pound or jump has finished } gravity.CanChangeGravityDirection = !overrideGravity; } else { airTimeTimer += Time.deltaTime; transform.parent = null; } if (MoveDirection.magnitude > 0.1f) { Quaternion targetRotation = Quaternion.LookRotation(lookDirection, Vector3.up); graphics.localRotation = Quaternion.Slerp(graphics.localRotation, targetRotation, Time.deltaTime * rotationSpeed); } if (groundPoundRecoveryTimer > 0.0f) { groundPoundRecoveryTimer -= Time.deltaTime; } }
/// <summary> /// Destories any planet that enters this ones trigger if and only if it is either destroyable and is not this planet itself. /// </summary> /// <param name="other"></param> protected void OnTriggerEnter(Collider other) { PlanetStats otherPlanet = other.GetComponentInParent <PlanetStats>(); if (otherPlanet && otherPlanet.isDestroyable && (!myPlanet || myPlanet != otherPlanet)) { PlayerStats[] players = otherPlanet.GetComponentsInChildren <PlayerStats>(); foreach (PlayerStats player in players) { player.transform.parent = null; } otherPlanet.gameObject.SetActive(false); // TODO: Disable and spawn destroyed mesh. } }
/// <summary> /// Computes the target rotation direction based on where the hit planet was hit and from which direction. The target rotation direcition /// will be along the axis perpendicular to the hit normal that is furthest from the galaxy origin. The affected blocks will also be calculated /// based on the target rotation axis and the maximum check distance from the axis. After a rotation is calculate it is started in a /// coroutine if none of the affected blocks are currently being rotated. /// </summary> /// <param name="user">The character that caused the hit.</param> /// <param name="hitPlanet">The planet that was hit.</param> /// <param name="hitPoint">The world coordinates of the hit.</param> /// <param name="hitNormal">The world normal of the hit.</param> private void Rotate(PlayerStats stats, PlanetStats hitPlanet, Vector3 hitPoint, Vector3 hitNormal) { Vector3 galaxyLocalHit = transform.InverseTransformPoint(hitPoint); Vector3 galaxyLocalNormal = transform.InverseTransformDirection(hitNormal); Vector3 columnRowOffset = Vector3.zero; Vector3 overLapSize = Vector3.zero; bool positiveRotation = true; // if X greatest, rotate based on Y and Z if (Mathf.Abs(galaxyLocalNormal.x) > Mathf.Abs(galaxyLocalNormal.y) && Mathf.Abs(galaxyLocalNormal.x) > Mathf.Abs(galaxyLocalNormal.z)) { if (Mathf.Abs(galaxyLocalHit.y) > Mathf.Abs(galaxyLocalHit.z)) // rotate on the Z axis { overLapSize = new Vector3(maxDistanceFromAxis, maxDistanceFromAxis, 0.1f); columnRowOffset = new Vector3(0.0f, 0.0f, galaxyLocalHit.z); } else // rotate on the Y axis { overLapSize = new Vector3(maxDistanceFromAxis, 0.1f, maxDistanceFromAxis); columnRowOffset = new Vector3(0.0f, galaxyLocalHit.y, 0.0f); } } // if Y greatest, rotate based on X and Z else if (Mathf.Abs(galaxyLocalNormal.y) > Mathf.Abs(galaxyLocalNormal.z)) { if (Mathf.Abs(galaxyLocalHit.z) > Mathf.Abs(galaxyLocalHit.x)) // rotate on the X axis { overLapSize = new Vector3(0.1f, maxDistanceFromAxis, maxDistanceFromAxis); columnRowOffset = new Vector3(galaxyLocalHit.x, 0.0f, 0.0f); } else // rotate on the Z axis { overLapSize = new Vector3(maxDistanceFromAxis, maxDistanceFromAxis, 0.1f); columnRowOffset = new Vector3(0.0f, 0.0f, galaxyLocalHit.z); } } // if Z greatest, rotate based on X and Y else { if (Mathf.Abs(galaxyLocalHit.x) > Mathf.Abs(galaxyLocalHit.y)) // rotate on the y axis { overLapSize = new Vector3(maxDistanceFromAxis, 0.1f, maxDistanceFromAxis); columnRowOffset = new Vector3(0.0f, galaxyLocalHit.y, 0.0f); } else // rotate on the X axis { overLapSize = new Vector3(0.1f, maxDistanceFromAxis, maxDistanceFromAxis); columnRowOffset = new Vector3(galaxyLocalHit.x, 0.0f, 0.0f); } } Vector3 rotationAxis = transform.TransformDirection(columnRowOffset.normalized); columnRowOffset = transform.rotation * columnRowOffset; Vector3 playerDirection = hitPoint - transform.position; positiveRotation = Vector3.Dot(Vector3.Cross(hitNormal, playerDirection), rotationAxis) > 0; Vector3 absDifference = new Vector3(Mathf.Abs(currentRotationAxis.x) - Mathf.Abs(rotationAxis.x), Mathf.Abs(currentRotationAxis.y) - Mathf.Abs(rotationAxis.y), Mathf.Abs(currentRotationAxis.z) - Mathf.Abs(rotationAxis.z)); if (activeRotations == 0 || 0.01f > absDifference.magnitude) { currentRotationAxis = rotationAxis; StartCoroutine(RotateCluster(columnRowOffset, overLapSize, positiveRotation)); } }
/// <summary> /// Begins the rotation logic. /// </summary> /// <param name="user">The character that caused the hit.</param> /// <param name="hitPlanet">The planet that was hit.</param> /// <param name="hitPoint">The world coordinates of the hit.</param> /// <param name="hitNormal">The world normal of the hit.</param> public void OnHit(PlayerStats stats, PlanetStats hitPlanet, Vector3 hitPoint, Vector3 hitNormal) { Rotate(stats, hitPlanet, hitPoint, hitNormal); }
/// <summary> /// Rotates along the column row offset direction in either the positive or negative direction. The rotation will be canceled /// if any of the affected planets (determined by overlap size and column row offset) are currently in motion. /// </summary> /// <param name="columnRowOffset">Rotation axis multiplied by the distance on the axis of the hit point.</param> /// <param name="overlapSize">The distance used to check for affected planets.</param> /// <param name="positiveRotation">True if we should rotate in the positive direction.</param> /// <returns>Returns a single frame delay during the rotation effects.</returns> private IEnumerator RotateCluster(Vector3 columnRowOffset, Vector3 overlapSize, bool positiveRotation) { Collider[] affectedPlanetColliders = Physics.OverlapBox(transform.position + columnRowOffset, overlapSize / 2.0f, transform.rotation, groundMask, QueryTriggerInteraction.Ignore); List <PlanetStats> affectedPlanets = new List <PlanetStats>(); List <int> friendGroupIds = new List <int>(); friendGroupIds.Add(myStats.PhysicsGroupId); Vector3 cachedRotationAxis = currentRotationAxis; foreach (GalaxyStats friend in friendGroups) { friendGroupIds.Add(friend.PhysicsGroupId); } // verify can rotate foreach (Collider planetCollider in affectedPlanetColliders) { PlanetStats planet = planetCollider.GetComponentInParent <PlanetStats>(); if (planet != null && friendGroupIds.Contains(planet.PhysicsGroupId)) { if (!affectedPlanets.Contains(planet)) { affectedPlanets.Add(planet); } if (planet.IsInMotion) { yield break; } } } // initialize rotation data activeRotations++; float remainingRotation = 90.0f; foreach (PlanetStats planet in affectedPlanets) { planet.transform.parent = transform; planet.IsInMotion = true; } // rotate while (remainingRotation > 0.0f) { float rotationAmount = (positiveRotation ? 1 : -1) * Mathf.Min(Time.deltaTime * rotationSpeed, remainingRotation); foreach (PlanetStats planet in affectedPlanets) { planet.transform.RotateAround(transform.position, cachedRotationAxis, rotationAmount); } remainingRotation -= Mathf.Abs(rotationAmount); yield return(new WaitForFixedUpdate()); } // uninitialize rotation data foreach (PlanetStats planet in affectedPlanets) { planet.IsInMotion = false; } activeRotations--; }
/// <summary> /// Beings the movement logic. A movement request could be canceled if this group is already moving. /// </summary> /// <param name="user">The character that caused the hit.</param> /// <param name="hitPlanet">The planet that was hit.</param> /// <param name="hitPoint">The world coordinates of the hit.</param> /// <param name="hitNormal">The world normal of the hit.</param> public void OnHit(PlayerStats stats, PlanetStats hitPlanet, Vector3 hitPoint, Vector3 hitNormal) { StartCoroutine(MoveAlongCurve()); }
/// <summary> /// Computes the target rotation direction based on where the hit planet was hit and from which direction. The target rotation direcition /// will be along the axis perpendicular to the hit normal that is furthest from the hit point. The affected blocks will also be calculated /// based on the target rotation axis and the maximum check distance from the axis. After a rotation is calculate it is started in a /// coroutine if none of the affected blocks are currently being rotated. /// </summary> /// <param name="user">The character that caused the hit.</param> /// <param name="hitPlanet">The planet that was hit.</param> /// <param name="hitPoint">The world coordinates of the hit.</param> /// <param name="hitNormal">The world normal of the hit.</param> private void Rotate(PlayerStats stats, PlanetStats hitPlanet, Vector3 hitPoint, Vector3 hitNormal) { Vector3 galaxyLocalHit = transform.InverseTransformPoint(hitPoint); Vector3 galaxyLocalNormal = transform.InverseTransformDirection(hitNormal); Vector3 planetLocalHit = transform.rotation * (hitPoint - hitPlanet.transform.position); //hitPlanet.transform.InverseTransformPoint(hitPoint); Vector3 columnRowOffset = Vector3.zero; Vector3 overLapSize = Vector3.zero; bool positiveRotation = true; // if X greatest, rotate based on Y and Z if (Mathf.Abs(galaxyLocalNormal.x) > Mathf.Abs(galaxyLocalNormal.y) && Mathf.Abs(galaxyLocalNormal.x) > Mathf.Abs(galaxyLocalNormal.z)) { bool rotateOnZ = false; if (Mathf.Abs(planetLocalHit.y) > Mathf.Abs(planetLocalHit.z)) { Vector3 testDirection = transform.TransformDirection(Mathf.Sign(planetLocalHit.y) * transform.up); if (!Physics.Raycast(hitPlanet.transform.position, testDirection, hitPlanet.boundingBoxSize.y / 2.0f + 0.1f, groundMask, QueryTriggerInteraction.Ignore)) { rotateOnZ = Mathf.Sign(galaxyLocalHit.y) == Mathf.Sign(planetLocalHit.y); } else { rotateOnZ = Mathf.Abs(galaxyLocalHit.z) < Mathf.Abs(galaxyLocalHit.y); } } else { Vector3 testDirection = transform.TransformDirection(Mathf.Sign(planetLocalHit.z) * Vector3.forward); if (!Physics.Raycast(hitPlanet.transform.position, testDirection, hitPlanet.boundingBoxSize.z / 2.0f + 0.1f, groundMask, QueryTriggerInteraction.Ignore)) { rotateOnZ = Mathf.Sign(galaxyLocalHit.z) != Mathf.Sign(planetLocalHit.z); } else { rotateOnZ = Mathf.Abs(galaxyLocalHit.y) > Mathf.Abs(galaxyLocalHit.z); } } if (rotateOnZ) // rotate on the Z axis { overLapSize = new Vector3(maxDistanceFromAxis, maxDistanceFromAxis, 0.1f); columnRowOffset = new Vector3(0.0f, 0.0f, galaxyLocalHit.z); } else // rotate on the Y axis { overLapSize = new Vector3(maxDistanceFromAxis, 0.1f, maxDistanceFromAxis); columnRowOffset = new Vector3(0.0f, galaxyLocalHit.y, 0.0f); } } // if Y greatest, rotate based on X and Z else if (Mathf.Abs(galaxyLocalNormal.y) > Mathf.Abs(galaxyLocalNormal.z)) { bool rotateOnX = false; if (Mathf.Abs(planetLocalHit.z) > Mathf.Abs(planetLocalHit.x)) { Vector3 testDirection = transform.TransformDirection(Mathf.Sign(planetLocalHit.z) * Vector3.forward); if (!Physics.Raycast(hitPlanet.transform.position, testDirection, hitPlanet.boundingBoxSize.z / 2.0f + 0.1f, groundMask, QueryTriggerInteraction.Ignore)) { rotateOnX = Mathf.Sign(galaxyLocalHit.z) == Mathf.Sign(planetLocalHit.z); } else { rotateOnX = Mathf.Abs(galaxyLocalHit.x) < Mathf.Abs(galaxyLocalHit.z); } } else { Vector3 testDirection = transform.TransformDirection(Mathf.Sign(planetLocalHit.x) * Vector3.right); if (!Physics.Raycast(hitPlanet.transform.position, testDirection, hitPlanet.boundingBoxSize.x / 2.0f + 0.1f, groundMask, QueryTriggerInteraction.Ignore)) { rotateOnX = Mathf.Sign(galaxyLocalHit.x) != Mathf.Sign(planetLocalHit.x); } else { rotateOnX = Mathf.Abs(galaxyLocalHit.z) > Mathf.Abs(galaxyLocalHit.x); } } if (rotateOnX) // rotate on X axis { overLapSize = new Vector3(0.1f, maxDistanceFromAxis, maxDistanceFromAxis); columnRowOffset = new Vector3(galaxyLocalHit.x, 0.0f, 0.0f); } else // rotate on Z axis { overLapSize = new Vector3(maxDistanceFromAxis, maxDistanceFromAxis, 0.1f); columnRowOffset = new Vector3(0.0f, 0.0f, galaxyLocalHit.z); } } // if Z greatest, rotate based on X and Y else { bool rotateOnY = false; if (Mathf.Abs(planetLocalHit.x) > Mathf.Abs(planetLocalHit.y)) { Vector3 testDirection = transform.TransformDirection(Mathf.Sign(planetLocalHit.x) * Vector3.right); if (!Physics.Raycast(hitPlanet.transform.position, testDirection, hitPlanet.boundingBoxSize.x / 2.0f + 0.1f, groundMask, QueryTriggerInteraction.Ignore)) { rotateOnY = Mathf.Sign(galaxyLocalHit.x) == Mathf.Sign(planetLocalHit.x); } else { rotateOnY = Mathf.Abs(galaxyLocalHit.y) < Mathf.Abs(galaxyLocalHit.x); } } else { Vector3 testDirection = transform.TransformDirection(Mathf.Sign(planetLocalHit.y) * Vector3.up); if (!Physics.Raycast(hitPlanet.transform.position, testDirection, hitPlanet.boundingBoxSize.y / 2.0f + 0.1f, groundMask, QueryTriggerInteraction.Ignore)) { rotateOnY = Mathf.Sign(galaxyLocalHit.y) != Mathf.Sign(planetLocalHit.y); } else { rotateOnY = Mathf.Abs(galaxyLocalHit.x) > Mathf.Abs(galaxyLocalHit.y); } } if (rotateOnY) // rotate on the y axis { overLapSize = new Vector3(maxDistanceFromAxis, 0.1f, maxDistanceFromAxis); columnRowOffset = new Vector3(0.0f, galaxyLocalHit.y, 0.0f); } else // rotate on the X axis { overLapSize = new Vector3(0.1f, maxDistanceFromAxis, maxDistanceFromAxis); columnRowOffset = new Vector3(galaxyLocalHit.x, 0.0f, 0.0f); } } Vector3 rotationAxis = transform.TransformDirection(columnRowOffset.normalized); columnRowOffset = transform.rotation * columnRowOffset; Vector3 playerDirection = hitPoint - transform.position; positiveRotation = Vector3.Dot(Vector3.Cross(hitNormal, playerDirection), rotationAxis) > 0; Vector3 absDifference = new Vector3(Mathf.Abs(currentRotationAxis.x) - Mathf.Abs(rotationAxis.x), Mathf.Abs(currentRotationAxis.y) - Mathf.Abs(rotationAxis.y), Mathf.Abs(currentRotationAxis.z) - Mathf.Abs(rotationAxis.z)); if (activeRotations == 0 || 0.01f > absDifference.magnitude) { currentRotationAxis = rotationAxis; StartCoroutine(RotateCluster(columnRowOffset, overLapSize, positiveRotation)); } }
/// <summary> /// Begins the rotation logic. /// </summary> /// <param name="user">The character that caused the hit.</param> /// <param name="hitPlanet">The planet that was hit.</param> /// <param name="hitPoint">The world coordinates of the hit.</param> /// <param name="hitNormal">The world normal of the hit.</param> public void OnHit(PlayerStats user, PlanetStats hitPlanet, Vector3 hitPoint, Vector3 hitNormal) { Vector3 galaxyHitPoint = hitPoint - transform.position; Vector3 planetHitPoint = hitPoint - hitPlanet.transform.position; // 1.) Find the normal axis float distUp = Vector3.Project(hitNormal, transform.up).magnitude; float distForward = Vector3.Project(hitNormal, transform.forward).magnitude; float distRight = Vector3.Project(hitNormal, transform.right).magnitude; Vector3 normalAxis; Vector3 closestRotationAxis; Vector3 furthestRotationAxis; Vector3 rotationAxis; Vector3 unusedRotationAxis; // if (distUp > distForward && distUp > distRight) { normalAxis = transform.up; closestRotationAxis = transform.forward; // default, may not be correct furthestRotationAxis = transform.right; // default, may not be correct } else if (distForward > distRight) { normalAxis = transform.forward; closestRotationAxis = transform.up; // default, may not be correct furthestRotationAxis = transform.right; // default, may not be correct } else { normalAxis = transform.right; closestRotationAxis = transform.forward; // default, may not be correct furthestRotationAxis = transform.up; // default, may not be correct } // 2.) Find which axis we are most likely to rotate around (the furthest) and which one we are least likely to rotate around (closest) if (Vector3.Project(galaxyHitPoint, furthestRotationAxis).magnitude > Vector3.Project(galaxyHitPoint, closestRotationAxis).magnitude) // projecting on to the opposite axis { Vector3 temp = closestRotationAxis; closestRotationAxis = furthestRotationAxis; furthestRotationAxis = temp; } // 3.) Determine the true rotation axis. If there is no planet next to this one in the furthest rotation axis direction (i.e. we are on the edge), // Then we rotate around the closest axis if an only if we are closer to the closest rotation axis than the furthest rotation axis. Otherwise, // we rotate around the furthest axis. RaycastHit hitTest; bool isPositive = Vector3.Angle(furthestRotationAxis, Vector3.ProjectOnPlane(planetHitPoint, normalAxis)) < 90.0f; if (!Physics.Raycast(hitPlanet.transform.position, isPositive ? furthestRotationAxis : -furthestRotationAxis, out hitTest, 26.0f, groundMask, QueryTriggerInteraction.Ignore) && Vector3.Project(planetHitPoint, closestRotationAxis).magnitude < Vector3.Project(planetHitPoint, furthestRotationAxis).magnitude) { rotationAxis = closestRotationAxis; unusedRotationAxis = furthestRotationAxis; } else { rotationAxis = furthestRotationAxis; unusedRotationAxis = closestRotationAxis; } // 4.) Determine if we should rotate in a positive or negative direction. isPositive = AngleDirection(rotationAxis, galaxyHitPoint, normalAxis) < 1.0f; // 5.) Determine how to find the affected planets. Vector3 overlapSize = maxDistanceFromAxis * (Quaternion.Inverse(transform.rotation) * unusedRotationAxis + Quaternion.Inverse(transform.rotation) * normalAxis).normalized; Vector3 rotationCenter = transform.position + Vector3.Project(galaxyHitPoint, rotationAxis); // 6.) Rotate if we are not currently rotating or if we are rotating on the same axis (positive and negative doesn't matter) Vector3 absDifference = new Vector3(Mathf.Abs(currentRotationAxis.x) - Mathf.Abs(rotationAxis.x), Mathf.Abs(currentRotationAxis.y) - Mathf.Abs(rotationAxis.y), Mathf.Abs(currentRotationAxis.z) - Mathf.Abs(rotationAxis.z)); if (activeRotations == 0 || 0.01f > absDifference.magnitude) { currentRotationAxis = rotationAxis; StartCoroutine(RotateCluster(rotationCenter, overlapSize, isPositive)); } }