I have a script attached to the main camera that I use to do a fly through of my game area. It works fine except that on the first move of the mouse, (used for pitch and yaw), the camera flips over. Scrubbing the mouse around a bit brings it back into proper orientation, and then the code works as expected, including using the mouse, after that. The camera is set to proper orientation at start and the view is correct until the first mouse move. I’ve done a whole lot of searching here and elsewhere, but, nothing quite seems to explain this. Any pointers would be appreciated. The code follows:
// FlyThough.cs
// Alan Swithenbank
// Version 0.0.1
// September 2013
// Attach to Main Camera to provide keyboard/mouse navigation (NOTE 01)
// through the 3D data space generated by {turtle,shark}TrackRend.cs,
// with terrain avoidance (NOTE 02). Controls are as follows:
//
// W/S or Up-Arrow/Down-Arrow: Z motion in/out (depth)
//
// A/D or Left-Arrow/Right-Arrow: X motion left/right (width)
//
// Q/E: Y motion down/up (altitude)
//
// Tab: Increase speed
//
// Left-Shift: Decrease speed
//
// Mouse Left/Right: Yaw (horizontal rotation)
//
// Mouse forward/Reverse: Pitch (vertical rotation)
//
// Home: Reset camera to start position
//
// End: Toggle cursor lock on screen
//
// Escape: Application quit
//
// <ctrl>-P: Toggle play mode
// NOTES:
// ------
// NOTE 01: This code is loosely based on ExtendedFlyCam.cs by Desi Quintans,
// CowfaceGames.com, 17 August 2012, released with Free as in speech,
// and Free as in beer license. Added start position set on Awake().
// Added terrain avoidance (NOTE 02). Updated speed control to work
// on all axes. Added escape key game exit.
// NOTE 02: Terrain avoidance is via Physics.Raycast(). The basic method is
// allow motion until the camera approaches within 10 meters of the
// bathymetry terrain, either forward or down. On a 10 meter approach
// stop motion, but allow rotations to reorient so forward and down
// directions are clear by more than 10 meters. Once the directions
// are clear allow motion again. Only the bathymetry terrain is
// involved in collisions (NOTE 03).
// NOTE 03: Along with the bathymetry terrain, the water surface planes are
// collider objects and could interfer in collision detection ray
// casts. The most general way to ignore collider objects is assign
// them to a layer and use a layer mask in the Physics.Raycast() call
// to ignore those layers. Here just assigning the water surfaces to
// the "Ignore Raycast" layer in their Inspector is sufficient to
// ensure only the bathymetry terrain is the only active collider.
// None of the displayed data objects have colliders so they are
// invisible to ray casts by default.
using UnityEngine;
using System.Collections;
public class FlyThrough : MonoBehaviour {
public float cameraSensitivity = 120;
public float climbSpeed = 10;
public float normalMoveSpeed = 15;
public float slowMoveFactor = 0.25f;
public float fastMoveFactor = 8;
private float rotationX;
private float rotationY;
private Camera mainCamera;
private Vector3 homePos;
private Vector3 homeRot;
private RaycastHit camhit = new RaycastHit();
private Vector3 rayStart;
private Vector3 rayDirFwd;
private Vector3 rayDirDwn;
public float cast_distance = 10f; // public for runtime adjustment test
void Awake() {
// Get the Main Camera GameObject (as a Camera:
mainCamera = GameObject.Find("Main Camera").camera;
// Set start position:
homePos = new Vector3(903.8f,294.6f,707.0f);
// Set start rotation:
homeRot = new Vector3(36.5f,211.0f,4.6f);
// Move to start position/rotation:
MoveCameraPosRot (mainCamera,homePos,homeRot);
}
void Start () {
Screen.lockCursor = true;
}
void Update () {
// Bail out on escape key (build game):
if (Input.GetKeyDown(KeyCode.Escape)) {
Application.Quit();
}
// Terrain avoidance: Always allow rotations:
if ( Input.GetAxis("Mouse X") > 0 || Input.GetAxis("Mouse X") < 0
|| Input.GetAxis("Mouse Y") > 0 || Input.GetAxis("Mouse Y") < 0) {
// This rotation method almost works. Don't allow rotation updates unless
// there is a change in the mouse input. The camera stays in the correct
// orientation on start, but, first motion of mouse takes a large jump.
// Can rotate back into place with mouse and then all is well.
rotationX += Input.GetAxis("Mouse X") * cameraSensitivity * Time.deltaTime;
rotationY += Input.GetAxis("Mouse Y") * cameraSensitivity * Time.deltaTime;
rotationY = Mathf.Clamp (rotationY, -90, 90);
transform.localRotation = Quaternion.AngleAxis(rotationX, Vector3.up);
transform.localRotation *= Quaternion.AngleAxis(rotationY, Vector3.left);
}
// Terrain avoidance: Stop translations if forward direction or down direction within
// 10 m of terrain:
// assign the ray start point to the current camera position:
// (Yes, for a script attached to Main Camera the mainCamera is
// superflous, but, I like to do it anyway.)
rayStart = mainCamera.transform.position;
// assign ray directions from the camera transform:
rayDirFwd = mainCamera.transform.forward;
rayDirDwn = -mainCamera.transform.up;
// make the cast to out to cast_distance meters:
// if (!( Physics.Raycast(rayStart, rayDirFwd, out camhit, cast_distance)
// || Physics.Raycast(rayStart, rayDirDwn, out camhit, cast_distance))) { // not using camhit now
if (!( Physics.Raycast(rayStart, rayDirFwd, cast_distance)
|| Physics.Raycast(rayStart, rayDirDwn, cast_distance))) {
if (Input.GetKey (KeyCode.Tab)) { // fast speed moves
transform.position += transform.forward * (normalMoveSpeed * fastMoveFactor) * Input.GetAxis("Vertical") * Time.deltaTime;
transform.position += transform.right * (normalMoveSpeed * fastMoveFactor) * Input.GetAxis("Horizontal") * Time.deltaTime;
if (Input.GetKey (KeyCode.E)) {transform.position += transform.up * climbSpeed * fastMoveFactor * Time.deltaTime;}
if (Input.GetKey (KeyCode.Q)) {transform.position -= transform.up * climbSpeed * fastMoveFactor * Time.deltaTime;}
} else if (Input.GetKey (KeyCode.LeftShift)) { // slow speed moves
transform.position += transform.forward * (normalMoveSpeed * slowMoveFactor) * Input.GetAxis("Vertical") * Time.deltaTime;
transform.position += transform.right * (normalMoveSpeed * slowMoveFactor) * Input.GetAxis("Horizontal") * Time.deltaTime;
if (Input.GetKey (KeyCode.E)) {transform.position += transform.up * climbSpeed * slowMoveFactor * Time.deltaTime;}
if (Input.GetKey (KeyCode.Q)) {transform.position -= transform.up * climbSpeed * slowMoveFactor * Time.deltaTime;}
} else { // normal speed moves
transform.position += transform.forward * normalMoveSpeed * Input.GetAxis("Vertical") * Time.deltaTime;
transform.position += transform.right * normalMoveSpeed * Input.GetAxis("Horizontal") * Time.deltaTime;
if (Input.GetKey (KeyCode.E)) {transform.position += transform.up * climbSpeed * Time.deltaTime;}
if (Input.GetKey (KeyCode.Q)) {transform.position -= transform.up * climbSpeed * Time.deltaTime;}
}
}
// This moves the camera to homePos, but flips it to reverse start rotation, not the given rotation...
if (Input.GetKeyDown (KeyCode.Home)) {
MoveCameraPosRot (mainCamera,homePos,homeRot);
}
if (Input.GetKeyDown (KeyCode.End)) {
Screen.lockCursor = (Screen.lockCursor == false) ? true : false;
}
}
void MoveCameraPosRot (Camera cam, Vector3 pos, Vector3 rot) {
// Set position:
cam.transform.position = pos;
// Set rotation:
cam.transform.eulerAngles = rot;
}
}