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使用效率,也能很好处理一些情况,如字符串的拼接,字符串的获取,进制…...
【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...
【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...
uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...
Razor编程中@Html的方法使用大全
文章目录 1. 基础HTML辅助方法1.1 Html.ActionLink()1.2 Html.RouteLink()1.3 Html.Display() / Html.DisplayFor()1.4 Html.Editor() / Html.EditorFor()1.5 Html.Label() / Html.LabelFor()1.6 Html.TextBox() / Html.TextBoxFor() 2. 表单相关辅助方法2.1 Html.BeginForm() …...
Web后端基础(基础知识)
BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。 优点:维护方便缺点:体验一般 CS架构:Client/Server,客户端/服务器架构模式。需要单独…...
【深度学习新浪潮】什么是credit assignment problem?
Credit Assignment Problem(信用分配问题) 是机器学习,尤其是强化学习(RL)中的核心挑战之一,指的是如何将最终的奖励或惩罚准确地分配给导致该结果的各个中间动作或决策。在序列决策任务中,智能体执行一系列动作后获得一个最终奖励,但每个动作对最终结果的贡献程度往往…...
RushDB开源程序 是现代应用程序和 AI 的即时数据库。建立在 Neo4j 之上
一、软件介绍 文末提供程序和源码下载 RushDB 改变了您处理图形数据的方式 — 不需要 Schema,不需要复杂的查询,只需推送数据即可。 二、Key Features ✨ 主要特点 Instant Setup: Be productive in seconds, not days 即时设置 :在几秒钟…...
中科院1区顶刊|IF14+:多组学MR联合单细胞时空分析,锁定心血管代谢疾病的免疫治疗新靶点
中科院1区顶刊|IF14:多组学MR联合单细胞时空分析,锁定心血管代谢疾病的免疫治疗新靶点 当下,免疫与代谢性疾病的关联研究已成为生命科学领域的前沿热点。随着研究的深入,我们愈发清晰地认识到免疫系统与代谢系统之间存在着极为复…...
