Niedawno zakończył się konkurs #ROG4Creators ( https://rog4creators.pl/code ).
Uczestnicy konkursu mieli do dyspozycji wstępną wersję projektu, który trzeba jak najlepiej rozwinąć.
Tak aby gra była grywalna, dodać kilka rzeczy, ulepszyć istniejące itd.
Chciałbym w tym poscie skomentować właśnie stan tego projektu startowego, bo autorzy albo go przygotowywali w dużym pośpiechu, albo nie są legendami za jakie się uważają :]
Sprite
1. Brak pivotów.
Wszelkie animacje postaci w klatkach kluczowych zmieniają pozycję i obrót obrazka względem standardowego pivotu centralnego (0.5, 0.5)
.
Powinno się ustawić odpowiedni pivot i zmieniać sam kąt, żeby nie utrudniać sobie roboty.
2. Różne skale obiektów.
Np. gracz ma skalę:
Chodzący robot ma skalę:
Nie jest to może ciężki grzech. Ale ja bym raczej starał się zawsze mieć skalę (1, 1, 1).
Wielkość spriteów można regulować w opcjach importera:
3. Rozmiar tekstur.
- są wielgachne dla takich małych postaci (nie cierpię kiedy graficy przygotowują dla programisty assety, nadające się tylko do dalszej obróbki...),
- NPOT (wymiary to nie są potęgi liczby 2),
- brak atlasu.
Kod
1. Bullet.cs
.
private IEnumerator SpawnImpactEffect()
{
var impactGameObject = Instantiate(impactEffect, transform.position, transform.rotation);
yield return new WaitForSeconds(1f);
if (impactGameObject != null)
{
Destroy(impactGameObject);
}
Destroy(gameObject);
}
- Kula po uderzeniu leci dalej jeszcze przez
1s
i uderza kolejne przeszkody, zanim zostanie zniszczona, - Metoda Destroy może przyjąć drugi parametr, który określa po jakim czasie ma nastąpić zniszczenie obiektu, więc metoda
SpawnImpactEffect()
nawet nie musi byćCoroutine
.
private void SpawnImpactEffect()
{
Destroy(Instantiate(
impactEffect,
transform.position,
transform.rotation), 1.0f);
Destroy(gameObject);
}
2. Serializowane pola.
Żebyśmy mogli przeciągnąć inne obiekty na pola klasy w Inspektorze Unity, muszą być one albo publiczne, albo prywatne z atrybutem SerializeField
.
Jeśli wybierzemy opcję numer dwa, to wypadałoby pozbyć się warningu o nigdy nie inicjowanym polu.
Czyli zamiast:
[SerializeField]
private GameObject deathEffect;
piszemy:
[SerializeField]
private GameObject deathEffect = null;
3. Szukanie obiektów.
Takie coś jest nieprofesjonalne:
waveManager = FindObjectOfType<WaveManager>();
Lepiej zrobić jeden singleton, który nam trzyma referencje do menadżerów itp.
A tam gdzie się da (obiekty nie tworzone podczas rozgrywki), deklarować serializowane pole, jak wyżej.
4. Enemy.cs
.
private void OnCollisionEnter2D(Collision2D other)
{
if ( other.transform.GetComponent<CharacterController2D>() )
{
waveManager.PlayerDead();
}
}
Trzeba wprowadzić do projektu różne warstwy i ustawić macierz kolizji.
Nie trzeba wtedy sprawdzić czy wróg, który w coś dobił, uderzył akurat w gracza.
5. Pole dla Prefaba.
Prefab nie musi być deklarowany jako GameObject
.
Tutaj autor poradnika tworzy nowy obiekt z prefaba metodą Instantiate()
.
A potem wyciąga z tego obiektu komponent Bullet
.
Gdyby prefab był od początku zadeklarowany jako Bullet
(a nie GameObject), to wywołanie Instantiate()
od razu dałoby nam referencję do komponentu Bullet
utworzonego obiektu. Nie trzeba by było wywoływać metody GetComponent<Bullet>()
.
Tak poza tym, ja ten problem też załatwiłbym macierzą kolizji. Jeśli chciałbym, żeby bronie gracza strzelały tymi samymi pociskami, co bronie wrogów, to moje skrypty ustawiałyby tylko warstwy obiektów pocisków, w momencie wystrzału.
6. Uruchamianie Coroutine
.
W kodzie trafiłem na wyjątkowo brzydki sposób uruchamiania Coroutine
.
waveCoroutine = StartCoroutine(nameof(WaveController));
Skoro wiemy jaka funkcja będzie wykonywana, to nie przekazujmy jej nazwy, tylko jej wywołanie.
waveCoroutine = StartCoroutine(WaveController());
Jak na razie THE END ;)