Nový začátek. Log #11 (Unity, C#)
18.02.2018

     V minulém díle jsem psal o low poly a vymodeloval pár modelů pro moji vesničku. Nicméně s terénem jsem nebyl spokojený. Představoval jsem si terén s viditelnými ostrými hranami tzv "faceted". Pro Unity existuje asset, který dokáže z výškové mapy udělat přesně takový terén, naneštěstí není free, já jsem však ochotný těch 15€ za to obětovat. Asset se jmenuje Low Poly Terrain, stačí do něj jen vložit výškovou mapu, případně texturu, pohrát si s nastavením dle libosti a vygenerovat terén. Detaily generování se budu zabývat později až se vrhnu na vytváření terénu světa. Pro mé účely zatím postačí jeden blok terénu, který má NavMesh, aby se po něm hráč mohl pohybovat.

 

      Dalé se mi nelíbila postava hráče kterou jsem udělal, nezapdá do mého low poly světa. Při vyhledávání jsem na internetu našel ideální model, který mi ušetřil práci, jelikož byl rovnou i narigovaný. Vytvořil jsem tedy zatím jen animace Idle a Run (spíš klus, než běh) a postavu natexturoval. Změnil jsem také můj přístup k importování modelů do Unity. Většinu modelů ponechávám originálně jako .blend, ušetří to čas při úpravách. Abych zachoval měřítko vždy vložím do scény model hráče a podle jeho velikosti modeluju předměty, pak model hráče smažu.

 

      V téhle fázi jsem opět s hrou napřed oproti blogům, tak je budu vytvářet zpětně. Patrně se blog i nadále ponese v tomhle duchu, zabrání to případným zbytečným úpravám článků, protože často ve hře věci měním, na základě nově vytvořených věcí a dalších podmětů. Nová verze Unity s sebou přinesla nový způsob aplikace post processingu, alias efektů kamery a pár dalších drobných změn.

 

      V předchozím blogu jsem slíbil že vložím do mého světa vymodelované modely. Není to nic složitého. Jen všechny modely uložím odděleně do vlastního .blend souboru a ty uložím do assets Objects/Levels/Architecture. Vesnička bude v jiho západním rohu bloku terénu, budovy rozmístím stejně jako v byly blenderu. Takhle vesnička vypadá i s terénem.

 

Settlement

 

      Pro terén jednoduše vygeneruji NavMesh v zálžce Navigation, aby se po ní mohl pohybovat hráč. Když je řeč o hráči, je potřeba také vložit postavu do Unity. Udělám to úplně stejně jako s budovama, jen s tím rozdílem že u hráče je potřeba vyčlenit animace a nastavit jim loop time a loop pose v Unity, v záložce Animations. Tam je box kde je Source Take, kdy vyberu jednotlivé animace a kliknutím je vložím do políčka Clips. Animator Controller zůstává původní, jen pozměním zdroje animací a vložím tam aktuální. 

      Abych oddělil fyzický objekt od ovladače hráče, vložím do scény EmptyObject, přejmenuji ho na Player a pod něj přetáhnu objekt hráče. Objektu postavy, v komponentech přidělím Animator Controller předchozího hráče. Pro ovládání hráče upravím scripty které jsem předtím pro hráče vytvořil. Inspiraci jsem našel v tutoriálu z YouTube kanálu Brackeys, kde rozdělili ovládání hráče na několik tříd. První je PlayerMotor, která se stará o NavMeshAgenta a jeho pohyb. 

 

[RequireComponent(typeof(NavMeshAgent))]
[RequireComponent(typeof(PlayerController))]
public class PlayerMotor : MonoBehaviour
{
    NavMeshAgent agent;     // Reference to our NavMeshAgent
    bool pending;
    Vector3 target;

    void Start()
    {
        agent = GetComponent<NavMeshAgent>();
    }

    public void MoveToObject(Vector3 position)
    {
        GetComponent<CharacterAnimation>().StopAllAnimations();
        pending = true;
        agent.SetDestination(position);
    }

    void Update()
    {
        if (pending && agent.remainingDistance <= agent.stoppingDistance)
        {
            pending = false;
        }
    }

    
}

 

Funkce MoveToObject() je funkcí stejná jako předtím, změnil se jen způsob vyhodnocování zda hráč dorazil k cíli.

Další třídou je script CharacterAnimation, ta se stará pouze o spuštění a zastavování animací.

 

public class CharacterAnimation : MonoBehaviour
{
    Animator animator;
    NavMeshAgent navmeshAgent;

    void Start()
    {
        animator = GetComponentInChildren<Animator>();
        navmeshAgent = GetComponent<NavMeshAgent>();
    }

    void Update()
    {
        if (navmeshAgent.velocity.magnitude > 0)
        {
            animator.SetBool("run", true);
        }
        else
        {
            animator.SetBool("run", false);
        }
    }
}

 

Animace běhu je spuštěna na základě rychlosti hráče. Není potřeba nijak rozvádět.

Posledním scriptem je PlayerController která se stará o chování postavy když hráč na něco klikne.

 

[RequireComponent(typeof(NavMeshAgent))]
public class PlayerController : MonoBehaviour
{
    public LayerMask movementMask;      // The ground
    public float mouseClickDistance;
    PlayerMotor motor;      // Reference to our motor
    Camera cam;             // Reference to our camera
    RaycastHit hit;

    void Start()
    {
        motor = GetComponent<PlayerMotor>();
        cam = Camera.main;
    }

    void Update()
    {

        // If we press left mouse
        if (Input.GetMouseButtonDown(0))
        {
            // Shoot out a ray
            Ray ray = cam.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;

            GetComponent<NavMeshAgent>().stoppingDistance = 0f;

            // If we hit walkable layer
            if (Physics.Raycast(ray, out hit, mouseClickDistance, movementMask))
            {
                motor.MoveToObject(hit.point);
            }
        }
    }
}

 

      Nic nového od předchozí funkčnosti, snad jen použití LayerMask pro definování masky RayCastu. Pro tohle používám inspektor, kdy nastavuji masku podle potřeby.

Všechny tyhle scripty přiřadím objektu Player. Teď jsem schopný s postavou hýbat.

 

 

      Dalším bodem úpravy je kamera. Tady jsem získal inspiraci také na YouTube na kanálu Brackeys, kde jsem v podstatě chování kamery zkopíroval, jelikož se jednalo o daleko elegantnější řešení, než bylo moje. 

 

[ExecuteInEditMode]
public class CameraController : MonoBehaviour
{
    public Transform target;    // Target to follow (player)

    public Vector3 offset;          // Offset from the player
    public float zoomSpeed = 4f;    // How quickly we zoom
    public float minZoom = 5f;      // Min zoom amount
    public float maxZoom = 15f;     // Max zoom amount
    public float pitchSpeed = 0.1f;
    public float maxPitch = -0.5f;
    public float minPitch = -1.8f;
    public float pitch = 2f;        // Pitch up the camera to look at head

    public float yawSpeed = 100f;   // How quickly we rotate

    // In these variables we store input from Update
    private float currentZoom = 10f;
    private float currentYaw = 0f;



    void Update()
    {
        try
        {
             // Adjust our zoom based on the scrollwheel
             currentZoom -= Input.GetAxis("Mouse ScrollWheel") * zoomSpeed;
             currentZoom = Mathf.Clamp(currentZoom, minZoom, maxZoom);

             // Adjust our camera's rotation around the player
             offset.y -= Input.GetAxis("Vertical") * pitchSpeed;
             offset.y = Mathf.Clamp(offset.y, minPitch, maxPitch);
             currentYaw -= Input.GetAxis("Horizontal") * yawSpeed * Time.deltaTime;
        }
        catch (System.NullReferenceException e){}
    }

    void LateUpdate()
    {
        // Set our cameras position based on offset and zoom
        transform.position = target.position - offset * currentZoom;
        // Look at the player's head
        transform.LookAt(target.position + Vector3.up * pitch);
        // Rotate around the player
        transform.RotateAround(target.position, Vector3.up, currentYaw);
    }
}

 

      Chování kamery je v podstatě stejné, jen daleko responzivnější a plynulejší. V kódu jsou jednotlivé kroky vysvětleny. Na tomhle základu se dá dobře stavět. V příštím díle se vrhnu na vytváření uživatelského rozhraní (UI) pro přístup do inventáře.