navigate with shortest path
This commit is contained in:
parent
de1e986577
commit
7ce78f4046
40
Node2d.cs
Normal file
40
Node2d.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
using Godot;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
public partial class Node2d : CharacterBody2D
|
||||||
|
{
|
||||||
|
public const float Speed = 300.0f;
|
||||||
|
public const float JumpVelocity = -400.0f;
|
||||||
|
|
||||||
|
public override void _PhysicsProcess(double delta)
|
||||||
|
{
|
||||||
|
Vector2 velocity = Velocity;
|
||||||
|
|
||||||
|
// Add the gravity.
|
||||||
|
if (!IsOnFloor())
|
||||||
|
{
|
||||||
|
velocity += GetGravity() * (float)delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Jump.
|
||||||
|
if (Input.IsActionJustPressed("ui_accept") && IsOnFloor())
|
||||||
|
{
|
||||||
|
velocity.Y = JumpVelocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the input direction and handle the movement/deceleration.
|
||||||
|
// As good practice, you should replace UI actions with custom gameplay actions.
|
||||||
|
Vector2 direction = Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down");
|
||||||
|
if (direction != Vector2.Zero)
|
||||||
|
{
|
||||||
|
velocity.X = direction.X * Speed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
velocity.X = Mathf.MoveToward(Velocity.X, 0, Speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
Velocity = velocity;
|
||||||
|
MoveAndSlide();
|
||||||
|
}
|
||||||
|
}
|
||||||
196
scripts/Lab.cs
196
scripts/Lab.cs
@ -1,5 +1,7 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
public partial class Lab : Node2D
|
public partial class Lab : Node2D
|
||||||
{
|
{
|
||||||
@ -9,10 +11,204 @@ public partial class Lab : Node2D
|
|||||||
var label = GetNode<Label>("TitleBar/TitleBarBodyDate/DateLabel");
|
var label = GetNode<Label>("TitleBar/TitleBarBodyDate/DateLabel");
|
||||||
label.Text = Player.Timeline.InternalDate.ToLongDateString();
|
label.Text = Player.Timeline.InternalDate.ToLongDateString();
|
||||||
Player.Timeline.OnDayChanged += d => label.Text = d.ToLongDateString();
|
Player.Timeline.OnDayChanged += d => label.Text = d.ToLongDateString();
|
||||||
|
tileMap = GetNode<TileMapLayer>("TestMap");
|
||||||
|
UpdateMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called every frame. 'delta' is the elapsed time since the previous frame.
|
// Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||||
public override void _Process(double delta)
|
public override void _Process(double delta)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static readonly Vector2I tableTileCoord = new(0, 6);
|
||||||
|
private static readonly Vector2I waterTileCoord = new(6, 9);
|
||||||
|
private static readonly Vector2I doorTileCoord = new(9, 3);
|
||||||
|
private static readonly Vector2I axeTileCoord = new(10, 9);
|
||||||
|
private static readonly Vector2I blockTileCoord = new(4, 3);
|
||||||
|
|
||||||
|
public enum MapNodeType
|
||||||
|
{
|
||||||
|
Table,
|
||||||
|
Water,
|
||||||
|
Door,
|
||||||
|
Axe,
|
||||||
|
Block,
|
||||||
|
Road,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private const int MAP_WIDTH = 24;
|
||||||
|
private const int MAP_HEIGHT = 13;
|
||||||
|
|
||||||
|
private readonly MapNodeType[,] blocks = new MapNodeType[MAP_WIDTH, MAP_HEIGHT];
|
||||||
|
|
||||||
|
|
||||||
|
private TileMapLayer tileMap;
|
||||||
|
|
||||||
|
public void UpdateMap()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < MAP_WIDTH; i++) {
|
||||||
|
for (int j = 0; j < MAP_HEIGHT; j++) {
|
||||||
|
var tile = tileMap.GetCellAtlasCoords(new Vector2I(i,j));
|
||||||
|
if (tile == tableTileCoord) {
|
||||||
|
blocks[i,j] = MapNodeType.Table;
|
||||||
|
} else if (tile == waterTileCoord) {
|
||||||
|
blocks[i,j] = MapNodeType.Water;
|
||||||
|
} else if (tile == doorTileCoord) {
|
||||||
|
blocks[i,j] = MapNodeType.Door;
|
||||||
|
} else if (tile == axeTileCoord) {
|
||||||
|
blocks[i,j] = MapNodeType.Axe;
|
||||||
|
} else if (tile == blockTileCoord) {
|
||||||
|
blocks[i,j] = MapNodeType.Block;
|
||||||
|
} else {
|
||||||
|
blocks[i,j] = MapNodeType.Road;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static bool IsValidPosition(Vector2I pos)
|
||||||
|
{
|
||||||
|
int x = pos.X;
|
||||||
|
int y = pos.Y;
|
||||||
|
if (x < 0 || x >= MAP_WIDTH || y < 0 || y >= MAP_HEIGHT) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
private List<Vector2I> GetNeighbors(Vector2I pos) {
|
||||||
|
int x = pos.X;
|
||||||
|
int y = pos.Y;
|
||||||
|
var neighbor = new List<Vector2I>();
|
||||||
|
if (!IsValidPosition(pos)) {
|
||||||
|
return neighbor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsValidPosition(new Vector2I(x-1,y)) && blocks[x-1,y] == MapNodeType.Road) {
|
||||||
|
neighbor.Add(new Vector2I(x-1,y));
|
||||||
|
}
|
||||||
|
if (IsValidPosition(new Vector2I(x+1,y)) && blocks[x+1,y] == MapNodeType.Road) {
|
||||||
|
neighbor.Add(new Vector2I(x+1,y));
|
||||||
|
}
|
||||||
|
if (IsValidPosition(new Vector2I(x,y-1)) && blocks[x,y-1] == MapNodeType.Road) {
|
||||||
|
neighbor.Add(new Vector2I(x,y-1));
|
||||||
|
}
|
||||||
|
if (IsValidPosition(new Vector2I(x,y+1)) && blocks[x,y+1] == MapNodeType.Road) {
|
||||||
|
neighbor.Add(new Vector2I(x,y+1));
|
||||||
|
}
|
||||||
|
return neighbor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Vector2I> GetShortestPath(Vector2I start, Vector2I end)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < MAP_HEIGHT; j++) {
|
||||||
|
string t = "";
|
||||||
|
for (int i = 0; i < MAP_WIDTH; i++) {
|
||||||
|
t += $"{blocks[i,j]} ";
|
||||||
|
}
|
||||||
|
GD.Print(t);
|
||||||
|
}
|
||||||
|
var path = new List<Vector2I>();
|
||||||
|
if (tileMap.GetCellSourceId(start) == -1 || tileMap.GetCellSourceId(end) == -1) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
if (start == end) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
GD.Print(start);
|
||||||
|
GD.Print(end);
|
||||||
|
static int vec2idx(Vector2I v) => v.Y * MAP_WIDTH + v.X;
|
||||||
|
static Vector2I idx2vec2(int i) => new(i % MAP_WIDTH, i / MAP_WIDTH);
|
||||||
|
|
||||||
|
HashSet<int> entriesIdx = new();
|
||||||
|
foreach (var e in GetNeighbors(end)) {
|
||||||
|
entriesIdx.Add(vec2idx(e));
|
||||||
|
}
|
||||||
|
if (entriesIdx.Count == 0) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use Dijkstra algorithm to find the shortest path
|
||||||
|
int[] dist = new int[MAP_WIDTH * MAP_HEIGHT];
|
||||||
|
int[] prev = new int[MAP_WIDTH * MAP_HEIGHT];
|
||||||
|
bool[] visited = new bool[MAP_WIDTH * MAP_HEIGHT];
|
||||||
|
|
||||||
|
for (int i = 0; i < dist.Length; i++) {
|
||||||
|
dist[i] = int.MaxValue;
|
||||||
|
prev[i] = -1;
|
||||||
|
visited[i] = false;
|
||||||
|
}
|
||||||
|
dist[vec2idx(start)] = 0;
|
||||||
|
|
||||||
|
int answer = -1;
|
||||||
|
for (int i = 0; i < dist.Length; i++) {
|
||||||
|
int min_distance = int.MaxValue;
|
||||||
|
int u = -1;
|
||||||
|
for (int j = 0; j < dist.Length; j++) {
|
||||||
|
if (!visited[j] && dist[j] <= min_distance) {
|
||||||
|
min_distance = dist[j];
|
||||||
|
u = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GD.Print($"u={u}");
|
||||||
|
if (u == -1) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
if (entriesIdx.Contains(u)){
|
||||||
|
answer = u;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
visited[u] = true;
|
||||||
|
var neighbors = GetNeighbors(idx2vec2(u));
|
||||||
|
int alt = dist[u] + 1;
|
||||||
|
GD.Print($"Alt={alt}");
|
||||||
|
foreach (var neighbor in neighbors) {
|
||||||
|
int idx = vec2idx(neighbor);
|
||||||
|
if (visited[idx]) continue;
|
||||||
|
GD.Print($"Neighbor: {neighbor}, id={idx}, dist={dist[idx]}");
|
||||||
|
if (alt < dist[idx]) {
|
||||||
|
dist[idx] = alt;
|
||||||
|
prev[idx] = u;
|
||||||
|
GD.Print($"Prev({idx}) = {u}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (answer != vec2idx(start)) {
|
||||||
|
path.Add(idx2vec2(answer));
|
||||||
|
answer = prev[answer];
|
||||||
|
}
|
||||||
|
path.Reverse();
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2I GetTypedBlock(MapNodeType nType, uint idx) {
|
||||||
|
Vector2I tileCoord;
|
||||||
|
switch(nType) {
|
||||||
|
case MapNodeType.Table:
|
||||||
|
tileCoord = tableTileCoord;
|
||||||
|
break;
|
||||||
|
case MapNodeType.Water:
|
||||||
|
tileCoord = waterTileCoord;
|
||||||
|
break;
|
||||||
|
case MapNodeType.Door:
|
||||||
|
tileCoord = doorTileCoord;
|
||||||
|
break;
|
||||||
|
case MapNodeType.Axe:
|
||||||
|
tileCoord = axeTileCoord;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return Vector2I.Zero;
|
||||||
|
}
|
||||||
|
var candidates = tileMap.GetUsedCellsById(0, tileCoord, -1);
|
||||||
|
idx %= (uint)candidates.Count;
|
||||||
|
return candidates[(int)idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2I Point2Coord(Vector2 pos) {
|
||||||
|
return tileMap.LocalToMap(tileMap.ToLocal(pos));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
94
scripts/Student.cs
Normal file
94
scripts/Student.cs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
using Godot;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public partial class Student : CharacterBody2D
|
||||||
|
{
|
||||||
|
public float Speed { get; set; } = 10.0f;
|
||||||
|
public const float JumpVelocity = -400.0f;
|
||||||
|
|
||||||
|
public int NextType = -1;
|
||||||
|
|
||||||
|
private Queue<Vector2I> PathToGo = new();
|
||||||
|
|
||||||
|
public override void _PhysicsProcess(double delta)
|
||||||
|
{
|
||||||
|
if (PathToGo.Count == 0)
|
||||||
|
{
|
||||||
|
return;// No path to follow.
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 velocity = new();
|
||||||
|
var nextPoint = PathToGo.Peek();
|
||||||
|
if ((int)GlobalPosition.X == nextPoint.X && (int)GlobalPosition.Y == nextPoint.Y) {
|
||||||
|
GD.Print(PathToGo.Dequeue());
|
||||||
|
}
|
||||||
|
nextPoint = PathToGo.Peek();
|
||||||
|
// if ((int)GlobalPosition.X == nextPoint.X) {
|
||||||
|
// // Move Y
|
||||||
|
// // velocity.Y = Math.Max(Speed, Math.Abs(nextPoint.Y - GlobalPosition.Y));
|
||||||
|
// velocity.Y = Speed;
|
||||||
|
// if (GlobalPosition.Y > nextPoint.Y) {
|
||||||
|
// velocity.Y = -velocity.Y;
|
||||||
|
// }
|
||||||
|
// } else if ((int)GlobalPosition.Y == nextPoint.Y) {
|
||||||
|
// // move X
|
||||||
|
// // velocity.X = Math.Max(Speed, Math.Abs(nextPoint.X - GlobalPosition.X));
|
||||||
|
// velocity.X = Speed;
|
||||||
|
// if (GlobalPosition.X > nextPoint.X) {
|
||||||
|
// velocity.X = -velocity.X;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
if ((int)GlobalPosition.X == nextPoint.X) {
|
||||||
|
// Move Y
|
||||||
|
// velocity.Y = Math.Max(Speed, Math.Abs(nextPoint.Y - GlobalPosition.Y));
|
||||||
|
velocity.Y = Speed;
|
||||||
|
if (GlobalPosition.Y > nextPoint.Y) {
|
||||||
|
velocity.Y = -velocity.Y;
|
||||||
|
}
|
||||||
|
} else if ((int)GlobalPosition.Y == nextPoint.Y) {
|
||||||
|
// move X
|
||||||
|
// velocity.X = Math.Max(Speed, Math.Abs(nextPoint.X - GlobalPosition.X));
|
||||||
|
velocity.X = Speed;
|
||||||
|
if (GlobalPosition.X > nextPoint.X) {
|
||||||
|
velocity.X = -velocity.X;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GlobalPosition = GlobalPosition with {
|
||||||
|
X = GlobalPosition.X + velocity.X,
|
||||||
|
Y = GlobalPosition.Y + velocity.Y
|
||||||
|
};
|
||||||
|
|
||||||
|
// Velocity = velocity;
|
||||||
|
MoveAndSlide();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
base._Ready();
|
||||||
|
var bt = GetNode<BTPlayer>("BTPlayer");
|
||||||
|
var bb = bt.Blackboard;
|
||||||
|
// bb.BindVarToProperty("Stayed", this, "Speed");
|
||||||
|
// GD.Print(bb.GetVar("Stayed"));
|
||||||
|
// GD.Print($"Speed: {Speed}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MoveFollowPath(List<Vector2I> path)
|
||||||
|
{
|
||||||
|
foreach (var p in path)
|
||||||
|
{
|
||||||
|
PathToGo.Enqueue(new Vector2I(p.X * 80 + 40, p.Y * 80 + 120));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GoTo(int nType) {
|
||||||
|
GD.Print($"Called with nType = {nType}");
|
||||||
|
var lab = GetParentOrNull<Lab>();
|
||||||
|
if (lab == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var block = lab.GetTypedBlock((Lab.MapNodeType)nType, GD.Randi());
|
||||||
|
MoveFollowPath(lab.GetShortestPath(lab.Point2Coord(GlobalPosition), block));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
|
|
||||||
public partial class TestMap : TileMapLayer
|
public partial class TestMap : TileMapLayer
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -36,4 +37,5 @@ public partial class TestMap : TileMapLayer
|
|||||||
GD.Print("Mouse is over cell: ", cell);
|
GD.Print("Mouse is over cell: ", cell);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
108
student.tscn
Normal file
108
student.tscn
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
[gd_scene load_steps=22 format=3 uid="uid://c413oatj0eqhu"]
|
||||||
|
|
||||||
|
[ext_resource type="Texture2D" uid="uid://bue73voyg0m8a" path="res://temp_res/kenney_tiny-dungeon/Tiles/tile_0099.png" id="1_aultg"]
|
||||||
|
[ext_resource type="Script" path="res://scripts/Student.cs" id="1_oesea"]
|
||||||
|
|
||||||
|
[sub_resource type="RectangleShape2D" id="RectangleShape2D_opr6h"]
|
||||||
|
size = Vector2(65, 74)
|
||||||
|
|
||||||
|
[sub_resource type="BlackboardPlan" id="BlackboardPlan_pdckj"]
|
||||||
|
var/Stayed/name = &"Stayed"
|
||||||
|
var/Stayed/type = 3
|
||||||
|
var/Stayed/value = 0.0
|
||||||
|
var/Stayed/hint = 0
|
||||||
|
var/Stayed/hint_string = ""
|
||||||
|
|
||||||
|
[sub_resource type="BBVariant" id="BBVariant_0reab"]
|
||||||
|
resource_name = "0"
|
||||||
|
saved_value = 0
|
||||||
|
type = 2
|
||||||
|
|
||||||
|
[sub_resource type="BBNode" id="BBNode_ae20x"]
|
||||||
|
resource_name = "."
|
||||||
|
saved_value = NodePath(".")
|
||||||
|
|
||||||
|
[sub_resource type="BTCallMethod" id="BTCallMethod_6osdv"]
|
||||||
|
node = SubResource("BBNode_ae20x")
|
||||||
|
method = &"GoTo"
|
||||||
|
args = Array[BBVariant]([SubResource("BBVariant_0reab")])
|
||||||
|
|
||||||
|
[sub_resource type="BBVariant" id="BBVariant_r6svu"]
|
||||||
|
resource_name = "1"
|
||||||
|
saved_value = 1
|
||||||
|
type = 2
|
||||||
|
|
||||||
|
[sub_resource type="BBNode" id="BBNode_0h6d4"]
|
||||||
|
resource_name = "."
|
||||||
|
saved_value = NodePath(".")
|
||||||
|
|
||||||
|
[sub_resource type="BTCallMethod" id="BTCallMethod_hlg51"]
|
||||||
|
node = SubResource("BBNode_0h6d4")
|
||||||
|
method = &"GoTo"
|
||||||
|
args = Array[BBVariant]([SubResource("BBVariant_r6svu")])
|
||||||
|
|
||||||
|
[sub_resource type="BBVariant" id="BBVariant_0kduy"]
|
||||||
|
resource_name = "2"
|
||||||
|
saved_value = 2
|
||||||
|
type = 2
|
||||||
|
|
||||||
|
[sub_resource type="BBNode" id="BBNode_3rnl4"]
|
||||||
|
resource_name = "."
|
||||||
|
saved_value = NodePath(".")
|
||||||
|
|
||||||
|
[sub_resource type="BTCallMethod" id="BTCallMethod_s6v6a"]
|
||||||
|
node = SubResource("BBNode_3rnl4")
|
||||||
|
method = &"GoTo"
|
||||||
|
args = Array[BBVariant]([SubResource("BBVariant_0kduy")])
|
||||||
|
|
||||||
|
[sub_resource type="BBVariant" id="BBVariant_ofgnx"]
|
||||||
|
resource_name = "3"
|
||||||
|
saved_value = 3
|
||||||
|
type = 2
|
||||||
|
|
||||||
|
[sub_resource type="BBNode" id="BBNode_sc5bo"]
|
||||||
|
resource_name = "."
|
||||||
|
saved_value = NodePath(".")
|
||||||
|
|
||||||
|
[sub_resource type="BTCallMethod" id="BTCallMethod_861fo"]
|
||||||
|
node = SubResource("BBNode_sc5bo")
|
||||||
|
method = &"GoTo"
|
||||||
|
args = Array[BBVariant]([SubResource("BBVariant_ofgnx")])
|
||||||
|
|
||||||
|
[sub_resource type="BTProbabilitySelector" id="BTProbabilitySelector_syngh"]
|
||||||
|
children = [SubResource("BTCallMethod_6osdv"), SubResource("BTCallMethod_hlg51"), SubResource("BTCallMethod_s6v6a"), SubResource("BTCallMethod_861fo")]
|
||||||
|
|
||||||
|
[sub_resource type="BTCooldown" id="BTCooldown_l6f38"]
|
||||||
|
children = [SubResource("BTProbabilitySelector_syngh")]
|
||||||
|
duration = 20.0
|
||||||
|
trigger_on_failure = true
|
||||||
|
cooldown_state_var = &"Stayed"
|
||||||
|
|
||||||
|
[sub_resource type="BTSequence" id="BTSequence_4yux8"]
|
||||||
|
children = [SubResource("BTCooldown_l6f38")]
|
||||||
|
|
||||||
|
[sub_resource type="BehaviorTree" id="BehaviorTree_rj16b"]
|
||||||
|
blackboard_plan = SubResource("BlackboardPlan_pdckj")
|
||||||
|
root_task = SubResource("BTSequence_4yux8")
|
||||||
|
|
||||||
|
[sub_resource type="BlackboardPlan" id="BlackboardPlan_gs1pu"]
|
||||||
|
var/Stayed/name = &"Stayed"
|
||||||
|
var/Stayed/type = 3
|
||||||
|
var/Stayed/value = -100.0
|
||||||
|
var/Stayed/hint = 0
|
||||||
|
var/Stayed/hint_string = ""
|
||||||
|
|
||||||
|
[node name="Student" type="CharacterBody2D"]
|
||||||
|
script = ExtResource("1_oesea")
|
||||||
|
|
||||||
|
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||||
|
scale = Vector2(5, 5)
|
||||||
|
texture = ExtResource("1_aultg")
|
||||||
|
|
||||||
|
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||||
|
position = Vector2(0.5, 3)
|
||||||
|
shape = SubResource("RectangleShape2D_opr6h")
|
||||||
|
|
||||||
|
[node name="BTPlayer" type="BTPlayer" parent="."]
|
||||||
|
behavior_tree = SubResource("BehaviorTree_rj16b")
|
||||||
|
blackboard_plan = SubResource("BlackboardPlan_gs1pu")
|
||||||
Loading…
Reference in New Issue
Block a user