basic Unity3D_Gameplay_method
1
2
3
4
5
6
7
8
9
10
| // 都是 vector3
// 全局位置
print(transform.eulerAngles);
print(transform.position);
print(transform.lossyScale);
// 相对位置
print(transform.localEulerAngles);
print(transform.localPosition);
print(transform.localScale);
|
Camera
- 用于将视锥内的模型渲染到屏幕上的组件, 可以设置视锥的近远面来控制渲染范團, 通过设置
Projection
投影可以切换Perspective
/Orthographic
模式,因此2D项目在Unity中本质上也是3D的此外相机还支持分层剔除,将不同层的对象分开渲染 - 相机的移动要放在
LateUodate
中以避免其他运动物体闪烁卡顿
Joint
- 关节所在物体必须挂载刚体,关节连接的刚体仍可以是关节,因此可以模拟一些复杂机械结构.
- 通过调节参数,可以实现很多常见的物理效果,比如降低弹簧关节的弹性即可模拟吊灯
- 除了常规关节,Configurablevoint可配置关节,可以进行非常详细的定制
- CharacterJoint角色关节可用来制作“布娃娃”系统,在很多游戏中用来模拟死亡的角色身体
Ray - Unity物理系统提供的一种检测方式
Pyhsics.Raycast
, Pyhsics.RaycastAll
规定射线的原点、方向、距离等,即可沿线检测是否有碰撞体存在,检测信息hitinfol以out参数形式带出一些不追求真实弹道模拟的射击游戏,经常使用射线检测来代替高速子弹判定,以节省性能开销 将鼠标的屏幕位置换算成摄像机视口位置,就可以利用射线制作MOBA游戏的点击移动效果 一些角色脚部贴合地面的的功能,也用到了射线检测(测量脚下地面法线)
1
2
3
4
5
6
7
8
9
10
11
| RaycastHit hitInfo;
// Start is called before the first frame update
void Start()
{
if (Physics.Raycast(Vector3.zero, Vector3.forward, out hitInfo))
{
// hitInfo.collider;
// hitInfo.point;
// hitInfo.normal;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
| RaycastHit hitInfo;
Ray mouseRay;
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
mouseRay = Camera.main.ScreenPointToRay(Input.mousePosition);
if(Physics.Raycast(mouseRay, out hitInfo)){
print("点击了" + hitInfo.point);
}
}
}
|
Navigation System
A* algorithm
carve - bake后 可移动分割区域
规划可移动路线
标记为static => not walkable => bake
objects pool
predefined pool (Queue) to store instance, so that improve the efficiency. There is no more instantiate
and destory
.
- 初始池可以为空,也可以携带一批默认对象,节约第一次生成对象的性能开销
- 由于对象池中的「存货」,只有场中对象最大存量大于池中存货时才生成对象。因此池中对象数量到达峰值后,几乎不再需要生成对象,也无需销毁(需要消失时关闭并入队即可)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| public GameObject bulletOrigin;
Queue<GameObject> bulletPool = new Queue<GameObject>();
void Start()
{
if(bulletPool.Count == 0){
Instantiate(bulletOrigin);
}else{
bulletPool.Dequeue().SetActive(true);
}
}
// if the encountered object is bullet => enqueue
private void OnTriggerEnter(Collider other) {
if (other.name == bulletOrigin.name + "(Clone)"){
other.gameObject.SetActive(false);
bulletPool.Enqueue(other.gameObject);
}
}
|
UnityWebRequest
1
2
3
4
5
6
7
8
9
| private void Start() {
StartCoroutine(Request());
}
IEnumerator Request()
{
UnityWebRequest request = UnityWebRequest.Get("unity.cn");
yield return request.SendWebRequest();
print(request.downloadHandler.text);
}
|
URP设置
shortcut
- 按住
v
可以吸附两个物体的顶点 - 按住
ctrl
+shift
可以吸附两个物体的平面 ctrl
+shift
+f
-> 把摄像机调整到当前视角
animation
Blend tree
- Control mutiple animations with blend tree using one parameter like speed.
- instead of using bool / trigger
1
2
3
4
| private void SwitchAnimation(){
// vector 3 => magnitude
anim.SetFloat("Speed", agent.velocity.sqrMagnitude);
}
|
post-processing
ShaderGraph
Create ShaderGraph => Create Material
遮挡剔除
By adjusting URP global renderer to achieve optional render.
需要应用于很多物体 / 需要挂很多component => 如何确保添加Component
1
2
3
4
5
6
7
8
9
10
11
| // 当脚本挂载的gameobject没有 NavmeshAgent时,自动挂载NavMeshAgent
[RequireComponent(typeof(NavMeshAgent))]
public class EnemyController : MonoBehaviour
{
private NavMeshAgent agent;
private void Awake()
{
agent = GetComponent<NavMeshAgent>();
}
}
|
StateMachine
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public enum EnemyStates { GUARD, PATROL, CHASE, DEAD }
void SwitchStates()
{
switch (enemyStates)
{
case EnemyStates.GUARD:
break;
case EnemyStates.PATROL:
break;
case EnemyStates.CHASE:
break;
case EnemyStates.DEAD:
break;
}
}
|
Gizmos - visualize parameter in Scene
1
2
3
4
| private void OnDrawGizmosSelected() {
Gizmos.color = Color.blue;
Gizmos.DrawWireSphere(transform.position, sightRadius);
}
|
StateMachineBehaviour
UI
scale with screen size
-> resolution independent
health silder
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
| using UnityEngine.UI;
public class MainMenu : MonoBehaviour
{
Button newGameBtn;
Button continueBtn;
Button quitBtn;
private void Awake()
{
newGameBtn = transform.GetChild(1).GetComponent<Button>();
continueBtn = transform.GetChild(2).GetComponent<Button>();
quitBtn = transform.GetChild(3).GetComponent<Button>();
quitBtn.onClick.AddListener(QuitGame);
newGameBtn.onClick.AddListener(NewGame);
continueBtn.onClick.AddListener(ContinueGame);
}
void QuitGame()
{
Application.Quit();
Debug.Log("Quit");
}
void NewGame()
{
PlayerPrefs.DeleteAll();
SceneController.Instance.TransitionToFirstLevel();
}
void ContinueGame()
{
SceneController.Instance.TransitionToLoadGame();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
| IEnumerator Transition(string sceneName, TransitionDestination.DestinationTag destinationTag)
{
// TODO: save data
if (SceneManager.GetActiveScene().name != sceneName)
{
// load scene and player
// asy -> make sure load everything in next scene, and current scene didn't freeze at the same time
yield return SceneManager.LoadSceneAsync(sceneName);
yield return Instantiate(playerPrefeb, GetDestination(destinationTag).transform.position, GetDestination(destinationTag).transform.rotation);
yield break;
}
}
|
Awake -> OnEnable -> Start
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| // subscribe the function when load the scene
private void OnEnable()
{
// move at ground
MouseManager.Instance.OnMouseClicked += MoveToTarget;
MouseManager.Instance.OnEnemyClicked += EventAttack;
}
private void Start()
{
GameManager.Instance.RegisterPlayer(characterStats);
}
// unsubscribe the function when switch the scene
private void OnDisable()
{
if(!MouseManager.IsInitialized) return;
MouseManager.Instance.OnMouseClicked -= MoveToTarget;
MouseManager.Instance.OnEnemyClicked -= EventAttack;
}
|
Save Data
PlayerPrefs: stores Player preferences
JsonUtility: Generate a JSON representation of the public fields of an object(SO/Mono)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| public void Save(Object data, string key)
{
var jsonData = JsonUtility.ToJson(data, true);
PlayerPrefs.SetString(key, jsonData);
PlayerPrefs.Save();
}
public void Load(Object data, string key)
{
if (PlayerPrefs.HasKey(key))
{
JsonUtility.FromJsonOverwrite(PlayerPrefs.GetString(key), data);
}
}
|
clear prefs before building 删除存档
Timeline 动画
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| using UnityEngine.Playables;
PlayableDirector director;
private void Awake()
{
newGameBtn = transform.GetChild(1).GetComponent<Button>();
director = FindObjectOfType<PlayableDirector>();
newGameBtn.onClick.AddListener(PlayTimeline);
director.stopped += NewGame;
}
void PlayTimeline()
{
director.Play();
}
void NewGame(PlayableDirector obj)
{
PlayerPrefs.DeleteAll();
SceneController.Instance.TransitionToFirstLevel();
}
|