#60 - Simple character controller for Unity

Date: 2019-05-11 12:00 - c#

A simple character controller for unity which allows to have different abilities.

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

[RequireComponent(typeof(CharacterController))]
[RequireComponent(typeof(Animator))]
public class Character : Interactable {
    public delegate void DeathEvent();

    public bool IsPlayerControlled = false;
    protected List<Ability> abilities = new List<Ability>();

    public bool PreventMovement = false;
    public virtual bool CanMove {
        get { return !PreventMovement && !IsDead; }
    }
    public bool IsGrounded = true;
    public float DragGround = 10.0f;
    public float DragAir = 1.0f;
    public Vector3 Velocity = Vector3.zero;
    public Vector3 Motion = Vector3.zero;

    private bool isDead;
    public bool IsDead {
        get { return isDead; }
        set {
            if (value != isDead) {
                isDead = value;
                if (isDead && OnDeath != null)
                    OnDeath();
            }
        }
    }

    public event DeathEvent OnDeath;

    public CharacterController CharacterController { get; private set; }
	public Animator Animator { get; private set; }
    public Transform CharacterCamera;

    public T GetAbility<T>() where T: Ability {
        foreach(Ability ability in abilities) {
            if (typeof(T).IsAssignableFrom(ability.GetType()))
                return (T) ability;
        }

        return null;
    }

    public T[] GetAbilities<T>() where T: Ability {
        List<Ability> abilitiesOfType = new List<Ability>();
        foreach(Ability ability in abilities) {
            if (typeof(T).IsAssignableFrom(ability.GetType()))
                abilitiesOfType.Add(ability);
        }

        return (T[]) abilitiesOfType.ToArray();
    }

    protected void Awake() {
        Type abilityType = typeof(Ability);
        PropertyInfo characterField = abilityType.GetProperty("character", BindingFlags.NonPublic | BindingFlags.Instance);
        FieldInfo[] fields = this.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);

        foreach(FieldInfo field in fields) {
            if (field.FieldType.IsSubclassOf(typeof(Ability))) {
                Ability ability = (Ability) field.GetValue(this);
                if (ability == null) {
                    Debug.Log(string.Format("Ability {0} is null in object {1} with name", field.FieldType.Name, this, field.Name));
                }

                characterField.SetValue(ability, this, null);
                abilities.Add(ability);
            }
        }

        CharacterController = GetComponent<CharacterController>();
        Animator = GetComponent<Animator>();
    }

    public void Move(Vector3 movement) {
        this.Motion += movement;
    }

    protected void FixedUpdate() {
        Motion = Vector3.zero;

		if (Velocity.x != 0 || Velocity.z != 0) {
			float dragCoef = IsGrounded ? DragGround : DragAir;
			float dragMultiplier = 1 - dragCoef * Time.fixedDeltaTime;
			Velocity.Set(Velocity.x * dragMultiplier, Velocity.y, Velocity.z * dragMultiplier);
			if (Mathf.Abs(Velocity.x) < 0.1) Velocity.x = 0;
			if (Mathf.Abs(Velocity.z) < 0.1) Velocity.z = 0;
		}

		Velocity += Physics.gravity * Time.fixedDeltaTime;

        foreach(Ability ability in abilities) {
            if (!ability.IsExecuting && ability.Enabled && ability.CanStartAbility()) {
                ability.Start();
            }

            if (ability.IsExecuting) {
                ability.Update();
            }

            if ((ability.IsExecuting && ability.CanStopAbility()) || !ability.Enabled) {
                ability.Stop();
            }
        }

        Motion += Velocity;

        if (!IsDead)
            CharacterController.Move(Motion * Time.fixedDeltaTime);

        if (IsGrounded && !Physics.Raycast(transform.position, -transform.up, 0.20f)) {
			IsGrounded = false;
		}

        if (!IsGrounded && CharacterController.isGrounded){
			IsGrounded = true;
		}

        if (IsGrounded) {
			Velocity.y = 0;
        }

		float forwardMovement = Vector3.Project(Motion, transform.forward).magnitude;
		float rightMovement = Vector3.Project(Motion, transform.right).magnitude;

		this.Animator.SetBool("OnGround", IsGrounded);
		this.Animator.SetFloat("Forward", forwardMovement, 0.05f, Time.fixedDeltaTime);
		this.Animator.SetFloat("Right", rightMovement, 0.05f, Time.fixedDeltaTime);

		if (IsGrounded)
			this.Animator.SetFloat("Jump", 0.0f);
		else
			this.Animator.SetFloat("Jump", Velocity.y, 0.1f, Time.fixedDeltaTime);

		float forwardValue = this.Animator.GetFloat("Forward");
		if (forwardValue != 0.0f && Mathf.Abs(forwardValue) < 0.05f)
			this.Animator.SetFloat("Forward", 0.0f);

		float rightValue = this.Animator.GetFloat("Right");
		if (rightValue != 0.0f && Mathf.Abs(rightValue) < 0.05f)
			this.Animator.SetFloat("Right", 0.0f);
    }

	public override bool CanInteract(GameObject caller) {
        return false;
	}

    public override void OnInteract(GameObject other)
    {
    }
}

Previous snippet | Next snippet