Skocz do zawartości

Przeszukaj forum

Pokazywanie wyników dla tagów 'GameDev'.

  • Szukaj wg tagów

    Wpisz tagi, oddzielając przecinkami.
  • Szukaj wg autora

Typ zawartości


Kategorie forum

  • Elektronika i programowanie
    • Elektronika
    • Arduino i ESP
    • Mikrokontrolery
    • Raspberry Pi
    • Inne komputery jednopłytkowe
    • Układy programowalne
    • Programowanie
    • Zasilanie
  • Artykuły, projekty, DIY
    • Artykuły redakcji (blog)
    • Artykuły użytkowników
    • Projekty - DIY
    • Projekty - DIY roboty
    • Projekty - DIY (mini)
    • Projekty - DIY (początkujący)
    • Projekty - DIY w budowie (worklogi)
    • Wiadomości
  • Pozostałe
    • Oprogramowanie CAD
    • Druk 3D
    • Napędy
    • Mechanika
    • Zawody/Konkursy/Wydarzenia
    • Sprzedam/Kupię/Zamienię/Praca
    • Inne
  • Ogólne
    • Ogłoszenia organizacyjne
    • Dyskusje o FORBOT.pl
    • Na luzie

Kategorie

  • Quizy o elektronice
  • Quizy do kursu elektroniki I
  • Quizy do kursu elektroniki II
  • Quizy do kursów Arduino
  • Quizy do kursu STM32L4
  • Quizy do pozostałych kursów

Szukaj wyników w...

Znajdź wyniki, które zawierają...


Data utworzenia

  • Rozpocznij

    Koniec


Ostatnia aktualizacja

  • Rozpocznij

    Koniec


Filtruj po ilości...

Data dołączenia

  • Rozpocznij

    Koniec


Grupa


Imię


Strona

Znaleziono 1 wynik

  1. Ten artykuł jest częścią serii "Tworzenie gier" #1 - Praca w game-dev'ie #2 - Zanim zaczniesz pisać kod... #3 - Przygotowania #4 - Wreszcie coś piszemy? (właśnie to czytasz) #5 - API i praktyki W tym rozdziale Napiszemy prostą gierkę, w której chodzimy postacią w sposób zbliżony do gatunku H&S (np. Diablo), rzucamy zaklęcie oraz zabijamy złe gobliny. Ten artykuł bierze udział w naszym konkursie! 🔥 Na zwycięzców czekają karty podarunkowe Allegro, m.in.: 2000 zł, 1000 zł i 500 zł. Potrafisz napisać podobny poradnik? Opublikuj go na forum i zgłoś się do konkursu! Czekamy na ciekawe teksty związane z elektroniką i programowaniem. Sprawdź szczegóły » Zaczynamy? Na początku musimy stworzyć sobie projekt w Unity. Nie będę się nad tym dłużej rozwodził, bo to jest dość banalne. Instalujemy Unity - ja korzystam z wersji 2020.2.7, aczkolwiek każda nowsza powinna być również kompatybilna. By tego dokonać musimy pobrać i zainstalować Unity Hub. Wewnątrz również musimy przypisać darmową licencję do konta (Unity Personal). Następnie możemy pobrać edytor - klikamy zakładkę "Installs" i przycisk "Add". Postępujemy zgodnie z zaleceniami instalatora. Następnie przechodzimy do zakładki "Projects" i klikamy przycisk "New..." - przycisk "Add" służy do dodawania projektu z dysku. Wybieramy projekt 3D i go tworzymy. Możemy również wybrać dowolną nazwę i lokalizację dla naszego projektu. Przykładowe na powyższym zrzucie ekranu. Krok następny Teraz powinien nam się uruchomić edytor. Okienka niekoniecznie będą w tym rozmieszczeniu, aczkolwiek powinny być wszystkie umieszczone na ekranie (czasem można je przełączyć). Skrócony opis - Scene (scena) to miejsce, gdzie umieszczamy nasze obiekty i je przesuwamy 🙂 Game (gra) - to miejsce gdzie widzimy widok, który zobaczy nasz gracz Inspector (inspektor) - narzędzie pozwalające edytować zmienne w skryptach Profiler - narzędzie pozwalające sprawdzić czemu gra tak mocno zacina 🙂 Navigation - to panel od AI, tym zajmiemy się później Project - pliki projektu np. grafika, modele etc. Hierarchy - hierarchia sceny, zależności obiektów - obiekty podrzędne są przesuwane razem z obiektami nadrzędnymi Console - konsola z błędami (i nie tylko) Co dalej? Teraz musimy wrzucić nasze Assety - wedle wcześniejszej listy - Spellie oraz narzędzia do AI. W Spellie i narzędzia od AI (Odin i DOTween są zbędne w naszej grze, aczkolwiek odruchowo je dodałem 🙂). By dodać Spellie pobieramy archiwum z GitHuba i przerzucamy folder Scripts z podfolderu Assets do naszego okienka Project. Podobnie postępujemy z archiwum NavMeshComponents. NavMeshComponents.zip By dodać assety z Asset Store musimy otworzyć Package Manager i pobrać z niego odpowiednie Assety. Warto zaznaczyć, że Package Manager pozwala zarządzać pakietami z różnych źródeł, więc musimy wybrać wcześniej Asset Store jako nasze źródło. Gdy już pobierzemy pakiet (przycisk Download) musimy go zaimportować (Import). To potrafi chwilę potrwać - można zaparzyć herbatkę 🙂 Import Simple Fantasy trwał około 2 minut. Design poziomu W tym nie ma dużej filozofii 🙂 Otwieramy okienko inspectora, gdyż bywa niekiedy przydatne. By umieścić obiekt na scenie po prostu przeciągamy go z okienka Project na okienko sceny i gotowe 😉 Potem mamy strzałeczki do przesuwania go w konkretne miejsce. Jak można zauważyć moja scena ma widok izometryczny, a Twoja nie. Widok przełączasz białą kosteczką w prawym górnym rogu sceny. Jest to bardzo praktyczny przycisk 🙂 By się nie denerwować przy ustawianiu obiektów polecam przełączyć pasek w tryb Global i zablokować przypinanie do siatki (magnes z siatką). Na pasku również mamy przyciski od przesuwania, obracania, skalowania, skalowania interfejsu UI oraz dodatkowe narzędzia. Każdy z przycisków ma intuicyjną ikonkę. Obiekt również możemy obracać/przesuwać w sekcji "Transform" inspektora podając odpowiednie parametry. Należy zwrócić uwagę, że rotacja jest podana w kątach Eulera, jeżeli ktoś zaspał na matematyce polecam się zapoznać z tą treścią 🙂 lub na chłopski rozum - X obraca wokół czerwonej strzałki, Y wokół zielonej, Z wokół niebieskiej 😉 Woda na poziomie Po dodaniu na poziom wody zmieniamy jej warstwę na TransparentFX - ta warstwa będzie przez nas wykorzystywana dla obiektów, po których gracz nie powinien chodzić. Ustawianie warstwy Warstwy w Unity służą do określania zależności fizycznych między obiektami, lub grupowania obiektów o takich samych parametrach (np. brak NavMesha) Teraz co? Warto, by wszystkie obiekty ze sceny podpiąć do dodatkowego pustego - Create > Empty GameObject. Następnie zaznaczamy obiekty, które chcemy przenieść i przenosimy je do nowego obiektu. Możemy również zmienić mu nazwę 🙂 Teraz przydałoby się zdefiniować "gdzie" może ruszać się gracz. W tym celu dodajemy komponent "NavMeshSurface" do naszej grupy obiektów od poziomu i klikamy w nim przycisk "Bake". Prawidłowy NavMesh I teraz chwila o okienku Navigation - w tym okienku możemy definiować "agentów" - czyli rodzaje postaci. My wykorzystamy standardowego Humanoida zmieniając mu tylko Radius na 1 oraz Height na 2. Tyle nam wystarczy 🙂 I znów klikamy przycisk "Bake" w NavMeshSurface (Inspector, musimy zaznaczyć obiekt grupujący elementy naszego poziomu). Więcej informacji o AI: tutaj Może dodać by gracza? Teraz na scenę możemy przesunąć postać gracza oraz przeciwnika. Akurat nasze pliki są miłe, gdyż zawierają cały zestaw animacji, co znacząco ułatwia nam pracę, gdyż nie musimy męczyć się z Animatorem 🙂 Okienko animatora Nie będę się nad tym rozwodził, tylko podeślę materiał, który szeroko opisuje to zagadnienie Okej... może wreszcie napiszę kod?! Tak, to jest dobry moment. Teraz przechodzimy do okienka Project, w którym w folderze Scripts dodajemy folder Game. Przechodzimy do nowo utworzonego folderu i dodajemy plik typu "C# Script" o nazwie "Player". Dwuklik na pliku powinien otworzyć go w przypisanym edytorze - będzie to Visual Studio, MonoDevelop albo Rider. Przykładowy edytor kodu 😉 Tutaj nad skryptem nie będę się rozwodził - musimy zaimplementować interfejs IEntity oraz opisać zachowanie gracza. Gotowe skrypty będą załączone w archiwach. Stage1.zip - zrobiony jest ruch gracza oraz kamera Stage2.zip - dodana jest implementacja rzucania zaklęć oraz parę poprawek błędów Stage3.zip - dodane jest AI przeciwników 🙂 Jak zaimplementować postać gracza? W naszym przypadku wystarczy zaimplementować interfejs IEntity. Skrypt gracza będzie wtedy gotowy 🙂 Jest on niemal identyczny z implementacją zawartą w przykładach Spellie. Oprócz implementacji interfejsu warto dodać następujące zmienne i metody: public class Player : MonoBehaviour, IEntity { // Zmienne private NavMeshAgent _nma; // Opis życia i stanu public int health = 100; public int maxHealth = 100; public bool isDead = false; // Dla AI i animacji public Vector3 target; public bool casting; // Wczytaj podstawowe parametry private void Awake() { _nma = GetComponent<NavMeshAgent> (); target = transform.position; } private void Update() { if (_nma.enabled) // Jeżeli AI jest aktywne _nma.SetDestination(target); // Ustal cel AI na aktualny cel ustawiony w zmiennej target } /* Miejsce na implementacje interfejsów Spellie :D */ } Te metody automatycznie aktualizują cel AI, co znacząco ułatwia poruszanie graczem 🙂 Następny skrypt to CameraFollow.cs - kamera śledząca postać gracza. By tego dokonać w metodzie "Awake" szukamy obiektu "Player" (FindObjectOfType<Player>) oraz w metodzie Update aktualizujemy pozycję kamery względem pozycji gracza (player.transform.position + new Vector(-2.5f, 5f, -2.5f). W naszym przypadku da to rzut na postać gracza w formie izometrycznej. Należy również pamiętać, by odpowiednio obrócić kamerę rotation: (45f, 45f, 0f). public class CameraFollow: MonoBehaviour { private Player _player; private void Awake() { _player = FindObjectOfType<Player>(); } private void Update() { transform.position = _player.transform.position + new Vector3(-2.5f, 5f, -2.5f); } } Ruch gracza Oprócz tego należy też dodać graczowi zewnętrzny skrypt o nazwie "NavMeshAgent". Jest to skrypt AI służący do Pathfindingu. Teraz w skrypcie PlayerMovement.cs w metodzie Update (oczywiście po uprzednim sprawdzeniu czy gracz jest wciąż żywy 😄) gdy klikniemy LPM (Input.GetMouseButton(0)) wykonujemy śledzenie promienia. Nie będzie to łatwe zadanie, ponieważ musimy śledzić go z miejsca gdzie kliknęła myszka. public class PlayerMovement : MonoBehaviour { // Zmienne private Camera _cam; private Player _player; // Wczytanie referencji do skryptów private void Awake() { _cam = FindObjectOfType<Camera>(); _player = GetComponent<Player>(); } public void Update() { // Jeżeli gracz jest martwy to raczej się nie powinien ruszać... bo nie jest zombie if (_player.isDead) return; // Jeżeli LMB jest wciśnięty (nie został, ale JEST - trzymanie przycisku będzie aktualizowało target gracza w każdej klatce gry) if (Input.GetMouseButton(0)) { // Podręczne zmienne RaycastHit hit; var transform1 = _cam.transform; // Wykonaj śledzenie promienia od pozycji myszki w świecie względem ułożenia wektora kamery na maksymalną odległość 100 jednostek if (Physics.Raycast(_cam.ScreenToWorldPoint(Input.mousePosition), transform1.forward, out hit, 100)) { _player.target = hit.point; // Ustaw cel gracza na miejsce uderzenia promienia w ziemię } } } } Animacje gracza? Tutaj odsyłam do gotowych skryptów (PlayerAnimator.cs). Oraz do wcześniej wspomnianego poradnika. Nie jest to nic skomplikowanego do zrobienia, gdyż wystarczy przełączać kilka zmiennych w animatorze względem tego, co aktualnie się dzieje z graczem 🙂 public class PlayerAnimator : MonoBehaviour { // Podręczne zmienne private Vector3 _lastPosition; private Player _player; private Animator _animator; // Parametry animatora private static readonly int DeathB = Animator.StringToHash("Death_b"); private static readonly int SpeedF = Animator.StringToHash("Speed_f"); private static readonly int WeaponTypeINT = Animator.StringToHash("WeaponType_int"); // Wczytanie skryptów (referencji) private void Awake() { _player = GetComponent<Player>(); _animator = GetComponent<Animator>(); } void Update() { // Animacja śmierci if (_player.isDead) { _animator.SetBool(DeathB, true); return; } // Animacja rzucania zaklęcia if (_player.casting) { _animator.SetInteger(WeaponTypeINT, 12); } else { _animator.SetInteger(WeaponTypeINT, 0); } // Animacja ruchu var position = transform.position; var speed = Vector3.Distance(position, _lastPosition) / Time.deltaTime; _animator.SetFloat(SpeedF, speed); _lastPosition = position; } } Oczywiście odpowiednie zmienne musimy dodać do gracza 🙂 Rzucanie zaklęć Tutaj nie ma również wielkiej filozofii 🙂 Otwieramy skrypt PlayerMovement.cs i dodajemy zmienne: [TextArea] public string spell1; public Spell currentSpell; private bool _canCast = true; Atrybut TextArea opisuje, że będziemy mieć więcej miejsca w inspektorze 🙂 W metodzie Awake musimy przetworzyć ten tekst na zaklęcie, więc wykonujemy parsing zgodnie z dokumentacją Spellie: currentSpell = ISLParser.ParseSpell(spell1); Dodajemy coroutine _Cast() public IEnumerator _Cast() { // Wyłącz NavMeshAgent podczas rzucania zaklęcia, zaimplementuj w Player.cs _player.EnableNavigation(false); // Blokada rzucenia drugiego zaklęcia w tym samym czasie _canCast = false; // Włącz animację rzucania zaklęcia _player.casting = true; yield return new WaitForSeconds(1f); // Odczekaj 1s // Rzuć aktualne zaklęcie zgodnie z dokumentacją Spellie currentSpell.Cast(_player, _player, _player); // Odwróć wszystko z początku :) _player.casting = false; _canCast = true; _player.EnableNavigation(true); } Metoda EnableNavigation wyłącza AI ruchu gracza 🙂 public void EnableNavigation(bool en) { _nma.enabled = en; // Wyłącz nawigację if (!en) target = transform.position; // Zmień pozycję celu na aktualną - po włączeniu nawigacji postać nie będzie się ruszać ;) } Należy ją dodać do Player.cs 😉 No i warto byłoby dodać wykonanie Coroutine do naszego skryptu PlayerMovement pod prawy przycisk myszki 😉 // Rzuć zaklęcie przy PPM if (_canCast && Input.GetMouseButtonDown(1)) { if (currentSpell != null) // Jeżeli zaklęcie istnieje StartCoroutine(_Cast()); // Wykonaj coroutine } Jak widzimy coroutine wykonujemy poprzez metodę "StartCoroutine". W skrócie Coroutine jest metodą wykonywaną poprzez wiele klatek, dzięki czemu zmniejsza obciążenie głównego wątku w Unity 😉 Teraz zostaje tylko dodać zaklęcie, sprawdzić czy czegoś nie zapomnieliśmy zaimplementować Przykładowe zaklęcie w ISL Ostatnia prosta Teraz jedyne co nam pozostało do zrobienia to implementacja AI przeciwnika. public class EnemyAI : MonoBehaviour, IEntity, IDamageSource // Implementacja interfejsów spellie { public int health = 100; // Życie public int maxHealth = 100; // Maksymalne życie public bool isDead = false; // Czy jest martwy private NavMeshAgent _nma; // Agent AI private float _attackTimeLeft; // Licznik czasu ataku private float _attackDelay = 1f; // Czas ataku (opóźnienie) private Player _targetPlayer; // Namierzany gracz public float seekingRange = 8f; // Maksymalna odległość w której AI będzie podążało za graczem public float damageRange = 1f; // Maksymalna odległość do zadania obrażeń graczowi public int damage = 1; // Obrażenia zadawane przez tego przeciwnika public bool attacking; // Definiuje czy przeciwnik jest w trakcie ataku (dla animacji) private void Awake() // Wczytanie skryptów { _targetPlayer = FindObjectOfType<Player>(); // Znajdź gracza na scenie _nma = GetComponent<NavMeshAgent>(); // Znajdź przypisany komponent NavMeshAgent } void Update() { // Jeżeli martwy to jest martwy - AI nie działa... no chyba, że to zombie... Nie, w zombie też nie działa... if (isDead) { _nma.enabled = false; return; } // Zmniejsz licznik czasu ataku _attackTimeLeft -= Time.deltaTime; // Znajdź pozycję gracza var tgtPos = _targetPlayer.GetTransform().position; // Sprawdź czy gracz jest w zasięgu wyszukiwania if (Vector3.Distance(tgtPos, transform.position) < seekingRange) { // Jeżeli tak idź do gracza _nma.SetDestination(tgtPos); // Jeżeli gracz jest w zasięgu ataku if (Vector3.Distance(tgtPos, transform.position) < damageRange) { // I mogę zaatakować if(_attackTimeLeft <= 0f) StartCoroutine(_Attack()); // Wykonaj coroutine ataku } } } // Atak public IEnumerator _Attack() { attacking = true; // Włącz animację _attackTimeLeft = _attackDelay; // Zaktualizuj licznik _targetPlayer.Damage(damage, this); // Zadaj obrażenia yield return new WaitForSeconds(0.2f); // Odczekaj chwilę attacking = false; // Wyłącz animację } /* Tutaj powinny być implementacje interfejsów Spellie, ale kod miałby kilometr :) */ } W skrócie AI szuka gracza w określonym zasięgu, jeżeli go znajdzie to się do niego zbliża, a jak jest wystarczająco blisko zaczyna atakować z pięści - to przykład typowego AI Zombie 🙂 Oczywiście do AI musimy też dodać animator (no, chyba, że ktoś lubi gry bez animacji... niczego nie narzucam) public class EnemyAnimator : MonoBehaviour { // Zmienne :) private Vector3 _lastPosition; private EnemyAI _enemy; private Animator _animator; // Definicje hashy dla parametrów animatora private static readonly int DeathB = Animator.StringToHash("Death_b"); private static readonly int SpeedF = Animator.StringToHash("Speed_f"); private static readonly int WeaponTypeINT = Animator.StringToHash("WeaponType_int"); private static readonly int MeleeTypeINT = Animator.StringToHash("MeleeType_int"); // Wczytanie standardowych parametrów private void Awake() { _enemy = GetComponent<EnemyAI>(); _animator = GetComponent<Animator>(); _animator.SetInteger(MeleeTypeINT, 0); // Atak z pięści } void Update() { // Animacja śmierci if (_enemy.isDead) { _animator.SetBool(DeathB, true); return; } // Animacja ataku if (_enemy.attacking) { _animator.SetInteger(WeaponTypeINT, 12); } else { _animator.SetInteger(WeaponTypeINT, 0); } // Animacja ruchu var position = transform.position; var speed = Vector3.Distance(position, _lastPosition) / Time.deltaTime; _animator.SetFloat(SpeedF, speed); _lastPosition = position; } } I co? No i mamy gotową grę 🙂 Game.zip Gierka otwiera się w oknie 1024x768 🙂 (tak ją zdefiniowałem). Skan antywirusowy. Malwarebytes uznaje, że plik jest nietypowy względem ich AI, dlatego wyrzuca wirusa 😉 Więcej o tym tutaj. Jeżeli są jakieś pytania proszę śmiało zadawać 😉 I to tyle? Tak, taka gra jest praktycznie gotowa pod CTR test - wystarczy dopracować odrobię wygląd mapy, zaprojektować ułożenie pod filmik i nagrać reklamę 😉 Czas pracy nad grą - ~3 godziny 😉 Co w następnym rozdziale? Omówimy ogólne triki programistyczne oraz kilka użytecznych narzędzi dla Unity (i nie tylko) 😉
×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.