行为树简易敌人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;减…