public void SetAnimationSetForCamera(Camera camera) { //Work out angle between character face direction and camera forward. //Choose a animation set based on horizontal angle between camera forward and character forward Transform graphicsOrigin = _graphicsOrigin; if (graphicsOrigin == null) { graphicsOrigin = this.transform; } //Convert camera pos and forward into character space Vector3 localspaceCameraPos = _graphicsOrigin.InverseTransformPoint(camera.transform.position); Vector3 localspaceCameraDir = _graphicsOrigin.InverseTransformDirection(-camera.transform.forward); //Get forward in XY space Vector2 localspaceCameraDirXY = new Vector2(localspaceCameraDir.x, localspaceCameraDir.z).normalized; Vector2 forwardXY = Vector2.up; //The angle between camera forward and character forward float horizAngle = MathUtils.AngleBetween(forwardXY, localspaceCameraDirXY); //Work out which animations to use int bestAnimationSet = -1; float nearestAngleDif = 0.0f; for (int i = 0; i < _animationSets.Length; i++) { //Disable the sets renderer SetAnimationSetActive(_animationSets[i], false); //Never use sprites that direction is more than 90 degrees to camera Vector2 spriteForwardXY = MathUtils.Rotate(forwardXY, _animationSets[i]._fowardAngle); float spriteAngle = MathUtils.AngleBetween(localspaceCameraDirXY, spriteForwardXY); if (Mathf.Abs(spriteAngle) < _animationSets[i]._maxAngle) { float angleDiff = MathUtils.AngleDiff(horizAngle, MathUtils.DegreesTo180Range(_animationSets[i]._faceAngle)); if (bestAnimationSet == -1 || Mathf.Abs(angleDiff) < Mathf.Abs(nearestAngleDif)) { bestAnimationSet = i; nearestAngleDif = angleDiff; } } } if (bestAnimationSet != -1) { Spine3DAnimationSet animationSet = _animationSets[bestAnimationSet]; SetAnimationSetActive(animationSet, true); if (_onRenderAnimationSet != null) { _onRenderAnimationSet.Invoke(camera, animationSet); } //Horizontal sprite rotation { //Rotate this forward to face sprite towards its face angle. Vector2 localSpaceSpriteForwardXY = MathUtils.Rotate(forwardXY, -animationSet._faceAngle); //When hit max view angle maintain that relative angle diff. float spriteAngle = MathUtils.AngleBetween(localspaceCameraDirXY, localSpaceSpriteForwardXY); float spriteMaxAngle = animationSet._maxViewAngle; if (Mathf.Abs(spriteAngle) > spriteMaxAngle) { //Rotate camera forward by max angle in correct direction float clampedAngle = spriteMaxAngle; if (spriteAngle < 0) { clampedAngle = -clampedAngle; } localSpaceSpriteForwardXY = MathUtils.Rotate(localspaceCameraDirXY, -clampedAngle); } //Set rotation matrix based off adjusted up and the correct sprite forward vector { Vector3 spriteForward = new Vector3(localSpaceSpriteForwardXY.x, 0.0f, localSpaceSpriteForwardXY.y).normalized; animationSet.transform.localRotation = Quaternion.FromToRotation(Vector3.forward, -spriteForward); } } //Vertical sprite rotation { Vector3 localSpaceCamerDirFlat = new Vector3(localspaceCameraDir.x, 0.0f, localspaceCameraDir.z).normalized; //Work out roll angle between sprite and camera float rollAngle = Vector3.Angle(localSpaceCamerDirFlat, localspaceCameraDir); if (localspaceCameraDir.y < 0) { rollAngle = -rollAngle; } //If roll angle is too severe then rotate sprite to align better with the camera if (Mathf.Abs(rollAngle) > _minRollAngle) { //ifs over min angle then rotate by amount over float angle = Mathf.Abs(rollAngle) - _minRollAngle; if (angle > _maxRollAngle) { angle = _maxRollAngle; } if (rollAngle < 0.0f) { angle = -angle; } Vector3 spriteAxis = Vector3.Cross(Vector3.up, localspaceCameraDir).normalized; animationSet.transform.localRotation *= Quaternion.AngleAxis(angle, Vector3.right); } } } }