Unity组件化通信三层次:事件、接口与消息总线实战

news2026/5/22 14:10:04
1. 这不是“写个脚本就完事”的游戏逻辑——为什么90%的Unity新手在交互设计上栽在第一步你有没有试过这样写PlayerController里直接调用EnemyHealth.TakeDamage(10)EnemyAI里又反过来调用PlayerStats.GetHealth()代码跑通了测试也过了但两周后加个“中毒状态”就得改三处脚本加个“护盾机制”又得在五个地方补if判断最后连自己都记不清谁该通知谁、谁该监听谁。这不是能力问题是设计起点错了。Unity不是纯面向对象语言但它强制你活在面向对象的物理世界里——每个GameObject是实体每个Component是职责而“玩家打敌人”这个动作本质是一次跨职责边界的协作契约。我带过27个Unity新人项目其中23个在第二周卡死在“怎么让UI显示敌人血条变化”这种看似简单的问题上根源全出在没建立组件化通信的思维惯性。关键词Unity组件化设计、脚本通信、玩家敌人交互、面向对象实战、GameObject职责分离。这篇文章不讲抽象理论只拆解一个真实可运行的“玩家挥剑→击中敌人→敌人掉血→播放受击动画→触发粒子特效→更新UI血条”完整链路从零开始构建三层通信结构基础事件驱动轻量、接口契约稳定、消息总线解耦。适合刚写完第一个MoveScript、正为“脚本之间怎么传数据”发愁的中级开发者也适合写了三年但还在用public变量硬绑的资深程序员——因为这套结构我在《暗影突袭》上线前夜紧急重构时验证过把原本37个强依赖脚本压缩到11个核心组件热更补丁体积减少64%。它解决的不是“能不能实现”而是“改起来痛不痛、扩起来稳不稳、查起来快不快”。2. 组件化设计的铁律每个脚本只做一件事且必须能独立测试2.1 职责切分不是按功能模块而是按“谁拥有数据、谁响应行为”很多人误以为“组件化把代码拆成多个脚本”结果拆出PlayerMovement、PlayerAttack、PlayerAnimation三个脚本每个都挂着public Transform enemyTarget、public Animator anim、public AudioSource audioSource。这叫“物理拆分”不是“逻辑解耦”。真正的组件化设计第一刀必须切在数据所有权上。我们以“敌人受击”为例反向推导血量数据currentHP, maxHP属于谁——EnemyHealth组件。它不关心怎么掉血只负责“我有多少血”和“我是否死亡”。受击行为触发被剑碰到、被子弹击中属于谁——Hitbox组件挂载在剑/子弹上。它只负责“我碰到了什么”不负责“碰到后发生什么”。受击反馈播放动画、音效、粒子属于谁——EnemyFeedback组件。它只监听“我被击中了”然后执行视觉听觉反馈。玩家攻击逻辑挥剑判定、冷却、伤害计算属于谁——PlayerCombat组件。它只管“我怎么发起攻击”不关心“敌人怎么反应”。提示检验职责是否清晰的唯一标准——删掉这个脚本其他脚本是否还能编译通过、是否还能在编辑器里正常显示Inspector面板如果删了EnemyHealthPlayerCombat就报错“找不到GetHealth()”说明职责污染了。2.2 实战建模用UML类图思维画出你的GameObject结构别急着写代码。打开纸笔或白板按以下顺序画出玩家和敌人的最小可行结构玩家根对象PlayerPlayerCombat攻击逻辑PlayerMovement移动逻辑PlayerAnimator动画状态机驱动无Health、UI、Audio等——这些是子对象职责玩家子对象Player/ModelModelRoot空GameObject挂载PlayerAnimatorSword空GameObject挂载SwordHitboxSwordHitbox检测碰撞触发OnHit事件敌人根对象EnemyEnemyHealth血量管理EnemyAI巡逻/追击逻辑EnemyAnimator动画状态机敌人子对象Enemy/ModelModelRoot空GameObject挂载EnemyAnimatorHitbox空GameObject挂载EnemyHitboxEnemyHitbox接收OnHit调用EnemyHealth.TakeDamage关键洞察Hitbox永远是子对象上的独立组件而非挂载在Player/Enemy根对象上。因为剑的Hitbox和子弹的Hitbox逻辑完全不同但它们都遵循同一接口——IHitbox。同理敌人身上的Hitbox和玩家身上的Hitbox用于格挡判定也应实现同一接口。这样PlayerCombat只需知道“IHitbox hitbox sword.GetComponent ()”完全不关心sword是模型的一部分还是特效生成的临时对象。2.3 避坑指南Unity特有的“组件生命周期陷阱”新手常踩的坑在Start()里获取其他组件引用结果NullReferenceException。原因在于Unity组件初始化顺序不可控。正确做法是延迟绑定防御性检查// ❌ 危险Start里直接GetComponent可能对方还没Awake private void Start() { enemyHealth GetComponentEnemyHealth(); // 如果EnemyHealth在Awake里才初始化某些字段这里可能为空 } // ✅ 安全用属性封装懒加载配合OnEnable确保可用 private EnemyHealth _enemyHealth; private EnemyHealth enemyHealth { get { if (_enemyHealth null) { _enemyHealth GetComponentEnemyHealth(); if (_enemyHealth null) { Debug.LogError(EnemyHealth组件缺失请检查Enemy预制体结构); enabled false; // 主动禁用自身避免后续错误 } } return _enemyHealth; } } private void OnEnable() { // 确保每次启用时都重新校验 if (enemyHealth null) return; }实测心得我在《废土守望者》项目中曾因忽略此点在敌人被池化复用时出现“血条UI显示上一个敌人的血量”问题。根源是EnemyHealth在OnDisable()里未重置UI绑定而新敌人启用时UI组件仍绑定旧引用。解决方案是在EnemyHealth的OnEnable()中强制刷新所有监听者“OnHealthChanged?.Invoke(currentHP);”。3. 脚本通信的三种武器何时用事件、何时用接口、何时上消息总线3.1 基础层C#事件Event——适合“一对多”且关系稳定的场景事件是最轻量、最直观的通信方式但滥用会导致“事件海啸”。核心原则只在明确知道监听者是谁、且监听者数量固定时使用。例如EnemyHealth的血量变化必然需要通知EnemyFeedback播放动画、EnemyUI更新血条、GameDirector判断是否触发Boss战。这三个监听者在敌人预制体中是确定存在的不会动态增减。// EnemyHealth.cs public class EnemyHealth : MonoBehaviour { public event System.Actionfloat OnHealthChanged; // 传递当前血量 public event System.Action OnDeath; // 无参数仅通知死亡事件 private float currentHP; public void TakeDamage(float damage) { currentHP - damage; OnHealthChanged?.Invoke(currentHP); // 触发事件 if (currentHP 0) { Die(); } } private void Die() { OnDeath?.Invoke(); // ... 死亡逻辑 } } // EnemyFeedback.cs —— 监听者1 public class EnemyFeedback : MonoBehaviour { [SerializeField] private Animator animator; private void OnEnable() { GetComponentEnemyHealth().OnHealthChanged HandleHealthChange; GetComponentEnemyHealth().OnDeath HandleDeath; } private void OnDisable() { GetComponentEnemyHealth().OnHealthChanged - HandleHealthChange; GetComponentEnemyHealth().OnDeath - HandleDeath; } private void HandleHealthChange(float hp) { if (hp GetComponentEnemyHealth().maxHP * 0.3f) { animator.SetTrigger(IsHurt); // 播放受伤动画 } } private void HandleDeath() { animator.SetTrigger(Die); } }注意必须在OnDisable()中移除事件监听否则敌人被销毁后EnemyFeedback实例已不存在但EnemyHealth的OnHealthChanged事件列表里还存着对它的引用导致GC无法回收内存泄漏。这是Unity事件通信最隐蔽的坑。3.2 稳定层接口Interface——解决“我不知道你是谁但我知道你能做什么”当通信双方没有直接引用关系时接口是黄金方案。典型场景PlayerCombat要攻击“任何能被击中的东西”但敌人、箱子、机关门都是不同类。定义统一接口// IHitReceiver.cs —— 所有可被击中的对象必须实现 public interface IHitReceiver { void OnHit(float damage, Vector3 hitPoint, GameObject attacker); bool IsAlive { get; } // 只读属性供攻击方判断是否有效目标 } // EnemyHealth.cs —— 实现接口 public class EnemyHealth : MonoBehaviour, IHitReceiver { public bool IsAlive currentHP 0; public void OnHit(float damage, Vector3 hitPoint, GameObject attacker) { if (!IsAlive) return; TakeDamage(damage); // ... 其他受击逻辑 } // ... 其余代码不变 } // SwordHitbox.cs —— 攻击方只依赖接口 public class SwordHitbox : MonoBehaviour { private void OnTriggerEnter(Collider other) { // 关键不关心other是什么类型只检查是否实现了IHitReceiver IHitReceiver receiver other.GetComponentIHitReceiver(); if (receiver ! null receiver.IsAlive) { receiver.OnHit(15f, transform.position, gameObject); // 传递伤害、位置、攻击者 } } }优势PlayerCombat甚至不需要知道EnemyHealth的存在。它只要拿到一个IHitReceiver就能安全调用OnHit。我在《机械之心》项目中用此方案接入了DLC扩展包——新敌人类型只需实现IHitReceiver无需修改一行原有攻击代码。3.3 解耦层消息总线Message Bus——处理“跨场景、跨系统、临时监听”的复杂通信当通信关系动态变化时如玩家拾取道具后全局UI要显示提示同时背景音乐要变奏同时任务系统要记录进度事件和接口都不够用。此时引入轻量级消息总线。拒绝第三方插件手写一个15行代码的静态类// GameMessageBus.cs —— 全局单例零依赖 public static class GameMessageBus { private static readonly Dictionarystring, ListSystem.Actionobject _subscribers new Dictionarystring, ListSystem.Actionobject(); public static void SubscribeT(string topic, ActionT callback) { var key typeof(T).FullName _ topic; if (!_subscribers.ContainsKey(key)) { _subscribers[key] new ListSystem.Actionobject(); } _subscribers[key].Add(obj callback((T)obj)); } public static void PublishT(string topic, T message) { var key typeof(T).FullName _ topic; if (_subscribers.TryGetValue(key, out var callbacks)) { foreach (var callback in callbacks) { callback(message); } } } public static void UnsubscribeT(string topic, ActionT callback) { var key typeof(T).FullName _ topic; if (_subscribers.TryGetValue(key, out var callbacks)) { callbacks.RemoveAll(cb cb.Target callback.Target cb.Method callback.Method); } } } // PlayerCombat.cs —— 发布者 public class PlayerCombat : MonoBehaviour { public void Attack() { // ... 攻击逻辑 GameMessageBus.Publish(PlayerAttack, new AttackMessage { Attacker gameObject, Damage 15f, Position transform.position }); } } // UIManager.cs —— 订阅者动态监听 public class UIManager : MonoBehaviour { private void OnEnable() { GameMessageBus.SubscribeAttackMessage(PlayerAttack, OnPlayerAttack); } private void OnDisable() { GameMessageBus.UnsubscribeAttackMessage(PlayerAttack, OnPlayerAttack); } private void OnPlayerAttack(AttackMessage msg) { ShowAttackToast(msg.Attacker.name); } }关键设计点用typeof(T).FullName topic组合为唯一key避免不同消息类型同名topic冲突。实测中我们在《星尘远征》的多人联机模式下用此总线同步“玩家进入战斗区域”事件12个系统模块UI、音效、网络、成就、AI、特效等全部通过Subscribe接入新增模块只需两行代码彻底告别“在Player脚本里硬加12个public引用”。4. 玩家敌人交互的完整链路从挥剑到血条更新的七步落地4.1 第一步构建可复用的Hitbox系统含碰撞过滤SwordHitbox不能无差别触发所有Collider。必须过滤只对敌人层Enemy和可破坏物层Breakable响应忽略玩家层Player和UI层UI。Unity的Layer Collision Matrix是基础但需配合代码二次过滤// SwordHitbox.cs public class SwordHitbox : MonoBehaviour { [Header(碰撞设置)] [Tooltip(允许击中的层如Enemy, Breakable)] public LayerMask validHitLayers; [Tooltip(击中时的伤害)] public float damage 15f; private void OnTriggerEnter(Collider other) { // 1. 层过滤只处理validHitLayers包含的层 int layer other.gameObject.layer; if (!IsLayerInMask(layer, validHitLayers)) return; // 2. 组件过滤必须有IHitReceiver IHitReceiver receiver other.GetComponentIHitReceiver(); if (receiver null || !receiver.IsAlive) return; // 3. 方向过滤只击中正面可选提升手感 Vector3 hitDir (other.transform.position - transform.position).normalized; if (Vector3.Dot(transform.forward, hitDir) 0.3f) return; // 仅正面30度内 // 4. 触发击中 receiver.OnHit(damage, transform.position, gameObject); } private bool IsLayerInMask(int layer, LayerMask mask) { return mask (mask | (1 layer)); // 位运算判断层是否在掩码中 } }实操技巧在Project窗口创建名为“Layers”的文件夹里面放一个Layers.assetScriptableObject用Editor脚本自动生成LayerMask枚举避免魔数。我在《深海回响》项目中用此法将LayerMask配置从硬编码转为可视化编辑策划可直接在Inspector里勾选“对Boss生效”、“对环境物体不生效”。4.2 第二步EnemyHealth的健壮实现含死亡状态机EnemyHealth不能只是个血量计数器。它必须管理状态流转Alive → Dying → Dead并提供安全的外部调用入口// EnemyHealth.cs public class EnemyHealth : MonoBehaviour, IHitReceiver { [Header(基础属性)] [SerializeField] private float maxHP 100f; [SerializeField] private bool isBoss false; [Header(状态管理)] private float currentHP; private bool isDead false; private bool isDying false; // 外部只读属性 public float CurrentHP currentHP; public float MaxHP maxHP; public bool IsAlive !isDead !isDying; public bool IsDying isDying; public bool IsDead isDead; // 事件 public event System.Actionfloat OnHealthChanged; public event System.Action OnDeath; public event System.Action OnDying; private void Awake() { currentHP maxHP; isDead false; isDying false; } public void TakeDamage(float damage) { if (!IsAlive) return; currentHP Mathf.Max(0f, currentHP - damage); OnHealthChanged?.Invoke(currentHP); if (currentHP 0 !isDying) { StartDying(); } } private void StartDying() { isDying true; OnDying?.Invoke(); // Boss特殊逻辑濒死阶段无敌、放大招 if (isBoss) { StartCoroutine(BossFinalPhase()); } else { Invoke(nameof(Die), 0.5f); // 普通敌人0.5秒后死亡 } } private IEnumerator BossFinalPhase() { // 濒死无敌、屏幕震动、大招蓄力... yield return new WaitForSeconds(2f); Die(); } private void Die() { isDying false; isDead true; OnDeath?.Invoke(); // 清理移除所有监听者防止内存泄漏 OnHealthChanged null; OnDeath null; OnDying null; } }关键细节OnHealthChanged事件在TakeDamage中立即触发而非在Die()中触发。因为UI血条需要实时更新而死亡动画可能持续1秒血条应在血量归零瞬间就清空而非等待死亡动画结束。4.3 第三步EnemyUI的响应式绑定避免每帧UpdateEnemyUI不应在Update里轮询EnemyHealth.currentHP。正确做法是事件驱动缓存引用// EnemyUI.cs public class EnemyUI : MonoBehaviour { [Header(UI引用)] [SerializeField] private Image healthBar; [SerializeField] private Text healthText; private EnemyHealth enemyHealth; private float lastKnownHP -1f; // 缓存上次值避免重复设置 private void Awake() { enemyHealth GetComponentInParentEnemyHealth(); if (enemyHealth null) { Debug.LogError(EnemyUI未找到父级EnemyHealth组件); enabled false; return; } } private void OnEnable() { // 仅在启用时绑定避免重复订阅 enemyHealth.OnHealthChanged UpdateHealthUI; enemyHealth.OnDeath HideUI; } private void OnDisable() { enemyHealth.OnHealthChanged - UpdateHealthUI; enemyHealth.OnDeath - HideUI; } private void UpdateHealthUI(float hp) { if (hp lastKnownHP) return; // 防抖避免相同值重复设置 lastKnownHP hp; float ratio hp / enemyHealth.MaxHP; healthBar.fillAmount ratio; healthText.text ${Mathf.CeilToInt(hp)}/{Mathf.CeilToInt(enemyHealth.MaxHP)}; // 血条颜色渐变绿→黄→红 healthBar.color Color.Lerp(Color.green, Color.red, 1f - ratio); } private void HideUI() { gameObject.SetActive(false); } }注意lastKnownHP -1f是关键。因为Awake时currentHP100而UpdateHealthUI第一次调用时hp100若初始值设为0则100≠0会错误触发UI更新。设为-1确保首次必更新。4.4 第四步PlayerCombat的攻击流程控制含冷却与状态校验PlayerCombat必须管理攻击状态避免“按键连按导致无限挥剑”// PlayerCombat.cs public class PlayerCombat : MonoBehaviour { [Header(攻击设置)] [SerializeField] private float attackCooldown 0.5f; [SerializeField] private Transform swordHitboxTransform; private bool canAttack true; private SwordHitbox currentSwordHitbox; private void Awake() { // 预先获取SwordHitbox避免每帧GetComponent currentSwordHitbox swordHitboxTransform?.GetComponentSwordHitbox(); if (currentSwordHitbox null) { Debug.LogError(PlayerCombat未找到SwordHitbox请检查剑子对象结构); } } private void Update() { if (Input.GetButtonDown(Fire1) canAttack) { Attack(); } } public void Attack() { if (!canAttack || currentSwordHitbox null) return; // 1. 激活Hitbox通常Hitbox默认禁用攻击时启用 currentSwordHitbox.enabled true; // 2. 播放攻击动画 GetComponentAnimator().SetTrigger(Attack); // 3. 启动冷却 canAttack false; Invoke(nameof(ResetAttack), attackCooldown); // 4. 发布全局消息供其他系统响应 GameMessageBus.Publish(PlayerAttack, new AttackMessage { Attacker gameObject, Damage currentSwordHitbox.damage, Position transform.position }); } private void ResetAttack() { canAttack true; // 5. 禁用Hitbox防止持续触发 if (currentSwordHitbox ! null) { currentSwordHitbox.enabled false; } } }实测经验在《暗影突袭》中我们将attackCooldown从0.3s调整为0.5s配合Hitbox激活时间0.1s禁用时间0.1s完美匹配动画挥剑帧第12帧激活第28帧禁用玩家手感从“飘忽”变为“扎实”。5. 高级实战处理常见边界问题与性能优化5.1 边界问题1多敌人同时被击中时的事件竞争当玩家AOE攻击如旋风斩击中5个敌人时5个EnemyHealth的OnHealthChanged事件几乎同时触发可能导致UI更新卡顿或动画错乱。解决方案事件批处理协程延迟// EnemyHealth.cs —— 修改OnHealthChanged触发逻辑 private Listfloat pendingHealthUpdates new Listfloat(); public void TakeDamage(float damage) { if (!IsAlive) return; currentHP Mathf.Max(0f, currentHP - damage); pendingHealthUpdates.Add(currentHP); // 缓存待处理值 if (pendingHealthUpdates.Count 1) { // 首次添加启动批处理协程 StartCoroutine(ProcessHealthUpdates()); } if (currentHP 0 !isDying) { StartDying(); } } private IEnumerator ProcessHealthUpdates() { // 等待一帧合并所有更新 yield return null; float latestHP pendingHealthUpdates[pendingHealthUpdates.Count - 1]; pendingHealthUpdates.Clear(); OnHealthChanged?.Invoke(latestHP); }原理利用Unity协程的yield return null特性在下一帧开始时处理所有累积的更新确保无论多少敌人被击中OnHealthChanged只触发一次最新值。5.2 边界问题2池化敌人复用时的组件状态残留敌人被击中后销毁再从对象池取出复用但EnemyHealth的currentHP仍是0EnemyUI仍处于隐藏状态。解决方案标准化重置接口// IResettable.cs public interface IResettable { void ResetState(); } // EnemyHealth.cs —— 实现重置 public void ResetState() { currentHP maxHP; isDead false; isDying false; lastKnownHP -1f; // 重置UI缓存 gameObject.SetActive(true); // 确保激活 } // EnemyUI.cs —— 实现重置 public void ResetState() { gameObject.SetActive(true); healthBar.fillAmount 1f; healthText.text ${maxHP}/{maxHP}; healthBar.color Color.green; } // 对象池Manager.cs —— 复用时调用 public GameObject GetEnemy() { GameObject enemy pool.Get(); enemy.GetComponentIResettable()?.ResetState(); return enemy; }提示在Inspector里为Enemy预制体添加Resettable组件空脚本并用Editor脚本自动为所有IResettable实现类添加Reset按钮策划一键重置测试。5.3 性能优化用ScriptableObject管理共享配置将伤害值、冷却时间、血量等数值抽离为ScriptableObject实现策划可配、美术可调// CombatSettingsSO.cs [CreateAssetMenu(fileName CombatSettings, menuName Game/Combat Settings)] public class CombatSettingsSO : ScriptableObject { public float playerBaseDamage 15f; public float playerAttackCooldown 0.5f; public float enemyBaseHP 100f; public LayerMask enemyHitLayers; } // PlayerCombat.cs —— 引用配置 [SerializeField] private CombatSettingsSO combatSettings; private void Attack() { if (currentSwordHitbox ! null) { currentSwordHitbox.damage combatSettings.playerBaseDamage; } // ... 其余逻辑 }在《废土守望者》中我们用此法将平衡性调整时间从“改代码→编译→打包→测试”缩短为“改SO文件→保存→Play模式即时生效”迭代效率提升8倍。5.4 最后一道防线用Unity Profiler定位通信瓶颈当交互链路变长如Player→Sword→Enemy→UI→Audio→VFX→Network必须用Profiler验证性能打开Window Analysis Profiler勾选“Deep Profile”深度分析录制一次完整攻击过程从按键到所有反馈结束查看“Scripts”区域若EnemyHealth.TakeDamage耗时0.2ms检查是否有复杂计算如实时寻路混入若GameMessageBus.Publish耗时高检查订阅者数量超过50个需优化若EnemyUI.UpdateHealthUI频繁调用检查lastKnownHP防抖是否生效实测案例在《星尘远征》Alpha版Profiler显示EnemyUI每帧调用127次UpdateHealthUI根源是忘了在OnDisable()中移除事件监听导致已销毁的UI实例仍在接收事件。修复后该函数调用降为0次/帧。我在实际项目中发现真正决定交互系统成败的从来不是“能不能实现”而是“改起来痛不痛”。当你把EnemyHealth的血量逻辑、EnemyUI的显示逻辑、EnemyFeedback的动画逻辑彻底解耦后策划说“把血条改成环形”你只需改EnemyUI.cs美术说“受击动画太短”你只需调EnemyFeedback.cs里的animator参数程序说“Boss要加无敌阶段”你只需在EnemyHealth.StartDying()里加几行。这套组件化通信结构不是银弹但它是让你在需求变更的洪流中始终能稳住船舵的压舱石。最后分享一个小技巧每次新增一个脚本前先问自己——它持有数据吗它响应行为吗它会被谁调用如果答案模糊就先画UML类图再写代码。毕竟在Unity里设计比编码慢十倍但维护比编码快百倍。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2634977.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…