280 likes | 483 Views
Re-making a Classic in VR. Chris Zaharia. @ chrisjz # SydVR. Project. Engine - Unity 3D (v4.5) View + head tracking - Oculus Rift DK1 Hand tracking - Razer Hydra C# Language Re-create game up to first mission in first world. To Do. Integrate Rift with player
E N D
Re-making a Classic in VR Chris Zaharia @chrisjz #SydVR
Project • Engine - Unity 3D (v4.5) • View + head tracking - Oculus Rift DK1 • Hand tracking - Razer Hydra • C# Language • Re-create game up to first mission in first world
To Do • Integrate Rift with player • Integrate Razer Hydra with player’s hands • Player avatar • Player movement • Hand interaction with objects
Setup World • Start a new project • Create a scene • Import or create an environment • Create a Directional Light to light up the environment
Create Player & Integrate Rift • Create a GameObject called Player • Attach the component Character Controller • Attach the scripts Character Motor and FPSInput Controller • Attach OVRCameraController prefab as child of Player GameObject • In script FPSInput Controller set “Ovr Camera” variable as CameraRight • Drag OVRCameraControllerbehind player
FPSInputController.cs using UnityEngine; using System.Collections; // Require a character controller to be attached to the same game object [RequireComponent(typeof(CharacterMotor))] [AddComponentMenu("Character/FPS Input Controller")] public class FPSInputController : MonoBehaviour { public GameObjectovrCamera; private CharacterMotor motor; private boolinputEnabled; // If input is enabled/disabled // Use this for initialization void Awake (){ motor = GetComponent<CharacterMotor>(); } void Start() { inputEnabled = true; }
FPSInputController.cs (2) // Update is called once per frame void Update (){ // Get the input vector from keyboard or analog stick Vector3 directionVector= new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")); motor.inputJump = Input.GetButton ("Jump"); // Play jumping audio clips if (initialJumpAudioClips.Length > 0 && motor.inputJump && motor.grounded && !audio.isPlaying) { audio.clip = initialJumpAudioClips[Random.Range(0, initialJumpAudioClips.Length)]; audio.Play(); } if (directionVector != Vector3.zero) { // Get the length of the directon vector and then normalize it // Dividing by the length is cheaper than normalizing when we already have the length anyway float directionLength= directionVector.magnitude; directionVector = directionVector / directionLength;
FPSInputController.cs (3) // Make sure the length is no bigger than 1 directionLength = Mathf.Min(1, directionLength); // Make the input vector more sensitive towards the extremes and less sensitive in the middle // This makes it easier to control slow speeds when using analog sticks directionLength = directionLength * directionLength; // Multiply the normalized direction vector by the modified length directionVector = directionVector * directionLength; } // Apply the direction to the CharacterMotor motor.inputMoveDirection = ovrCamera.transform.rotation * directionVector; } public void SetInputEnabled (bool status) { inputEnabled = status; } }
Attach avatar to player • Place player 3D model as child of Player object and name it “Avatar” • Move the OVR camera object behind the model • Make the model’s head rotate with the Rift’s head tracker by moving the head model inside the CameraRight object
Hydra Integration for Hands • Import Sixense Unity plugin for Razer integration • Create GameObject “Hands” as child of OVRCameraController • Place SixenseInput prefab as child of OVRCamera Controller
Hydra Integration for Hands • Create GameObjects “Left Hand” + “Right Hand” as children of “Hands” • Place the player’s hand models within each of those 2 hand objects respectively • Attach script SixenseHandController to each hand GameObject • Set the “Hand” variable to either LEFT or RIGHT, as appropriate
Control Player using Hydra • Modify FPSInputController script to map Hydra’s left joystick and a button to moving and jumping • Create PlayerLook script in project to handle looking in environment • Create HydraLook script to map Hydra’s right joystick to looking, inheriting PlayerLookclass in its script
FPSInputController.cs void Start() { IgnorePlayerColliders (); inputEnabled = true; } // Update is called once per frame void Update (){ // Get the input vector from keyboard or analog stick Vector3 directionVector= new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")); // Get the input vector from hydra SixenseInput.ControllerhydraLeftController = SixenseInput.GetController (SixenseHands.LEFT); SixenseInput.ControllerhydraRightController = SixenseInput.GetController (SixenseHands.RIGHT); if (hydraLeftController != null) { directionVector= new Vector3(hydraLeftController.JoystickX, 0, hydraLeftController.JoystickY); } if (hydraRightController != null) { motor.inputJump = hydraRightController.GetButton (SixenseButtons.BUMPER); } else { motor.inputJump = Input.GetButton ("Jump"); }
FPSInputController.cs (2) … } … // Prevent colliders on player from colliding with each other i.e. hand colliders with body collider void IgnorePlayerColliders () { Collider[] cols = GetComponentsInChildren<Collider>(); foreach (Collider col in cols) { if (col != collider) { Physics.IgnoreCollision(col, collider); } } } }
PlayerLook.cs using UnityEngine; using System.Collections; /// PlayerLook rotates the transform based on the input device's delta. /// Minimum and Maximum values can be used to constrain the possible rotation. /// Based on Unity's MouseLook script. [AddComponentMenu("Camera-Control/Player Look")] public class PlayerLook : MonoBehaviour { public enumRotationAxes { XAndY = 0, X = 1, Y = 2 } public RotationAxes axes = RotationAxes.XAndY; public float sensitivityX = 15F; public float sensitivityY = 15F; public float minimumX = -360F; public float maximumX = 360F; public float minimumY = -60F; public float maximumY = 60F; protected float rotationY = 0F;
PlayerLook.cs (2) protected float axisX, axisY; void Start () { // Make the rigid body not change rotation if (rigidbody) rigidbody.freezeRotation = true; } protected virtual void Update () { if (axes == RotationAxes.XAndY) { float rotationX = transform.localEulerAngles.y + axisX * sensitivityX; rotationY += axisY * sensitivityY; rotationY = Mathf.Clamp (rotationY, minimumY, maximumY); transform.localEulerAngles = new Vector3(-rotationY, rotationX, 0); } else if (axes == RotationAxes.X) { transform.Rotate(0, axisX * sensitivityX, 0); }
PlayerLook.cs (3) else { rotationY += axisY * sensitivityY; rotationY = Mathf.Clamp (rotationY, minimumY, maximumY); transform.localEulerAngles = new Vector3(-rotationY, transform.localEulerAngles.y, 0); } } }
HydraLook.cs using UnityEngine; using System.Collections; public class HydraLook : PlayerLook { protected override void Update () { // Get the input vector from hydra SixenseInput.ControllerhydraRightController = SixenseInput.GetController (SixenseHands.RIGHT); if (hydraRightController != null) { axisX = hydraRightController.JoystickX; axisY = hydraRightController.JoystickY; } base.Update (); } }
Hand Interactions using Hydra • Attach invisible rectangles to each hand to use as colliders for hands • Name the objects LeftHandCollider and RightHandCollider respectively • Modify the SixenseHandController with functions to grab objects and throw based on hand velocity • Tag any grabbable objects with “Grabbable” and give them a rigidbody for physics
SixenseHandController.cs public class SixenseHandController : SixenseObjectController { public float minGrabDistance = 1.0f; // Grabbable object must be within this distance from hand colliders to be picked up public float throwForce = 30.0f; // Force multiplyer for throwing objects private boolisHoldingObject = false; private GameObjectclosestObject = null; private GrabObjectgrabObject; // Script attached to grabbed object with grappling data on that object private float handVelocity; private Vector3 handVector; private Vector3 handPrevious; …
SixenseHandController.cs (2) protected override void UpdateObject( SixenseInput.Controller controller ) { … if ( controller.Enabled ) { // Animation update UpdateAnimationInput( controller ); // Action update UpdateActionInput ( controller ); } base.UpdateObject(controller); } …
SixenseHandController.cs (3) protected void UpdateActionInput( SixenseInput.Controller controller) { Vector3 currentPosition = new Vector3(); Quaternion currentRotation = new Quaternion(); Velocity(); if (isHoldingObject && !controller.GetButton(SixenseButtons.TRIGGER)) { Throw(); isHoldingObject = false; } if (Hand == SixenseHands.LEFT) { currentPosition = GameObject.Find("LeftHandCollider").transform.position; currentRotation = GameObject.Find("LeftHandCollider").transform.rotation; } if (Hand == SixenseHands.RIGHT) { currentPosition = GameObject.Find("RightHandCollider").transform.position; currentRotation = GameObject.Find("RightHandCollider").transform.rotation; }
SixenseHandController.cs (4) if (!isHoldingObject) { foreach (GameObject o in GameObject.FindGameObjectsWithTag ("Grabbable")) { float dist = Vector3.Distance(o.transform.position, currentPosition); if (dist < minGrabDistance) { closestObject = o; } } }
SixenseHandController.cs (5) if (closestObject != null && Vector3.Distance(closestObject.transform.position, currentPosition) < minGrabDistance && controller.GetButton(SixenseButtons.TRIGGER)) { if (closestObject.rigidbody && closestObject.rigidbody.isKinematic) { return; } grabObject = closestObject.GetComponent<GrabObject>(); if (grabObject && grabObject.isEnabled) { closestObject.transform.position = currentPosition + grabObject.GetPosition(Hand); closestObject.transform.rotation = currentRotation * Quaternion.Euler(grabObject.GetRotation(Hand)); } else { closestObject.transform.position = currentPosition; closestObject.transform.rotation = currentRotation; } isHoldingObject = true; } }
SixenseHandController.cs (6) // Calculate velocity of hand protected void Velocity () { if (Time.deltaTime != 0) { handVector = (transform.position - handPrevious) / Time.deltaTime; handPrevious = transform.position; } handVelocity = Vector3.Magnitude(handVector); } // Throw the held object once player lets go based on hand velocity protected void Throw () { if (closestObject.rigidbody) { Vector3 dir = (closestObject.transform.position - transform.position).normalized; closestObject.rigidbody.AddForce(dir * handVelocity * throwForce); } }
Next • Presentation slides will be up online • Project will be open sourced once beta is completed • YouTube video showing a play through
Future • Integrate alternative and supporting devices i.e. STEM, Leap Motion, MYO, Omni + • Re-make further (complete?) • Boilerplate? • Re-create other classics (Super Smash Bros?)