【程序猿】Unity超級角色控制器 - Part 2

2021-01-12 Gad-騰訊遊戲開發者平臺

  現在我們了解了角色控制器的基本碰撞處理,接下來馬上就演示如何在Unity中實現上一章所展示的效果。


  在開始以前,先確保你的Unity是否已經完成下載安裝。這篇文章中所使用的版本是Unity 4.3.4f1。(檢查Unity版本的方法是Help->About Unity)打開一個現有的工程或者創建一個新的來開始這篇教程。創建一個新的場景(Scene),然後創建一個立方體(Cube)和一個球體(Sphere)。雖然我們最終會用膠囊體作為我們的控制器形狀,但是剛開始還是讓事情保持簡單一些。將球體命名為Player,立方體命名為Wall。改變牆體每個軸的縮放到6。為了更加形象,我還給Player加了藍色的材質,給Wall加了綠色的材質。將Player上的Sphere Collider組件移除掉。


  
  創建新的C#腳本,然後命名為SuperCharacterController.cs。為了表示我們的角色,拷貝和粘貼一下腳本,然後掛到Player身上:


using UnityEngine;

using System;


using System.Collections.Generic;

public class SuperCharacterController : MonoBehaviour {

[SerializeField]

float radius = 0.5f;

private bool contact;

// Update is called once per frame  void Update () {

contact = false;

foreach (Collider col in Physics.OverlapSphere(transform.position, radius))

{

Vector3 contactPoint = col.ClosestPointOnBounds(transform.position);

Vector3 v = transform.position - contactPoint;

transform.position += Vector3.ClampMagnitude(v, Mathf.Clamp(radius - v.magnitude, 0, radius));

contact = true;

}

}

void OnDrawGizmos()

{

Gizmos.color = contact ? Color.cyan : Color.yellow;

Gizmos.DrawWireSphere(transform.position, radius);

}

}


  然後就完了。運行項目並且打開場景。將Player往牆體的邊緣慢慢拖過去。你可以看到牆體在反推,讓Player總是停留在邊緣。那麼這裡做了什麼呢?


  Physics.OverlapSphere返回了與球體發生碰撞的一組Collider。這是個很好用的函數,參數很簡單。只需要傳入球心與半徑就可以了。


  一旦檢測到任何碰撞,我們就會開始處理。為了找到box collider上的最近點,我們用了ClosestPointOnBounds函數。緊接著我們就可以通過contactPoint得到我們的位置。contactPoint的長度就是我們所需要推出去的距離。


  你可能會注意到,我實現了OnDrawGizmos函數,這樣OverlapSphere的碰撞就一清二楚了。



  兩幀演示了碰撞被檢測,而後被處理。


  相當簡單。但是我們至今為止的勝利可能只是個開始。創建一個DebugDraw.cs的類,然後添加如下代碼。


using UnityEngine;

using System.Collections;


public static class DebugDraw {

public static void DrawMarker(Vector3 position, float size, Color color, float duration, bool depthTest = true)

{

Vector3 line1PosA = position + Vector3.up * size * 0.5f

Vector3 line1PosB = position - Vector3.up * size * 0.5f;

Vector3 line2PosA = position + Vector3.right * size * 0.5f;

Vector3 line2PosB = position - Vector3.right * size * 0.5f;

Vector3 line3PosA = position + Vector3.forward * size * 0.5f;

Vector3 line3PosB = position - Vector3.forward * size * 0.5f;

Debug.DrawLine(line1PosA, line1PosB, color, duration, depthTest);

Debug.DrawLine(line2PosA, line2PosB, color, duration, depthTest);

Debug.DrawLine(line3PosA, line3PosB, color, duration, depthTest);

}

}


  這是一個我寫的挺有用的幫助函數,它可以讓我們在任何地方繪製在編輯器中(與此相對的,我們只能在OnDrawGizmos函數中繪製)。修改foreach循環如下。


foreach (Collider col in Physics.OverlapSphere(transform.position, radius))

{


Vector3 contactPoint = col.ClosestPointOnBounds(transform.position);

DebugDraw.DrawMarker(contactPoint, 2.0f, Color.red, 0.0f, false);

Vector3 v = transform.position - contactPoint;

transform.position += Vector3.ClampMagnitude(v, Mathf.Clamp(radius - v.magnitude, 0, radius));

contact = true;

}


  運行代碼,你會注意到當碰撞發生的時候,會在位置上繪製一個紅色十字標記。現在,拖動player到牆體內,就能看到標記跟隨者player。這對於ClosestPointOnBounds函數來說也不完全是個錯誤,但是如果要對應上上回提到的退回策略,我們真的希望有一個ClosestPointOnSurfaceOfBoundsOrSomething函數。



  問題就在於當我們的角色在碰撞體內部的時候,隨著返回最近點函數失效,沒法正確地處理碰撞。現在,我們就來處理這個問題。


  將我們的牆體在y軸上旋轉大概20度,然後運行場景。你回發現一切都不正常了。這是因為ClosestPointOnBounds函數返回的最近點實在軸對齊包圍盒(AABB)上,而不是朝向包圍盒(OBB)上。



  你可能已經在想如果這個問題擴展一下,不僅僅是盒子會是怎樣。由於函數只能返回軸對齊包圍盒的最近點,哪怕是其他類型的碰撞,它也是肯定沒法得到表面的最近點的。因此這個問題是沒有銀彈的(也許是我沒發現),我們只能每個碰撞類型自己實現。


  先讓我們從最簡單的開始:球體碰撞。在場景中創建一個新的球體遊戲對象。找到表面上的最近點需要好幾步,每一步都比較簡單。要知道哪個方向推出玩家,我們計算從我們為之到球體中心的方向。由於球體表面每個點距離球心都一樣,我們只要正規化我們的向量,然後乘以半徑以及local scale因子即可。


  下面是代碼實現。你可以看到新的方法中,多加了一個檢測當前OverlapSphere的碰撞類型。


using UnityEngine;

using System;


using System.Collections.Generic;

public class SuperCharacterController : MonoBehaviour {

[SerializeField]

float radius = 0.5f;

private bool contact;

// Update is called once per frame  void Update () {

contact = false;

foreach (Collider col in Physics.OverlapSphere(transform.position, radius))

{

Vector3 contactPoint = Vector3.zero;

if (col is BoxCollider)

{

contactPoint = col.ClosestPointOnBounds(transform.position);

}

else if (col is SphereCollider)

{

contactPoint = ClosestPointOn((SphereCollider)col, transform.position);

}

DebugDraw.DrawMarker(contactPoint, 2.0f, Color.red, 0.0f, false);

Vector3 v = transform.position - contactPoint;

transform.position += Vector3.ClampMagnitude(v, Mathf.Clamp(radius - v.magnitude, 0, radius));

contact = true;

}

}

Vector3 ClosestPointOn(SphereCollider collider, Vector3 to)

{

Vector3 p;

p = to - collider.transform.position;

p.Normalize();

p *= collider.radius * collider.transform.localScale.x;

p += collider.transform.position;

return p;

}

void OnDrawGizmos()

{

Gizmos.color = contact ? Color.cyan : Color.yellow;

Gizmos.DrawWireSphere(transform.position, radius);

}

}


  機智的讀者可能會發現ClosestPointOn實際上返回的是球體表面的最近點。不像ClosestPointOnBounds返回的是包圍盒的最近點。這很簡單,但是在使用之前還有一些問題要解決。現在來看看第二種(也是今天的最後一種)碰撞類型的實現:朝向包圍盒。



  圖像演示了如何通過球心與控制器位置之間的向量推算出最近點。


  我們的一般做法是獲取輸入,然後clamp到box內部。這樣的效果與內建的ClosestPointOnBounds是一樣的,除了即使box帶旋轉也能處理之外。


  Box Collider的擴展定義了局部大小x、y和z。為了將我們的點clamp到Box Collider內部,我們需要將作為從世界坐標系轉換到Box Collider的局部坐標系。在完成之後,我們對位置clamp到包圍盒內即可。最後,我們再將改點轉換回世界坐標系。代碼如下。


using UnityEngine;

using System;


using System.Collections.Generic;

public class SuperCharacterController : MonoBehaviour {

[SerializeField]

float radius = 0.5f;

private bool contact;

// Update is called once per frame  void Update () {

contact = false;

foreach (Collider col in Physics.OverlapSphere(transform.position, radius))

{

Vector3 contactPoint = Vector3.zero;

if (col is BoxCollider)

{

contactPoint = ClosestPointOn((BoxCollider)col, transform.position);

}

else if (col is SphereCollider)

{

contactPoint = ClosestPointOn((SphereCollider)col, transform.position);

}

DebugDraw.DrawMarker(contactPoint, 2.0f, Color.red, 0.0f, false);

Vector3 v = transform.position - contactPoint;

transform.position += Vector3.ClampMagnitude(v, Mathf.Clamp(radius - v.magnitude, 0, radius));

contact = true;

}

}

Vector3 ClosestPointOn(BoxCollider collider, Vector3 to)

{

if (collider.transform.rotation == Quaternion.identity)

{

return collider.ClosestPointOnBounds(to);

}

return closestPointOnOBB(collider, to);

}

Vector3 ClosestPointOn(SphereCollider collider, Vector3 to)

{

Vector3 p;

p = to - collider.transform.position;

p.Normalize();

p *= collider.radius * collider.transform.localScale.x;

p += collider.transform.position;

return p;

}

Vector3 closestPointOnOBB(BoxCollider collider, Vector3 to)

{

// Cache the collider transform  var ct = collider.transform;

// Firstly, transform the point into the space of the collider  var local = ct.InverseTransformPoint(to);

// Now, shift it to be in the center of the box  local -= collider.center;

// Inverse scale it by the colliders scale  var localNorm =

new Vector3(

Mathf.Clamp(local.x, -collider.size.x * 0.5f, collider.size.x * 0.5f),

Mathf.Clamp(local.y, -collider.size.y * 0.5f, collider.size.y * 0.5f),

Mathf.Clamp(local.z, -collider.size.z * 0.5f, collider.size.z * 0.5f)

);

// Now we undo our transformations  localNorm += collider.center;

// Return resulting point  return ct.TransformPoint(localNorm);

}

void OnDrawGizmos()

{

Gizmos.color = contact ? Color.cyan : Color.yellow;

Gizmos.DrawWireSphere(transform.position, radius);

}

}


  你可能會注意到在主碰撞循環中做了一些修改,使得我們不管是軸對齊還是朝向的用ClosesPointOn就可以了。這裡的大部分實現都參考自fholm的RPGController package。



  到這裡我們第一部分的實現就結束了。在後面的文章中,我會講講Unity物理API會遇到的一些問題。然後開始為實現理想中的角色控制器開發一些組件。



  這篇文章主要的代碼參考自fholm的RPGController package。其中的推出來自RPGMotor.cs,最近點來自RPGCollisions.cs。


相關焦點

  • Unity超級角色控制器(三):物理API分析與功能實現
    CapsuleCastAll: 第一眼看上去,這個函數用在角色控制器上是非常理想的(由於它使用的是膠囊體形狀)。值得注意的是,這是一個投射,它只能檢測法線面上投射的表面,背面是無法檢測出來的。也就是說,投射無法檢測出包圍了膠囊體初始位置的物體,也就是那些與初始位置相交的對象。如果我們想將其用於角色控制器的開發,這是一個必須要攻克的缺陷。
  • Unity 實用技巧 - 物理系統初識
    物理系統主要由以下幾部分組成:Rigidbody(剛體)、Collider(碰撞器)、Triggers(觸發器) 、Joint(關節)、Character Controller(角色控制器)。05  Character Controllers(角色控制器)為第一人稱或第三人稱遊戲中的角色做基於碰撞的物理特性。不能穿過靜態碰撞物體;可以利用它設置物體的速度和方向等。
  • unity業餘愛好者說一下
    unity業餘愛好者說一下,這幾天傳的關於《太吾繪卷》代碼的事幾乎都是無中生有的事...一群用.net和vs做工程的人談論第三方引擎做的遊戲...真是雞同鴨講。太吾繪卷現在針對幾個常見誤會說一下1.只有一個main (x)unity的腳本都是依附於各個精靈的,沒有main,只有update2.沒有注釋(x)你反編譯出來的代碼有注釋
  • 2021新年匯總:Unity項目原型快速開發資源,看這一篇就夠
    Tutorial: https://assetstore.unity.com/packages/essentials/tutorial-projects/tanks-tutorial-46209 Angry Bots 2:https://github.com/UnityTechnologies/AngryBots2
  • 使用Unity 粒子系統實現 2D 人物足跡效果
    其基本思路是根據輸入計算一個 2 維向量lookDirection作為角色的「面向」,並根據這一向量移動剛體以及播放動畫。 它能夠保證對於任意角度,角色的移動速度都保持一致,並且這一向量對於腳印效果的實現很重要。 如有疑問可以查閱底部的教程連結或直接下載腳本。
  • 程序猿的第24天:圓球表面積和體積
    讓我們繼續C++的操練,今天的題目是:程序猿每日一題 (2018年1月3日)Day所有的實數輸出請使用C語言的默認捨入方式保留2位小數,注意行尾輸出換行。樣例輸入  1.5 樣例輸出  28.27 14.14我回答:讓我們先了解一下數學小知識:球體表面積公式 S(球面)=4πR^2;球體體積公式 V(球體)=4/3πR^3
  • Unity3D 尋路系統
    給需尋路的物體添加NavMeshAgent組件(Unity主要通過NavMeshAgent組件實現自動尋路的功能,角色添加NavMeshAgent組件後,就可以在NavMesh尋路網格上尋找最優路徑找到目標)選中物體,然後在屬性面板中點擊Add Component,搜索Nav Mesh Algent,添加此組件。
  • 小麋鹿大戰程序猿,紅演圈美模引爆聖誕節狂歡!
    解救程序猿大作戰下午14點,全員抵達新浪大樓,開啟今日活動重頭戲——小麋鹿大戰程序猿,以簡短熱舞作為暖場環節,隨後便是一系列小遊戲,如「蒙眼吃蘋果」、「團隊默契大比拼」,簡單有趣,拉近了現場所有人的距離,氣氛十分熱烈。
  • 在unity中用C#連接資料庫步驟
    所以本文就介紹一下unity連MySQL資料庫所遇到的一些坑。unity連接資料庫,首先你需要導入如圖所示的五個數據連結庫。在本圖中,歐陽講他們放到了Mysql文件夾下,在這裡歐陽強調一下――最好將他們放到Plugins文件夾下,不然會出現莫名其妙的問題哦。
  • 《龍珠:超宇宙2》DLC角色超級歐布截圖 招式酷炫
    之前萬代南夢宮宣布超級歐布將加入《龍珠:超宇宙2》,在今冬第二彈DLC角色包中正式推出。今日(11月22日)官方公布「超級歐布」新截圖,展示了他戰鬥英姿以及酷炫的招式。超級歐布是歐布與胖布歐(純善)合體之後誕生的形態,無論力量、速度都和之前相比都有大幅度的提升。《龍珠:超宇宙2》以穿梭時空修正《七龍珠》世界的歷史偏差為主題。故事設定在前作2年後,敘述在前作引發的歷史改變,導致了最壞的狀況。玩家將扮演新手時空巡邏員,與夥伴齊心協力修正錯誤的歷史。
  • [語音]各種廚具英文Part2
    PART 2    peeler 削皮刀    baking mould 烘焙模具▷往期推薦◁[語音]酒店英語情景對話—Showing the Room 客房迎賓服務[語音]各類蔬菜英文[語音]酒店客房物品英文part
  • Unity 2018.3 Beta版發布
    直播課程:Facial AR Remote面部捕捉解決方案課程(第一期)直播地址:https://connect.unity.com/events/unitychina-facialarUnity官方教師培訓報名火熱進行中Unity將在10月22-26日,舉辦為期5天的專業的Unity官方教師培訓課程,誠邀廣大教師與
  • unity什麼意思
    unity什麼意思uni前綴,只包含一個的,更多例子還有:uniform, unique, unilateral, etc. 發音類似於有你,整個世界中有你就夠了,不需要別人,也就是只包含一個的。unity,聯合、統一、團結、和睦。學單詞,只記住意思可不行,會用才行,小夥伴們可以在評論區造句,我們一起學習哦!我先來:造句:Unity is strength. (團結就是力量)
  • 使用unity製作RPG遊戲3——2D精靈
    context=%7B%22nid%22%3A%22news_9564882242542237691%22%2C%22sourceFrom%22%3A%22bjh%22%2C%22url_data%22%3A%22bjhauthor%22%7D下面需要利用Tiled2Unity把01地圖導入unity下載Tiled2Unity,在根目錄下打開可執行文件進入unity。
  • unity遊戲製作初始人物控制代碼
    大家好,今天小編帶大家學習一哈unity遊戲製作中初始人物控制代碼。1.我們知道遊戲中,選中人物,在人物未開始運動前,往往會有一個初始的動作,好的,我們這節課通過unity中相關代碼和基礎設置來實現這一效果。
  • unity三維遊戲製作13
    1.交互 指的是玩家與物體,玩家與玩家,物體與物體2.Mesh Collider 網格碰撞器網格碰撞體一般在遊戲中佔用的資源比較大,所以很少用一般用其他碰撞器替換網格碰撞器3.實現主角碰撞檢測開門(1)添加標籤
  • 如何在Unity中利用nReal製作AR應用
    就我個人而言,我仍然在使用Unity 2018.3.6f1,因為它是我正在為我的諮詢公司開發的其他項目,但實際上,nReal建議使用Unity 2018.2.x。我認為如果你下載了最新的版本,也就是2019.2,你應該也沒有問題。在任何情況下,您都可以直接從Unity Hub或這個頁面下載您想要的特定版本的Unity。
  • 漫威:超級英雄,按角色順序排列
    漫威宇宙中有很多超級英雄以及耐人尋味的配角,有幫兇、惡棍,也有戀人。有些角色要麼是他們是超級英雄團隊的重要組成部分,要麼是他們領導了自己的電影。超級英雄的世界始於2008年,在其存在期間,它不止一次改變了英雄們的生活。
  • Unity基礎之物理引擎
    我們在unity裡面建了一個正方體cube , 要如何使這個cube可以跟現實中的物體一樣受重力呢 ? 這就需要用到Rigidbody(剛體)組件了 .單擊cube , 在右邊的Inspector面板添加Rigidbody組件添加完Rigidbody組件後,cube1就可以受重力影響了,運行unity時,cube1會因為受到重力往下落.下面為大家介紹 Rigidbody 組件常用參數 : 1. Mass : 物體的質量 .
  • CF超級角色修道者、虛空終結者前瞻 全新生化地圖角色預覽
    CF超級角色修道者、虛空終結者前瞻 全新生化地圖角色預覽 發布時間:2020-08-18 09:45 來源:官方掌火