0%

关于Unity的笔记

可惜上一次记载unity笔记的时候还没有使用博客.
纸质材料跟我的C++入门课一起静躺在书架上,不在脑内也不舍得丢弃.
来都来了,记点记点.

个人用,杂乱,请见谅.

Ctrl + K + C : Comment out the selected code.
Ctrl + K + U : Uncomment the selected code.
Ctrl + / : Toggle line comments.

Shift + Space -> 将鼠标所在的unity窗口放大.




Lighting

Light好像只能4个,再多会唐突消失在camera内.

Skybox

Skybox很好看,像是真实照片(背景)所做的lighting.还有自带的360度照片.
需要Create Skybox Material first, shader as Skybox/Panoramic, drag hdr file into it(the skybox).
(右手边的)Lighting -> Environment -> Skybox Material.



GameObject

Camera

Click on Main Camera -> GameObject -> Aligh with View,可以快捷调节camera的view.
Click on any window, shift space可以放大window.

Scrolling Repeating Background in Unity.

Sound

Camera default to have audio listener, but we can’t have 2 audio listener in scene, if we have two camera, remove one.
Background sound -> audio source -> 3D sound settings -> min distance set to 0, there is no minimum distance the user need (they always hear).

Post Process volume

很酷的一种visual滤镜.
首先要现在window(菜单),package manager,添加post processing.
Volume -> global volume + camera -> post processing turn on
Under global volume(object created) create new profile.
Then add override, vignette, intensity for testing. Bloom, tone mapping is fun too
effect -> particle system.

Collision

For trigger object(碰撞trigger的情况), 2 objects(collision) all needs rigid body into it.


Collision有两种.一种是“碰撞”,一种是"接触".
1
2
3
4
5
private void OnCollisionEnter2D(Collision2D collision){
if(collision.gameObject.tag == "Ground"){
// do something
}
}
这一种一般用于,比如合成大西瓜,物体与物体的碰撞.
1
2
3
4
5
private void OnTriggerEnter2D(Collider2D other){
if(other.gameObject.tag == "Coin"){
// do something
}
}

这一种一般用于”路径点”,拾取道具.
需要在Collider 2D中turn on “Is trigger”.



Rigid body跟character control好像不兼容(需要查询).
Character control可以控制更多. -> 如果要使用on trigger,因为需要二者之一的rigid body所以对面需要拥有rigid body.



Canvas

要查看canvas的种类,在canvas -> render mode中查看.
如果是screen space - camera, 或者screen space - overlay -> 会让canvas的位置根据camera变动.
如果是world space -> 可以rename成world canvas,是一个canvas物体,跟其他物品一样.

要加入canvas中的物件.推荐使用这个小窗口.

Left button selection + alt + shift, place object in one of default position.

Frame canvas -> canvas scaler-> UI Scale Mode, scale with screen size.

Sprite renderer renders a sprite in the sccene, UI image renders a sprite in UI.



Animator

如果需要给game object增加动画,需要添加animator component.
Animator需要animation controller(assets).



一个造门的小妙招.

创造一个空的game object parent,然后再造一个door.
Gizmo的position正好在门要转动的轴上,所以只要用代码转动Gizmo就能转动门.
用两层parent是因为,一层用来转动,一层用来移动.



Joint

https://www.youtube.com/watch?v=MElbAwhMvTc&ab_channel=DitzelGames

https://www.youtube.com/@JamesMakesGamesYT/videos

https://www.youtube.com/watch?v=dEtuAPo5vAg&ab_channel=ChristopherFrancis



Input

1
2
3
if (Input.GetKeyDown(KeyCode.Space)){
// do some action
}

关于博主把GetKeyDown和GetKey混着用的这件事.
GetKeyDown如题只记录Down这个action是否实施过.
GetKey而是get key的这段时间一直都会return true.



Object Pooling



Sprite Multi



OOP Principles

Encapsulation: Bundling data and methods into a single unit (class) for organized code.
Inheritance: Allows a class to inherit properties and behaviors from another class, promoting code reuse.
Polymorphism: Objects can take multiple forms, enabling a single interface for different data types.
Abstraction: Simplifying complex systems by modeling classes based on essential properties.



只有abstract或者virtual才能override.

Encapsulation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Health{
//we keep this private, but use method to change it
private float currentHealth;
private float maxHealth;

public Health{}//this is a constructor
public Health(float _maxHealth, float _currentHealth = 100){
currentHealth = _currentHealth;
maxHealth = _maxHealth;
}

public void AddHealth(float value){
currentHealth += value;
}

public void DeductHealth(float value){
currentHealth -= value;
}

}


Inheritance

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class Car{
public string Model{get; set;}
public string Make{get; set;}

private string wheels{get; set;}

// abstract - have to used
public abstract float GasPerKM();
public abstract void Drive();

//concrete method - can be used, and overrided
public void move(){}
}


Polymorphism

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Animal{
public virtual void MakeSound{
Debug.Log("Animal noise");
}
}

public class Cat : Animal{
public override void MakeSound{
Debug.Log("Cat meows");
}
}

public class Dog: Animal{
public override void MakeSound{
Debug.Log("Dog barks");
}
}

public class AnimalTest: MonoBehaviour{
void Start(){
Animal[] animals = new Animal[3];
animals[0] = new Animal();
animals[1] = new Dog();
animals[2] = new Cat();

foreach (Animal animal in animals){
animal.MakeSound();
}
}

}


Abstraction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public abstract class PlayableObject: MonoBehaviour, IDamageable
{
public Health health = new Health();
public Weapon weapon;

public abstract void Move(Vector2 direction, Vector2 target);
public abstract void Attack(float interval);
}

public class Enemy: PlayableObject{
private string enemyName;
private Transform target;

public void SetEnemyType(EnemyType enemyType){
this.enemyType = enemyType;
}
public override void Move(Vector2 direction, Vector2 target){
// the enemy move
}
public override void Attack(float interval);
Debug.Log($"Attacking with an interval of {interval}")
}



Interfaces/what to use(使用方法类似于class,但只有declare没有definition) -> can use multi interfaces into one class.



Abstract class -> only declare no definition.
Virtual you can define.



Protected.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Enemy: PlayableObject{
protected virtual void Start(){
target = GameObject.FindWithTag("Player").transform;
}

protected virtual void Update(){
if(target != null){
Move(target.position);
}
}

public override void Move(Vector2 direction){
direction.x -= transform.position.x;
direction.y -= transform.position.y;

float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0,0,angle);
transform.Translate(Vector2.right * speed * Time.deltaTime);
}

public override void Move(float speed){
transform.Translate(Vector2.right * speed * Time.deltaTime);
}
}


Static的好处是static class下面的function到哪里都可以用.
一般需要getComponent读取object,但static就不用.

1
2
3
4
5
public static class ConstantExample{
public static void PrintMessage(String message){
Debug.Log(message);
}
}


Custom Module

有几种比较便利的,常用class整理方法.

  • PlayableObject

  • IDamageable

  • GameManager

  • LevelLoader

  • SceneManager

  • ScoreManager

  • SoundManager

  • UIManager

比较方便的data写法.

  • Array
  • List
  • Stack
  • Dictionary
1
2
3
4
5
6
7
 public GameObject[] array = new GameObject[2];

array[0] = Instantiate(testPrefab, transform);
array[0].transform.position = Vector2.zero;

array[Random.Range(0,array.Length)].GetComponent<SpriteRenderer>().color = Random.ColorHSV();

List is dynamic but array is not. (and least performance).
Array holds same type of object. (string array only holds strings).



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public GameObject testPrefab;

public List<GameObejct> list = new List<GameObject>();

void Start(){
GameObejct tempObject;

tempObject = Instantiate(testPrefab, transform);
tempObject.transform.position = new Vector2(0,0);
list.Add(tempObject);
}

list[Random.Range(0,list.Count)].GetComponent<SpriteRenderer>().color = Random.ColorHSV();

list.Clear();
list.RemoveAt(Random.Range(0,list.Count));
Destory(list[Random.Range(0,list.Count]);



Stack: last in first out. (Use in backtracking, search history, undo behavior).

1
2
3
4
5
6
7
8
9
10
11
public Stack<GameObject> stack = new Stack<GameObject>();
private GameObjet tempObject;

tempObject = Instantiate(testPrefab, transform);
tempObject.transform.position = new Vector2(stack.Count,0);

tempObject.name = "Stacked: " + stack.Count;
stack.Push(tempObject);

GameObject removeObject = stack.Pop();
Debug.Log("object at the top of the stack" + stack.Peek().name);


Queue: first in first out. (Use in order, schedule list, request).

1
2
3
4
5
6
7
8
9
10
11
12
public Queue<GameObject> queue = new Queue<GameObject>();
private GameObjet tempObject;
Vector2 lastEnqueuedPosition = Vector2.zero;

tempObject = Instantiate(testPrefab, transform);
tempObject.transform.position = new Vector2(lastEnqueuedPosition.x+1,0);

queue.Enqueue(tempObject);
lastEnqueuedPosition = tempObject.transform.position;

GameObject removeObject = queue.Dequeue();
Debug.Log("object at the last of the queue" + queue.Peek().name);


Dictionary: Phonebook, user section (user id), inventory.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using TMPro;

[Serialize Field] private TMP_Text txtCoin, txtScore, txtGems;

public Dictionary<string, int> dictionary = new dictionary<string, int>();
public string CheckForKey = "Bullets";

dictionary.Add("Coins", 0);
dictionary.Add("Scores", 0);
dictionary.Add("Gems", 0);

if (dictionary.ContainsKey("Coins")){
dictionary["Coins"]++;
txtCoin.text = dictionary["Coins"].ToString();
}

bool hasKey = dictionary.TryGetValue(CheckForKey, out val);



Singleton

We can only have one singleton (therefore it called singleton).
Be careful for the variable in singleton (since they can only have one).

Eg.GameManager:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

public class GameManager : MonoBehaviour{
/// <summary>
/// Singleton
/// </summary>

private static GameManager instance;

public static GameManager GetInstance(){
return instance;
}

void Singleton(){
if (instance != null && instance != this){
Destroy(this);
}else{
instance = this;
}
}

private void Awake(){
SetSingleton();
}

}


1
2
3
4
5
6
7
8
9

[CreateAssetMenu(filename = "DataSample", menuName = "Data Menu Sample", order = 1)]

public class NewTypeOfObject : ScriptableObject{
public string objectName;
public string score;
public Vector2 startPosition;

}


Delay

Unity timer IEnumertator.

//something wanna do before the wait
yield return new WaitForSeconds(0.5f);
//something wanna do after the wait

1
2
3
4
5
6
7
8
9
10
11
StartCoroutine(EnemySpawner());

IEnumerator EnemySpawner()
{
while (isEnemySpawning) {
// Anthing abover here will fire when the Start Corountine is callled
yield return new WaitForSeconds(1.0f / enemySpawnRate);
// Anthing after the wait, will be called.
CreateEnemy();
}
}

除了IEnumertator,还能用Invoke.

1
2
3
4
5
Invoke("theFunctionName", 3);//3 seconds then the function

public void theFunctionName{
// can be anything
}


IDamageable

以我的理解,用interface(contract)来限制所有增加的class都会有其interface内的内容.
像给div增加class一样可以一起使用.

1
2
3
public interface IDamageable{
void GetDamage(float damage);
}
1
2
3
4
5
6
7
8
9
10
private void OnTriggerEnter2D(Collider2D collision){
Debug.Log(collision.gameObject.name);

if(!collision.gameObject.CompareTag(targetTag))
return;

//using interface
IDamageable damageable = coollision.GetComponent<IDamageable>();
Damage(damageable);
}


Solid Principle

Single responsibility
Open–closed
Liskov substitution
Interface segregation
Dependency inversion

Comment

1
2
3
4
连打三条杠(///).
可以叫出<summary></summary>的commenting block.
在function前面会自动变成那个function的summary.
<param name=''></param>解释function里的parameter.

Using Interfaces in Unity Effectively | Unity Clean Code
An Automated Model Based Testing Approach for Platform Games



shader

Package Manager.
注意title选择Packages: Unity Registry.
Find Shader Graph.

Create -> Sharder Graph -> URP -> Lit Shader Graph/Unlit Shader Graph.

做好的shader要先attach to material.再material attach to the plane(or other stuff).



Builds

For building in different platforms.

Project setting -> player -> configuration -> scripting backend
mono is fine for testing, but for deliver version should choose IL2CPP.

For Api Compatibility Level -> .NET Standard 2.1 is fine.
don’t build project in repository, will blow up it, just share source code.



Add-on

ProBuilder

Strip All ProBuilder Scripts in Scene.
一款捏简单3D物体的plugin.

VRoid

How to Export VRoid Studio Models to Unity (3D/URP)

Game Programming Patterns

https://www.c-sharpcorner.com/UploadFile/damubetha/solid-principles-in-C-Sharp/

https://www.meshy.ai/