國慶期間本來是想找份工作的冠句,結(jié)果目前沒有合適的轻掩。只好閉門造車。懦底。唇牧。感慨世事萬千,生命的神奇。廢話不多說丐重,三句就夠了腔召!
00.png
1、前期準備
1弥臼、PC平臺
2宴咧、資源(UI素材,粒子特效径缅,動畫等)
3掺栅、導入SteamVR
4、那個運行HTC Vive設(shè)備最少970顯卡
01.png
注意:全部選擇
2纳猪、導入3D視角
2.png
3氧卧、導入模型資源
需要那個手柄控制,就放置在那個手柄下
04.png
4氏堤、基于設(shè)備調(diào)整好模型與手柄之間的角度沙绝、距離
10.png
5、針對箭頭鼠锈,掛載腳本
設(shè)置箭頭的位置和控制箭頭的父物體闪檬,腳本在父物體掛載
05.png
6、設(shè)置弓與箭的觸發(fā)器
06.png
07.png
7购笆、實例化一個箭頭
08.png
箭頭與弓是分離的粗悯,所以在手柄控制器中,放置在string里面來達到收納箭頭同欠,控制箭頭的位置信息
10.png
12.png
8样傍、拉動弓箭
8.1箭頭控制器應(yīng)該拿到弓玄的起始位置
13.png
8.2弓箭的起始位置與拉動位置
14.png
15.png
9、箭的發(fā)射
箭頭所在的腳本:
16.png
箭頭控制器里面的方法:
17.png
18.png
射箭:
19.png
上面就是開發(fā)一款虛擬與現(xiàn)實最簡單的應(yīng)用(國外的開發(fā)牛人提供的素材)
箭頭控制器源碼:
using UnityEngine;
using System.Collections;
using System;
public class ArrowsManager : MonoBehaviour {
public float dir;
// 實例化對象
public static ArrowsManager instance;
void Awake()
{
instance = this;
}
// 過渡游戲?qū)ο? private GameObject curArrow;
// 實際的箭頭
public GameObject arrowPf;
// 獲得VR設(shè)備(因為箭頭要在控制手柄上铺遂,所以必須要有手柄對象)
public SteamVR_TrackedObject trackObj;
// 擁有箭頭位置的對象衫哥,也就是箭頭在手柄內(nèi)部位置
public GameObject stringAttachPoint;
// 開始點
public GameObject arrowStartPoint;
// 弓玄的起始位置
public GameObject StringStartPoint;
// 判斷是否觸發(fā)
private bool isAttached;
void Update () {
AttachArrow();
PullString(); // 判斷拉弓
}
// 射箭
private void Fire()
{
curArrow.transform.parent = null;
// 拿到當前箭頭的剛體組件
var r = curArrow.GetComponent<Rigidbody>();
r.useGravity = true; // 使用重力
r.velocity = curArrow.transform.forward * 50f * dir; //設(shè)置剛體的速度
// 將弓玄string還原
stringAttachPoint.transform.position = StringStartPoint.transform.position;
curArrow = null; // 射出去了,當前箭頭就為空
isAttached = false; // 射出去后襟锐,就不會在觸發(fā)了撤逢。
}
// 箭頭的實時位置
void AttachArrow()
{
if (curArrow == null)
{ // 實例化箭頭
curArrow = Instantiate(arrowPf);
// 設(shè)置箭頭的父控件
arrowPf.transform.parent = trackObj.transform;
// 設(shè)置箭頭的地方坐標
curArrow.transform.localPosition = new Vector3(0, 0, 0.256f);
// 設(shè)置角度
// Quaternion.identity就是指Quaternion(0,0,0,0),就是每旋轉(zhuǎn)前的初始角度,是一個確切的值,
// 而transform.rotation是指本物體的角度,是一個屬性變量
curArrow.transform.localRotation = Quaternion.identity;
}
}
/*
*觸發(fā)器觸發(fā)后調(diào)整箭頭位置
*/
public void AttachBowToArrow()
{
// 當前箭頭的父控件 = 手柄的位置
curArrow.transform.parent = stringAttachPoint.transform;
// 當前箭頭的本地坐標就是開始箭頭的本地坐標(開始箭頭的坐標通過賦值對象的坐標來獲取)
curArrow.transform.localPosition = arrowStartPoint.transform.localPosition;
// 當前箭頭的旋轉(zhuǎn) = 開始箭頭的旋轉(zhuǎn)
curArrow.transform.rotation = arrowStartPoint.transform.rotation;
isAttached = true; // 標志位粮坞,觸發(fā)了蚊荣,其實也就調(diào)用了拉動弓玄方法
}
/*
*拉動弓玄
*/
public void PullString()
{
if (isAttached) // 如果觸發(fā),再調(diào)整箭頭的位置
{
// InverseTransformPoint:變換位置從自身坐標到世界坐標(弓玄的本地坐標轉(zhuǎn)換成世界坐標的X(就是拉動玄的長度))
// 獲得轉(zhuǎn)換后的vector的X值
dir = StringStartPoint.transform.InverseTransformPoint(trackObj.transform.position).x;
print(StringStartPoint.transform.InverseTransformPoint(trackObj.transform.position));
// 拿到初始弓玄與手柄設(shè)備的差值
// float dis = (StringStartPoint.transform.position - trackObj.transform.position).magnitude;
// 箭頭的實際位置 = 起始位置+上面的差值
if (dir < 0)
{
dir = 0;
}
dir = dir > 0.4f ? 0.4f : dir;
stringAttachPoint.transform.localPosition = StringStartPoint.transform.localPosition + new Vector3(dir, 0, 0);
// 獲得輸入的VR手柄設(shè)備
var device = SteamVR_Controller.Input((int)ArrowsManager.instance.trackObj.index);
// 如果扣動扳機(如果處于攻擊),發(fā)射弓箭
if (device.GetTouch(SteamVR_Controller.ButtonMask.Trigger))
{
Fire(); // 開火
}
}
}
}
箭頭掛載的腳本:
using UnityEngine;
using System.Collections;
using System;
public class Arrows : MonoBehaviour {
private bool isFire;
private bool isAttached;
void Update () {
if (isFire)
{ //當前的朝向 當前的位置+當前剛體的速率
// LookAt: 朝向捞蚂,是一個相對坐標
transform.LookAt(transform.position + transform.GetComponent<Rigidbody>().velocity);
}
}
// 觸發(fā)器(API)
void OnTriggerEnter(Collider c)
{
AttackArrow();
AttackEnemy(c);
}
// 根據(jù)傳入的碰撞器標簽妇押,來攻擊怪物
private void AttackEnemy(Collider c)
{
if (c.tag == "Enemy")
{ // 拿到碰撞器所在物體的《怪物》腳本執(zhí)行TakeDamage方法
c.gameObject.GetComponent<Enmy>().TakeDamage();
}
}
public void Fire()
{
isFire = true;
}
// 攻擊
public void AttackArrow()
{
// 獲得輸入的VR手柄設(shè)備
var device = SteamVR_Controller.Input((int)ArrowsManager.instance.trackObj.index);
// 如果扣動扳機(如果處于攻擊)
if (isAttached == false && device.GetTouch(SteamVR_Controller.ButtonMask.Trigger))
{
// 拿到Arrowsmanager,調(diào)用箭頭的位置
ArrowsManager.instance.AttachBowToArrow();
isAttached = true;
}
}
}
怪物生成腳本:
using UnityEngine;
using System.Collections;
public class EnemySpawner : MonoBehaviour {
// 起始路點
public PathNode m_startNode;
// 保存所有的從XML讀取的數(shù)據(jù)
ArrayList m_enemyList;
// 存儲敵人出場順序
public TextAsset xmldata;
// 出場敵人的序列號
int m_index = 0;
// 距離下一個敵人的出場時間
float m_timer = 0;
int liveEnemy;
void Start () {
ReadXML();
// 獲取初始敵人
SpawnData date = (SpawnData)m_enemyList[m_index];
m_timer = date.wait;
}
void Update () {
SpawnEnemy();
}
// 每個一定時間生成一個敵人
void SpawnEnemy()
{
if (m_index >= m_enemyList.Count)
{
return;
}
// 更新時間姓迅,等待下一個敵人
m_timer -= Time.deltaTime;
if (m_timer > 0)
{
return;
}
// 獲取下一個敵人的數(shù)據(jù)
SpawnData data = (SpawnData)m_enemyList[m_index];
// 如果下一個敵人是下一波敲霍,需要等待前一波敵人全部銷毀
if (GameManager.Instance.wave < data.wave)
{
if (liveEnemy > 0)
{
return;
}
else
{
GameManager.Instance.wave = data.wave; // 更新wave數(shù)值
}
}
m_index++;
if (m_index < m_enemyList.Count)
{
m_timer = ((SpawnData)m_enemyList[m_index]).wait;// 更新等待的時間
}
// 讀取敵人的模型
GameObject enemymodel = Resources.Load<GameObject>(data.enemyname);
// Debug.Log(" 調(diào)試 "+m_startNode.transform.position);
// 實例化敵人的模型俊马,并轉(zhuǎn)向第一個路點
Vector3 dir = m_startNode.transform.position - this.transform.position;
// 預設(shè)物,位置,旋轉(zhuǎn)角度
GameObject enmeyObj = (GameObject)Instantiate(enemymodel,this.transform.position, Quaternion.LookRotation(dir));
// 添加Enemy
Enmy eney = enmeyObj.AddComponent<Enmy>();
// 設(shè)置敵人出發(fā)點
eney.curNode = m_startNode;
Debug.Log(m_startNode.transform.position+" 設(shè)置敵人出發(fā)點 ");
// 根據(jù)data.level設(shè)置敵人數(shù)值肩杈,本示例只是簡單的根據(jù)波數(shù)增加敵人的生命
eney.m_life = data.level * 3;
eney.m_maxlife = data.level * 3;
// 更新存活敵人數(shù)量
liveEnemy++;
// 為敵人指定死亡動作柴我,當敵人死亡回調(diào)減少敵人數(shù)量
OnEnmyDeath(eney, (Enmy e) =>
{
liveEnemy--;
});
}
// 定義了動作的函數(shù)
void OnEnmyDeath(Enmy eney, System.Action<Enmy> onDeath)
{
eney.onDeath = onDeath;
}
void OnDrawGizmos()
{
Gizmos.DrawIcon(transform.position, "spawner.tif");
}
void ReadXML()
{
m_enemyList = new ArrayList();
XMLParser xmlparse = new XMLParser();
XMLNode node = xmlparse.Parse(xmldata.text);
// 取得XML數(shù)據(jù) = 傳入XML文件路徑
XMLNodeList list = node.GetNodeList("ROOT>0>table");
for (int i = 0; i < list.Count; i++)
{
string wave = node.GetValue("ROOT>0>table>" + i + ">@wave");
string enemyname = node.GetValue("ROOT>0>table>" + i + ">@enemyname");
string level = node.GetValue("ROOT>0>table>" + i + ">@level");
string wait = node.GetValue("ROOT>0>table>" + i + ">@wait");
SpawnData data = new SpawnData();
data.wave = int.Parse(wave);
data.enemyname = enemyname;
data.level = int.Parse(level);
data.wait = float.Parse(wait);
m_enemyList.Add(data);
}
}
// xml數(shù)據(jù)
public class SpawnData
{ // 波數(shù)
public int wave = 1;
public string enemyname = "";
public int level = 1;
public float wait = 1.0f;
}
}
using UnityEngine;
using System.Collections;
using System;
public class Enmy : MonoBehaviour {
public PathNode curNode; // 怪物的起始點
public float speed = 2; // 怪物的速度
internal int m_life;
internal int m_maxlife;
public System.Action<Enmy> onDeath;
void Start () {
ShowEffect();
}
void Update () {
RorateTo();
MoveTo();
}
public void MoveTo()
{
Vector3 pos1 = this.transform.position; // 當前怪物所在位置
Vector3 pos2 = Vector3.zero;
if (curNode!=null)
{
pos2 = curNode.transform.position; // 起始點的位置
}
// 兩者之間的距離差值
float dis = Vector2.Distance(new Vector2(pos1.x, pos1.z), new Vector2(pos2.x, pos2.z));
// 判斷目的地
if (dis < 0.3f) // 到達目的地
{
if (curNode.next == null)
{
DestroyMe();
}
else {
curNode = curNode.next; // 如果還有下個點,那么當前點就是起始點
}
}
transform.Translate(new Vector3(0, 0, speed * Time.deltaTime)); // 移動
}
// internal : 只能在程序集中訪問的意思
internal void TakeDamage()
{ // 加載粒子資源
var p = Resources.Load("CFX2_SoulsEscape Rainbow");
// 實例化(預制物扩然,預制物位置艘儒,預制物角度)
Instantiate(p, transform.position, Quaternion.identity);
DestroyMe(); // 摧毀自己
}
// 例子特效
void ShowEffect()
{
var p = Resources.Load("CFX2_EnemyDeathSkull");
Instantiate(p, transform.position, Quaternion.identity);
}
// 旋轉(zhuǎn)視角
public void RorateTo()
{ // 拿到當前對象的歐拉值的Y軸角度
//http://wiki.ceeger.com/script:unityengine:classes:transform:transform.eulerangles
float cur = this.transform.eulerAngles.y;
// 朝向當前點的方向
transform.LookAt(curNode.transform);
// 移向目標(從當前的歐拉Y值,相對于父級的y軸變換旋轉(zhuǎn)角度夫偶,目標速度*時間)
float next = Mathf.MoveTowardsAngle(cur, this.transform.localEulerAngles.y, 120 * Time.deltaTime);
//// 為當前對象賦值歐拉角
this.transform.eulerAngles = new Vector3(0, next, 0);
}
private void DestroyMe()
{
onDeath(this);
Destroy(gameObject);
}
}
怪物路徑控制器腳本
using UnityEngine;
using System.Collections;
public class PathManager : MonoBehaviour {
public ArrayList PathNode;
void Start () {
}
void Update () {
}
[ContextMenu("BuildPath")]
void BuildPath() // 編譯路徑
{
PathNode = new ArrayList(); // 初始化數(shù)組
GameObject[] objs = GameObject.FindGameObjectsWithTag("pathnode"); // 找到所有Pathnode節(jié)點
for (int i = 0; i < objs.Length; i++)
{
PathNode node = objs[i].GetComponent<PathNode>(); // 取出每一個節(jié)點
PathNode.Add(node);
}
}
public void OnDrawGizmos() // 窗口可見時界睁,每一幀調(diào)用這個函數(shù)
{
if (PathNode == null) return;
Gizmos.color = Color.blue;
foreach (PathNode item in PathNode)
{
if (item.next != null) // 只要有下一個點
{
Gizmos.DrawLine(item.transform.position, item.next.transform.position); //畫線
}
}
}
}
怪物路徑
using UnityEngine;
using System.Collections;
public class PathNode : MonoBehaviour
{
public PathNode parent;// PathNode類型的起始點
public PathNode next; // 下一個點
void Start()
{
}
void Update()
{
}
public void SetNext(PathNode node) // 設(shè)置下一個點
{
if (next != null) // 如果下個點不存在
{
next.parent = null; // 那么起始點也不存在
}
next = node; //如果下個點存在,那么下個點就是傳入的這個點
node.parent = this; // 起始點就是當前點
}
// 在窗口可見時兵拢,每一幀都會調(diào)用這個函數(shù)翻斟。在其中進行Gizmos的繪制,也就是輔助編輯的線框體
void OnDrawGizmos() // 畫圖 當繪制Gizmos
{
Gizmos.DrawIcon(this.transform.position, "Node.tif");
}
}
關(guān)于怪物路徑的編輯器的拓展工具條腳本(不用掛載说铃,只需要放置在Editor文件下访惜,沒有就創(chuàng)建)
using UnityEngine;
using UnityEditor;
using System.Collections;
public class PathTool : ScriptableObject
{
static PathNode parent; // 靜態(tài)起始點
[MenuItem("PathTool/Creat PathNode")]
static void GreatePathNoce()
{
// 創(chuàng)建一個新的路點
GameObject go = new GameObject();
go.AddComponent<PathNode>(); // 添加PathNode腳本
go.name = "pathnode";
// 設(shè)置標簽
go.tag = "pathnode";
// 使該路點處于選擇狀態(tài) (這個將絕不返回預設(shè)物或者不可修改的物體)
Selection.activeTransform = go.transform;
}
[MenuItem("PathTool/Set Parent %q")]
static void SetParent() // 設(shè)置起始點
{
// Selection.activeGameObject 返回激活的游戲物體。(在檢查面板中顯示)
// SelectionMode.Unfiltered 返回整個選擇,
// Selection.GetTransforms(SelectionMode.Unfiltered).Length
// 允許對選擇類型進行精細的控制腻扇,使用SelectionMode枚舉類型债热。
if (!Selection.activeGameObject || Selection.GetTransforms(SelectionMode.Unfiltered).Length > 1)
{
return;
}
// 如果選擇的游戲?qū)ο蟮臉撕?= 點標簽
if (Selection.activeGameObject.tag.CompareTo("pathnode") == 0)
{ // 那么起始點 = 選中游戲?qū)ο蟮乃谀_本
parent = Selection.activeGameObject.GetComponent<PathNode>();
Debug.Log("設(shè)置" + parent.name + "起始點.");
}
}
[MenuItem("PathTool/Set Next")]
static void SetNextChild()
{
// 沒有選擇激活得游戲物體,并且沒有起始點幼苛,并且所有選擇的長度>1
if (!Selection.activeGameObject || parent == null || Selection.GetTransforms(SelectionMode.Unfiltered).Length > 1)
{
return;
}
// 如果選擇的激活的游戲?qū)ο蟮臉撕?== pathNode
if (Selection.activeGameObject.tag.CompareTo("pathnode") == 0)
{
parent.SetNext(Selection.activeGameObject.GetComponent<PathNode>()); // 那么設(shè)置下一個點
parent = null;
Debug.Log("設(shè)置" + Selection.activeGameObject.name + "所選擇激活的游戲?qū)ο蟮拿?);
}
}
}
20.png