《計(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í))