寫在前面
AR簡介
增強現實(Argumented Reality (AR))是一種將真實世界信息和虛擬世界信息“無縫”集成的新技術,是把原本在現實世界的一定時間空間範圍內很難體驗到的實體信息(視覺信息,聲音,味道,觸覺等),通過電腦等科學技術,模擬仿真後再疊加,將虛擬的信息應用到真實世界,被人類感官所感知,從而達到超越現實的感官體驗。
混合現實(Mixed reality (MR)) 有時被稱爲超現實(hybrid reality),是真實和虛擬世界的合併,產生新的可視化環境,物理和數字對象實時共存且在其中交互。混合現實不僅發生在物理世界或虛擬世界中,而是融合了現實和虛擬現實,通過身臨其境的技術包含增強現實和增強虛擬。
實際應用中,我們對 AR 和 MR 技術通常是不區分,簡稱 AR/MR 技術。
Vuforia使用
Vuforia一直是開發者最青睞的AR SDK,衆多的功能以及高質量的識別技術,良好的跨平臺性和兼容性,兼容目前主流的PC,Android,IOS平臺。 Vuforia 引擎支持 Unity 引擎以及三個主要平臺:iOS、Android 和 UWP (Windows)。所以它收到許多開發者的喜愛。
Unity內嵌的Vuforia模塊
使用Unity2017以上版本就可以在安裝的時候選擇添加Vuforia的模塊,從而能夠在Unity編輯器上使用Vuforia的組件。
或者是從官網中下載(鏈接地址)相應的安裝包。
對於2019版本,有一點坑就是需要導入相應的package,然後該包中含有一個自動執行的腳本,會將所需要安裝包的鏈接加入到Unity2019版的包管理器中,然後自動從鏈接中下載所需要的資源,但是由於服務器的國外的,所以下載速度非常慢,會造成卡住不動的現象。
所以我安裝了2018版的Unity來使用。
安裝完成之後,需要在Unity中打開相應的設置:
然後就可以在對象樹中添加相應的AR組件了:
使用Vuforia的AR組件
到官網(鏈接)註冊一個賬號,然後添加一個License Key。
然後添加數據庫:
並且添加一張圖片,作爲識別的目標:
注意圖片的識別度,星越多,越容易識別。
把數據庫下載下來:
使用AR Camera組件
進入Unity,設置playsetting:
然後就可以在Unity中添加Vuforia的AR組件了:
添加AR Camera,刪除原有的Camera。然後進行一點設置:
添加License和database:
數據庫的添加通過導入之前我們下載的包來實現。
在場景中添加Image,就會在結構樹中出現一個ImageTarget的對象。將準備好的模型添加在上面,並且調整一下位置:
運行,然後攝像頭對着圖片,就能夠顯示相應的模型了:
添加虛擬按鈕
在Image Target 的 Inspecter中找到添加虛擬按鈕的選項:
給虛擬按鈕掛載一下代碼:
using UnityEngine;
using Vuforia;
public class VirtualButtonEventHandler : MonoBehaviour, IVirtualButtonEventHandler {
public GameObject vb;
public Animator ani;
void Start() {
VirtualButtonBehaviour vbb = vb.GetComponent<VirtualButtonBehaviour>();
if(vbb){
vbb.RegisterEventHandler(this);
}
}
public void OnButtonPressed(VirtualButtonBehaviour vb) {
ani.SetTrigger("Take Off");
ani.SetBool("onPress", true);
Debug.Log("Pressed!");
}
public void OnButtonReleased(VirtualButtonBehaviour vb) {
ani.SetTrigger("Land");
ani.SetBool("onPress", false);
Debug.Log("Released!");
}
}
其中改變了模型的動畫,動畫狀態轉換圖如下:
然後爲了顯示button,可以在button的位置加一個平面對象(Plane),使其能夠顯示在屏幕中。
觸碰虛擬按鈕(需要完全覆蓋),就能發現模型能夠動起來。
項目地址:傳送門
AR小遊戲製作
利用AR組件製作一個簡單的躲避球小遊戲:
玩家控制角色(天藍色小球),向上移動(會自然下落),從而躲避衝來的障礙小球。
碰到障礙則遊戲失敗
首先還是一樣用到Vuforia的圖像數據庫,這裏直接用之前的龍紋圖片。
然後將我們控制的角色(一個天藍色小球)添加爲ImageTarget的子對象,就能夠隨着圖像的識別而顯示出來,基本佈局很簡單,上面已經有過相關的實踐了。
直接來遊戲設計部分:
首先需要有各種障礙出現,來躲避。所以這裏可以給這些障礙製成預製體,並且掛載一個控制移動的腳本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Move : MonoBehaviour
{
public Controller controller;
public float speed = 9;
Vector3 target;
float time = 0;
bool flag = true;
// Start is called before the first frame update
void Start()
{
GameObject tmp = GameObject.Find("Bird");
target = 3*tmp.transform.position - 2*this.transform.position;
controller = Director.getInstance().currentSceneController as Controller;
}
// Update is called once per frame
void Update()
{
if (controller.getState() == 0) return;
if (time > 5) {
controller.addScore();
flag = false;
Debug.Log("score: " + controller.getScore());
Destroy(this.gameObject);
return;
}
time += Time.deltaTime;
this.transform.position = Vector3.MoveTowards(transform.position,target,Time.deltaTime * speed);
// this.transform.Translate(target*speed*Time.deltaTime);
}
}
就是朝着我們控制目標的方向移動,但是這不是實時追蹤,而只是設定一個初始目標位置的方向。並且設定一段時間後就消失。
而這裏,場景控制器的作用就是不斷地拋出障礙:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Controller : MonoBehaviour, SceneController, Interaction{
public PipeFactory pipeFactory;
public int score = 0;
float time = 0;
public int state = 1;
public void loadResources() {
}
private void Start()
{
score = 0;
pipeFactory = this.gameObject.AddComponent<PipeFactory>();
Director director = Director.getInstance();
director.currentSceneController = this;
}
private void Update() {
if (state == 1) {
GameObject tmp = null;
if (time > 3)
{
tmp = pipeFactory.getPipe();
time = 0;
}
else
{
time += Time.deltaTime;
}
}
}
public int getState() {
return state;
}
public void addScore() {
score += 1;
}
public int getScore() {
return score;
}
}
然後給我們的主角,添加一個剛體屬性,使其能夠受到重力作用。還要掛載一個控制腳本,這裏暫時用鼠標點擊來觸發向上的動作:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Fly : MonoBehaviour
{
public float speed = 5.5f;
private Rigidbody rig;
public Controller controller;
// Start is called before the first frame update
void Start()
{
rig = GetComponent<Rigidbody>();
controller = Director.getInstance().currentSceneController as Controller;
}
// Update is called once per frame
void FixedUpdate()
{
if (controller == null) {
controller = Director.getInstance().currentSceneController as Controller;
return;
}
if (controller.getState() == 0) {
this.transform.localPosition = Vector3.zero;
rig.useGravity = false;
return;
}
if (this.transform.position.y < -5f) {
rig.useGravity = false;
rig.velocity = Vector3.zero;
}
else {
rig.useGravity = true;
}
if(Input.GetButtonDown("Fire1")) {
rig.velocity = Vector3.up * speed;
}
}
}
添加碰撞檢測,如果碰到了障礙就遊戲失敗:
private void OnCollisionEnter(Collision other)
{
Controller controller = Director.getInstance().currentSceneController as Controller;
controller.state = 0;
Destroy(other.gameObject);
}
使用工廠進行障礙物的創建:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PipeFactory: MonoBehaviour{
List<GameObject> activeList = new List<GameObject>();
List<GameObject> freeList = new List<GameObject>();
public GameObject getPipe() {
GameObject pipe = null;
GameObject img = GameObject.Find("ImageTarget");
if (img != null) {
if (freeList.Count > 0)
{
pipe = freeList[0].gameObject;
freeList.Remove(freeList[0]);
pipe.transform.Rotate(0,0,0);
}
else
{
pipe = Instantiate(Resources.Load<GameObject>("Prefabs/Pipe"), new Vector3(8, Random.Range(-3,3),0), Quaternion.identity);
}
pipe.SetActive(true);
pipe.transform.parent = img.transform;
float x = Random.Range(-1,1) > 0 ? 0.5333f : -0.5333f;
pipe.transform.localPosition = new Vector3(x, Random.Range(-1f,1f),0);
}
return pipe;
}
public void free(GameObject pipe) {
GameObject tmp = null;
foreach (GameObject i in activeList)
{
if (pipe.GetInstanceID() == i.gameObject.GetInstanceID())
{
tmp = i;
break;
}
}
if (tmp != null) {
tmp.gameObject.SetActive(false);
freeList.Add(tmp);
activeList.Remove(tmp);
}
}
}
使用虛擬按鈕,添加相應的事件:
使得一直按着按鈕的時候,小球會一直向上運動。
using UnityEngine;
using Vuforia;
public class VirtualButtonEventHandler : MonoBehaviour, IVirtualButtonEventHandler {
public GameObject vb;
public GameObject ball;
public float speed = 7f;
private Rigidbody rig;
public Controller controller;
bool isPressed = false;
void Start() {
VirtualButtonBehaviour vbb = vb.GetComponent<VirtualButtonBehaviour>();
if(vbb){
vbb.RegisterEventHandler(this);
}
rig = ball.GetComponent<Rigidbody>();
controller = Director.getInstance().currentSceneController as Controller;
}
public void OnButtonPressed(VirtualButtonBehaviour vb) {
if (controller == null) {
controller = Director.getInstance().currentSceneController as Controller;
return;
}
if (controller.getState() == 0) {
return;
}
isPressed = true;
Debug.Log("Pressed!");
}
public void OnButtonReleased(VirtualButtonBehaviour vb) {
isPressed = false;
Debug.Log("Released!");
}
private void FixedUpdate()
{
if (isPressed) {
rig.velocity = Vector3.up * speed;
}
}
}
還可以根據情況調整button的靈敏度:
最終效果: