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 基类设计要点
-
组件封装
- 将
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
- 优化性能关键点
相关文章:
Unity项目实战-Player玩家控制脚本实现
玩家控制脚本设计思路 1. 代码演变过程 1.1 初始阶段:单一Player类实现 最初的设计可能是一个包含所有功能的Player类: public class Player : MonoBehaviour {private CharacterController controller;private Animator animator;[SerializeField] …...
CP AUTOSAR标准之ICUDriver(AUTOSAR_SWS_ICUDriver)(更新中……)
1 简介和功能概述 该规范指定了AUTOSAR基础软件模块ICU驱动程序的功能、API和配置。 ICU驱动程序是一个使用输入捕获单元(ICU)来解调PWM信号、计数脉冲、测量频率和占空比、生成简单中断和唤醒中断的模块。 ICU驱动程序提供服务 信号边缘通知控制唤醒中断周期信号时间测…...
Python3 ImportError: cannot import name ‘XXX‘ from ‘XXX‘
个人博客地址:Python3 ImportError: cannot import name XXX from XXX | 一张假钞的真实世界 例如如下错误: $ python3 git.py Traceback (most recent call last):File "git.py", line 1, in <module>from git import RepoFile &quo…...

[学习笔记] Kotlin Compose-Multiplatform
Compose-Multiplatform 原文:https://github.com/zimoyin/StudyNotes-master/blob/master/compose-multiplatform/compose.md Compose Multiplatform 是 JetBrains 为桌面平台(macOS,Linux,Windows)和Web编写Kotlin UI…...

【R语言】t检验
t检验(t-test)是用于比较两个样本均值是否存在显著差异的一种统计方法。 t.test()函数的调用格式: t.test(x, yNULL, alternativec("two.sided", "less", "greater"), mu0, pairedFALSE, var.equalFALSE, co…...
flutter ListView Item复用源码解析
Flutter 的 ListView 的 Item 复用机制是其高性能列表渲染的核心,底层实现依赖于 Flutter 的渲染管线、Element 树和 Widget 树的协调机制。以下是 ListView 复用机制的源码级解析,结合关键类和核心逻辑进行分析。 1. ListView 的底层结构 ListView 的复…...
Spring Boot 配置 Mybatis 读写分离
JPA 的读写分离配置不能应用在 Mybatis 上, 所以 Mybatis 要单独处理 为了不影响原有代码, 使用了增加拦截器的方式, 在拦截器里根据 SQL 的 CRUD 来路由到不同的数据源 需要单独增加Mybatis的配置 Beanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) t…...

网络初识-
网络的相关概念 一、局域网和广域网 将各种计算机、外部设备等相互连接起来,实现在这个范围内数据通信和资源共享的计算机网络。它的覆盖范围通常在几百米到几公里之内。例如,一个小型企业的办公室,通过交换机将多台电脑连接在一起…...
DNS污染:网络世界的“隐形劫持”与防御
在互联网的底层架构中,DNS(域名系统)如同数字世界的“导航员”,将用户输入的域名翻译成机器可读的IP地址。然而,DNS污染(DNS Poisoning)正像一场无声的“地址篡改”危机,威胁着全球网…...

MQTT(Message Queuing Telemetry Transport)协议(三)
主题是什么 2. TCP 协议封装 tcp.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h>// 建立 TCP 连接 int tcp_connect(const char *server_ip, int s…...
多核cpu与时间片多线程的问题
在多核处理器中,每个核心可以独立运行一个线程。操作系统负责管理和调度这些线程,以确保高效利用处理器资源。下面详细解释如何获取时间片以及四个线程如何在四个核心上同时工作。 ### 时间片和调度 #### 1. 时间片(Time Slice)…...

电脑出现蓝屏英文怎么办?查看修复过程
电脑出现蓝屏英文是一种常见的电脑故障,它通常表示电脑遇到了严重的错误,需要停止运行以防止进一步的损坏。电脑蓝屏英文的原因可能有很多,比如硬件故障、驱动程序错误、系统文件损坏、病毒感染等。那么,当电脑出现蓝屏英文时&…...
安卓基础(第一集)
SharedPreferences(本地存储简单数据) 在 Android 中,SharedPreferences 用于存储小型数据。 (1)存储数据 // 获取 SharedPreferences 对象 SharedPreferences sharedPreferences getSharedPreferences("MyPre…...
【从零开始入门unity游戏开发之——C#篇56】C#补充知识点——模式匹配
考虑到每个人基础可能不一样,且并不是所有人都有同时做2D、3D开发的需求,所以我把 【零基础入门unity游戏开发】 分为成了C#篇、unity通用篇、unity3D篇、unity2D篇。 【C#篇】:主要讲解C#的基础语法,包括变量、数据类型、运算符、流程控制、面向对象等,适合没有编程基础的…...

【数据可视化-16】珍爱网上海注册者情况分析
🧑 博主简介:曾任某智慧城市类企业算法总监,目前在美国市场的物流公司从事高级算法工程师一职,深耕人工智能领域,精通python数据挖掘、可视化、机器学习等,发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…...
c/c++蓝桥杯经典编程题100道(21)背包问题
背包问题 ->返回c/c蓝桥杯经典编程题100道-目录 目录 背包问题 一、题型解释 二、例题问题描述 三、C语言实现 解法1:0-1背包(基础动态规划,难度★) 解法2:0-1背包(空间优化版,难度★…...
电赛DEEPSEEK
以下是针对竞赛题目的深度优化方案,重点解决频率接近时的滤波难题和相位测量精度问题: 以下是使用NI Multisim 14.3实现本项目的详细解决方案: 一、基础要求实现方案(模块化设计) 1. 双频信号发生电路 电路结构&…...
VSOMEIP ROUTING应用和CLIENT应用之间交互的消息
#define VSOMEIP_ASSIGN_CLIENT 0x00 // client应用请求分配client_id #define VSOMEIP_ASSIGN_CLIENT_ACK 0x01 // routing应用返回分配的client_id #define VSOMEIP_REGISTER_APPLICATION 0x02 // client应用注册someip应用 #…...
HTML之基本布局div|span
HTML基本布局使用 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"width<device-width>, initial-scale1.0"><title>布局</title> <…...

Linux下学【MySQL】常用函数助你成为数据库大师~(配sql+实操图+案例巩固 通俗易懂版~)
绪论 每日激励:“唯有努力,才能进步” 绪论: 本章是MySQL中常见的函数,利用好函数能很大的帮助我们提高MySQL使用效率,也能很好处理一些情况,如字符串的拼接,字符串的获取,进制…...

springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...
MVC 数据库
MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...

3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...