BrianGodd(黃永恩)
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # 113-1 Metaverse 課程講義 # 雲端資料夾 [課程素材包](https://drive.google.com/drive/folders/1HzvexBlurS1gE15yS91XBQlEzpq2Qfa9) [Google Drive: FruitNinja_VR_Tutorial](https://drive.google.com/file/d/1_mJaNeCo7-2mSqYG3ZbRdaBy_uXAWsRd/view?usp=sharing) # 目錄 1. [創建URP專案](#1-創建URP專案) 2. [匯入場景、道具](#2-匯入場景、道具) 3. [產生UI與中文字幕](#3-產生UI與中文字幕) 4. [VR遊戲 vs 3D遊戲](#4-VR遊戲-vs-3D遊戲) 5. [Unity VR設定](#5-Unity-VR設定) 6. **VR角色基礎建置** 6.1. [角色控制 (Camera、Hands、Move、Turn)](#61-角色控制) 6.2. [場景互動 (Hover、Grab)](#62-場景互動) 6.3. [UI](#63-UI) 6.4. [VR Raycast](#64-Raycast) 6.5. [Feedback (Vision、Haptic)](#65-Feedback) 7. [整理 Heirachy 視窗](#7-整理-Heirachy-視窗) 8. [互動與遊戲流程](#8-互動與遊戲流程) [8.0. XRCamp_Utilities部分功能介紹](#80-XRCamp_Utilities部分功能介紹-請先將-XRCamp_Utilities-匯入專案中) [8.1. [補充] Unity Event vs. Game Event](#81-補充-Unity-Event-vs-Game-Event) 9. [場景(Scene)切換](#9-場景Scene切換) 10. [為遊戲加分 (負責美術、音樂的同學注意!!!)](#10-為遊戲加分-美術、音樂組同學注意!!!) 10.1. [為遊戲添加音樂吧!](#101-為遊戲添加音樂吧!) 10.2. [為場景增加一個會動的NPC吧! (內含Sketchfab及Mixamo教學)](#102-為場景增加一個會動的NPC吧!) 10.3. [Particle System (粒子系統)–某名人:「逆轟高灰!」](#103-Particle-System-粒子系統–「逆轟高灰」!) 10.4 [Post Processing 提升畫面氛圍](#104-Post-Processing-提升畫面氛圍) --- X. [其他 (內有Plastic SCM)](#x-其他) X2. [注意與建議](#x2-注意與建議) # 2024/09/12 Unity (VR) Fruit Ninja ## 目標 從頭開始,完整製作出一個 XR project (可銜接多人)。 預設已經先行完成Unity Learn - Misson 1,對Unity有基礎的認知。 TARGET: [Fruit Ninja VR - Gameplay Trailer | PS VR](https://www.youtube.com/watch?v=hPY4TRRHwZc) ## 所使用到的素材 (皆已放在課程素材包內) 1. [XR Camp Utilities](https://drive.google.com/drive/folders/1cN4LKXDMaVtaXzZxrDOUkjezCA6RT8QD?usp=sharing) -- 其中包含助教寫好,常用到的Script和工具 2. [Low-Poly Simple Nature Pack](https://assetstore.unity.com/packages/3d/environments/landscapes/low-poly-simple-nature-pack-162153) 3. [Low Poly 3D Medieval Weapon Pack](https://assetstore.unity.com/packages/3d/props/weapons/low-poly-3d-medieval-weapon-pack-186935) 4. [Fruits LowPoly Pack Lite](https://assetstore.unity.com/packages/3d/props/fruits-lowpoly-pack-lite-273980) 5. [super Mario warp pipe](https://sketchfab.com/3d-models/super-mario-warp-pipe-463aa0650b0b4ced9401f14ada0f22c6) 6. [Bomb](https://sketchfab.com/3d-models/bomb-1a9395dac5bc4742bdd780b3cce93416) 7. [Low-poly Ninja](https://sketchfab.com/3d-models/low-poly-ninja-54783c019894430bb163e02fff915fe9) 8. [FREE Skybox Extended Shader](https://assetstore.unity.com/packages/vfx/shaders/free-skybox-extended-shader-107400) 9. [Sketchfab SDK](https://assetstore.unity.com/packages/tools/input-management/sketchfab-for-unity-14302) -- 可以將從Sketchfab上下載之GLTF檔直接匯入Unity的工具 ## 上課重點紀錄 :::success 請善用瀏覽器的關鍵字搜尋功能:```Ctrl+F``` ::: [完整Unity專案檔](https://drive.google.com/file/d/1KG7SyJWF9JT5kpWhiUQRjZg92ZnOhjuv/view?usp=sharing) ### 1. 創建URP專案 * **創建專案** 打開Unity Hub,建立一個空的Universal 3D Unity專案,版本請選擇2022.3.x以上的LTS版,並按下Create Project: ![image](https://hackmd.io/_uploads/r12fdzepA.png) 如欲使用版本控制,請勾選右下角的"Use Unity Version Control"。 ### 2. 匯入場景、道具 * 課程資源、網站介紹 * 課程Discord頻道中的"綜合資源"欄位底下,有這門課[過去所購買的諸多素材](https://drive.google.com/drive/folders/1HzvexBlurS1gE15yS91XBQlEzpq2Qfa9)供同學免費使用,同時也提供了許多免費資源連結,請多加利用。 * 這堂課所使用到的所有素材,都已列在[#所使用到的素材](#所使用到的素材-皆已放在課程素材包內)底下 * 將材質(Material)轉換成URP格式 1. 當匯入的物件呈現粉紅色(無法正確顯示材質), 2. 請前往 Window -> Rendering -> Render Pipeline Converter 3. 全部勾選,並點擊 Initialize And Convert ![image](https://hackmd.io/_uploads/BJFcPpbTp.png) ### 3. 產生UI與中文字幕 1. 創建一個UI->Text ![image](https://hackmd.io/_uploads/BkxmRmjp6.png) 2. 自行上網下載字體 (Windows預設字體在C:\Windows\Fonts) 3. 前往 Window -> Text Mesh Pro -> Font Asset Creator 產生客製化的字形圖檔。 ![image](https://hackmd.io/_uploads/B1xdUTbap.png) 4. 將生成的檔案,丟入Text的"Font Asset"欄位中 ![image](https://hackmd.io/_uploads/ryFcAQspT.png) ### 4. VR遊戲 vs 3D遊戲 * **沉浸感**—360全景、120FOV,遊玩時有「空間感」 * **人機互動**、互動**反饋** * **不同視角**的體驗 (沙盒、上帝視角等等) * 更多想像空間 (魔法、劍技、溶解等等) ### 5. Unity VR設定 * 下載Oculus並link至電腦 ![image](https://hackmd.io/_uploads/S1Oxg4JRa.png) * 下載XR Plugin Management ![image](https://hackmd.io/_uploads/r1ThcTHp6.png) * 使用OpenXR (支援oculus&steamVR) ![image](https://hackmd.io/_uploads/SJ1fjar6T.png) * Fix/Edit All warning rules ![image](https://hackmd.io/_uploads/HkCFiaBpT.png) * 選擇使用的裝置Profiles (基本上這堂課是HTC vive/Oculus Touch) ![image](https://hackmd.io/_uploads/HJxKl4JRp.png) * Package Manager>Unity Registry> **XR Interaction Toolkit** >Install,順便也Import **starter asset** ![image](https://hackmd.io/_uploads/r18mh6Hpa.png) ![image](https://hackmd.io/_uploads/ryiInTHTT.png) ### 6.1 角色控制 1. Camera * 右鍵Create-> XR-> XR Origin ![image](https://hackmd.io/_uploads/SkLA1PWRp.png) * Camera tracking mode: "floor" ![image](https://hackmd.io/_uploads/S15jV0B6a.png) 2. Hands(Controller) * 設置Controller Profile ![image](https://hackmd.io/_uploads/SySBevW0p.png) * 把跟Raycast相關的Components從Controller上拔掉 ![image](https://hackmd.io/_uploads/HJ2jlDb0T.png) * 將Oculus Hands(已製作好捏、握的手勢動作)放到Controller底下,使玩家的Controller有模型 ![image](https://hackmd.io/_uploads/HJI8-vbRp.png) * 上述步驟請在Left、Right Controller各做一次 3. Move * 在XR Origin的Inspector中新增: 1. Locomotion System 2. Character Controller(記得去調整角色的Height喔) 3. Continuous Move Provider(**Action-based**) * 將**Use Gravity**打勾 * Mode選用**Immediately** * Move Speed調至2~4左右即可(視人物、場景大小) * 在想給予移動功能的Hand上勾選Use Reference,並在下方貼入**XRI/Hand/Locomotion/Move**之Reference ![image](https://hackmd.io/_uploads/BkhsfwZRp.png) 4. Turn * 分為連續轉動(Continuous turn)與間斷轉動(Snap turn),因現實世界的玩家都是使用連續轉動的關係,在遊戲中若使用Continuous turn的話容易導致動暈症的產生(轉動速度不一致),因此會建議大家以Snap turn為主 * 與新增Move的方法相似,新增**Snap Turn Provider(Action-based)** * 在想給予移動功能的Hand上勾選Use Reference,並在下方貼入**XRI/Hand/Locomotion/Snap turn**之Reference即可 ![image](https://hackmd.io/_uploads/H1-s4w-06.png) 5. Jump * 在VR遊戲中不是那麼建議讓玩家有太過劇烈的動作(避免動暈),因此Jump的功能在一般**VR劇情遊戲的專案內不會推薦**給大家使用。 ### 6.2. 場景互動 1. 先介紹一下,在VR遊戲中,互動功能需要兩個角色-- * Interactor:互動方,在XR Origin中便是兩個Controller(手) * Interactable:可被互動方,通常有XR Simple Interactable(**Hover、Select、Activate**)、以及XR **Grab** Interactable 2. 互動時,**會動的一方需要有Rigidbody**(剛體),**雙方都要有Collider** (碰撞箱/偵測箱) 3. 先新增XR Direct Interactor、Sphere Collider至我們兩個Controller上,並**把Sphere Collider的Is trigger打開**(變成觸發箱而非碰撞箱)。另外,注意Sphere的Collider大小,大概調至0.1就可以了。 ![image](https://hackmd.io/_uploads/BJLFkXgT0.png) 4. 將XR Grab Interactable新增到我們場景中的武器上 * 要記得把Knife的Rigidbody-Use Gravity打開喔,不然拿起放開後會飄來飄去XD * 此時武器需要跟環境做"碰撞",因此碰撞箱要打開(IsTrigger關閉) * XR Grab Interactable-Attach Transform可以設定為武器握柄的部分(另創一個Gameobject),並調整角度到正確地拿武器姿勢即可。 ![image](https://hackmd.io/_uploads/rJVNeQlTC.png) 5. [Movement type](https://fistfullofshrimp.com/unity-vr-grab-interactables/) * Instantaneous(較常用):**無視物體的Rigidbody**,且**會穿透任何物體**,會一直黏在手上直到放開為止。 * Kinematic :會**有些許的延遲**(不會完全貼在手上),且會被手的物理性質影響,**會穿透只要是沒有Rigidbody的物體** * Velocity Tracking(較真實):**模擬真實**在拿物體的現象,且會與沒有Rigidbody的物體進行碰撞(也就是說**除了自己額外設定的物體以外都會進行碰撞**)、**不會穿透過去**。 6. 好啦!到目前為止,目前VR場景就有一個可以移動的玩家,以及一個可以拿起來丟來丟去的武器囉~ 如果還有時間的話,可以試試看把場景中的其他物件也變成可以互動的物件喔~ (**記得把static關掉**^^) 7. Q. 在互動中如何觸發event? A. 第一種方式,即是在Interactable Component底下的Interactable Events中,有Hover(碰觸)、Select(捏)、Activate(握)、Focus等等放Event的地方,也就是在物件上觸發。 第二種方式,即是在Controller的Interactor上觸發,那就多了Haptic Event(震動反饋)可以設定。 第三種方式(給那些Coding比較強,想自由一點的同學),你當然可以使用OntriggerEnter、OnCollisionEnter等方式,利用Hand Contrller的Sphere Collider與物件的Collider碰撞時觸發Script自訂的程式。 ![image](https://hackmd.io/_uploads/SJ1H3DW0p.png) ![image](https://hackmd.io/_uploads/r1PkkO-0a.png) ![image](https://hackmd.io/_uploads/HJHopPZRp.png) ### 6.3. UI 1. UI在VR專案中,基本上我們**不會將其放在頭盔眼前顯示**(像是3D遊戲,UI為Screen Space),一方面是因為控制字幕、圖片在頭盔眼上的清晰度這件事是非常麻煩,每個人的感覺也差很多。 2. 另一方面也是因為使用者在遊玩的時候,頭盔會一直跟著玩家移動、旋轉,若視野底下一直有字幕等UI出現,會使**動暈症**的發生更加容易,體感變差。 3. 因此助教這邊推薦大家使用的是**World Space**的UI,透過有背景顏色的Panel、Image或是特效,上面附有你想要呈現的字幕,在VR頭盔中呈現的感覺會好很多 (記得Canvas的大小要重新設定,通常會小很多) ![image](https://hackmd.io/_uploads/HJc3eOZCa.png) 4. 取消原有的Graphic Raycaster,新增**Tracked Device Graphic Raycaster**,使UI在VR空間中能被使用者互動到(例如Button) ![image](https://hackmd.io/_uploads/HyezEbdb0T.png) 5. **VR專案中透過World Space的UI去提示玩家目前該怎麼做、要去哪裡,是很重要的元素喔!** ![image](https://hackmd.io/_uploads/HyIa-7eTC.png) ![image](https://hackmd.io/_uploads/H1PxfXgaR.png) ### 6.4. Raycast 1. XR > **Ray Interactor(Action-based)**,新增兩個(左、右)並取名 ![image](https://hackmd.io/_uploads/H1WhbuWA6.png) 2. 一樣要設定兩隻手分別的Preset 3. 設定射線起點位置 ![image](https://hackmd.io/_uploads/SyKBG7gT0.png) 5. 目前兩隻手的Ray便可顯示在我們的VR頭盔中,隨著我們的Controller移動,並也可以使用此Ray interactor來與場景得interactable物件互動喔! 6. Q. 如何讓我們的Raycast在想顯示的時候顯示呢? (加分項目) 比方說按下Activate(捏,食指按紐)來打開Raycast,其他時候隱藏,使遊戲更完整 A. * 增加Ray Able Controller至XR Origin中 * 並把XRI/Hand/Activate(兩手)分別放到兩個Action Reference中 * 將剛剛Create的兩個Ray Interactor放入Left Ray及Right Ray中即可 ![image](https://hackmd.io/_uploads/B1ADzuWR6.png) ### 6.5. Feedback 1. Vision * 使用至於Camera前方的Block來改變視野,是在VR專案中常常使用在轉場動畫上的一個技巧 * 將Prefab/FadeScreen加在XR Origin>Main Camera底下就好,然後要記得把FadeScreen的Shader改成**URP/Lit**喔! ![image](https://hackmd.io/_uploads/rygTqNubRT.png) 2. Haptic * 助教有提供Haptic Interactable的腳本,方便同學使用震動反饋來添增VR遊戲的互動性! * 使用方式:添增Script至兩個Controller上,貼好Controller Reference後便可在Game Event或是Unity Event使用此腳本的**HapticActiveOnce**函式來觸發震動 * 若同學好奇在程式中是怎麼觸發的話,都可以點進去C#檔案查看腳本內容喔~ 助教提供的都是很基礎、常用的C#、Unity語法,特別是程式組的同學可以參考一下! ![image](https://hackmd.io/_uploads/H1QdtObAp.png) ![image](https://hackmd.io/_uploads/Sk7s77gTC.png) ### 7. 整理 Heirachy 視窗 * 雜亂的Heirachy不利於開發。 * 請善用Empty GameObject為其命名當作「抽屜」,將不同功能的GameObject分類以方便管理。 ![image](https://hackmd.io/_uploads/rJHCX7ga0.png) ### 8. 互動與遊戲流程 我們的目標是實現以下動作: 1. 武器碰到水果時,手把震動並加分 2. 遊戲前、中、後,UI有所變化 3. Slice:https://www.youtube.com/watch?v=GQzW6ZJFQ94 ### 8.0. XRCamp_Utilities部分功能介紹 (請先將 XRCamp_Utilities 匯入專案中) > **XRCamp_Utilities中,包含了三種讓玩家與物體互動的方式:** > * **TriggerOnClick** 點擊滑鼠左鍵時,朝玩家畫面(MainCamera)面向方向射出一道射線,射線接觸到物體時觸發動作。 > * **TriggerOnCollision** 物體與對應Layer的其他物體碰撞時觸發動作。 > * **TriggerOnKeyPress** 玩家按下特定按鍵時觸發動作。 > > 以上互動的方式皆透過 Unity Event/Game Event觸發動作。 ### 8.1. [補充] Unity Event vs. Game Event #### Unity Event 使用方法: 1. 點擊Unity Event()底下的+號,即可添加要執行的動作即可。 ![image](https://hackmd.io/_uploads/SJSD_Miaa.png) #### Game Event 使用方法: 1. 先於Project視窗底下點擊右鍵,創建一個GameEvent ![image](https://hackmd.io/_uploads/ryNTtzsaa.png) 2. 在Heirachy中創建一個空的GameObject,為其添加"GameEventListener.cs"的Script。 ![image](https://hackmd.io/_uploads/HkUq2fsTT.png) 3. 將創建的GameEvent拖曳至"On Event"欄位中,並於底下的Response添加想觸發的動作。 GameEventListener會監聽對應的GameEvent,並於GameEvent觸發時執行底下的動作。 ![image](https://hackmd.io/_uploads/SJH1sfipT.png) ### 9. 場景(Scene)切換 1. 創建一個空物件"SceneManager",並為其加上Script "Scene Transition" ![image](https://hackmd.io/_uploads/ryb_DQs6T.png) 2. 創建一個空物件"MoveToNextScene",為其添加"Box Collider"、"Trigger On Collision" * Box Collider裡面的"Is Trigger"需要打勾 3. 使用"Trigger On Collision",觸發SceneManager的轉場指令。 * "GoToSceneAsync"所輸入的數字,為Scene在"File"->"Build Settings"中,Scenes in Build底下的數字編號。 ![image](https://hackmd.io/_uploads/rJfHumoTT.png) 4. 為玩家人物"PlayerCapsule"創建一個新的Layer名為"Player",以此作為Target Layer Mask。 5. 將MoveToNextScene移動到門的後方,使玩家走出門後觸發轉場。 ### 10. 為遊戲加分 (美術、音樂組同學注意!!!) 自學/課前: * [Mixamo](https://www.mixamo.com/#/) * [AI: Text to Speech](https://elevenlabs.io/) * [Particle System](https://www.youtube.com/watch?v=SrWrUN56UWU) ### 10.1. 為遊戲添加音樂吧! 1. 音效在Unity中主要是兩個角色在作用 * Audio Listener:聆聽者,通常會自動加在Main Camera中 * Audio Source:音源,可放入Audio Clip並調整類似音樂軟體的相關設定,也有空間音效(3D)的多項設定可以設置 2. 注意!!! Unity基本上只接受**Mp3**以及**Wav**喔 3. 添加BGM (2D音效) * 在場景Manager物件中新增Audio Source的Component,並將你的Mp3/Wav放到Audio Clip裡面 * 將Play On Awake、Loop打勾,遊戲中就有開啟時自動播放且循環播放的背景音啦! ![image](https://hackmd.io/_uploads/rkPR6_Z0T.png) 4. 添增音效 (3D音效) * 在想要產生聲音的物件(例如麥克風)上加上Audio Source,一樣將音檔放入Audio Clip中 * 將Spatial Blend設為1,此時這個音源便是一個3D空間音效 * 3D空間音效在Unity有幾個常用的設定: * Volume Rolloff:距離與聲音大小的關係式,預設是對數下降,其他也有像是線性下降的關係是可以選擇 * Min Distance & Max Distance:Min~Max中間的空間則是可以聽到聲音的範圍,要注意這些數值會跟物體大小有關,因此物件若要有3D音效的話都要親自去做調整 ![image](https://hackmd.io/_uploads/rymilFZ0p.png) * 在Scene視窗中也可以看到Min/Max Distance的範圍,如下圖即為(0.41 ~ 2) ![image](https://hackmd.io/_uploads/BJgfpQlpC.png) * Q. 如何在Unity Event中觸發音效播放? A. * 在Unity Event加入Animator.Play()的函示即可,若想在Script裡面控制播放Audio Clip的話,同樣也是使用Animator.Play()喔 ![image](https://hackmd.io/_uploads/HkpH0Kb06.png) > public AudioClip[] ballClip; > public AudioSource ballSource; > ballSource.clip = ballClip[1]; > ballSource.Play(); ### 10.2. 為場景增加一個會動的NPC吧! 1. Sketchfab in Unity * 先去下載 [Sketchfab for Unity](https://assetstore.unity.com/packages/tools/input-management/sketchfab-for-unity-14302) * 在Unity即可直接載入GLTF模型檔 (若跳出Plugin Update就不用理他,直接關掉該提醒視窗就好) ![image](https://hackmd.io/_uploads/r1LnmYbAa.png) 2. Mixamo * 找到GLTF匯入Unity後檔案中的模型fbx檔,或是直接在Sketchfab上下載fbx也可(要人形完整的) * 匯入[Mixamo](https://www.mixamo.com/#/) ![image](https://hackmd.io/_uploads/Hyb8NYbAT.png) * 製作完動畫後,匯出成fbx,直接丟進去Unity就大功告成!(可以在匯入的model底下找到剛剛的Animation Clip) ![image](https://hackmd.io/_uploads/ryvTNYWCp.png) 3. Model * 大小-- 匯入的模型檔太大的話,可以改Scale Factor(先改這個為主,不要直接改場景的Scale,不然Scale數值會太大或太小) ![image](https://hackmd.io/_uploads/rJKrHtW0T.png) * 材質-- 若發現材質消失的話,可以試試看Use External Material或是Use Embedded Material,再不行的話就直接在Unity內創幾個Material然後重貼進來(適用在模型Material算少的時候) ![image](https://hackmd.io/_uploads/SJ1gUt-0a.png) * 動畫-- 通常fbx附加的動畫都不是Write able,所以要編輯Animation的一些性質,例如looping、Curve等等簡單的設定可以直接從這邊設定 ![image](https://hackmd.io/_uploads/SkfYIKZ0a.png) 4. Animation (若同學是在Blender中製作後再匯入Unity的就可以先跳過^^) * 在Unity中,產生Animation主要也有兩個角色(怎麼都是兩個XD)-- * Animation:包含整個動畫內容,時間軸上每個物件的屬性變化、時間軸上的事件觸發等等 ![image](https://hackmd.io/_uploads/H1PX_YW0T.png) * Animator:包含Layer(同時執行不同的動畫流程,一心二用的感覺)、Parameter(控制流程的變數)、流程圖(包含Animation、Transition、Condition)等等 ![image](https://hackmd.io/_uploads/Byv2dKWCT.png) * 把剛剛fbx底下的Animation Clip放入場景中的人物模型上,Unity就會自動生成Animator,並設計好遊戲開始變自動播放該Animation囉! ![image](https://hackmd.io/_uploads/SJdaDFW0p.png) * Q. 如何在Unity Event中觸發動畫播放? A. * 跟Audio source很像,加入Animator.Play()的函示即可,Play的名稱即為想播放的Animation名稱(在流程圖上的,非Clip名稱) ![image](https://hackmd.io/_uploads/rJxIKtZA6.png) * 設計簡單的流程圖,讓角色觸發完後回到原本的行為動畫 ![image](https://hackmd.io/_uploads/BJue9Y-Aa.png) * **更多有關Animation、Animator的製作,在之後上課外聘講師也會介紹喔!** ### 10.3. Particle System (粒子系統)--「逆轟高灰」! * 透過Light、PostProcessing、Model、UI的場景創建下,我們可以讓場景看起來更有氣氛 ![image](https://hackmd.io/_uploads/S1e2pQl60.png) * 在管子中加上向上的(Cone-Shape) Particle System,使水果拋上時有上升氣流的感覺 ![image](https://hackmd.io/_uploads/rJZNJ4gpR.png) * 在參數中可多使用Random between constants/curves使效果更自然 ![image](https://hackmd.io/_uploads/BkIFJ4l6C.png) * 適時增加點天氣效果,例如下雨、迷霧、打雷等等的特效,會使作品更加分! ![image](https://hackmd.io/_uploads/HJbHA7gpR.png) * 配合自己的場景大小以及所需的雨量,可以分別在Emission以及Shape的Radius中調整喔 ![image](https://hackmd.io/_uploads/HkZaitZCT.png) * **更多有關Particle System的功能介紹,在3/23的Unity工作坊時講師也會介紹喔!** ### 10.4. Post Processing 提升畫面氛圍 * Global Volume 1. 創建一個空的GameObject 2. 在Inspector介面中,點選"Add Component" -> "Volume" 3. 創建一個新的profile 4. 點擊"Add Override",為其添加各種濾鏡 5. 使用Weight來設定權重,也可以利用此參數設計動態效果(例如受傷) ![image](https://hackmd.io/_uploads/B1jzlElaC.png) * Local Volume 1. 為這個GameObject添加一個Box Collider,限定濾鏡作用範圍 2. 將"Volume"底下的Mode欄位,改為"Local"即可 ![image](https://hackmd.io/_uploads/H1LCy4l6C.png) ![image](https://hackmd.io/_uploads/SksyeNxTR.png) --- # 2024/09/19 Multiplayer Fruit Ninja VR ## 目標 承續上週的Fruit Ninja VR,教學如何使用Photon Pun快速轉換成多人版本,並介紹如何在Unity中多人合作與製作專案須注意的要素。 課前/自學:[How to Make a VR Multiplayer Game by Valem](https://www.youtube.com/watch?v=KHWuTBmT1oI&t=1698s) ## 上課重點紀錄 :::success 請善用瀏覽器的關鍵字搜尋功能:```Ctrl+F``` ::: ### 1. PUN 2 * 設定Photon Cloud Apps 1. 登入(註冊)Photon帳號,進入設定畫面 ![image](https://hackmd.io/_uploads/SJTO7eLaA.png) 2. 建立新應用程式 ![image](https://hackmd.io/_uploads/B1gfNlIpA.png) 3. 應用類型「多人遊戲 -> SDK「Pun」 -> 應用名稱「自訂」 ![image](https://hackmd.io/_uploads/rydCNlUpR.png) * 加入PUN 2到Unity專案 1. Asset store 搜尋 ["PUN 2"](https://assetstore.unity.com/packages/tools/network/pun-2-free-119922),點擊「Open in Unity」 ![image](https://hackmd.io/_uploads/Hk1XxxUT0.png) 2. 回到Unity Package Manager將PUN2 import ![image](https://hackmd.io/_uploads/BJgxblL6C.png) ![image](https://hackmd.io/_uploads/rJFUbeU6A.png) 3. 匯入後自動跳出Pun Wizard或是可以在window標籤中找到 ![image](https://hackmd.io/_uploads/BkwN8g8aR.png) ![image](https://hackmd.io/_uploads/r1GxPe8TR.png) 4. 將先前建好的PUN App ID貼上後Setup Project,將自動找到Photon Server Settings並填入App ID ![image](https://hackmd.io/_uploads/B1cOOgIaR.png) ![image](https://hackmd.io/_uploads/H1b6uxITC.png) * [PUN 2 API reference](https://doc-api.photonengine.com/en/pun/current/index.html) 善用官方的API reference可以快速知道每個class、function的使用方法、層級關係 ![image](https://hackmd.io/_uploads/rJ6wXZUaC.png) ### 2. Connect to Server 1. 新增一個C# script "connectToServer.cs" ```c# using UnityEngine; using Photon.Pun; using Photon.Realtime; public class ConnectToServer : MonoBehaviourPunCallbacks { void Start() { Debug.Log("Connecting..."); PhotonNetwork.GameVersion = "0.0.1"; PhotonNetwork.ConnectUsingSettings(); } public override void OnConnectedToMaster() { Debug.Log("Connected to Server"); PhotonNetwork.JoinLobby(); } public override void OnDisconnected(DisconnectCause cause) { Debug.Log("Disconnected from Server for reason " + cause.ToString()); } } ``` 2. 在場景中新增一個Empty object,將"connectToServer.cs"拉到component中 ![image](https://hackmd.io/_uploads/SJvDZZITA.png) 3. 如果目前為止的設定都正確,Play之後可以在console看到以下Debug log ![image](https://hackmd.io/_uploads/HkB6bbU6C.png) ### 3.1. 創建房間 - XRI Keyboard * XRI keyboard 1. import ![image](https://hackmd.io/_uploads/ByCUEGIaR.png) 2. 將XRI Global Keyboard Manager加到場景,並且把XR Origin拉到player root ![image](https://hackmd.io/_uploads/B1aBrz86C.png) 3. 使用Samples -> XR Interaction Toolkit -> 版本號 -> Spatial Keyboard -> Prefabs中的Input Field Global Keyboard來新增Input Field ![image](https://hackmd.io/_uploads/SJ-wIzUTA.png) ### 3.2. 創建房間 - 列出、加入房間 * **RoomListing.cs** - 綁在顯示房間名稱的按鈕prefab上,將會由"RoomListingMenu.cs"根據房間的列表生成與房間個數相同的prefab ```c# using UnityEngine; using Photon.Realtime; using Photon.Pun; using TMPro; public class RoomListing : MonoBehaviour { [SerializeField] private TMP_Text _text; public RoomInfo RoomInfo { get; private set; } public void SetRoomInfo(RoomInfo info) { RoomInfo = info; _text.text = info.Name; } public void OnClick_JoinRoom() { Debug.Log("Joining Room: " + RoomInfo.Name); PhotonNetwork.JoinRoom(RoomInfo.Name); } } ``` * **public void SetRoomInfo(RoomInfo info)** - 由"RoomListingMenu.cs"傳入RoomInfo來更改顯示房名 * **public void OnClick_JoinRoom()** - 當JoinRoom按鈕被處時調用 ![image](https://hackmd.io/_uploads/HJa3GEwa0.png) * RoomListingMenu.cs - 繼承MonoBehaviourPunCallbacks類,覆寫Photon提供的Callback function,用來控制UI的房間清單中RoomList Prefab的生成與消滅 ```c# using System.Collections.Generic; using Photon.Pun; using UnityEngine; public class RoomListingMenu : MonoBehaviourPunCallbacks { [SerializeField] private RoomListing _roomListingPrefab; [SerializeField] private Transform _content; private List<RoomListing> _listings = new List<RoomListing>(); public override void OnRoomListUpdate(List<Photon.Realtime.RoomInfo> roomList) { foreach (Photon.Realtime.RoomInfo info in roomList) { if (info.RemovedFromList) { int index = _listings.FindIndex(x => x.RoomInfo.Name == info.Name); if (index != -1) { Destroy(_listings[index].gameObject); _listings.RemoveAt(index); } } else { int index = _listings.FindIndex(x => x.RoomInfo.Name == info.Name); if (index == -1){ RoomListing listing = Instantiate(_roomListingPrefab, _content); if (listing != null) { listing.SetRoomInfo(info); _listings.Add(listing); } } } } } } ``` > 標記為 removeFromList 的房間不會立即從伺服器上刪除,避免瞬間的改變在其他客戶端出現混亂或不一致的狀態,以及性能考量 > * OnRoomListUpdate(List<Photon.Realtime.RoomInfo> roomList) : 當RoomList的內容變化時被調用,回傳有房間資訊的List ![image](https://hackmd.io/_uploads/BkLHI4Pa0.png) ### 3.3. 創建房間 - 載入新場景 * 當加入或創建房間之後,顯示當前房間的面板 1. 創建房間 - CreateRoom.cs / OnCreatedRoom() ```c# using UnityEngine; using Photon.Pun; using Photon.Realtime; using TMPro; public class CreateRoom : MonoBehaviourPunCallbacks { [SerializeField] private TMP_Text _roomName; public void OnClick_CreateRoom() { if (!PhotonNetwork.IsConnected) return; RoomOptions roomOptions = new RoomOptions(); roomOptions.MaxPlayers = 2; PhotonNetwork.JoinOrCreateRoom(_roomName.text, roomOptions, TypedLobby.Default); } public override void OnCreatedRoom() { Debug.Log("Room Created Successfully", this); PhotonNetwork.LoadLevel(1); } public override void OnCreateRoomFailed(short returnCode, string message) { Debug.Log("Room Creation Failed: " + message, this); } } ``` ### 3.4. 房間 - 玩家同步至host場景 * PhotonNetwork.AutomaticallySyncScene = true - 可以使client自動轉移到與host相同的場景 ### 4.1. 網路物件 * Photon View > * 唯一識別 > * 物件同步 > * ownership > * 監聽網路事件 ![image](https://hackmd.io/_uploads/SJ994wKaC.png) * Photon Transform View (Classic) > * 同步位置、旋轉、大小 > * 處理網路同步中的不連續 (interpolation) > * 預測物體位置 (Extrapolation) ![image](https://hackmd.io/_uploads/H1lswvYpA.png) * Photon Rigidbody View > * 同步速度和角速度,使運動符合物理規則。 > * 預測、插值 * Photon Animation View > * 同步動畫**參數** (trigger容易丟失) > * 平滑過度 ### 4.2. 網路物件 - 玩家 * 生成玩家要解決的問題: > 1. 一個場景只能有一個Camera > 2. 只能有一個Audio Listener > 3. 只能操控自己的player prefab > 4. photon rigidbody view必須綁一個rigidbody,但是player controller組件也有rigidbody效果,所以必須拔掉 * 解決方法 > 1. 在Player prefab中先把Camera、Audio Listener、Controller除model外的東西、Locomotion相關scripts、轉場用的FadeScreen預設關掉 > 2. 在載入場景的時候將自己的player prefab中這些東西打開 ![image](https://hackmd.io/_uploads/HJ1mAwYa0.png) * **LocalPlayerSetup.cs** ```c# using System.Collections.Generic; using Photon.Pun; using UnityEngine; public class LocalPlayerSetup : MonoBehaviourPunCallbacks { public List<Component> componentsToEnable; public List<GameObject> gameObjectsToEnable; public void Setup() { if (photonView.IsMine) { foreach (Component component in componentsToEnable) { if(component != null) { Behaviour behaviour = component as Behaviour; if (behaviour != null) { behaviour.enabled = true; } else { Renderer renderer = component as Renderer; if (renderer != null) { renderer.enabled = true; } } } } foreach (GameObject gameObject in gameObjectsToEnable) { if(gameObject != null) { gameObject.SetActive(true); } } } } } ``` ### 4.3. 網路物件 - 生成 * 修改ConnectToServer.cs ```c# using UnityEngine; using Photon.Pun; using Photon.Realtime; using UnityEngine.XR.Interaction.Toolkit.Samples.SpatialKeyboard; using UnityEngine.SceneManagement; public class ConnectToServer : MonoBehaviourPunCallbacks { private GameObject singlePlayer; public GameObject playerPrefab; public GameObject singlePlayerPrefab; public Transform spawnPoint; private GameObject _player; public GlobalNonNativeKeyboard keyboardManager; private int sceneIndex; private Scene currentScene; void Start() { PhotonNetwork.AutomaticallySyncScene = true; PhotonNetwork.AddCallbackTarget(this); currentScene = SceneManager.GetActiveScene(); sceneIndex = currentScene.buildIndex; Debug.Log("Scene Index: " + sceneIndex); if(sceneIndex == 0) { Debug.Log("Connecting..."); PhotonNetwork.GameVersion = "0.0.1"; PhotonNetwork.ConnectUsingSettings(); InstantiateSinglePlayer(); }else if(sceneIndex == 1) { Debug.Log("Joining Room..."); _player = PhotonNetwork.Instantiate(playerPrefab.name, spawnPoint.position, spawnPoint.rotation); _player.GetComponent<LocalPlayerSetup>().Setup(); PhotonNetwork.LocalPlayer.NickName = "Player " + PhotonNetwork.CurrentRoom.PlayerCount; } } public override void OnConnectedToMaster() { Debug.Log("Connected to Server"); if(!PhotonNetwork.InLobby)PhotonNetwork.JoinLobby(); } public override void OnDisconnected(DisconnectCause cause) { Debug.Log("Disconnected from Server for reason " + cause.ToString()); } public void InstantiateSinglePlayer() { singlePlayer = Instantiate(singlePlayerPrefab, spawnPoint.position, spawnPoint.rotation); keyboardManager.playerRoot = singlePlayer.transform; keyboardManager.cameraTransform = singlePlayer.GetComponentInChildren<Camera>().transform; } } ``` > * 生成網路物件 (使用prefab name,prefab必須含Photon View並放在Assets/Resources) > * PhotonNetwork.Instantiate(playerPrefab.name, spawnPoint.position, spawnPoint.rotation); > * 生成一般物件 > * Instantiate(singlePlayerPrefab, spawnPoint.position, spawnPoint.rotation); ### 5. RPC (Remote Procedure Calls) * 使用時機 1. 玩家間狀態同步的主要手段之一(血量、分數等) 2. 某個行為觸發的事件需要立即通知多位玩家 3. 需要由伺服器或MasterClient決策的邏輯處理 4. 針對特定玩家的通訊(私訊、交易等) * 程式範例(加分) ```c# public void AddScore(string playerName) { Debug.Log("AddScore by fruit"); GetComponent<PhotonView>().RPC("RPC_AddScore", RpcTarget.AllBuffered, playerName); } [PunRPC] public void RPC_AddScore(string playerName) { Debug.Log("RPC_AddScore by fruit, playerName: " + playerName); NetworkGameManager networkGameManager = GameObject.Find("NetworkGameManager").GetComponent<NetworkGameManager>(); networkGameManager.AddScore(playerName, fruitScore); } ``` > * 當AddScore(string playerName)被調用,透過掛載此腳本的物件上的PhotonView來調用RPC,其他client的相同view ID物件上的RPC_AddScore(string playerName)將會被調用 > * RPC function 需要在前面加上[PunRPC]標籤 * 使用方式 * RPC(string methodName, **RpcTarget target**, params object[] parameters) > target指定傳送RPC的方式與對象,詳見下一部分RPC Target說明 > parameters的數量為要傳入所調用的RPC function的參數數量 * RPC(string methodName, **Player targetPlayer**, params object[] parameters ) > 此方法允許你在指定玩家的客戶端上進行 RPC 調用。當然,調用會受到本客戶端和遠端客戶端的延遲影響。 > targetPlayer傳入指定玩家的Player類,可以透過Player Prefab上的PhotonView中的Controller或Owner兩種Properties來取得 * RPC Target 1. All > 將 RPC 發送給所有其他玩家,並立即在本客戶端執行。後來加入的玩家將不會執行此 RPC。 2. Others > 將 RPC 發送給所有其他玩家。本客戶端不執行該 RPC。後來加入的玩家將不會執行此 RPC。 3. MasterClient > 將 RPC 僅發送給 MasterClient。請注意:MasterClient 可能會在執行該 RPC 之前斷開連接,這可能會導致 RPC 丟失。 4. AllBuffered > 將 RPC 發送給所有其他玩家,並立即在本客戶端執行。新玩家加入時會收到此 RPC,因為它被緩存(直到本客戶端離開)。 5. OthersBuffered > 將 RPC 發送給所有其他玩家。本客戶端不執行該 RPC。新玩家加入時會收到此 RPC,因為它被緩存(直到本客戶端離開)。 6. AllViaServer > 通過伺服器將 RPC 發送給所有玩家(包括本客戶端)。本客戶端像其他客戶端一樣在從伺服器接收到該 RPC 時執行它。優點:伺服器發送 RPC 的順序在所有客戶端上是相同的。 7. AllBufferedViaServer > 通過伺服器將 RPC 發送給所有玩家(包括本客戶端),並為後來加入的玩家緩存它。本客戶端像其他客戶端一樣在從伺服器接收到該 RPC 時執行它。優點:伺服器發送 RPC 的順序在所有客戶端上是相同的。 ### 6. RaiseEvent * 使用時機 1. 類似於RPC,為玩家間狀態同步的主要手段之一(分數、金錢等) 2. 可以使用在未綁定PhotonView的對象上(較RPC更靈活) 3. 非特定對象的事件,例如:天氣變化、難度調整 * 程式範例(Fruit Ninja VR沒有使用到RaiseEvent,以官方文件範例說明) ```c# using ExitGames.Client.Photon; using Photon.Realtime; using Photon.Pun; public class SendEventExample { // If you have multiple custom events, it is recommended to define them in the used class public const byte MoveUnitsToTargetPositionEventCode = 1; private void SendMoveUnitsToTargetPositionEvent() { object[] content = new object[] { new Vector3(10.0f, 2.0f, 5.0f), 1, 2, 5, 10 }; // Array contains the target position and the IDs of the selected units RaiseEventOptions raiseEventOptions = new RaiseEventOptions { Receivers = ReceiverGroup.All }; // You would have to set the Receivers to All in order to receive this event on the local client as well PhotonNetwork.RaiseEvent(MoveUnitsToTargetPositionEventCode, content, raiseEventOptions, SendOptions.SendReliable); } } ``` > 此程式將MoveUnitsToTargetPosition這個事件的EventCode設為1, 並傳送了Vector3(10.0f, 2.0f, 5.0f), 1, 2, 5, 10以上五項Objects,ReceiverGroup是All,使用Reliable方式傳送 * 使用方式 * 發起事件 * RaiseEvent(byte eventCode, object eventContent, RaiseEventOptions raiseEventOptions, SendOptions sendOptions) > Eventcode須介於0-199間,但避免使用0較好,大約有200自定義事件可以使用 > eventContent須為可序列化的對象,例如string, byte, integer, float等,以及他們的array,或者這些類型的數組。使用byte作為key的Hashtable也可以 > [raiseEventOption](https://doc-api.photonengine.com/en/pun/current/class_photon_1_1_realtime_1_1_raise_event_options.html#a1e93c8a0af49774c4da13c1ca93f04d5)可以做一些客製化設定例如能夠接收此事件的群組 > sendOptions用於設置可靠性、加密等選項的傳送選項。 * 接收事件 * 方法1 - 實作IOnEventCallback的OnEvent() ```c# using ExitGames.Client.Photon; using Photon.Realtime; using Photon.Pun; public class ReceiveEventExample : MonoBehaviour, IOnEventCallback { private void OnEnable() { PhotonNetwork.AddCallbackTarget(this); } private void OnDisable() { PhotonNetwork.RemoveCallbackTarget(this); } public void OnEvent(EventData photonEvent) { byte eventCode = photonEvent.Code; if (eventCode == MoveUnitsToTargetPositionEvent) { object[] data = (object[])photonEvent.CustomData; Vector3 targetPosition = (Vector3)data[0]; for (int index = 1; index < data.Length; ++index) { int unitId = (int)data[index]; UnitList[unitId].TargetPosition = targetPosition; } } } } ``` > 1. 需要PhotonNetwork.AddCallbackTarget(this) > 2. 需要繼承IOnEventCallback > 3. 必須實作OnEvent() * 方法2 - 註冊function到PhotonNetwork.NetworkingClient.EventReceived ```c# using ExitGames.Client.Photon; using Photon.Realtime; using Photon.Pun; public class ReceiveEventExample : MonoBehaviour { private void OnEnable() { PhotonNetwork.NetworkingClient.EventReceived += OnEvent; } private void OnDisable() { PhotonNetwork.NetworkingClient.EventReceived -= OnEvent; } private void OnEvent(EventData photonEvent) { byte eventCode = photonEvent.Code; if (eventCode == MoveUnitsToTargetPositionEvent) { object[] data = (object[])photonEvent.CustomData; Vector3 targetPosition = (Vector3)data[0]; for (int index = 1; index < data.Length; ++index) { int unitId = (int)data[index]; UnitList[unitId].TargetPosition = targetPosition; } } } } ``` > 1. 必須註冊接收事件的function --- # 2024/11/07 Multi-Final IK ## 目標 OpenXR + Final IK + PhotonStream 製作多人系統中的Full Body Avatar ## 所使用到的素材 (皆已放在課程素材包內) 1. [Final IK](https://www.dropbox.com/scl/fo/hvt3vmuju6xmbufl9lns7/AMexg822sFl3cOEZ6nmi8Xo?e=1&preview=Final+IK+JiaJun.unitypackage&rlkey=qtk39lhtjyt6dbnwiuwtdh4ct&st=zcdlxvhp&dl=0) ## 上課重點紀錄 --- # x. 其他 自學/課後: * [Quest Quick Build (Newest)](https://www.youtube.com/watch?v=paVX3Pm4Yq4) * [Oculus Gesture](https://www.youtube.com/watch?v=Lc1PuEatrCA) * Body Skeleton 若**還有時間**則可以去研究一下身體的部分,有身體的虛擬角色會更有帶入感! [Valem Tutorial](https://www.youtube.com/watch?v=tBYl-aSxUe0&list=PLrk7hDwk64-ZRB5lz-xJhgH7Lp6MIRcHJ&pp=iAQB) * Final IK 這個Package是包含手、手指以及身體等的終極Module,如果你是**高手的話**則可以嘗試一下! [Unity Asset Store](https://assetstore.unity.com/packages/tools/animation/final-ik-14290) * [Multiplayer](https://doc.photonengine.com/fusion/current/tutorials/host-mode-basics/1-getting-started) --- * Plastic SCM * 注意:在製作新進度前、或Push之前要先Pull!!! * 建議:Comment最好簡潔有力(寫重點),也可以再標記一次push的時間點 * 建議三人分別是兩位程式、一位美術來使用Plastic SCM * Q. 如果不只三個人想合作撰寫怎麼辦? A. 如果是美術或音樂組的話,程式組可以先生出一個prototype讓他們測試(例如模型大小、整體空間等等是可以先決定的),製作好模型、場景、動畫或音樂後則可包成Unity Package後匯入到有連結到Plastic SCM的專案中 * 功能(有學過Git概念的可以自行跳過) 1. Pull **注意:在製作新進度前、或Push之前要先Pull!!!** 自動Merge其他更變集的資料,如有衝突的話則可能使用到以下兩種情況: * choose from Origin Master : 選擇以本機的更變集為主 * choose from resources : 選擇以目的地的更變集為主 請自行與其他組員協調好再執行Pull的動作,否則更變集被覆蓋有時會很難救回來的! 2. Push * 建議每次上傳都打個簡短明瞭的註解 * Unity會先進行Check in的動作 * Push前如果想恢復某個物件或檔案修改前的版本,Undo即可 3. Branch * 假設今天非主要開發人員想進行測試或開發新功能,會建議在新的場景或另一個Branch製作 * 好處是別人不會Pull到你的Branch的東西,也就是沒有Merge或是Unity設定被覆蓋的問題 4. Switch to workspace 可以在Plastic SCM應用程式的更變集介面中右鍵某個更變集,並按下Switch to workspace則可將目前的專案還原到當時的版本 * 還原前必須先將當下的變更Push上去 * 若確定要還原到該變更,則需把該更變集之後的更變集**全部刪除**! * 創建 1. 下載Plastic SCM雲端版 [https://www.plasticscm.com/download](https://) 限制:三人、5Gb內免費 ![image](https://hackmd.io/_uploads/BkOeTpH6p.png) 2. 至Plastic SCM網頁版,使用Unity ID登入後,創建一個新的組織並創建Unity Repo * 先去Unity Dashboard新增Project [https://cloud.unity.com/](https://) ![image](https://hackmd.io/_uploads/B1FYAprTp.png) * 再到Plastic SCM Dashboard、進入Cloud [https://www.plasticscm.com/dashboard](https://) ![image](https://hackmd.io/_uploads/BksTppSaT.png) * 有出現專案畫面的話就代表專案建立成功囉! ![image](https://hackmd.io/_uploads/B186kASTa.png) * 現在就可以打開剛下載好的Plastic SCM,確認存儲庫是否有該專案啦 3. 開啟你想同步的Unity Project,並到Unity Version Control的介面 4. 填寫相關資訊,包含repo名稱,即步驟2.的專案名 5. 出現類似下面畫面後則代表你成功了! ![image](https://hackmd.io/_uploads/HyD86uZ06.png) 7. 最後一步啦! 寫下你的第一則Comment、Check in changes並Push就大功告成啦! * 邀請 1. 到Plastic SCM Cloud,點選組織的設定成員功能 ![image](https://hackmd.io/_uploads/B1IF1CBTT.png) 3. 利用Gmail邀請你的成員加入組織後,他們就可以在Plastic SCM的應用程式中選擇該組織並找到你的repo進行共編啦! # x2. 注意與建議 :::success 注意事項! 製作專案前有空看看吧!小心踩到這些地雷喔~ (是教授會注意的點^^) ::: * 避免**動暈症** (順移時角度變化過大、角色旋轉方式、急速移動) * 畫面過於炫彩(特效、顏色、燈光),導致**視覺疲乏** * 遊戲**元素過多**,無法呈現遊玩特色或故事主軸 (也會使專案loading變重) * 美術**風格相斥或過多**、與音樂不搭 * 隨時注意遊戲時的fps (最好能維持在**60Hz以上**) * 若角色有全身的話(除了頭以外),記得把**影子**拿掉! (mesh renderer>lightning * 有導引玩家的提示會更好! :::success 建議です! 學長姐們修課經驗談~ ::: * **遊戲的操作自由度越大越好、操作方式越少越好、越有創意越好** * 在VR世界中因場景很大的關係,有時候不會選擇切換整個scene,而是在不同地區使用不同模型、不同效果渲染+fadein/fadeout來轉場。(善用VR世界的環境) * 美術的部分,要注意素材不要選擇過度high poly的模型,會導致幀數下降,particle system在VR作品中也要慎用 * 一個專案的美術風格盡量維持一~兩種就好,且不要極端 (lowpoly+highpoly就會很怪) * 配音的部分,由於同學們並非專業配音員,容易因為聲音含糊或收錄到環境音,而導致玩家出戲、甚至聽不清內容。建議同學可以多加利用免費的 AI文字轉語音工具(TTS),如: [Elevenlabs](https://elevenlabs.io/)、[ttsmaker](https://ttsmaker.com/zh-hk)、[edge-tts(需使用python,可調性較大)](https://pypi.org/project/edge-tts/)等等。 * VR遊戲中,使用[空間音效(Audio Spatializer)](https://docs.unity3d.com/Manual/VRAudioSpatializer.html),可以讓玩家透過聲音,定位視野外的物體,為你的故事/玩法創造更多可能性! * 使用好音樂/音效,可以大幅提升玩家的沉浸感、改變故事的氛圍、甚至可以用來引導玩家以推進劇情! * 專案到後面script、prefab、package各類檔案會越來越多,記得隨時整理「需要」的檔案,並命名分類,分裝至不同資料夾中。 # 番外篇1. 連結VR頭盔與突發狀況解決方法 * Oculus Quest 2 / 3 1. Oculus Link (有線) 步驟1. 下載[Oculus](https://www.meta.com/zh-tw/help/quest/articles/headsets-and-accessories/oculus-rift-s/install-app-for-link/)應用程式並開啟 步驟2. 頭盔啟用Usb外部存取權(有出現的話) 步驟3. Enable Oculus Link (插上數據線的時候應該會出現,沒有的話去選單手動點選或切換有無線幾次) 步驟4. 到了白色的大廳就代表成功囉! ![image](https://hackmd.io/_uploads/S1Oxg4JRa.png) 2. Air Link(無線) 步驟1. 下載[Oculus](https://www.meta.com/zh-tw/help/quest/articles/headsets-and-accessories/oculus-rift-s/install-app-for-link/)應用程式並開啟 步驟2. 將電腦與頭盔連線至同樣網路(電腦分享也可) 步驟3. 到選單切換成Air Link模式、點選自己電腦的名稱並連線 步驟4. 連線成功後按下Enable Link 步驟5. 到了白色的大廳就代表成功囉! 3. 突發狀況 * 連線至白色大廳的途中一直轉圈圈進不去 **解決方法**:頭盔斷開重連,再不行就重新開機 * 線插進去了卻沒接到Oculus Link **解決方法**:頭盔重新開機,電腦將Unity關閉、去系統管理員把所有有關Oculus的程序關閉 (起碼比電腦重開來得好==) * 進入大廳後或是測試途中開始閃爍或卡卡的 **解決方式**:盡量使用有線的方式Link、或拔掉重連 # 番外篇2. 好用資源及連結 * 專案流程、組內分工/討論工具 * 約? [約時間網站](https://www.when2meet.com/) * 專案流程分工? [Trello](https://trello.com/zh-Hant) * 討論工具(圖像式)? 1. [Milanote](https://milanote.com/) 2. [Excalidraw](https://excalidraw.com/) * 資料與連結整理 * 程式組 1. [Unity 論壇](https://forum.unity.com/) 2. [Unity Document- Script](https://docs.unity3d.com/ScriptReference/) 3. [Unity 常用語法(較友善XD)](https://www.gameislearning.url.tw/article_content.php?getb=2&foog=9997) 4. [Steven Hu](https://steven-net.medium.com/) * 美術組 1. [Sketchfab](https://sketchfab.com/feed) 2. [SpeedTree](https://store.speedtree.com/) 3. [去背神器](https://www.remove.bg/zh-tw) 4. [UI Icon](https://www.flaticon.com/) 5. [Quadspinner](https://quadspinner.com/) 6. [Pinterest](https://www.pinterest.com/) 7. [ArtStation](https://www.artstation.com/?sort_by=community&dimension=all) 8. [Quixel](https://quixel.com/) * 音樂組 1. [小森平:常用音效](https://taira-komori.jpn.org/game01tw.html) 2. [甘茶音樂工坊:BGM](https://amachamusic.chagasi.com/music_uchiagehanabi.html) 3. [Text-to-Speech: Elevenlabs](https://elevenlabs.io/) 4. [音檔裁切](https://mp3cut.net/tw/) 5. [背景降躁](https://products.aspose.app/audio/zh-hant/remove-background-noise/mp3) * Youtube超好用教學 * Unity基礎 1. [Brackeys](https://www.youtube.com/@Brackeys) 2. [米飯教學室](https://www.youtube.com/watch?v=OZH7GSsLgaE) 3. [阿空](https://www.youtube.com/@RemptyGame/videos) * VR基礎 1. [Valem Tutorial](https://www.youtube.com/@ValemTutorials) 2. [Valem](https://www.youtube.com/@ValemVR)

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully