練手的第一個(gè)游戲

《計(jì)算機(jī)游戲開發(fā)

課題實(shí)驗(yàn)報(bào)告

名:李基銘

級(jí):2014211602

學(xué)號(hào):2014212123

指導(dǎo)教師:李學(xué)明

北京郵電大學(xué)數(shù)字媒體與設(shè)計(jì)藝術(shù)學(xué)院

一、游戲簡介

《夢游大逃殺》是一款基于UNITY3D開發(fā)制作的第三人稱視角射擊生存類游戲,其游戲背景是一個(gè)畫風(fēng)非??ㄍǖ男∨笥眩趬粲芜^程中拿著假想的槍支和自己的毛絨玩具們展開了逃跑或是追殺(依關(guān)卡難度而定),游戲本身的每一個(gè)關(guān)卡均沒有固定的結(jié)束機(jī)制(例如到達(dá)安全屋或者殺掉足夠數(shù)量的敵人)而是更加類似于FPS游戲中的生存模式,玩家可以根據(jù)關(guān)卡的設(shè)定制定不同的策略與戰(zhàn)術(shù),并在逐漸增多的敵人面前存活盡可能長的時(shí)間。

二、游戲規(guī)則

由于所學(xué)知識(shí)的限制和素材原因,我并不能在短時(shí)間內(nèi)制作出豐富的劇情,但是游戲本身擁有四個(gè)關(guān)卡和開始界面五個(gè)場景素材,且主角人物和敵人均設(shè)定了動(dòng)作動(dòng)畫。游戲內(nèi)所有模型(人物,地形,環(huán)境)均有碰撞檢測,并為地方單位受到攻擊時(shí)添加了粒子效果。游戲本身擁有循環(huán)的背景音樂,且開槍,受到傷害,死亡均設(shè)計(jì)了獨(dú)立音效??紤]到內(nèi)存和模型的原因,在槍支的設(shè)定上并未采用子彈設(shè)定,而是從槍管方向添加了一條很長的ShootRay,在主色調(diào)偏暗的游戲場景內(nèi)也更容易瞄準(zhǔn)。

游戲開始界面如下:

其中Stage Select部分由四個(gè)關(guān)卡代表的按鈕組成,點(diǎn)擊每個(gè)按鈕就可以進(jìn)入該關(guān)卡,如果點(diǎn)擊Start按鈕則直接進(jìn)入第一個(gè)關(guān)卡。此外還在右下角設(shè)置了音量按鈕,可以在游戲開始前調(diào)節(jié)游戲內(nèi)部的音量(0-100)

開始游戲后以第三關(guān)舉例:

游戲視角跟隨持槍的主角,屏幕正上方的是分?jǐn)?shù),其中三種敵人:

僵尸兔子分值20;僵尸小熊分值30,僵尸大象分值80,擊殺難度/造成傷害也隨著分值相應(yīng)不同,在擊殺的時(shí)候處理優(yōu)先級(jí)也根據(jù)游戲關(guān)卡不同而不同。

游戲左下角是生命值,根據(jù)每個(gè)關(guān)卡的難度/機(jī)制而不同,例如第三關(guān)的敵人傷害較高,且人物射速較低,因此生命值為300(相比于第一關(guān)的100和第三關(guān)的500略有不同,但是數(shù)值具體僅經(jīng)過了初步平衡,游戲具體的難度體驗(yàn)因人而異);在人物受到攻擊時(shí)屏幕會(huì)出現(xiàn)紅色,提醒玩家注意規(guī)避:

在生命數(shù)值歸零后,游戲會(huì)結(jié)束,播放玩家死亡音效,敵人停止追逐玩家并且玩家不能繼續(xù)進(jìn)行射擊和移動(dòng),在1秒后進(jìn)入游戲結(jié)束畫面:

玩家再次點(diǎn)擊屏幕后,回到游戲的開始界面進(jìn)行關(guān)卡選擇。

游戲的關(guān)卡設(shè)計(jì)了四關(guān),分別叫Late Night(夜半);Nightmare(噩夢);Dawn(黎明);Polar(極圈);四關(guān)的游戲機(jī)制如下:

夜半是游戲的初始關(guān)卡,難度較低,設(shè)定上人物移動(dòng)速度較快,且槍支的射速較快(對應(yīng)的傷害較低,但總體DPS比較平衡,適合不熟悉控制的人熟悉瞄準(zhǔn)機(jī)制),三種敵人刷新速度也較慢,兩種血量較低的敵人(兔子和熊)刷新時(shí)間為3秒,大象刷新時(shí)間為5秒。此外,對應(yīng)30每發(fā)/每秒10發(fā)的射速,擊殺小型敵人所需時(shí)間很短,因此主要難度是利用地形和走位規(guī)避傷害并盡可能多的擊殺大象。

噩夢和夜半類似,是難度較高的夜半,例如敵人的傷害變高(通過提高攻擊間隔,單次傷害實(shí)現(xiàn)),血量變高,但控制人物的血量沒變,但相應(yīng)提高了槍的傷害(例如擊殺大象原來需要17發(fā),現(xiàn)在需要14發(fā)),相應(yīng)的存活時(shí)間也會(huì)降低,因此難度較高,但同時(shí)人物的射速與單發(fā)傷害與第一關(guān)并無區(qū)別,玩家只需要一直按住左鍵然后向敵人反方向跑就可以了。

黎明的設(shè)計(jì)理念與前兩關(guān)不同,如果說前兩關(guān)的武器是輕型自動(dòng)步槍,這關(guān)的武器則是狙擊槍。單發(fā)傷害提升到170意味著可以單發(fā)擊殺100血量的兔子,200血量的熊需要兩槍,500血量的大象需要三槍。相應(yīng)的,人物的血量也提高為300,同時(shí)射速被削減到了0.5發(fā)每秒。此外,在該關(guān)卡中玩家的移動(dòng)速度被提高到了8.0f(前兩關(guān)為6.0f),可以拉開距離進(jìn)行遠(yuǎn)距離的精確瞄準(zhǔn),由于設(shè)定時(shí)為地形中的障礙設(shè)計(jì)了碰撞檢測(設(shè)計(jì)navigation時(shí)烘培了所有模型),敵人無法穿過的模型子彈也無法穿過,因此關(guān)卡難度也很大。

極圈的設(shè)計(jì)類似于上世紀(jì)的游戲搶灘登陸等的理念,玩家的移動(dòng)速度被極大限制但是射速和傷害被極大提高,在該關(guān)卡中人物移動(dòng)速度僅為1.0f,但射速,單發(fā)傷害極高,對應(yīng)的敵人刷新速度也很快且個(gè)人血量也很高,因此該關(guān)卡下玩家只需要站在原地然后擊退一波又一波的敵人即可。

三.設(shè)計(jì)理念:

1.UI設(shè)計(jì)思路:

游戲本身由兩類場景組成:開始界面和游戲關(guān)卡,其中開始界面的設(shè)計(jì)難點(diǎn)在于UI的布置,不同場景之間的連接和控件功能的實(shí)現(xiàn);游戲關(guān)卡的設(shè)計(jì)難點(diǎn)在于人物動(dòng)作之間的銜接,主角和敵方人物的移動(dòng)控制和武器的命中判定。

游戲的UI為了契合游戲本身的卡通風(fēng)格,因此大部分使用了較為活潑的字體和顏色設(shè)計(jì),例如游戲從標(biāo)題,關(guān)卡選擇到游戲結(jié)束使用的均是這種叫LUCKIEST-GUY的字體,但同時(shí)作為一個(gè)打僵尸的游戲,背景音樂設(shè)定上采用了比較陰森但是較為清澈的打擊樂。

在界面設(shè)計(jì)中,由于游戲本身色調(diào)偏暗,因此背景使用了紫色,但標(biāo)題采用了鮮艷的顏色(手調(diào)的黃色和淺藍(lán)色,不一定好看)

之后為Dream-Walking使用Animation功能添加了動(dòng)畫效果,讓開始界面顯得不那么突兀。此外,為了增加標(biāo)題的層次感,為兩段text分別添加了陰影,經(jīng)過調(diào)整后藍(lán)色字體添加了黑色陰影,黃色字體添加了深黃色陰影,看起來比較像3D制作的標(biāo)題。

同理,在關(guān)卡選擇部分的UI設(shè)計(jì)也為了整體的一致性采用了同樣的字體:

圖標(biāo)本身使用了UI中的button選項(xiàng),并為每一個(gè)按鈕的image組件添加了關(guān)卡的截圖。在設(shè)計(jì)時(shí)考慮到unity的button都是圓角按鈕,為了整體的美觀性想把關(guān)卡按鈕也做成圓角的,但是采用圖片作為按鈕背景時(shí)無論圖片是否是圓角圖片,保存成jpg格式后導(dǎo)入到texture中的結(jié)果都是矩形圖片(圓角圖片導(dǎo)入后會(huì)留下四個(gè)白色的角);查閱資料發(fā)現(xiàn)需要使用shader組件進(jìn)行代碼控制,但在這版本的游戲中還沒有實(shí)現(xiàn)。

UI部分代碼實(shí)現(xiàn)思路:

游戲中用到的UI設(shè)計(jì)主要包含以下三部分:關(guān)卡切換,玩家信息顯示,音量調(diào)節(jié);分別用一段代碼進(jìn)行實(shí)現(xiàn)。

I. 關(guān)卡切換部分:

利用Load Level/Load Scene函數(shù)均可實(shí)現(xiàn),為了避免bug加入了Debug.Log函數(shù);完成代碼后在界面中的Button組件中使用OnBtnClick方法調(diào)用,并將每個(gè)關(guān)卡和代碼關(guān)聯(lián)即可。

關(guān)卡切換部分代碼如下:

publicclassGameStartManager:MonoBehaviour{

publicButtonStartButton;

publicvoidOpenScene00()

{

Debug.Log("Game Started");

Application.LoadLevel("Level 00");

}

publicvoidOpenScene01()

{

Debug.Log("Game Started");

Application.LoadLevel("Level 01");

}

publicvoidOpenScene02()

{

Debug.Log("Game Started");

Application.LoadLevel("Level 02");

}

publicvoidOpenScene03()

{

Debug.Log("Game Started");

Application.LoadLevel("Level 03");

}

publicvoidOpenScene04()

{

Debug.Log("Game Started");

Application.LoadLevel("Level 04");

}

}

II. 關(guān)卡中對人物的信息顯示:

屏幕中主要需要顯示人物的分?jǐn)?shù)和血量,和游戲結(jié)束后的Game Over界面;

分?jǐn)?shù)實(shí)現(xiàn)非常簡單,只需要構(gòu)建一個(gè)Text文件然后設(shè)置每個(gè)敵方單位的得分并相加即可。

分?jǐn)?shù)的代碼實(shí)現(xiàn):

publicstaticintscore;

Texttext;

voidAwake()

{

text = GetComponent ();

score = 0;

}

voidUpdate()

{

text.text ="Score: "+ score;

}

}

III.人物血量的顯示:

血量的代碼實(shí)現(xiàn)稍后人物生命值部分會(huì)講解,只需要將人物血量顯示到已經(jīng)在代碼中聲明過的Health Slider中即可,此處粘貼一下該部分代碼:

publicvoidTakeDamage (intamount)

{

damaged =true;

currentHealth -= amount;

healthSlider.value = currentHealth;

playerAudio.Play ();

if(currentHealth <= 0 && !isDead)

{

Death ();

}

}

IV. 游戲通關(guān)界面:

人物死亡后的Game Over動(dòng)畫的實(shí)現(xiàn)也非常簡單,只需要在Animation中構(gòu)建三部分:藍(lán)色背景的透明-----不透明,分?jǐn)?shù)的高亮(大小變大再變?。┖虶ame Over字樣的顯示。

在Animation中構(gòu)建完成后只需要在代碼中調(diào)用該Animation組件即可。

代碼如下:

Animatoranim;

voidAwake()

{

anim = GetComponent();

}

V. 音量調(diào)節(jié)部分:

需要首先創(chuàng)建一個(gè)Slider,之后向Slider中添加Audio Source組件,并將背景音樂和該組件關(guān)聯(lián)。

之后向該Slider中添加代碼即可。該部分代碼實(shí)現(xiàn)如下:

usingSystem.Collections;

usingSystem.Collections.Generic;

usingUnityEngine;

usingUnityEngine.UI;

publicclassMusicAdministor:MonoBehaviour

{

publicSliderVolume;

publicvoidOnVolume()

{

GetComponent().volume = Volume.value;

}

}

2. 對第一人稱人物的設(shè)計(jì):

I.控制人物的設(shè)計(jì)思路:

從代碼實(shí)現(xiàn)角度講分三部分實(shí)現(xiàn):人物的移動(dòng);人物的傷害判定;人物的射擊判定。

從人物模型的角度講同樣分三部分實(shí)現(xiàn):人物模型/動(dòng)作的關(guān)聯(lián)(Animator組件),人物模型移動(dòng)的判定(對地表模型的烘培,navigation模塊),人物開槍的動(dòng)作設(shè)計(jì)

II.控制人物移動(dòng)的設(shè)計(jì):

人物的移動(dòng)核心代碼如下:

floath =Input.GetAxisRaw("Horizontal");

floatv =Input.GetAxisRaw("Vertical");

voidMove(floath,floatv)

{

movement.Set(h, 0f, v);

movement = movement.normalized * speed *Time.deltaTime;

playerRigidbody.MovePosition(transform.position + movement);

}

首先利用input.getaxisraw方法獲得來自鍵盤/鼠標(biāo)的控制系數(shù),Horizontal和Vertical是獲得鍵盤上下/左右鍵按下時(shí)觸發(fā)的,后面調(diào)用人物旋轉(zhuǎn)時(shí)使用的Mouse X,Mouse Y同理,獲得的是鼠標(biāo)沿著屏幕的橫/縱軸進(jìn)行移動(dòng)時(shí)獲得的長度。

之后設(shè)置人物的移動(dòng)面為X和Z(可以在人物模型的剛體組件內(nèi)進(jìn)行如下修改:

設(shè)置人物可以在XZ軸內(nèi)進(jìn)行平移)

之后對人物的移動(dòng)設(shè)置一個(gè)三維數(shù)組movement,將Y值設(shè)置為0,并在代碼開始定義人物的移動(dòng)速度:public float speed = 6.0f ,便于之后各關(guān)卡對人物的移動(dòng)速度進(jìn)行調(diào)節(jié)。

之后設(shè)置人物的位置為原位置+速度x時(shí)間,并每幀調(diào)用一次該函數(shù)即可。

III.人物的方向控制設(shè)計(jì):

人物的旋轉(zhuǎn)函數(shù)與之類似:

voidTurning()

{

RaycamRay =Camera.main.ScreenPointToRay(Input.mousePosition);

RaycastHitfloorHit;

if(Physics.Raycast (camRay,outfloorHit, camRayLength, floorMask))

{

Vector3playerToMouse = floorHit.point - transform.position;

playerToMouse.y = 0f;

QuaternionnewRotation =Quaternion.LookRotation(playerToMouse);

playerRigidbody.MoveRotation(newRotation);

}

}

獲取人物鼠標(biāo)位置的方法如上,創(chuàng)建三維數(shù)組后定義PlayerToMouse初始位置為玩家位置面向鼠標(biāo)位置,旋轉(zhuǎn)角度的改變量為地面位置-改變位置,同樣每幀調(diào)用一次該方法。

IV.人物的動(dòng)畫銜接設(shè)計(jì):

之后對人物的動(dòng)畫銜接進(jìn)行設(shè)置:首先從Animator窗口對人物動(dòng)作進(jìn)行綁定:

提取到的動(dòng)畫模組有三個(gè):Move(移動(dòng)),Idle(發(fā)呆),Death(死亡)。

之后對Conditions中添加兩個(gè)變量作為三種模組之間的轉(zhuǎn)換:

控制Idle-Move/Move-Idle的判定情況:Is Walking;是Bool型變量,Is True情況下轉(zhuǎn)為Move動(dòng)畫,Is False情況下轉(zhuǎn)為Idle動(dòng)畫。

控制Any State-Death的判定情況:Is Dead;是Trigger型變量,觸發(fā)時(shí)結(jié)束所有當(dāng)前動(dòng)作并進(jìn)入Death動(dòng)畫。

完成調(diào)節(jié)后對時(shí)間軸的切換點(diǎn)進(jìn)行設(shè)置,因?yàn)檫M(jìn)入Idle狀態(tài)需要一點(diǎn)時(shí)間的動(dòng)畫延續(xù)以防止人物動(dòng)作過于僵硬,因此將Move動(dòng)畫稍微延長一段,并勾選Has Exit Time選項(xiàng)。而另外一邊的開始移動(dòng)如果使用Has Exit Time就會(huì)在人物開始移動(dòng)時(shí)位置出現(xiàn)平移而沒有動(dòng)畫效果,所以需要取消掉Has Exit Time選項(xiàng)。

voidAnimating(floath,floatv)

{

boolwalking = h != 0f || v != 0f;

anim.SetBool("IsWalking", walking);

}

利用該代碼判定人物是否在移動(dòng)(通過對水平/垂直平面的坐標(biāo)進(jìn)行判定),并修改Is Walking模組的數(shù)據(jù)。

voidDeath ()

{

isDead =true;

playerShooting.DisableEffects ();

anim.SetTrigger ("Die");

playerAudio.clip = deathClip;

playerAudio.Play ();

playerMovement.enabled =false;

playerShooting.enabled =false;

}

利用該代碼判定人物是否存活(通過人物的血量判斷是否存活,若死亡則停止人物的射擊和移動(dòng)動(dòng)畫),并修改Is Alive模組的數(shù)據(jù)。

V.人物的射擊判定:

因?yàn)槿宋锏纳鋼敉ㄟ^ShootRay思路進(jìn)行實(shí)現(xiàn),所以對開火的設(shè)計(jì)如下:

1.設(shè)置單發(fā)傷害

2.設(shè)置開火間隔

3.設(shè)置射擊有效距離

4.將開火時(shí)的粒子效果/光效/音效在代碼中進(jìn)行定義,并在Unity中進(jìn)行綁定

5.利用代碼設(shè)置:左鍵按下判定開火;若距離上一發(fā)開火時(shí)間>開火間隔則設(shè)計(jì),否則不射擊

6.定義射擊方向:從槍口開始,做一條長度為開火距離,角度為鼠標(biāo)指向/人物角度的射線

7.定義射擊傷害判定:若射線和模型collider發(fā)生碰撞,且目標(biāo)具有生命值(考慮到可能會(huì)射擊到地形/墻面/地形上)則造成傷害

控制射擊部分的代碼如下:

publicintdamagePerShot = 20;

publicfloattimeBetweenBullets = 0.3f;

publicfloatrange = 100f;

floattimer;

RayshootRay =newRay();

RaycastHitshootHit;

intshootableMask;

ParticleSystemgunParticles;

LineRenderergunLine;

AudioSourcegunAudio;

LightgunLight;

floateffectsDisplayTime = 0.2f;

voidAwake()

{

shootableMask =LayerMask.GetMask ("Shootable");

gunParticles = GetComponent ();

gunLine = GetComponent ();

gunAudio = GetComponent();

gunLight = GetComponent ();

}

voidUpdate()

{

timer +=Time.deltaTime;

if(Input.GetButton ("Fire1") && timer >= timeBetweenBullets &&Time.timeScale!=0

{

Shoot ();

}

if(timer >= timeBetweenBullets * effectsDisplayTime)

{

DisableEffects ();

}

}

voidShoot ()

{

timer = 0f;

gunAudio.Play();

gunLight.enabled =true;

gunParticles.Stop ();

gunParticles.Play ();

gunLine.enabled =true;

gunLine.SetPosition (0, transform.position);

shootRay.origin = transform.position;

shootRay.direction = transform.forward;

if(Physics.Raycast (shootRay,outshootHit, range, shootableMask))

{

EnemyHealthenemyHealth = shootHit.collider.GetComponent ();

if(enemyHealth !=null)

{

enemyHealth.TakeDamage (damagePerShot, shootHit.point);

}

gunLine.SetPosition (1, shootHit.point);

}

else

{

gunLine.SetPosition (1, shootRay.origin + shootRay.direction * range);

}

}

}

VI.人物的生命值判定:

控制人物的生命判定如下:

1.設(shè)定人物的生命上限,

2.將人物生命值投射到UI中的slider中,

3.通過生命值對Is Alive(上文中提到的判定模組)進(jìn)行判斷,以對人物的動(dòng)畫進(jìn)行設(shè)置。

4.設(shè)置敵人的傷害參數(shù)(此處只是定義變量,后面設(shè)置敵人的攻擊代碼時(shí)會(huì)填入?yún)?shù))

5.為了傷害效果逼真,在人物受到傷害時(shí)讓屏幕變紅,實(shí)現(xiàn)方法為在游戲關(guān)卡的UI中添加一個(gè)和屏幕一樣大的紅色image,平常透明度為0,若受到傷害則講透明度重置為100%,0.5秒后回復(fù)。該方法中利用color.lerp函數(shù)對圖片的顏色進(jìn)行設(shè)置。

6.設(shè)置人物如果生命值降低至0時(shí),觸發(fā)Death動(dòng)畫,并在0.5秒內(nèi)播放Game Over界面(和受傷的紅色界面類似,需要在外界通過animation和UI進(jìn)行設(shè)計(jì),但是在游戲中只需要調(diào)用觸發(fā)函數(shù))

7.Game Over后調(diào)用Load Scene函數(shù),回到主界面選擇重新開始。

代碼實(shí)現(xiàn)如下:

usingUnityEngine;

usingUnityEngine.UI;

usingSystem.Collections;

usingUnityEngine.SceneManagement;

publicclassPlayerHealth:MonoBehaviour

{

publicintstartingHealth = 100;

publicintcurrentHealth;

publicSliderhealthSlider;

publicImagedamageImage;

publicAudioClipdeathClip;

publicfloatflashSpeed = 5f;

publicColorflashColour =newColor(1f, 0f, 0f, 0.1f);

Animatoranim;

AudioSourceplayerAudio;

PlayerMovementplayerMovement;

PlayerShootingplayerShooting;

boolisDead;

booldamaged;

voidAwake()

{

anim = GetComponent ();

playerAudio = GetComponent ();

playerMovement = GetComponent ();

playerShooting = GetComponentInChildren ();

currentHealth = startingHealth;

}

voidUpdate()

{

if(damaged)

{

damageImage.color = flashColour;

}

else

{

damageImage.color =Color.Lerp (damageImage.color,Color.clear, flashSpeed *Time.deltaTime);

}

damaged =false;

}

publicvoidTakeDamage (intamount)

{

damaged =true;

currentHealth -= amount;

healthSlider.value = currentHealth;

playerAudio.Play ();

if(currentHealth <= 0 && !isDead)

{

Death ();

}

}

voidDeath ()

{

isDead =true;

playerShooting.DisableEffects ();

anim.SetTrigger ("Die");

playerAudio.clip = deathClip;

playerAudio.Play ();

playerMovement.enabled =false;

playerShooting.enabled =false;

}

publicvoidRestartLevel ()

{

SceneManager.LoadScene(0);

}

}

關(guān)卡結(jié)束部分代碼:

voidUpdate()

{

if(playerHealth.currentHealth <= 0)

{

anim.SetTrigger("GameOver");

restartTimer +=Time.deltaTime;

if(restartTimer >= restartDelay)

{

Application.LoadLevel(Application.loadedLevel);

}

}

}

3.敵方單位設(shè)計(jì)思路:

和控制人物類似,對敵方單位的設(shè)計(jì)也從以下方面著手:敵人血量,敵人移動(dòng),敵人傷害,和敵人的動(dòng)畫構(gòu)建,和人物不同的是由于動(dòng)畫素材限制,敵人沒有遠(yuǎn)程攻擊(只有近戰(zhàn),對應(yīng)的也利用Animator進(jìn)行不同動(dòng)畫的關(guān)聯(lián)),并且敵人需要設(shè)置Navigation組件以智能的尋找玩家位置。

I.對敵人移動(dòng)方式的設(shè)計(jì):

1.設(shè)置敵人的基本移動(dòng)速度;

2.將敵人的位置命名為一個(gè)三維數(shù)組;并定義敵人的新位置為原位置+速度x Delta.time,每幀執(zhí)行一次。

3.為敵人設(shè)計(jì)碰撞檢測;以便后面對敵人的攻擊玩家/地形碰撞進(jìn)行處理;

4.判定玩家的血量和敵人本身的血量,若血量>0則利用nav函數(shù)表達(dá)敵人的方向?yàn)槊嫦蛲婕椅恢谩?/p>

敵人移動(dòng)的代碼實(shí)現(xiàn)如下:

usingUnityEngine;

usingSystem.Collections;

usingUnityEngine.AI;

publicclassEnemyMovement:MonoBehaviour

{

publicfloatenemySpeed = 100.0f;

Transformplayer;

PlayerHealthplayerHealth;

EnemyHealthenemyHealth;

NavMeshAgentnav;

Vector3enemyMovement;

RigidbodyenemyRigidbody;

voidAwake()

{

player =GameObject.FindGameObjectWithTag("Player").transform;

playerHealth = player.GetComponent();

enemyHealth = GetComponent();

nav = GetComponent();

enemyRigidbody = GetComponent();

}

voidMove(floath,floatv)

{

enemyMovement.Set(h, 0f, v);

enemyMovement = enemyMovement.normalized * enemySpeed *Time.deltaTime;

enemyRigidbody.MovePosition(transform.position + enemyMovement);

}

voidUpdate()

{

if(enemyHealth.currentHealth > 0 && playerHealth.currentHealth > 0)

{

nav.SetDestination(player.position);

}

else

{

nav.enabled =false;

}

}

}

II. 對敵人的血量設(shè)計(jì):

1.設(shè)置敵人的初始血量(因敵人的不同而異),并設(shè)計(jì)擊殺后尸體消失的時(shí)間和增加的分值

2.定義敵人遭到攻擊時(shí)的粒子特效,聲效。

3.設(shè)置尸體消失的動(dòng)畫:由于Animation中可以看出敵人死后是平躺的,所以不需要加入旋轉(zhuǎn)函數(shù),只需要在Z軸上對敵人尸體進(jìn)行平移即可,Z坐標(biāo)為原坐標(biāo)+尸體消失速度x Delta Time,每幀調(diào)用一次。

4.設(shè)置敵人遭到攻擊(在人物開槍的代碼中定義過傷害判定,此處只需要定義血量削減函數(shù)并通過get component調(diào)用受傷時(shí)的粒子特效和音效即可。

敵人血量的代碼設(shè)計(jì)如下:

usingUnityEngine;

usingUnityEngine.Events;

publicclassEnemyHealth:MonoBehaviour

{

publicintstartingHealth = 100;

publicintcurrentHealth;

publicfloatsinkSpeed = 2.5f;

publicintscoreValue = 10;

publicAudioClipdeathClip;

Animatoranim;

AudioSourceenemyAudio;

ParticleSystemhitParticles;

CapsuleCollidercapsuleCollider;

boolisDead;

boolisSinking;

voidAwake()

{

anim = GetComponent ();

enemyAudio = GetComponent ();

hitParticles = GetComponentInChildren ();

capsuleCollider = GetComponent ();

currentHealth = startingHealth;

}

voidUpdate()

{

if(isSinking)

{

transform.Translate (-Vector3.up * sinkSpeed *Time.deltaTime);

}

}

publicvoidTakeDamage (intamount,Vector3hitPoint)

{

if(isDead)

return;

enemyAudio.Play ();

currentHealth -= amount;

hitParticles.transform.position = hitPoint;

hitParticles.Play();

if(currentHealth <= 0)

{

Death ();

}

}

publicvoidStartSinking ()

{

GetComponent ().enabled =false;

GetComponent ().isKinematic =true;

isSinking =true;

ScoreManager.score += scoreValue;

Destroy (gameObject, 2f);

}

}

此外,和人物死亡動(dòng)畫類似,敵人死亡也需要調(diào)用Death----Idle的Animator模組,此處也是定義一個(gè)Trigger變量Death,和人物死亡一樣----取消Exit Time并在代碼中調(diào)用該段動(dòng)畫。

敵人的animator如下:

三.對敵人的攻擊設(shè)計(jì)

敵人對玩家的攻擊動(dòng)作和人物射擊敵人的設(shè)計(jì)方式類似,步驟如下:

1.設(shè)置敵人的攻擊間隔和攻擊傷害(作為全局public變量,便于拖入敵人的prefab后修改數(shù)值,以改變敵人的難度)

2.調(diào)用玩家人物,作為敵人的移動(dòng)方向和攻擊目標(biāo)(此處同樣調(diào)用人物的血量以判定人物是否存活)

3.進(jìn)行碰撞檢測(此處的碰撞檢測和敵人與地形碰撞檢測并不一樣,此處是判定敵人的攻擊區(qū)域內(nèi)是否存在玩家----若存在則運(yùn)行攻擊玩家的方法)

4.判定玩家人物的血量,若血量在0+且進(jìn)入了敵人的攻擊范圍則進(jìn)行一次攻擊判定(并開始計(jì)算敵人的攻擊動(dòng)作的冷卻時(shí)間)

攻擊設(shè)計(jì)的代碼實(shí)現(xiàn)如下:

usingUnityEngine;

usingSystem.Collections;

publicclassEnemyAttack:MonoBehaviour

{

publicfloattimeBetweenAttack = 0.5f;

publicintattackDamage = 10;

Animatoranim;

GameObjectplayer;

PlayerHealthplayerHealth;

EnemyHealthenemyHealth;

boolplayerInRange;

floattimer;

voidAwake()

{

player =GameObject.FindGameObjectWithTag ("Player");

playerHealth = player.GetComponent ();

enemyHealth = GetComponent();

anim = GetComponent ();

}

voidOnTriggerEnter(Colliderother)

{

if(other.gameObject == player)

{

playerInRange =true;

}

}

voidOnTriggerExit(Colliderother)

{

if(other.gameObject == player)

{

playerInRange =false;

}

}

voidUpdate()

{

timer +=Time.deltaTime;

if(timer >= timeBetweenAttack && playerInRange && enemyHealth.currentHealth > 0)

{

Attack ();

}

if(playerHealth.currentHealth <= 0)

{

anim.SetTrigger ("PlayerDead");

}

}

voidAttack ()

{

timer = 0f;

if(playerHealth.currentHealth > 0)

{

playerHealth.TakeDamage (attackDamage);

}

}

}

四.設(shè)計(jì)步驟:

由于游戲本身內(nèi)容復(fù)雜,為了較為形象的解釋該游戲從空場景到可以構(gòu)建為成熟場景的過程,在此處簡要的解釋一下該游戲設(shè)計(jì)過程中的主要步驟:

1.構(gòu)建一個(gè)游戲場景,并導(dǎo)入下載好的環(huán)境prefab文件(此處包含燈光設(shè)計(jì))

2.導(dǎo)入玩家的人物模型,并在Animator中導(dǎo)入人物的不同動(dòng)畫,并創(chuàng)建變量來規(guī)范動(dòng)作間的變換觸發(fā)點(diǎn)和持續(xù)時(shí)間

3.對人物模型進(jìn)行優(yōu)化,首先對人物的槍支進(jìn)行設(shè)計(jì),加入開火的特效,并添加腳本對開火行為進(jìn)行構(gòu)建

槍支的Inspector界面最后如下圖:

4.對人物的移動(dòng)進(jìn)行設(shè)計(jì),其中包括鏡頭的跟隨,人物的移動(dòng)旋轉(zhuǎn)和移動(dòng)動(dòng)畫的觸發(fā),并在代碼中對各個(gè)功能進(jìn)行實(shí)現(xiàn)

人物本身的Animator最后如下:

人物本身的Inspector最后如下:

5.完成對玩家控制人物的調(diào)試后,對敵人的角色進(jìn)行設(shè)計(jì);首先將敵人的模型導(dǎo)入到游戲中;

6.對敵人的Animator進(jìn)行控制,將各種動(dòng)畫通過添加變量的方式進(jìn)行關(guān)聯(lián),并在代碼中對各個(gè)變量的作用時(shí)間分別進(jìn)行定義。

敵人的Animator窗口如下:

7.對敵人的移動(dòng)方式進(jìn)行控制,通過代碼實(shí)現(xiàn)敵人的移動(dòng)和攻擊,并設(shè)定敵人對地形的碰撞體積和對玩家進(jìn)行攻擊的碰撞體積。

設(shè)計(jì)完成后敵人的Inspector窗口如下:

8.完成敵人移動(dòng)的設(shè)計(jì)后需要對場景進(jìn)行Navigation的烘培,對地形的角度/人物模型進(jìn)行對比后將臺(tái)階等設(shè)置進(jìn)行平衡,最后點(diǎn)擊烘培

烘培后的地形效果如下:

藍(lán)色部分為可移動(dòng)平面。

9.將單個(gè)敵人保存為Prefab,并將同樣的腳本內(nèi)容賦值給三種不同的模型(對動(dòng)畫效果重新進(jìn)行綁定),并修改人物的屬性(移動(dòng)速度,單次傷害,傷害間隔,血量等)

10.設(shè)置怪物刷新的位置,并使用相應(yīng)的代碼進(jìn)行實(shí)現(xiàn)

11.對游戲場景進(jìn)行相應(yīng)的細(xì)化,例如燈光,鏡頭遠(yuǎn)近,背景音樂設(shè)置等

12.完成游戲場景的布置后對場景進(jìn)行build,并以此類推進(jìn)行以下幾個(gè)游戲關(guān)卡的設(shè)計(jì)

13.完成游戲關(guān)卡的設(shè)計(jì)后開始進(jìn)行UI界面的設(shè)計(jì),對每關(guān)人物的血量/分?jǐn)?shù)等利用代碼進(jìn)行調(diào)用,并完成Game Over動(dòng)畫的實(shí)現(xiàn)。

14.對游戲開始界面的GUI進(jìn)行設(shè)計(jì),包括關(guān)卡切換/音量調(diào)節(jié)等,并利用相關(guān)的代碼進(jìn)行關(guān)卡切換等功能的實(shí)現(xiàn)。

15.對所有場景進(jìn)行build操作,并對游戲進(jìn)行基本測試,完成后導(dǎo)出游戲素材。

五.后記

本游戲參考了Unity官方教程https://unity3d.com/cn/learn/tutorials/projects/survival-shooter-tutorial

該教程從

12個(gè)方面對游戲設(shè)計(jì)進(jìn)行了講解,是我在unity官網(wǎng)完成的第五部游戲開發(fā)教程,整個(gè)視頻課程約300分鐘,我從該視頻課程中受益匪淺,學(xué)到了相當(dāng)多Unity中各方面的開發(fā)知識(shí)和前端代碼的實(shí)現(xiàn)與程序構(gòu)建。

視頻的播放列表鏈接如下:

https://unity3d.com/cn/learn/tutorials/projects/survival-shooter/camera-setup?playlist=17144

本游戲非本人制作的部分如下:

游戲關(guān)卡的地形場景;游戲中玩家和敵人的模型;模型使用的動(dòng)畫;游戲UI中使用的字體。

參考了教程中的部分如下:

游戲中玩家和敵人的運(yùn)動(dòng)代碼參考了部分視頻中給出的案例;

游戲內(nèi)燈光的布置和設(shè)計(jì);

游戲內(nèi)Navigation地形的渲染設(shè)置。

獨(dú)立設(shè)計(jì)部分:

游戲機(jī)制,人物開火動(dòng)畫,關(guān)卡,開始界面UI,分?jǐn)?shù)判定等。

游戲素材內(nèi)包含以下部分,如果需要檢查素材可以按以下方式檢查:

代碼部分:

分為玩家,敵人,攝像機(jī),游戲機(jī)制四部分:

游戲內(nèi)場景部分:

包括Level 00(開始界面)和Level 01-04(游戲關(guān)卡)

游戲內(nèi)環(huán)境材質(zhì):

(來自素材包)

游戲內(nèi)Prefab:

包括環(huán)境,槍和受傷的粒子特效,光效和玩家/敵人的模型。

游戲內(nèi)材質(zhì)球:

(來自素材包)

游戲內(nèi)字體:

游戲內(nèi)聲音素材:

游戲內(nèi)人物受傷/死亡音效;背景音樂等

(來自素材包)

游戲內(nèi)動(dòng)畫效果素材:

主界面UI的動(dòng)畫效果,游戲結(jié)束效果和設(shè)計(jì)時(shí)槍口的動(dòng)畫效果

六.組員信息:

游戲設(shè)計(jì)人:

14級(jí)數(shù)媒技術(shù)2班 李基銘?

負(fù)責(zé)工作:

模型設(shè)計(jì),動(dòng)畫設(shè)計(jì),關(guān)卡設(shè)計(jì),人物設(shè)計(jì),配樂,游戲機(jī)制設(shè)計(jì),代碼設(shè)計(jì),動(dòng)畫組件設(shè)計(jì),等等等等

游戲設(shè)計(jì)時(shí)間:大約30小時(shí)(包括寫報(bào)告時(shí)間5小時(shí),海報(bào)制作2小時(shí),看視頻10小時(shí)和獨(dú)立制作10小時(shí))

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容