Unity 面向对象实战:掌握组件化设计与脚本通信,构建玩家敌人交互
Langchain系列文章目录
01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
PyTorch系列文章目录
Python系列文章目录
C#系列文章目录
01-C#与游戏开发的初次见面:从零开始的Unity之旅
02-C#入门:从变量与数据类型开始你的游戏开发之旅
03-C#运算符与表达式:从入门到游戏伤害计算实践
04-从零开始学C#:用if-else和switch打造智能游戏逻辑
05-掌握C#循环:for、while、break与continue详解及游戏案例
06-玩转C#函数:参数、返回值与游戏中的攻击逻辑封装
07-Unity游戏开发入门:用C#控制游戏对象移动
08-C#面向对象编程基础:类的定义、属性与字段详解
09-C#封装与访问修饰符:保护数据安全的利器
10-如何用C#继承提升游戏开发效率?Enemy与Boss案例解析
11-C#多态性入门:从零到游戏开发实战
12-C#接口王者之路:从入门到Unity游戏开发实战 (IAttackable案例详解)
13-C#静态成员揭秘:共享数据与方法的利器
14-Unity 面向对象实战:掌握组件化设计与脚本通信,构建玩家敌人交互
文章目录
- Langchain系列文章目录
- PyTorch系列文章目录
- Python系列文章目录
- C#系列文章目录
- 前言
- 一、Unity 与面向对象:为何如此契合?
- 1.1 Unity 的核心理念:组件化设计 (Component-Based Design)
- 1.2 面向对象如何赋能组件化
- 二、核心知识点:组件化设计
- 2.1 什么是组件?
- 2.2 组件化思维的优势
- 2.3 如何设计良好的组件
- 三、核心知识点:脚本间的通信
- 3.1 为何需要通信?
- 3.2 常见的通信方式
- 3.2.1 直接引用 (Direct Reference)
- (1)通过 `GetComponent<T>()` 获取
- 3.2.2 `SendMessage` / `BroadcastMessage` / `SendMessageUpwards`
- 3.2.3 事件/委托 (Events/Delegates) 与 C# 事件
- 3.2.4 静态变量/单例模式 (Static Variables/Singleton Pattern)
- 3.3 选择合适的通信方式
- 四、实践:玩家与敌人交互
- 4.1 场景设定
- 4.2 创建 Player 脚本
- 4.3 创建 Enemy 脚本
- 4.4 实现交互逻辑
- 4.5 可能遇到的问题与优化
- 五、总结
前言
大家好!欢迎来到我们 C# 学习之旅的第 14 天。在过去的一周里,我们系统学习了 C# 面向对象编程(OOP)的核心概念,包括类、封装、继承、多态、接口以及静态成员。这些知识为我们构建结构清晰、可维护性强的代码打下了坚实的基础。然而,理论最终要服务于实践。今天,我们将聚焦于如何在强大的游戏引擎 Unity 中巧妙地应用这些 OOP 原则,特别是在游戏开发中最常见的场景:组件化设计、脚本间的通信,并通过一个玩家与敌人交互的实例来巩固所学。理解如何在 Unity 的框架内运用 OOP,是 C# 游戏开发进阶的关键一步。
一、Unity 与面向对象:为何如此契合?
在我们深入具体技术点之前,首先需要理解 Unity 的核心设计哲学,以及为什么面向对象的思想能够与之完美融合,并极大地赋能游戏开发。
1.1 Unity 的核心理念:组件化设计 (Component-Based Design)
Unity 的架构并非严格意义上的传统面向对象,它更多地采用了实体-组件系统 (Entity-Component System, ECS) 的思想(尽管早期版本更偏向纯粹的组件化)。其核心理念是:
- 游戏对象 (GameObject): 场景中的一切皆为游戏对象,但它们本身只是一个空的容器。
- 组件 (Component): 负责赋予游戏对象特定的行为或数据。例如,
Transform组件负责位置、旋转和缩放;Rigidbody组件负责物理行为;我们编写的 C# 脚本(继承自MonoBehaviour)也是一种组件,负责自定义逻辑。
这种设计的核心思想是组合优于继承。一个游戏对象可以通过挂载不同的组件组合,来实现复杂多样的功能,而不是通过复杂的继承树来定义。
1.2 面向对象如何赋能组件化
虽然 Unity 强调组件化,但面向对象的原则在设计和实现这些组件时至关重要:
- 封装 (Encapsulation): 每个组件应该封装好自己的数据和逻辑,只暴露必要的接口(公共方法和属性)。这使得组件更加独立和内聚。例如,一个
PlayerHealth组件应该封装玩家的生命值数据,并提供如TakeDamage()这样的公共方法来修改它,而不是让外部代码随意直接访问内部的health变量。 - 单一职责原则 (Single Responsibility Principle - SRP): 这是 OOP 的重要设计原则,在组件设计中同样适用。一个组件应该只负责一项明确的功能。例如,将玩家的移动逻辑放在
PlayerMovement组件中,将生命值管理放在PlayerHealth组件中,而不是将所有逻辑都塞进一个庞大的Player脚本。这提高了代码的可读性、可维护性和复用性。 - 继承与多态 (Inheritance & Polymorphism): 虽然 Unity 鼓励组合,但在某些场景下,继承和多态依然有用。例如,你可以创建一个基础的
Enemy类(组件),然后派生出MeleeEnemy和RangedEnemy,它们继承通用属性(如生命值)并重写(Override)攻击方法 (Attack()) 来实现不同的攻击行为。 - 接口 (Interfaces): 接口在定义组件间的“契约”时非常有用,尤其是在需要解耦通信的场景。例如,任何可以被攻击的对象(玩家、敌人、可破坏的障碍物)都可以实现一个
IAttackable接口,该接口定义了一个ReceiveDamage(int amount)方法。这样,攻击方代码只需要知道目标是否是IAttackable,而不需要关心其具体类型。
二、核心知识点:组件化设计
理解了 Unity 的哲学后,我们来深入探讨组件化设计的具体实践。
2.1 什么是组件?
在 Unity 中,组件可以看作是附加到游戏对象上的功能模块或行为单元。
- 内置组件: Unity 提供了大量内置组件,如
Transform,Mesh Renderer,Collider,Rigidbody,Animator等,它们提供了游戏开发所需的基础功能。 - 脚本组件: 我们通过 C# 编写的、继承自
MonoBehaviour的类,就是自定义的脚本组件。它们是我们实现游戏特定逻辑的主要方式。
把游戏对象想象成一个空的乐高底板,而组件就是各种形状和功能的乐高积木。通过将不同的积木(组件)拼装到底板(游戏对象)上,我们就能创造出丰富多彩的游戏实体。
2.2 组件化思维的优势
采用组件化思维进行开发,能带来诸多好处:
- 模块化 (Modularity): 功能被拆分成独立的模块(组件),易于理解和管理。
- 可重用性 (Reusability): 设计良好的组件可以在不同的游戏对象甚至不同的项目中使用。例如,一个通用的
Health组件可以用于玩家、敌人、甚至可破坏的箱子。 - 灵活性与可扩展性 (Flexibility & Scalability): 可以通过添加、移除或替换组件来轻松改变游戏对象的行为,而无需修改大量代码。需要新功能?添加一个新组件即可。
- 解耦 (Decoupling): 组件之间应尽量减少直接依赖,使得修改一个组件不会轻易影响到其他组件。
- 并行开发 (Parallel Development): 不同的开发者可以专注于开发不同的组件,提高团队协作效率。
2.3 如何设计良好的组件
遵循单一职责原则是关键。问问自己:这个组件的核心职责是什么?它是否做了太多事情?
- 反例: 一个
Player脚本处理移动、攻击、生命值、动画、物品栏、任务等所有逻辑。 - 正例:
PlayerMovement: 处理输入和物理移动。PlayerAttack: 处理攻击逻辑(输入、动画触发、伤害计算)。PlayerHealth: 管理生命值、受伤和死亡逻辑。PlayerAnimation: 控制动画状态机。InventoryManager: 管理物品。QuestLog: 管理任务。
将这些组件挂载到同一个 Player GameObject 上,它们协同工作,共同构成完整的玩家功能。
三、核心知识点:脚本间的通信
当我们将功能拆分到不同组件后,这些组件之间不可避免地需要进行交互和信息传递。这就是脚本间通信要解决的问题。
3.1 为何需要通信?
游戏逻辑往往涉及多个组件的协作:
- 玩家的攻击脚本 (
PlayerAttack) 需要通知敌人的生命值脚本 (EnemyHealth) 敌人受到了伤害。 - 敌人的 AI 脚本 (
EnemyAI) 可能需要获取玩家的位置信息(来自玩家的Transform组件或PlayerMovement脚本)。 - 当玩家生命值降为零时,
PlayerHealth脚本可能需要通知游戏管理器脚本 (GameManager) 游戏结束。 - UI 脚本 (
UIHealthBar) 需要获取PlayerHealth脚本中的当前生命值来更新血条显示。
3.2 常见的通信方式
Unity 中实现脚本间通信有多种方法,各有优缺点:
3.2.1 直接引用 (Direct Reference)
这是最常用也最直观的方式。一个脚本持有对另一个脚本实例的引用。
(1)通过 GetComponent<T>() 获取
在一个脚本中,可以通过 GetComponent<T>() 方法获取同一个游戏对象上的其他组件。
// 假设此脚本和 PlayerHealth 脚本挂在同一个 GameObject 上
using UnityEngine;public class PlayerEffects : MonoBehaviour
{private PlayerHealth playerHealth; // 存储对 PlayerHealth 组件的引用void Start(){// 在 Start 方法中获取 PlayerHealth 组件playerHealth = GetComponent<PlayerHealth>();// 健壮性检查:确保找到了组件if (playerHealth == null){Debug.LogError("PlayerHealth component not found on this GameObject!");}}public void PlayDamageEffect(){// 调用 PlayerHealth 的方法或访问其公共属性if (playerHealth != null){Debug.Log("Playing damage effect because health is: " + playerHealth.CurrentHealth);// 假设 PlayerHealth 有一个公共属性 CurrentHealth}}
}// 假设的 PlayerHealth 脚本
public class PlayerHealth : MonoBehaviour
{[SerializeField] private int maxHealth = 100;public int CurrentHealth { get; private set; } // 公共只读属性void Awake(){CurrentHealth = maxHealth;}public void TakeDamage(int amount){CurrentHealth -= amount;Debug.Log($"Player took {amount} damage. Current health: {CurrentHealth}");if (CurrentHealth <= 0){Die();}// 通知 PlayerEffects 播放特效PlayerEffects effects = GetComponent<PlayerEffects>();if (effects != null){effects.PlayDamageEffect(); // 直接调用另一个组件的方法}}void Die(){Debug.Log("Player Died!");// 处理死亡逻辑...}
}
-
获取其他 GameObject 上的组件:
-
公共变量与检视面板赋值: 在脚本中声明一个公共变量(或使用
[SerializeField]标记的私有变量),然后在 Unity 编辑器的检视面板 (Inspector) 中将目标游戏对象或其上的组件拖拽过去。这是最推荐的方式之一,因为它清晰、直观且性能较好。using UnityEngine;public class EnemyAI : MonoBehaviour {[SerializeField] private Transform playerTransform; // 在 Inspector 中拖拽玩家对象[SerializeField] private PlayerHealth playerHealth; // 在 Inspector 中拖拽挂有 PlayerHealth 的对象void Update(){if (playerTransform != null){// 朝向玩家transform.LookAt(playerTransform);}}void AttackPlayer(){if (playerHealth != null){playerHealth.TakeDamage(10); // 调用其他对象上的组件方法}} } -
GameObject.Find()/FindObjectOfType<T>(): 这些方法可以在整个场景中查找游戏对象或特定类型的组件。强烈不建议在Update等频繁调用的方法中使用它们,因为性能开销较大。最好在Start或Awake中调用一次并缓存结果。using UnityEngine;public class GameManager : MonoBehaviour {private PlayerHealth playerHealth;void Start(){// 查找场景中第一个 PlayerHealth 组件实例playerHealth = FindObjectOfType<PlayerHealth>();if (playerHealth == null){Debug.LogError("PlayerHealth not found in the scene!");}// 通过名字查找 GameObject,再获取组件(效率更低,且名字可能变化)// GameObject playerObject = GameObject.Find("PlayerObjectName");// if (playerObject != null)// {// playerHealth = playerObject.GetComponent<PlayerHealth>();// }} }
-
-
优点: 简单直观,易于理解和调试,性能较好(尤其是 Inspector 赋值和缓存
GetComponent结果)。 -
缺点: 增加了脚本之间的耦合度。如果被引用的脚本或游戏对象发生变化(例如重命名、删除或结构调整),可能会导致引用丢失(在 Inspector 中显示为 None)或代码出错(
NullReferenceException)。
3.2.2 SendMessage / BroadcastMessage / SendMessageUpwards
这些方法允许脚本调用同一个游戏对象 (SendMessage)、自身及其所有子对象 (BroadcastMessage) 或自身及其所有父对象 (SendMessageUpwards) 上特定名称的方法,无需直接引用。
// 在某个脚本中
void DealDamageToPlayer()
{GameObject player = GameObject.Find("Player"); // 假设能找到玩家if (player != null){// 调用 player GameObject 上所有脚本中的 "TakeDamage" 方法// 传递一个整数参数 10player.SendMessage("TakeDamage", 10, SendMessageOptions.DontRequireReceiver);// SendMessageOptions.DontRequireReceiver 表示如果找不到方法,也不会报错}
}// 在 PlayerHealth 脚本中需要有对应的方法
public void TakeDamage(int amount) // 方法名必须匹配,参数类型也要匹配
{// ... 处理伤害 ...
}
- 优点: 一定程度上降低了耦合,调用方不需要知道接收方的具体脚本类型,只需要知道方法名。
- 缺点:
- 性能较差: 底层使用反射,比直接方法调用慢很多。
- 类型不安全: 方法名是字符串,编译器无法检查拼写错误,容易出错。
- 重构困难: 如果修改了方法名,需要手动查找并修改所有
SendMessage调用处。 - 调试困难: 难以追踪是谁调用了方法。
- 通常不推荐使用,除非有特殊需求且了解其性能影响。
3.2.3 事件/委托 (Events/Delegates) 与 C# 事件
这是更高级、更推荐的解耦通信方式。它允许一个脚本(发布者)发出事件信号,而其他脚本(订阅者)可以监听并响应这些信号,两者之间无需直接引用。我们将在后续的课程中(第 23、24、28 天)详细学习委托和事件。
- 优点: 低耦合,发布者和订阅者相互独立;灵活性高,可以有多个订阅者;符合观察者设计模式。
- 缺点: 相对直接引用,理解和实现稍微复杂一些。
3.2.4 静态变量/单例模式 (Static Variables/Singleton Pattern)
对于需要全局访问的数据或功能(如游戏管理器、配置数据),可以使用静态变量或单例模式。
// 简单的单例模式示例
using UnityEngine;public class GameManager : MonoBehaviour
{// 静态实例,全局唯一public static GameManager Instance { get; private set; }public int Score { get; private set; }void Awake(){// 实现单例模式的核心逻辑if (Instance == null){Instance = this;DontDestroyOnLoad(gameObject); // 可选:让 GameManager 在场景切换时不被销毁}else{Destroy(gameObject); // 如果已有实例,销毁自己}}public void AddScore(int points){Score += points;Debug.Log("Score: " + Score);// 这里可以触发一个事件,通知 UI 更新分数显示}
}// 其他任何脚本都可以通过 GameManager.Instance 访问
public class Enemy : MonoBehaviour
{void Die(){// 通知 GameManager 加分GameManager.Instance.AddScore(100);Destroy(gameObject);}// 假设在某个时机调用 Die()
}
- 优点: 提供全局访问点,方便访问。
- 缺点: 可能破坏封装,滥用会导致代码难以管理和测试;需要小心处理初始化顺序和生命周期。我们将在第 41 天详细讨论单例模式。
3.3 选择合适的通信方式
没有绝对最好的方式,需要根据具体场景权衡:
- 紧密相关的组件 (通常在同一个 GameObject 上):
GetComponent或 Inspector 赋值通常足够,简单高效。 - 不相关的对象,需要解耦: 事件/委托是理想选择。
- 全局状态或管理器: 单例模式或静态类。
- 避免使用:
SendMessage系列通常应避免,GameObject.Find等查找方法应谨慎使用并缓存结果。
核心原则: 优先选择耦合度低、易于维护且性能满足需求的方式。
四、实践:玩家与敌人交互
现在,让我们通过一个简单的实例,将前面学到的组件化设计和脚本通信知识应用起来。我们将创建一个玩家和一个敌人,当玩家走进敌人的触发范围时,敌人会“发现”玩家,并在控制台输出信息。
4.1 场景设定
- 创建一个新的 3D Unity 项目。
- 在场景中创建一个 Plane 作为地面。
- 创建一个 Cube,命名为 “Player”,并给它添加一个
Rigidbody组件(取消 Use Gravity,勾选 Is Kinematic,或者你也可以实现移动逻辑)。 - 再创建一个 Cube,命名为 “Enemy”,给它添加一个
Rigidbody组件。 - 给 “Enemy” 添加一个
Sphere Collider组件,并勾选Is Trigger。调整其Radius,使其代表敌人的“感知范围”。
4.2 创建 Player 脚本
创建一个新的 C# 脚本,命名为 PlayerIdentifier(或者更复杂的如 PlayerHealth),并将其附加到 “Player” 游戏对象上。这个脚本目前可以很简单,只是为了标识玩家。
// PlayerIdentifier.cs
using UnityEngine;public class PlayerIdentifier : MonoBehaviour
{public string playerName = "Hero";void Start(){Debug.Log($"Player '{playerName}' initialized.");}public void DetectedByEnemy(string enemyName){Debug.Log($"Player '{playerName}' has been detected by '{enemyName}'!");// 这里可以添加后续逻辑,比如玩家进入战斗状态}
}
4.3 创建 Enemy 脚本
创建一个新的 C# 脚本,命名为 EnemyAI,并将其附加到 “Enemy” 游戏对象上。
// EnemyAI.cs
using UnityEngine;public class EnemyAI : MonoBehaviour
{public string enemyName = "Goblin";// 当其他 Collider 进入该触发器时调用 (需对方或自身有 Rigidbody)private void OnTriggerEnter(Collider other){Debug.Log($"Trigger entered by: {other.gameObject.name}"); // 打印进入触发器的对象名字// 尝试获取进入触发器的对象上的 PlayerIdentifier 组件PlayerIdentifier player = other.GetComponent<PlayerIdentifier>();// 检查是否成功获取到了 PlayerIdentifier 组件if (player != null){// 如果是玩家进入了范围,执行逻辑Debug.Log($"Enemy '{enemyName}' found the player: {player.playerName}");// 调用玩家脚本上的方法进行通信player.DetectedByEnemy(this.enemyName);// 在这里可以添加敌人进入攻击状态、追击状态等的逻辑// 例如:GetComponent<EnemyMovement>().StartChasing(player.transform);}else{Debug.Log($"'{other.gameObject.name}' is not the player.");}}// 当其他 Collider 离开该触发器时调用private void OnTriggerExit(Collider other){PlayerIdentifier player = other.GetComponent<PlayerIdentifier>();if (player != null){Debug.Log($"Player '{player.playerName}' left the detection range of '{enemyName}'.");// 在这里可以添加敌人停止追击、返回巡逻状态等的逻辑}}
}
4.4 实现交互逻辑
在上面的 EnemyAI.cs 中,我们已经实现了核心的交互逻辑:
- 触发检测:
OnTriggerEnter方法会在有其他Collider进入标记为Is Trigger的Sphere Collider时被 Unity 自动调用。参数other就是进入范围的那个对象的Collider组件。 - 身份识别: 我们通过
other.GetComponent<PlayerIdentifier>()来尝试获取碰撞对象上的PlayerIdentifier脚本。如果返回的不是null,说明进入范围的是挂载了PlayerIdentifier脚本的对象,也就是我们的玩家。 - 通信:
- 从敌人到玩家:
player.DetectedByEnemy(this.enemyName);这一行直接调用了获取到的PlayerIdentifier脚本实例上的DetectedByEnemy方法,并将敌人的名字作为参数传递过去,实现了从敌人到玩家的通信。 - 从玩家到敌人(潜在): 虽然本例中没有显式展示,但如果玩家需要主动与敌人交互(例如攻击),玩家的攻击脚本可以通过类似的方式(如
GetComponent<EnemyAI>()或GetComponent<EnemyHealth>())获取敌人脚本的引用并调用其方法(如TakeDamage())。
- 从敌人到玩家:
现在运行游戏,并将 “Player” Cube 移动到 “Enemy” Cube 的球形触发器范围内,观察 Console 窗口的输出。你会看到类似以下的日志:
Trigger entered by: Player
Enemy 'Goblin' found the player: Hero
Player 'Hero' has been detected by 'Goblin'!
当你将 “Player” 移出范围时,会看到:
Player 'Hero' left the detection range of 'Goblin'.
4.5 可能遇到的问题与优化
-
NullReferenceException: 最常见的问题是在调用player.DetectedByEnemy()之前没有检查player是否为null。如果进入触发器的对象不是玩家(没有PlayerIdentifier组件),GetComponent会返回null,此时访问null的方法或属性就会抛出此异常。务必进行if (player != null)检查。 -
性能: 在
OnTriggerEnter/Stay中频繁调用GetComponent可能有性能开销,尤其当触发器交互频繁时。如果需要持续交互,可以在OnTriggerEnter中获取引用并存储起来,在OnTriggerExit中清空引用。 -
标签 (Tag) 与层 (Layer): 在实际项目中,更高效的识别对象的方式是使用标签 (Tag) 或层 (Layer)。可以在
OnTriggerEnter中先检查other.CompareTag("Player")或other.gameObject.layer == LayerMask.NameToLayer("PlayerLayer"),这样可以快速过滤掉非玩家对象,只有在确认是玩家后才执行GetComponent。// 使用 Tag 优化 private void OnTriggerEnter(Collider other) {if (other.CompareTag("Player")) // 假设 Player GameObject 的 Tag 设置为 "Player"{PlayerIdentifier player = other.GetComponent<PlayerIdentifier>();if (player != null){Debug.Log($"Enemy '{enemyName}' found the player: {player.playerName}");player.DetectedByEnemy(this.enemyName);}else{// 理论上设置了 Tag 应该有对应脚本,但也可能配置错误Debug.LogWarning("Object tagged as Player but missing PlayerIdentifier script!");}} }
五、总结
本文将 C# 面向对象的理论知识与 Unity 的实践相结合,深入探讨了如何在 Unity 游戏开发中有效运用 OOP 原则。核心要点回顾:
- Unity 与 OOP 的融合: Unity 的核心是组件化设计,而 OOP 的封装、单一职责、继承、多态和接口原则极大地赋能了组件的设计与实现,使代码更模块化、可维护、可复用。
- 组件化设计: 将游戏对象的功能拆分成独立的、职责单一的组件(脚本),是 Unity 开发的关键思维模式,它提高了代码的灵活性和可扩展性。
- 脚本间通信: 组件化后,脚本间的通信变得至关重要。我们学习了几种常用方法:
- 直接引用 (
GetComponent, Inspector 赋值): 简单直接,性能较好,但耦合度高。适用于关系紧密的组件。 SendMessage系列: 性能差,类型不安全,不推荐常规使用。- 事件/委托: 低耦合,灵活性高,是复杂交互的推荐方式(后续详解)。
- 静态/单例: 适用于全局访问点。
选择哪种方式取决于具体场景下的耦合度、性能和维护性需求。
- 直接引用 (
- 实践应用: 通过玩家与敌人交互的实例,我们练习了使用
OnTriggerEnter检测碰撞,使用GetComponent获取其他对象的脚本引用,并直接调用方法实现通信。同时,了解了使用 Tag 或 Layer 进行优化的方法。
掌握如何在 Unity 中应用面向对象思想,特别是组件化设计和脚本间通信,是提升你游戏开发能力的重要一步。它能帮助你构建更大型、更复杂且更易于维护的游戏项目。
相关文章:
Unity 面向对象实战:掌握组件化设计与脚本通信,构建玩家敌人交互
Langchain系列文章目录 01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南 02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖 03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南 04-玩转 LangChai…...
3. 第三放平台部署deepseek
有时候我们会发现使用deepseek服务器,异常卡顿,这是由于多方面原因造成的,比如说访问人数过多等。想要解决这个问题,我们可以选择第三方平台进行部署 第三方平台 我们可以选择的第三方平台很多,比如硅基流动、秘塔搜索…...
【C++指针】搭建起程序与内存深度交互的桥梁(下)
🔥🔥 个人主页 点击🔥🔥 每文一诗 💪🏼 往者不可谏,来者犹可追——《论语微子篇》 译文:过去的事情已经无法挽回,未来的岁月还可以迎头赶上。 目录 C内存模型 new与…...
.NET开发基础知识1-10
1. 依赖注入(Dependency Injection) 技术知识:依赖注入是一种设计模式,它允许将对象的依赖关系从对象本身中分离出来,通过构造函数、属性或方法参数等方式注入到对象中。这样可以提高代码的可测试性、可维护性和可扩展…...
IEEE PDF Xpress校验出现 :字体无法嵌入问题以及pdf版本问题
文章目录 问题描述一、字体嵌入问题首先查看一下,哪些字体没有被嵌入查看window的font文件夹里的字体下载字体的网站修复字体嵌入问题 二、pdf版本不对 问题描述 在处理IEEE的camera ready的时候,提交到IEEE express的文件没有办法通过validate…...
cookie详解
一、cookie出现原因 http是无状态的,浏览器无法记录当前是哪个人浏览的,所以出现了cookie 作用:会话状态管理(用户登录状态、购物车、游戏分数)、个性化设置(主题、自定义设置)、浏览器行为跟…...
Mayo Clinic Platform在人工智能医疗领域的现状及启示意义研究
一、引言 1.1 研究背景与意义 在科技飞速发展的当下,人工智能(AI)已逐渐渗透至各个行业,医疗领域作为关乎人类生命健康的重要领域,也迎来了人工智能技术带来的深刻变革。人工智能医疗,作为人工智能与医疗行业深度融合的产物,正重塑着全球医疗的格局。 从全球范围来看,…...
Rust基础语法
以下是 Rust 语言基础语法的核心要点,结合与 JavaScript 的对比,帮助前端开发者快速掌握核心概念: 一、变量与常量 1. 变量声明 Rust:变量默认不可变,需用 mut 显式声明可变性。let x 5; // 不可变变量 le…...
如何将 Java 应用做成 EXE 的可执行软件
目录 前言一、情景介绍二、实现步骤1. 打 Jar 包2. 编写 bat 批处理文件3. bat 转 exe 前言 最近使用 GUI 帮朋友写了一个软件,为了方便他处理工作上的重复性且很麻烦的事情,程序是使用 Java 写的,就不得不面对一个问题:我必须将…...
第一篇:系统分析师首篇
目录 一、目标二、计划三、完成情况1.宏观思维导图2.过程中的团队管理和其它方面的思考 四、意外之喜(最少2点)1.计划内的明确认知和思想的提升标志2.计划外的具体事情提升内容和标志 一、目标 通过参加考试,训练学习能力,而非单纯以拿证为目的。 1.在复…...
自动关机监控器软件 - 您的电脑节能助手
## 自动关机监控器 - 您的电脑节能助手 自动关机监控器是一款基于Python开发的实用工具,旨在帮助用户节省电力资源并延长电脑使用寿命。该程序通过监控用户的鼠标和键盘活动,在设定的无活动时间后自动关闭计算机,特别适合需要长时间离开电脑但…...
线程概念与控制(中)
线程概念与控制(上)https://blog.csdn.net/Small_entreprene/article/details/146464905?sharetypeblogdetail&sharerId146464905&sharereferPC&sharesourceSmall_entreprene&sharefrommp_from_link我们经过上一篇的学习,接…...
K8S学习之基础六十二:helm部署memcached服务
helm部署memcached服务 #安装memcached的Chart docker load -i memcache_1_4_36.tar.gz #如果k8s用的是docker做容器运行时,用docker load -i导出镜像 ctr -nk8s.io images import memcache_1_4_36.tar.gz #如果k8s用的是containerd做容器运行时,用ctr…...
CPU 超线程技术以及如何关闭CPU超线程功能
CPU超线程技术介绍 CPU 超线程技术(Hyper-Threading Technology,HT)是英特尔提出的一种同时多线程(Simultaneous Multi-Threading, SMT)实现方式,其核心思想是通过逻辑层面的优化,让单个物理…...
Redis 源码硬核解析系列专题 - 第二篇:核心数据结构之SDS(Simple Dynamic String)
1. 引言 Redis没有直接使用C语言的标准字符串(以\0结尾的字符数组),而是自定义了SDS(Simple Dynamic String)。SDS是Redis的基础数据结构之一,广泛用于键值存储、命令参数等场景。本篇将深入剖析SDS的实现原理、优势以及源码细节。 2. 为什么不用C标准字符串? C字符串…...
1--当「穷举」成为艺术:CTF暴力破解漏洞技术从入门到入刑指南(知识点讲解版)
当「穷举」成为艺术:CTF暴力破解漏洞技术从入门到入刑指南 引言:论暴力破解的哲学意义 “世界上本没有漏洞,密码设得简单了,便成了漏洞。” —— 鲁迅(并没有说过) 想象你是个不会撬锁的小偷,面…...
DHCP报文的详细流程
在DHCP协议的工作流程中,Discover和Request报文使用广播MAC地址,而Offer和ACK报文通常使用单播MAC地址。这种差异源于DHCP协议的设计逻辑和网络通信的实际需求,具体原因如下: 1. DHCP报文交互流程 DHCP的完整流程分为四个阶段…...
通信协议和特征
文章目录 双工时钟电平串并行 双工 全双工:全双工通信允许同一时刻数据在两个方向上同时进行传输。一般来说,全双工的通信都有两根数据线,一根发送,一根接收,二者互不影响。半双工:允许数据在两个方向上传…...
Python 循环全解析:从语法到实战的进阶之路
一、问答题 (1)下面的循环体被重复了多少次?每次循环的输出结果是什么? i1 while i < 10:if i % 2 0:print(i)死循环,没有输出结果 i1 while i < 10:if i % 2 0:print(i)i l死循环,没有输出结果 i 1 while i< 10…...
The Rust Programming Language 学习 (七)
常见集合 使用 Vector 存储表 Vec<T>,也被称为 vector。vector 允许我们在一个单独的数据结构中储存多于一个的值,它在内存中彼此相邻地排列所有的值。vector 只能储存相同类型的值。它们在拥有一系列项的场景下非常实用 新建Vector 为了创建一…...
[GXYCTF2019]禁止套娃1 [GitHack] [无参数RCE]
Git基础 Git信息泄露原理解析及利用总结 - FreeBuf网络安全行业门户 CTF中的GIT泄露_ctf git泄露-CSDN博客 Git结构 dirsearch扫出来一大堆东西(然而这些并没有什么屁用) 但也算起码了解了git结构了吧 /.git/HEAD:表示当前HEAD指针的指…...
从ChatGPT到AutoGPT——AI Agent的范式迁移
一、AI Agent的范式迁移 1. ChatGPT的局限性与Agent化需求 单轮对话的“工具属性” vs. 多轮复杂任务的“自主性” ChatGPT 作为强大的生成式AI,虽然能够进行连贯对话,但本质上仍然是“工具型”AI,依赖用户提供明确的指令,而无法自主规划和执行任务。 人类介入成本过高:提…...
stock-pandas,一个易用的talib的替代开源库。
原创内容第841篇,专注智能量化投资、个人成长与财富自由。 介绍一个ta-lib的平替——我们来实现一下,最高价突破布林带上轨,和最低价突破布林带下轨的可视化效果: cross_up_upper stock[high].copy()# cross_up_upper 最高价突破…...
Spring Cloud Gateway详细介绍简单案例
文章目录 1、Spring Cloud Gateway 详细介绍1.1. 统一入口(Single Entry Point)1.2. 请求路由(Request Routing)1.3. 负载均衡(Load Balancing)1.4. 流量控制(Rate Limiting)1.5. 身…...
鸿蒙原生开发之状态管理V2
一、ArkTS状态变量的定义: State:状态,指驱动UI更新的数据。用户通过触发组件的事件方法,改变状态数据。状态数据的改变,引起UI的重新渲染。 在鸿蒙原生开发中,使用ArkTS开发UI的时候,我们可以…...
矩阵中对角线的遍历问题【C++】
1,按对角线进行矩阵排序 题目链接:3446. 按对角线进行矩阵排序 - 力扣(LeetCode) 【题目描述】 对于一个m*n的矩阵grid,要求对该矩阵进行 变换,使得变换后的矩阵满足: 主对角线右上的所有对角…...
Python小练习系列 Vol.4:迷宫寻路(回溯 + DFS)
🧠 Python小练习系列 Vol.4:迷宫寻路(回溯 DFS) 🚪 本期我们将探索一个二维世界,借助回溯算法帮助角色走出迷宫!这是学习路径搜索类题目的经典案例。 🧩 一、题目描述 给定一个二维…...
[Lc4_dfs] 解数独 | 单词搜索
目录 1.解数独 题解 2.单词搜索 题解 1.解数独 链接:37. 解数独 编写一个程序,通过填充空格来解决数独问题。 数独的解法需 遵循如下规则: 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线…...
day17 学习笔记
文章目录 前言一、数组的增删改查1.resize函数2.append函数3.insert函数4.delete函数5.argwhere函数6.unique函数 二、统计函数1.amax,amin函数2.ptp函数3.median函数4.mean函数5.average函数6.var,std函数 前言 通过今天的学习,我掌握了num…...
自动语音识别(ASR)技术详解
语音识别(Automatic Speech Recognition, ASR)是人工智能和自然语言处理领域的重要技术,旨在将人类的语音信号转换为对应的文本。近年来,深度学习的突破推动语音识别系统从实验室走入日常生活,为智能助手、实时翻译、医…...
