主頁 >  其他 > 一款類似“恐龍快打”的 橫版街機格斗游戲 該如何制作?| 一起來學習 順便送原始碼【碼文不易,建議收藏學習】

一款類似“恐龍快打”的 橫版街機格斗游戲 該如何制作?| 一起來學習 順便送原始碼【碼文不易,建議收藏學習】

2021-08-06 09:01:55 其他

  • 📢博客主頁:https://blog.csdn.net/zhangay1998
  • 📢歡迎點贊 👍 收藏 ?留言 📝 如有錯誤敬請指正!
  • 📢本文由 呆呆敲代碼的小Y 原創,首發于 CSDN🙉
  • 📢未來很長,值得我們全力奔赴更美好的生活?

目錄

      • 📢引言
  • 🎬橫版格斗游戲
    • 💫游戲制作思路
    • 🎉開始制作
      • 🏳??🌈第一步:新建專案,匯入資源包
      • 🏳??🌈第二步:游戲場景搭建
      • 🏳??🌈第三步:模型影片設定
      • 🏳??🌈第四步:模型基本移動跳躍等以及相機跟隨等設定
      • 🏳??🌈第五步:攻擊方式設定
      • 🏳??🌈第六步:敵人AI設定
      • 🏳??🌈第七步:血條相關設定
      • 🏳??🌈第八步:道具設定
      • 🏳??🌈最后一步:游戲控制器
    • 🌻游戲展示🌻
    • 🎁資源下載🎁
  • 💬總結


📢引言

  • ??輕拾月光,聽風聲繾綣,如剎時花開,
  • 🌻浩浩九州,一文一墨皆是驕陽~
  • ??最近在上海天天下雨,風還很大!但還是要每天兢兢業業的當一個打工仔呀!
  • 🔓周末窩在家正在想著接下來要發一篇什么文章呢,突然來了一條訊息
  • 🔔叮咚~ 原來是 憨憨的小云兒 發來的訊息~
  • 小云兒👩:小Y哥哥,是不是最近天氣不咋好,你也沒心情制作游戲了呀😳!
  • 小Y(博主):咦~ 是不是你又想玩我制作的游戲了呀!才故意這么說的🙃
  • 小云兒👩:(偷笑)…哈哈哈嗝~ 小YY你變聰明了啊,不像是以前呆頭呆腦的小Y哥哥了😏!
  • 小Y:Emma…我啥時候不聰明了!說吧,你最近又想學哪種型別的游戲了,說出來我聽聽🤪!
  • 小云兒👩:嘿嘿~小Y哥哥,我最近又對恐龍快打那樣的橫版格斗游戲玩法非常感興趣,你能不能😛…
  • 小Y:恐龍快打呀~ 我之前也很喜歡玩!可是要從頭做一款這個游戲確實有點麻煩唉🙄~
  • 小云兒👩:哎呀,小Y哥哥 你最棒啦~ 一定難不住你的對嗎🧐!
  • 小Y:那好伐,那我想想辦法給你整一個出來,我這就開始動手做👊!
  • 小云兒👩:好咧小Y哥哥,那我就慢慢期待了!老規矩!可以動手做,不闊以動腳做哦🍓!
    請添加圖片描述

🎬橫版格斗游戲

  • 今天給大家帶來一款橫版格斗游戲,跟小時候玩過的恐龍快打一個型別😀!
  • 由于最近時間并不是很充裕,所以就在網上尋找一些可以用的素材😂
  • 突然發現了一款插件,這款插件居然完美符合我要做的一個橫版格斗游戲所需的工具😜
  • 插件和本文涉及到的資源在文章最后都會提供原始碼工程的🤪!

簡單游戲效果:

請添加圖片描述
請添加圖片描述


💫游戲制作思路

既然要做一款橫版格斗游戲,那還是老規矩,先來整一下游戲思路😘!

我們先來想一下一個基本的橫版格斗游戲都需要那些操作呢😊~

  • 首先有一個游戲場景那是必然的,還要有一個人物,是我們玩家可以操控的,下面稱他為“主角”!
  • 然后這個場景需要根據玩家的位置,來進行攝像機的調整已達到一個視角的操控!
  • 接下來就是要有敵人,因為對打才是這種游戲玩法的核心所在!
  • 還有攻擊方式,包括拳打腳踢和跳躍等
  • 敵人同樣可以進行這些操作!
  • 還要有一個血條,在邊緣顯示我們的血量和當前打的敵人的血條!
  • 場景中還需要存在一些可互動的游戲物體
  • 比如可以打碎的箱子,可以吃的游戲物品
  • 還可以加一些可以拿起來的武器,比如刀槍棍棒等等!
  • 當然這些都是一個基本的游戲框架做出來的后話了!
  • 這樣一說,感覺這個游戲做起來好像不難的樣子哈,那我們就來的嘗試一下吧
  • 如果遇到問題了,在進行補充說明!

請添加圖片描述


🎉開始制作

首先打開Unity新建一個專案,博主開發這個使用的Unity2017.4.40

如果有小伙伴下載本文中的游戲資源,酌情使用Unity版本哦~

每個細節都寫出來肯定是不現實噠,由于制作程序是在是又臭又長

所以文章中只把關鍵性操作和配置介紹出來,一起加油~


🏳??🌈第一步:新建專案,匯入資源包

  • 前邊說過了,我們現在從頭制作一款游戲,如果模型之類的都要自己做那是不現實的
  • 而且模型這一塊也會有專門的美術相關人員負責
  • 所以我們自己閑暇之余自己做游戲肯定要省去這一步
  • 直接尋找自己適合的資源即可!
  • 所以這里我們匯入一個資源包

新建一個Unity專案,設定名字和對應路徑
在這里插入圖片描述
然后將資源啊包匯入即可!資源包和工程在文章末尾都會提供!

簡單看一下工程中的資源
模型資源
在這里插入圖片描述
影片資源
在這里插入圖片描述
圖片素材
在這里插入圖片描述
聲音資源
在這里插入圖片描述
差不多這些東西就夠我們做一個Demo使用了!


🏳??🌈第二步:游戲場景搭建

  • 那現在一些基本的模型圖片等素材有了,接下來就是需要一個游戲場景了👈!
  • 那是不是要我們自己搭建一個呢😬~
  • 正常的話是這樣沒錯,但是我這么懶又沒有時間,怎么會自己去費工夫搭建場景呢哈哈🤣!
  • 所以我們這里直接拿一個現成的場景來搞一下😝
  • 因為畢竟說到底搭建場景就是一個苦力活,對自身學習也沒有太大的幫助😲~

一起來看一下這個場景吧!

在這里插入圖片描述

在這里插入圖片描述


🏳??🌈第三步:模型影片設定

  • 場景已經有了,那現在就開始給模型配置影片
  • 因為影片片段都是資源包中有了的,所以我們這里只需要創建影片控制器Animator Controller就好啦
  • 看到這有的小伙伴就要說了,啥都有了,那你做的也太簡單了吧!!!
  • 害,這都是多虧了前輩們將框架結構搭建出來了,
  • 我們能做的就是站在巨人的肩膀上,做一些微不足道的打工仔的搬磚活罷了,一切為了學習和生活!

廢話不多說,先來右鍵創建一個Animator Controller,如下圖
在這里插入圖片描述

敵人影片設定

  • 因為主角的影片控制器配置略微有些麻煩,因為主角可執行的影片確實太多了
  • 所以我們這里先從敵人的影片控制器配置開始
  • 創建完Animator Controller以后雙擊打開這個影片控制器

一開始是這樣的,只有這三個影片狀態
在這里插入圖片描述

敵人的影片片段資源路徑為:Enemy - > Animations檔案夾下
在這里插入圖片描述
我們先把待機狀態Idle加進去當做默認狀態,默認是黃色的,直接把影片片段拖到Animator視窗即可
在這里插入圖片描述
然后將敵人的影片片段都拖上去,并簡單的排個序!示例如下
在這里插入圖片描述

  • 當前還只是將影片片段拖上去了,并沒有進行邏輯關系設定

  • 那接下來先配置一下影片狀態,只有先創建完影片狀態

  • 在接下來的影片過度邏輯中才能正確的操作下去!

  • 來看一下設定的影片狀態吧,下圖所示
    在這里插入圖片描述
    其中,GroundHit、Death、Idel、Walk、KnockDown_Up、KnockDown_Down、KnockDown_End等等都是Trigger型別
    Blend和MovementSpeed都是Float型別的,isDead是Bool型別的

  • 為什么要這樣設定不用的型別呢,這里再簡單說一下

  • 因為攻擊、死亡等狀態都是需要執行的時候,執行一次就好,所以采用Trigger型別最合適!

  • 那移動速度這種自然用Float控制比較好,還可以控制

  • 最后還有一個isDead是否死亡肯定用Bool 值判斷最好啦!簡單的一個true和False表示是否死亡!

  • 然后給每個影片片段添加上影片過度引數,還不知道怎樣添加的可以再去仔細看看我這篇影片基礎教程哦

  • Unity零基礎到進階 ??| 近萬字教程 對 Unity 中的 影片系統基礎 全面決議+實戰演練

給對應的影片過渡按照對應的影片狀態添加上影片引數就好了!最終添加完的效果如下
在這里插入圖片描述

主角影片設定
主角的影片片段相比較敵人的就會更多了一些,但是整體思路是一樣的
先來看一下主角都有哪些影片片段
在這里插入圖片描述
影片引數:
在這里插入圖片描述

  • 具體影片控制器配置如下,看起來很復雜,其實就是一個影片之間過渡的引數配置
  • 這里就不挨個來說具體怎樣配置的那些引數了,不知道咋配置的可以直接在原始碼工程里看一下就好啦!
  • 想明白思路之后其實就是一個體力活!
    在這里插入圖片描述

🏳??🌈第四步:模型基本移動跳躍等以及相機跟隨等設定

基本的模型影片配置完了,那接下來就是角色移動的相關問題了
這里的話就要開始寫代碼了,一起來順著思路看一下吧!

  • 這里的話我們采用兩種移動方式
  • 一種是使用鍵盤控制角色移動,另一種則是通過虛擬按鍵來進行移動
  • 下面來看一下掛在主角身上的腳本PlayerMovement
  • 該腳本主要是控制主角的移動跳躍等功能的邏輯撰寫

先來看一下代碼

	[Header("Settings")]
	public float walkSpeed = 3f;
	public float ZSpeed = 1.5f;
	public float JumpForce = 8f;
	public bool AllowDepthJumping;
	public float AirAcceleration = 3f;
	public float AirMaxSpeed = 3f;
	public float rotationSpeed = 15f;
	public float jumpRotationSpeed = 30f;
	public float lookAheadDistance = .2f;
	public float landRecoveryTime = .1f;
	public float landTime = 0;
	public LayerMask CollisionLayer;

這里提供了一些基本的設定資訊,可以在unity面板上看到

	private List<UNITSTATE> MovementStates = new List<UNITSTATE> {
		UNITSTATE.IDLE,
		UNITSTATE.WALK,
		UNITSTATE.JUMPING,
		UNITSTATE.JUMPKICK,
		UNITSTATE.LAND,
	};

然后定義了一個串列存盤了影片的幾種狀態,其中這個UNITSTATE是在另一個腳本中定義的屬性

UnitState腳本內容如下,該腳本主要是用來管控角色的影片狀態

using UnityEngine;

public class UnitState : MonoBehaviour {

	public UNITSTATE currentState = UNITSTATE.IDLE;

	public void SetState(UNITSTATE state){
		currentState = state;
	}
}

public enum UNITSTATE {
	IDLE,
	WALK,
	JUMPING,
	LAND,
	JUMPKICK,
	PUNCH,
	KICK,
	ATTACK,
	DEFEND,
	HIT,
	DEATH,
	THROW,
	PICKUPITEM,
	KNOCKDOWN,
	KNOCKDOWNGROUNDED,
	GROUNDPUNCH,
	GROUNDKICK,
	GROUNDHIT,
	STANDUP,
	USEWEAPON,
};
  • 然后還需要定義一個腳本用來控制Input輸入
  • 那就來創建一個新腳本InputManager
  • 這里面是實際的Input輸入方式控制
  • 加入了鍵盤輸入控制方式自定義
	[Header("Keyboard keys")]
	public KeyCode Left = KeyCode.LeftArrow;
	public KeyCode Right = KeyCode.RightArrow;
	public KeyCode Up = KeyCode.UpArrow;
	public KeyCode Down = KeyCode.DownArrow;
	public KeyCode PunchKey = KeyCode.Z;
	public KeyCode KickKey = KeyCode.X;
	public KeyCode DefendKey = KeyCode.C;
	public KeyCode JumpKey = KeyCode.Space;

	[Header("Joypad keys")]
	public KeyCode JoypadPunch = KeyCode.JoystickButton2;
	public KeyCode JoypadKick = KeyCode.JoystickButton3;
	public KeyCode JoypadDefend = KeyCode.JoystickButton1;
	public KeyCode JoypadJump = KeyCode.JoystickButton0;

具體代碼如下:

//delegates
	public delegate void InputEventHandler(Vector2 dir);
	public static event InputEventHandler onInputEvent;
	public delegate void CombatInputEventHandler(INPUTACTION action);
	public static event CombatInputEventHandler onCombatInputEvent;

	[Space(15)]
	public UIManager _UIManager; //link to the UI manager
	[HideInInspector]
	public Vector2 dir;
	private bool TouchScreenActive;
	public static bool defendKeyDown;

	void Start(){
		_UIManager = GameObject.FindObjectOfType<UIManager>();

		//automatically enable touch controls on IOS or android
		#if UNITY_IOS || UNITY_ANDROID
			UseTouchScreenInput = true;
			UseKeyboardInput = UseJoypadInput = false;
		#endif
	}

	public static void InputEvent(Vector2 dir){
		if( onInputEvent != null) onInputEvent(dir);
	}

	public static void CombatInputEvent(INPUTACTION action){
		if(onCombatInputEvent != null) onCombatInputEvent(action);
	}

	public static void OnDefendButtonPress(bool state){
		defendKeyDown = state;
	}
		
	void Update(){

		//use keyboard
		if (UseKeyboardInput) KeyboardControls();

		//use joypad
		if (UseJoypadInput) JoyPadControls();

		//use touchScreen
		EnableDisableTouchScrn(UseTouchScreenInput);
	}

	void KeyboardControls(){
		
		//vector
		float x = 0f;
	 	float y = 0f;

		if (Input.GetKey (Left)) x = -1f;
		if (Input.GetKey (Right))x = 1f;
		if (Input.GetKey (Up)) y = 1f;
		if (Input.GetKey (Down)) y = -1f;
	
		dir = new Vector2(x,y);
		InputEvent(dir);

		//Combat input
		if(Input.GetKeyDown(PunchKey)){
			CombatInputEvent(INPUTACTION.PUNCH);
		}

		if(Input.GetKeyDown(KickKey)){
			CombatInputEvent(INPUTACTION.KICK);
		}
			
		if(Input.GetKeyDown(JumpKey)){
			CombatInputEvent(INPUTACTION.JUMP);
		}

		defendKeyDown = Input.GetKey(DefendKey);
	}

	void JoyPadControls(){
		float x = Input.GetAxis("Joypad Left-Right");
		float y = Input.GetAxis("Joypad Up-Down");

		dir = new Vector2(x,y);
		InputEvent(dir.normalized);

		if(Input.GetKeyDown(JoypadPunch)){
			CombatInputEvent(INPUTACTION.PUNCH);
		}

		if(Input.GetKeyDown(JoypadKick)){
			CombatInputEvent(INPUTACTION.KICK);
		}

		if(Input.GetKey(JoypadJump)){
			CombatInputEvent(INPUTACTION.JUMP);
		}

		defendKeyDown = Input.GetKey(JoypadDefend);
	}

	//enables or disables the touch screen interface
	public void EnableDisableTouchScrn(bool state){
		InputEvent(dir.normalized);

		if (_UIManager != null) {
			if (state) {
				
				//show touch screen
				if(!TouchScreenActive) {
					_UIManager.ShowMenu ("TouchScreenControls", false);
					TouchScreenActive = true;
				}

			} else {

				//hide touch screen
				if (TouchScreenActive) {
					TouchScreenActive = false;
					_UIManager.CloseMenu ("TouchScreenControls");
				}
			}
		}
	}

	//returns true if the defend key is held down
	public bool isDefendKeyDown(){
		return defendKeyDown;
	}
}

public enum INPUTACTION {
	NONE,
	PUNCH,
	KICK,
	JUMP,
	DEFEND,
	WEAPONATTACK,
}

public enum INPUTTYPE {
	KEYBOARD = 0,	
	JOYPAD = 5,	
	TOUCHSCREEN = 10, 
}
  • 這個腳本中不止自定義了幾種鍵盤輸入方式
  • 而且包括具體的鍵盤輸入的方法呼叫和虛擬鍵盤輸入方法的呼叫
  • 定義了幾個列舉,說明了一下不同輸入要執行的輸出效果!
  • 還可以自由選擇鍵盤輸入或者虛擬按鈕輸入!
    [Header("使用鍵盤輸入")]
    public bool UseKeyboardInput;
    [Header("使用手柄輸入")]
    public bool UseJoypadInput;
    [Header("使用虛擬按鍵輸入")]
    public bool UseTouchScreenInput;

在這里插入圖片描述

相機跟隨
給相機寫一個腳本,掛載到攝像機上即可!
通過控制攝像機的偏移量,讓相機一直保持正面對著角色~

using UnityEngine;

public class CameraFollow : MonoBehaviour {

	public Transform target;
	[Header ("Follow Settings")]
	public float distanceToTarget = 5; // The distance to the target
	public float heightOffset = 5; // the height offset of the camera relative to it's target
	public float viewAngle = 10; //a downwards rotation
	public Vector3 AdditionalOffset; //any additional offset
	public bool FollowZAxis; //enable or disable the camera following the z axis

	[Header ("Damp Settings")]
	public float DampX = 3f;
	public float DampY = 3f;
	public float DampZ = 3f;

	[Header ("View Area")]
	public float MinLeft;
	public float MaxRight;

	[Header ("Wave Area collider")]
	public bool UseWaveAreaCollider;
	public BoxCollider CurrentAreaCollider;
	public float AreaColliderViewOffset;

	void Start(){

		//set player as follow target
		if (!target) SetPlayerAsTarget();

		//set camera start position
		if (target) {
			Vector3 playerPos = target.transform.position;
			transform.position = new Vector3(playerPos.x, playerPos.y - heightOffset, playerPos.z + (distanceToTarget));
		}
	}

	void Update () {
		if (target){

			//initial values
			float currentX = transform.position.x;
			float currentY = transform.position.y;
			float currentZ = transform.position.z;
			Vector3 playerPos = target.transform.position;

			//Damp X
			currentX = Mathf.Lerp(currentX, playerPos.x, DampX * Time.deltaTime);

			//DampY
			currentY = Mathf.Lerp(currentY, playerPos.y - heightOffset, DampY * Time.deltaTime);

			//DampZ
			if (FollowZAxis) { 
				currentZ = Mathf.Lerp (currentZ, playerPos.z + distanceToTarget, DampZ * Time.deltaTime);
			} else {
				currentZ = distanceToTarget;
			}

			//Set cam position
			if(CurrentAreaCollider == null) UseWaveAreaCollider = false;
			if (!UseWaveAreaCollider) {
				transform.position = new Vector3 (Mathf.Clamp (currentX, MaxRight, MinLeft), currentY, currentZ) + AdditionalOffset;
			} else {
				transform.position = new Vector3 (Mathf.Clamp (currentX, CurrentAreaCollider.transform.position.x + AreaColliderViewOffset, MinLeft), currentY, currentZ) + AdditionalOffset;
			}

			//Set cam rotation
			transform.rotation = new Quaternion(0,180f,viewAngle,0);
		}
	}

	void SetPlayerAsTarget(){
		GameObject player = GameObject.FindGameObjectWithTag ("Player");
		if (player != null) {
			target = player.transform;
		}
	}
}

這樣的話,一個角色的基本移動和攝像機基礎跟隨移動就實作啦!
請添加圖片描述

@@基本移動展示圖:只有場景和一個人物@@


🏳??🌈第五步:攻擊方式設定

那現在場景角色都有啦,包括角色移動角色影片也已經配置好啦!
接下來就是關于攻擊部分的內容了,我們需要對攻擊這一塊進行配置

  • 現在我們攻擊影片是已經配置好了的,但是還缺少對應的攻擊部分邏輯
  • 這里需要一個新的腳本PlayerCombat用于戰斗時的控制
  • 值得一提的是這里的攻擊判定是基于檢測碰撞判定之后才能進行攻擊和傷害等判定的!

輸入戰斗事件代碼:

	#region 輸入戰斗事件
	//combat input event
	private void CombatInputEvent(INPUTACTION action) {
		if (AttackStates.Contains (playerState.currentState) && !isDead) {

			//挑選物品
			if (action == INPUTACTION.PUNCH && itemInRange != null && isGrounded && currentWeapon == null) {
				interactWithItem();
				return;
			}

			//使用武器攻擊
			if (action == INPUTACTION.PUNCH && isGrounded && currentWeapon != null) {
				useCurrentWeapon();
				return;
			}

			//地面攻擊
			if (action == INPUTACTION.PUNCH && (playerState.currentState != UNITSTATE.PUNCH && NearbyEnemyDown()) && isGrounded) {
				if(GroundPunchData.animTrigger.Length > 0) doAttack(GroundPunchData, UNITSTATE.GROUNDPUNCH, INPUTACTION.PUNCH);
				return;
			}

			//切換到其他組合鍵時重置組合(用戶設定)
			if (resetComboChainOnChangeCombo && (action != lastAttackInput)){
				attackNum = -1;
			}

			//默認拳頭攻擊
			if (action == INPUTACTION.PUNCH && playerState.currentState != UNITSTATE.PUNCH && playerState.currentState != UNITSTATE.KICK && isGrounded) {

				//如果時間在組合視窗內,則繼續下一次攻擊,連續攻擊判定
				bool insideComboWindow = (lastAttack != null && (Time.time < (lastAttackTime + lastAttack.duration + lastAttack.comboResetTime)));
				if (insideComboWindow && !continuePunchCombo && (attackNum < PunchCombo.Length -1)) {
					attackNum += 1;
				} else {
					attackNum = 0;
				}

				if(PunchCombo[attackNum] != null && PunchCombo[attackNum].animTrigger.Length > 0) doAttack(PunchCombo[attackNum], UNITSTATE.PUNCH, INPUTACTION.PUNCH);
				return;
			}
				
			//如果在打孔攻擊中按下“攻擊”,則推進攻擊組合
			if (action == INPUTACTION.PUNCH && (playerState.currentState == UNITSTATE.PUNCH) && !continuePunchCombo && isGrounded) {
				if (attackNum < PunchCombo.Length - 1){
					continuePunchCombo = true;
					continueKickCombo = false;
					return;
				}
			}

			//跳躍拳頭攻擊
			if (action == INPUTACTION.PUNCH && !isGrounded) {
				if(JumpKickData.animTrigger.Length > 0) {	
					doAttack(JumpKickData, UNITSTATE.JUMPKICK, INPUTACTION.KICK);
					StartCoroutine(JumpKickInProgress());
				}
				return;
			}

			//跳躍腿踢
			if (action == INPUTACTION.KICK && !isGrounded) {
				if(JumpKickData.animTrigger.Length > 0) {
					doAttack(JumpKickData, UNITSTATE.JUMPKICK, INPUTACTION.KICK);
					StartCoroutine(JumpKickInProgress());
				}
				return;
			}

			//后踢
			if (action == INPUTACTION.KICK && (playerState.currentState != UNITSTATE.KICK && NearbyEnemyDown()) && isGrounded) {
				if(GroundKickData.animTrigger.Length > 0) doAttack(GroundKickData, UNITSTATE.GROUNDKICK, INPUTACTION.KICK);
				return;
			}

			//默認腿踢
			if (action == INPUTACTION.KICK && playerState.currentState != UNITSTATE.KICK && playerState.currentState != UNITSTATE.PUNCH && isGrounded) {

				//continue to the next attack if the time is inside the combo window
				bool insideComboWindow = (lastAttack != null && (Time.time < (lastAttackTime + lastAttack.duration + lastAttack.comboResetTime)));
				if (insideComboWindow && !continueKickCombo && (attackNum < KickCombo.Length -1)) {
					attackNum += 1;
				} else {
					attackNum = 0;
				}

				doAttack(KickCombo[attackNum], UNITSTATE.KICK, INPUTACTION.KICK);
				return;
			}
				
			//如果在踢腿攻擊中按下“踢腿”,則推進踢腿組合,連續踢腿判定
			if (action == INPUTACTION.KICK && (playerState.currentState == UNITSTATE.KICK) && !continueKickCombo && isGrounded) {
				if (attackNum < KickCombo.Length - 1){
					continueKickCombo = true;
					continuePunchCombo = false;
					return;
				}
			}

		}
	}
  • 這一塊的戰斗事件代碼中包含了各種攻擊方式的判定
  • 包括普通攻擊,跳躍攻擊等等邏輯
  • 然后在挨打的角色身上進行受傷影片扣血等處理即可!

受傷執行的影片和操作代碼

	public void Hit(DamageObject d) {

		//看看我們是否能再次被擊中,判定是否連續受傷
		if(Time.time < LastHitTime + hitThreshold) return;

		//check if we are in a hittable state
		if (HitableStates.Contains (playerState.currentState)) {
			CancelInvoke();
			 
			//相機抖動處理
			CamShake camShake = Camera.main.GetComponent<CamShake>();
			if (camShake != null) camShake.Shake(.1f);

			//防御判定
			if(playerState.currentState == UNITSTATE.DEFEND && !d.DefenceOverride && (isFacingTarget(d.inflictor) || blockAttacksFromBehind)) {
				Defend(d);
				return;
			} else {
				animator.SetAnimatorBool("Defend", false);	
			}
				
			//我們被擊中了
			UpdateHitCounter ();
			LastHitTime = Time.time;

			//顯示命中效果
			animator.ShowHitEffect ();

			//受傷后扣血處理
			HealthSystem hs = GetComponent<HealthSystem> ();
			if (hs != null) {
				hs.SubstractHealth (d.damage);
				if (hs.CurrentHp == 0)
					return;
			}

			//檢查是否擊倒
			if ((hitKnockDownCount >= knockdownHitCount || !IsGrounded() || d.knockDown) && playerState.currentState != UNITSTATE.KNOCKDOWN) {
				hitKnockDownCount = 0;
				StopCoroutine ("KnockDownSequence");
				StartCoroutine ("KnockDownSequence", d.inflictor);
				GlobalAudioPlayer.PlaySFXAtPosition (d.hitSFX, transform.position + Vector3.up);
				GlobalAudioPlayer.PlaySFXAtPosition (knockdownVoiceSFX, transform.position + Vector3.up);
				return;
			}

			//默認命中后受傷
			int i = Random.Range (1, 3);
			animator.SetAnimatorTrigger ("Hit" + i);
			SetVelocity(Vector3.zero);
			playerState.SetState (UNITSTATE.HIT);

			//增加一點沖擊力
			if (isFacingTarget(d.inflictor)) { 
				animator.AddForce (-1.5f);
			} else {
				animator.AddForce (1.5f);
			}

			//特技效果
			GlobalAudioPlayer.PlaySFXAtPosition (d.hitSFX, transform.position + Vector3.up);
			GlobalAudioPlayer.PlaySFXAtPosition (hitVoiceSFX, transform.position + Vector3.up);

			Invoke("Ready", hitRecoveryTime);
		}
	}
  • 因為命中敵人之后,缺少一個直觀的回饋
  • 所以我們可以使用一個簡單的特效,在命中是呼叫一下即可
  • 上面的腳本中在受傷時已經用到了,下面來簡單說一下這個基礎影片類UnitAnimator
  • 這個腳本中也包括對所有影片使用時的一個呼叫工具類!

先來看一下UnitAnimator代碼部分

	void Awake() {
		if(animator == null) animator = GetComponent<Animator>();
		isplayer = transform.parent.CompareTag("Player");
		currentDirection = DIRECTION.Right;
	}
	//播放影片
	public void SetAnimatorTrigger(string triggerName) {
		animator.SetTrigger(triggerName);
	}

	//在影片中設定布林值
	public void SetAnimatorBool(string name, bool state){
		animator.SetBool(name, state);
	}

	//在影片中設定浮點值
	public void SetAnimatorFloat(string name, float value){
		animator.SetFloat (name, value);
	}

	//確定方向
	public void SetDirection(DIRECTION dir){
		currentDirection = dir;
	}
	//顯示命中效果
	public void ShowHitEffect() {
		float unitHeight = 1.6f;
		GameObject.Instantiate (HitEffect, transform.position+Vector3.up * unitHeight, Quaternion.identity);
	}

	//顯示防御效果
	public void ShowDefendEffect() {
		GameObject.Instantiate(DefendEffect, transform.position + Vector3.up * 1.3f, Quaternion.identity);
	}

	//顯示塵埃效果
	public void ShowDustEffectLand() {
		GameObject.Instantiate (DustEffectLand, transform.position + Vector3.up * .13f , Quaternion.identity);
	}

	//顯示跳起特效
	public void ShowDustEffectJump() {
		GameObject.Instantiate (DustEffectJump, transform.position + Vector3.up * .13f , Quaternion.identity);
	}

	//播放音頻
	public void PlaySFX(string sfxName) {
		GlobalAudioPlayer.PlaySFXAtPosition(sfxName, transform.position + Vector3.up);
	}

	//增加一個小的前進力
	public void AddForce(float force) {
		StartCoroutine (AddForceCoroutine(force));
	}

	//隨著時間的推移,會增加的力逐漸變小
	IEnumerator AddForceCoroutine(float force) {
		DIRECTION startDir = currentDirection;
		Rigidbody rb = transform.parent.GetComponent<Rigidbody>();
		float speed = 8f;
		float t = 0;

		while (t < 1) {
			yield return new WaitForFixedUpdate();
			rb.velocity = Vector2.right * (int)startDir * Mathf.Lerp (force, rb.velocity.y, MathUtilities.Sinerp (0, 1, t));
			t += Time.fixedDeltaTime * speed;
			yield return null;
		}
	}

	//閃爍特效
	public IEnumerator FlickerCoroutine(float delayBeforeStart){
		yield return new WaitForSeconds (delayBeforeStart);

		//查找此游戲物件中的所有渲染器
		Renderer[] CharRenderers = GetComponentsInChildren<Renderer>();

		if(CharRenderers.Length > 0) {
			float t = 0;
			while(t < 1) {
				float speed = Mathf.Lerp(15, 35, MathUtilities.Coserp(0, 1, t));
				float i = Mathf.Sin(Time.time * speed);
				foreach(Renderer r in CharRenderers)
					r.enabled = i > 0;
				t += Time.deltaTime / 2;
				yield return null;
			}
			foreach(Renderer r in CharRenderers)
				r.enabled = false;
		}
		Destroy(transform.parent.gameObject);
	}
  • 這個腳本中的內容可以在播放所有影片時使用
  • 也包括上文提到的命中敵人、防御攻擊等等的一個簡單特效釋放處理!
  • 這樣的話我們在攻擊、防御等等動作釋放后都能得到一個視覺反饋效果,能有一個更好的體驗

🏳??🌈第六步:敵人AI設定

到這一步了,我們的基本游戲行為(比如移動、攻擊和防御等等)都已經設定完畢
那現在就需要加入敵人AI了,畢竟只有加了這一塊這個游戲才有可玩性~

  • 我們來思考一下AI都需要干那些事情
  • 首先,敵人AI應該一開始是保持默認狀態的,在發現玩家后開始向著玩家移動
  • 并且等到攻擊距離足夠時會進行攻擊
  • 然后就是對攻擊的處理,比如和何時攻擊,攻擊方式等等,,,

那就先創建一個腳本EnemyActions用于對敵人AI的一些基礎屬性控制

部分屬性代碼如下

[Header ("組件")]
	public GameObject target; //當前目標
	public UnitAnimator animator; //影片組件
    public GameObject GFX; //GFX of this unit 本單元的GFX
    public Rigidbody rb; //剛體組件
    public CapsuleCollider capsule; //碰撞體

	[Header("攻擊資料")]
	public DamageObject[] AttackList; //攻擊串列
    public bool PickRandomAttack; //從串列中選擇一個隨機攻擊
    public float hitZRange = 2; //所有攻擊的z范圍
    public float defendChance = 0; //來襲攻擊被防御的幾率%
    public float hitRecoveryTime = .4f; //攻擊后敵人可以采取行動之前的超時時間
    public float standUpTime = 1.1f; //這個敵人站起來的時間
    public bool canDefendDuringAttack; //如果敵人在進行自己的攻擊時能夠防御來襲的攻擊,則為真
    public bool AttackPlayerAirborne; //當玩家在空中時攻擊他
    private DamageObject lastAttack; //上次發生的攻擊的資料
    private int AttackCounter = 0; //當前攻擊數
    public bool canHitEnemies; //這個敵人可以攻擊其他敵人
    public bool canHitDestroyableObjects; //敵人可以擊中諸如板條箱、桶之類的可摧毀物體,
    [HideInInspector]
	public float lastAttackTime; //上次攻擊的時間

    [Header ("設定")]
	public bool pickARandomName; //指定一個隨機名稱
    public TextAsset enemyNamesList; //這個敵人的名字串列
    public string enemyName = ""; //這個敵人的名字
    public float attackRangeDistance = 1.4f; //距離敵人能夠攻擊的目標的距離
    public float closeRangeDistance = 2f; //近距離與目標的距離
    public float midRangeDistance = 3f; //中程距離目標的距離
    public float farRangeDistance = 4.5f; //遠程距離目標的距離
    public float RangeMarging = 1f; //在我們重新定位自己之前,玩家和敵人之間允許的空間量
    public float walkSpeed = 1.95f; //行走的速度
    public float walkBackwardSpeed = 1.2f; //向后走的速度
    public float sightDistance = 10f; //我們能看到目標的距離
    public float attackInterval = 1.2f; //進攻之間的時間
    public float rotationSpeed = 15f; //切換方向時的轉速
    public float lookaheadDistance; //我們檢查周圍障礙物的距離
    public bool ignoreCliffs; //忽略懸崖探測
    public float KnockdownTimeout = 0f; //我們被擊倒后站起來之前的時間
    public float KnockdownUpForce = 5f; //擊倒的力量
    public float KnockbackForce = 4; //擊倒的水平力
    private LayerMask HitLayerMask; //可損壞物體的圖層標記
    public LayerMask CollisionLayer; //我們檢查碰撞的層
    public bool randomizeValues = true; //隨機化值以避免敵人同步
    [HideInInspector]
	public float zSpreadMultiplier = 2f; //敵人之間z距離的倍增層

    [Header ("狀態")]
	public RANGE range;
	public ENEMYTACTIC enemyTactic;
	public UNITSTATE enemyState;
	public DIRECTION currentDirection; 
	public bool targetSpotted;
	public bool cliffSpotted;
	public bool wallspotted;
	public bool isGrounded;
	public bool isDead;
	private Vector3 moveDirection;
	public float distance;
	private Vector3 fixedVelocity;
	private bool updateVelocity;

    //敵人無法移動的狀態串列
    private List<UNITSTATE> NoMovementStates = new List<UNITSTATE> {
		UNITSTATE.DEATH,
		UNITSTATE.ATTACK,
		UNITSTATE.DEFEND,
		UNITSTATE.GROUNDHIT,
		UNITSTATE.HIT,
		UNITSTATE.IDLE,
		UNITSTATE.KNOCKDOWNGROUNDED,
		UNITSTATE.STANDUP,
	};

    //玩家可能被擊中的狀態串列
    private List<UNITSTATE> HitableStates = new List<UNITSTATE> {
		UNITSTATE.ATTACK,
		UNITSTATE.DEFEND,
		UNITSTATE.HIT,
		UNITSTATE.IDLE,
		UNITSTATE.KICK,
		UNITSTATE.PUNCH,
		UNITSTATE.STANDUP,
		UNITSTATE.WALK,
		UNITSTATE.KNOCKDOWNGROUNDED,
	};

	[HideInInspector]
	public float ZSpread; //z軸上敵人之間的距離

    public Vector3 distanceToTarget;

	private List<UNITSTATE> defendableStates = new List<UNITSTATE> { UNITSTATE.IDLE, UNITSTATE.WALK, UNITSTATE.DEFEND }; //敵人能夠防御來襲攻擊的狀態串列
    //敵人的全域事件處理程式
    public delegate void UnitEventHandler(GameObject Unit);

    //用于銷毀單位的全域事件處理程式
    public static event UnitEventHandler OnUnitDestroy;
  • 上述部分代碼主要是對于敵人AI的一些基本屬性定義
  • 下面看一下敵人被擊中的時候一些邏輯處理
 #region 我們被擊中了

    //單位被擊中
    public void Hit(DamageObject d){
		if(HitableStates.Contains(enemyState)) {

            //只有當我們被擊倒時,才允許地面攻擊擊中我們
            if (enemyState == UNITSTATE.KNOCKDOWNGROUNDED && !d.isGroundAttack) return;

			CancelInvoke();
			StopAllCoroutines();
			animator.StopAllCoroutines();
			Move(Vector3.zero, 0f);

            //增加攻擊超時,使敵人在命中后不能立即攻擊
            lastAttackTime = Time.time;

            //當它準備就緒時,不要擊中這個單元
            if ((enemyState == UNITSTATE.KNOCKDOWNGROUNDED || enemyState == UNITSTATE.GROUNDHIT) && !d.isGroundAttack)
				return;

            //防御來襲
            if (!d.DefenceOverride && defendableStates.Contains(enemyState)) {
				int rand = Random.Range(0, 100);
				if(rand < defendChance) {
					Defend();
					return;
				}
			}

            //擊中sfx
            GlobalAudioPlayer.PlaySFXAtPosition(d.hitSFX, transform.position);

            //擊中粒子效應
            ShowHitEffectAtPosition(new Vector3(transform.position.x, d.inflictor.transform.position.y + d.collHeight, transform.position.z));

            //相機抖動
            CamShake camShake = Camera.main.GetComponent<CamShake>();
			if(camShake != null)
				camShake.Shake(.1f);

            //啟動慢鏡頭
            if (d.slowMotionEffect) {
				CamSlowMotionDelay cmd = Camera.main.GetComponent<CamSlowMotionDelay>();
				if(cmd != null)
					cmd.StartSlowMotionDelay(.2f);
			}

            //減除血量
            HealthSystem hs = GetComponent<HealthSystem>();
			if(hs != null) {
				hs.SubstractHealth(d.damage);
				if(hs.CurrentHp == 0)
					return;
			}

			//地面攻擊
			if(enemyState == UNITSTATE.KNOCKDOWNGROUNDED) {
				StopAllCoroutines();
				enemyState = UNITSTATE.GROUNDHIT;
				StartCoroutine(GroundHit());
				return;
			}

            //轉向來襲的攻擊方向
            int dir = d.inflictor.transform.position.x > transform.position.x? 1 : -1;
			TurnToDir((DIRECTION)dir);

            //檢查是否擊倒
            if (d.knockDown) {
				StartCoroutine(KnockDownSequence(d.inflictor));
				return;

			} else {

                //默認命中
                int rand = Random.Range(1, 3);
				animator.SetAnimatorTrigger("Hit" + rand);
				enemyState = UNITSTATE.HIT;

                //從沖擊中增加小的力
                LookAtTarget(d.inflictor.transform);
				animator.AddForce(-KnockbackForce);

                //攻擊時將敵方狀態從被動切換為主動
                if (enemyTactic != ENEMYTACTIC.ENGAGE) {
					EnemyManager.setAgressive(gameObject);
				}

				Invoke("Ready", hitRecoveryTime);
				return;
			}
		}
	}

	//防御
	void Defend(){
		enemyState = UNITSTATE.DEFEND;
		animator.ShowDefendEffect();
		animator.SetAnimatorTrigger ("Defend");
		GlobalAudioPlayer.PlaySFX ("DefendHit");
		animator.SetDirection (currentDirection);
	}
    #endregion
  • 上面這一塊代碼則是對AI被擊中時做出的一些邏輯處理
  • 包括防御、扣血、倒地、命中特效和相機抖動等等相關方法

然后我們在新建一個腳本EnemyAI
上一個腳本EnemyActions是一個基礎類,用于控制敵人AI的一些相關邏輯
那這個EnemyAI腳本就是最終要掛載到每個敵人身上的,用于更細致的對每個AI的處理

來看一下EnemyAI內的關鍵代碼

	void Update(){

        //當沒有目標或AI被禁用時,不執行任何操作
        if (target == null || !enableAI) {
			Ready ();
			return;

		} else {

            //到達目標的距離
            range = GetDistanceToTarget ();
		}
			
		if(!isDead && enableAI){
			if(ActiveAIStates.Contains(enemyState) && targetSpotted) {

                //AI 活動
                AI();

			} else {

                //試著找出那個玩家
                if (distanceToTarget.magnitude < sightDistance) targetSpotted = true;
			}
		}
	}

	void AI(){
		LookAtTarget(target.transform);
		if (range == RANGE.ATTACKRANGE){

            //attack the target 攻擊目標
            if (!cliffSpotted){
				if (Time.time - lastAttackTime > attackInterval) {
					ATTACK();
				} else {
					Ready();
				}
				return;
			}

            //actions for ATTACKRANGE distance 攻擊距離的動作
            if (enemyTactic == ENEMYTACTIC.KEEPCLOSEDISTANCE) WalkTo(closeRangeDistance, 0f);
			if (enemyTactic == ENEMYTACTIC.KEEPMEDIUMDISTANCE) WalkTo(midRangeDistance, RangeMarging);
			if (enemyTactic == ENEMYTACTIC.KEEPFARDISTANCE) WalkTo(farRangeDistance, RangeMarging);
			if (enemyTactic == ENEMYTACTIC.STANDSTILL) Ready ();

		} else {

            //actions for CLOSERANGE, MIDRANGE & FARRANGE distances 近距離、中距離和遠距離的行動
            if (enemyTactic == ENEMYTACTIC.ENGAGE) WalkTo (attackRangeDistance, 0f);
			if (enemyTactic == ENEMYTACTIC.KEEPCLOSEDISTANCE) WalkTo(closeRangeDistance, RangeMarging);
			if (enemyTactic == ENEMYTACTIC.KEEPMEDIUMDISTANCE) WalkTo(midRangeDistance, RangeMarging);
			if (enemyTactic == ENEMYTACTIC.KEEPFARDISTANCE) WalkTo(farRangeDistance, RangeMarging);
			if (enemyTactic == ENEMYTACTIC.STANDSTILL) Ready();
		}
	}

    //更新當前活動范圍
    private RANGE GetDistanceToTarget(){
		if (target != null) {

            //獲得距離目標的距離
            distanceToTarget = target.transform.position - transform.position;
			distance = Vector3.Distance (target.transform.position, transform.position);

			float distX = Mathf.Abs(distanceToTarget.x);
			float distZ = Mathf.Abs(distanceToTarget.z);

            //攻擊范圍
            if (distX <= attackRangeDistance){
				if(distZ < (hitZRange/2f)) 
					return RANGE.ATTACKRANGE;
				else
					return RANGE.CLOSERANGE;
			}

            //近距離
            if (distX > attackRangeDistance && distX < midRangeDistance) return RANGE.CLOSERANGE;

			//中距離
			if(distX > closeRangeDistance && distance < farRangeDistance) return RANGE.MIDRANGE;

			//遠距離
			if(distX > farRangeDistance) return RANGE.FARRANGE;

		}
		return RANGE.FARRANGE;
	}

    //設定敵人的戰術
    public void SetEnemyTactic(ENEMYTACTIC tactic){
		enemyTactic = tactic;
	}

    //將敵人分散在z距離內
    void SetZSpread(){
		ZSpread = (ZSpread - (float)(EnemyManager.enemyList.Count - 1) / 2f) * (capsule.radius*2) * zSpreadMultiplier;
		if (ZSpread > attackRangeDistance) ZSpread = attackRangeDistance - 0.1f;
	}

    //單位已經死亡
    void Death(){
		StopAllCoroutines();
		CancelInvoke();

		enableAI = false;
		isDead = true;
		animator.SetAnimatorBool("isDead", true);
		Move(Vector3.zero, 0);
		EnemyManager.RemoveEnemyFromList(gameObject);
		gameObject.layer = LayerMask.NameToLayer ("Default");

        //地面死亡
        if (enemyState == UNITSTATE.KNOCKDOWNGROUNDED) {
			StartCoroutine(GroundHit());
		} else {

            //正常死亡
            animator.SetAnimatorTrigger("Death");
		}

		GlobalAudioPlayer.PlaySFXAtPosition("EnemyDeath", transform.position);
		StartCoroutine (animator.FlickerCoroutine(2));
		enemyState = UNITSTATE.DEATH;
		DestroyUnit();
	}
  • 該腳本中對敵人AI的行動做了一個處理
  • 包括攻擊串列發現敵人、尋找目標、進行攻擊和死亡等所做的一個邏輯!

然后還有一個生命值控制腳本HealthSystem

腳本代碼如下,里面只是寫了一些生命值的減少等對HP生命值的管理

	public int MaxHp = 20;
	public int CurrentHp = 20;
	public bool invulnerable;
	public delegate void OnHealthChange(float percentage, GameObject GO);
	public static event OnHealthChange onHealthChange;

	void Start(){
		SendUpdateEvent();
	}

    //減除生命值
    public void SubstractHealth(int damage){
		if(!invulnerable){

            //降低hp
            CurrentHp = Mathf.Clamp(CurrentHp -= damage, 0, MaxHp);

            //生命值達到0
            if (isDead()) gameObject.SendMessage("Death", SendMessageOptions.DontRequireReceiver);
		}

        //更新生命事件
        SendUpdateEvent();
	}

    //增加生命值
    public void AddHealth(int amount){
		CurrentHp = Mathf.Clamp(CurrentHp += amount, 0, MaxHp);
		SendUpdateEvent();
	}

    //健康更新事件
    void SendUpdateEvent(){
		float CurrentHealthPercentage = 1f/MaxHp * CurrentHp;
		if(onHealthChange != null) onHealthChange(CurrentHealthPercentage, gameObject);
	}

	//死亡
	bool isDead(){
		return CurrentHp == 0;
	}

最后來看一下敵人預制體身上的面板屬性,碰撞體和剛體,還有一個扣血和AI腳本
在這里插入圖片描述


🏳??🌈第七步:血條相關設定

終于到這一步啦,上面的步驟差不多就是將整個思路和邏輯
那現在就對血條UI部分進行撰寫

  • 血條分為兩個部分,一個是玩家的血條變化

  • 另一個是當前攻擊敵人的UI部分

  • 那就先來整一下玩家的血條UI,這一塊其實就很簡單啦

  • 首先使用Slider當做一個血條

  • 然后使用工程中的資源將UI所需的圖片對應換上就好啦!

  • 用一個腳本HealthBar來執行UI血條的管理,在相應的受傷方法執行時呼叫即可!

	void UpdateHealth(float percentage, GameObject go){
		if(isPlayer && go.CompareTag("Player")){
			HpSlider.value = percentage;
		} 	

		if(!isPlayer && go.CompareTag("Enemy")){
			HpSlider.gameObject.SetActive(true);
			HpSlider.value = percentage;
			nameField.text = go.GetComponent<EnemyActions>().enemyName;
			if(percentage == 0) Invoke("HideOnDestroy", 2);
		}
	}

敵人的血條部分就要稍微復雜一些啦

  • 需要我們從當前的攻擊串列中拿到正在攻擊的敵人屬性資訊,獲取到當前敵人的血量,然后顯示到血條UI上面~
  • 但是扣血的邏輯和玩家的一樣,就是多了一個死亡和不在玩家的攻擊范圍時就取消敵人血量UI,其余的與玩家無差別!

簡單看一下效果圖
在這里插入圖片描述


🏳??🌈第八步:道具設定

  • 這一步我們在游戲中加入幾個游戲道具,提供撿起和攻擊等效果
  • 我們需要在游戲場景的某個位置擺放好對應的道具
  • 這里提供了匕首和幾個可以打碎的箱子!
  • 先來寫一下匕首所需的腳本WeaponPickup,用于撿起物品的相關內容判定!

看一下代碼


public class WeaponPickup : MonoBehaviour {

	[Header("武器設定")]
	public Weapon weapon;

	[Header("拾取設定")]
	public string SFX = "";
	public GameObject pickupEffect;
	public float pickUpRange = 1;

	private GameObject[] Players;
	private GameObject playerinRange;

	void Start(){
		Players = GameObject.FindGameObjectsWithTag("Player");
	}

    //檢查此專案是否在拾取范圍內
    void LateUpdate(){
		foreach(GameObject player in Players) {
			if(player) {
				float distanceToPlayer = Vector3.Distance(player.transform.position, transform.position);

                //拾取范圍內的專案
                if (distanceToPlayer < pickUpRange && playerinRange == null) {
					playerinRange = player;
					player.SendMessage("ItemInRange", gameObject, SendMessageOptions.DontRequireReceiver);
					return;

				}

                //專案超出拾取范圍
                if (distanceToPlayer > pickUpRange && playerinRange != null) {
					player.SendMessage("ItemOutOfRange", gameObject, SendMessageOptions.DontRequireReceiver);
					playerinRange = null;
				}
			}
		}
	}

    //拿起這個東西
    public void OnPickup(GameObject player){

        //顯示拾取效果
        if (pickupEffect) {
			GameObject effect = GameObject.Instantiate (pickupEffect);
			effect.transform.position = transform.position;
		}

		//play sfx
		if(SFX != null) GlobalAudioPlayer.PlaySFX(SFX);

        //將武器交給玩家
        GiveWeaponToPlayer(player);

		//remove pickup
		Destroy(gameObject);
	}

	public void GiveWeaponToPlayer(GameObject player){
		PlayerCombat pc = player.GetComponent<PlayerCombat>();
		if(pc) pc.equipWeapon(weapon);
	}

	#if UNITY_EDITOR 
	//顯示拾取范圍
	void OnDrawGizmos(){
		Gizmos.color = Color.green;
		Gizmos.DrawWireSphere (transform.position, pickUpRange); 
	}
	#endif
}
  • 然后是可互動的物品,箱子和鐵桶
  • 這一塊其實也很簡單,加一個碰撞體,與被受傷時候的檢測一樣
  • 被命中時就銷毀自己并釋放一個特效即可!

看一下代碼

    //這個物體被擊中了
    public void Hit(DamageObject DO){

        //播放命中特效
        if (hitSFX != "") {
			GlobalAudioPlayer.PlaySFXAtPosition (hitSFX, transform.position);
		}

        //引起銷毀游戲物件版本
        if (destroyedGO != null) {
			GameObject BrokenGO = GameObject.Instantiate (destroyedGO);
			BrokenGO.transform.position = transform.position;

            //基于沖擊方向的機會方向
            if (orientToImpactDir && DO.inflictor != null) {
				float dir = Mathf.Sign(DO.inflictor.transform.position.x - transform.position.x);
				BrokenGO.transform.rotation = Quaternion.LookRotation(Vector3.forward * dir);
			}
		}

        //產生一個專案
        if (spawnItem != null) {
			if (Random.Range (0, 100) < spawnChance) {
				GameObject item = GameObject.Instantiate (spawnItem);
				item.transform.position = transform.position;

				//add up force to object
				item.GetComponent<Rigidbody>().velocity = Vector3.up * 8f;
			}
		}

		//銷毀 
		if (destroyOnHit) {
			Destroy(gameObject);
		}
	}

🏳??🌈最后一步:游戲控制器

  • 游戲控制器就是控制這一關的場景中會出現的敵人數量的控制
  • 也包括將所有敵人擊殺后的成功界面和失敗界面控制
  • 這里我們新建一個腳本EnemyManager,用作一個AI管理器,來負責場景中的敵人數量和綜合狀態等等

來看一下代碼

	public static List<GameObject> enemyList = new List<GameObject>(); //當前關卡中敵人的總串列
    public static List<GameObject> enemiesAttackingPlayer = new List<GameObject>(); //當前正在攻擊的敵人串列
    public static List<GameObject> activeEnemies = new List<GameObject>(); //當前處于活動狀態的敵人串列
    //從敵人串列中洗掉一個敵人
    public static void RemoveEnemyFromList( GameObject g ){
		enemyList.Remove(g);
		SetEnemyTactics();
	}
    //設定當前波中每個敵人的戰術
    public static void SetEnemyTactics(){
		getActiveEnemies();
		if(activeEnemies.Count > 0){
			for(int i=0; i<activeEnemies.Count; i++){
				if(i < MaxEnemyAttacking()){
					activeEnemies[i].GetComponent<EnemyAI>().SetEnemyTactic(ENEMYTACTIC.ENGAGE);
				} else {
					activeEnemies[i].GetComponent<EnemyAI>().SetEnemyTactic(ENEMYTACTIC.KEEPMEDIUMDISTANCE);
				}
			}
		}
	}
    //迫使所有敵人使用某種戰術
    public static void ForceEnemyTactic(ENEMYTACTIC tactic){
		getActiveEnemies();
		if(activeEnemies.Count > 0){
			for(int i=0; i<activeEnemies.Count; i++){
				activeEnemies[i].GetComponent<EnemyAI>().SetEnemyTactic(tactic);
			}
		}
	}

    //禁用所有敵人AI
    public static void DisableAllEnemyAIs(){
		getActiveEnemies();
		if(activeEnemies.Count > 0){
			for(int i=0; i<activeEnemies.Count; i++){
				activeEnemies [i].GetComponent<EnemyAI> ().enableAI = false;
			}
		}
	}

    //回傳當前活動的敵人串列
    public static void getActiveEnemies(){
		activeEnemies.Clear();
		foreach(GameObject enemy in enemyList){
			if(enemy != null && enemy.activeSelf)activeEnemies.Add(enemy);
		}
	}

    //玩家已經死了
    public static void PlayerHasDied(){
		DisableAllEnemyAIs();
		enemyList.Clear();
	}

    //回傳一次可以攻擊玩家的最大敵人數(工具/游戲設定)
    static int MaxEnemyAttacking(){
		EnemyWaveSystem EWS = GameObject.FindObjectOfType<EnemyWaveSystem>();
		if(EWS != null) return EWS.MaxAttackers;
		return 3;
	}

    //使敵人處于侵略狀態
    public static void setAgressive(GameObject enemy){
		enemy.GetComponent<EnemyAI>().SetEnemyTactic(ENEMYTACTIC.ENGAGE);

        //使另一個敵人處于被動狀態
        foreach (GameObject e in activeEnemies){
			if (e != enemy) {
				e.GetComponent<EnemyAI>().SetEnemyTactic (ENEMYTACTIC.KEEPMEDIUMDISTANCE);
				return;
			}
		}
	}
  • 還有一個腳本EnemyWaveSystem用于管理當前場景中的敵人數量以及攻擊狀態
  • 主要是負責敵人的波次控制、同時攻擊玩家的最大敵人數、所有敵人都被消滅等等的邏輯部分!

看一下關鍵代碼

public class EnemyWaveSystem : MonoBehaviour {

	public int MaxAttackers = 3; //可以同時攻擊玩家的最大敵人數

    [Header ("敵人波次一覽表")]
	public EnemyWave[] EnemyWaves;
	public int currentWave;

	[Header ("慢動作設定")]
	public bool activateSlowMotionOnLastHit;
	public float effectDuration = 1.5f;

	[Header ("加載關卡完成")]
	public bool loadNewLevel;
	public string levelName;

	void OnEnable(){
		EnemyActions.OnUnitDestroy += onUnitDestroy;
	}

	void OnDisable(){
		EnemyActions.OnUnitDestroy -= onUnitDestroy;
	}

	void Awake(){
		if (enabled) DisableAllEnemies();
	}

	void Start(){
		currentWave = 0;
		UpdateAreaColliders();
		StartNewWave();
	}
    //消滅所有敵人
    void DisableAllEnemies(){
		foreach(EnemyWave wave in EnemyWaves){
			for(int i=0; i<wave.EnemyList.Count; i++){
				if (wave.EnemyList[i] != null){

                    //消滅敵人
                    wave.EnemyList[i].SetActive(false);
				} else {
                    //從串列中洗掉空欄位
                    wave.EnemyList.RemoveAt(i);
				}
			}
			foreach(GameObject g in wave.EnemyList){
				if (g != null) g.SetActive(false);
			}
		}
	}

    //掀起新的敵人浪潮
    public void StartNewWave(){

		//hide UI hand pointer
		HandPointer hp = GameObject.FindObjectOfType<HandPointer>();
		if (hp != null)	hp.DeActivateHandPointer ();

		//activate enemies
		foreach (GameObject g in EnemyWaves[currentWave].EnemyList) {
			if(g!=null)	g.SetActive (true);
		}
		Invoke("SetEnemyTactics", .1f);
	}

    //更新區域碰撞器
    void UpdateAreaColliders(){

        //將當前區域碰撞器切換到觸發器
        if (currentWave > 0) {
			BoxCollider areaCollider = EnemyWaves [currentWave - 1].AreaCollider;
			if (areaCollider != null) {
				areaCollider.enabled = true;
				areaCollider.isTrigger = true;
				AreaColliderTrigger act = areaCollider.gameObject.AddComponent<AreaColliderTrigger> ();
				act.EnemyWaveSystem = this;
			}
		}

        //將下一個碰撞器設定為攝影機區域限制器
        if (EnemyWaves[currentWave].AreaCollider != null) { 
			EnemyWaves[currentWave].AreaCollider.gameObject.SetActive(true);
		}
		CameraFollow cf = GameObject.FindObjectOfType<CameraFollow>();
		if (cf != null)	cf.CurrentAreaCollider = EnemyWaves [currentWave].AreaCollider;

        //顯示用戶界面指標
        HandPointer hp = GameObject.FindObjectOfType<HandPointer>();
		if (hp != null)	hp.ActivateHandPointer ();
	}

    //敵人被消滅了
    void onUnitDestroy(	GameObject g){
		if(EnemyWaves.Length > currentWave){
			EnemyWaves[currentWave].RemoveEnemyFromWave(g);
			if(EnemyWaves[currentWave].waveComplete()){
				currentWave += 1;
				if(!allWavesCompleted()){ 
					UpdateAreaColliders();
				} else{
					StartCoroutine (LevelComplete());
				}
			}
		}
	}
    //如果所有波次都已完成,則為True
    bool allWavesCompleted(){
		int waveCount = EnemyWaves.Length;
		int waveFinished = 0;

		for(int i=0; i<waveCount; i++){
			if(EnemyWaves[i].waveComplete()) waveFinished += 1;
		}

		if(waveCount == waveFinished) 
			return true;
		else 
			return false;
	}

    //更新敵人的戰術
    void SetEnemyTactics(){
		EnemyManager.SetEnemyTactics();
	}
	//通關
	IEnumerator LevelComplete(){

		//activate slow motion effect
		if (activateSlowMotionOnLastHit) {
			CamSlowMotionDelay cmd = Camera.main.GetComponent<CamSlowMotionDelay>();
			if (cmd != null) {
				cmd.StartSlowMotionDelay (effectDuration);
				yield return new WaitForSeconds (effectDuration);
			}
		}

		//Timeout before continuing
		yield return new WaitForSeconds (1f);

		//Fade to black
		UIManager UI = GameObject.FindObjectOfType<UIManager>();
		if (UI != null) {
			UI.UI_fader.Fade (UIFader.FADE.FadeOut, 2f, 0);
			yield return new WaitForSeconds (2f);
		}

        //禁用玩家
        GameObject[] players = GameObject.FindGameObjectsWithTag("Player");
		foreach (GameObject p in players) {
			Destroy(p);
		}

        //進入下一關或在螢屏上顯示游戲
        if (loadNewLevel) {
			if (levelName != "")
				SceneManager.LoadScene (levelName);

		} else {

            //在螢屏上顯示游戲
            if (UI != null) {
				UI.DisableAllScreens ();
				UI.ShowMenu ("LevelComplete");
			}
		}
	}
}
  • 然后還有一個腳本KillBox,這一個就厲害啦
  • 只要有游戲物件碰到就會將其摧毀并重新啟動游戲難度級別
  • 一般來說是玩家將所有敵人消滅后會執行的
  • 因為我們這里只有一個場景,所以你懂的…

看一下代碼

using UnityEngine;
using System.Collections;
using UnityEngine.SceneManagement;

public class KillBox : MonoBehaviour {

	public bool RestartOnPlayerKill;
	public bool RestartOnEnemyKill;

    //摧毀所有進入這個觸發器的東西
    void OnTriggerEnter(Collider coll){

        //玩家死亡時重新啟動關卡
        if (RestartOnPlayerKill && coll.CompareTag("Player")) StartCoroutine(RestartLevel());

        //重新啟動敵方殺戮等級
        if (RestartOnEnemyKill && coll.CompareTag("Enemy")) StartCoroutine(RestartLevel());

        //摧毀游戲物件
        Destroy(coll.gameObject);
	}

    //重新啟動級別
    IEnumerator RestartLevel(){

        //fade to black
        UIManager UI = GameObject.FindObjectOfType<UIManager>();
		if (UI != null) {
			float fadeOutTime = 0.7f;
			UI.UI_fader.Fade (UIFader.FADE.FadeOut, fadeOutTime, 0);
			yield return new WaitForSeconds (fadeOutTime);
		}

		//加載關卡
		SceneManager.LoadScene (SceneManager.GetActiveScene().name);
	}
}

🌻游戲展示🌻

請添加圖片描述

@@ 動圖游戲展示

  • 由于這個錄屏的幀數記憶體太大了,所以動圖觀感實在不咋地,建議直接觀看我錄制的視頻最佳!
  • 真機實際效果比這個還好,可以自行下載原始碼工程體驗哦!!!
    請添加圖片描述

@@ 視頻游戲展示

<iframe id="ztRCOMtc-1628038408228" src="https://live.csdn.net/v/embed/174422" allowfullscreen="true" data-mediaembed="csdn"></iframe>

自制街機格斗游戲效果展示


🎁資源下載🎁

  • 該文章中所提供這個游戲Demo已經 上傳到CSDN
  • 想要原始碼的小伙伴來這里點擊下載即可!

@@ 橫版格斗游戲原始碼下載

  • 資源中有該工程的Unity原始碼打包好的apk,可直接裝到手機試玩!

  • 也可以打開工程在電腦端運行試玩,只是一個Demo,后期可以加劇情之類的東西,隨意操作!

  • 如果積分不夠的小伙伴直接私信我就好!

  • 在此宣告一下,文章中所做的一個游戲Demo并不是博主全部自己開發的

  • 文中一開始匯入的插件中已經有了包括各類預制體和大部分腳本等資源

  • 博主在此基礎上對開發一個橫版格斗游戲做了一個整體的思路和游戲制作程序闡述

  • 也算是對格斗游戲這一塊做一個開發練習,目的是給許多對格斗游戲感興趣的小伙伴一個游戲制作思路!


💬總結

  • 🌻 終于到了總結這一塊啦,小Y耗時一個周才將這一篇博客和工程整理完畢

  • 🌻 每天還要努力的當一個打工仔,只能拿出周末和每天節省出的一些時間來寫這一篇博客

  • 🌻 文中代碼量不算少,但已經是只把重要的一些放上了,實際上的代碼更多,所以請理解啦!

  • 🌻 不知道大家對小Y寫的這一款 橫版格斗游戲 是否還滿意呢!

  • 🌻 寫文章的時候還特意去玩了幾個小時 恐龍快打,不得不說經典游戲還是好玩啊

  • 🌻 恐龍快打的節奏感和游戲體驗真的超級爽!還有好多東西需要學習!

  • 🌻 我是在電腦上玩的,打開這個網址,超級多的小霸王游戲隨便玩!

  • 🌻 直接分享給大家啦!還不趕緊把三連支持+評論奉上!小霸王游戲網址
    在這里插入圖片描述

恐龍快打游戲展示
請添加圖片描述

這里就分享給大家了!也不用下載模擬器,超級舒服,還不點個三連支持博主!!!

  • 那本篇游戲教程就到此結束了,學廢了之后還可以看一下我寫的其他游戲哦~

  • 這個游戲專欄點擊查看游戲專欄以后也會繼續更新游戲的!

  • 本文章也會放進這個游戲專欄里面,感興趣的小伙伴也可以來我這個游戲專欄看一下哦!

  • 里面還有幾個游戲,以后也會繼續更新的,起碼十五個游戲,上不封頂哈哈,早訂閱早學習哦! 等著你!

游戲專欄


  • 復刻皇室戰爭: ??爆肝整整一個周末寫一款類似 皇室戰爭 的 即時戰斗類 游戲Demo!兩萬多字游戲制作程序+決議!【建議收藏學習】
  • 飛機大戰:花一天時間做一個高質量飛機大戰游戲,過萬字Unity完整教程!漂亮學妹看了直呼666!
  • 第一人稱射擊:通宵一晚做出來的一款類似CS的第一人稱射擊游戲Demo!原來做游戲也不是很難,連憨憨學妹都學會了!
  • 炸彈人:回憶童年和小伙伴一起玩過的經典游戲【炸彈人小游戲】制作程序+決議 |收藏起來跟曾經的小伙伴一起夢回童年!
  • 坦克大戰:??Unity ? 小游戲 | 帶你重回童年的經典系列——坦克大戰3D版!
  • 文字游戲:C# 游戲制作 | ?簡易文字小游戲

請添加圖片描述

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/292137.html

標籤:其他

上一篇:c語言基于Easyx實作的貪吃蛇

下一篇:C語言實作小游戲:掃雷

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more