Perspektywa pierwszoosobowa- dodawanie funkcji zdarzeń w animacji.

Ten temat bazuje na kodzie z poprzedniego tematu (zobacz) i dotyczy wywołania funkcji z kodu programu w momencie wykonywania wybranej klatki animacji. Przykład: w grze jest animowana postać rzucająca oszczepem. Powstaje problem, w którym momencie animacji wyrzucić oszczep? Ułatwieniem pozwalającym rozwiązać postawione zadanie, jest wykorzystanie zdarzeń wywoływanych w określonej animacji dla wybranej klatki ruchu animowanego obiektu.

Kliknij na obrazku aby zobaczyć krótki film

dodawanie funkcji zdarzeń w animacji Unity

Dodajemy animację rzutu

W Blenderze do naszej postaci tworzymy nową animację rzutu oszczepem lub innym dowolnym przedmiotem.

animacja rzutu Unity

Eksportujemy zmodyfikowaną postać jako plik *.fbx i importujemy w Unity. Ja swoją animację nazwałem Rzut. W oknie importu animacji ustawiamy właściwość pętli na pustą (Loop Time)

plik fbx animacja rzutu Unity

Przechodzimy do Animatora i w grafie dodajemy nowy stan animacji. Przypisujemy mu nazwę Rzut i robimy połączenia z innymi stanami. Patrz poniższa ilustracja.

Animator rzutu Unity

Stan animacji rzutu wywołamy spełnieniem takich warunków: postać musi mieć w reku włócznię oraz poprzednia animacja rzutu musi być zakończona. Oznacza to, że w kodzie programu dla wywołania rzutu muszą być spełnione jednocześnie oba logiczne warunki.

Dodajemy nowy parametr wyzwalacza (Trigger), przypisujemy mu nazwę trRzut.

Trigger Unity

Wyzwalacze mają tę właściwość, że zaraz po wykonaniu automatycznie przyjmują stan false. Pisząc kod nie musimy tego stanu zmieniać na false.

W grafie przejść animatora ustawiamy jak poniżej

grafie przejść animatora Unity

Zmiany w skrypcie AnimacjaDzikiego

Przechodzimy do skryptu AnimacjaDzikiego i dodajemy nowy element typu wyliczeniowego o nazwie RZUT. Patrz poniżej

Wskazówka:


public class AnimacjaDzikiego : MonoBehaviour
{
    //typ wyliczeniowy dla animacji
    public enum STAN_ANIMACJI { 
                         CZEKAJ,//stan 0
                         SPACER,//stan 1
                         BIEG,//stan 2
                         CZOLGANIE,//stan 3
                         SKOK,//stan 4
                         RZUT//stan 5
                         }
    public float wspolczynnikZmianyPredkosci = 1;
    Animator animator;

W skrypcie dopisujemy trzy nowe funkcje. Pierwsze dwie będą wywoływane w odpowiednich klatkach animacji eventsObrocDzide() i eventsRzucaj(). Trzecia UstawGotowoscRzutu() będzie wywołana w skrypcie KolizjeZasoby(). Po kolei wykonujemy zmiany w skryptach.

Pełny kod skryptu AnimacjaDzikiego() po zmianach.

Wskazówka:


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AnimacjaDzikiego : MonoBehaviour
{
    //typ wyliczeniowy dla animacji
    public enum STAN_ANIMACJI { 
                         CZEKAJ,//stan 0
                         SPACER,//stan 1
                         BIEG,//stan 2
                         CZOLGANIE,//stan 3
                         SKOK,//stan 4
                         RZUT//stan 5
                         }
    public float wspolczynnikZmianyPredkosci = 1;
    Animator animator;
    STAN_ANIMACJI stanAnimacji = STAN_ANIMACJI.CZEKAJ;

    public void eventsObrocDzide()
    {
        //obróć dzidę grotem do rzutu
        GameObject d=GetComponent<KolizjeZasoby>().BronObracana;
        //w zalezności jak jest zorientowna siatka, kąt rotacji może być inny
        d.transform.Rotate(new Vector3(0,180,0));
    }
    public void eventsRzucaj()
    {
        GetComponent<KolizjeZasoby>().RzucajDzida();
    }
    
    public void UstawGotowoscRzutu()
    {
        //jesli ma coś w ręce to może tym rzucać
        animator.SetTrigger("trRzut");
    }


    public STAN_ANIMACJI StanAnimacji
    {
        get { return stanAnimacji; }
        set { 
              stanAnimacji=value;
              //ustwa stan animacji z indeksu aktywnego typu wyliczeniowego
              animator.SetInteger("intStanAnimacji", (int)stanAnimacji);
            }
    }
    // Start is called before the first frame update
    void Start()
    {
        animator = GetComponent<Animator>();
    }

public void PrzelaczAnimacje(float _constV)
    {
        if (Input.GetAxis("Spacer") != 0)
        {
            StanAnimacji = STAN_ANIMACJI.SPACER;
            //ustaw predkośc na stała
            wspolczynnikZmianyPredkosci = _constV;
        }
        if (Input.GetAxis("Bieg") != 0)
        {
            StanAnimacji = STAN_ANIMACJI.BIEG;
            //zwiększ predkośc podczas biegu 2.5 razy
            wspolczynnikZmianyPredkosci = 2.5f* _constV;
        }
        if (Input.GetAxis("Czolgaj") != 0)
        {
            StanAnimacji = STAN_ANIMACJI.CZOLGANIE;
            //zmniejsz prędkośc podczas czołgania 2 razy
            wspolczynnikZmianyPredkosci = 0.5f* _constV;
        }
    }
    

    // Update is called once per frame
    void Update()
    {
        
    }
}

Przechodzimy do importowanego zasobu fbx postaci z animacjami i podpinamy zdarzenia w wybranych klatkach. Wybieramy pierwsza klatkę dla funkcji eventsObrocDzide(). Patrz poniżej

zdarzenia w klatkach animacji Unity

Klatkę odpowiedzialną za rzut wybieram taką, w której kość dłoni ma zwrot wektora odpowiadający kątowi rzutu. W kodzie programu odczytamy ten wektor ułożenia kości, co znacznie skróci implementację jak i zrezygnujemy z niepotrzebnych dodatkowych obliczeń.

Powtarzamy poprzednie czynności dla osadzenia kolejnego zdarzenia w klatce animacji. Patrz poniżej

zdarzenia w klatkach animacji Unity

Na zakończenie zatwierdź zmiany klawiszem Apply.

Modyfikujemy skrypt RuchBohatera()

Przechodzimy do skryptu RuchBohatera() i dopisujemy funkcję Rzucaj(). Rzut będzie wykonywany po wciśnięciu klawisza Lewy CTRL. Funkcja Rzut() jest wywoływana w metodzie Update(). Pełny kod skryptu po zmianie.

Wskazówka:


using System.Collections;
using System.Collections.Generic;
using Unity.Android.Gradle.Manifest;
using UnityEngine;
using static AnimacjaDzikiego;

public class RuchBohatera : MonoBehaviour
{
    public CharacterController kontroler;
    public Transform kamera;
    private bool fGrunt;
    //przyspieszenie grawitacyjne
    public float g = -9.8f;

    //współczynnik prędkości
    const float constV = 2.5f;
    public float v= constV,
                 wysSkoku=2.0f;
    Vector3 wektorPredkosci = Vector3.zero;
    public float wygladzenie = 0.1f;
    float buforBiezacegoWygladzania;
    


    public void Rzucaj()
    {
        //lewy CTRL
        if (Input.GetAxis("Fire1") != 0)
        {
            GetComponent<AnimacjaDzikiego>().StanAnimacji = STAN_ANIMACJI.RZUT;
        }
    }

    public void Skok()
    {
        if (fGrunt && Input.GetAxis("Jump") != 0)
        {
            //v=Pierwiastek(2gh)
            wektorPredkosci.y = Mathf.Sqrt(-2f * g * wysSkoku);
            GetComponent<AnimacjaDzikiego>().StanAnimacji 
                              = STAN_ANIMACJI.SKOK;
        }
    }

    void Spadaj()
    {
        wektorPredkosci.y += g * Time.deltaTime;
        if (fGrunt && wektorPredkosci.y < 0)
        {
            //nie spadaj szybciej niż 54m/s
            wektorPredkosci.y = -54f;
        }
        kontroler.Move(wektorPredkosci * Time.deltaTime);
    }
    void Ruch()
    {
        float poziom = Input.GetAxis("Horizontal");
        float pion = Input.GetAxis("Vertical");
        Vector3 kierunek = new Vector3(poziom, 0, pion).normalized;
        //czy ruch, powiedzmy wiecej niż 5 centymetrów
        if (kierunek.magnitude >= 0.05f)
        {
            //obrót postaci
            //zwróc kąt w radianach z płaszczyzny 2D- oś X a Z
            //zamień radiany na stopnie
            float katPatrzenia = Mathf.Atan2(kierunek.x, kierunek.z)
                                 *Mathf.Rad2Deg+
                                 kamera.eulerAngles.y;
            //wygladzaj obrot,
            //predkosc wygladzania wyslij do bufora
            float kat = Mathf.SmoothDampAngle(
                      transform.eulerAngles.y,
                      katPatrzenia,
                      ref wygladzenie,
                      buforBiezacegoWygladzania
                      );
            transform.rotation = Quaternion.Euler(0, kat, 0);
            Vector3 kierunekRuchu = Quaternion.Euler(0, katPatrzenia, 0)
                                    *Vector3.forward;
            kontroler.Move(kierunekRuchu.normalized * v * Time.deltaTime);
            //jest ruch to domyslnie ustawiaj na spacer
            if (GetComponent<AnimacjaDzikiego>().StanAnimacji == STAN_ANIMACJI.CZEKAJ)
              {
                GetComponent<AnimacjaDzikiego>().StanAnimacji = STAN_ANIMACJI.SPACER;
                GetComponent<AnimacjaDzikiego>().wspolczynnikZmianyPredkosci = constV;
              }
            GetComponent<AnimacjaDzikiego>().PrzelaczAnimacje(constV);
            v = GetComponent<AnimacjaDzikiego>().wspolczynnikZmianyPredkosci;
        }
        else
        {
            //pozostałe animacje przełacz
            //w skrypcie AnimacjaDzikiego
            GetComponent<AnimacjaDzikiego>().StanAnimacji = STAN_ANIMACJI.CZEKAJ;
        }
    }
    
    // Start is called before the first frame update
    void Start()
    {
        //blokuj kursor na srodku ekranu
        Cursor.lockState = CursorLockMode.Locked;
        //ukryj kursor
        //standardowo klawisz Esc przywróci widok kursora
        Cursor.visible = false;
    }

    // Update is called once per frame
    void Update()
    {
        //sprawdzaj czy jestes nad dowolnym podlozem
        fGrunt = kontroler.isGrounded;
        Ruch();
        Skok();
        Rzucaj();
        Spadaj();
    }
    
}

Modyfikacja skryptu KolizjeZasoby()

Przechodzimy do skryptu KolizjeZasoby(). Dodajemy zmienną na obracaną w ręce broń (GameObject bronObracana=null;) Tworzymy publiczną metodę ustawiania i odczytywani dla zmiennej bronObracana. Piszemy funkcję RzucajDzida() i wykonujemy zmiany w funkcji KolizjaDzida().

Pełny kod skryptu po zmianach podaję poniżej

Wskazówka:


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static AnimacjaDzikiego;

public class KolizjeZasoby : MonoBehaviour
{
    [SerializeField] float silaWartosc = 0;
    [SerializeField] GameObject slotBroniPrawy;
    //tablica prefabrykatów broni
    [SerializeField] GameObject[] prafbBroni;

    GameObject bronObracana=null;

    public GameObject BronObracana
    {
        get { return bronObracana; }
        set { bronObracana = value; }
    }

    public void RzucajDzida() 
    { 
        
        //zrob rzucana dzide z prefabrykatu
        GameObject d = Instantiate(prafbBroni[0],
                                   BronObracana.transform.position,
                                   BronObracana.transform.rotation);
        //usun obrocona dzide z reki
        Destroy(BronObracana);
        BronObracana = null;
        //ustaw predkośc oszczepu na 12 m/s
        d.GetComponent<Rigidbody>().velocity = d.transform.forward * 12.0f;
        //dla rzucanej dzidy
        //ustaw warstwe na niekolidującą z graczem
        LayerMask maskuj = LayerMask.GetMask("Gracz");
        d.GetComponent<CapsuleCollider>().excludeLayers = maskuj;
    }

    private void KolizjaDzida(ControllerColliderHit hit)
    {
        GameObject dzida=hit.gameObject;
        //usuń dzidę ze swiata gry
        Destroy(dzida);
        //daj dzide do reki bohatera
        GameObject d = Instantiate(prafbBroni[0],
                                   slotBroniPrawy.transform.position,
                                   slotBroniPrawy.transform.rotation);
        d.transform.SetParent(slotBroniPrawy.transform);
        d.GetComponent<Rigidbody>().useGravity = false;
        d.GetComponent<Rigidbody>().isKinematic = true;
        //dla podniesionej dzidy
        //ustaw warstwe na niekolidującą z graczem
        LayerMask maskuj = LayerMask.GetMask("Gracz");
        d.GetComponent<CapsuleCollider>().excludeLayers = maskuj;
        GetComponent<AnimacjaDzikiego>().UstawGotowoscRzutu();
        BronObracana=d;
    }

    private void OnControllerColliderHit(ControllerColliderHit hit)
    {
        Rigidbody rb=hit.collider.attachedRigidbody;
        if (rb != null)
        {
            if (hit.gameObject.tag == "dzida") 
                    {KolizjaDzida(hit); return; }
            //nie jest to dzida to przesuń ciało
            Vector3 silaKierunek =hit.gameObject.transform.position-
                    transform.position;
            //zeruj pion
            silaKierunek.y = 0;
            //wyznacz jednostkowy wektor kierunku wektora
            silaKierunek.Normalize();
            //przekaz siłe uderzenia
            rb.AddForceAtPosition(silaKierunek * silaWartosc,
                      transform.position,
                      ForceMode.Impulse);
        }
    }
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

Możemy uruchomić scenę i sprawdzić efekt działania po wykonanych zmianach.

animacja rzutu Unity

Jeżeli przyjrzysz się lotowi dzidy, to zauważysz, że włócznia nie ustawia się do trajektorii lotu. Fizyka parabolicznego lotu dział poprawnie (prefabrykat dzidy ma dołączony komponent ciał sztywnego- Rigidbody), ale włócznia się nie ustawia stycznie do paraboli lotu.

Skrypt ruchu po torze parabolicznym

Aby ustawienie włóczni do trajektorii lotu było prawidłowe, należy na bieżąco znać zmiany kierunku wektora prędkości w ruchu ciała w rzuci ukośnym. Można to obliczać lub w prosty sposób odczytać wektor aktualnej prędkość. Znormalizować ten wektor, a z normalnej wektora wyznaczyć kąt ułożenia włóczni.

Tworzymy nowy skrypt o nazwie RuchWloczni(). Podpinamy do prefabrykatu dzidy

Skrypt ruchu po torze parabolicznym Unity

Kod skryptu RuchDzidy()

Wskazówka:


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Animations.Rigging;

public class RuchDzidy : MonoBehaviour
{
    public bool fRuch = false;
    Rigidbody rb;

    private void Start()
    {
        rb = GetComponent<Rigidbody>();
        rb.useGravity = true;
        rb.isKinematic = false;
    }
    void Update()
    {
        if (!fRuch) return;
        //obracaj zgodnie z kierunkiem wektora predkosci 
        transform.rotation = Quaternion.LookRotation(rb.velocity.normalized);
    }
    private void OnCollisionEnter(Collision collision)
    {
        fRuch = false;
        //wbij sie w cel na przykład w grunt
        rb.velocity = Vector3.zero;
        rb.useGravity = false;
        rb.isKinematic = true;
        //pozwól aby gracz mógł zabrać rzuconą dzidę
        //usuń brak kolizji z graczem
        LayerMask maskuj = LayerMask.GetMask("Default");
        GetComponent<CapsuleCollider>().excludeLayers = maskuj;
    }
}

Przechodzimy do skryptu KolizjeZasoby(), i w funkcji RzucajDzida() dodajemy tę linijkę kodu

Wskazówka:


d.GetComponent<RuchDzidy>().fRuch = true;

Kod funkcji po zmianie

Wskazówka:


public void RzucajDzida() 
{ 
	
	//zrob rzucana dzide z prefabrykatu
	GameObject d = Instantiate(prafbBroni[0],
							   BronObracana.transform.position,
							   BronObracana.transform.rotation);
	//usun obrocona dzide z reki
	Destroy(BronObracana);
	BronObracana = null;
	d.GetComponent<RuchDzidy>().fRuch = true;
	//ustaw predkośc oszczepu na 12 m/s
	d.GetComponent<Rigidbody>().velocity = d.transform.forward * 12.0f;
	//dla rzucanej dzidy
	//ustaw warstwe na niekolidującą z graczem
	LayerMask maskuj = LayerMask.GetMask("Gracz");
	d.GetComponent<CapsuleCollider>().excludeLayers = maskuj;
}

Uruchom scenę i sprawdź lot rzuconej włóczni.

Skrypt ruchu po torze parabolicznym Unity

Lot włóczni jest poprawny. Dodatkowo wyłącznie grawitacji i ustawienie kinematyki na true po kolizji dzidy z dowolnym przedmiotem symuluje efekt wbicia w cel. Dodawanych zmian we właściwościach rzucanego przedmiotu może być bardzo dużo. Wszystko zależy od pomysłów i potrzeb sceny.

Układ okresowy- kod qr
Układ okresowy

Układ okresowy pierwiastków- darmowa aplikacja na Androida

Pobierz ze sklepu Google Play
Alkomat- wirtualny test kod qr
Alkomat- wirtualny test

Alkomat- darmowa aplikacja na Androida

Pobierz ze sklepu Google Play
Taklarz- olinowanie stałe kod qr
Olinowanie stałe- kalkulator średnic

Olinowanie stałe- darmowa aplikacja na Androida

Pobierz ze sklepu Google Play
przepis na gogfry

Przepis na gofry

zobacz
przepis na bitą śmietanę

Przepis na bitą śmietanę

zobacz