玩家控制脚本设计思路

1. 代码演变过程

1.1 初始阶段:单一Player类实现

最初的设计可能是一个包含所有功能的Player类:

public class Player : MonoBehaviour
{
    private CharacterController controller;
    private Animator animator;
    [SerializeField] private float speed = 4f;
    private Vector3 move;
    private float gravity = -9.81f;
    private Vector3 velocity;
    
    void Awake()
    {
        controller = GetComponent<CharacterController>();
        animator = GetComponent<Animator>();
    }
    
    void Update()
    {
        HandleMovement();
        HandleGravity();
    }
    
    private void HandleMovement()
    {
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        move = new Vector3(horizontal, 0, vertical);
        // ... 其他移动逻辑
    }
}

这种设计的问题:

  • 所有功能耦合在一起
  • 代码复用性差
  • 难以扩展新角色类型
  • 维护成本高

1.2 第一次重构:提取基类

识别出通用功能,创建抽象基类:

public abstract class Character : MonoBehaviour
{
    protected CharacterController controller;
    protected Animator animator;
    [SerializeField] protected float speed = 4f;
    protected Vector3 move;
    protected float gravity = -9.81f;
    protected Vector3 velocity;
    
    protected virtual void Awake()
    {
        controller = GetComponent<CharacterController>();
        animator = GetComponent<Animator>();
    }
    
    protected virtual void Update()
    {
        HandleMovement();
    }
    
    protected abstract void HandleMovement();
}

1.3 当前架构:功能模块化

1.3.1 Character基类(最终版本)
public abstract class Character : MonoBehaviour
{
    // 基础组件
    protected CharacterController controller;
    protected Animator animator;
    
    // 移动相关参数
    [SerializeField] protected float speed = 4f;
    protected Vector3 move;
    
    // 物理相关参数
    protected float gravity = -9.81f;    
    protected Vector3 velocity; 
    
    protected virtual void Awake()
    {
        controller = GetComponent<CharacterController>();
        animator = GetComponent<Animator>();
    } 

    protected virtual void Update()
    {
        HandleMovement();
    }
    
    // 抽象方法强制子类实现
    protected abstract void HandleMovement();
}
1.3.2 Player类(最终版本)
public class Player : Character
{
    #region 移动系统
    [SerializeField] private float speedMax = 6f;
    private float currentSpeed;
    private bool isShift;
    #endregion
    
    #region 跳跃系统
    private bool isGrounded;
    [SerializeField] private float jumpHeight = 3f;
    [SerializeField] private LayerMask groundMask;
    [SerializeField] private Transform groundCheck;
    [SerializeField] private float groundDistance = 0.15f;
    #endregion
    
    #region 攻击系统
    private bool isAttacking;
    private float lastAttackTime;
    #endregion

    protected override void Update()
    {
        isGrounded = Physics.CheckSphere(groundCheck.position, groundDistance, groundMask);
        base.Update();
        HandleGravity();
        HandleAttack();
    }

    #region 移动系统实现
    protected override void HandleMovement()
    {
        if(isAttacking)
        {
            move.Set(0, 0, 0);
            animator.SetFloat("Speed", 0);
            return;
        }
        
        // 基础移动输入
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        move = new Vector3(horizontal, 0, vertical);
        
        // 移动规范化
        if(move.magnitude > 1f)
        {
            move = move.normalized;
        }
        
        // 45度视角调整
        move = Quaternion.Euler(0, -45f, 0) * move;
        
        // 角色朝向
        if(move != Vector3.zero)
        {
            transform.rotation = Quaternion.LookRotation(move);
        }
        
        // 冲刺系统
        HandleSprint();
        
        // 应用移动
        controller.Move(move * currentSpeed * Time.deltaTime);
        
        // 动画控制
        animator.SetFloat("Speed", move.magnitude);
        animator.SetBool("IsShift", isShift);
    }
    
    private void HandleSprint()
    {
        if(Input.GetKey(KeyCode.LeftShift))
        {
            currentSpeed = speedMax;
            isShift = true;
        }
        else 
        {
            currentSpeed = speed;
            isShift = false;
        }
    }
    #endregion

    #region 重力系统实现
    private void HandleGravity()
    {
        animator.SetBool("isAir", !isGrounded);
        
        if(isGrounded && Input.GetButtonDown("Jump"))
        {
            velocity.y = jumpHeight;
        }
        
        velocity.y += gravity * Time.deltaTime;
        controller.Move(velocity * Time.deltaTime);
    }
    #endregion

    #region 战斗系统实现
    private void HandleAttack()
    {
        if(Input.GetButtonDown("Fire1"))
        {
            if(!isAttacking)
            {
                isAttacking = true;
                animator.SetTrigger("Attack");
            }
            else
            {
                string triggerName = animator.GetCurrentAnimatorClipInfo(0)[0].clip.name;
                float triggerProgress = animator.GetCurrentAnimatorStateInfo(0).normalizedTime;
                
                // 连击系统
                if(triggerName != "LittleAdventurerAndie_ATTACK_03" && 
                   triggerProgress > 0.3f && triggerProgress < 0.7f)
                {
                    animator.SetTrigger("Attack");
                }
            }
        }
    }

    // 动画事件触发的特效
    public void PlayBlade01() => VFXManager.instance.Play("Particle Sword Blade 01");
    public void PlayBlade02() => VFXManager.instance.Play("Particle Sword Blade 02");
    public void PlayBlade03() => VFXManager.instance.Play("Particle Sword Blade 03");
    
    private void OnAttackEnd()
    {
        isAttacking = false;
    }
    #endregion
}

2. 详细设计分析

2.1 基类设计要点

  1. 组件封装

    • CharacterControllerAnimator组件封装在基类中
    • 使用protected访问级别允许子类直接访问
    • Awake中统一初始化
  2. 移动系统框架

    • 定义基础移动变量(speed, move, velocity)
    • 提供抽象的HandleMovement()方法
    • 实现基础的Update循环
  3. 物理系统基础

    • 封装重力相关参数
    • 提供基础的物理运动框架

2.2 Player类实现分析

  1. 移动系统

    protected override void HandleMovement()
    {
        // 1. 状态检查
        if(isAttacking) { ... }
        
        // 2. 输入处理
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        
        // 3. 移动计算
        move = new Vector3(horizontal, 0, vertical);
        
        // 4. 规范化处理
        if(move.magnitude > 1f) { move = move.normalized; }
        
        // 5. 视角调整
        move = Quaternion.Euler(0, -45f, 0) * move;
        
        // 6. 朝向控制
        if(move != Vector3.zero) { ... }
        
        // 7. 移动应用
        controller.Move(move * currentSpeed * Time.deltaTime);
        
        // 8. 动画更新
        animator.SetFloat("Speed", move.magnitude);
    }
    
  2. 跳跃系统

    private void HandleGravity()
    {
        // 1. 地面检测
        animator.SetBool("isAir", !isGrounded);
        
        // 2. 跳跃输入
        if(isGrounded && Input.GetButtonDown("Jump"))
        {
            velocity.y = jumpHeight;
        }
        
        // 3. 重力应用
        velocity.y += gravity * Time.deltaTime;
        controller.Move(velocity * Time.deltaTime);
    }
    
  3. 战斗系统

    private void HandleAttack()
    {
        // 1. 攻击输入检测
        if(Input.GetButtonDown("Fire1"))
        {
            // 2. 状态检查
            if(!isAttacking)
            {
                // 3. 开始攻击
                isAttacking = true;
                animator.SetTrigger("Attack");
            }
            else
            {
                // 4. 连击处理
                string triggerName = animator.GetCurrentAnimatorClipInfo(0)[0].clip.name;
                float triggerProgress = animator.GetCurrentAnimatorStateInfo(0).normalizedTime;
                
                if(可以连击条件)
                {
                    animator.SetTrigger("Attack");
                }
            }
        }
    }
    

3. 系统交互

3.1 状态管理

  • 移动状态与攻击状态互斥
  • 跳跃状态可以与其他状态叠加
  • 使用布尔值控制状态转换

3.2 动画系统

  • 使用参数控制动画过渡
    • Speed: 控制移动动画
    • IsShift: 控制冲刺动画
    • isAir: 控制跳跃动画
    • Attack: 触发攻击动画

3.3 特效系统

  • 通过动画事件触发特效
  • 使用VFXManager统一管理特效播放

4. 总结

当前的代码架构展现了良好的面向对象设计原则:

  1. 单一职责原则:每个类和方法都有明确的职责
  2. 开闭原则:通过继承和抽象方法支持扩展
  3. 里氏替换原则:子类可以替换父类而不影响程序正确性

后续优化方向:

  1. 引入状态模式管理角色状态
  2. 使用事件系统解耦模块间通信
  3. 将配置数据抽离到ScriptableObject
  4. 优化性能关键点
Logo

分享前沿Unity技术干货和开发经验,精彩的Unity活动和社区相关信息

更多推荐