Basic Unity Method
1
2
3
4
5
6
7
8
9
10
| void Transform.Rotate(float xAngle, float yAngle, float zAngle) (+ 5 多个重载)
/*The implementation of this method applies a rotation of zAngle degrees around the z axis, xAngle degrees around the x axis, and yAngle degrees around the y axis (in that order).*/
// Update is called once per frame
void Update()
{
transform.Rotate(0, 0, 0.1f);
}
|
1
2
3
4
5
6
7
| void Transform.Translate(float x, float y, float z) (+ 5 多个重载)
//Moves the transform by x along the x axis, y along the y axis, and z along the z axis
void Update()
{
transform.Translate(0, 0.01f, 0);
}
|
SerializeField
1
2
3
4
| //Force Unity to serialize a private field. -> access in the inspector
// the value in inspector overide the script
// difference between "public": can be inspected in the unity and be private at the same time
[SerializeField] float moveSpeed = 0.01f;
|
1
2
3
4
5
6
7
8
9
| [Header("Questions")]
[SerializeField] TextMeshProUGUI questionText;
[SerializeField] QuestionSO question;
[Header("answers")]
[SerializeField] GameObject[] ansButtons;
int correctAnsIdx;
[SerializeField] Sprite CorrectSprite;
[SerializeField] Sprite DefaultSprite;
|
1
2
3
4
5
6
7
8
9
10
11
| [Header("General")]
[SerializeField] GameObject projectilePrefab;
[SerializeField] float projectileSpeed = 10f;
[SerializeField] float projectileLifetime = 5f;
[SerializeField] float baseFiringRate = 0.2f;
[Header("AI")]
[SerializeField] bool isAI;
[SerializeField] float firingRateVariance = 0f;
[SerializeField] float minimumFiringRate = 0.1f;
[HideInInspector] public bool isFiring;
|
1
2
3
4
5
6
7
| void Update()
{
float steerAmount = Input.GetAxis("Horizontal") * steerSpeed;
float MoveAmount = Input.GetAxis("Vertical") * moveSpeed;
transform.Rotate(0, 0, -steerAmount);
transform.Translate(0, MoveAmount, 0);
}
|
Time.deletaTime()
Rigid Body and Collider
Rigidbody.interpolation
插帧平滑分为Interpolate
根据上一帧插帧,和Extrapolate
根据下一帧插帧,可分别尝试
- 碰撞监测模式通常设置为Discrete离散即可,高速运动时可使用连续监测,但性能消耗较大。
- Continuous用于会撞上纯碰撞体的高速刚体,Continurous Dynamic用于会撞上其他刚体的高速刚体。
- Continuous Speculative 比t Dynamic节约性能,对高速旋转扫掠的监测效果更好,但移动类监测更差
OnCollisionEnter2D & OnTrigger
Collider -> is Trigger√
1
2
3
4
5
6
7
8
9
10
| public class Collision : MonoBehaviour
{
void OnCollisionEnter2D(Collision2D other)
{
Debug.Log("Ouch!");
}
private void OnTriggerEnter2D(Collider2D other) {
Debug.Log("triiger");
}
}
|
pixel per unit
pixel per unit get less -> image shows bigger -> resolution get less
FollowCamera
we need a reference to access this game object
1
2
3
4
5
| [SerializeField] GameObject thingToFollow;
void LateUpdate()
{
transform.position = thingToFollow.transform.position + new Vector3(0, 0, -10);
}
|
Destory
1
2
| //Removes a GameObject, component or asset.
Destroy(other.gameObject, destoryDelay);
|
color32
1
2
| Color32.Color32(byte r, byte g, byte b, byte a)
//Constructs a new Color32 with given r, g, b, a components.
|
GetComponent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // collide -> color switch
// get the component -> spriteRenderer.color
SpriteRenderer spriteRenderer;
private void Start() {
spriteRenderer = GetComponent<SpriteRenderer>();
}
private void OnTriggerEnter2D(Collider2D other) {
if(other.tag == "package" && !hasPackage){
Debug.Log("1");
hasPackage = true;
spriteRenderer.color = hasPackagecolor;
Destroy(other.gameObject, destoryDelay);
}
if(other.tag == "customer" && hasPackage == true){
Debug.Log("2");
spriteRenderer.color = noPackagecolor;
hasPackage = false;
}
}
}
|
Sprite Shape
collider -> offset -> make object closer to the sprite shape
height -> make the surrounding sprite higher
Cinemathine [unity package]
- mange multiple cameras
- create rules for our cameras
follow Camera: Follow -> game object
Body -> Screen X/Y -> adjust the position of the camera -> show more what’s to come
Cinemachine Confiner
FreeLook
三个维度 自由设置摄像机
Location
The position of children is relative to the parents
Surface Effector 2D
applies tangent forces along the surfaces of colliders
Unity Doc: Surface Effector 2D
not gonna stucked
AddTorque()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| public class PlayerController : MonoBehaviour
{
Rigidbody2D rb2d;
[SerializeField] float torqueAmout = 1f;
// Start is called before the first frame update
void Start()
{
rb2d = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update()
{
if(Input.GetKey(KeyCode.LeftArrow)){
rb2d.AddTorque(torqueAmout);
}
}
}
|
SceneManagenment
1
2
3
4
5
| private void OnTriggerEnter2D(Collider2D other) {
if(other.tag == "Player"){
SceneManager.LoadScene(0);
}
}
|
Invoke()
1
2
3
4
5
6
7
8
9
| //Invokes the method methodName in time seconds.
private void OnTriggerEnter2D(Collider2D other) {
if(other.tag == "Player"){
Invoke("Reload", reloadTime);
}
}
void Reload(){
SceneManager.LoadScene(0);
}
|
ParticleSystem
1
2
3
4
5
6
7
| [SerializeField] ParticleSystem finishEffect;
private void OnTriggerEnter2D(Collider2D other) {
if(other.tag == "Player"){
finishEffect.Play();
Invoke("Reload", reloadTime);
}
}
|
1
2
3
4
5
6
7
8
| void playHitEffect()
{
if (hitEffect != null)
{
ParticleSystem instance = Instantiate(hitEffect, transform.position, Quaternion.identity);
Destroy(instance.gameObject, instance.main.duration + instance.main.startLifetime.constantMax);
}
}
|
Uncheck “Play on awake”
FindObjectOfType
1
2
3
4
5
6
| void Start()
{
rb2d = GetComponent<Rigidbody2D>();
surfaceEffector2D = FindObjectOfType<SurfaceEffector2D>();
}
// (automatically) The first active loaded object that matches the specified type.
|
Audio
audio listener
audio speaker
AudioSource
1
2
3
4
5
6
7
| void playClip(AudioClip audioClip, float Volum)
{
if (audioClip != null)
{
AudioSource.PlayClipAtPoint(audioClip, Camera.main.transform.position, Volum);
}
}
|
1
2
3
4
5
6
7
8
9
10
11
| [SerializeField] AudioClip coinPickupSFX;
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player")
{
// one shot audio
AudioSource.PlayClipAtPoint(coinPickupSFX, Camera.main.transform.position);
Destroy(gameObject);
}
}
|
UI Canvas
Canvas
Background
TextMashPRO
TextMeshPro For more fonts: https://www.dafont.com/
Get child component
1
2
3
4
5
6
7
8
9
10
| void Start()
{
questionText.text = question.GetQuestion();
for (int i = 0; i < ansButtons.Length; i++)
{
TextMeshProUGUI buttonText = ansButtons[i].GetComponentInChildren<TextMeshProUGUI>();
buttonText.text = question.GetAns(i);
}
}
|
Image type: Filled -> make the image change
slider
Unity Doc Slider
Scriptable Objects
- Create SO Script
- instantiate SO objects
- Create stats (MonoBehavior) script including each arribute in SO with
get
and set
- bind the stats to your “Player” / “Enemy” Object
- Fill in the SO objects in step 2
1
2
3
4
5
6
| [CreateAssetMenu(menuName = "Quiz Question", fileName = "New Question")]
public class QuestionSO : ScriptableObject
{
[TextArea(2, 6)]
[SerializeField] string question = "Enter new question text here";
}
|
How to make a indivudual Data (每个个人不会共享数据)
- uncheck the chararcterData in the inspector
- put scriptable objects in the templateData
- it will instantiate a new object for the individual object
1
2
3
4
5
6
7
8
9
| public CharacterStats_SO templateData;
public CharacterStats_SO characterData;
private void Awake()
{
if (templateData != null)
{
characterData = Instantiate(templateData);
}
}
|
Lock the Inspector
InspectorOptions
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
26
27
28
29
30
31
| Quiz quiz;
EndScreen endScreen;
void Awake()
{
quiz = FindObjectOfType<Quiz>();
endScreen = FindObjectOfType<EndScreen>();
}
void Start()
{
quiz.gameObject.SetActive(true);
endScreen.gameObject.SetActive(false);
}
void Update()
{
if (quiz.isComplete)
{
quiz.gameObject.SetActive(false);
endScreen.gameObject.SetActive(true);
endScreen.ShowFinalScore();
}
}
public void OnReplayLevel()
{
// get the current scene again
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
|
Sprite Sheet
Multiple sprite fill in the area Automatic -> no space around
Grid by cell size -> maintain the space around intentionally
TileMap
Creating a Tile Palette
rule tile (assest)
rule tile
Animation
Terminology
- Animator Component - Assigns animations to GameObjects through an Animator Controller
- Animator Controller - Arrangement of animations and transitions (state machine).
- Animation - Specific pieces of motion
- Sprite Renderer - displays the 2D sprite on screen
Set Up Your Character’s Idle
- Import spritesheet and slice
- Add sprite renderer to Player
- Create idle animation clip
- Create Character Animator Controller,
- Add idle animation to Animator Controller
- Add Animator to Player
- Assign Character Animator Controller to Player
idle -> loop check
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| void Start()
{
MyRgdb = GetComponent<Rigidbody2D>();
MyAnimator = GetComponent<Animator>();
MyCapsuleCollider = GetComponent<CapsuleCollider2D>();
}
void run()
{
// only control the x axis
// maintain the original rgbd velocity
Vector2 playerVelocity = new Vector2(moveInput.x * runSpeed, MyRgdb.velocity.y);
MyRgdb.velocity = playerVelocity;
// running state check
bool HasHorizontalSpeed = Mathf.Abs(MyRgdb.velocity.x) > Mathf.Epsilon;
MyAnimator.SetBool("isRunning", HasHorizontalSpeed);
}
|
LayerMask.GetMask
Collider2D.IsTouchingLayers
transition
Switch in different layers
- create a new layer
- create an empty state
- make a bool parameter to transition to any state
using UnityEngine.InputSystem;
void OnMove(InputValue value)
{
// value.Get<Vector2>()!!!
moveInput = value.Get<Vector2>();
Debug.Log(moveInput);
}
void OnJump(InputValue value)
{
if (!MyCapsuleCollider.IsTouchingLayers(LayerMask.GetMask("Ground"))) { return; }
if (value.isPressed)
{
MyRgdb.velocity += new Vector2(0f, jumpSpeed);
}
}
void run()
{
// only control the x axis
// maintain the original rgbd velocity
Vector2 playerVelocity = new Vector2(moveInput.x * runSpeed, rgdb.velocity.y);
rgdb.velocity = playerVelocity;
}
Filp
1
2
3
4
5
6
7
8
9
10
11
| void FlipSprite()
{
bool HasHorizontalSpeed = Mathf.Abs(rgdb.velocity.x) > Mathf.Epsilon;
// only there is speed when moving
if(HasHorizontalSpeed)
{
transform.localScale = new Vector2(Mathf.Sign(rgdb.velocity.x), 1f);
// Sign consider 0 as positive
}
}
|
Physic Material
1
2
3
4
5
| void OnFire(InputValue value)
{
if (!isAlive) { return; }
Instantiate(bullet, gun.position, transform.rotation);
}
|
- Coroutines are another way for us to create a delay in our game.
- The core concept to understand is that we start a process (ie. Start Coroutine) and then go off and do other things (ie. Yield) until our condition (eg. we’ve waited 2 seconds) is met.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| void OnTriggerEnter2D(Collider2D other)
{
StartCoroutine(LoadNextLevel());
// things here will be excuted first
}
IEnumerator LoadNextLevel()
{
yield return new WaitForSecondsRealtime(levelLoadDelay);
int currentSceneIndex = SceneManager.GetActiveScene().buildIndex;
int nextSceneIndex = currentSceneIndex + 1;
if (nextSceneIndex == SceneManager.sceneCountInBuildSettings)
{
nextSceneIndex = 0;
}
SceneManager.LoadScene(nextSceneIndex);
}
|
Instantiate with nesting parent
1
2
3
4
5
6
7
8
| // the transform of parents
private void spawnEnemy()
{
Instantiate(currentWave.getEnemyPrefeb(0),
currentWave.getStartingWaypoint().position,
Quaternion.identity,
transform);
}
|
Game Session
Object.FindObjectsOfType
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
| using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameSession : MonoBehaviour
{
[SerializeField] int playerLives = 3;
void Awake()
{
int numGameSessions = FindObjectsOfType<GameSession>().Length;
if (numGameSessions > 1)
{
Destroy(gameObject);
}
else
{
DontDestroyOnLoad(gameObject);
}
}
public void ProcessPlayerDeath()
{
if (playerLives > 1)
{
TakeLife();
}
else
{
ResetGameSession();
}
}
void TakeLife()
{
playerLives--;
int currentSceneIndex = SceneManager.GetActiveScene().buildIndex;
SceneManager.LoadScene(currentSceneIndex);
}
void ResetGameSession()
{
SceneManager.LoadScene(0);
Destroy(gameObject);
}
}
|
Singleton Pattern (Scene Persist)
Put persistant stuff as Scene Persist
’s children
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| void Awake()
{
int numScenePersists = FindObjectsOfType<ScenePersist>().Length;
if (numScenePersists > 1)
{
Destroy(gameObject);
}
else
{
DontDestroyOnLoad(gameObject);
}
}
public void ResetScenePersist()
{
Destroy(gameObject);
}
|
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
32
| static AudioPlayer instance;
void Awake()
{
ManageSingleton();
}
void ManageSingleton()
{
if(instance != null)
{
gameObject.SetActive(false);
Destroy(gameObject);
}
else
{
instance = this;
// do not destory when switch the scene
DontDestroyOnLoad(gameObject);
}
}
// ------------------------ another example -------------------
private void Awake()
{
if (Instance != null)
{
Destroy(gameObject);
}
Instance = this;
}
|
Generic Singleton
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
32
33
| public class Singleton<T> : MonoBehaviour where T : Singleton<T>
{
private static T instance;
public static T Instance
{
get { return instance; }
}
protected virtual void Awake()
{
if (instance != null)
{
Destroy(gameObject);
}
else
{
instance = (T)this;
}
}
public static bool IsInitialized
{
get { return instance != null; }
}
protected virtual void OnDestory()
{
if (instance == this)
{
instance = null;
}
}
}
|
Override on the Subclass
1
2
3
4
5
| protected override void Awake()
{
base.Awake();
// DontDestroyOnLoad(this);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| void initBounds()
{
Camera mainCamera = Camera.main;
minBounds = mainCamera.ViewportToWorldPoint(new Vector2(0, 0));
maxBounds = mainCamera.ViewportToWorldPoint(new Vector2(1, 1));
}
void Move(){
Vector2 delta = rawInput * moveSpeed * Time.deltaTime;
Vector2 newPos = new Vector2();
newPos.x = Mathf.Clamp(transform.position.x + delta.x, minBounds.x + paddingLeft, maxBounds.x - paddingRight);
newPos.y = Mathf.Clamp(transform.position.y + delta.y, minBounds.y + paddingBottom, maxBounds.y - paddingTop);
transform.position = newPos;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| [SerializeField] Vector2 moveSpeed;
Vector2 offset;
Material material;
void Awake()
{
material = GetComponent<SpriteRenderer>().material;
}
// Update is called once per frame
void Update()
{
offset = moveSpeed * Time.deltaTime;
material.mainTextureOffset += offset;
}
|