本文最后编辑于 前,其中的内容可能需要更新。
API
SmoothDamp
平滑的改变当前值至另一个值
1 2 3
| Mathf.SmoothDamp(float current, float target, ref float currentVelocity, float smoothTime, float maxSpeed = Mathf.Infinity, float deltaTime = Time.deltaTime);
Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 currentVelocity, float smoothTime, float maxSpeed = Mathf.Infinity, float deltaTime = Time.deltaTime);
|
*current*:当前位置
*target*:尝试达到的目标值
*currentVelocity*:当前速度,该值在每次调用时都会由函数修改。
*smoothTime*:达到目标值的时间
*maxSpeed*:最大速度
*deltaTime*:默认为Time.deltatime
*ref关键字*:相当于c的指针传参,及引用传参。
1
| public Vector3 InverseTransformPoint(Vector3 position);
|
将position这个Vector3类型变量转化为 以V3的世界坐标为零点基准的情况下 position相对于V3的坐标值。
Physics
Physics.OverlapSphere
检测范围内的Collider
public static Collider[] OverlapSphere(Vector3 position, float radius, int layerMask = AllLayers, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal);
*position*:圆心
*radius*:检测半径
*layerMask*:检测层级
*queryTriggerInteraction*:判断是否应该检测Trigger
Rigidbody
targetRigidbody.AddExplosionForce
爆炸力将随着到物体的距离变小。
public void AddExplosionForce(float explosionForce, Vector3 explosionPosition, float explosionRadius, float upwardsModifier = 0.0f, ForceMode mode = ForceMode.Force));
*explosionForce*:爆炸的力量(会根据距离变化)
*explosionPosition*:爆炸中心
*explosionRadius*:爆炸半径
*upwardsModifier*:可以调整爆炸的位置,让物体有被炸起来向上的效果,但爆炸本身的位置不变
*ForceMode*:对物体施加力的方法
Manual
相机的两种投影方式
透视投影(Perspective)(左): 正交投影的观察体是长方体,它使用一组平行投影将三维对象投影到投影平面上去,即场景中的物体没有近大远小的效果。
正交投影(Orthographic)(右): 透视投影的观察体是视锥体,它使用一组由投影中心产生的放射投影线,将三维对象投影到投影平面上去,即屏幕中的物体存在透视效果
Aodio Mixer
类似于Windows的音量合成器,但更为复杂
可以用来进行多种音效的混合表现
要用可百度学习
问题与解决
移动和旋转问题
有问题的代码
1 2
| Vector3 move = Vector3.forward * m_MovementInputValue * Time.deltaTime * m_Speed; m_Rigidbody.MovePosition(m_Rigidbody.position + move);
|
此代码会导致物体旋转后会继续以世界坐标的z轴为前后方向,而导致旋转看起来不起作用,像坐标轴没有跟着旋转一样
正确的代码
1 2
| Vector3 move = transform.forward * m_MovementInputValue * Time.deltaTime * m_Speed; m_Rigidbody.MovePosition(m_Rigidbody.position + move);
|
修改后一切正常
Vector3.forward和transform.forward的区别
Vector3.forward的值永远是世界坐标(0,0,1),
而transform.forward是世界坐标对应的物体坐标的轴的向量
代码记录
相机的平滑运动
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
| private void Move() { FindAveragePosition();
transform.position = Vector3.SmoothDamp(transform.position, m_DesiredPosition, ref m_MoveVelocity, m_DampTime); }
private void FindAveragePosition() { Vector3 averagePos = new Vector3();
int numTargets = 0;
for (int i = 0; i < m_Targets.Length; i++) { if (!m_Targets[i].gameObject.activeSelf) continue;
averagePos += m_Targets[i].position; numTargets++; }
if (numTargets > 0) averagePos /= numTargets;
averagePos.y = transform.position.y;
m_DesiredPosition = averagePos; }
|
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
| private void Zoom() { float requiredSize = FindRequiredSize(); m_Camera.orthographicSize = Mathf.SmoothDamp(m_Camera.orthographicSize, requiredSize, ref m_ZoomSpeed, m_DampTime); }
private float FindRequiredSize() { Vector3 desiredLocalPos = transform.InverseTransformPoint(m_DesiredPosition);
float size = 0f;
for (int i = 0; i < m_Targets.Length; i++) { if (!m_Targets[i].gameObject.activeSelf) continue;
Vector3 targetLocalPos = transform.InverseTransformPoint(m_Targets[i].position);
Vector3 desiredPosToTarget = targetLocalPos - desiredLocalPos;
size = Mathf.Max (size, Mathf.Abs (desiredPosToTarget.y));
size = Mathf.Max (size, Mathf.Abs (desiredPosToTarget.x) / m_Camera.aspect); }
size += m_ScreenEdgeBuffer;
size = Mathf.Max(size, m_MinSize);
return size; }
|
炮弹的爆炸和伤害判定
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
| private void OnTriggerEnter(Collider other) { Collider[] colliders = Physics.OverlapSphere(transform.position, m_ExplosionRadius, m_TankMask);
for (int i = 0; i < colliders.Length; i++) { Rigidbody targetRigidbody = colliders[i].GetComponent<Rigidbody>(); if (!targetRigidbody) continue;
targetRigidbody.AddExplosionForce(m_ExplosionForce, transform.position, m_ExplosionRadius);
TankHealth tankHealth = targetRigidbody.GetComponent<TankHealth>();
if (!tankHealth) continue;
float damage = CalculateDamage(targetRigidbody.position);
tankHealth.TakeDamage(damage); }
m_ExplosionParticles.transform.parent = null; m_ExplosionParticles.Play(); m_ExplosionAudio.Play();
Destroy(m_ExplosionParticles.gameObject, m_ExplosionParticles.main.duration); Destroy(gameObject); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| private float CalculateDamage(Vector3 targetPosition) { Vector3 explosionToTarget = targetPosition - transform.position; float explosionDistance = explosionToTarget.magnitude;
float relativeDistance = (m_ExplosionRadius - explosionDistance) / m_ExplosionRadius;
float damage = relativeDistance * m_MaxDamage;
damage = Mathf.Max(0, damage);
return damage; }
|
子弹的对象池模式
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
| public class ShellExplosion : MonoBehaviour,IPooler {
public void OnSpawning() { StartCoroutine(Spawning()); }
IEnumerator Spawning() { yield return m_TimeToFalse; m_ExplosionParticles.transform.parent = gameObject.transform; m_ExplosionParticles.transform.position = gameObject.transform.position; gameObject.transform.position = new Vector3(0, 0, 0); gameObject.SetActive(false); } }
|
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| using System.Collections; using System.Collections.Generic; using UnityEngine;
public class ShellPool : MonoBehaviour { [System.Serializable] public class Pool { public string tag; public GameObject prefab; public int size; }
public Transform parentTransform;
public List<Pool> poolList; public Dictionary<string, Queue<GameObject>> poolDictionary;
public static ShellPool shellPoolInsatance; private void Awake() { if (shellPoolInsatance == null) shellPoolInsatance = this; else if (shellPoolInsatance != this) Destroy(this); }
void Start() { poolDictionary = new Dictionary<string, Queue<GameObject>>();
foreach (var pool in poolList) { Queue<GameObject> tPool = new Queue<GameObject>();
for (int i = 0; i < pool.size; i++) { GameObject tShell = Instantiate(pool.prefab, parentTransform,true); tShell.SetActive(false); tPool.Enqueue(tShell); }
poolDictionary.Add(pool.tag, tPool); } } public GameObject SpawnFromPool(string tag, Vector3 position, Quaternion rotation) { if(!poolDictionary.ContainsKey(tag)) { Debug.Log(tag + "不存在"); return null; }
GameObject theSpawnObj = poolDictionary[tag].Dequeue(); Debug.Log(theSpawnObj);
theSpawnObj.SetActive(true); theSpawnObj.transform.position = position; theSpawnObj.transform.rotation = rotation;
IPooler poolSpawn = theSpawnObj.GetComponent<IPooler>(); if (poolSpawn != null) poolSpawn.OnSpawning();
poolDictionary[tag].Enqueue(theSpawnObj);
return theSpawnObj; } }
|
1 2 3 4 5 6 7
|
interface IPooler { public void OnSpawning(); }
|
学习总结
游戏循环模式(协程完成)
游戏管理模式
一些游戏物体的代码不需要继承MonoBehaviour(无需挂载),只当实例化后赋予其GameObject或直接更具里面的信息实例化一个物体。例如此例中的Tank 或者 一些随机地图的部分地图信息
可能的心得(……..)
- 协程内调用多个协程,只会在上一个协程调用完成后,下一个协程才会开始
- 回合制的游戏可以使用协程控制游戏流程,开始、游玩、结束,都很清晰明了
- 写代码时因该将所有功能块写成函数,可以让代码结构更清晰
- 尽量将可能的变量全定义在类的开头,理由同上