和一些小伙伴们一起做了一个关于香港的VR,由于大家之前都没有接触过所以做的比较草率。即使如此,还是学到了不少东西。
1. 第一人称视角
创建一个胶囊(Capsule),重命名为Player,把主摄像机(Main Camera)放到Player下。创建脚本FPSInput,实现可以调节速度的人物移动。代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FPSInput : MonoBehaviour
{
public float speed = 1.0f;
public float gravity = -9.8f; //重力
private float deltaX = 0.0f;
private float deltaZ = 0.0f;
private CharacterController _characterController;
// Start is called before the first frame update
void Start()
{
_characterController = GetComponent<CharacterController>();
}
// Update is called once per frame
void Update()
{
deltaX = Input.GetAxis("Horizontal") * speed;
deltaZ = Input.GetAxis("Vertical") * speed;
Vector3 movement = new Vector3(deltaX, 0, deltaZ);
movement = Vector3.ClampMagnitude(movement, speed);
movement.y = gravity;
movement *= Time.deltaTime;
movement = transform.TransformDirection(movement);
_characterController.Move(movement);
}
}
在Player下增加组件Character Controller,Rigidbody,把这个脚本放到Player下。这个脚本能够实现角色的移动,但是不能实现视野的变换。新建脚本MouseLook,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MouseLook : MonoBehaviour
{
public enum RotationAxes
{
MouseXAndY = 0,
MouseX = 1,
MouseY = 2
}
public RotationAxes axes = RotationAxes.MouseXAndY;
public float sensitivityHor = 9.0f;
public float sensitivityVert = 9.0f;
public float minimumVert = -45.0f;
public float maximumVert = 45.0f;
private float _rotationX = 0.0f;
// Start is called before the first frame update
void Start()
{
Rigidbody body = GetComponent<Rigidbody>();
if (body != null)
{
body.freezeRotation = true;
}
}
// Update is called once per frame
void Update()
{
if(axes == RotationAxes.MouseX)
{
transform.Rotate(0, Input.GetAxis("Mouse X") * sensitivityHor, 0);
}
else if(axes == RotationAxes.MouseY)
{
_rotationX -= Input.GetAxis("Mouse Y") * sensitivityVert;
_rotationX = Mathf.Clamp(_rotationX, minimumVert, maximumVert);
float rotationY = transform.localEulerAngles.y;
transform.localEulerAngles = new Vector3(_rotationX, rotationY, 0.0f);
}
else
{
_rotationX -= Input.GetAxis("Mouse Y") * sensitivityVert;
_rotationX = Mathf.Clamp(_rotationX, minimumVert, maximumVert);
float delta = Input.GetAxis("Mouse X") * sensitivityHor;
float rotationY = transform.localEulerAngles.y + delta;
transform.localEulerAngles = new Vector3(_rotationX, rotationY, 0.0f);
}
}
}
把这个脚本放到Player和Main Camera下,在Player中选择MouseX,使物体只能横向旋转,在Main Camera下选择MouseY,使摄像机只能纵向变换。
2. 场景切换
打开File->Build Setting,添加所有的场景,使所有场景都有属于自己的Build Index。
新建脚本ChangeScenes,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class ChangeScenes : MonoBehaviour
{
private int ScenesCount = 5;
private Scene sm;
// Start is called before the first frame update
void Start()
{
sm = SceneManager.GetActiveScene();
}
// Update is called once per frame
void Update()
{
}
private void OnTriggerEnter(Collider other)
{
for(int i=0; i<ScenesCount; i++)
{
if (other.gameObject.CompareTag(i.ToString()))
{
SceneManager.UnloadSceneAsync(i, UnloadSceneOptions.UnloadAllEmbeddedSceneObjects);
SceneManager.LoadScene(++i, LoadSceneMode.Additive);
other.gameObject.SetActive(false);
break;
}
}
}
}
这个脚本的大概意思是走到一个地方检测是否发生碰撞,如果有碰撞检测它的标志(Tag),如果它是我们想要切换到Build Index下一个场景的标志,首先关掉当前场景,然后读取下一个场景,同时使标志物消失防止在读取下一个场景的时间内不断激发此函数从而陷入循环。所以此脚本附在Player下。
设置Tag的方法,可以参考前一篇《滚球游戏》。
3.锁死鼠标
新建脚本附在Main Camera下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RayShooter : MonoBehaviour
{
private Camera _camera;
// Use this for initialization
void Start()
{
_camera = GetComponent<Camera>();
//隐藏屏幕中心的光标
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}
private void OnGUI()
{
int size = 12;
float posX = _camera.pixelWidth / 2 - size / 4;
float posY = _camera.pixelHeight / 2 - size / 2;
GUI.Label(new Rect(posX, posY, size, size), "*"); //GUI.Label()命令在屏幕上显示文本
}
// Update is called once per frame
void Update()
{
}
}
大概意思是将光标锁死在屏幕中心,并且设置不可解。同时利用UI,在屏幕中央打一个" * "。
4.按钮
在本项目中该按钮的作用是从主场景切换到其他场景,其中主场景无法隐藏。在Hierarchy中新建UI->Button,此时会出现Canvas和EventSystem。在Canvas下有Button,再向下有Text,这里是Button中心的文字。新建脚本附在Button下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class MajorToTST : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
GameObject btnObj = GameObject.Find("Button");//"Button"为你的Button的名称
Button btn = btnObj.GetComponent<Button>();
btn.onClick.AddListener(delegate ()
{
this.GoNextScene(btnObj);
});
}
// Update is called once per frame
void Update()
{
}
public void GoNextScene(GameObject NScene)
{
SceneManager.UnloadSceneAsync(0, UnloadSceneOptions.UnloadAllEmbeddedSceneObjects);
SceneManager.LoadScene(1, LoadSceneMode.Additive);
NScene.SetActive(false);
}
}
5.显示动画
在Project页面向右寻找能找到Animation卡片。点击create,保存动画。在时间轴合适的位置创建关键帧,改变物体状态。在Inspector界面下取消Loop Time。附加到你想要的物体上。
在Project界面下新建Animator Controller,作为该物体Animator组件下Controller选项的内容。在Animator界面下新建空结点:右击 -> Create State -> Empty。右击空结点,选择Set as Layer Default State,然后右击选择Make Transition,指向活动节点。
在左边设置好Tigger,右边Conditions下面选择这个Trigger。新建脚本附加在Player上,实现碰撞某物后播放动画,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GetTicket : MonoBehaviour
{
public Animator animator;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag("2.1"))
{
animator.SetTrigger("ticket");
other.gameObject.SetActive(false);
}
}
}
6.显示文字
新建脚本ShowName,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ShowName : MonoBehaviour
{
public string InGUI;
public string MousePass;
bool isShowTip;
public bool WindowShow = false;
// Use this for initialization
void Start()
{
isShowTip = false;
}
void OnMouseEnter()
{
isShowTip = true;
}
void OnMouseExit()
{
isShowTip = false;
}
void OnGUI()
{
if (isShowTip)
{
GUIStyle style1 = new GUIStyle();
style1.fontSize = 30;
style1.normal.textColor = Color.red;
GUI.Label(new Rect(Input.mousePosition.x, Screen.height - Input.mousePosition.y, 400, 50), MousePass, style1);
}
if (WindowShow)
GUI.Window(0, new Rect(30, 30, 200, 100), MyWindow, "Name");
}
//对话框函数
void MyWindow(int WindowID)
{
GUILayout.Label(InGUI);
}
//鼠标点击事件
void OnMouseDown()
{
Debug.Log("show");
if (WindowShow)
WindowShow = false;
else
WindowShow = true;
}
}
在想要显示的物体上添加Box Collider组件,再附上此文本。MousePass是鼠标滑过是显示的文字,不想显示可以设置为空。InGUI是鼠标点击时的文字,会出现在一个新的GUI界面下,再点击终止显示。
7.天空盒
一般来说,天空盒有两种构造方法,一个是给摄像机加天空盒,一个是给场景加天空盒。在这个项目中我们用的是给相机添加天空盒,这样在切换场景时,不同的摄像机看到的天空时不一样的,避免了场景切换时遗留的天空对下一个场景的影响。
首先新建一个材质Sky,在Inspector界面下找shader,选择skybox。具体的设置可以自由发挥。通常是6个面设置和CubeMap。
7.1 在摄像机中添加天空盒
给摄像机新建一个组件skybox,选择刚刚创建的材质Sky。
7.2 在场景中添加天空盒
不同版本的Unity可能不同,我的时2018.3的版本,他在Window->Rendering->Lighting Setting中,找Skybox Material进行修改。
参考文献
- 鼠标经过或点击显示文字:https://blog.youkuaiyun.com/HanGuangFei/article/details/78094214
- 天空盒:https://blog.youkuaiyun.com/www11e/article/details/78765147
- 按钮:https://blog.youkuaiyun.com/u014581901/article/details/51325955