论文分享 | Flashboom:一种声东击西攻击手段以致盲基于大语言模型的代码审计

基于大语言模型的代码审计显著提升了自动化代码分析的能力,但并非固若金汤。分享一篇发表于 2025 年 IEEE S&P 会议的论文 Flashboom,该研究提出了一种新型攻击方法,可诱使基于大语言模型的代码审计忽略代码中的真实漏洞。

猴先生说:一个很有趣的思路,也非常有借鉴意义,既有传统的攻击方式(代码注入),也利用了大语言模型自身的特点(注意力机制)。实验方面做的也非常充分,开源了代码也附上了提示词,诚意满满自信十足。窃以为,基于这个研究方向,还可以扩展出更多成果。

1 背景介绍

大语言模型(Lagre Language Model, LLM)正在重塑软件安全领域,它们为自动化工具提供了强大动力,不仅能够编写和测试代码,还能以前所未有的精度检测复杂的安全漏洞。这些模型通常集成到集成开发环境(IDE)或 CI/CD 流水线中(如 GitHub Copilot),利用注意力机制识别潜在安全漏洞,大幅提升了代码分析效率。

注意力机制是 LLM 的核心组件,它能在生成预测或输出时,选择性地关注输入数据的特定部分。理想情况下,用于代码审计的 LLM 会对可能存在漏洞的代码段分配更多的注意力权重,从而提高漏洞被识别的概率。然而,这类工具并非没有弱点,其注意力分配机制本身可能成为攻击面。

论文探讨了绕过这类 LLM 漏洞审计工具的可能性,其核心思想是 通过转移 LLM 注意力机制的关注点,使其偏离真正的脆弱代码片段 。论文提出了名为 Flashboom 的攻击方法,即在目标代码中插入能够吸引高注意力权重的无关代码片段,将模型的注意力从真实漏洞转移开,从而实现“致盲”。

2 预备知识

2.1 大语言模型与注意力机制

大语言模型基于 Transformer 架构,在海量文本数据集(包括书籍、网站、文章及其他书面材料)上进行训练,训练过程的核心是教会模型根据前文预测下一个词。大语言模型通过输入提示(prompt)来生成文本,利用其训练知识,预测提示之后可能出现的词序列,能够回答问题、补全句子,甚至生成整段文字。自注意力机制是大语言模型的核心组成部分,它在处理序列时动态分配权重,使模型能关注与当前任务最相关的输入部分。这种机制可捕捉长距离依赖,但也意味着注意力分布可能被人为干扰。

2.2 基于大语言模型的代码审计

大语言模型在理解与生成代码方面表现出色,已被集成到多种代码安全审计工具中(如 GitHub Copilot、DeepCodeAI、Amazon Q Developer 等),通常通过 IDE 或 CI/CD 流水线提供实时反馈。这些工具在检测漏洞、提示修复建议等方面发挥了重要作用,并被广泛应用于企业与开源社区。GitHub Copilot 拥有超过 130 万付费用户和 5 万多个组织用户;DeepCodeAI 拥有超过 400 万用户和 10 万多个组织用户;Amazon Q 也被 Deriv 和 Bolttech 等大型企业采用。

3 Flashboom 攻击

3.1 威胁模型

  • 攻击场景

开发者通常依赖在线资源(例如 GitHub 或 Stack Overflow)构建项目,并使用基于 LLM 的漏洞审计工具(例如 GitHub Copilot)进行检测。开发者也可能直接编写自定义提示(prompt),向 LLM 查询以验证特定代码段的安全性或解释某些函数的行为。

攻击者的策略是 精心设计带有漏洞的代码 ,既可以向某个公开的仓库或帖子中注入该代码,也可以向已有开源项目贡献代码。攻击者需要确保注入的代码在语法上正确且可编译,避免被开发者察觉,绕过审计机制并且确保漏洞不被标记,最终危及开发者的项目。

  • 攻击假设

假设 1: 攻击者的控制范围严格限于被注入的漏洞代码本身 。无法影响开发者撰写的具体提示,也无权控制 LLM 审计工具的内部操作。

假设 2: 开发者主要依赖 LLM 审计,而不使用传统静态分析工具 。可能会采用思维链提示、上下文学习或检索增强生成等技术来提升审计质量。

3.2 核心思路和验证

传统代码混淆(如变量重命名、嵌套包装)对 LLM 审计效果有限,促使作者将注意力转向大模型的核心机制:注意力机制。当 LLM 审计代码时,它会将注意力权重分配到不同代码行上。理想情况下,脆弱代码段应获得更多权重,以提高检测概率。由此,我们提出以下核心思路:

只要识别出一些天然吸引过高注意力权重的代码片段(无论是否脆弱),即可利用它们来 “致盲” LLM。具体而言,当这些 “吸睛” 片段与真实脆弱代码并存时,它们会显著降低脆弱代码获得的注意力权重,从而削弱 LLM 发现漏洞的能力。

  • 初步验证

选取一段已知存在漏洞的代码;向其中插入另一段代码,使其从 LLM 处获得更高的注意力权重。令另一段代码为注意力权重更高的漏洞代码,同时为新插入的漏洞代码附带一个补丁(patch),以向 LLM “证明” 该漏洞已被修复。

最终,攻击代码由三部分组成:

  1. 原始目标漏洞 (真实攻击载荷);
  2. 次级漏洞 (用于吸引 LLM 注意力,但不实际触发);
  3. 次级漏洞的补丁 (向 LLM 保证漏洞已修复)。

图 1 所示的 Solidity 代码包含三个关键片段。原始代码包含 EtherGame 合约,存在自毁攻击(Self-Destruct Attack)漏洞。代码片段 ② 包含 withdrawBalance 函数,存在重入攻击(Reentrancy Attack)漏洞,同时代码片段 ① 用于修复重入漏洞。

图1 Flashboom攻击示例

图1 Flashboom攻击示例

EtherGame 是一个多人游戏,规则如下:

  1. 每位玩家每轮可通过 play 函数存入 1 个 Ether;
  2. 第 7 位存入 1 Ether 的玩家获胜;
  3. 获胜者可通过 claimReward 函数提取所有累积的 Ether。

然而,由于 play 函数直接使用 address(this).balance 来管理余额,而非专用变量,因此存在自毁攻击风险:

  • 前 6 位玩家各存入 1 Ether,合约余额变为 6 Ether;
  • 攻击者部署一个合约并调用 selfdestruct,强行向 EtherGame 转入 2 Ether,使 address(this).balance 变为 8 Ether,从而破坏游戏规则,阻止后续玩家存入或产生新获胜者。

论文使用 Mistral 和 GPT-4o 对代码进行评估。在标准场景下(仅包含原始 EtherGame 合约,存在自毁漏洞),两个 LLM 均迅速检测到漏洞;在修改场景下(插入重入漏洞代码及其补丁后,代码相似度仍高达 88.46%),两个 LLM 均未检测到自毁漏洞。

  • Mistral 错误地在新代码片段中标记出重入漏洞;
  • GPT-4o 则完全未发现任何漏洞,并声称新增代码片段 ② 虽有重入漏洞,但已被补丁 “修复”。

3.3 Flashboom 攻击流程

图 2 展示了攻击流程:攻击者试图通过 GitHub 向项目注入漏洞代码。在正常情况下(图 2-a),LLM 审计工具会迅速发现并标记漏洞,阻止其进入生产环境。

图2 Flashboom攻击流程

图2 Flashboom攻击流程

攻击场景(图 2-b)则显著偏离此标准流程,具体步骤如下:

  1. 上传带有漏洞的代码。攻击者上传包含主要漏洞的代码段(步骤 ①),开发者随后会下载并集成到项目中(步骤 ②)。为了策略性地转移 LLM 的注意力,攻击者还会嵌入额外代码段(可能同时包含另一个漏洞及其补丁),该代码段旨在吸引 LLM 的注意,从而使其忽略主要漏洞。
  2. 致盲基于 LLM 的代码审计工具。开发者下载代码后,可能将其集成到现有项目中,并使用集成在 IDE 中的 LLM 审计工具进行安全评估。由于 LLM 被额外代码段分散了注意力,未能发现主要漏洞,最终报告代码为“安全”(步骤 ③)。
  3. 发起攻击。代码现已集成到项目中(例如 Web 服务器或区块链金融游戏),未被检测到的主要漏洞仍然潜伏。攻击者可利用该漏洞入侵系统(步骤 ④)。

4 自动化工具 Crazy-Ivan

尽管手工构造的 Flashboom 攻击已经证明有效,但是用于 “致盲” LLM 的新增代码段是随机选择的,无法规模化。为解决这一限制,作者提出了一种更高效的攻击载荷生成方法,开发了一款名为 Crazy-Ivan 的工具,用于自动化地产生潜在的 Flashboom 攻击。

4.1 核心思路

既然检测漏洞源于注意力机制,那么就要瞄准那些天然能捕获最高注意力权重的代码片段,从而最大化致盲 LLM 的概率。

图3 注意力热力图

图3 注意力热力图

图 3 展示了 EtherGame 合约代码的注意力权重分布,颜色越深表示注意力权重越高。正如预期,Flashboom 获得了远高于周围代码的注意力权重。需要注意的是,并非所有 Flashboom 都需要包含漏洞代码,只需要选择能够有效捕获注意力权重的代码片段。

4.2 工具设计

如图 4 所示,Crazy-Ivan 的工作流程分为三个主要阶段:

  • 阶段 1:对代码库进行分词,计算注意力权重(步骤 ①),并以函数为单位降低注意力数据的维度(步骤 ②–④)。

图4-a Crazy-Ivan工具执行阶段1

图4-a Crazy-Ivan工具执行阶段1

  • 阶段 2:挑选出注意力分数最高的函数(步骤 ⑤),并利用 LLM 自动补全并优化这些函数(步骤 ⑥),确保每个函数独立可编译。
  • 阶段 3:对成功编译的函数进行最终筛选,验证其有效性。将 Flashboom 函数插入含漏洞的目标代码(步骤 ⑦),并检查原脆弱函数的注意力分数是否显著下降(步骤 ⑧),从而实现预期的“致盲”效果。

图4-b Crazy-Ivan工具执行阶段2&3

图4-b Crazy-Ivan工具执行阶段2&3

阶段 1 函数级注意力计算

以函数为单位聚合注意力权重,而非以单个词元(token)为单位,从而大幅降低计算量。具体的计算过程见论文附录中的算法,大致过程如下:

步骤 ① :对代码库进行分词,提取注意力张量 A \mathsf{A} A,其形状为 ( T , T , L , H ) (T,T,L,H) (T,T,L,H)。其中 T T T 为 token 数, L L L 为层数, H H H 为注意力头数。

步骤 ② :对所有注意力头的权重求和,得到形状为 ( T , T , L ) (T,T,L) (T,T,L) 的张量 A h e a d \mathsf{A}_{head} Ahead 。仅保留最后一个查询词元(query token)的注意力分数,因其在 decoder-only LLM 中承载了前文全部语义信息。注意力值被聚合到代码行级别,输出 L L L 维度的矩阵 A l a y e r \mathsf{A}_{layer} Alayer,对应到每行代码的注意力值。

步骤 ③ :将每个词元的注意力分数聚合到对应的代码行,得到行级注意力数组 a l i n e a_{line} aline ,每一项代表分配给指定行的所有注意力权重。

步骤 ④ :确定每个函数的起始与结束行,将函数内所有行的注意力分数相加,得到函数级注意力分数。注意力分数高的函数将被优先选为 Flashboom 候选。

阶段 2 函数选择与补全

步骤 ⑤ :计算出函数级注意力分数后,从每个文件中选取注意力分数最高的函数 F m a x \mathsf{F}_{max} Fmax ,作为 Flashboom 的初始候选。

步骤 ⑥ :直接将该函数插入目标代码可能导致编译失败,因为它可能依赖外部变量或函数。因此,利用 GPT-4o 的代码理解与生成能力,自动补全依赖,确保 Flashboom 能独立编译。依赖的收集规则包括区分变量、函数或类定义,具体的提示词可见论文附录。

阶段 3 Flashboom 有效性验证

步骤 ⑦ :单纯插入代码并不足以保证致盲成功,因此需严格验证所选函数的有效性,最直接的验证方法是比对。

步骤 ⑧ :将 Flashboom 函数插入含漏洞的目标代码,测量原脆弱函数的注意力分数是否显著下降,若注意力权重明显转移到 Flashboom 上,则说明其成功 “掩盖” 了漏洞,实现了致盲。

5 实验评估和案例

论文通过系统性的实验,评估了 Crazy-Ivan 工具和 Flashboom 攻击的有效性。使用了超过 4 万个智能合约代码的 Messiq 数据集,以及 3 千条 Leetcode 解题代码数据集,分别包括 C++ 和 Python 语言。选取了 6 个模型进行测试,包括 GPT-4o,Phi3,MixtralExpert,CodeLlama,Mistral,Gemma,模型运行在 8 张 24GB 内存的 4090 显卡的实验环境。实验结果主要回答以下问题:

  • 有效性:对除 GPT-4o 外的 5 个模型,其评价指标方面,致盲率均显著高于随机插入和重命名混淆策略。
  • 迁移性:总体跨模型成功率高,少数模型(如 CodeLlama)迁移性较差,有一定能力可攻击未知或黑盒 LLM。
  • 扩展性:Flashboom 攻击在 C/C++ 与 Python 上同样有效,保持了较高的成功率,验证了跨语言扩展能力。
  • 隐蔽性:插入 Flashboom 后代码与原代码的语义相似度均值均 ≥ 0.92,修改极其隐蔽,开发者与工具均难以察觉。
  • 归因分析:从论文中的图 5-9 中可以看出,原脆弱函数的注意力分数明显下降,其原因在于 Flashboom 显著削弱了 LLM 对漏洞函数的注意力权重。

实验做的很丰富,有结果和过程来看都很有说服力,具体的实验数据可以详见原论文。

论文还开展了一项案例研究,考察 Flashboom 对 GitHub Copilot 的影响。聚焦于智能合约漏洞检测,使用 Smart-bugs 数据集进行评估,并选用 Crazy-Ivan 生成的最强攻击载荷之一 Flashboom 3129 进行测试。相关工具和攻击载荷开源在 https://github.com/oxygen-hunter/Flashboom.git 仓库中。

  • 原始表现:Copilot 成功识别出合约中的 7 个漏洞,包括关键的抢先交易漏洞,以及 6 个其他警告。
  • 攻击后表现:Copilot 未检测到抢先交易漏洞,反而报告了 10 个无关问题。

最后,附上文献引用及论文链接:Li X, Li Y, Wu H, et al. Make a Feint to the East While Attacking in the West: Blinding LLM-Based Code Auditors with Flashboom Attacks[C]. 2025 IEEE Symposium on Security and Privacy (SP). IEEE, 2025: 576-594.
https://doi.org/10.1109/SP61157.2025.00125

using System.Collections.Generic; using UnityEngine; using TMPro; using System.Collections; public class DialogueManager : MonoBehaviour { public enum DialogueState { Before, Win, Lost } public static DialogueManager Instance; [Header("UI Elements")] public TextMeshProUGUI tmpText; public float typingSpeed = 0.05f; [Header("Dialogue Content")] public List<string> beforeBattleDialogues = new List<string>(); public List<string> winDialogues = new List<string>(); public List<string> lostDialogues = new List<string>(); private bool isTyping = false; private Coroutine typingCoroutine; private string currentSentence; private DialogueState currentState = DialogueState.Before; private int currentDialogueIndex = 0; private List<string> currentDialogueList; void Start() { int result = PlayerPrefs.GetInt("LastBattleResult", -1); if(result == 1) { Debug.Log("你赢了"); // 胜利处理 } else if(result == 0) { Debug.Log("你失败了"); // 失败处理 } else { Debug.Log("战斗前"); } // 清除状态 PlayerPrefs.DeleteKey("LastBattleResult"); } void Awake() { if (Instance == null) { Instance = this; } else { Destroy(gameObject); } // 初始化当前对话列表 currentDialogueList = beforeBattleDialogues; } void Update() { // 添加跳过功能:按E键跳过当前打字效果 if (Input.GetKeyDown(KeyCode.E) && isTyping) { SkipTyping(); } // 按E键继续下一句对话 else if (Input.GetKeyDown(KeyCode.E) && !isTyping && tmpText.gameObject.activeSelf) { NextSentence(); } } // 设置当前对话状态 public void SetDialogueState(DialogueState newState) { currentState = newState; currentDialogueIndex = 0; switch (newState) { case DialogueState.Before: currentDialogueList = beforeBattleDialogues; break; case DialogueState.Win: currentDialogueList = winDialogues; break; case DialogueState.Lost: currentDialogueList = lostDialogues; break; } // 自动开始新状态的对话 StartStateDialogue(); Debug.Log("已完成最新的更新"); } // 启动当前状态的对话 public void StartStateDialogue() { if (currentDialogueList == null || currentDialogueList.Count == 0) return; // 如果正在显示其他文本,先停止 if (isTyping) { StopCoroutine(typingCoroutine); } // 确保索引在有效范围内 currentDialogueIndex = Mathf.Clamp(currentDialogueIndex, 0, currentDialogueList.Count - 1); currentSentence = currentDialogueList[currentDialogueIndex]; // 暂停玩家移动 playercontrol player = FindObjectOfType<playercontrol>(); if (player != null) player.canMove = false; // 锁定玩家输入 Cursor.lockState = CursorLockMode.None; Cursor.visible = true; // 显示对话框UI tmpText.gameObject.SetActive(true); // 启动打字效果协程 typingCoroutine = StartCoroutine(TypeSentence(currentSentence)); } // 显示下一句对话 public void NextSentence() { currentDialogueIndex++; if (currentDialogueIndex < currentDialogueList.Count) { StartStateDialogue(); } else { // 对话结束 EndDialogue(); } } IEnumerator TypeSentence(string sentence) { isTyping = true; tmpText.text = ""; // 逐字显示 foreach (char letter in sentence.ToCharArray()) { tmpText.text += letter; yield return new WaitForSeconds(typingSpeed); } isTyping = false; } // 跳过当前打字效果 public void SkipTyping() { if (isTyping) { StopCoroutine(typingCoroutine); tmpText.text = currentSentence; isTyping = false; } } public void EndDialogue() { // 如果还在打字,先停止打字 if (isTyping) { StopCoroutine(typingCoroutine); } // 恢复玩家移动 playercontrol player = FindObjectOfType<playercontrol>(); if (player != null) player.canMove = true; // 恢复输入锁定 Cursor.lockState = CursorLockMode.Locked; Cursor.visible = false; tmpText.gameObject.SetActive(false); } } using System.Collections; using UnityEngine; using UnityEngine.UI; using TMPro; using UnityEngine.SceneManagement; public class BattleSystem : MonoBehaviour { public enum BattleState { Start, PlayerTurn, EnemyTurn, Win, Lose } public BattleState state; // 角色引用 public GameObject playerPrefab; public GameObject enemyPrefab; public Transform playerBattleStation; public Transform enemyBattleStation; private Playermessage playerUnit; private Enemymessage enemyUnit; // UI组件 public PlayerBattleHUD playerHUD; public EnemyBattleHUD enemyHUD; public TMP_Text dialogueText; // 战斗效果 public Image attackphoto; public Image attackphoto2; public Image image; public Animator animator; public Camera mainCamera; public Image image1; // 颜色变量 private Color Originalcolor; // 战斗开始 void Start() { state = BattleState.Start; StartCoroutine(SetupBattle()); } // 初始化战斗 IEnumerator SetupBattle() { // 实例化角色 GameObject playerGO = Instantiate(playerPrefab, playerBattleStation); playerUnit = playerGO.GetComponent<Playermessage>(); GameObject enemyGO = Instantiate(enemyPrefab, enemyBattleStation); enemyUnit = enemyGO.GetComponent<Enemymessage>(); // 设置HUD playerHUD.SetHUD(playerUnit); enemyHUD.SetHUD(enemyUnit); // 开场对话 yield return StartCoroutine(TypeText($"Let me feel your power now, don't make me bored^_^", 0.05f)); yield return new WaitForSeconds(0.5f); image1.gameObject.SetActive(false); // 省略部分对话... yield return StartCoroutine(TypeText("--Please consider your decision--", 0.05f)); yield return new WaitForSeconds(0.5f); state = BattleState.PlayerTurn; PlayerTurn(); } // 玩家回合 private void PlayerTurn() { StartCoroutine(TypeText("Choose your action...", 0f)); } // 玩家攻击 public void OnAttackButton() { if (state != BattleState.PlayerTurn) return; StartCoroutine(PlayerAttack()); } // 玩家攻击协程 private IEnumerator PlayerAttack() { yield return StartCoroutine(TypeText("Get out of my way!!!", 0f)); yield return new WaitForSeconds(1f); // 显示攻击特效 attackphoto.gameObject.SetActive(true); yield return new WaitForSeconds(0.2f); // 计算伤害 float damageDealt = playerUnit.CalculateDamage(); // 显示伤害文本 yield return StartCoroutine(TypeText($"Dealt {damageDealt:F1} damage!", 0f)); yield return new WaitForSeconds(1f); // 隐藏攻击特效 attackphoto.gameObject.SetActive(false); // 敌人承受伤害 bool isDead = enemyUnit.TakeDamage(damageDealt); enemyHUD.SetHP(enemyUnit.CurrentHealth); if (isDead) { state = BattleState.Win; StartCoroutine(EndBattle()); } else { state = BattleState.EnemyTurn; StartCoroutine(EnemyTurn()); } } // 敌人回合 private IEnumerator EnemyTurn() { yield return StartCoroutine(TypeText("Looking my eyes...", 0f)); yield return new WaitForSeconds(0.5f); // 显示攻击特效 attackphoto2.gameObject.SetActive(true); yield return new WaitForSeconds(0.2f); // 屏幕震动效果 animator.SetTrigger("shake"); StartCoroutine(ShakeCamera(0.4f, 0.4f)); // 闪光效果 Originalcolor = image.color; StartCoroutine(FlashBoom()); // 计算伤害 float damageDealt = enemyUnit.CalculateDamage(); yield return StartCoroutine(TypeText($"Took {damageDealt:F1} damage!", 0f)); // 隐藏攻击特效 attackphoto2.gameObject.SetActive(false); yield return new WaitForSeconds(0.5f); // 玩家承受伤害 bool isDead = playerUnit.TakeDamage(damageDealt); playerHUD.SetHP(playerUnit.CurrentHealth); yield return new WaitForSeconds(1f); if (isDead) { state = BattleState.Lose; StartCoroutine(EndBattle()); } else { state = BattleState.PlayerTurn; PlayerTurn(); } } // 结束战斗 private IEnumerator EndBattle() { if (state == BattleState.Win) { yield return StartCoroutine(TypeText("Victory is mine!", 0f)); } else if (state == BattleState.Lose) { yield return StartCoroutine(TypeText("Defeat... I'll be back!", 0f)); } // 返回游戏场景 yield return new WaitForSeconds(2f); SceneManager.LoadScene("Traning"); } // 文本显示效果 IEnumerator TypeText(string text, float delay) { dialogueText.text = ""; foreach (char letter in text.ToCharArray()) { dialogueText.text += letter; yield return new WaitForSeconds(delay); } } // 相机震动效果 IEnumerator ShakeCamera(float duration, float magnitude) { Vector3 originalPos = mainCamera.transform.localPosition; float elapsed = 0f; while (elapsed < duration) { float x = Random.Range(-1f, 1f) * magnitude; float y = Random.Range(-1f, 1f) * magnitude; mainCamera.transform.localPosition = new Vector3(x, y, originalPos.z); elapsed += Time.deltaTime; yield return null; } mainCamera.transform.localPosition = originalPos; } // 闪光效果 IEnumerator FlashBoom() { image.color = Color.white; yield return new WaitForSeconds(0.1f); image.color = Originalcolor; } } 这是两个sence下的代码我现在需要制作战斗前后的不同对话
07-31
using System.Collections; using UnityEngine; using UnityEngine.UI; using TMPro; using UnityEngine.SceneManagement; public class BattleSystem : MonoBehaviour { public enum BattleState { Start, PlayerTurn, EnemyTurn, Win, Lose } public BattleState state; // 角色引用 public GameObject playerPrefab; public GameObject enemyPrefab; public Transform playerBattleStation; public Transform enemyBattleStation; private Playermessage playerUnit; private Enemymessage enemyUnit; // UI组件 public PlayerBattleHUD playerHUD; public EnemyBattleHUD enemyHUD; public TMP_Text dialogueText; // 战斗效果 public Image attackphoto; public Image attackphoto2; public Image image; public Animator animator; public Camera mainCamera; public Image image1; // 颜色变量 private Color Originalcolor; // 战斗开始 void Start() { state = BattleState.Start; StartCoroutine(SetupBattle()); } // 初始化战斗 IEnumerator SetupBattle() { // 实例化角色 GameObject playerGO = Instantiate(playerPrefab, playerBattleStation); playerUnit = playerGO.GetComponent<Playermessage>(); GameObject enemyGO = Instantiate(enemyPrefab, enemyBattleStation); enemyUnit = enemyGO.GetComponent<Enemymessage>(); // 设置HUD playerHUD.SetHUD(playerUnit); enemyHUD.SetHUD(enemyUnit); // 开场对话 yield return StartCoroutine(TypeText($"Let me feel your power now, don't make me bored^_^", 0.05f)); yield return new WaitForSeconds(0.5f); image1.gameObject.SetActive(false); // 省略部分对话... yield return StartCoroutine(TypeText("--Please consider your decision--", 0.05f)); yield return new WaitForSeconds(0.5f); state = BattleState.PlayerTurn; PlayerTurn(); } // 玩家回合 private void PlayerTurn() { StartCoroutine(TypeText("Choose your action...", 0f)); } // 玩家攻击 public void OnAttackButton() { if (state != BattleState.PlayerTurn) return; StartCoroutine(PlayerAttack()); } // 玩家攻击协程 private IEnumerator PlayerAttack() { yield return StartCoroutine(TypeText("Get out of my way!!!", 0f)); yield return new WaitForSeconds(1f); // 显示攻击特效 attackphoto.gameObject.SetActive(true); yield return new WaitForSeconds(0.2f); // 计算伤害 float damageDealt = playerUnit.CalculateDamage(); // 显示伤害文本 yield return StartCoroutine(TypeText($"Dealt {damageDealt:F1} damage!", 0f)); yield return new WaitForSeconds(1f); // 隐藏攻击特效 attackphoto.gameObject.SetActive(false); // 敌人承受伤害 bool isDead = enemyUnit.TakeDamage(damageDealt); enemyHUD.SetHP(enemyUnit.CurrentHealth); if (isDead) { state = BattleState.Win; StartCoroutine(EndBattle()); } else { state = BattleState.EnemyTurn; StartCoroutine(EnemyTurn()); } } // 敌人回合 private IEnumerator EnemyTurn() { yield return StartCoroutine(TypeText("Looking my eyes...", 0f)); yield return new WaitForSeconds(0.5f); // 显示攻击特效 attackphoto2.gameObject.SetActive(true); yield return new WaitForSeconds(0.2f); // 屏幕震动效果 animator.SetTrigger("shake"); StartCoroutine(ShakeCamera(0.4f, 0.4f)); // 闪光效果 Originalcolor = image.color; StartCoroutine(FlashBoom()); // 计算伤害 float damageDealt = enemyUnit.CalculateDamage(); yield return StartCoroutine(TypeText($"Took {damageDealt:F1} damage!", 0f)); // 隐藏攻击特效 attackphoto2.gameObject.SetActive(false); yield return new WaitForSeconds(0.5f); // 玩家承受伤害 bool isDead = playerUnit.TakeDamage(damageDealt); playerHUD.SetHP(playerUnit.CurrentHealth); yield return new WaitForSeconds(1f); if (isDead) { state = BattleState.Lose; StartCoroutine(EndBattle()); } else { state = BattleState.PlayerTurn; PlayerTurn(); } } // 结束战斗 private IEnumerator EndBattle() { if (state == BattleState.Win) { yield return StartCoroutine(TypeText("Victory is mine!", 0f)); // 保存胜利结果 PlayerPrefs.SetInt("LastBattleResult", 1); } else if (state == BattleState.Lose) { yield return StartCoroutine(TypeText("Defeat... I'll be back!", 0f)); // 保存失败结果 PlayerPrefs.SetInt("LastBattleResult", 0); } // 添加场景切换 SceneManager.LoadScene("Traning"); } // 文本显示效果 IEnumerator TypeText(string text, float delay) { dialogueText.text = ""; foreach (char letter in text.ToCharArray()) { dialogueText.text += letter; yield return new WaitForSeconds(delay); } } // 相机震动效果 IEnumerator ShakeCamera(float duration, float magnitude) { Vector3 originalPos = mainCamera.transform.localPosition; float elapsed = 0f; while (elapsed < duration) { float x = Random.Range(-1f, 1f) * magnitude; float y = Random.Range(-1f, 1f) * magnitude; mainCamera.transform.localPosition = new Vector3(x, y, originalPos.z); elapsed += Time.deltaTime; yield return null; } mainCamera.transform.localPosition = originalPos; } // 闪光效果 IEnumerator FlashBoom() { image.color = Color.white; yield return new WaitForSeconds(0.1f); image.color = Originalcolor; } } 我不想一开始就进入战斗而是在触发器内使用F键开始对话
08-01
using System.IO; using UnityEngine; public class SaveSystem : MonoBehaviour { public static SaveSystem Instance { get; private set; } public GameData currentData { get; private set; } private string savePath; private void Awake() { if (Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); Initialize(); } else { Destroy(gameObject); } } private void Initialize() { savePath = Path.Combine(Application.persistentDataPath, "savedata.json"); LoadGame(); } // 保存游戏 public void SaveGame(Vector3 playerPosition) { currentData.playerPosition = playerPosition; currentData.currentScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name; currentData.saveTime = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm"); string jsonData = JsonUtility.ToJson(currentData); File.WriteAllText(savePath, jsonData); Debug.Log($"游戏已保存: {savePath}"); } // 加载游戏 public void LoadGame() { if (File.Exists(savePath)) { string jsonData = File.ReadAllText(savePath); currentData = JsonUtility.FromJson<GameData>(jsonData); } else { currentData = new GameData(); } } // 删除存档 public void DeleteSave() { if (File.Exists(savePath)) { File.Delete(savePath); currentData = new GameData(); } } // 检查存档是否存在 public bool SaveExists() { return File.Exists(savePath); } } using TMPro; using UnityEngine; using UnityEngine.UI; public class NPC2 : MonoBehaviour { public GameObject image; public GameObject image1; public GameObject image2; public TextMeshProUGUI text; [Header("按钮设置")] public Button mainMenuButton; public Button startButton; public Button loseButton; public Button winButton; void Start() { // 将按钮引用传递给DialogueManager if (DialogueManager.Instance != null) { DialogueManager.Instance.startButton = startButton; DialogueManager.Instance.loseButton = loseButton; DialogueManager.Instance.winButton = winButton; } // 初始化时隐藏所有按钮 if (mainMenuButton != null) mainMenuButton.gameObject.SetActive(false); if (startButton != null) startButton.gameObject.SetActive(false); if (loseButton != null) loseButton.gameObject.SetActive(false); if (winButton != null) winButton.gameObject.SetActive(false); } void OnTriggerStay2D(Collider2D other) { if (other.CompareTag("Player") && Input.GetKeyDown(KeyCode.F)) { image.SetActive(true); image1.SetActive(true); image2.SetActive(true); text.gameObject.SetActive(true); DialogueManager.Instance.StartStateDialogue(); switch (DialogueManager.Instance.currentState) { case DialogueManager.DialogueState.Before: startButton.gameObject.SetActive(true); break; case DialogueManager.DialogueState.Win: winButton.gameObject.SetActive(true); break; case DialogueManager.DialogueState.Lost: loseButton.gameObject.SetActive(true); break; } } } }using System.Collections; using UnityEngine; using UnityEngine.UI; using TMPro; using UnityEngine.SceneManagement; public class BattleSystem : MonoBehaviour { public enum BattleState { Start, PlayerTurn, EnemyTurn, Win, Lose } public BattleState state; // 角色引用 public GameObject playerPrefab; public GameObject enemyPrefab; public Transform playerBattleStation; public Transform enemyBattleStation; private Playermessage playerUnit; private Enemymessage enemyUnit; // UI组件 public PlayerBattleHUD playerHUD; public EnemyBattleHUD enemyHUD; public TMP_Text dialogueText; // 战斗效果 public Image attackphoto; public Image attackphoto2; public Image image; public Animator animator; public Camera mainCamera; public Image image1; // 颜色变量 private Color Originalcolor; // 战斗开始 void Start() { state = BattleState.Start; StartCoroutine(SetupBattle()); } // 初始化战斗 IEnumerator SetupBattle() { // 实例化角色 GameObject playerGO = Instantiate(playerPrefab, playerBattleStation); playerUnit = playerGO.GetComponent<Playermessage>(); GameObject enemyGO = Instantiate(enemyPrefab, enemyBattleStation); enemyUnit = enemyGO.GetComponent<Enemymessage>(); // 设置HUD playerHUD.SetHUD(playerUnit); enemyHUD.SetHUD(enemyUnit); // 开场对话 yield return StartCoroutine(TypeText($"Let me feel your power now, don't make me bored^_^", 0.05f)); yield return new WaitForSeconds(0.5f); image1.gameObject.SetActive(false); // 省略部分对话... yield return StartCoroutine(TypeText("--Please consider your decision--", 0.05f)); yield return new WaitForSeconds(0.5f); state = BattleState.PlayerTurn; PlayerTurn(); } // 玩家回合 private void PlayerTurn() { StartCoroutine(TypeText("Choose your action...", 0f)); } // 玩家攻击 public void OnAttackButton() { if (state != BattleState.PlayerTurn) return; StartCoroutine(PlayerAttack()); } // 玩家攻击协程 private IEnumerator PlayerAttack() { yield return StartCoroutine(TypeText("Get out of my way!!!", 0f)); yield return new WaitForSeconds(1f); // 显示攻击特效 attackphoto.gameObject.SetActive(true); yield return new WaitForSeconds(0.2f); // 计算伤害 float damageDealt = playerUnit.CalculateDamage(); // 显示伤害文本 yield return StartCoroutine(TypeText($"Dealt {damageDealt:F1} damage!", 0f)); yield return new WaitForSeconds(1f); // 隐藏攻击特效 attackphoto.gameObject.SetActive(false); // 敌人承受伤害 bool isDead = enemyUnit.TakeDamage(damageDealt); enemyHUD.SetHP(enemyUnit.CurrentHealth); if (isDead) { state = BattleState.Win; StartCoroutine(EndBattle()); } else { state = BattleState.EnemyTurn; StartCoroutine(EnemyTurn()); } } // 敌人回合 private IEnumerator EnemyTurn() { yield return StartCoroutine(TypeText("Looking my eyes...", 0f)); yield return new WaitForSeconds(0.5f); // 显示攻击特效 attackphoto2.gameObject.SetActive(true); yield return new WaitForSeconds(0.2f); // 屏幕震动效果 animator.SetTrigger("shake"); StartCoroutine(ShakeCamera(0.4f, 0.4f)); // 闪光效果 Originalcolor = image.color; StartCoroutine(FlashBoom()); // 计算伤害 float damageDealt = enemyUnit.CalculateDamage(); yield return StartCoroutine(TypeText($"Took {damageDealt:F1} damage!", 0f)); // 隐藏攻击特效 attackphoto2.gameObject.SetActive(false); yield return new WaitForSeconds(0.5f); // 玩家承受伤害 bool isDead = playerUnit.TakeDamage(damageDealt); playerHUD.SetHP(playerUnit.CurrentHealth); yield return new WaitForSeconds(1f); if (isDead) { state = BattleState.Lose; StartCoroutine(EndBattle()); } else { state = BattleState.PlayerTurn; PlayerTurn(); } } // 结束战斗 private IEnumerator EndBattle() { if (state == BattleState.Win) { yield return StartCoroutine(TypeText("Victory is mine!", 0f)); // 保存胜利结果 PlayerPrefs.SetInt("LastBattleResult", 1); } else if (state == BattleState.Lose) { yield return StartCoroutine(TypeText("Defeat... I'll be back!", 0f)); // 保存失败结果 PlayerPrefs.SetInt("LastBattleResult", 0); } // 添加场景切换 SceneManager.LoadScene("Traning"); } // 文本显示效果 IEnumerator TypeText(string text, float delay) { dialogueText.text = ""; foreach (char letter in text.ToCharArray()) { dialogueText.text += letter; yield return new WaitForSeconds(delay); } } // 相机震动效果 IEnumerator ShakeCamera(float duration, float magnitude) { Vector3 originalPos = mainCamera.transform.localPosition; float elapsed = 0f; while (elapsed < duration) { float x = Random.Range(-1f, 1f) * magnitude; float y = Random.Range(-1f, 1f) * magnitude; mainCamera.transform.localPosition = new Vector3(x, y, originalPos.z); elapsed += Time.deltaTime; yield return null; } mainCamera.transform.localPosition = originalPos; } // 闪光效果 IEnumerator FlashBoom() { image.color = Color.white; yield return new WaitForSeconds(0.1f); image.color = Originalcolor; } } 我给你提供三个脚本,我不希望更改以上任何代码,来对当前状态进行一个存储功能的实现
08-08
using System.Collections; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; public enum Battle { Start, PlayerTurn, EnemyTurn, Lose, Win } public class BattleSystem : MonoBehaviour { public enum gameover{win,lose} public Battle State; public float typingSpeed = 0.05f; private bool isTyping = false; public GameObject PlayerPrefeb; public GameObject EnemyPrefeb; public Transform PlayerPosition; public Transform EnemyPosition; private Playermessage playerpeople; private Enemymessage emenypeople; public EnemyBattleHUD enemyHUD; public PlayerBattleHUD playerHUD; public TextMeshProUGUI Text; public GameObject F; public GameObject m; public GameObject back; public GameObject select1; public GameObject select2; public Color flash; private Color Originalcolor; public float Time; public UnityEngine.UI.Image image; public Animator animator; private Camera mainCamera; private Vector3 originalCameraPosition; public UnityEngine.UI.Image attackphoto; public UnityEngine.UI.Image attackphoto2; void Start() { mainCamera = Camera.main; if (mainCamera != null) { originalCameraPosition = mainCamera.transform.localPosition; } State = Battle.Start; StartCoroutine(SetupBattle()); } private IEnumerator SetupBattle() { GameObject player = Instantiate(PlayerPrefeb, PlayerPosition); playerpeople = player.GetComponent<Playermessage>(); GameObject emeny = Instantiate(EnemyPrefeb, EnemyPosition); emenypeople = emeny.GetComponent<Enemymessage>(); playerHUD.SetHUD(playerpeople); enemyHUD.SetHUD(emenypeople); // 显示开场信息(带打字效果) yield return StartCoroutine(TypeText("Let me feel your power now, don't make me bored^_^", 0.5f)); yield return new WaitForSeconds(0.5f); if (F != null) F.SetActive(false); if (m != null) m.SetActive(true); yield return StartCoroutine(TypeText("I can't lose here", 0.5f)); yield return new WaitForSeconds(0.5f); if (m != null) m.SetActive(false); yield return StartCoroutine(TypeText("--Please consider your decision--", 2f)); State = Battle.PlayerTurn; PlayerTurn(); } // 玩家回合逻辑 private void PlayerTurn() { StartCoroutine(TypeText("Choose your action...", 0f)); } public void OnAttackButton() { if (State != Battle.PlayerTurn) return; StartCoroutine(PlayerAttack()); } private IEnumerator PlayerAttack() { yield return StartCoroutine(TypeText("Get out of my way!!!", 0f)); yield return new WaitForSeconds(1f); attackphoto.gameObject.SetActive(true); // 使用新的伤害计算方法 float damageDealt = player.CalculateDamage(); yield return StartCoroutine(TypeText($"Dealt {damageDealt} damage!", 0f)); yield return new WaitForSeconds(1f); attackphoto.gameObject.SetActive(false); // 调用新的TakeDamage方法 bool IsDead = emenypeople.TakeDamage(damageDealt); enemyHUD.SetHUD(emenypeople); if (IsDead) { State = Battle.Win; EndBattle(); } else { State = Battle.EnemyTurn; StartCoroutine(EnemyTurn()); } } private IEnumerator EnemyTurn() { yield return StartCoroutine(TypeText("Looking my eyes...", 0f)); yield return new WaitForSeconds(0.5f); attackphoto2.gameObject.SetActive(true); yield return new WaitForSeconds(0.5f);animator.SetTrigger("shake"); StartCoroutine(ShakeCamera(0.4f, 0.4f)); Originalcolor = image.color; StartCoroutine(flashboom()); attackphoto2.gameObject.SetActive(false); float damageDealt = GetComponent<SpeacialAttack>().GetDamage(); yield return StartCoroutine(TypeText($"Took {damageDealt} damage!", 0f)); bool IsDead = playerpeople.TakeDamage(); playerHUD.SetHUD(playerpeople); yield return new WaitForSeconds(1f); if (IsDead) { State = Battle.Lose; EndBattle(); } else { State = Battle.PlayerTurn; PlayerTurn(); } } private void EndBattle() { if (State == Battle.Win) { StartCoroutine(TypeText("Victory is mine!", 0f)); EndBattle(true); } else if (State == Battle.Lose) { StartCoroutine(TypeText("Defeat... I'll be back!", 0f)); EndBattle(false); } select1.SetActive(false); select2.SetActive(false); back.SetActive(true); } const string BATTLE_RESULT_KEY = "LastBattleResult"; public void EndBattle(bool isWin) { PlayerPrefs.SetInt(BATTLE_RESULT_KEY, isWin ? 1 : 0); PlayerPrefs.Save(); SceneManager.LoadScene("Traning"); } IEnumerator flashboom()//受击红屏效果 { image.color = flash; yield return new WaitForSeconds(Time); image.color = Originalcolor; } private IEnumerator ShakeCamera(float duration = 0.5f, float magnitude = 0.4f) { Vector3 originalPos = mainCamera.transform.localPosition; float elapsed = 0f; AnimationCurve attenuationCurve = AnimationCurve.EaseInOut(0, 1, 1, 0); while (elapsed < duration) { float attenuation = attenuationCurve.Evaluate(elapsed / duration); float x = Mathf.PerlinNoise(UnityEngine.Time.time * 30f, 0) * 2 - 1; float y = Mathf.PerlinNoise(0, UnityEngine.Time.time * 30f) * 2 - 1; // 5. 应用衰减后的震动幅度 Vector3 shakeOffset = new Vector3(x, y, 0) * magnitude * attenuation; mainCamera.transform.localPosition = originalPos + shakeOffset; // 6. 使用unscaledDeltaTime确保时间精确 elapsed += UnityEngine.Time.unscaledDeltaTime; yield return null; } // 7. 平滑回归原位 float returnDuration = 0.2f; float returnElapsed = 0f; Vector3 currentPos = mainCamera.transform.localPosition; while (returnElapsed < returnDuration) { mainCamera.transform.localPosition = Vector3.Lerp( currentPos, originalPos, returnElapsed / returnDuration ); returnElapsed += UnityEngine.Time.unscaledDeltaTime; yield return null; } mainCamera.transform.localPosition = originalPos; } public IEnumerator TypeText(string sentence, float initialDelay = 0f) { if (Text == null) yield break; isTyping = true; yield return new WaitForSeconds(initialDelay); Text.text = ""; foreach (char letter in sentence.ToCharArray()) { Text.text += letter; yield return new WaitForSeconds(typingSpeed); } isTyping = false; } }using UnityEngine; [System.Serializable] public class Playermessage : MonoBehaviour { // 角色基本属性 [Header("角色属性")] public string characterName; public int level = 100; public float attackPower=50; public float defense; public float maxHealth; [SerializeField] private float currentHealth; // 伤害计算参数 [Header("伤害计算")] public float criticalChance = 0.5f; public float criticalMultiplier = 1.5f; public float damageRandomness = 0.1f; // 属性访问器 public float CurrentHealth => currentHealth; // 初始化 private void Start() { currentHealth = maxHealth; } // 伤害计算核心方法 public float CalculateDamage() { // 基础伤害 = 攻击力 * (1 ± 随机波动) float baseDamage = attackPower * (1 + Random.Range(-damageRandomness, damageRandomness)); // 暴击判定 if (Random.value <= criticalChance) { baseDamage *= criticalMultiplier; Debug.Log($"暴击! 伤害: {baseDamage}"); } return Mathf.Max(1, baseDamage); // 确保最小伤害为1 } // 受到伤害处理 public bool TakeDamage(float incomingDamage) { // 实际伤害 = 输入伤害 - 防御力 (至少造成1点伤害) float actualDamage = Mathf.Max(1, incomingDamage - defense); // 更新生命值 currentHealth -= actualDamage; currentHealth = Mathf.Clamp(currentHealth, 0, maxHealth); Debug.Log($"{characterName}受到{actualDamage}点伤害, 剩余生命值: {currentHealth}/{maxHealth}"); // 返回是否死亡 return currentHealth <= 0; } } using UnityEngine; [System.Serializable] public class Enemymessage : MonoBehaviour { // 角色基本属性 [Header("角色属性")] public string characterName; public int level = 1; public float attackPower=50; public float defense; public float maxHealth; [SerializeField] private float currentHealth; // 伤害计算参数 [Header("伤害计算")] public float criticalChance = 0.5f; public float criticalMultiplier = 1.5f; public float damageRandomness = 0.1f; // 属性访问器 public float CurrentHealth => currentHealth; // 初始化 private void Start() { currentHealth = maxHealth; } // 伤害计算核心方法 public float CalculateDamage() { // 基础伤害 = 攻击力 * (1 ± 随机波动) float baseDamage = attackPower * (1 + Random.Range(-damageRandomness, damageRandomness)); // 暴击判定 if (Random.value <= criticalChance) { baseDamage *= criticalMultiplier; Debug.Log($"暴击! 伤害: {baseDamage}"); } return Mathf.Max(1, baseDamage); // 确保最小伤害为1 } // 受到伤害处理 public bool TakeDamage(float incomingDamage) { // 实际伤害 = 输入伤害 - 防御力 (至少造成1点伤害) float actualDamage = Mathf.Max(1, incomingDamage - defense); // 更新生命值 currentHealth -= actualDamage; currentHealth = Mathf.Clamp(currentHealth, 0, maxHealth); Debug.Log($"{characterName}受到{actualDamage}点伤害, 剩余生命值: {currentHealth}/{maxHealth}"); // 返回是否死亡 return currentHealth <= 0; } } using UnityEngine; [System.Serializable] public class Enemymessage : MonoBehaviour { // 角色基本属性 [Header("角色属性")] public string characterName; public int level = 1; public float attackPower=50; public float defense; public float maxHealth; [SerializeField] private float currentHealth; // 伤害计算参数 [Header("伤害计算")] public float criticalChance = 0.5f; public float criticalMultiplier = 1.5f; public float damageRandomness = 0.1f; // 属性访问器 public float CurrentHealth => currentHealth; // 初始化 private void Start() { currentHealth = maxHealth; } // 伤害计算核心方法 public float CalculateDamage() { // 基础伤害 = 攻击力 * (1 ± 随机波动) float baseDamage = attackPower * (1 + Random.Range(-damageRandomness, damageRandomness)); // 暴击判定 if (Random.value <= criticalChance) { baseDamage *= criticalMultiplier; Debug.Log($"暴击! 伤害: {baseDamage}"); } return Mathf.Max(1, baseDamage); // 确保最小伤害为1 } // 受到伤害处理 public bool TakeDamage(float incomingDamage) { // 实际伤害 = 输入伤害 - 防御力 (至少造成1点伤害) float actualDamage = Mathf.Max(1, incomingDamage - defense); // 更新生命值 currentHealth -= actualDamage; currentHealth = Mathf.Clamp(currentHealth, 0, maxHealth); Debug.Log($"{characterName}受到{actualDamage}点伤害, 剩余生命值: {currentHealth}/{maxHealth}"); // 返回是否死亡 return currentHealth <= 0; } }我现在更新了后面两个脚本,帮我修改BattleSystem,写出BattleSystem全代码
07-31
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值