当前位置: 首页 > 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. 优化性能关键点

相关文章:

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

玩家控制脚本设计思路 1. 代码演变过程 1.1 初始阶段&#xff1a;单一Player类实现 最初的设计可能是一个包含所有功能的Player类&#xff1a; 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‘

个人博客地址&#xff1a;Python3 ImportError: cannot import name XXX from XXX | 一张假钞的真实世界 例如如下错误&#xff1a; $ 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 原文&#xff1a;https://github.com/zimoyin/StudyNotes-master/blob/master/compose-multiplatform/compose.md Compose Multiplatform 是 JetBrains 为桌面平台&#xff08;macOS&#xff0c;Linux&#xff0c;Windows&#xff09;和Web编写Kotlin UI…...

【R语言】t检验

t检验&#xff08;t-test&#xff09;是用于比较两个样本均值是否存在显著差异的一种统计方法。 t.test()函数的调用格式&#xff1a; t.test(x, yNULL, alternativec("two.sided", "less", "greater"), mu0, pairedFALSE, var.equalFALSE, co…...

flutter ListView Item复用源码解析

Flutter 的 ListView 的 Item 复用机制是其高性能列表渲染的核心&#xff0c;底层实现依赖于 Flutter 的渲染管线、Element 树和 Widget 树的协调机制。以下是 ListView 复用机制的源码级解析&#xff0c;结合关键类和核心逻辑进行分析。 1. ListView 的底层结构 ListView 的复…...

Spring Boot 配置 Mybatis 读写分离

JPA 的读写分离配置不能应用在 Mybatis 上, 所以 Mybatis 要单独处理 为了不影响原有代码, 使用了增加拦截器的方式, 在拦截器里根据 SQL 的 CRUD 来路由到不同的数据源 需要单独增加Mybatis的配置 Beanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) t…...

网络初识-

网络的相关概念 一、局域网和广域网 将各种计算机、外部设备等相互连接起来&#xff0c;实现在这个范围内数据通信和资源共享的计算机网络。它的覆盖范围通常在几百米到几公里之内。例如&#xff0c;一个小型企业的办公室&#xff0c;通过交换机将多台电脑连接在一起&#xf…...

DNS污染:网络世界的“隐形劫持”与防御

在互联网的底层架构中&#xff0c;DNS&#xff08;域名系统&#xff09;如同数字世界的“导航员”&#xff0c;将用户输入的域名翻译成机器可读的IP地址。然而&#xff0c;DNS污染&#xff08;DNS Poisoning&#xff09;正像一场无声的“地址篡改”危机&#xff0c;威胁着全球网…...

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与时间片多线程的问题

在多核处理器中&#xff0c;每个核心可以独立运行一个线程。操作系统负责管理和调度这些线程&#xff0c;以确保高效利用处理器资源。下面详细解释如何获取时间片以及四个线程如何在四个核心上同时工作。 ### 时间片和调度 #### 1. 时间片&#xff08;Time Slice&#xff09;…...

电脑出现蓝屏英文怎么办?查看修复过程

电脑出现蓝屏英文是一种常见的电脑故障&#xff0c;它通常表示电脑遇到了严重的错误&#xff0c;需要停止运行以防止进一步的损坏。电脑蓝屏英文的原因可能有很多&#xff0c;比如硬件故障、驱动程序错误、系统文件损坏、病毒感染等。那么&#xff0c;当电脑出现蓝屏英文时&…...

安卓基础(第一集)

SharedPreferences&#xff08;本地存储简单数据&#xff09; 在 Android 中&#xff0c;SharedPreferences 用于存储小型数据。 &#xff08;1&#xff09;存储数据 // 获取 SharedPreferences 对象 SharedPreferences sharedPreferences getSharedPreferences("MyPre…...

【从零开始入门unity游戏开发之——C#篇56】C#补充知识点——模式匹配

考虑到每个人基础可能不一样,且并不是所有人都有同时做2D、3D开发的需求,所以我把 【零基础入门unity游戏开发】 分为成了C#篇、unity通用篇、unity3D篇、unity2D篇。 【C#篇】:主要讲解C#的基础语法,包括变量、数据类型、运算符、流程控制、面向对象等,适合没有编程基础的…...

【数据可视化-16】珍爱网上海注册者情况分析

&#x1f9d1; 博主简介&#xff1a;曾任某智慧城市类企业算法总监&#xff0c;目前在美国市场的物流公司从事高级算法工程师一职&#xff0c;深耕人工智能领域&#xff0c;精通python数据挖掘、可视化、机器学习等&#xff0c;发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…...

c/c++蓝桥杯经典编程题100道(21)背包问题

背包问题 ->返回c/c蓝桥杯经典编程题100道-目录 目录 背包问题 一、题型解释 二、例题问题描述 三、C语言实现 解法1&#xff1a;0-1背包&#xff08;基础动态规划&#xff0c;难度★&#xff09; 解法2&#xff1a;0-1背包&#xff08;空间优化版&#xff0c;难度★…...

电赛DEEPSEEK

以下是针对竞赛题目的深度优化方案&#xff0c;重点解决频率接近时的滤波难题和相位测量精度问题&#xff1a; 以下是使用NI Multisim 14.3实现本项目的详细解决方案&#xff1a; 一、基础要求实现方案&#xff08;模块化设计&#xff09; 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+实操图+案例巩固 通俗易懂版~)

绪论​ 每日激励&#xff1a;“唯有努力&#xff0c;才能进步” 绪论​&#xff1a; 本章是MySQL中常见的函数&#xff0c;利用好函数能很大的帮助我们提高MySQL使用效率&#xff0c;也能很好处理一些情况&#xff0c;如字符串的拼接&#xff0c;字符串的获取&#xff0c;进制…...

浅谈 React Hooks

React Hooks 是 React 16.8 引入的一组 API&#xff0c;用于在函数组件中使用 state 和其他 React 特性&#xff08;例如生命周期方法、context 等&#xff09;。Hooks 通过简洁的函数接口&#xff0c;解决了状态与 UI 的高度解耦&#xff0c;通过函数式编程范式实现更灵活 Rea…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动

一、前言说明 在2011版本的gb28181协议中&#xff0c;拉取视频流只要求udp方式&#xff0c;从2016开始要求新增支持tcp被动和tcp主动两种方式&#xff0c;udp理论上会丢包的&#xff0c;所以实际使用过程可能会出现画面花屏的情况&#xff0c;而tcp肯定不丢包&#xff0c;起码…...

shell脚本--常见案例

1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件&#xff1a; 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...

第25节 Node.js 断言测试

Node.js的assert模块主要用于编写程序的单元测试时使用&#xff0c;通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试&#xff0c;通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面

代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口&#xff08;适配服务端返回 Token&#xff09; export const login async (code, avatar) > {const res await http…...

ArcGIS Pro制作水平横向图例+多级标注

今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作&#xff1a;ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等&#xff08;ArcGIS出图图例8大技巧&#xff09;&#xff0c;那这次我们看看ArcGIS Pro如何更加快捷的操作。…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台

🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列&#xff0c;以便知晓哪些列包含有价值的数据&#xff0c;…...

10-Oracle 23 ai Vector Search 概述和参数

一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI&#xff0c;使用客户端或是内部自己搭建集成大模型的终端&#xff0c;加速与大型语言模型&#xff08;LLM&#xff09;的结合&#xff0c;同时使用检索增强生成&#xff08;Retrieval Augmented Generation &#…...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)

RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发&#xff0c;后来由Pivotal Software Inc.&#xff08;现为VMware子公司&#xff09;接管。RabbitMQ 是一个开源的消息代理和队列服务器&#xff0c;用 Erlang 语言编写。广泛应用于各种分布…...