New beginning. Log #11 (Unity, C#)
28.02.2018

    In previous log I mentioned low poly and made few 3D models for my settlement. However I was not satisfied with my terrain model. My idea of the terrain is more faceted terrain. There is asset for Unity, which can create the exact terrain only from height map. Unfortunately it's not free, but I am willing to spend 15€ for it. The asset is called Low Poly Terrain, its available on Unity Asset store, and only with height map, eventually texture, setting some parameters I can generate terrain. But let's not talk about it right now, for my purposes is one piece of terrain, with NavMesh, enough.

    Next thing I didn't like is player character, it's not exactly low poly. When I was searching on google for some inspiration I bumped into ideal 3D model, that saved me lots of work, because it was already rigged. For now I created only Run (more like trot) and Idle animation and textured the character. I also changed stance to importing models into Unity. Most of my models I save as -blend instead of .fbx, it saves some time when tweaking models. To preserve scale and dimensions when creating models I only append character mesh into new scene, after setting proper dimensions I delete mesh.

    At this point I am furhter with game than logs, therefore I need to describe things I have already done. I suppose I will continue in this sort of attitude, it actually can prevent from changings logs often. Because I change things in game a lot, based on newly added assets and scripts. New version of Unity brings several new features like new way of camera post processing and others.

    At the end of previous log I said I will add new 3D models into Unity. It's nothing complicated, just saving each model as new .blend file into assets folder Objects/Levels/Architecture. Settlement is in south-east corner of my terrain block. This is how it looks like in Unity.

 

Settlement

 

    I generate NavMesh for my terrain in panel Navigation, so character could move. Sepeaking of player character, I need insert its model into Unity. It's the same approach as for buildings. Only different is, that in import settings I need define animations and set loop time and loop pose, in panel Animations. There is small box and there is Source Take in it, so I choose my Idle and Run animations and add them into my clips. My CharacterController remains the same, only need to drag new clips for each animation.

    To separate character model from player controllers I create new EmptyObject, rename it to Player and drag new character model under it. Into the Animator component of my character model I drag my CharacterController. For character controller I rewrite scripts. I took inspiration in tutorials from YouTube channel Brackeys, where is controller separated into more classes. First is PlayerMotor, controlling only NavMeshAgent and its movement.

 

[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;
        }
    }
}

 

Function MoveToObject() is same as previous, different is only the way how I evaluate if character reached the destination.

Another class is CharacterAnimation, controlling only animations.

 

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);
        }
    }
}

 

The Run animation is based on character velocity. No need to describe.

Last script is PlayerController, controlling when player clicks on something in game.

 

[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);
            }
        }
    }
}

 

Almost the same as before, only using LayerMask for defined layer of mask in inspector.

All scripts I attach to object Player. Now I am able to controll my character.

 

 

    Last thing is CameraController. In this case I also have inspiration from Brackeys channel. Because it was more elegant solution I copied almost whole code.

 

[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);
    }
}

 

    Camera is more responsive and fluent. There are comments in code for explanation. So this base it good for start. In next log I will look at UI for acces into inventory.