前言
實現(xiàn)FPS槍支不同武器效果,比如手槍,噴子,狙擊槍,機槍,其實我最開始的想法是先做一個基類腳本,寫一些公共屬性和方法,然后再起不同的武器腳本這個基礎(chǔ)基類,實現(xiàn)不同的武器效果。
這樣的實現(xiàn)思路其實是沒什么問題的,直到我看到這個視頻:https://www.youtube.com/watch?v=bqNW08Tac0Y,作者只用一個腳本就實現(xiàn)了不同的武器效果更加方便,下面我就參考一下作者的思路實現(xiàn)一下大致的效果。
順帶說一下,在第一人稱射擊(FPS)游戲中實現(xiàn)子彈射擊效果,可以通過不同的技術(shù)和方法來完成。以下是幾種常見的實現(xiàn)方式:
-
射線投射(Raycasting):
這是最常用的方法之一。射線投射意味著從槍口發(fā)出一個虛擬的射線,并檢測這個射線與游戲世界中的對象之間的交互。如果射線與某個對象相交,那么就可以認(rèn)為子彈擊中了該對象。實現(xiàn)步驟:
- 從玩家的攝像機或槍口位置發(fā)出一條射線。
- 使用物理引擎提供的射線投射功能來檢測射線路徑上的碰撞。
- 如果射線與對象相交,根據(jù)交互結(jié)果執(zhí)行相應(yīng)的邏輯,比如扣除生命值、播放受擊動畫等。
- 在射擊點顯示擊中效果,如粒子效果或貼圖。
-
拋射物模擬(Projectile Simulation):
對于需要模擬子彈飛行軌跡的情況,比如遠(yuǎn)距離狙擊、火箭筒或者拋射武器,可以使用拋射物模擬。實現(xiàn)步驟:
- 創(chuàng)建一個子彈實體,并賦予它初始速度和方向。
- 通過物理引擎模擬子彈的飛行軌跡,考慮重力、空氣阻力等因素。
- 檢測子彈與其他對象的碰撞,并在碰撞發(fā)生時處理相應(yīng)的邏輯。
- 在子彈飛行過程中可以添加軌跡效果,如拖尾。
每種方法都有其適用場景和優(yōu)缺點。射線投射適合快速射擊和近距離交火,拋射物模擬適合遠(yuǎn)距離和弧線射擊。在實際開發(fā)中,這些方法可以組合使用,以達到最佳的效果。
模型素材
不會配置模型可以看我之前的文章,進行下載和配置:
unity中導(dǎo)入下載的3D模型及albedo/baseColor、normal 、AO/Occlus、metallic、roughness貼圖紋理設(shè)置
文章用到的粒子火光特效
https://assetstore.unity.com/packages/vfx/particles/legacy-particle-pack-73777
射擊效果
[Tooltip("是否正在射擊")]
bool shooting;
[Tooltip("是否允許按住射擊")]
public bool allowButtonHold;
[Tooltip("是否可以射擊")]
bool readyToShoot;
[Tooltip("是否在換彈")]
bool reloading;
[Tooltip("彈夾容量")]
public int magazineSize;
[Tooltip("當(dāng)前彈夾容量")]
public int bulletsLeft;
[Tooltip("儲備彈藥容量")]
public int reservedAmmoCapacity = 300;
[Tooltip("當(dāng)前剩余射擊發(fā)射的子彈數(shù)")]
public int bulletsShot;
[Tooltip("槍口火焰特效")]
public ParticleSystem muzzleFlash;
[Tooltip("子彈擊中效果")]
public GameObject bulletHoleGraphic;
[Tooltip("射擊間隔時間")]
public float timeBetweenShooting;
[Tooltip("連發(fā)射擊之間的間隔時間")]
public float timeBetweenShots;
[Tooltip("射擊時的散布度")]
public float spread;
[Tooltip("射擊的最大距離")]
public float range;
[Tooltip("每次射擊發(fā)射的子彈數(shù)")]
public int bulletsPerTap;
[Tooltip("是否允許按住射擊")]
public bool allowButtonHold;
[Tooltip("每次射擊造成的傷害")]
public int damage; // 傷害
public Camera fpsCam;
private void Awake()
{
bulletsLeft = magazineSize;
readyToShoot = true;
}
private void Update()
{
MyInput();
}
private void MyInput()
{
if (allowButtonHold)
shooting = Input.GetKey(KeyCode.Mouse0);
else
shooting = Input.GetKeyDown(KeyCode.Mouse0);
// 射擊
if (readyToShoot && shooting && !reloading && bulletsLeft > 0)
{
bulletsShot = bulletsPerTap;
Shoot();
}
}
private void Shoot()
{
readyToShoot = false;
// 散布
float x = Random.Range(-spread, spread);
float y = Random.Range(-spread, spread);
// 計算帶有散布的射擊方向
Vector3 direction = fpsCam.transform.forward + fpsCam.transform.TransformDirection(new Vector3(x, y, 0));
// 射線檢測
if (Physics.Raycast(fpsCam.transform.position, direction, out RaycastHit rayHit, range))
{
//場景顯示紅線,方便調(diào)試查看
Debug.DrawLine(fpsCam.transform.position, rayHit.point, Color.red, 10f);
Debug.Log(rayHit.collider.name);
muzzleFlash.Play();//槍口火焰/火光
//TODO:相機震動
if (rayHit.collider.CompareTag("Enemy"))
{
Debug.Log("擊中敵人");
Rigidbody rb = rayHit.transform.GetComponent<Rigidbody>();
if (rb != null)
{
rb.constraints = RigidbodyConstraints.None; // 解除剛體約束
rb.AddForce(transform.parent.transform.forward * 500); // 給敵人施加一個力
}
// 擊中敵人特效
//使用 LookRotation() 方法來讓子彈孔特效朝向被擊中表面的法線方向。其中 rayHit.normal 是表示被擊中表面法線方向的向量
var res1 = Instantiate(bulletHoleGraphic, rayHit.point, Quaternion.LookRotation(rayHit.normal));
Destroy(res1, 0.5f);
//TODO:扣血
}
}
bulletsLeft--;
bulletsShot--;
Invoke("ResetShot", timeBetweenShooting);
if (bulletsShot > 0 && bulletsLeft > 0)
Invoke("Shoot", timeBetweenShots);
}
private void ResetShot()
{
readyToShoot = true;
}
換彈
private void MyInput()
{
//。。。
if (Input.GetKeyDown(KeyCode.R) && bulletsLeft < magazineSize && !reloading)
Reload();
}
//換彈
private void Reload()
{
reloading = true;
Invoke("ReloadFinished", reloadTime);
}
private void ReloadFinished()
{
if (reservedAmmoCapacity <= 0) return;
//計算需要填裝的子彈數(shù)=1個彈匣子彈數(shù)-當(dāng)前彈匣子彈數(shù)
int bullectToLoad = magazineSize - bulletsLeft;
//計算備彈需扣除子彈數(shù)
int bullectToReduce = (reservedAmmoCapacity >= bullectToLoad) ? bullectToLoad : reservedAmmoCapacity;
reservedAmmoCapacity -= bullectToReduce;//減少備彈數(shù)
bulletsLeft += bullectToReduce;//當(dāng)前子彈數(shù)增加
bulletsLeft = magazineSize;
reloading = false;
}
瞄準(zhǔn)
private void MyInput()
{
//。。。
//瞄準(zhǔn)
DetermineAim();
}
void DetermineAim()
{
Vector3 target = normalLocalPosition; // 默認(rèn)目標(biāo)位置為正常瞄準(zhǔn)時的本地位置
if (Input.GetMouseButton(1)){
//spread = 0;//瞄準(zhǔn)情況下我們通常可能會讓射擊散步值為0,這個看自己的情況而定
target = aimingLocalPosition; // 如果按下鼠標(biāo)右鍵,目標(biāo)位置為瞄準(zhǔn)時的本地位置
}
Vector3 desiredPosition = Vector3.Lerp(transform.localPosition, target, Time.deltaTime * aimSmoothing); // 使用插值平滑過渡到目標(biāo)位置
transform.localPosition = desiredPosition; // 更新槍支的本地位置
}
效果
開槍抖動效果
如果你的槍模型沒有開槍動畫
的話,這個方法就很方便了
private void Shoot()
{
transform.localPosition -= Vector3.forward * 0.1f; // 后坐力使槍支向后移動
//。。。
}
設(shè)置顯示文本
private void Update()
{
//。。。
SetUI();
}
// 設(shè)置文本
private void SetUI()
{
text.SetText(bulletsLeft + " / " + reservedAmmoCapacity);
}
生成實體子彈
[Header("子彈")]
public float bulletForce = 100f;//子彈的力
public GameObject bulletPrefab;//子彈預(yù)制體
public GameObject BulletShootPoint;//子彈生成點
//實例化一個子彈
GameObject bullet = Instantiate(bulletPrefab, BulletShootPoint.transform.position, BulletShootPoint.transform.rotation);
//給子彈拖尾一個向前的速度力(加上射線打出去的偏移值)
bullet.GetComponent<Rigidbody>().velocity = (BulletShootPoint.transform.forward + direction) * bulletForce;
最終代碼
public class GunSystem : MonoBehaviour
{
public Camera fpsCam;
[Header("槍械狀態(tài)")]
[Tooltip("是否正在射擊")]
bool shooting;
[Tooltip("是否可以射擊")]
bool readyToShoot;
[Tooltip("是否在換彈")]
bool reloading;
[Header("彈夾")]
[Tooltip("彈夾容量")]
public int magazineSize;
[Tooltip("當(dāng)前彈夾容量")]
public int bulletsLeft;
[Tooltip("儲備彈藥容量")]
public int reservedAmmoCapacity = 300;
[Tooltip("當(dāng)前剩余射擊發(fā)射的子彈數(shù)")]
public int bulletsShot;
[Header("射擊")]
[Tooltip("射擊間隔時間")]
public float timeBetweenShooting;
[Tooltip("射擊時的散布度")]
public float spread;
[Tooltip("射擊的最大距離")]
public float range;
[Tooltip("每次射擊發(fā)射的子彈數(shù)")]
public int bulletsPerTap;
[Tooltip("是否允許按住射擊")]
public bool allowButtonHold;
[Tooltip("每次射擊造成的傷害")]
public int damage; // 傷害
[Tooltip("裝填彈藥的時間")]
public float reloadTime;
[Tooltip("連發(fā)射擊之間的間隔時間")]
public float timeBetweenShots;
[Header("瞄準(zhǔn)")]
[Tooltip("正常情況的本地位置")]
public Vector3 normalLocalPosition;
[Tooltip("瞄準(zhǔn)時的本地位置")]
public Vector3 aimingLocalPosition;
[Tooltip("瞄準(zhǔn)過程的平滑度")]
public float aimSmoothing = 10;
[Header("效果")]
[Tooltip("槍口火焰特效")]
public ParticleSystem muzzleFlash;
[Tooltip("子彈擊中效果")]
public GameObject bulletHoleGraphic;
[Header("UI")]
public TextMeshProUGUI text; // 彈藥顯示文本
private void Awake()
{
bulletsLeft = magazineSize;
readyToShoot = true;
}
private void Update()
{
MyInput();
SetUI();
}
// 設(shè)置文本
private void SetUI()
{
text.SetText(bulletsLeft + " / " + reservedAmmoCapacity);
}
private void MyInput()
{
if (allowButtonHold)
shooting = Input.GetKey(KeyCode.Mouse0);
else
shooting = Input.GetKeyDown(KeyCode.Mouse0);
// 射擊
if (readyToShoot && shooting && !reloading && bulletsLeft > 0)
{
bulletsShot = bulletsPerTap;
Shoot();
}
//換彈
if (Input.GetKeyDown(KeyCode.R) && bulletsLeft < magazineSize && !reloading)
Reload();
//瞄準(zhǔn)
DetermineAim();
}
private void Shoot()
{
readyToShoot = false;
transform.localPosition -= Vector3.forward * 0.1f; // 后坐力使槍支向后移動
// 散布
float x = Random.Range(-spread, spread);
float y = Random.Range(-spread, spread);
// 計算帶有散布的射擊方向
Vector3 direction = fpsCam.transform.forward + fpsCam.transform.TransformDirection(new Vector3(x, y, 0));
// 射線檢測
if (Physics.Raycast(fpsCam.transform.position, direction, out RaycastHit rayHit, range))
{
Debug.Log(rayHit.collider.name);
muzzleFlash.Play();//槍口火焰/火光
//相機震動
if (rayHit.collider.CompareTag("Enemy"))
{
//場景顯示紅線,方便調(diào)試查看
Debug.DrawLine(fpsCam.transform.position, rayHit.point, Color.red, 10f);
Debug.Log("擊中敵人");
// Rigidbody rb = rayHit.transform.GetComponent<Rigidbody>();
// if (rb != null)
// {
// rb.constraints = RigidbodyConstraints.None; // 解除剛體約束
// rb.AddForce(transform.parent.transform.forward * 500); // 給敵人施加一個力
// }
// 擊中敵人特效
//使用 LookRotation() 方法來讓子彈孔特效朝向被擊中表面的法線方向。其中 rayHit.normal 是表示被擊中表面法線方向的向量
var res = Instantiate(bulletHoleGraphic, rayHit.point, Quaternion.LookRotation(rayHit.normal));
res.transform.parent = rayHit.transform;//設(shè)置父類
//TODO:扣血
}
}
//實例化一個子彈
GameObject bullet = Instantiate(bulletPrefab, BulletShootPoint.transform.position, BulletShootPoint.transform.rotation);
//給子彈拖尾一個向前的速度力(加上射線打出去的偏移值)
bullet.GetComponent<Rigidbody>().velocity = (BulletShootPoint.transform.forward + direction) * bulletForce;
bulletsLeft--;
bulletsShot--;
Invoke("ResetShot", timeBetweenShooting);
if (bulletsShot > 0 && bulletsLeft > 0)
Invoke("Shoot", timeBetweenShots);
}
void DetermineAim()
{
Vector3 target = normalLocalPosition; // 默認(rèn)目標(biāo)位置為正常瞄準(zhǔn)時的本地位置
if (Input.GetMouseButton(1)) target = aimingLocalPosition; // 如果按下鼠標(biāo)右鍵,目標(biāo)位置為瞄準(zhǔn)時的本地位置
Vector3 desiredPosition = Vector3.Lerp(transform.localPosition, target, Time.deltaTime * aimSmoothing); // 使用插值平滑過渡到目標(biāo)位置
transform.localPosition = desiredPosition; // 更新槍支的本地位置
}
private void ResetShot()
{
readyToShoot = true;
}
//換彈
private void Reload()
{
reloading = true;
Invoke("ReloadFinished", reloadTime);
}
private void ReloadFinished()
{
if (reservedAmmoCapacity <= 0) return;
//計算需要填裝的子彈數(shù)=1個彈匣子彈數(shù)-當(dāng)前彈匣子彈數(shù)
int bullectToLoad = magazineSize - bulletsLeft;
//計算備彈需扣除子彈數(shù)
int bullectToReduce = (reservedAmmoCapacity >= bullectToLoad) ? bullectToLoad : reservedAmmoCapacity;
reservedAmmoCapacity -= bullectToReduce;//減少備彈數(shù)
bulletsLeft += bullectToReduce;//當(dāng)前子彈數(shù)增加
bulletsLeft = magazineSize;
reloading = false;
}
}
不同武器射擊效果
注意
:這里為了方便,我就用一把槍做演示了
1. 手槍
參數(shù)配置
效果
2. 機槍
參數(shù)
效果
3. 狙擊槍
參數(shù),狙擊槍其實和手槍參數(shù)差不多,可以就需要修改射擊間隔時間、換彈時間和傷害
效果
4. 霰彈槍
參數(shù)
效果
5. 加特林
參數(shù)
效果
其他
可以看到其實還有很多功能沒有實現(xiàn),比如后座力或者放大鏡等等效果,這篇文章說的已經(jīng)夠多了,后面我再單獨做其他內(nèi)容的探究吧!
感謝
【視頻】https://www.youtube.com/watch?v=bqNW08Tac0Y
完結(jié)
贈人玫瑰,手有余香!如果文章內(nèi)容對你有所幫助,請不要吝嗇你的點贊評論和關(guān)注
,以便我第一時間收到反饋,你的每一次支持
都是我不斷創(chuàng)作的最大動力。當(dāng)然如果你發(fā)現(xiàn)了文章中存在錯誤
或者有更好的解決方法
,也歡迎評論私信告訴我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net文章來源:http://www.zghlxwxcb.cn/news/detail-765634.html
一位在小公司默默奮斗的開發(fā)者,出于興趣愛好,于是最近才開始自習(xí)unity。如果你遇到任何問題,也歡迎你評論私信找我, 雖然有些問題我可能也不一定會,但是我會查閱各方資料,爭取給出最好的建議,希望可以幫助更多想學(xué)編程的人,共勉~文章來源地址http://www.zghlxwxcb.cn/news/detail-765634.html
到了這里,關(guān)于【unity實戰(zhàn)】一個通用的FPS槍支不同武器射擊控制腳本的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!