当前位置: 首页 > news >正文

Unity项目实战-Player玩家控制脚本实现

玩家控制脚本设计思路

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;#endregionprotected 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. 优化性能关键点
http://www.lryc.cn/news/534872.html

相关文章:

  • CP AUTOSAR标准之ICUDriver(AUTOSAR_SWS_ICUDriver)(更新中……)
  • Python3 ImportError: cannot import name ‘XXX‘ from ‘XXX‘
  • [学习笔记] Kotlin Compose-Multiplatform
  • 【R语言】t检验
  • flutter ListView Item复用源码解析
  • Spring Boot 配置 Mybatis 读写分离
  • 网络初识-
  • DNS污染:网络世界的“隐形劫持”与防御
  • MQTT(Message Queuing Telemetry Transport)协议(三)
  • 多核cpu与时间片多线程的问题
  • 电脑出现蓝屏英文怎么办?查看修复过程
  • 安卓基础(第一集)
  • 【从零开始入门unity游戏开发之——C#篇56】C#补充知识点——模式匹配
  • 【数据可视化-16】珍爱网上海注册者情况分析
  • c/c++蓝桥杯经典编程题100道(21)背包问题
  • 电赛DEEPSEEK
  • VSOMEIP ROUTING应用和CLIENT应用之间交互的消息
  • HTML之基本布局div|span
  • Linux下学【MySQL】常用函数助你成为数据库大师~(配sql+实操图+案例巩固 通俗易懂版~)
  • 【Rabbitmq篇】高级特性----TTL,死信队列,延迟队列
  • 机器学习赋能的智能光子学器件系统研究与应用
  • 尚硅谷课程【笔记】——大数据之Linux【三】
  • Visual Studio踩过的坑
  • 教程 | MySQL 基本指令指南(附MySQL软件包)
  • 企业数据集成案例:吉客云销售渠道到MySQL
  • 网络编程 day3
  • Excel 融合 deepseek
  • 【论文笔记】Are Self-Attentions Effective for Time Series Forecasting? (NeurIPS 2024)
  • 游戏手柄Type-c方案,支持一边充电一边传输数据
  • 2. 4 模块化JDK:JDK模块结构与核心模块