https://blog.csdn.net/qq_15807167/article/details/52048998?locationNum=8&fps=1
最近有点忙,只有挤时间去了解VR这方面的Demo了,之前关注了一个Android平台的视频VR的demo研读的差不多了,现在开始关注Unity3d建造VR游戏环境的demo.
Android下demo例子地址。
* https://github.com/ashqal/MD360Player4AndroidVR视频播放demo
* https://github.com/ejeinc/RajawaliCardboardExamplecardboard的demo
正题
- 准备demo地址http://unity3d.com/cn/learn/tutorials/topics/virtual-reality点击跳转到VR sample界面
- unity3d
- vistual studio 2015
- 现在还等啥 嗨起来
前提界面介绍
初始界面。
主界面
飞机游戏的模型界面
迷宫的模型界面
射击场景有两个 一个是180度的视角比较远,一个是360度的视角(等距离)
了解结构
- 我们从飞机那个unity的工程中来看,其Hierarchy的结构(主要讲重点部分)
- 首先我们从相机开始说起
- 首先我们从每个C#脚本去看实现的过程
VREyeRaycaster.cs
- 首先我们从每个C#脚本去看实现的过程
using System;
using UnityEngine;
namespace VRStandardAssets.Utils
{
// In order to interact with objects in the scene
// this class casts a ray into the scene and if it finds
// a VRInteractiveItem it exposes it for other classes to use. // This script should be generally be placed on the camera. //为了与场景中的物体相互作用 //这个类投射到场景中的光线,如果它发现 // vrinteractiveitem它暴露了它的其他类使用。 //这个脚本应该被放置在相机上。 public class VREyeRaycaster : MonoBehaviour { public event Action<RaycastHit> OnRaycasthit; // This event is called every frame that the user's gaze is over a collider. [SerializeField] private Transform m_Camera; [SerializeField] private LayerMask m_ExclusionLayers; // Layers to exclude from the raycast. 光线投射的层 [SerializeField] private Reticle m_Reticle; // The reticle, if applicable.网线 [SerializeField] private VRInput m_VrInput; // Used to call input based events on the current VRInteractiveItem. [SerializeField] private bool m_ShowDebugRay; // Optionally show the debug ray.是否显示调试光线 [SerializeField] private float m_DebugRayLength = 5f; // Debug ray length.光线的长度 [SerializeField] private float m_DebugRayDuration = 1f; // How long the Debug ray will remain visible.光线存活时间 [SerializeField] private float m_RayLength = 500f; // How far into the scene the ray is cast.光线投射到场景的距离 private VRInteractiveItem m_CurrentInteractible; //The current interactive item 当前交互点 private VRInteractiveItem m_LastInteractible; //The last interactive item 最后交互点 //从其他类中获得当前的交互点 // Utility for other classes to get the current interactive item public VRInteractiveItem CurrentInteractible { get { return m_CurrentInteractible; } } private void OnEnable() { m_VrInput.OnClick += HandleClick; m_VrInput.OnDoubleClick += HandleDoubleClick; m_VrInput.OnUp += HandleUp; m_VrInput.OnDown += HandleDown; } private void OnDisable () { m_VrInput.OnClick -= HandleClick; m_VrInput.OnDoubleClick -= HandleDoubleClick; m_VrInput.OnUp -= HandleUp; m_VrInput.OnDown -= HandleDown; } //unity的更新方法 private void Update() { EyeRaycast(); } private void EyeRaycast() { //如果是必要的话,则显示调试线(在unity上设置 ) // Show the debug ray if required if (m_ShowDebugRay) { Debug.DrawRay(m_Camera.position, m_Camera.forward * m_DebugRayLength, Color.blue, m_DebugRayDuration); } //创建相机前方的光线 // Create a ray that points forwards from the camera. Ray ray = new Ray(m_Camera.position, m_Camera.forward); RaycastHit hit; //这里主要判断前方光线的展示与否 // Do the raycast forweards to see if we hit an interactive item if (Physics.Raycast(ray, out hit, m_RayLength, ~m_ExclusionLayers)) { VRInteractiveItem interactible = hit.collider.GetComponent<VRInteractiveItem>(); //attempt to get the VRInteractiveItem on the hit object m_CurrentInteractible = interactible; // If we hit an interactive item and it's not the same as the last interactive item, then call Over if (interactible && interactible != m_LastInteractible) interactible.Over(); // Deactive the last interactive item if (interactible != m_LastInteractible) DeactiveLastInteractible(); m_LastInteractible = interactible; // Something was hit, set at the hit position. if (m_Reticle) m_Reticle.SetPosition(hit); if (OnRaycasthit != null) OnRaycasthit(hit); } else { // Nothing was hit, deactive the last interactive item. DeactiveLastInteractible(); m_CurrentInteractible = null; // Position the reticle at default distance. if (m_Reticle) m_Reticle.SetPosition(); } } private void DeactiveLastInteractible() { if (m_LastInteractible == null) return; m_LastInteractible.Out(); m_LastInteractible = null; } private void HandleUp() { if (m_CurrentInteractible != null) m_CurrentInteractible.Up(); } private void HandleDown() { if (m_CurrentInteractible != null) m_CurrentInteractible.Down(); } private void HandleClick() { if (m_CurrentInteractible != null) m_CurrentInteractible.Click(); } private void HandleDoubleClick() { if (m_CurrentInteractible != null) m_CurrentInteractible.DoubleClick(); } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
VRCameraUi.cs
using System;
using UnityEngine;
namespace VRStandardAssets.Utils
{ //这类保证UI(如网线和选择吧)
//正确地设置。
// This class ensures that the UI (such as the reticle and selection bar) // are set up correctly. public class VRCameraUI : MonoBehaviour { [SerializeField] private Canvas m_Canvas; // Reference to the canvas containing the UI.包含用户界面的画布的参考。 private void Awake() { // Make sure the canvas is on.确保画布上。 m_Canvas.enabled = true; // Set its sorting order to the front.把它的排序顺序设置为前面。 m_Canvas.sortingOrder = Int16.MaxValue; // Force the canvas to redraw so that it is correct before the first render.在第一次渲染前 力将会重绘。 Canvas.ForceUpdateCanvases(); } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
SelectionRadial.cs
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System;
namespace VRStandardAssets.Utils
{
// This class is used to control a radial bar that fills // up as the user holds down the Fire1 button. When it has // finished filling it triggers an event. It also has a // coroutine which returns once the bar is filled. public class SelectionRadial : MonoBehaviour { public event Action OnSelectionComplete; // This event is triggered when the bar has filled. [SerializeField] private float m_SelectionDuration = 2f; // How long it takes for the bar to fill. [SerializeField] private bool m_HideOnStart = true; // Whether or not the bar should be visible at the start. [SerializeField] private Image m_Selection; // Reference to the image who's fill amount is adjusted to display the bar. [SerializeField] private VRInput m_VRInput; // Reference to the VRInput so that input events can be subscribed to. private Coroutine m_SelectionFillRoutine; // Used to start and stop the filling coroutine based on input. private bool m_IsSelectionRadialActive; // Whether or not the bar is currently useable. private bool m_RadialFilled; // Used to allow the coroutine to wait for the bar to fill. public float SelectionDuration { get { return m_SelectionDuration; } } private void OnEnable() { m_VRInput.OnDown += HandleDown; m_VRInput.OnUp += HandleUp; } private void OnDisable() { m_VRInput.OnDown -= HandleDown; m_VRInput.OnUp -= HandleUp; } private void Start() { // Setup the radial to have no fill at the start and hide if necessary. m_Selection.fillAmount = 0f; if(m_HideOnStart) Hide(); } public void Show() { m_Selection.gameObject.SetActive(true); m_IsSelectionRadialActive = true; } public void Hide() { m_Selection.gameObject.SetActive(false); m_IsSelectionRadialActive = false; // This effectively resets the radial for when it's shown again. m_Selection.fillAmount = 0f; } private IEnumerator FillSelectionRadial() { // At the start of the coroutine, the bar is not filled. m_RadialFilled = false; // Create a timer and reset the fill amount. float timer = 0f; m_Selection.fillAmount = 0f; // This loop is executed once per frame until the timer exceeds the duration. while (timer < m_SelectionDuration) { // The image's fill amount requires a value from 0 to 1 so we normalise the time. m_Selection.fillAmount = timer / m_SelectionDuration; // Increase the timer by the time between frames and wait for the next frame. timer += Time.deltaTime; yield return null; } // When the loop is finished set the fill amount to be full. m_Selection.fillAmount = 1f; // Turn off the radial so it can only be used once. m_IsSelectionRadialActive = false; // The radial is now filled so the coroutine waiting for it can continue. m_RadialFilled = true; // If there is anything subscribed to OnSelectionComplete call it. if (OnSelectionComplete != null) OnSelectionComplete(); } public IEnumerator WaitForSelectionRadialToFill () { // Set the radial to not filled in order to wait for it. m_RadialFilled = false; // Make sure the radial is visible and usable. Show (); // Check every frame if the radial is filled. while (!m_RadialFilled) { yield return null; } // Once it's been used make the radial invisible. Hide (); } private void HandleDown() { // If the radial is active start filling it. if (m_IsSelectionRadialActive) { m_SelectionFillRoutine = StartCoroutine(FillSelectionRadial()); } } private void HandleUp() { // If the radial is active stop filling it and reset it's amount. if (m_IsSelectionRadialActive) { if(m_SelectionFillRoutine != null) StopCoroutine(m_SelectionFillRoutine); m_Selection.fillAmount = 0f; } } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
Reticle.cs
using UnityEngine;
using UnityEngine.UI;
namespace VRStandardAssets.Utils
{
// The reticle is a small point at the centre of the screen.
// It is used as a visual aid for aiming. The position of the
// reticle is either at a default position in space or on the // surface of a VRInteractiveItem as determined by the VREyeRaycaster. public class Reticle : MonoBehaviour { [SerializeField] private float m_DefaultDistance = 5f; // The default distance away from the camera the reticle is placed. [SerializeField] private bool m_UseNormal; // Whether the reticle should be placed parallel to a surface. [SerializeField] private Image m_Image; // Reference to the image component that represents the reticle. [SerializeField] private Transform m_ReticleTransform; // We need to affect the reticle's transform. [SerializeField] private Transform m_Camera; // The reticle is always placed relative to the camera. private Vector3 m_OriginalScale; // Since the scale of the reticle changes, the original scale needs to be stored. private Quaternion m_OriginalRotation; // Used to store the original rotation of the reticle. public bool UseNormal { get { return m_UseNormal; } set { m_UseNormal = value; } } public Transform ReticleTransform { get { return m_ReticleTransform; } } private void Awake() { // Store the original scale and rotation. m_OriginalScale = m_ReticleTransform.localScale; m_OriginalRotation = m_ReticleTransform.localRotation; } public void Hide() { m_Image.enabled = false; } public void Show() { m_Image.enabled = true; } // This overload of SetPosition is used when the the VREyeRaycaster hasn't hit anything. public void SetPosition () { // Set the position of the reticle to the default distance in front of the camera. m_ReticleTransform.position = m_Camera.position + m_Camera.forward * m_DefaultDistance; // Set the scale based on the original and the distance from the camera. m_ReticleTransform.localScale = m_OriginalScale * m_DefaultDistance; // The rotation should just be the default. m_ReticleTransform.localRotation = m_OriginalRotation; } // This overload of SetPosition is used when the VREyeRaycaster has hit something. public void SetPosition (RaycastHit hit) { m_ReticleTransform.position = hit.point; m_ReticleTransform.localScale = m_OriginalScale * hit.distance; // If the reticle should use the normal of what has been hit... if (m_UseNormal) // ... set it's rotation based on it's forward vector facing along the normal. m_ReticleTransform.rotation = Quaternion.FromToRotation (Vector3.forward, hit.normal); else // However if it isn't using the normal then it's local rotation should be as it was originally. m_ReticleTransform.localRotation = m_OriginalRotation; } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
VR CameraaFade
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Audio; namespace VRStandardAssets.Utils { // This class is used to fade the entire screen to black (or // any chosen colour). It should be used to smooth out the // transition between scenes or restarting of a scene. public class VRCameraFade : MonoBehaviour { public event Action OnFadeComplete; // This is called when the fade in or out has finished. [SerializeField] private Image m_FadeImage; // Reference to the image that covers the screen. [SerializeField] private AudioMixerSnapshot m_DefaultSnapshot; // Settings for the audio mixer to use normally. [SerializeField] private AudioMixerSnapshot m_FadedSnapshot; // Settings for the audio mixer to use when faded out. [SerializeField] private Color m_FadeColor = Color.black; // The colour the image fades out to. [SerializeField] private float m_FadeDuration = 2.0f; // How long it takes to fade in seconds. [SerializeField] private bool m_FadeInOnSceneLoad = false; // Whether a fade in should happen as soon as the scene is loaded. [SerializeField] private bool m_FadeInOnStart = false; // Whether a fade in should happen just but Updates start. private bool m_IsFading; // Whether the screen is currently fading. private float m_FadeStartTime; // The time when fading started. private Color m_FadeOutColor; // This is a transparent version of the fade colour, it will ensure fading looks normal. public bool IsFading { get { return m_IsFading; } } private void Awake() { m_FadeOutColor = new Color(m_FadeColor.r, m_FadeColor.g, m_FadeColor.b, 0f); m_FadeImage.enabled = true; } private void Start() { // If applicable set the immediate colour to be faded out and then fade in. if (m_FadeInOnStart) { m_FadeImage.color = m_FadeColor; FadeIn(true); } } private void OnLevelWasLoaded() { // If applicable set the immediate colour to be faded out and then fade in. if (m_FadeInOnSceneLoad) { m_FadeImage.color = m_FadeColor; FadeIn(true); } } // Since no duration is specified with this overload use the default duration. public void FadeOut(bool fadeAudio) { FadeOut(m_FadeDuration, fadeAudio); } public void FadeOut(float duration, bool fadeAudio) { // If not already fading start a coroutine to fade from the fade out colour to the fade colour. if (m_IsFading) return; StartCoroutine(BeginFade(m_FadeOutColor, m_FadeColor, duration)); // Fade out the audio over the same duration. if(m_FadedSnapshot && fadeAudio) m_FadedSnapshot.TransitionTo (duration); } // Since no duration is specified with this overload use the default duration. public void FadeIn(bool fadeAudio) { FadeIn(m_FadeDuration, fadeAudio); } public void FadeIn(float duration, bool fadeAudio) { // If not already fading start a coroutine to fade from the fade colour to the fade out colour. if (m_IsFading) return; StartCoroutine(BeginFade(m_FadeColor, m_FadeOutColor, duration)); // Fade in the audio over the same duration. if(m_DefaultSnapshot && fadeAudio) m_DefaultSnapshot.TransitionTo (duration); } public IEnumerator BeginFadeOut (bool fadeAudio) { // Fade out the audio over the default duration. if(m_FadedSnapshot && fadeAudio) m_FadedSnapshot.TransitionTo (m_FadeDuration); yield return StartCoroutine(BeginFade(m_FadeOutColor, m_FadeColor, m_FadeDuration)); } public IEnumerator BeginFadeOut(float duration, bool fadeAudio) { // Fade out the audio over the given duration. if(m_FadedSnapshot && fadeAudio) m_FadedSnapshot.TransitionTo (duration); yield return StartCoroutine(BeginFade(m_FadeOutColor, m_FadeColor, duration)); } public IEnumerator BeginFadeIn (bool fadeAudio) { // Fade in the audio over the default duration. if(m_DefaultSnapshot && fadeAudio) m_DefaultSnapshot.TransitionTo (m_FadeDuration); yield return StartCoroutine(BeginFade(m_FadeColor, m_FadeOutColor, m_FadeDuration)); } public IEnumerator BeginFadeIn(float duration, bool fadeAudio) { // Fade in the audio over the given duration. if(m_DefaultSnapshot && fadeAudio) m_DefaultSnapshot.TransitionTo (duration); yield return StartCoroutine(BeginFade(m_FadeColor, m_FadeOutColor, duration)); } private IEnumerator BeginFade(Color startCol, Color endCol, float duration) { // Fading is now happening. This ensures it won't be interupted by non-coroutine calls. m_IsFading = true; // Execute this loop once per frame until the timer exceeds the duration. float timer = 0f; while (timer <= duration) { // Set the colour based on the normalised time. m_FadeImage.color = Color.Lerp(startCol, endCol, timer / duration); // Increment the timer by the time between frames and return next frame. timer += Time.deltaTime; yield return null; } // Fading is finished so allow other fading calls again. m_IsFading = false; // If anything is subscribed to OnFadeComplete call it. if (OnFadeComplete != null) OnFadeComplete(); } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
ReturnToMainMenu.cs
using UnityEngine;
using System.Collections;
using UnityEngine.SceneManagement;
namespace VRStandardAssets.Utils
{
// This class simply allows the user to return to the main menu.
public class ReturnToMainMenu : MonoBehaviour { [SerializeField] private string m_MenuSceneName = "MainMenu"; // The name of the main menu scene. [SerializeField] private VRInput m_VRInput; // Reference to the VRInput in order to know when Cancel is pressed. [SerializeField] private VRCameraFade m_VRCameraFade; // Reference to the script that fades the scene to black. private void OnEnable () { m_VRInput.OnCancel += HandleCancel; } private void OnDisable () { m_VRInput.OnCancel -= HandleCancel; } private void HandleCancel () { StartCoroutine (FadeToMenu ()); } private IEnumerator FadeToMenu () { // Wait for the screen to fade out. yield return StartCoroutine (m_VRCameraFade.BeginFadeOut (true)); // Load the main menu by itself. SceneManager.LoadScene(m_MenuSceneName, LoadSceneMode.Single); } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
VR Tracking Reset.cs
using UnityEngine;
using UnityEngine.VR;
namespace VRStandardAssets.Utils
{
// This class simply insures the head tracking behaves correctly when the application is paused.
public class VRTrackingReset : MonoBehaviour { private void OnApplicationPause(bool pauseStatus) { InputTracking.Recenter(); } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
以上是相机中的C#脚步代码,本来想全部注释的,后来发现英文解释很全,这里大家看英文介绍就好。有空再写环境的demo实现。