Így készült:
Zombie Battle
Erdős Zoltán
Érd
2023.04.09
A játék letölthető innen:
https://ezszoftver.hu/hu/ZombieBattle.html
Ezzel
a leírással be szeretném mutatni, hogy „Unity”-ben hogyan készült el a „Zombie
Battle” játék.
Tanulási
célból készült ez a leírás. Ha valakit érdekel egy FPS játék elkészítése
„Unity”-ben, de nem tudja hogy hogyan kezdjen neki, neki hasznos lehet ez a
leírás.
Képernyőképekkel
mutatom be a játékban lévő objektumokat, C# forráskóddal.
Sajnos a “ZombieBattle” játék “Unity projektet” nem
oszthatom meg veled, mert abban vannak fizetős tartalmak is, de ez a leírás
is segíthet.
Jó játékot! 😊
1. Pálya:
Feladata:
A
pályán játszódik a játék. Itt vannak a Zombik, Tudósok, lőszerek és
életek.
Megvalósítás:
Rendelkezik
„MeshCollider”-el, ami az ütközésért felelős. A pályán lévő
objektumok nagyrészt „static”-kusok. Láthatatlan „BoxCollider”-el is
rendelkelkezik, hogy ne tudjon az „Avatar” kiugrani a pályáról.
2. Helikopter:
Feladata:
A
helikopter mellett kezdődik a játék, és itt fejeződik be. Ha
megtalálta az Avatar a doktorokat, akkor a helikopterhez kell mennie az
Avatarnak és a doktoroknak, mert akkor menekültek meg a zombik elől.
Fel-
le mozog a helikopter.
Megvalósítás:
Flying.cs: fel-
le mozgás.
Hangot
játszik le a helikopter.
Az
„Animator” komponens felelős a propeller forgásáért.
3. Avatar:
Feladata:
Az avatar tud mozogni a pályán, lőni,
ugrani tud, újra tölti a fegyvert.
Feladata, hogy megkeresse a doktorokat,
majd a helikopterhez kísérje őket, így menti meg őket a zombik
elől.
Ha egy zombi támad, azt meg kell ölni,
hogy ne sebezzen meg.
Tud életet és lőszert felvenni.
Tud hordót lőni, ami sebzi a zombikat.
Megvalósítás:
CapsuleCollider:
ütközés miatt kell.
4. Avatar
Canvas:
Feladata:
Megjeleníti a képernyőn a Canvas az
avatar életét, a lőszer mennyiségét, a megmentett doktorok számát.
Megjeleníti, hogy milyen messze található
a legközelebbi doktor és helikopter.
5. Avatar
Zseblámpa:
Feladata:
Ha az avatar egy sötét részen van, akkor a
lámpával meg lehet világítani az előtte lévő teret.
6. Zombi:
Feladata:
Kóborol a zombi, de ha rá lő az
avatar, vagy meglátja az avatar-t, akkor az avatar felé kezd futni.
Útvonalon fut a zombi, vagyis ha akadály van előtte, akkor azt képes
kikerülni.
Ha közel ér a zombi az avatar-hoz, akkor
ütni kezdi az avatar-t, akinek az élete csökken ilyenkor.
Van a zombi-nak élete, ami akkor csökken,
ha az avatar lövi, vagy hordó robban fel a közelében. Ha elfogy a zombi élete,
akkor meghal a zombie.
Megvalósítás:
CapsuleCollider: ütközésért felel.
NavMeshAgent: ez a komponens tud mozogni
az úton, így tud kikerülni akadályokat.
Zombie.cs: a zombi gondolkodásáért
felel.
7. Zombi
Kezelő:
Feladata:
Ha túl messze van egy zombi az avatartól,
akkor ez a GameObject „disabled” lesz, különben „enabled”. Ez a távolság 60
méter. Így kevesebb cpu-t használ a játék.
Megvalósítás:
ZombiesManager.cs
8. Hordó:
Feladata:
Ha az avatar rá lő a hordóra, akkor a
hordó felrobban.
Ha a robbanás közelében van egy másik
hordó, akkor az kb. 0,5-1 másodperc múlva felrobban.
A robbanás levonja a zombi és avatar
életét.
Megvalósítás:
CapsuleCollider: Ütközésért felel.
SphereCollider: Egy gömb, amiben ha benne
van az avatar vagy zombi, akkor arról értesül az Explosion.cs
Explosion.cs
9. Hordó
Kezelő:
Feladata:
Ha a hordó túl messze van az avatartól,
akkor a GameObject „disabled”-re állítódik, különben „enabled”. Így kevesebbet
kell számolnia a cpu-nak. Ez most 60méter.
Megvalósítás:
GasBallonesManager.cs
10. Élet:
Feladata:
Ez ad életet az avatarnak, ha az avatar
ütözik ezzel.
Megvalósítás:
SphereCollider: Ütözés figyelő.
HealUp.cs
11. Lőszer:
Feladata:
Ez ad lőszert az avatarnak, ha az
ütközik ezzel.
Megvalósítás:
SphereCollider: Ütközés figyelő.
WeaponUp.cs
12. Háttérzene:
Feladata:
A háttérzene lejátszást végzi. A zene
ismétlődik.
13. Doktor:
Feladata:
Őket kell megmentenie az avatarnak.
Egy helybe állnak, néha „help”-et kiabálnak.
Ha az avatar melléjük érkezik, akkor
megjelenik egy „HealUp” a doktor mellett, amit felvehet az avatar.
Majd a doktor elkezdi követni az avatart. Ha akadály van a doktor és avatar
között, akkor azt kikerüli a doktor.
Megvalósítás:
CapsuleCollider: az ütközésért felel.
NavMeshAgent: ez a komponens felel azért
hogy a doktor tudjon útvonalon közlekedni, így kikerülve az akadályokat.
Doctor.cs
14. Doktor
Kezelő:
Feladata:
Ha az összes doktor a helikopter alatt
van, akkor át lett véve a pálya.
Megvalósítás:
DoctorsManager.cs
15. Pause
menü:
Feladata:
Szüneteltetni lehet a játékot, vagy ki
lehet lépni a főmenübe.
16. Fő
menü:
Feladata:
Ez az első felület, ami a játék
indítása után fogadja a felhasználót.
„Új játékot” lehet kezdeni.
Folytatni lehet a játékot, így nem kell
ellőről kezdeni azt.
A Beállításokba lehet lépni.
Információt lehet olvasni.
Ki lehet lépni a játékból.
17. Fő
menü (DontDestroy Object):
Feladata:
A DontDestroy GameObject végig a
memóriában marad, nem törlődik ha egy másik .scene kerül betöltésre.
Ez tálolja a játék állását.
Megvalósítás:
DontDestroy.cs
18. Beállítások
menü:
Feladata:
Itt vannak a beállítások.
Felbontást lehet változtatni.
Lehet váltani teljes képernyő/ablak
mód között.
Hangerőt lehet állítani.
19. Infó
menü:
Feladata:
Információt kap a felhasználó az avatar
irányításáról és a játékról.
20. Történet
panel:
Feladata:
Amikor új játékot kezd a felhasználó,
akkor egy rövid történet fogadja őt.
Kattints a „Start” button-ra.
21. Success
menü:
Feladata:
Ha átvitt egy pályát a játékos, akkor ez a
képernyő fogadja.
Tovább tud lépni a következő pályára.
Mentésre kerül a játék, így később
tudja folytatni ettől a ponttól a felhasználó.
22. GameOver
menü:
Feladata:
Ha elfogy az élete az avatar-nak, akkor a
„GameOver” képernyő fogadja a játékost.
Vissza tud lépni a főmenübe.
23. Congratulations
menü:
Feladata:
Ha átvitte a pályákat a játékos, akkor ez
a képernyő fogad.
Játék vége szöveg és információ a
fejlesztőről.
Vissza lehet lépni a főmenü-be.
24. Post
Processing:
Feladata:
Effekt-et lehet a képre tenni.
Az Ambient Occlusion effekt besötétíti a
sarkokat.
Az élsimítás effekt az éleket elmossa.
Megvalósítás:
„Post Process Volume” és „Post-Process
Layer” komponensek.
25. Occlusion
Culling:
Feladata:
Azokat az objektumokat nem rajzolja ki a
Unity, amik a képernyőn takarásban vannak.
Megvalósítás:
Kattints az „Window -> Rendering ->
Occlusion” menüre, és a megjelenő panelen kattints a „Bake” button-ra.
26. Baked
Lightmaps:
Feladata:
Azok a GameObject-ek amik statikus-ak
(mozdulatlanok), azokra ki lehet számolni a rá érkező fényeket,
árnyékokat, amik szebbek, mint amit a Unity valós időben számol fényeket,
árnyékokat.
Így szebb lesz a pálya kinézete.
Megvalósítás:
Kattints a „Window -> Rendering ->
Lighting” menüre, állísd be a paramétereket, majd kattints a „Generate
Lighting” buttonra.
27. Build
Game:
Feladata:
Le lehet menteni a játékot Windows-ra,
Linux-ra, és MacOS-re. Így a játék futtatásához nincs szükség a Unity
játékszerkesztőre.
A textúrák maximális méretét is be lehet
itt állítani.
Megvalósítás:
Kattints a „File -> Bulid” menüre, válaszd
ki a legördülő menüben az Operációs rendszert, majd kattints a „Build”
buttonra.
Forráskódok:
Flying.cs:
public class Flying : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
m_v3Base = new Vector3(this.transform.position.x, this.transform.position.y, this.transform.position.z); // helikopter pozíció eltárolása
}
// Update is called once per frame
void Update()
{
m_fTime += Time.deltaTime; // idő növelése
float y = Mathf.Sin((2.0f * Mathf.PI) *
m_fTime * speed) * amplitude; //
hullámzás kiszámítása
Vector3 v3NewPosition = m_v3Base + new Vector3(0, y, 0);
this.transform.position = v3NewPosition;
// hullámzás alkalmazása
}
Vector3 m_v3Base;
float speed = 0.5f;
float amplitude = 0.1f;
float m_fTime = 0.0f;
}
Zombie.cs:
public class Zombie : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
animator =
GetComponent<Animator>(); //
komponensek lekérdewzése, inicializálás
//movement = GetComponent<CharacterController>();
rigidBody =
GetComponent<Rigidbody>();
m_navMeshAgent = GetComponent<NavMeshAgent>();
m_soundIdle = Instantiate(m_soundIdle, this.transform);
m_soundAttack =
Instantiate(m_soundAttack, this.transform);
m_soundDie = Instantiate(m_soundDie, this.transform);
v3Dir = this.transform.forward;
state = State.Walk;
m_nElapsedLife = m_nLife;
}
// Update is called once per frame
void FixedUpdate()
{
m_soundIdle.transform.position = this.transform.position; // hangok
pozíciójának beállítása
m_soundAttack.transform.position = this.transform.position;
m_soundDie.transform.position = this.transform.position;
// die
if (m_nLife <= 0.0f) // ha az élet negatív, akkor meghal a zombi
{
if (false == m_soundDie.GetComponent<AudioSource>().isPlaying
&& true == m_bIsFirstDie) // hang lejátszása
{
m_bIsFirstDie = false;
m_soundDie.GetComponent<AudioSource>().Play();
//m_ZombieCounter.Add();
}
state = State.Die;
animator.SetBool("walk", false);
animator.SetBool("run", false);
animator.SetBool("attack", false);
animator.SetBool("die", true); // halál animáció indítása
Destroy(GetComponent<Rigidbody>(), 0.0f); // komponensek törlése
Destroy(GetComponent<NavMeshAgent>(), 0.0f);
Destroy(GetComponent<CapsuleCollider>(), 0.0f);
return;
}
if (true == SearchAvatar()) // megtalálta az avatar-t?
{
if ((m_objAvatar.transform.position - this.transform.position).magnitude < 1.45f) // ha közel van hozzá, akkor támadás animáció
{
state = State.Attack;
animator.SetBool("walk", false);
animator.SetBool("run", false);
animator.SetBool("die", false);
animator.SetBool("attack", true);
}
else // ha távol van tőle akkor futás animáció
{
state = State.Run;
animator.SetBool("walk", false);
animator.SetBool("attack", false);
animator.SetBool("die", false);
animator.SetBool("run", true);
}
}
else if (LeaveAvatar()) // elhagyta az avatart?
{
state = State.Walk; // sétálás animáció
animator.SetBool("run", false);
animator.SetBool("attack", false);
animator.SetBool("die", false);
animator.SetBool("walk", true);
}
// idle
switch (state)
{
case (State.Walk): // ha sétál
{
if (null == sleep) // várakozás 0.1 .. 3.0 másodpercig
{
float fSleep = Random.Range(0.1f, 3.0f);
sleep =
Instantiate(m_objSleep);
sleep.GetComponent<Sleep>().Begin(fSleep);
fYawSpeed = Random.Range(-90.0f,
90.0f);
}
if (true == sleep?.GetComponent<Sleep>().IsEnd()) // ha lejárt a várakozás
{
Destroy(sleep);
sleep = null;
// new yaw
fYawSpeed =
Random.Range(-90.0f, 90.0f); //
zombi forgás szögsebesség
// sound
if (false ==
m_soundIdle.GetComponent<AudioSource>().isPlaying) // hang lejátszás
{
m_soundIdle.GetComponent<AudioSource>().Play();
}
}
v3Dir = Quaternion.Euler(0,
fYawSpeed * Time.deltaTime, 0) * v3Dir; // forgás kiszámítása
v3Dir.Normalize();
Vector3 v3TargetPos = this.transform.position + v3Dir;
Vector3 v3Step = (v3Dir *
m_fWalkVelocity) * Time.deltaTime;
//movement.enabled = true;
//movement.Move(v3Step);
rigidBody.isKinematic = true;
this.transform.position += v3Step;
this.transform.LookAt(v3TargetPos, new Vector3(0, 1, 0)); // zombi forogjon
break;
}
case (State.Run): // ha fut a zombi
{
//movement.enabled = false;
if (true ==
m_navMeshAgent?.SetDestination(m_objAvatar.transform.position)) // avatarhoz, útvonal keresése
{
rigidBody.isKinematic =
true;
}
else
{
rigidBody.isKinematic =
false;
}
// sound
if (false ==
m_soundAttack.GetComponent<AudioSource>().isPlaying) // hang lejátszás
{
m_soundAttack.GetComponent<AudioSource>().Play();
}
break;
}
case (State.Attack): // ha támad a zombi
{
//movement.enabled = false;
if (true ==
m_navMeshAgent?.SetDestination(m_objAvatar.transform.position)) // avatarhoz, útvonal keresése
{
rigidBody.isKinematic =
true;
}
else
{
rigidBody.isKinematic =
false;
}
m_objAvatar.GetComponent<PlayerMovementScript>().OnAttack(); // levonni az avatar életéből
// sound
if (false ==
m_soundAttack.GetComponent<AudioSource>().isPlaying)// hang lejátszás
{
m_soundAttack.GetComponent<AudioSource>().Play();
}
break;
}
}
}
bool SearchAvatar() // avatar keresése
{
Vector3 v3Dir1 = this.transform.forward;
Vector3 v3Dir2 =
(m_objAvatar.transform.position - this.transform.position).normalized;
float fDistance =
(m_objAvatar.transform.position - this.transform.position).magnitude;
if (Vector3.Angle(v3Dir1, v3Dir2)
<= 88.0f && fDistance <= 15.0f) // ha az avatar a zombi előtt van, és közelebb van
mint 15 méter, akkor megtalálta az avatart
{
return true;
}
// avatar hit the zombie
if (m_nElapsedLife != m_nLife)
{
return true;
}
return false; // nem találta meg az avatart.
}
bool LeaveAvatar() // elhagyta az avatart?
{
float fDistance =
(m_objAvatar.transform.position - this.transform.position).magnitude;
if (fDistance > 20.0f) // ha a távolság az avatartól nagyobb mint 20
méter, akkor elhagyta.
{
m_nElapsedLife = m_nLife; // reset
return true;
}
return false; // nem hagyta el
}
public void Damage() // zombi életéből levonni 10-et
{
m_nLife -= 10;
if (m_nLife < 0)
{
m_nLife = 0;
}
}
public void Damage(int nValue) // zombi
életéből levonni
{
m_nLife -= nValue;
if (m_nLife < 0)
{
m_nLife = 0;
}
}
GameObject sleep = null;
Animator animator = null;
//CharacterController movement = null;
Rigidbody rigidBody = null;
NavMeshAgent m_navMeshAgent = null;
enum State { Walk, Run, Attack, Die };
State state = State.Walk;
public float m_fWalkVelocity = 0.5f;
public float m_fRunVelocity = 3.0f;
Vector3 v3Dir;
float fYawSpeed = 0.0f;
int m_nElapsedLife = 0;
bool m_bIsFirstDie = true;
[SerializeField] GameObject m_objSleep = null;
[SerializeField] GameObject m_objAvatar = null;
public int m_nLife = 100;
[SerializeField] GameObject m_soundIdle = null;
[SerializeField] GameObject m_soundAttack =
null;
[SerializeField] GameObject m_soundDie = null;
[SerializeField] KilledZombieCounter
m_ZombieCounter;
}
ZombiesManager.cs:
public class ZombiesManager :
MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
arrZombies = this.GetComponentsInChildren<Zombie>(); // zombi gameobject-ek lekérése
}
// Update is called once per frame
void Update()
{
Vector3 pos1 =
m_Avatar.transform.position; //
avatar pozíciója
foreach (Zombie zombie
in arrZombies)
{
if (zombie.m_nLife <= 0) { continue; }
Vector3 pos2 =
zombie.gameObject.transform.position; // aktuális zombi pozíciója
if (Vector3.Distance(pos1, pos2) >
m_fHideDistance) // ha nagyon
mewssze van a zombi az avatartól, akkor a zombi gameobject disabled-re
állítása. Így nem terheli a cpu-t a sok gameobject
{
zombie.gameObject.SetActive(false);
}
else
{
zombie.gameObject.SetActive(true);
}
}
}
Zombie[] arrZombies = null;
[SerializeField] GameObject m_Avatar;
[SerializeField] float m_fHideDistance = 75.0f;
}
Explosion.cs:
public class Explosion :
MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
m_listGameObjects.Clear();
}
private void LateUpdate() // az Update()
függvény után hívódik meg automatikusan ez a függvény
{
if (m_nLife <= 0) // ha a hordó élete negatív, akkor robbanás
{
StartExplosion();
}
}
public void StartExplosion(float fTime) // robbanás
függvény meghívása x másodperc múlva
{
Invoke("StartExplosion", fTime);
}
public List<GameObject>
m_listGameObjects = new List<GameObject>(); // a hordó közelében lévő gameobject-ek
public void StartExplosion()
{
if (true == m_bIsFirstExplosion) // ha még nem volt robbanás
{
m_bIsFirstExplosion = false;
m_nLife = 0;
// particle system
GameObject explosion =
Instantiate<GameObject>(m_objExplosion, this.transform.position, this.transform.rotation); //
robbanás változó létre hozása
explosion.GetComponent<ParticleSystem>().Play();
Destroy(explosion, 10.0f);
// sound
GameObject sound =
Instantiate<GameObject>(m_objSound, this.transform.position, this.transform.rotation); //
hang változó létre hozása
sound.GetComponent<AudioSource>().Play();
Destroy(sound, 5.0f);
// explosion force
Rigidbody rigidbody =
GetComponent<Rigidbody>();
rigidbody.AddExplosionForce(10000.0f, transform.position + new Vector3(0.0f, 0.4f, 0.0f), 5.0f, -10.0f, ForceMode.Impulse); // robbanás erő alkalmazása
// light
GetComponentInChildren<Light>().enabled = true; // robbanás fény bekapcsolása
// hide
Destroy(transform.gameObject,
0.25f); // 0.25 másodperc múlva a hordó
törlődik
// damage
foreach (GameObject
obj in m_listGameObjects)
{
if (null == obj) { continue; }
// zombie
Zombie zombie =
obj.GetComponent<Zombie>();
if (null != zombie) // ha zombit talált a hordó mellett, akkor
levonni az életéből
{
zombie.Damage(100);
}
// avatar
PlayerMovementScript movement =
obj.GetComponent<PlayerMovementScript>(); // ha avatart talált a zombi mellett, akkor levonni az
életéből
if (null != movement)
{
movement.AddLife(-75.0f);
}
}
}
}
void OnTriggerStay(Collider other)// automatikusan meghívódik ez a függvény, ha
a hordó triggerjében valamilyen gameobject benne van
{
if (null != other.gameObject)
{
m_listGameObjects.Add(other.gameObject); // eltárolni a gameobject-et
}
if (false == m_bIsFirstExplosion) // ha az aktuális hordó még nem robbant fel
{
Explosion explosion =
other.gameObject.GetComponent<Explosion>(); // és robbanás van a közelben
if (null != explosion) // akkor az aktuális hordó is robbanjon fel
0.75 ,, 1.75 másodperc múlva.
{
explosion.StartExplosion(Random.Range(0.75f, 1.75f));
}
}
}
public float m_nLife = 3;
public GameObject m_objExplosion = null;
private bool m_bIsFirstExplosion = true;
public GameObject m_objSound = null;
}
GasBallonesManager.cs:
public class GasBallonesManager :
MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
arrExplosions = this.GetComponentsInChildren<Explosion>(); // hordók megkeresése
}
// Update is called once per frame
void Update()
{
Vector3 pos1 =
m_Avatar.transform.position; //
avatar pozíciója
foreach (Explosion
explosion in arrExplosions)
{
if (explosion.m_nLife <= 0) { continue; }
Vector3 pos2 =
explosion.gameObject.transform.position; // hordó pozíciója
if (Vector3.Distance(pos1, pos2) >
m_fHideDistance) // ha a hordó
túl messze van az avatartól, akkor disabled-re állítani a gameobject-et. Így
kevesebb cpu-t használ a játék.
{
explosion.gameObject.SetActive(false);
}
else
{
explosion.gameObject.SetActive(true); // különben enabled.
}
}
}
Explosion[] arrExplosions = null;
[SerializeField] GameObject m_Avatar;
[SerializeField] float m_fHideDistance = 75.0f;
}
HealUp.cs:
public class HealUp : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
m_fTime -= Time.deltaTime; // respawn time csökkentése
if (m_fTime <= 0.0f) // ha a respawn time negatív, akkor legyen
újra aktív a healup
{
m_objDraw.SetActive(true);
}
else
{
m_objDraw.SetActive(false);
}
}
private void OnTriggerEnter(Collider other) // ha ütközik a healup-al valaki, ...
{
if (m_fTime > 0.0f)
{
return;
}
if (other.tag == "Player") // ha az avatar ütközik a helup-al
{
m_objSound.GetComponent<AudioSource>().Play();// zene lejátszás
m_Avatar.OnCollision_LifeUp(); // avatar élet növelése
m_fTime = m_fRespawnTime; // respawn time legyen pozitív
}
}
[SerializeField] GameObject m_objDraw = null;
[SerializeField] GameObject m_objSound = null;
[SerializeField] PlayerMovementScript
m_Avatar = null;
[SerializeField] float m_fRespawnTime = 30.0f;
float m_fTime = 0.0f;
}
WeaponUp.cs:
public class WeaponUp :
MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
m_fTime -= Time.deltaTime; // respawn time csökkentése
if (m_fTime <= 0.0f) // ha eltelt 30 másodperc, akkor újra aktív
lesz ez a lőszer.
{
m_objDraw.SetActive(true);
}
else
{
m_objDraw.SetActive(false);
}
}
private void OnTriggerEnter(Collider other) // ha a tölténnyel ütközik egy gameobject
{
if (m_fTime > 0.0f)
{
return;
}
if (other.tag == "Player") // és az avatar, akkor
{
m_objSound.GetComponent<AudioSource>().Play(); // hang lejátszása
m_Avatar.OnCollision_WeaponUp(); // avatar lőszerének növelése
m_fTime = m_fRespawnTime; // respawn time legyen 30másodperc
}
}
[SerializeField] GameObject m_objDraw = null;
[SerializeField] GameObject m_objSound = null;
[SerializeField] PlayerMovementScript
m_Avatar = null;
[SerializeField] float m_fRespawnTime = 30.0f;
float m_fTime = 0.0f;
}
Doctor.cs:
public class Doctor : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
animator =
GetComponent<Animator>(); //
komponensek lekérése
m_navMeshAgent =
GetComponent<NavMeshAgent>();
m_soundHelp = Instantiate(m_soundHelp, this.transform);
m_soundThankYou = Instantiate(m_soundThankYou,
this.transform);
}
// Update is called once per frame
void Update()
{
m_soundHelp.transform.position = this.transform.position; // hangok
pozíciójának frissítése
m_soundThankYou.transform.position = this.transform.position;
if (null == sleep) // várakozás indítása (20 .. 30 másodperc
között)
{
float fSleep = Random.Range(20.0f,
30.0f);
sleep = Instantiate(m_objSleep);
sleep.GetComponent<Sleep>().Begin(fSleep);
}
// play sound
if (true ==
sleep?.GetComponent<Sleep>().IsEnd()) // "help" kiabálás indítása
{
Destroy(sleep);
sleep = null;
// sound
if (Vector3.Distance(this.transform.position, m_Avatar.transform.position) > 25.0f && false == m_soundHelp.GetComponent<AudioSource>().isPlaying)
{
m_soundHelp.GetComponent<AudioSource>().Play();
}
}
if (m_state == States.Waiting) // ha várakozik a doktor
{
animator.SetBool("walk", false); // várakozás animáció lejátszása
animator.SetBool("idle", true);
if (null == sleep)
{
float fSleep = Random.Range(20.0f,
30.0f);
sleep =
Instantiate(m_objSleep);
sleep.GetComponent<Sleep>().Begin(fSleep);
}
// play sound
if (true ==
sleep?.GetComponent<Sleep>().IsEnd()) // help kiabálás indítása 20 .. 30 másodperc között
{
Destroy(sleep);
sleep = null;
// sound
if (false ==
m_soundHelp.GetComponent<AudioSource>().isPlaying)
{
m_soundHelp.GetComponent<AudioSource>().Play();
}
}
if (Vector3.Distance(this.transform.position, m_Avatar.transform.position) < 3.0f) // ha közel van az avatar a doktorhoz, akkor
{
if (true ==
m_soundHelp.GetComponent<AudioSource>().isPlaying) // help kiabálás leállítása
{
m_soundHelp.GetComponent<AudioSource>().Stop();
}
// play sound
m_soundThankYou.GetComponent<AudioSource>().Play(); // "thank you" hang lejátszása
// logic
DoctorsManager.Instance.Add(); // a DoctorsManager növelése eggyel
// add heal
Vector3 dir =
(m_Avatar.transform.position - this.transform.position);
dir.y = 0.0f;
dir.Normalize();
m_objHealUp =
Instantiate<GameObject>(m_objHealUp, this.transform.position + (dir * 0.5f) +
new Vector3(0, 0.7f, 0), Quaternion.Euler(0, 0, 0)); // HealUp lehelyezése az Doktor mellé
m_objHealUp.SetActive(true);
elapsedPos = new Vector3(this.transform.position.x, this.transform.position.y, this.transform.position.z);
currentPos = new Vector3(this.transform.position.x, this.transform.position.y, this.transform.position.z);
m_state =
States.FollowToAvatar; // mostantól a
doktor követi az avatar-t.
if (null != sleep)
{
Destroy(sleep);
sleep = null;
}
}
}
else if (m_state == States.FollowToAvatar)
{
// go to Avatar
if (Vector3.Distance(this.transform.position, m_Avatar.transform.position) > 8.0f) // ha messze van a doktor az avatartól, akkor
{
m_navMeshAgent.stoppingDistance
= 3.5f;
m_navMeshAgent?.SetDestination(m_Avatar.transform.position); // útvonal keresése az avatarhoz
}
elapsedPos = currentPos;
currentPos = new Vector3(this.transform.position.x, this.transform.position.y, this.transform.position.z);
float velocity = Vector3.Distance(elapsedPos,
currentPos) / Time.fixedDeltaTime;
if (velocity < 0.05f) // ha kicsi a sebessége a doktornak, akkor
várakozás animáció lejátszáse
{
animator.SetBool("walk", false);
animator.SetBool("idle", true);
}
else // különben sétálás animáció lejátszása
{
animator.SetBool("idle", false);
animator.SetBool("walk", true);
}
}
}
public enum States
{
Waiting,
FollowToAvatar
}
Vector3 elapsedPos, currentPos;
public States m_state = States.Waiting;
GameObject sleep = null;
Animator animator = null;
NavMeshAgent m_navMeshAgent = null;
[Header("Common")]
[SerializeField] GameObject m_Avatar = null;
[SerializeField] GameObject m_objSleep = null;
[SerializeField] GameObject m_objHealUp = null;
[Header("Sounds")]
[SerializeField] GameObject m_soundHelp = null;
[SerializeField] GameObject m_soundThankYou
= null;
}
DoctorsManager.cs:
public class DoctorsManager :
MonoBehaviour
{
public static DoctorsManager Instance = null;
private void Awake()
{
Instance = this; // eltárolni egy statikus változóban
az egyetlen "DoctorsManager" címét.
}
// Start is called before the first frame update
void Start()
{
m_nMaxDoctors =
Instance.GetComponentsInChildren<Doctor>().Length;
m_nNumDoctors = 0;
}
// Update is called once per frame
void Update()
{
if (true == IsFailed()) // ha meghalt az avatar, akkor gameover scene
betöltése
{
SceneManager.LoadScene("Scenes/GAMEOVER");
}
if (true == IsSuccess()) // ha megtalálta az összes doktort az avatar,
és a helikopter közelében van mindenki, akkor a Success scene betöltése
{
SceneManager.LoadScene("Scenes/SUCCESS");
}
}
public void Add() // egy doktort megtalált az avatar.
{
m_nNumDoctors++;
}
public int MaxDoctors() // doktorok maximális száma
{
return m_nMaxDoctors;
}
public int NumDoctors() // megtalált doktorok pillanatnyi értéke
{
return m_nNumDoctors;
}
public bool IsSuccess()
{
float fRadius = 15.0f;
bool bIsOk1 = (m_nNumDoctors >=
m_nMaxDoctors && Vector3.Distance(m_objAvatar.transform.position,
m_objFinish.transform.position) < fRadius); // ha az avatar közel van a helikopterhez
bool bIsOk2 = true;
foreach (Doctor doctor
in Instance.GetComponentsInChildren<Doctor>()) // ha az összes doktor közel van a
helikopterhez
{
if
(Vector3.Distance(doctor.gameObject.transform.position,
m_objFinish.transform.position) > fRadius)
{
bIsOk2 = false;
}
}
return (true == bIsOk1 && true == bIsOk2);
}
public bool IsFailed()
{
if (m_objAvatar.GetComponent<PlayerMovementScript>().GetLife()
<= 0) // ha az avatar élete negatív
{
return true;
}
return false;
}
public bool IsFoundAllDoctors() // megtalálta az összes doktort az avatar?
{
if (m_nNumDoctors >= m_nMaxDoctors)
{
return true;
}
return false;
}
public float NextDoctorDistance() // legközelebbi várakozó doktor távolságát
adja vissza ez a függvény
{
float fMinDistance = float.MaxValue;
foreach (Doctor doctor
in Instance.GetComponentsInChildren<Doctor>())
{
if (doctor.m_state ==
Doctor.States.Waiting)
{
float fDistance =
Vector3.Distance(m_objAvatar.transform.position,
doctor.gameObject.transform.position);
if (fDistance < fMinDistance)
{
fMinDistance = fDistance;
}
}
}
return fMinDistance;
}
public float FinishDistance() // a helikopter távolságát adja vissza ez a
függvény
{
float fDistance =
Vector3.Distance(m_objAvatar.transform.position,
m_objFinish.transform.position);
return fDistance;
}
[SerializeField] GameObject m_objAvatar = null;
[SerializeField] GameObject m_objFinish = null;
public int m_nMaxDoctors = 0;
public int m_nNumDoctors = 0;
}
.