
Unity项目实战-Player玩家控制脚本实现
单一职责原则:每个类和方法都有明确的职责开闭原则:通过继承和抽象方法支持扩展里氏替换原则:子类可以替换父类而不影响程序正确性引入状态模式管理角色状态使用事件系统解耦模块间通信将配置数据抽离到ScriptableObject优化性能关键点。
·
玩家控制脚本设计思路
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 基类设计要点
-
组件封装
- 将
CharacterController
和Animator
组件封装在基类中 - 使用
protected
访问级别允许子类直接访问 - 在
Awake
中统一初始化
- 将
-
移动系统框架
- 定义基础移动变量(speed, move, velocity)
- 提供抽象的
HandleMovement()
方法 - 实现基础的Update循环
-
物理系统基础
- 封装重力相关参数
- 提供基础的物理运动框架
2.2 Player类实现分析
-
移动系统
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); }
-
跳跃系统
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); }
-
战斗系统
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. 总结
当前的代码架构展现了良好的面向对象设计原则:
- 单一职责原则:每个类和方法都有明确的职责
- 开闭原则:通过继承和抽象方法支持扩展
- 里氏替换原则:子类可以替换父类而不影响程序正确性
后续优化方向:
- 引入状态模式管理角色状态
- 使用事件系统解耦模块间通信
- 将配置数据抽离到ScriptableObject
- 优化性能关键点
更多推荐
所有评论(0)