Unity2d速通
1 概述
關于Unity正文前,先大致整理一些預備內容和框架。
-
Unity是一個平臺/引擎,有很多庫文件,開發者需要做兩件事:利用Unity自帶的圖形化界面(Inspector面板)掛載調整;利用自己寫的C#(Script腳本)調用庫文件實現邏輯。
-
一個Unity工程有多個場景(Scene),每個場景下有多個對象(GameObject,也稱物件),一個對象就是一個容器(Container),容器中有很多組件(Component),一個組件有很多屬性(Attribute)。
-
通常一個Unity2d項目的對象包括:基礎(Camera、EventSystem、Light等)、環境(Background、Midground、Foreground等)、游戲(Player、Enemy等)、用戶界面(Canvas、UI Elements等)。
-
Unity的xyz坐標系是左手系。對于在3D場景中z坐標更小(朝外)但圖層更低的對象,在2D中會被z坐標更大(朝里)的對象遮住。
-
做好的復用對象模版保存在預制體(Prefab)中。整體Assets文件夾通常包括Art、Animation、Prefab、Scene、Script等。
-
如果需要共享工程,只需要打包Assets、Packages、ProjectSettings三個文件夾。
2 C#
好了,讓我們學習代碼基礎。
2.1 讓變量顯示在Inspector面板
兩種方式:SerializeField(序列化字段)或Public(公有)。
[SerializeField] private float walkSpeed = 1;
public float jumpForce = 45f;
區別在于SerializeField仍保持封裝不被外部腳本修改(推薦),Public的變量則是完全公開修改的。
通常為了易讀性,這樣組織變量定義:
[Header("Ground Check Settings:")]
[SerializeField] private Transform groundCheckPoint; //point at which ground check happens
[SerializeField] private float groundCheckY = 0.2f; //how far down from ground chekc point is Grounded() checked
[SerializeField] private float groundCheckX = 0.5f; //how far horizontally from ground chekc point to the edge of the player is
[SerializeField] private LayerMask whatIsGround; //sets the ground layer
[Space(5)]
可以在定義時賦值或不賦值。
2.2 生命周期函數
-
Awake():初始化,最先調用
-
OnEnable():對象被啟用時調用
-
Start():在Awake()和OnEnable()之后調用
-
FixedUpdate():按固定間隔,多幀調用
-
Update():每幀調用
-
LateUpdate():在Update()之后,每幀調用
-
OnDisable():對象被禁用時調用
-
OnDestroy():對象被銷毀時調用
單例模式(Singleton)的類在同一時刻只存在一個實例(比如玩家角色就是單例),并且提供全局訪問點,是典型的Awake()函數實現:
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
}
else
{
Instance = this;
}
DontDestroyOnLoad(gameObject);
}
Start()通常用于獲取組件和屬性,為了避免后面每一幀Update()頻繁調用,例如:
void Start()
{
pState = GetComponent<PlayerStateList>();
rb = GetComponent<Rigidbody2D>();
sr = GetComponent<SpriteRenderer>();
anim = GetComponent<Animator>();
gravity = rb.gravityScale;
Health = maxHealth;
Mana = mana;
manaStorage.fillAmount = Mana;
}
Update()處理每一幀動畫,以下是一個完整的2D角色單幀處理流程:
void Update()
{
if (pState.cutscene) return;
GetInputs();
UpdateJumpVariables();
if (pState.dashing) return;
RestoreTimeScale();
FlashWhileInvincible();
Move();
Heal();
CastSpell();
if (pState.healing) return;
Flip();
Jump();
StartDash();
Attack();
}
FixedUpdate()用于處理多幀動作:
private void FixedUpdate()
{
if (pState.dashing || pState.healing || pState.cutscene) return;
Recoil();
}
2.3 獲取Input輸入
這些可以在Input Manager調整,代碼如:
void GetInputs()
{
xAxis = Input.GetAxisRaw("Horizontal");
yAxis = Input.GetAxisRaw("Vertical");
attack = Input.GetButtonDown("Attack");
}
2.4 地面檢測
這個功能通常需要完成兩個步驟:將地面設置為專用圖層,然后給對象添加地面檢查點。
圖層設置可以在Inspector中完成(指派Ground圖層為whatIsGround),地面檢查點作為子對象附加到父對象(groundCheck寬度小于父對象的Box Collider)。
代碼可以這樣實現:
public bool Grounded()
{
if (Physics2D.Raycast(groundCheckPoint.position, Vector2.down, groundCheckY, whatIsGround)
|| Physics2D.Raycast(groundCheckPoint.position + new Vector3(groundCheckX, 0, 0), Vector2.down, groundCheckY, whatIsGround)
|| Physics2D.Raycast(groundCheckPoint.position + new Vector3(-groundCheckX, 0, 0), Vector2.down, groundCheckY, whatIsGround))
{
return true;
}
else
{
return false;
}
}
這里Raycast(射線檢測)指的是從起點沿著指定的方向發射一條射線,檢測是否與任何物體發生碰撞。Vector2和Vector3分別代表二維向量與三維向量,Vector2.down是正下方,groundCheckX和groundCheckY是檢測范圍長度。
2.5 左右翻轉
這可以通過一個狀態bool變量實現。
public bool lookingRight;
然后編寫代碼:
void Flip()
{
if (xAxis < 0)
{
transform.localScale = new Vector2(-Mathf.Abs(transform.localScale.x), transform.localScale.y);
pState.lookingRight = false;
}
else if (xAxis > 0)
{
transform.localScale = new Vector2(Mathf.Abs(transform.localScale.x), transform.localScale.y);
pState.lookingRight = true;
}
}
Transform是少數不需要手動GetComponent()的組件,其三個屬性分別是position、rotation、localscale。
另外移動的代碼如下:
private void Move()
{
if (pState.healing) rb.velocity = new Vector2(0, 0);
rb.velocity = new Vector2(walkSpeed * xAxis, rb.velocity.y);
anim.SetBool("Walking", rb.velocity.x != 0 && Grounded());
}
2.6 幀間時間deltatime
為了避免幀率對時間造成影響,采用deltatime(每幀的時間流逝量)如:
void Attack()
{
timeSinceAttack += Time.deltaTime;
if (attack && timeSinceAttack >= timeBetweenAttack)
{
timeSinceAttack = 0;
anim.SetTrigger("Attacking");
if (yAxis == 0 || yAxis < 0 && Grounded())
{
Hit(SideAttackTransform, SideAttackArea, ref pState.recoilingX, recoilXSpeed);
Instantiate(slashEffect, SideAttackTransform);
}
else if (yAxis > 0)
{
Hit(UpAttackTransform, UpAttackArea, ref pState.recoilingY, recoilYSpeed);
SlashEffectAtAngle(slashEffect, 80, UpAttackTransform);
}
else if (yAxis < 0 && !Grounded())
{
Hit(DownAttackTransform, DownAttackArea, ref pState.recoilingY, recoilYSpeed);
SlashEffectAtAngle(slashEffect, -90, DownAttackTransform);
}
}
這里也用到了自定義的計時器,用于控制時間間隔。如果涉及Time.timeScale,那么Time.unscaledDeltaTime則是不受縮放影響的真實時間,Time.deltaTime會受到更改。
2.7 可視化調試OnDrawGizmos
這種方法只在編輯模式和游戲模式下的場景視圖中起作用,不會在實際游戲運行時的屏幕上顯示。
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawWireCube(SideAttackTransform.position, SideAttackArea);
Gizmos.DrawWireCube(UpAttackTransform.position, UpAttackArea);
Gizmos.DrawWireCube(DownAttackTransform.position, DownAttackArea);
}
2.8 協程Coroutine
協程用于處理需要一定時間完成的動作。以沖刺為例:
IEnumerator Dash()
{
canDash = false;
pState.dashing = true;
anim.SetTrigger("Dashing");
rb.gravityScale = 0;
int _dir = pState.lookingRight ? 1 : -1;
rb.velocity = new Vector2(_dir * dashSpeed, 0);
if (Grounded()) Instantiate(dashEffect, transform);
yield return new WaitForSeconds(dashTime);
rb.gravityScale = gravity;
pState.dashing = false;
yield return new WaitForSeconds(dashCooldown);
canDash = true;
}
3 面板
3.1 圖像
PPU(Pixels Per Unit)越大,導入圖片越小,畫面越清晰。
Max Size是導入的圖片在縮放下不超過的最大尺寸,如果過小會使得畫面模糊。
Filter Mode是過濾模式,通常多線性的紋理效果優于點線性。
Wrap Mode是環繞模式,Repeat會在超出范圍時重復顯示,Clamp是鉗制。
3.2 剛體
Rigidbody處理物體的物理運動,包括重力、速度、力。
注意如果子對象不是剛體,那么父對象的Collider是自己和子對象的Collider之和。此時子對象可以觸發父對象的OnTriggerEnter2D()。
3.3 SortingLayer
Unity有兩種自帶的圖層:Layer和Sorting Layer。前者如檢測碰撞用于圖層邏輯(邏輯分層),后者用于顯示渲染覆蓋(排序分層)。
Sorting Layer在制作環境、背景時非常有用,可以制作景深、霧氣、特殊的前后遮擋效果等等。
如果UI物件和場景物體相互遮蓋,可以在Layer中隱藏。
3.4 URP Lighting
2D Light在新版Unity中被放進URP(Universal Render Pipeline)Package中。
為了添加Lighting,需要把所有被Light的對象材質換成URP材質。
選擇需要的Sorting Layer添加2D Light,適當調整顏色和強度以匹配環境。
4 動畫
4.1 K幀
K幀即關鍵幀動畫,通過在關鍵幀之間插值實現平滑過渡。
Unity的K幀動畫在Animation窗口,樸素的方法是把預制的關鍵幀Sprite添加進去保存,會自動在Animator窗口生成狀態機,然后可以在代碼中調用如:
anim.SetBool("Walking", rb.velocity.x != 0 && Grounded());
這樣可以控制Animator的標記變量實現條件狀態轉移。
4.2 骨骼動畫
樸素的K幀消耗大量時間,這時可以用Unity的skinning editor處理分層的單張圖片(推薦psb/psd,也可以是png;Unity可以自動切割圖片,注意背景透明)。
通常先綁定(Rigging)骨骼(如要細致動畫可在頭發或衣服上添加分叉的骨骼),并在Visualization分配骨骼排序。然后使用Auto Geometry生成權重,應用后在需要動畫的對象添加Sprite Skin組件創建骨骼。
如果需要為同一個物件準備多個Sprite,那么都對其作綁定,后續動畫采用Sprite Resolver或Sprite Swap等組件設置同一個物件不同Sprite的可見性。
武器裝備之類應當和角色骨骼層級相同,不應低于角色。
4.3 IK反向動力學
IK Manager可以實現子骨骼連續帶動父骨骼轉動的效果。
這通常是把手、腳等邊緣骨骼掛載到IK Manager的List中,然后創建Target,并對翻轉、約束作調整。
4.4 粒子系統ParticleSystem
粒子系統可以制作動畫特效。
首先需要粒子的Material,然后在層次面板創建粒子系統,在Inspector中配置。

浙公網安備 33010602011771號