【Behavior Tree】-- 行為樹AI邏輯實現- Unity 游戲引擎實現

行為樹簡易敵人AI

前言:

有些天沒更新新文章了,主要是最近科一有些頭疼,而且最近琢磨這個行為樹代碼有些難受,但是終于熬出頭了,MonoGame的系列會繼續更新的,今天不說別的就說困擾我兩三天的行為樹

有限狀態機 -》分層狀態機 -》 行為樹:

首先我們得先理解一個概念:有限狀態機,在游戲開發中因為團隊協作開發,和編程效率的緣故,人們在游戲編程模式中發明了一種編程模式:有限狀態機,他的邏輯簡單而且編寫起來也很優雅,我非常喜歡這種游戲編程模式,在我早期接觸游戲開發的時候百分之九十九的項目都是使用有限狀態機來編寫玩家操控和怪物AI,但是很快就會遇到難題,普通小怪倒還好,簡單的行為模式時的有限狀態機來編寫邏輯清晰沒什么缺點,但是一旦到了Boss級別敵人邏輯復雜,技能繁多,如果用有限狀態機來處理的話機會面臨以下問題:狀態切換太過復雜,狀態切換條件繁多,從邏輯上來說這個有限狀態機變不再適合開發這種技能繁多的BossAI了,聰明的你想到:欸!要是把意思相同的邏輯封裝成一個集合,比如:移動,站立歸為Move層,魔法攻擊,普通攻擊,等等攻擊歸為戰斗層,跳躍,下落歸為跳躍層等等,這樣我們的邏輯就清晰了很多了,把所有狀態設為二級節點,但是這樣依舊不是最佳選擇,那么我們本篇文章的:行為樹

行為樹:

1.行為樹是為了簡化游戲的邏輯,在很多游戲開發過程中人們都會選擇行為樹來開發這款游戲的AI,甚至可以說一款游戲的大多數時間和代碼都是來源游戲AI,行為樹都是由一個個節點組成的主要包括:

  • 葉子節點(Node)
  • 順序節點(SequenceNode)
  • 選擇節點 (SelectorNode)
  • 裝飾節點 (DecoratorNode)

以上這些節點都是在游戲開發中常用的節點,當然不妨還有一些擴展節點但最為常用的還是這些節點的狀態,每個節點都由三種狀態:

  • Success
  • Failure
  • Running

葉子節點(Node)
所謂葉子節點也是樹的最底部,也就是他沒有子節點,葉子節點沒有子節點,這也就意味著葉子節點必須得執行游戲中Boss/Monster的具體邏輯,因為他沒有子節點無法再繼續往下遍歷了,所以我們必須得使用葉子節點來實現Boss的具體功能;

順序節點(SequenceNode)
順序節點是一個父級節點,他會一次從左向右遍歷所有的子樹,一旦遍歷到返回失敗節點返回失敗,就意味著這個節點失敗了。

選擇節點 (SelectorNode)
這種節點和順序節點一樣是一種父級節點,但是不同的是這個節點會選擇,從左往右數的子樹中第一個返回成功或者運行的節點。

裝飾節點 (DecoratorNode)
這種節點通常都是再Sequence節點下的前置節點,通常用來判斷條件,一條條件不滿足直接返回失敗,那么相應的Sequence節點也會返回失敗;

代碼部分:

首先我們得先完成一個示例的簡單AI邏輯來實踐一下,這個AI邏輯代碼很簡單就是一個Boss在Idle, Walk, Attack三個形態之間的切換,會了這個就相當與只要你畫出行為樹的邏輯圖,那么搞定這個也就簡單起來了:
演示
在這里插入圖片描述

首先我們得先寫一下基礎的節點代碼:
BTNode

using System.Collections.Generic;namespace ETFramework
{public class BTNode{protected NodeState state;public BTNode parent;public List<BTNode> children = new List<BTNode>();public BTNode(){parent = null;}public BTNode(List<BTNode> children){foreach (BTNode child in children)AddNode(child);}private void AddNode(BTNode node){node.parent = this;children.Add(node);}public virtual NodeState Evaluate() => NodeState.Failure;}
}

SelectorNode

using System.Collections;
using System.Collections.Generic;namespace ETFramework
{public class SelectorNode : BTNode{public SelectorNode() : base() {}public SelectorNode(List<BTNode> children) : base(children) {}public override NodeState Evaluate(){foreach (BTNode node in children){switch (node.Evaluate()){case NodeState.Failure:continue;case NodeState.Success:state = NodeState.Success;return state;case NodeState.Running:state = NodeState.Running;return state;default:continue;}}state = NodeState.Failure;return state;}}
}

SequenceNode

using System.Collections.Generic;namespace ETFramework
{public class SequenceNode : BTNode{public SequenceNode() : base() {}public SequenceNode(List<BTNode> children) : base(children) {}public override NodeState Evaluate(){bool AnyChildIsRunning = false;foreach (BTNode node in children){switch (node.Evaluate()){case NodeState.Failure:state = NodeState.Failure;return state;case NodeState.Success:continue;case NodeState.Running:AnyChildIsRunning = true;continue;default:state = NodeState.Success;return state;}}state = AnyChildIsRunning ? NodeState.Running : NodeState.Success;return state;}}
}

BTree

using UnityEngine;namespace ETFramework
{[RequireComponent(typeof(Rigidbody2D))][RequireComponent(typeof(Animator))][RequireComponent(typeof(SpriteRenderer))]public abstract class BTree : MonoBehaviour{/// <summary>/// 這個實例的名字/// </summary>public string InstanceName;/// <summary>/// 實例類型/// </summary>public InstanceType TypeIns;/// <summary>/// 渲染組件/// </summary>public SpriteRenderer Render;/// <summary>/// 動畫組件/// </summary>public Animator animator;/// <summary>/// 剛體組件/// </summary>public Rigidbody2D Rb;private BTNode Root = null;protected virtual void Awake(){/*初始化添加組件*/animator = GetComponent<Animator>();Render = GetComponent<SpriteRenderer>();Rb = GetComponent<Rigidbody2D>();}protected void Start(){Root = SetupTree();}private void Update(){if (Root != null)Root.Evaluate();}protected abstract BTNode SetupTree();}
}

接下來寫具體的游戲AI邏輯代碼,就是Boss具體的行動和行為:
包括Idle,Walk, Attack,那么把這棵行為樹畫出來就搞定了
在這里插入圖片描述
Code :

using ETFramework;
using UnityEngine;public class DarkBossIdle : BTNode
{private Rigidbody2D Rb;private Animator animator;private InstanceCheck instanceCheck;public DarkBossIdle(Rigidbody2D Rb, Animator animator, InstanceCheck instanceCheck){this.Rb = Rb;this.animator = animator;this.instanceCheck = instanceCheck;}public override NodeState Evaluate(){if (!instanceCheck.isEnter){state = NodeState.Running;Rb.velocity = Vector2.zero;animator.SetBool("DarkWalk", false);}else{state = NodeState.Failure;}return state;}
}
using ETFramework;
using UnityEngine;public class DarkBossWalk : BTNode
{private SpriteRenderer Sr;private Animator animator;private Rigidbody2D Rb;private InstanceCheck instanceCheck;private Transform transform;private float MoveSpeed;public DarkBossWalk(SpriteRenderer Sr, Rigidbody2D Rb, Animator animator, InstanceCheck instanceCheck, Transform transform, float MoveSpeed){this.Sr = Sr;this.Rb = Rb;this.animator = animator;this.instanceCheck = instanceCheck;this.transform = transform;this.MoveSpeed = MoveSpeed;}public override NodeState Evaluate(){Collider2D collider = instanceCheck.Collider;if (collider == null){state = NodeState.Failure;animator.SetBool("DarkWalk", false);return state;}animator.SetBool("DarkWalk", true);if (collider.transform.position.x > transform.position.x){Rb.velocity = new Vector2(MoveSpeed, 0);Sr.flipX = true;transform.GetComponent<AttackCheck>().Offset.x = 5;transform.GetComponent<SearchInstanceCheck>().Offset.x = 5;}if (collider.transform.position.x < transform.position.x){Rb.velocity = new Vector2(-MoveSpeed, 0);Sr.flipX = false;transform.GetComponent<AttackCheck>().Offset.x = -5;transform.GetComponent<SearchInstanceCheck>().Offset.x = -5;}state = NodeState.Running;return state;}
}
using ETFramework;
using UnityEngine;public class DarkBossAttack : BTNode
{private AttackCheck attackCheck;private SearchInstanceCheck searchInstanceCheck;private Transform transform;private Animator animator;private Rigidbody2D Rb;private int temp = 0;public DarkBossAttack(Animator animator, AttackCheck attackCheck, Transform transform, Rigidbody2D Rb, SearchInstanceCheck searchInstanceCheck){this.animator = animator;this.attackCheck = attackCheck;this.transform = transform;this.Rb = Rb;this.searchInstanceCheck = searchInstanceCheck;}public override NodeState Evaluate(){AnimatorStateInfo info = animator.GetCurrentAnimatorStateInfo(0);if (attackCheck.isEnter && temp == 0){temp++;}if (info.normalizedTime <= 0.9f && info.IsName("DarkBossAttack")){state = NodeState.Running;return state;}if (searchInstanceCheck.isEnter){state = NodeState.Running;animator.SetBool("DarkAttack", true);return state;}state = NodeState.Failure;animator.SetBool("DarkAttack", false);return state;}
}
using System.Collections.Generic;
using ETFramework;
using UnityEngine;public class DarkBoss : BTree
{[SerializeField][Tooltip("攻擊檢測")] private AttackCheck attackCheck;[SerializeField][Tooltip("實例搜索")] private InstanceCheck instanceCheck;[SerializeField][Tooltip("檢測實例")] private SearchInstanceCheck searchInstanceCheck;[SerializeField][Tooltip("面向方向")] private float MoveSpeed;protected override BTNode SetupTree(){BTNode root = new SelectorNode(new List<BTNode>{new SelectorNode(new List<BTNode>{new DarkBossAttack(animator, attackCheck, transform, Rb, searchInstanceCheck),new DarkBossWalk(Render, Rb, animator, instanceCheck, transform, MoveSpeed)}),new DarkBossIdle(Rb, animator, instanceCheck)});return root;}public void AttackEnter(){attackCheck.IsStart = true;}public void AttackExit(){attackCheck.IsStart = false;}
}

結語:

這個是我困擾兩天的代碼問題,我最近在開發一個能快速成型游戲的Unity框架,這個框架我打算免費發行,我計劃的是集有限狀態機,UI模式,單例模式,行為樹,代碼模板,場景切換組件合為一體的只要給出美術資源能快速幫助我構建出一個游戲的模板框架,為什么突發奇想想開發一個這個,因為我要備戰明年的Game Jam我得趕緊疊疊我的技術棧,和發展一下我的弱項:3D游戲開發!不然到時候沒人要嚶嚶嚶!還有就是騰訊的游戲開發大賽我也想參與大家一起加油!!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/87975.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/87975.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/87975.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

百度大模型開源,倆條命令、本地啟動

百度大模型開源 本地啟動手冊 安裝依賴&#xff1a; python -m pip install paddlepaddle-gpu3.1.0 -i https://www.paddlepaddle.org.cn/packages/stable/cu126/python -m pip install fastdeploy-gpu -i https://www.paddlepaddle.org.cn/packages/stable/fastdeploy-gpu-80_…

rabbitMQ讀取不到ThreadLocal消息的bug

rabbitMQ讀取不到ThreadLocal消息的bug 當使用消息隊列時&#xff0c;監聽隊列不會運行到主線程上&#xff0c;線程消息之間是不會共享的&#xff0c;故屬于主線程的ThreadLocal就讀取不到數據的值 主線程名字&#xff1a;main使用消息隊列的線程名字&#xff1a;ntContainer#2…

IDEA Maven報錯 無法解析 com.taobao:parent:pom:1.0.1【100%解決 此類型問題】

IDEA Maven報錯 無法解析com.taobao:parent:pom:1.0.1【100%解決 此類型問題】 報錯日志 PS D:\Learn_Materials\IDEA_WorkSpace\Demo\spring_test_demo> mvn clean install -U [INFO] Scanning for projects... [WARNING] [WARNING] Some problems were encountered whi…

函數-1-字符串函數

函數-1-字符串函數字符串函數函數語法字符串函數的使用字符串函數語法案例演示實戰練習字符串函數 函數 函數是一段可以直接被另一端程序調用的程序或代碼 語法 SELECT 函數名(參數名)大家可能會有那么一點點疑惑, 為什么執行函數還需要加上SELECT語句? 總結一下, 因為SEL…

打破AI落地困局:易路iBuilder的“垂直深耕+開箱即用”破壁之道

中國企業的數字化轉型已步入深水區&#xff0c;人力資源管理作為企業核心競爭力的關鍵引擎&#xff0c;正經歷從“信息化”向“智能化”的范式躍遷。在這場以AI為驅動的組織效能革命中&#xff0c;??易路人力資源科技??憑借前瞻性的“軟件AI服務”戰略&#xff0c;推出國內…

Higress離線部署

1.前提條件檢查docker和docker compose是否已經具備 [roothost151 ~]# docker -v Docker version 26.1.4, build 5650f9b [roothost151 ~]# docker composeUsage: docker compose [OPTIONS] COMMANDDefine and run multi-container applications with DockerOptions:--all-res…

利用AI技術快速提升圖片編輯效率的方法

通過更換背景或進行其他創意編輯&#xff0c;可以為圖片賦予新的生命力和視覺效果&#xff0c;使得創意表達更加自由靈活。這款AI摳圖工具堪稱強大&#xff0c;依托先進的阿爾法通道技術&#xff0c;能夠精準、自然地實現圖像摳取與背景更換。操作也非常簡單&#xff0c;只需將…

Wend看源碼-RAGFlow(上)

前言 最近在github上搜羅Rag相關項目的時候&#xff0c;我根據star 搜索到了目前star 最高的一些RAG 項目 &#xff0c;其中穩居榜首的就是RAGFlow。 RAG stars:>1000 language:Python pushed:>2025-01-01 github RAG 相關項目搜索結果 為了系統性的學習RAG 技術棧&#…

LangChain實現RAG檢索增強

1:啟動vllm的openai兼容server&#xff1a; export VLLM_USE_MODELSCOPETrue python -m vllm.entrypoints.openai.api_server --model qwen/Qwen-7B-Chat-Int4 --trust-remote-code -q gptq --dtype float16 --gpu-memory-utilization 0.6 2:構建向量數據庫 from langchain_…

Redis基礎(6):SpringDataRedis

SpringDataRedis簡介 SpringData是Spring中專門進行數據操作的模塊&#xff0c;包含了對于各種數據庫的集成。其中對Redis的集成模塊叫做SpringDataRedis&#xff08;官網地址&#xff1a;Spring Data Redis&#xff09;。其最核心的特點就是提供了不同Redis客戶端的整合&…

B. Shrinking Array/縮小數組

B. Shrinking Array讓我們稱一個數組 b 為 i 美麗 &#xff0c;如果它至少包含兩個元素&#xff0c;并且存在一個位置 |bi?bi1|≤1 使得 |x| (其中 x 是 #10# #11# 的絕對值)。給定一個數組 a &#xff0c;只要它至少包含兩個元素&#xff0c;你就可以執行以下操作&#xff1a…

【學習筆記】Linux系統中SSH服務安全配置

一、背景知識 以ubuntu為例&#xff0c;查看ssh服務是否安全并配置&#xff0c;執行 ssh -V ssh的配置文件路徑&#xff1a;/etc/ssh/sshd_config 二、SSH服務配置文件 1.端口和監聽設置 Port 22 含義&#xff1a;指定SSH服務監聽的端口號&#xff08;默認是22&#xff09…

FastAPI + Tortoise-ORM + Aerich 實現數據庫遷移管理(MySQL 實踐)

在 FastAPI 項目中&#xff0c;Tortoise-ORM 是一個輕量的異步 ORM 框架&#xff0c;適用于 async/await 場景。結合數據庫遷移工具 Aerich&#xff0c;可以優雅地管理數據庫表結構演進&#xff0c;本文將通過完整流程演示如何在 MySQL 環境下使用。&#x1f4e6; 一、環境準備…

7.7日 實驗03-Spark批處理開發(2)

使用Spark處理數據文件檢查數據檢查$DATA_EXERCISE/activations里的數據&#xff0c;每個XML文件包含了客戶在指定月份活躍的設備數據。拷貝數據到HDFS的/dw目錄樣本數據示例&#xff1a;<activations><activation timestamp"1225499258" type"phone&q…

C語言可變參數感悟

#include <stdio.h> #include <stdarg.h> #if 1 /* *在C語言中&#xff0c;可變參函數是指參數數量不固定的函數&#xff0c;比如printf\scanf *可變參函數的語法&#xff1a; *返回類型 函數名&#xff08;固定函數&#xff0c;.....) { //函數體 } *1、包含頭文件…

LeetCode 1248.統計優美子數組

給你一個整數數組 nums 和一個整數 k。如果某個連續子數組中恰好有 k 個奇數數字&#xff0c;我們就認為這個子數組是「優美子數組」。 請返回這個數組中 「優美子數組」 的數目。 示例 1&#xff1a; 輸入&#xff1a;nums [1,1,2,1,1], k 3 輸出&#xff1a;2 解釋&#xf…

FastAPI Docker環境管理腳本使用指南

作者: 源滾滾AI編程 創建時間: 2025年07月08日 版本: v1.0.0 文檔狀態: 完成 版權聲明 本文檔由源滾滾AI編程創作,版權所有。未經作者書面許可,不得復制、分發或用于商業用途。 免責聲明 本文檔僅用于技術交流和學習目的。作者不對使用本文檔內容導致的任何問題承擔責任。…

前端常見 HTTP 狀態碼

作為前端開發者&#xff0c;與后端 API 交互時&#xff0c;HTTP 狀態碼是判斷請求成敗的關鍵信號。理解常見狀態碼的含義、責任歸屬及應對策略&#xff0c;能極大提升調試效率和團隊協作。以下是關鍵狀態碼的詳細解析&#xff1a; 首先說一下如何查看狀態碼&#xff1a; 如上圖…

深度解析C語言內存函數(小米面試題)

目錄 一、memcpy1.1 代碼演示1.2 memcpy的模擬實現 二、memmove2.1 代碼演示2.2 模擬實現&#xff08;小米面試題&#xff09; 三、memset3.1 代碼演示3.2 總結 四、memcmp4.1 代碼演示4.2 總結 總結 一、memcpy &#xff08;memory copy 內存復制&#xff09; 之前文章中寫的…

DK124反激式開關電源芯片

18W 高性能交直流轉換芯片 特性 DK124 是一款離線式開關電源芯片&#xff0c;最大輸出功率達到 24W。內部集成了 PWM 控制器、700V 功率管和初級峰值電流檢測電路&#xff0c;并采用了可以省略輔助供電繞組的專利自供電技術&#xff0c;極大簡化了外圍應用電路&#xff0c;減…