五月天青色头像情侣网名,国产亚洲av片在线观看18女人,黑人巨茎大战俄罗斯美女,扒下她的小内裤打屁股

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

減壓發(fā)泄之妙方:割草——我們用Unity來做做無雙類游戲

2019-12-19 21:38 作者:皮皮關做游戲  | 我要投稿

作者:四五二十


之前有不少童鞋提了一些建議,希望出一些“節(jié)奏快的3D游戲教程”。

我首先想到的自然是biubiubiu的FPS。

幾十年來一直處在爭議中的“真實FPS視角”

不過FPS類的樣品教程太多了,我想稍微換個口味。

一刀下去一群人升天的無雙類割草游戲出現(xiàn)在了我的腦海里。

按慣例,先把效果演示放在這里:


視頻展示了游戲的主要功能:

1、敵人AI功能

2、主角的攻擊功能

3、技能條的顯示

4、新手引導

接下來不廢話,挨個實現(xiàn)這些功能。

首先是敵人功能,在這里敵人擁有4種主動狀態(tài)(紅色字體),5種被動狀態(tài)(黃色字體):

先創(chuàng)建一個敵人類Enemy,暫時空著,再為敵人做了一個動畫類EnemyAnima:

先創(chuàng)建一個敵人類Enemy,暫時空著,再為敵人做了一個動畫類EnemyAnima:

[HideInInspector]

public AnimatorStateInfo animaState; //動畫狀態(tài)

Animator anim;

[HideInInspector]

public Enemy enemy; //敵人類

void Start()

{

??? anim = GetComponentInChildren<Animator>();

}

void Update()

{

??? //獲取動畫狀態(tài)

??? animaState = anim.GetCurrentAnimatorStateInfo(0);

}

?

然后就是定義一堆播放動畫的方法:

public void SwitchAnimaForDis(float dis) //根據(jù)距離切換動畫狀態(tài)

{

??? anim.SetFloat("State", dis);

}

public void PlayAttackAnim(int attNum) //攻擊動畫,不同參數(shù)播放不同攻擊動畫

{

??? anim.SetInteger("Attack", attNum);

}

public void PlayHitAnim() //被打動畫

{

??? anim.SetTrigger("Hit");

}

public void PlayHitFlyAnim() //被打飛動畫

{

??? anim.SetTrigger("HitFly");

}

public void PlayFallDownAnim() //倒地動畫

{

??? anim.SetTrigger("FallDown");

??? Util.Instance.Delay(1, () => anim.SetTrigger("GitUp"));

}

public void PlayGieUpAnim() //起身動畫

{

??? anim.SetTrigger("GitUp");

}

public void PlayDeathAnim() //死亡動畫

{

??? anim.SetTrigger("Death");

}

?

敵人會根據(jù)與玩家距離主動切換動畫狀態(tài)::

待機動畫==》移動動畫==》戒備動畫

距離小于50開始跑向玩家
距離小于3.5準備攻擊玩家

在Enemy類中的Update中實時判斷與玩家距離:

//判斷和玩家距離,//不同距離播放不同動畫???

float dis = (player.transform.position - transform.position).magnitude;

enemyAnima.SwitchAnimaForDis(dis);

?

發(fā)現(xiàn)玩家后要面向玩家,并移動:


public void LookAtPlayer(Vector3 position) //面向玩家

{

??? Vector3 pos = new Vector3(position.x, transform.position.y, position.z);

??? Vector3 dir = pos - transform.position; //玩家方向

??? transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(dir), Time.deltaTime * 5);

}

public void Run() //前進

{

??? transform.Translate(transform.forward * Time.deltaTime * speed, Space.World);

}

?

進入戒備狀態(tài)就可以隨意攻擊了,在攻擊方法中可以設置攻擊頻率,隨機采用一種攻擊方式:

float attCD = 0;//攻擊冷卻

void AtWillAttack() //隨意攻擊

{

??? attCD += Time.deltaTime;

??? //每兩秒決策一次是否攻擊

??? if (attCD >= 2)

??? {

??????? attCD = 0;

??????? enemyAnima.PlayAttackAnim(Random.Range(0, 5)); //3/5的攻擊概率

??????? Util.Instance.Delay(0.1f, () => enemyAnima.PlayAttackAnim(0));

??? }

}

?

使用UI的Slider控件為敵人做血條,并做成預制體,敵人創(chuàng)建時加載,在Canvas下做一個空物體管理加載出的血條:

float maxHp; //滿血血量

float hp = 100; //當前血量

Slider hpSlider; //血條

void InitHpSlider() //初始化血條

{

??? hpSlider = Instantiate(Resources.Load<Slider>("Prefab/HPSlider"), GameObject.Find("EnemyHps").transform);

??? maxHp = hp;

??? hpSlider.value = hp / maxHp;

}

?

根據(jù)敵人是否在攝像機視錐范圍判斷是否隱藏血條:

//進入鏡頭顯示血條,離開則隱藏

bool onCamera; //是否進入攝像機

void OnBecameVisible()

{

??? onCamera = true;

}

void OnBecameInvisible()

{

??? onCamera = false;

}

?

血條跟隨敵人移動:

RaycastHit hit;

void HideEnemyHpSlider() //隱藏敵人血條

{

??? Vector3 pos = transform.position + Vector3.up * 2.5f; //射線點

??? Physics.Raycast(pos, cameraTH.transform.position - pos, out hit, 100);

??? //當敵人進入視錐且未被遮擋時顯示血條

??? if (hpSlider != null)

??? {

??????? if (hit.collider == cameraTH && onCamera)

??????????? hpSlider.gameObject.SetActive(true);

??????? else

??????????? hpSlider.gameObject.SetActive(false);

??????? hpSlider.transform.position = Camera.main.WorldToScreenPoint(pos); //血條跟隨

??? }

}

?

敵人被打時會受傷,受傷調用受傷方法,沒血就調用死亡方法:

void Damage(float damage) //受傷

{

??? if (hp > damage)

??????? hp -= damage;

??? else

??? {

??????? hp = 0;

??????? Death();

??????? ShowKillCount();

??? }

??? if (hpSlider != null)

??????? hpSlider.value = hp / maxHp;

}

void Death() //死亡方法

{

??? if (!hitFly) //沒有被擊飛時才播放死亡動畫

??????? enemyAnima.PlayDeathAnim();

??? if (hpSlider != null)

??????? Destroy(hpSlider.gameObject);

}

?

敵人的主要功能就是以上這些,然后用了一個敵人系統(tǒng)EnemySystem刷新敵人,保證地圖上的敵人數(shù)量,在敵人腳本中定義一個靜態(tài)變量記錄敵人數(shù)量,創(chuàng)建時增加,死亡時減少:

public static int total = 0;

?

將EnemySystem掛在一個空物體上,把空物體在地圖上方,在一定范圍內隨機刷新敵人:


public class EnemySystem : MonoBehaviour

{

??? [SerializeField]

??? private GameObject enemy;

??? [SerializeField]

??? private int enemyCount;

??? void Update()

??? {

??????? if (Enemy.total < enemyCount)

??????????? CreateEnemy();

??? }

??? RaycastHit hit;

??? void CreateEnemy()

??? {

??????? transform.position = new Vector3(Random.Range(-65, 56), transform.position.y, Random.Range(-65, 71));

??????? if (Physics.Raycast(transform.position, -transform.up, out hit, 100))

??????? {

??????????? Debug.DrawRay(transform.position, hit.point - transform.position, Color.red);

??????????? if (hit.collider.tag == "Ground")

??????????? {

??????????????? Instantiate(enemy, hit.point, Quaternion.identity);

??????????????? Enemy.total++;

??????????? }

??????? }

??? }

}

?

然后我們來看看玩家的功能:

創(chuàng)建一個動畫類,掛在動畫模型上:

public class PlayerAnima : MonoBehaviour

{

??? Animator anim;

??? [HideInInspector]

??? public AnimatorStateInfo animaState; //動畫狀態(tài)

??? [SerializeField]

??? private float attRange; //攻擊范圍

??? [SerializeField]

??? private float angle; //扇形射線角度范圍

??? [SerializeField]

??? private float attRange1; //攻擊范圍

??? [HideInInspector]

??? public Player player;

??? [HideInInspector]

??? public int doubleHit; //連擊計數(shù)

??? void Start()

??? {

??????? anim = GetComponent<Animator>();

??? }

??? void Update()

??? {

??????? //待機動畫和跑步動畫以外的動畫播放完后自動返回待機動畫

??????? animaState = anim.GetCurrentAnimatorStateInfo(0);

??????? if (!animaState.IsName("idle") && !animaState.IsName("run") && animaState.normalizedTime > 1.0f)

??????? {

??????????? doubleHit = 0;

??????????? anim.SetInteger("AttNumber", doubleHit);

??????????? player.attackTrail.SetActive(false);

??????? }

??????? //關閉攻擊特效

??????? if (!animaState.IsName("attack3"))

??????????? player.attack3_1.Stop();

??????? //ShowCheckRange(Color.green, attRange, angle);

??????? //ShowCheckRange(Color.red, attRange1);

??? }

}

?

然后定義玩家的動畫播放功能,然后在控制類中調用就行了:


public void PlayRunAnima(bool run) //跑動動畫

{

??? anim.SetBool("Run", run);

}

public void PlayHitAnima() //被打動畫

{

??? anim.SetTrigger("Hit");

}

public void PlayDeathAnima() //死亡動畫

{

??? anim.SetTrigger("Death");

}

public void PlayAttackAnima() //連擊動畫

{

??? switch (doubleHit)

??? {

??????? case 0:

??????????? anim.SetInteger("AttNumber", ++doubleHit);

??????????? break;

??????? case 1:

??????????? if (animaState.IsName("attack1") && animaState.normalizedTime > 0.6f && animaState.normalizedTime < 0.9f)

??????????????? anim.SetInteger("AttNumber", ++doubleHit);

??????????? break;

??????? case 2:

??????????? if (animaState.IsName("attack2") && animaState.normalizedTime > 0.6f && animaState.normalizedTime < 0.9f)

??????????????? anim.SetInteger("AttNumber", ++doubleHit);

??????????? break;

??? }

}

public void PlaySkillAnima() //技能動畫

{

??? anim.SetTrigger("Skill");

}

public void PlayBigSkillAnima() //大技能動畫

{

??? anim.SetTrigger("BigSkill");

}

?

攻擊敵人的方法使用了動畫幀事件,在攻擊動畫播放到一定時候發(fā)射扇形射線檢測一定范圍內的敵人,被檢測到則會受到攻擊。

玩家的狀態(tài)條使用了三個Slider搭建,在這里分別代表血量和真氣以及怒氣,真氣可以用來釋放小技能,通過攻擊敵人增加,怒氣可以釋放大技能,被敵人攻擊時增加(讀者也可以嘗試其他不同的模式):

創(chuàng)建一個玩家數(shù)據(jù)類對數(shù)據(jù)進行管理:

public class PlayerData

{

??? private static PlayerData _Instanc;

??? public static PlayerData Instanc //單例

??? {

??????? get

??????? {

??????????? if (_Instanc == null)

??????????????? _Instanc = new PlayerData();

???????? ???return _Instanc;

??????? }

??? }

??? public float maxHp = 100; //最大血量

??? public float hp = 100; //當前血量

??? public void SubHp(float _hp, Slider slider) //減血

??? {

??????? if (hp > _hp)

??????????? hp -= _hp;

??????? else

??????????? hp = 0;

??????? slider.value = hp / maxHp; //顯示到界面

??? }

?

??? public float maxGas = 100; //最大真氣

??? public float gas = 0; //當前真氣

??? public void AddGas(float _gas, Slider slider) //加真氣

??? {

??????? if (gas + _gas < maxGas)

??????????? gas += _gas;

??????? else

??????????? gas = maxGas;

??????? slider.value = gas / maxGas;

??? }

??? public void SubGas(float _gas, Slider slider) //減真氣

??? {

??????? gas -= _gas;

??????? slider.value = gas / maxGas;

??? }

?

??? public float maxAnger = 100; //最大怒氣

??? public float anger = 0; //當前怒氣

??? public void AddAnger(float _anger, Slider slider) //加怒氣

??? {

??????? if (anger + _anger < maxAnger)

??????????? anger += _anger;

??????? else

??????????? anger = maxAnger;

??????? slider.value = anger / maxAnger;

??? }

??? public void SubAnger(Slider slider) //減怒氣

??? {

??????? anger = 0;

??????? slider.value = anger / maxAnger;

??? }

}

?

我們采用角色控制器來控制玩家。創(chuàng)建玩家類,先定義一些要用到的屬性,并對狀態(tài)進行初始化:


public class Player : MonoBehaviour

{

??? [SerializeField]

??? private Slider hpSlider, gasSlider, angerSlider; //狀態(tài)條

??? [SerializeField]

??? private Animator hpAnima, angerAnima; //狀態(tài)條動畫

?

??? [HideInInspector]

??? public PlayerAnima playerAnima;

??? [SerializeField]

??? private float speed;

??? [SerializeField]

??? private Transform cameraTh; //攝像機?

??? [SerializeField]

??? private Transform muzzle; //槍口

??? [SerializeField]

??? private GameObject bullet; //子彈

??? [SerializeField]

??? private float gasCost; //技能消耗

??? [SerializeField]

??? private float commonAttack; //普通攻擊力

??? [SerializeField]

??? private float heavyAttack; //重擊攻擊力

??? [SerializeField]

??? private float skillAttack; //技能攻擊力

??? [SerializeField]

??? private float bigSkillAttack; //大技能攻擊力

?

??? public GameObject runTrail; //移動軌跡

??? public GameObject attackTrail; //刀光軌跡

??? public ParticleSystem attack3_1, attack3_2; //攻擊特效??

??? public ParticleSystem bigSkill1, bigSkill2, bigSkill3; //大技能特效

??? [SerializeField]

??? private GameObject fireImage;

?

??? CharacterController cc;

??? [HideInInspector]

??? public bool superArmor; //霸體狀態(tài)

?

??? bool skill = true; //是否釋放小技能

??? void Start()

??? {

??????? playerAnima = GetComponentInChildren<PlayerAnima>();

??????? playerAnima.player = this;

??????? cc = GetComponent<CharacterController>();

??????? InitState();

??? }

??? void InitState() //初始化狀態(tài)界面

??? {

??????? hpSlider.value = PlayerData.Instanc.hp / PlayerData.Instanc.maxHp;

??????? gasSlider.value = PlayerData.Instanc.gas / PlayerData.Instanc.maxGas;

??????? angerSlider.value = PlayerData.Instanc.anger / PlayerData.Instanc.maxAnger;

??? }

}

?

定義移動方法,用一個攝像機跟隨玩家移動旋轉,移動的前方始終為攝像機指向方向:


float v = 0;

void Run(float x, float z) //移動

{

??? cc.Move(Physics.gravity * Time.deltaTime);

??? //播放跑步動畫

??? playerAnima.PlayRunAnima(x != 0 || z != 0);

??? //攝像機指向方向,進行歸一化消除加速度

??? Vector3 dir = cameraTh.forward * z + cameraTh.right * x;

??? //重力

??? // dir.y -= Time.deltaTime;

??? //只有在跑步動畫時才能移動,顯示移動軌跡

??? if (playerAnima.animaState.IsName("run"))

??? {

??????? runTrail.SetActive(true);

??????? cc.Move(dir.normalized * speed * Time.deltaTime);

??????? //使用transform進行移動時使用

??????? //前進方向為攝像機指向方向(攝像機本地方向轉世界)

??????? //Vector3 runDir = transform.InverseTransformDirection(dir);

??????? //transform.Translate(runDir * Time.deltaTime * speed);

??? }

??? else

??????? runTrail.SetActive(false);

??? //面向移動方向

??? if (dir != Vector3.zero && !superArmor)

??????? transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(dir), Time.deltaTime * 10);

}

?

當我們在攻擊敵人時,使用射線檢測敵人,可以自行調整檢測范圍:

綠色是普通攻擊方位,紅色是大技能攻擊范圍

為了體現(xiàn)出爽快感,攻擊效果需要有普通攻擊和擊飛兩種。

這兩種都需要射線檢測,所以定義一個射線檢測方法,具體的效果可以用委托定義:

//扇形射線檢測敵人

public void _RayCheckEnemy(Action<Collider> action, float _attRange, float _angle = 360)

{

??? //根據(jù)半徑(攻擊長度)獲取周長,如果發(fā)射角度<360則獲取弧長

??? float length = _attRange * 2 * Mathf.PI / (360 / _angle);

??? //長度除以檢測物體的碰撞器直徑得到所需射線數(shù)(這里物體寬度為1,所以不用再除)

??? int rayCount = (int)length;

??? float space = _angle / rayCount; //間隔角度

?

??? List<Collider> enemys = new List<Collider>();

??? //從右往左逆時針發(fā)射射線(扇形射線增加一根射線)

??? for (int i = 0; i < rayCount + Convert.ToInt32(_angle != 360); i++)

??? {

??????? Vector3 dir = Quaternion.AngleAxis(_angle / 2 - space * i, Vector3.up) * transform.forward;

??????? RaycastHit[] hit = Physics.RaycastAll(transform.position + Vector3.up, dir, _attRange, LayerMask.GetMask("Enemy"));

??????? foreach (var item in hit)

??????? {

??????????? if (!enemys.Contains(item.collider))

??????????? {

??????????????? enemys.Add(item.collider);

??????????????? action(item.collider); //具體攻擊效果

??????????? }

??????? }

??? }

}

?

定義具體攻擊效果和擊飛效果:


public void AttackEnemy(Collider item) //攻擊敵人(回調函數(shù))

{

??? //大技能造成更多傷害

??? float damage = superArmor ? bigSkillAttack : commonAttack;

??? item.GetComponent<Enemy>().Hit(damage);

??? PlayerData.Instanc.AddGas(damage / 50, gasSlider);//增加真氣

}

public void HitFlyEnemy(Collider item) //擊飛敵人(回調函數(shù))

{

??? //大技能的擊飛力度不同于普通擊飛,傷害也不同

??? float force = superArmor ? 15 : 10;

??? float damage = superArmor ? bigSkillAttack : heavyAttack;

??? item.GetComponent<Enemy>().HitFly(transform, force, damage);

??? PlayerData.Instanc.AddGas(damage / 100, gasSlider);

}

?

在玩家釋放小技能時,會發(fā)出一個能量球,能量球落地后也可以通過射線檢測周圍敵人,然后擊飛:


//加載特效,2s后銷毀

GameObject effect = Resources.Load<GameObject>("Prefab/Effect/Boom");

effect = Instantiate(effect, transform.position, effect.transform.rotation);

Destroy(effect, 2);

?

Collider[] collids = Physics.OverlapSphere(transform.position, 3, LayerMask.GetMask("Enemy"));

for (int i = 0; i < collids.Length; i++)

{

??? collids[i].GetComponent<Enemy>().HitFly(transform, 5, attack);

}

?

而大技能功能則可以自行定義表現(xiàn)方式。


結語

其實從上面的內容可以看出來:基于現(xiàn)有通用引擎的功能,可以很容易搭建出這類3D動作游戲的基本功能框架。當然,一些進階的系統(tǒng)(如防反等)需要額外單獨設計和實現(xiàn),不過本文對于“想要快速打起來”這樣一個目的來說,大概是已經足夠了。

感興趣的讀者可以在此基礎上進一步演繹和發(fā)揮。

以下是工程鏈接,歡迎查閱:https://github.com/wushupei/GeCao


有意向參與線下游戲開發(fā)學習的童鞋,歡迎訪問:http://www.levelpp.com/

皮皮關的游戲開發(fā)QQ群也歡迎各位強勢插入:869551769

減壓發(fā)泄之妙方:割草——我們用Unity來做做無雙類游戲的評論 (共 條)

分享到微博請遵守國家法律
常德市| 民乐县| 探索| 平度市| 德格县| 綦江县| 阿瓦提县| 清河县| 澳门| 桓台县| 枣庄市| 高青县| 江门市| 调兵山市| 吉木萨尔县| 灵宝市| 宁夏| 游戏| 喜德县| 阿克苏市| 磐安县| 七台河市| 岫岩| 阿城市| 平利县| 瑞昌市| 淮南市| 称多县| 开远市| 高清| 佳木斯市| 闽侯县| 福清市| 托里县| 双辽市| 金平| 罗平县| 湘潭县| 九台市| 勃利县| 阿瓦提县|