Unity: Move, Camera Tracking, Animator

Unity: Move, Camera Tracking, Animator

最近跟着网络上的课程班学习了一些Unity的使用,记录一下自己的学习心得。

移动:Move

实现角色的移动需要在Update方法里对物体的位置进行更新。

Unity支持数学公式,角色控制器以及刚体等移动方式。
而每种方式又有不同的子方法。

旋转:Rotation

我们先要根据input获取键盘上对应的操作命令。
如果水平和垂直数值均为0,则直接返回,不进行下面的更新命令。

将输入的方向新建为一个向量。
然后将其转换为一个四元数,通过rotation方法对角色进行旋转。

1
2
3
4
5
6
7
8
9
10
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
m_anim.SetBool("bMove", h != 0 || v != 0);
if (h == 0 && v == 0) return;

Vector3 dir = new Vector3(-h, 0, -v);
Quaternion targetQ = Quaternion.LookRotation(dir, Vector3.up);
//transform.rotation = targetQ;
transform.rotation = Quaternion.Lerp(transform.rotation, targetQ, Time.deltaTime * mfRoateSpeed);
//对四元数进行插值,控制旋转速度

数学公式:Math Function

我们可以直接以数学公式的方法更新当前位置,将其加入向量乘以帧时间乘以速度的方式更新当前位置:
transform.position += transform.forward * Time.deltaTime * mfMoveSpeed;

也可以使用Translate()方法,注意这里需要传入world空间模式
transform.Translate(transform.forward * Time.deltaTime * mfMoveSpeed, Space.World);

MoveTowards方法,不太常用。
transform.position = Vector3.MoveTowards(transform.position, transform.position + transform.forward * Time.deltaTime * mfMoveSpeed, Time.deltaTime * mfMoveSpeed);

角色控制器:Character Controller

这种模式需要在全局变量中创建并获取角色控制器对象。
使用角色控制器需要设定其盒子范围。

1
2
3
4
5
6
7
m_characterController = transform.gameObject.GetComponent<CharacterController>();
if(m_characterController == null)
{
m_characterController = transform.gameObject.AddComponent<CharacterController>();
}
m_characterController.center = new Vector3(0f, 0.6f, 0f);
m_characterController.height = 1.3f;

我们也可以使用角色控制器移动角色。

Simple Move模式可以直接模拟重力:
m_characterController.SimpleMove(transform.forward * mfMoveSpeed);

注意Simple Move模式下按秒进行移动,不需要乘以帧时间。

Move模式则需要手动添加向下的重力,否则物体Y轴移动后不会下降。
m_characterController.Move(-transform.up * Time.deltaTime * mfMoveSpeed);m_characterController.Move(transform.forward * Time.deltaTime * mfMoveSpeed);

刚体:Rigidbody

首先需要在全局变量中创建并获取刚体对象。

刚体模式需要创建碰撞盒,并且将Y轴freeze,否则物体会直接向下落下。

1
2
3
4
5
6
7
8
9
10
11
m_rigidbody = transform.gameObject.GetComponent<Rigidbody>();
if(m_rigidbody == null)
{
m_rigidbody = transform.gameObject.AddComponent<Rigidbody>();
}
m_rigidbody.constraints = RigidbodyConstraints.FreezePositionY | RigidbodyConstraints.FreezeRotationX |
RigidbodyConstraints.FreezeRotationY | RigidbodyConstraints.FreezeRotationZ;

CapsuleCollider collider = transform.gameObject.AddComponent<CapsuleCollider>();
collider.center = new Vector3(0f, 0.6f, 0f);
collider.height = 1.3f;

可以采用恒速的velocity方法
m_rigidbody.velocity = transform.forward * mfMoveSpeed;

或者采用AddForce方法,这样会不断加速。
m_rigidbody.AddForce(transform.forward * mfMoveSpeed * mAddForceFactor);

MovePosition方法,同样是直接计算移动后的位置。
m_rigidbody.MovePosition(transform.position + transform.forward * Time.deltaTime * mfMoveSpeed);

镜头跟随:Camera Tracking

脚本需要挂在到相机下。

两个向量相减的几何意义是减数方向指向被减数方向

用相机位置减去玩家的位置,则可以得到玩家指向相机的向量。
然后在Update中实时更新相机的位置,就可以实现镜头跟随的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MainCamera : MonoBehaviour
{
private Transform m_player; //需要聚焦的角色的位置
private Vector3 m_v3RelativePos; //相机和角色的相对向量

// Start is called before the first frame update
void Start()
{
m_player = GameObject.FindGameObjectWithTag("Player").transform; //获取玩家角色
m_v3RelativePos = transform.position - m_player.position; //计算相对位置
}

// Update is called once per frame
void Update()
{
transform.position = m_player.position + m_v3RelativePos; //实时更新相机的位置
}
}

动画状态机:Animator

模型下需要有Anim模块才能实现动画。
原有的资源里已经制作了动画,在这里额外实现的是根据动作来切换动画。

在Start()里初始化成员变量Animator m_anim。
m_anim = transform.Find("Anim").GetComponent<Animator>();
然后在Update()里使用SetBool方法对Animator的开关真值进行更新。
m_anim.SetBool("bMove", h != 0 || v != 0);

Animator支持几种类型的trigger如Bool,Int,

1641. Count Sorted Vowel Strings

1641. Count Sorted Vowel Strings

Question

Given an integer n, return the number of strings of length n that consist only of vowels (a, e, i, o, u) and are lexicographically sorted.

A string s is lexicographically sorted if for all valid i, s[i] is the same as or comes before s[i+1] in the alphabet.

Solution

动态规划,创建数组dp[]记录以每个字符开头能组成的字符串数量。
由于当长度为1时,每个字符串都能组成一次,因此初始化所有值为1。

n增长时,每一个层级都等于该字符后的所有值相加。
最后得出的结果在下一个层级的第一位。(因为第一位是所有上一个层级加和)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public int countVowelStrings(int n) {
int[] dp = new int[]{1,1,1,1,1};

for(int i = 0; i < n; i++){
for(int j = 0; j < 5; j++){
for(int k = j+1; k < 5; k++){
dp[j] += dp[k];
}
}
}
return dp[0];
}
}

Solution

递归,使用成员变量计算有效递归次数。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
int count;
public int countVowelStrings(int n) {
count = 0;
count(n, 0);
return count;
}

private void count(int n, int start){
if(n == 0){
count++;
return;
}
for(int i = start; i < 5; i++){
count(n-1, i);
}
}
}
216. Combination Sum III

216. Combination Sum III

Question

Find all valid combinations of k numbers that sum up to n such that the following conditions are true:

  • Only numbers 1 through 9 are used.
  • Each number is used at most once.

Return a list of all possible valid combinations. The list must not contain the same combination twice, and the combinations may be returned in any order.

Solution

回溯,创建一个成员变量visited[]记录访问情况。

剪枝优化,遍历所有可选择的数字,将其加和,如果加和大于target则返回。
剪枝优化,每层级遍历的范围大于传入列表中的最后一个元素。
回溯,向下递归。

如果k等于0并且sum等于target则添加arr到列表ret中。

Code

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
class Solution {
int[] visited;
List<List<Integer>> ret;

public List<List<Integer>> combinationSum3(int k, int n) {
visited = new int[9];
ret = new ArrayList<>();
backTracking(k, n, new ArrayList<>(), 0);
return ret;
}

private void backTracking(int k, int target, List<Integer> arr, int sum){
if(k == 0 && sum == target) ret.add(new ArrayList<>(arr));
int start = 1;
if(arr.size() != 0) start = arr.get(arr.size()-1)+1;
for(int i = start ; i <= 9; i++){
if(visited[i-1] == 1) continue;
sum+=i;
if(sum > target) return;
visited[i-1] = 1;
arr.add(i);

backTracking(k-1, target, arr, sum);
sum-=i;
arr.remove(new Integer(i));
visited[i-1] = 0;
}
}
}
341. Flatten Nested List Iterator

341. Flatten Nested List Iterator

Question

You are given a nested list of integers nestedList. Each element is either an integer or a list whose elements may also be integers or other lists. Implement an iterator to flatten it.

Implement the NestedIterator class:

  • NestedIterator(List<NestedInteger> nestedList) Initializes the iterator with the nested list nestedList.
  • int next() Returns the next integer in the nested list.
  • boolean hasNext() Returns true if there are still some integers in the nested list and false otherwise.

Your code will be tested with the following pseudocode:

initialize iterator with nestedList
res = []
while iterator.hasNext()
append iterator.next() to the end of res
return res

If res matches the expected flattened list, then your code will be judged as correct.

Solution

将所有的NestedInteger展开后加入全局变量队列。

辅助方法buildQueue():
递归,如果当前元素是列表,则向下递归列表内的所有元素。
如果当前元素是单个整数,则将其加入队列。

next():
返回并挤出队列中的下一个元素,返回其整数。

hasNext():
返回队列是否为空。

Code

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
50
51
52
53
/**
* // This is the interface that allows for creating nested lists.
* // You should not implement it, or speculate about its implementation
* public interface NestedInteger {
*
* // @return true if this NestedInteger holds a single integer, rather than a nested list.
* public boolean isInteger();
*
* // @return the single integer that this NestedInteger holds, if it holds a single integer
* // Return null if this NestedInteger holds a nested list
* public Integer getInteger();
*
* // @return the nested list that this NestedInteger holds, if it holds a nested list
* // Return empty list if this NestedInteger holds a single integer
* public List<NestedInteger> getList();
* }
*/
public class NestedIterator implements Iterator<Integer> {
Queue<NestedInteger> q;
public NestedIterator(List<NestedInteger> nestedList) {
q = new LinkedList<>();
for(NestedInteger item : nestedList){
buildQueue(item);
}
}

private void buildQueue(NestedInteger ni){
if(!ni.isInteger()){
for(NestedInteger item : ni.getList()){
buildQueue(item);
}
}
else{
q.offer(ni);
}
}

@Override
public Integer next() {
return q.poll().getInteger();
}

@Override
public boolean hasNext() {
return !q.isEmpty();
}
}

/**
* Your NestedIterator object will be instantiated and called as such:
* NestedIterator i = new NestedIterator(nestedList);
* while (i.hasNext()) v[f()] = i.next();
*/
456. 132 Pattern

456. 132 Pattern

Question

Given an array of n integers nums, a 132 pattern is a subsequence of three integers nums[i], nums[j] and nums[k] such that i < j < k and nums[i] < nums[k] < nums[j].

Return true if there is a 132 pattern in nums, otherwise, return false.

Solution

单调栈,专门用于找某一个元素的左边/右边第一个比自己大/小的位置。
递增单调栈会提出波峰,留下波谷。递减单调栈会剔除波谷,留下波峰。

我们需要找到同时满足j < k和nums[k] <[j]情况的位置。因此采用单调栈正合适。
我们枚举132模式中的“3”,由于我们要找到波谷,因此可以采用递增栈。

递增单调栈的实现:

当遍历的当前值小于栈顶元素时,不满足递增规律,因此挤出栈顶。
循环此操作直至当前栈顶于当前值满足递增规律或栈空。
此时的当前值是nums[j],而最后一个被挤出的值就是nums[k]。
由于递增单调栈的性质,此时的nums[k] < nums[j]且nums[k]大于被挤出的所有元素。

对于nums[i],我们可以通过遍历nums[]数组,计算出其在i位置的最小值,并存在放minOfLeft[]中。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public boolean find132pattern(int[] nums) {
Stack<Integer> stack = new Stack<>();
int[] minOfLeft = new int[nums.length];
minOfLeft[0] = nums[0];
for(int i = 1; i < nums.length; i++){
minOfLeft[i] = Math.min(minOfLeft[i-1], nums[i]);
}

for(int j = nums.length-1; j >= 0; j--){
int k = Integer.MIN_VALUE;
while(!stack.isEmpty() && nums[j] > stack.peek()){
k = stack.pop();
}
if(minOfLeft[j] < k){
return true;
}
stack.push(nums[j]);
}
return false;
}
}