# 观前提醒

项目会在 GitHub 中开源,链接:https://github.com/Maikire/Unity/tree/main/UnityFramework/A simple ARPG character framework

# 框架设计

  • 角色状态类:角色的基础信息和基础行为(抽象类)
  • 角色的动画:因为动画参数的名字可能会改变,所以单独做一个类储存名字
  • 角色的移动方式:提供移动的方法,由输入控制类调用
  • 输入控制:处理玩家的输入方式,例如:摇杆、键鼠、手柄......(本章以这个摇杆为例)
  • 角色选择器(可选):选中目标后显示选中特效

img

# 代码

# 动画信息类

储存动画的名字

AnimationParameter
using UnityEngine;
namespace ARPGDemo.Character
{
    // 可序列化,将当前对象 “嵌入” 到脚本后,可以在编辑器中显示属性
    // 对应的,这将生成一个对象,不需要手动 new AnimationParameter ()
    [System.Serializable]
    /// <summary>
    /// AnimationParameter
    /// </summary>
    public class AnimationParameter
    {
        [Tooltip("跑步")]
        public string Run = "run";
        [Tooltip("死亡")]
        public string Death = "death";
        [Tooltip("闲置")]
        public string Idle = "idle";
        [Tooltip("复活")]
        public string Revive = "revive";
    }
}

# 角色状态类

基础信息和基础行为

CharacterStatus
using UnityEngine;
namespace ARPGDemo.Character
{
    /// <summary>
    /// CharacterStatus
    /// </summary>
    public abstract class CharacterStatus : MonoBehaviour
    {
        [Tooltip("动画信息")]
        public AnimationParameter PlayerAnimationParameter;
        [Tooltip("最大生命值")]
        public float MaxHP = 500;
        [Tooltip("生命值")]
        public float HP = 500;
        [Tooltip("最大魔力")]
        public float MaxMP = 500;
        [Tooltip("魔力")]
        public float MP = 500;
        [Tooltip("防御力")]
        public float Defense = 5;
        [Tooltip("攻击力")]
        public float AttackPower = 10;
        public void Start()
        {
            HP = MaxHP;
            MP = MaxMP;
        }
        /// <summary>
        /// 受伤
        /// </summary>
        /// <param name="damage"> 伤害值 & lt;/param>
        public virtual void Damage(float damage)
        {
            float temp = damage - Defense;
            if (temp > 0)
            {
                HP -= temp;
            }
            if (HP <= 0)
            {
                Die();
            }
        }
        /// <summary>
        /// 死亡
        /// </summary>
        public abstract void Die();
    }
}

# 角色的移动方式

提供移动的方法,由输入控制类调用

CharacterMotor
using UnityEngine;
namespace ARPGDemo.Character
{
    /// <summary>
    /// CharacterMotor
    /// </summary>
    public class CharacterMotor : MonoBehaviour
    {
        private CharacterController PlayerController;
        [Tooltip("移动速度")]
        public float MoveSpeed = 3;
        [Tooltip("转向速度")]
        public float RotationSpeed = 10;
        [Tooltip("下落速度")]
        public float DropSpeed = 1;
        private void Awake()
        {
            PlayerController = this.GetComponent<CharacterController>();
        }
        /// <summary>
        /// 注视目标方向旋转
        /// </summary>
        /// <param name="direction"> 方向 & lt;/param>
        public void LookAtTarget(Vector3 direction)
        {
            this.transform.rotation = Quaternion.Slerp(this.transform.rotation, Quaternion.LookRotation(direction), Time.deltaTime * RotationSpeed);
        }
        /// <summary>
        /// 移动
        /// </summary>
        /// <param name="direction"> 方向 & lt;/param>
        public void Movement(Vector3 direction)
        {
            Vector3 fix = direction + Vector3.down * DropSpeed;
            PlayerController.Move(fix * Time.deltaTime * MoveSpeed);
        }
    }
}

# 输入控制

处理玩家的输入

CharacterInputController
using UnityEngine;
namespace ARPGDemo.Character
{
    [RequireComponent(typeof(CharacterMotor), typeof(PlayerStatus))]
    /// <summary>
    /// CharacterInputController
    /// </summary>
    public class CharacterInputController : MonoBehaviour
    {
        [Tooltip("Joystick")]
        public VariableJoystick MainJoystick;
        [HideInInspector]
        [Tooltip("true:角色可以移动")]
        public bool IsMove;
        private CharacterMotor Player;
        private PlayerStatus Status;
        private Animator PlayerAnimator;
        private void Awake()
        {
            Player = this.GetComponent<CharacterMotor>();
            Status = this.GetComponent<PlayerStatus>();
            PlayerAnimator = this.GetComponentInChildren<Animator>();
        }
        private void Start()
        {
            IsMove = true;
        }
        private void Update()
        {
            JoystickMove();
        }
        /// <summary>
        /// 调用移动
        /// </summary>
        public void JoystickMove()
        {
            if (Status.HP <= 0 || !IsMove) return;
            Vector3 direction = Vector3.forward * MainJoystick.Vertical + Vector3.right * MainJoystick.Horizontal;
            if (direction != Vector3.zero)
            {
                // 旋转
                Player.LookAtTarget(direction);
                // 移动
                Player.Movement(direction);
                // 移动动画
                PlayerAnimator.SetBool(Status.PlayerAnimationParameter.Run, true);
                PlayerAnimator.SetBool(Status.PlayerAnimationParameter.Idle, false);
            }
            else
            {
                // 闲置动画
                PlayerAnimator.SetBool(Status.PlayerAnimationParameter.Idle, true);
                PlayerAnimator.SetBool(Status.PlayerAnimationParameter.Run, false);
            }
        }
    }
}

# 角色选择器(可选)

  • 选中目标后显示选中特效(选中特效是一个预制件,名字为 SelectTarget)
  • 使用变换组件助手类找出目标

代码如下:

CharacterSelected
using Common;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace ARPGDemo.Character
{
    /// <summary>
    /// 角色选择器
    /// 添加给有选择功能的角色
    /// </summary>
    public class CharacterSelected : MonoBehaviour
    {
        //1. 选中目标(启用目标的一个子物体用来表示选中),间隔指定时间后取消选中(禁用这个子物体)
        //2. 选中 A 目标,在自动取消选中前,如果选中 B 目标,则立即取消选中 A 目标并选中 B 目标
        //3. 选中 A 目标,在自动取消选中前,如果继续选中 A 目标,
        [Tooltip("选中特效的名字")]
        public string SelectedName = "SelectTarget";
        [Tooltip("选中时间")]
        public float SelectedTime = 3;
        private List<Transform> SelectedTarget; // 上一群被选中的目标的选中特效
        private void Start()
        {
            SelectedTarget = new List<Transform>();
        }
        /// <summary>
        /// 选中目标,开启选中特效
        /// </summary>
        /// <param name="targets"> 目标 & lt;/param>
        /// <param name="time"> 选中时间 & lt;/param>
        public void SelectTargets(Transform[] targets)
        {
            if (targets == null)
            {
                return;
            }
            // 禁用上一群被选中的目标的选中特效
            foreach (var item in SelectedTarget)
            {
                item.gameObject.SetActive(false);
            }
            // 刷新目标
            Transform[] Selected = new Transform[targets.Length];
            for (int i = 0; i < targets.Length; i++)
            {
                Selected[i] = TransformHelper.FindChildByName(targets[i], SelectedName);
                Selected[i].gameObject.SetActive(true);
            }
            // 刷新协程
            this.StopAllCoroutines();
            this.StartCoroutine(CancelSelection(Selected, SelectedTime));
            // 刷新选中特效列表
            SelectedTarget.Clear();
            SelectedTarget.AddRange(Selected);
        }
        /// <summary>
        /// 指定时间后自动取消选中
        /// </summary>
        /// <param name="target"></param>
        /// <param name="time"></param>
        /// <returns></returns>
        private IEnumerator CancelSelection(Transform[] Selected, float time)
        {
            yield return new WaitForSeconds(time);
            foreach (Transform selected in Selected)
            {
                selected.gameObject.SetActive(false);
            }
        }
        #region 使用Update实现(当前脚本自动禁用)
        //private float HidTime;
        //public void SetSelectedActive(bool status, float time)
        //{
        //    //....SetActive (status); // 设置物体激活
        //    this.enabled = status; // 设置当前脚本的激活状态
        //    if (status)
        //    {
        //        HidTime = Time.time + time;
        //    }
        //}
        //private void Update()
        //{
        //    if (HidTime <= Time.time)
        //    {
        //        SetSelectedActive(false, 0);
        //    }
        //}
        #endregion
    }
}

# 玩家状态类

  • 继承角色状态类

代码如下:

PlayerStatus
using UnityEngine;
namespace ARPGDemo.Character
{
    /// <summary>
    /// PlayerStatus
    /// </summary>
    public class PlayerStatus : CharacterStatus
    {
        private Animator PlayerAnimator;
        private void Awake()
        {
            PlayerAnimator = this.transform.GetComponentInChildren<Animator>();
        }
        /// <summary>
        /// 死亡
        /// </summary>
        public override void Die()
        {
            PlayerAnimator.SetBool(PlayerAnimationParameter.Death, true);
        }
    }
}

# 敌人状态类

  • 继承角色状态类

代码如下:

EnemyStatus
using UnityEngine;
namespace ARPGDemo.Character
{
    /// <summary>
    /// EnemyStatus
    /// </summary>
    public class EnemyStatus : CharacterStatus
    {
        private Animator EnemyAnimator;
        private void Awake()
        {
            EnemyAnimator = this.transform.GetComponentInChildren<Animator>();
        }
        /// <summary>
        /// 死亡
        /// </summary>
        public override void Die()
        {
            EnemyAnimator.SetBool(PlayerAnimationParameter.Death, true);
        }
    }
}