using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class Weapon : MonoBehaviour
{
static RaycastHit[] s_HitInfoBuffer = new RaycastHit[8];//受擊射線
public enum TriggerType//碰觸類型
{
Auto,//自動
Manual//手動
}
public enum WeaponType//武器類型
{
Raycast,//射線
Projectile//投擲物
}
public enum WeaponState//武器狀態
{
Idle,//等待
Firing,//開火
Reloading//重填
}
[System.Serializable]
public class AdvancedSettings//高級設置
{
public float spreadAngle = 0.0f;//傳播角度
public int projectilePerShot = 1;//射彈
public float screenShakeMultiplier = 1.0f;//屏幕抖動倍增器
}
public TriggerType triggerType = TriggerType.Manual;//觸碰類型初始化爲手動
public WeaponType weaponType = WeaponType.Raycast;//武器類型初始化爲射線
public float fireRate = 0.5f;//開火頻率
public float reloadTime = 2.0f;//裝彈時間
public int clipSize = 4;//剪輯大小,音頻
public float damage = 1.0f;//傷害
[AmmoType]
public int ammoType = -1;//彈藥類型
public Projectile projectilePrefab;//投擲物預製體
public float projectileLaunchForce = 200.0f;//投擲物發射力
public Transform EndPoint; //結束點
public AdvancedSettings advancedSettings;//高級設置
[Header("Animation Clips")]//面板顯示時給予一個開頭的標識
public AnimationClip FireAnimationClip;//開火動畫音頻
public AnimationClip ReloadAnimationClip;//裝彈動畫音頻
[Header("Audio Clips")]
public AudioClip FireAudioClip;//開火音頻
public AudioClip ReloadAudioClip;//裝彈音頻
[Header("Visual Settings")]
public LineRenderer PrefabRayTrail;//預製射線追蹤
[Header("Visual Display")]
public AmmoDisplay AmmoDisplay;//彈藥顯示
public bool triggerDown//是否碰觸
{
get { return m_TriggerDown; }
set
{
m_TriggerDown = value;
if (!m_TriggerDown) m_ShotDone = false;
}
}
public WeaponState CurrentState => m_CurrentState;//當前狀態,=>不知道是什麼意思
public int ClipContent => m_ClipContent;//音頻內容
public Controller Owner => m_Owner;//所有者
Controller m_Owner;//所有者
Animator m_Animator;//動畫管理器
WeaponState m_CurrentState;//武器狀態
bool m_ShotDone;//射擊完成
float m_ShotTimer = -1.0f;//射擊計時器
bool m_TriggerDown;//觸碰
int m_ClipContent;//自身音頻的內容
AudioSource m_Source;//資源
Vector3 m_ConvertedMuzzlePos;//轉換後槍口的位置
class ActiveTrail//射擊光線的拖尾
{
public LineRenderer renderer;//光線渲染
public Vector3 direction;//方向
public float remainingTime;//持續時間
}
List<ActiveTrail> m_ActiveTrails = new List<ActiveTrail>();//光線拖尾列表
Queue<Projectile> m_ProjectilePool = new Queue<Projectile>();//投擲物
int fireNameHash = Animator.StringToHash("fire");//開火
int reloadNameHash = Animator.StringToHash("reload");//裝彈
void Awake()
{
m_Animator = GetComponentInChildren<Animator>();//獲取到當前位置孩子中的動畫控制器
m_Source = GetComponentInChildren<AudioSource>();//獲取音頻資源器
m_ClipContent = clipSize;//音頻大小
if (PrefabRayTrail != null)//拖尾預製體不爲空
{
const int trailPoolSize = 16;//拖尾池大小
PoolSystem.Instance.InitPool(PrefabRayTrail, trailPoolSize);//初始化對象池
}
if (projectilePrefab != null)//投擲物預製體不爲空
{
//a minimum of 4 is useful for weapon that have a clip size of 1 and where you can throw a second
//or more before the previous one was recycled/exploded.
int size = Mathf.Max(4, clipSize) * advancedSettings.projectilePerShot;//控制投擲物體多少
for (int i = 0; i < size; ++i)
{
Projectile p = Instantiate(projectilePrefab);//實例化
p.gameObject.SetActive(false);//隱藏
m_ProjectilePool.Enqueue(p);//退出隊列
}
}
}
public void PickedUp(Controller c)//指定當前武器歸屬權
{
m_Owner = c;
}
public void PutAway()//
{
m_Animator.WriteDefaultValues();//爲動畫器寫入默認值
for (int i = 0; i < m_ActiveTrails.Count; ++i)//製造拖尾並隱藏
{
var activeTrail = m_ActiveTrails[i];
m_ActiveTrails[i].renderer.gameObject.SetActive(false);
}
m_ActiveTrails.Clear();//清空
}
public void Selected()//被選中
{
var ammoRemaining = m_Owner.GetAmmo(ammoType);//獲取倉庫中的彈藥量
//gun get disabled when ammo is == 0 and there is no more ammo in the clip, so this allow to re-enable it if we
//grabbed ammo since last time we switched
gameObject.SetActive(ammoRemaining != 0 || m_ClipContent != 0);//只要還有彈藥或者音頻內容不爲0,就一直顯示
//設置動畫
if (FireAnimationClip != null)
m_Animator.SetFloat("fireSpeed", FireAnimationClip.length / fireRate);
if(ReloadAnimationClip != null)
m_Animator.SetFloat("reloadSpeed", ReloadAnimationClip.length / reloadTime);
m_CurrentState = WeaponState.Idle;//當前狀態爲等待或者說初始
triggerDown = false;//初始化
m_ShotDone = false;//射擊完成
WeaponInfoUI.Instance.UpdateWeaponName(this);//刷新當前武器名稱
WeaponInfoUI.Instance.UpdateClipInfo(this);//刷新射擊聲音
WeaponInfoUI.Instance.UpdateAmmoAmount(m_Owner.GetAmmo(ammoType));//刷新彈藥量
if(AmmoDisplay)//彈藥顯示
AmmoDisplay.UpdateAmount(m_ClipContent, clipSize);
if (m_ClipContent == 0 && ammoRemaining != 0)//裝彈的時候彈藥儲備不能爲空,同時音頻內容爲空
{
//this can only happen if the weapon ammo reserve was empty and we picked some since then. So directly
//reload the clip when wepaon is selected
int chargeInClip = Mathf.Min(ammoRemaining, clipSize);//
m_ClipContent += chargeInClip;
if(AmmoDisplay)//顯示彈藥
AmmoDisplay.UpdateAmount(m_ClipContent, clipSize);
m_Owner.ChangeAmmo(ammoType, -chargeInClip); //改變彈藥
WeaponInfoUI.Instance.UpdateClipInfo(this);//刷新音頻
}
m_Animator.SetTrigger("selected");//設置爲被選中
}
public void Fire()//開火
{
if (m_CurrentState != WeaponState.Idle || m_ShotTimer > 0 || m_ClipContent == 0)//狀態必須是等待狀態,射擊計時器大於0,音頻內容等於0
return;
m_ClipContent -= 1;
m_ShotTimer = fireRate;//計時器等於開火頻率
if(AmmoDisplay)//彈藥顯示
AmmoDisplay.UpdateAmount(m_ClipContent, clipSize);
WeaponInfoUI.Instance.UpdateClipInfo(this);//刷新音頻
//the state will only change next frame, so we set it right now.
m_CurrentState = WeaponState.Firing;//更新狀態
m_Animator.SetTrigger("fire");//設置觸發器爲開火
m_Source.pitch = Random.Range(0.7f, 1.0f);//隨機音調
m_Source.PlayOneShot(FireAudioClip);//播放開火音效
CameraShaker.Instance.Shake(0.2f, 0.05f * advancedSettings.screenShakeMultiplier);//攝像機抖動
if (weaponType == WeaponType.Raycast)//判斷射擊方式
{
for (int i = 0; i < advancedSettings.projectilePerShot; ++i)
{
RaycastShot();
}
}
else
{
ProjectileShot();
}
}
void RaycastShot()//射線射擊
{
//compute the ratio of our spread angle over the fov to know in viewport space what is the possible offset from center
float spreadRatio = advancedSettings.spreadAngle / Controller.Instance.MainCamera.fieldOfView;//傳播角度
Vector2 spread = spreadRatio * Random.insideUnitCircle;//傳播
RaycastHit hit;
Ray r = Controller.Instance.MainCamera.ViewportPointToRay(Vector3.one * 0.5f + (Vector3)spread);//返回從相機出發穿過視點的一射線
Vector3 hitPosition = r.origin + r.direction * 200.0f;//被接觸位置
if (Physics.Raycast(r, out hit, 1000.0f, ~(1 << 9), QueryTriggerInteraction.Ignore))//武器layer
{
Renderer renderer = hit.collider.GetComponentInChildren<Renderer>();//獲取到被接觸物體下的渲染
ImpactManager.Instance.PlayImpact(hit.point, hit.normal, renderer == null ? null : renderer.sharedMaterial);
//if too close, the trail effect would look weird if it arced to hit the wall, so only correct it if far
if (hit.distance > 5.0f)
hitPosition = hit.point;
//this is a target
if (hit.collider.gameObject.layer == 10)//如果被碰觸物體是第十層
{
Target target = hit.collider.gameObject.GetComponent<Target>();//就獲取其目標組件
target.Got(damage);//給予傷害
}
}
if (PrefabRayTrail != null)//預製體拖尾存在
{
var pos = new Vector3[] { GetCorrectedMuzzlePlace(), hitPosition };
var trail = PoolSystem.Instance.GetInstance<LineRenderer>(PrefabRayTrail);//獲取拖尾對象池中的對象
trail.gameObject.SetActive(true);//顯示拖尾
trail.SetPositions(pos);//設置位置
m_ActiveTrails.Add(new ActiveTrail()
{
remainingTime = 0.3f,//維繫時間
direction = (pos[1] - pos[0]).normalized,//方向
renderer = trail//渲染
});
}
}
void ProjectileShot()//投擲
{
for (int i = 0; i < advancedSettings.projectilePerShot; ++i)
{
float angle = Random.Range(0.0f, advancedSettings.spreadAngle * 0.5f);//角度
Vector2 angleDir = Random.insideUnitCircle * Mathf.Tan(angle * Mathf.Deg2Rad);//角度方向
Vector3 dir = EndPoint.transform.forward + (Vector3)angleDir;//目標方向和角度方向之和
dir.Normalize();//歸一化
var p = m_ProjectilePool.Dequeue();//對象池操作
p.gameObject.SetActive(true);
p.Launch(this, dir, projectileLaunchForce);
}
}
//For optimization, when a projectile is "destroyed" it is instead disabled and return to the weapon for reuse.
public void ReturnProjecticle(Projectile p)//增加投擲物
{
m_ProjectilePool.Enqueue(p);
}
public void Reload()//重裝彈藥
{
if (m_CurrentState != WeaponState.Idle || m_ClipContent == clipSize)//判斷狀態
return;
int remainingBullet = m_Owner.GetAmmo(ammoType);//獲取彈藥量
if (remainingBullet == 0)//如果沒有彈藥了,就隱藏武器
{
//No more bullet, so we disable the gun so it's not displayed anymore and change weapon
gameObject.SetActive(false);
return;
}
if (ReloadAudioClip != null)//裝彈聲音不爲空,就隨機音量,播放以此
{
m_Source.pitch = Random.Range(0.7f, 1.0f);
m_Source.PlayOneShot(ReloadAudioClip);
}
int chargeInClip = Mathf.Min(remainingBullet, clipSize - m_ClipContent);
//the state will only change next frame, so we set it right now.
m_CurrentState = WeaponState.Reloading;//狀態
m_ClipContent += chargeInClip;
if(AmmoDisplay)//彈藥顯示刷新
AmmoDisplay.UpdateAmount(m_ClipContent, clipSize);
m_Animator.SetTrigger("reload");//動畫
m_Owner.ChangeAmmo(ammoType, -chargeInClip);//改變彈藥
WeaponInfoUI.Instance.UpdateClipInfo(this);//刷新武器UI
}
void Update()
{
UpdateControllerState(); //刷新控制器狀態
if (m_ShotTimer > 0)//射擊計時器
m_ShotTimer -= Time.deltaTime;
Vector3[] pos = new Vector3[2];
for (int i = 0; i < m_ActiveTrails.Count; ++i)//控制運動中的光束
{
var activeTrail = m_ActiveTrails[i];
activeTrail.renderer.GetPositions(pos);
activeTrail.remainingTime -= Time.deltaTime;
pos[0] += activeTrail.direction * 50.0f * Time.deltaTime;
pos[1] += activeTrail.direction * 50.0f * Time.deltaTime;
m_ActiveTrails[i].renderer.SetPositions(pos);
if (m_ActiveTrails[i].remainingTime <= 0.0f)
{
m_ActiveTrails[i].renderer.gameObject.SetActive(false);
m_ActiveTrails.RemoveAt(i);
i--;
}
}
}
void UpdateControllerState()//刷新控制器狀態
{
m_Animator.SetFloat("speed", m_Owner.Speed);//速度
m_Animator.SetBool("grounded", m_Owner.Grounded);//在地否
var info = m_Animator.GetCurrentAnimatorStateInfo(0);//獲取動畫管理器狀態
WeaponState newState;
if (info.shortNameHash == fireNameHash)//開火
newState = WeaponState.Firing;
else if (info.shortNameHash == reloadNameHash)//裝彈
newState = WeaponState.Reloading;
else
newState = WeaponState.Idle;
if (newState != m_CurrentState)//自動裝彈
{
var oldState = m_CurrentState;
m_CurrentState = newState;
if (oldState == WeaponState.Firing)
{//we just finished firing, so check if we need to auto reload
if(m_ClipContent == 0)
Reload();
}
}
if (triggerDown)//碰觸完成
{
if (triggerType == TriggerType.Manual)
{
if (!m_ShotDone)
{
m_ShotDone = true;
Fire();
}
}
else
Fire();
}
}
/// <summary>
/// This will compute the corrected position of the muzzle flash in world space. Since the weapon camera use a
/// different FOV than the main camera, using the muzzle spot to spawn thing rendered by the main camera will appear
/// disconnected from the muzzle flash. So this convert the muzzle post from
/// world -> view weapon -> clip weapon -> inverse clip main cam -> inverse view cam -> corrected world pos
/// </summary>
/// <returns></returns>
public Vector3 GetCorrectedMuzzlePlace()//返回彈藥位置
{
Vector3 position = EndPoint.position;
position = Controller.Instance.WeaponCamera.WorldToScreenPoint(position);
position = Controller.Instance.MainCamera.ScreenToWorldPoint(position);
return position;
}
}
public class AmmoTypeAttribute : PropertyAttribute
{
}
public abstract class AmmoDisplay : MonoBehaviour//抽象類
{
public abstract void UpdateAmount(int current, int max);
}
#if UNITY_EDITOR
//沒有調用,目前不知道具體作用,以後再說吧
[CustomPropertyDrawer(typeof(AmmoTypeAttribute))]//自定義特定類型的編輯器界面
public class AmmoTypeDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)//重寫界面
{
AmmoDatabase ammoDB = GameDatabase.Instance.ammoDatabase;
if (ammoDB.entries == null || ammoDB.entries.Length == 0)
{
EditorGUI.HelpBox(position, "Please define at least 1 ammo type in the Game Database", MessageType.Error);
}
else
{
int currentID = property.intValue;
int currentIdx = -1;
//this is pretty ineffective, maybe find a way to cache that if prove to take too much time
string[] names = new string[ammoDB.entries.Length];
for (int i = 0; i < ammoDB.entries.Length; ++i)
{
names[i] = ammoDB.entries[i].name;
if (ammoDB.entries[i].id == currentID)
currentIdx = i;
}
EditorGUI.BeginChangeCheck();
int idx = EditorGUI.Popup(position, "Ammo Type", currentIdx, names);
if (EditorGUI.EndChangeCheck())
{
property.intValue = ammoDB.entries[idx].id;
}
}
}
}
[CustomEditor(typeof(Weapon))]//自定義編輯器界面
public class WeaponEditor : Editor
{
SerializedProperty m_TriggerTypeProp;
SerializedProperty m_WeaponTypeProp;
SerializedProperty m_FireRateProp;
SerializedProperty m_ReloadTimeProp;
SerializedProperty m_ClipSizeProp;
SerializedProperty m_DamageProp;
SerializedProperty m_AmmoTypeProp;
SerializedProperty m_ProjectilePrefabProp;
SerializedProperty m_ProjectileLaunchForceProp;
SerializedProperty m_EndPointProp;
SerializedProperty m_AdvancedSettingsProp;
SerializedProperty m_FireAnimationClipProp;
SerializedProperty m_ReloadAnimationClipProp;
SerializedProperty m_FireAudioClipProp;
SerializedProperty m_ReloadAudioClipProp;
SerializedProperty m_PrefabRayTrailProp;
SerializedProperty m_AmmoDisplayProp;
void OnEnable()
{
m_TriggerTypeProp = serializedObject.FindProperty("triggerType");
m_WeaponTypeProp = serializedObject.FindProperty("weaponType");
m_FireRateProp = serializedObject.FindProperty("fireRate");
m_ReloadTimeProp = serializedObject.FindProperty("reloadTime");
m_ClipSizeProp = serializedObject.FindProperty("clipSize");
m_DamageProp = serializedObject.FindProperty("damage");
m_AmmoTypeProp = serializedObject.FindProperty("ammoType");
m_ProjectilePrefabProp = serializedObject.FindProperty("projectilePrefab");
m_ProjectileLaunchForceProp = serializedObject.FindProperty("projectileLaunchForce");
m_EndPointProp = serializedObject.FindProperty("EndPoint");
m_AdvancedSettingsProp = serializedObject.FindProperty("advancedSettings");
m_FireAnimationClipProp = serializedObject.FindProperty("FireAnimationClip");
m_ReloadAnimationClipProp = serializedObject.FindProperty("ReloadAnimationClip");
m_FireAudioClipProp = serializedObject.FindProperty("FireAudioClip");
m_ReloadAudioClipProp = serializedObject.FindProperty("ReloadAudioClip");
m_PrefabRayTrailProp = serializedObject.FindProperty("PrefabRayTrail");
m_AmmoDisplayProp = serializedObject.FindProperty("AmmoDisplay");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(m_TriggerTypeProp);
EditorGUILayout.PropertyField(m_WeaponTypeProp);
EditorGUILayout.PropertyField(m_FireRateProp);
EditorGUILayout.PropertyField(m_ReloadTimeProp);
EditorGUILayout.PropertyField(m_ClipSizeProp);
EditorGUILayout.PropertyField(m_DamageProp);
EditorGUILayout.PropertyField(m_AmmoTypeProp);
if (m_WeaponTypeProp.intValue == (int)Weapon.WeaponType.Projectile)
{
EditorGUILayout.PropertyField(m_ProjectilePrefabProp);
EditorGUILayout.PropertyField(m_ProjectileLaunchForceProp);
}
EditorGUILayout.PropertyField(m_EndPointProp);
EditorGUILayout.PropertyField(m_AdvancedSettingsProp, new GUIContent("Advance Settings"), true);
EditorGUILayout.PropertyField(m_FireAnimationClipProp);
EditorGUILayout.PropertyField(m_ReloadAnimationClipProp);
EditorGUILayout.PropertyField(m_FireAudioClipProp);
EditorGUILayout.PropertyField(m_ReloadAudioClipProp);
if (m_WeaponTypeProp.intValue == (int)Weapon.WeaponType.Raycast)
{
EditorGUILayout.PropertyField(m_PrefabRayTrailProp);
}
EditorGUILayout.PropertyField(m_AmmoDisplayProp);
serializedObject.ApplyModifiedProperties();
}
}
#endif
Unity教學項目Ceator Kit:FPS 源代碼學習筆記(二)Weapon類
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.