目:Ruby‘s Adventure主角與第一腳本全解析)
做Unity 2D新手項(xiàng)目Ruby‘s Adventure是繞不開的一課。這是Unity官方放出來(lái)的免費(fèi)2D游戲案例大家習(xí)慣叫它“Ruby的冒險(xiǎn)”而“主角和第一腳本”這一段正好是整個(gè)項(xiàng)目里從“擺場(chǎng)景”轉(zhuǎn)向“寫邏輯”的關(guān)鍵節(jié)點(diǎn)。你會(huì)發(fā)現(xiàn)主角落地之后不再是一張靜態(tài)圖片通過(guò)Rigidbody2D、Animator和一份C#腳本它第一次能被鍵盤控制、隨方向切換動(dòng)畫。這一節(jié)解決的不只是“讓角色動(dòng)起來(lái)”更重要的是讓你理解Unity推薦的組件化協(xié)作方式GameObject只是殼動(dòng)畫、物理、輸入處理各司其職腳本負(fù)責(zé)把能力串起來(lái)。適合誰(shuí)剛學(xué)完Unity界面操作、準(zhǔn)備進(jìn)入2D游戲開發(fā)的新手以及想要快速搭一個(gè)2D橫版或俯視角色控制器的同學(xué)。下面我按自己實(shí)際做這個(gè)項(xiàng)目的順序把這節(jié)課拆開講透。1. 項(xiàng)目整體拆解為什么Unity官方教程要讓你先做“主角和第一腳本”1.1 Ruby’s Adventure在2D學(xué)習(xí)路徑中的位置Unity官方學(xué)習(xí)路徑里Ruby‘s Adventure是給“已經(jīng)會(huì)基礎(chǔ)操作、但還沒(méi)有寫過(guò)完整游戲邏輯”的開發(fā)者準(zhǔn)備的。前面你可能已經(jīng)學(xué)會(huì)了創(chuàng)建場(chǎng)景、擺放Tilemap、給地面加碰撞器但這些操作都還停留在編輯器層面。到了“主角和第一腳本”這一節(jié)才第一次真正進(jìn)入代碼驅(qū)動(dòng)的游戲循環(huán)玩家按方向鍵角色移動(dòng)動(dòng)畫切換攝像機(jī)跟隨。這個(gè)位置安排得很講究。移動(dòng)控制是所有動(dòng)作游戲的地基后面你想加敵人巡邏、拾取物品、觸發(fā)機(jī)關(guān)、播放對(duì)話都離不開“主角能走動(dòng)”這個(gè)大前提。而且Ruby項(xiàng)目刻意把復(fù)雜度控制得很好沒(méi)有跳躍、沒(méi)有重力、沒(méi)有敵人AI主角就是一個(gè)在俯視角場(chǎng)景里漫游的小女孩。這種設(shè)計(jì)讓新手能把全部注意力放在“腳本是怎么和組件協(xié)作”上而不是被一堆物理參數(shù)淹沒(méi)。1.2 主角對(duì)象背后的組件化設(shè)計(jì)思路很多人第一次接觸Unity會(huì)犯一個(gè)思維錯(cuò)誤以為“主角”是某種特殊物體寫代碼時(shí)想在一個(gè)腳本里把移動(dòng)、動(dòng)畫、碰撞全部搞定。實(shí)際上Unity的核心思想是組件化。一個(gè)名為Ruby的GameObject本質(zhì)上只是一個(gè)容器它身上掛了SpriteRenderer負(fù)責(zé)顯示Animator負(fù)責(zé)播放動(dòng)畫Rigidbody2D負(fù)責(zé)物理模擬BoxCollider2D負(fù)責(zé)碰撞檢測(cè)最后再加一個(gè)你自己寫的腳本負(fù)責(zé)接收輸入并指揮這些組件工作。這樣的好處是職責(zé)清晰。顯示出問(wèn)題查SpriteRenderer和動(dòng)畫面板物理出問(wèn)題查剛體和碰撞體輸入沒(méi)反應(yīng)查腳本和Input設(shè)置。你不用在一個(gè)幾百行的腳本里翻找問(wèn)題根源。后面寫敵人、NPC、道具時(shí)你會(huì)發(fā)現(xiàn)幾乎所有對(duì)象都是同一套模式先組裝組件再寫一個(gè)控制腳本。Ruby這個(gè)項(xiàng)目就是一次最標(biāo)準(zhǔn)的組件化示范。2. 動(dòng)手前準(zhǔn)備Unity版本、2D項(xiàng)目模板和官方素材導(dǎo)入2.1 選對(duì)Unity版本和項(xiàng)目模板做Ruby‘s AdventureUnity版本建議選LTS版本目前主流是2021 LTS或2022 LTS別追最新版。不是說(shuō)新版不能用而是這套官方教程用舊版Input Manager寫新版本默認(rèn)可能開啟新輸入系統(tǒng)會(huì)讓新手平白多踩一個(gè)坑。我自己帶人做這個(gè)項(xiàng)目時(shí)最常見(jiàn)的卡點(diǎn)是打開項(xiàng)目后腳本報(bào)錯(cuò)一查往往是輸入系統(tǒng)不兼容。創(chuàng)建項(xiàng)目時(shí)在Unity Hub里選“2DBuilt-In Render Pipeline”模板即可。如果編輯器里看到Universal 2D模板也能用但對(duì)這個(gè)項(xiàng)目來(lái)說(shuō)內(nèi)置渲染管線更省事。重點(diǎn)看一個(gè)設(shè)置Edit Project Settings Player Other Settings Active Input Handling。如果你用的是Input.GetAxisRaw這種老接口這里需要是Input Manager (Old)或者Both。如果當(dāng)前是Input System Package (New)舊代碼會(huì)直接報(bào)“InvalidOperationException: You are trying to read Input using the Input class, but you have switched active Input handling to Input System package”解決辦法就是把這個(gè)選項(xiàng)改成Both或Old改完重啟編輯器。2.2 導(dǎo)入官方素材與認(rèn)識(shí)項(xiàng)目結(jié)構(gòu)素材獲取方式很簡(jiǎn)單打開Unity Learn頁(yè)面找到Ruby‘s Adventure課程按引導(dǎo)把資源包導(dǎo)入當(dāng)前項(xiàng)目或者在Asset Store里搜索官方2D Beginner教程資源。導(dǎo)入后Project窗口會(huì)多出一個(gè)文件夾里面通常包含Art、Prefabs、Scenes、Scripts等子目錄。官方示例場(chǎng)景可以直接打開跑起來(lái)看看最終效果角色可以在一個(gè)2D場(chǎng)景里自由走動(dòng)播放跑步動(dòng)畫切換方向。這個(gè)素材包對(duì)新手特別友好因?yàn)樗衙佬g(shù)資源、動(dòng)畫剪輯、預(yù)制體都準(zhǔn)備好了你不需要自己畫圖也不用手K動(dòng)畫。我建議拿到素材后先花5分鐘把Prefabs目錄里的Player預(yù)制體拖到場(chǎng)景里看一下重點(diǎn)觀察它的Inspector面板有哪些組件、Animator Controller里掛了哪些狀態(tài)。這種“先看成品再動(dòng)手拆”的學(xué)習(xí)方式比一上來(lái)就自己從零組裝高效得多。2.3 場(chǎng)景與相機(jī)初始設(shè)置如果你的場(chǎng)景是空?qǐng)鼍巴先隦uby角色后還要調(diào)整相機(jī)。2D項(xiàng)目里相機(jī)默認(rèn)是Orthographic投影模式意味著沒(méi)有透視變形適合做2D游戲。相機(jī)的Size參數(shù)決定屏幕上能看到多少世界單位數(shù)值越小畫面拉得越近。Ruby素材的美術(shù)風(fēng)格是小型像素角色相機(jī)Size一般設(shè)置在5到8之間具體要看你的Tilemap尺寸。場(chǎng)景里鋪好地面后把相機(jī)拖到能看到角色全景的位置即可。另外建議在項(xiàng)目里添加一個(gè)“Player”的Sorting Layer。選中Ruby角色在SpriteRenderer組件里可以把Sorting Layer設(shè)為Player并設(shè)一個(gè)合適的Order in Layer值。Sorting Layer從上到下決定渲染順序Order in Layer在同層內(nèi)進(jìn)一步排序。比如地面是Default層角色放在Player層這樣角色永遠(yuǎn)蓋在地面上不會(huì)被地面的圖塊擋掉。這是2D開發(fā)里非?;A(chǔ)但特別重要的層級(jí)概念Ruby項(xiàng)目里你就能直觀體會(huì)到。3. 創(chuàng)建主角精靈、動(dòng)畫狀態(tài)機(jī)與物理組件的組合3.1 場(chǎng)景中生成玩家角色有兩種方式在場(chǎng)景里創(chuàng)建主角直接拖官方素材包里的Player預(yù)制體或者手動(dòng)把Ruby的Sprite拖進(jìn)場(chǎng)景再補(bǔ)組件。新手我建議直接拖預(yù)制體因?yàn)橥嫫饋?lái)不會(huì)出錯(cuò)。但這里的學(xué)習(xí)重點(diǎn)不是“能跑”而是搞清楚預(yù)制體上每個(gè)組件的作用所以哪怕你用預(yù)制體也要逐個(gè)打開看看。SpriteRenderer負(fù)責(zé)顯示角色貼圖默認(rèn)會(huì)關(guān)聯(lián)一張Ruby的正面立繪。如果你發(fā)現(xiàn)角色顯示異常先檢查SpriteRenderer的Sprite字段有沒(méi)有正確賦值再檢查Sprite導(dǎo)入設(shè)置里的Filter Mode和PPUPixels Per Unit。PPU決定這個(gè)精靈在世界坐標(biāo)中的大小比如素材是16x16像素、PPU設(shè)為32那這個(gè)角色在世界里就是0.5個(gè)單位寬。如果角色在畫面里顯得過(guò)大或過(guò)小先調(diào)PPU和相機(jī)Size不要直接暴力改Transform的Scale否則后續(xù)碰撞器不好對(duì)齊。3.2 動(dòng)畫狀態(tài)機(jī)與參數(shù)設(shè)計(jì)接下來(lái)是Animator。主角平時(shí)站著不動(dòng)要播待機(jī)動(dòng)畫移動(dòng)時(shí)要播跑步動(dòng)畫而且上下左右四個(gè)方向最好都有對(duì)應(yīng)動(dòng)作。這套邏輯用Animator Controller實(shí)現(xiàn)。打開Animator窗口你會(huì)看到狀態(tài)機(jī)面板。官方素材包里往往自帶一個(gè)Controller里面可能已經(jīng)有一些狀態(tài)和過(guò)渡你要做的是確認(rèn)“控制切換的參數(shù)”和“腳本里寫入的參數(shù)”名字一致。如果你是自己從頭創(chuàng)建推薦用2D Blend Tree而不是一堆Transition。Blend Tree適合處理方向運(yùn)動(dòng)把Idle、Up、Down、Left、Right這些動(dòng)畫剪輯混合到一個(gè)連續(xù)的狀態(tài)里。具體操作在Animator窗口右鍵 Create State From New Blend Tree雙擊進(jìn)入Blend Tree然后在Inspector里把Blend Type設(shè)為2D Simple DirectionalParameters選兩個(gè)float類型參數(shù)比如moveX和moveY。接著把四個(gè)方向的跑步動(dòng)畫拖進(jìn)Motion列表分別設(shè)置它們對(duì)應(yīng)的坐標(biāo)比如向左是-1,0向右是1,0向上是0,1向下是0,-1。腳本里只要持續(xù)寫入moveX、moveY狀態(tài)機(jī)就會(huì)自動(dòng)切換對(duì)應(yīng)動(dòng)畫。參數(shù)類型不要用錯(cuò)。在Animator的Parameters面板里moveX、moveY要設(shè)為Float后面腳本用anim.SetFloat寫入。如果官方自帶的Controller里參數(shù)名和你腳本不一致比如它叫Speed你在腳本里就寫“Speed”別強(qiáng)行改成moveX。新手最常見(jiàn)的動(dòng)畫不切換問(wèn)題十有八九是參數(shù)名對(duì)不上。3.3 物理體與碰撞體的實(shí)際配置值給角色添加Rigidbody2D時(shí)注意幾個(gè)關(guān)鍵屬性。Body Type設(shè)為Dynamic這是動(dòng)態(tài)剛體會(huì)受到物理模擬影響Gravity Scale設(shè)為0因?yàn)檫@個(gè)2D游戲是俯視角角色不需要重力往下掉。這一步非常重要如果你忘記設(shè)0角色一運(yùn)行就會(huì)往下掉。Constraints里建議勾選Freeze Rotation Z否則角色撞到障礙物時(shí)可能會(huì)旋轉(zhuǎn)看起來(lái)像倒地了一樣。BoxCollider2D要手動(dòng)調(diào)整大小。直接點(diǎn)Edit Collider按鈕在Scene視圖里把綠色線框拖到和角色身體差不多大小即可注意不要覆蓋住整個(gè)Sprite的透明區(qū)域否則會(huì)出現(xiàn)“明明沒(méi)碰到墻壁卻卡住”的錯(cuò)覺(jué)。不同版本的免費(fèi)素材里Ruby角色形態(tài)略有差異但原則是碰撞器貼合身體輪廓稍微縮一點(diǎn)都比完全覆蓋好。Ruby項(xiàng)目里撿物品、觸發(fā)對(duì)話都是靠碰撞器觸發(fā)所以這一步別馬虎。4. PlayerController腳本移動(dòng)邏輯的完整實(shí)現(xiàn)與逐行解讀4.1 腳本文件命名與MonoBehaviour生命周期在Project窗口右鍵 Create C# Script把腳本命名為PlayerController。注意文件名必須和類名一致這是C#的硬性要求改掉類名卻沒(méi)改文件名會(huì)導(dǎo)致“The associated script can not be loaded”的報(bào)錯(cuò)。腳本創(chuàng)建后直接拖到Ruby角色上或者用Add Component搜索腳本名掛載。Unity腳本的生命周期方法最開始只需要關(guān)注三個(gè)Awake在對(duì)象創(chuàng)建時(shí)調(diào)用適合獲取組件引用Start在第一幀Update之前調(diào)用適合做初始化Update每幀調(diào)用適合處理輸入和邏輯。還有一個(gè)FixedUpdate是固定時(shí)間間隔調(diào)用的適合處理物理操作。Ruby項(xiàng)目里移動(dòng)代碼放Update還是FixedUpdate其實(shí)都能跑但為了規(guī)范我下面會(huì)給出兩種寫法之間的取舍說(shuō)明。4.2 基于Input Manager的移動(dòng)代碼逐行解析下面這份代碼是Ruby項(xiàng)目最經(jīng)典的PlayerController我一行行講using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { public float speed 5f; private Rigidbody2D rb; private Animator anim; void Start() { rb GetComponentRigidbody2D(); anim GetComponentAnimator(); } void Update() { float moveX Input.GetAxisRaw(Horizontal); float moveY Input.GetAxisRaw(Vertical); Vector2 movement new Vector2(moveX, moveY).normalized * speed; rb.velocity movement; anim.SetFloat(moveX, moveX); anim.SetFloat(moveY, moveY); anim.SetFloat(speed, Mathf.Max(Mathf.Abs(moveX), Mathf.Abs(moveY))); if (moveX 0) { transform.localScale new Vector3(1, 1, 1); } else if (moveX 0) { transform.localScale new Vector3(-1, 1, 1); } } }先看輸入部分。Input.GetAxisRaw(“Horizontal”)會(huì)返回-1、0或1分別代表左、不按、右Vertical對(duì)應(yīng)上下。注意GetAxisRaw和GetAxis的區(qū)別GetAxis帶平滑濾波松開按鍵后數(shù)值會(huì)逐漸回到0角色會(huì)有“滑行”手感GetAxisRaw是即時(shí)值響應(yīng)更直接適合像素風(fēng)2D游戲。Ruby這個(gè)項(xiàng)目里我推薦GetAxisRaw控制更跟手。然后看向量處理。new Vector2(moveX, moveY)直接乘speed斜向移動(dòng)時(shí)會(huì)出問(wèn)題同時(shí)按上和右向量長(zhǎng)度是√2約1.414角色斜著走會(huì)比直線快。加一個(gè).normalized就能把向量長(zhǎng)度保持為1這樣四條線方向速度一致。這個(gè)細(xì)節(jié)很多人會(huì)漏但對(duì)游戲手感影響很大。再解釋為什么用rb.velocity而不是transform.Translate。Rigidbody2D是物理組件它的運(yùn)動(dòng)應(yīng)該交給物理系統(tǒng)管理。如果用transform.Translate直接改位置會(huì)繞過(guò)物理模擬可能導(dǎo)致碰撞檢測(cè)不準(zhǔn)確甚至穿過(guò)墻壁。雖然很多老教程喜歡用transform.Translate但Ruby項(xiàng)目這種2D俯視角游戲用Rigidbody2D.velocity更規(guī)范。由于velocity是按物理幀更新的這里不需要乘Time.deltaTime直接賦速度值即可。動(dòng)畫部分用anim.SetFloat把moveX、moveY和speed寫進(jìn)狀態(tài)機(jī)。如果Blend Tree只靠moveX、moveY驅(qū)動(dòng)那speed可以不用但如果你用狀態(tài)機(jī)里的Transitionspeed可能作為“是否移動(dòng)”的開關(guān)。Mathf.Max(Mathf.Abs(moveX), Mathf.Abs(moveY))的意思是取兩個(gè)方向絕對(duì)值較大的那個(gè)這樣斜走時(shí)speed也能正常為1。最后是角色翻轉(zhuǎn)。Ruby素材的Sprite默認(rèn)朝左或朝右通過(guò)翻轉(zhuǎn)Transform的localScale.x可以讓角色左右轉(zhuǎn)向。這套方法簡(jiǎn)單直接但如果素材里已經(jīng)做了四方向動(dòng)畫你也可以不用localScale而是靠Blend Tree切換左右方向動(dòng)畫。兩種方案選一種即可不要混用否則會(huì)出現(xiàn)角色在某個(gè)方向變扁的怪異效果。4.3 基于新Input System的可選方案如果你的Unity項(xiàng)目激活的是新輸入系統(tǒng)上面代碼會(huì)報(bào)錯(cuò)。解決方案有兩個(gè)一個(gè)是前面說(shuō)過(guò)把Active Input Handling改成Both或Old另一個(gè)是用新Input System重寫移動(dòng)邏輯。這里給一個(gè)簡(jiǎn)化版參考using UnityEngine; using UnityEngine.InputSystem; public class PlayerController : MonoBehaviour { public float speed 5f; private Rigidbody2D rb; private Vector2 moveInput; public void OnMove(InputAction.CallbackContext context) { moveInput context.ReadValueVector2(); } void FixedUpdate() { Vector2 movement moveInput.normalized * speed; rb.velocity movement; } }使用新輸入系統(tǒng)時(shí)你要給角色添加PlayerInput組件配置InputAction資產(chǎn)并確保Action名為Move的“Performed”“Canceled”事件指向PlayerController的OnMove方法。這個(gè)方案代碼更簡(jiǎn)潔但配置過(guò)程對(duì)新手來(lái)說(shuō)反而多了一層理解成本。我個(gè)人的建議是第一次做Ruby項(xiàng)目直接用舊Input Manager跑通邏輯理解“輸入→邏輯→物理→動(dòng)畫”這條鏈路之后再回來(lái)學(xué)新輸入系統(tǒng)。否則你會(huì)在輸入系統(tǒng)的配置環(huán)節(jié)陷進(jìn)去忘了主角為什么要?jiǎng)印?.4 攝像機(jī)跟隨的實(shí)現(xiàn)與LateUpdate角色動(dòng)起來(lái)之后如果不做攝像機(jī)跟隨角色跑兩步就出屏幕了。Ruby項(xiàng)目里攝像機(jī)跟隨用的是平滑跟隨。方法很簡(jiǎn)單新建腳本CameraFollow掛到主相機(jī)上把目標(biāo)設(shè)為Ruby角色。using UnityEngine; public class CameraFollow : MonoBehaviour { public Transform target; public float smoothTime 0.2f; private Vector3 velocity Vector3.zero; void LateUpdate() { if (target null) { return; } Vector3 targetPosition target.position; targetPosition.z -10f; transform.position Vector3.SmoothDamp(transform.position, targetPosition, ref velocity, smoothTime); } }為什么在LateUpdate而不是Update因?yàn)閁pdate階段主角剛移動(dòng)完攝像機(jī)在LateUpdate里再跟隨可以保證這一幀的畫面完整所有對(duì)象的位置都更新完了相機(jī)才挪過(guò)去不會(huì)出現(xiàn)角色先移動(dòng)、相機(jī)晚半拍的視覺(jué)卡頓。SmoothDamp是做阻尼平滑0.2秒的smoothTime意味著相機(jī)不是瞬間跳到角色身上而是帶一點(diǎn)追趕效果手感更柔和。如果想做“硬跟隨”直接transform.position target.position再設(shè)z為-10即可。另一個(gè)關(guān)鍵點(diǎn)是相機(jī)z軸。相機(jī)默認(rèn)在z-10的位置視角朝正z方向看。如果跟隨腳本里直接把target.position賦給相機(jī)相機(jī)的z會(huì)變成0然后就看不到場(chǎng)景了。所以不管用哪種跟隨寫法都要把z固定為-10或者用相機(jī)初始的z值。5. 調(diào)試與避坑新手做Ruby項(xiàng)目最容易踩的坑5.1 角色不動(dòng)、動(dòng)畫不切換的排查順序做Ruby項(xiàng)目新手問(wèn)得最多的就是“腳本掛上了角色還是不動(dòng)”。遇到這個(gè)問(wèn)題我建議按這個(gè)順序排查。第一步看Console窗口有沒(méi)有紅色報(bào)錯(cuò)編譯錯(cuò)誤會(huì)導(dǎo)致所有腳本失效最常見(jiàn)的是類名和文件名不一致、少了一個(gè)大括號(hào)。第二步看Inspector面板里腳本組件有沒(méi)有顯示“No script”或者“Script cannot be loaded”有的話檢查文件名和類名。第三步看Rigidbody2D和Animator組件是否存在腳本里GetComponent如果找不到組件會(huì)返回null調(diào)用rb.velocity時(shí)會(huì)直接報(bào)空引用。第四步看Input.GetAxisRaw是否真的拿到了輸入最簡(jiǎn)單的驗(yàn)證方式是在Update里加一行Debug.Log(moveX “,” moveY)運(yùn)行后按方向鍵看Console輸出。如果數(shù)值始終是0說(shuō)明輸入系統(tǒng)配置有問(wèn)題按前面說(shuō)的檢查Active Input Handling。動(dòng)畫不切換的坑又是另一類。腳本里anim.SetFloat的參數(shù)名必須和Animator面板里的參數(shù)名完全一致大小寫也要一致。另一個(gè)問(wèn)題是Transition條件設(shè)置錯(cuò)誤比如你在Blend Tree的入口加了一個(gè)“speed大于0.1才切換”的條件但腳本里只寫了moveX、moveY那動(dòng)畫永遠(yuǎn)切換不了。建議把Blend Tree的參數(shù)直接設(shè)為moveX、moveY腳本同步寫入這兩個(gè)值邏輯最簡(jiǎn)單。5.2 角色抖動(dòng)、卡頓與物理參數(shù)失常角色運(yùn)行時(shí)抖動(dòng)通常是幾個(gè)原因疊加的。第一是剛體的Interpolate沒(méi)有設(shè)置。Rigidbody2D組件里有一個(gè)Interpolate選項(xiàng)默認(rèn)None如果角色移動(dòng)看起來(lái)一頓一頓的改成Interpolate可以平滑渲染幀和物理幀之間的間隙。這個(gè)選項(xiàng)不會(huì)改變真實(shí)物理狀態(tài)只影響視覺(jué)表現(xiàn)2D像素游戲加上它觀感會(huì)好很多。第二是角色卡在墻壁邊緣。檢查BoxCollider2D的大小是不是比Sprite可視部分大了一圈尤其是貼圖有透明邊時(shí)碰撞器默認(rèn)會(huì)包住整個(gè)貼圖像素范圍。另一種情況是Sorting Layer沒(méi)問(wèn)題但Collider Editor Mode無(wú)意間改動(dòng)了碰撞器偏移了。雙擊角色打開Collider的Edit Collider把邊界重新拉一下。第三是角色運(yùn)動(dòng)速度異常快或者加normalized之后移動(dòng)速度感覺(jué)不對(duì)。先用Debug.Log把movement向量打出來(lái)看看如果speed設(shè)成5但實(shí)際感覺(jué)像飛檢查腳本里是否同時(shí)存在多個(gè)移動(dòng)邏輯比如既有PlayerController又沿用了某個(gè)舊腳本兩個(gè)腳本同時(shí)給rb.velocity賦值就會(huì)互相覆蓋。5.3 我在實(shí)際項(xiàng)目里的幾個(gè)擴(kuò)展建議Ruby項(xiàng)目跑通之后別急著關(guān)掉。我個(gè)人建議趁熱打鐵做兩個(gè)擴(kuò)展。第一個(gè)是把移動(dòng)改成真正的8方向動(dòng)畫控制。官方示例里大多數(shù)版本只有左右翻轉(zhuǎn)和上下方向感覺(jué)像橫版游戲。如果你把Blend Tree的Type設(shè)為2D Freeform Directional并把上下左右動(dòng)畫都掛進(jìn)去角色就能朝8個(gè)方向自然移動(dòng)游戲手感立刻提升一個(gè)檔次。第二個(gè)是加一個(gè)“交互鎖”比如角色進(jìn)入對(duì)話、打開菜單時(shí)禁用移動(dòng)輸入。實(shí)現(xiàn)方式很簡(jiǎn)單在PlayerController里聲明一個(gè)public bool canMove trueUpdate開始時(shí)判斷if (!canMove) { rb.velocity Vector2.zero; return; }。Ruby后面有對(duì)話系統(tǒng)、機(jī)關(guān)觸發(fā)這個(gè)bool會(huì)在你實(shí)現(xiàn)NPC交互時(shí)起到大作用。還有一個(gè)細(xì)節(jié)值得注意腳本對(duì)組件的引用最好在Start里獲取一次不要在Update里反復(fù)GetComponent。GetComponent有性能開銷雖然單個(gè)角色感覺(jué)不到但養(yǎng)成緩存引用的習(xí)慣對(duì)后續(xù)開發(fā)復(fù)雜項(xiàng)目非常有幫助。這也是Ruby官方教程里很早就在強(qiáng)調(diào)的代碼習(xí)慣。最后再分享一個(gè)我做Ruby項(xiàng)目時(shí)常用的調(diào)試思路把角色當(dāng)成一個(gè)“信號(hào)中心”。輸入產(chǎn)生信號(hào)腳本處理信號(hào)動(dòng)畫和物理響應(yīng)信號(hào)。出問(wèn)題時(shí)從信號(hào)源頭逐段排查按鍵有沒(méi)有信號(hào)腳本有沒(méi)有收到信號(hào)組件有沒(méi)有響應(yīng)信號(hào)。用這個(gè)思路你會(huì)發(fā)現(xiàn)大多數(shù)問(wèn)題都能在五分鐘內(nèi)定位。做完整個(gè)Ruby項(xiàng)目真正得到的不是那幾十行代碼而是這套排查和拆解問(wèn)題的能力。后面做任何2D游戲再回頭看你寫的第一個(gè)PlayerController就會(huì)明白這一課的價(jià)值在哪里。