Some effort for path smoothing.

WorldChunkRefactoring
Martin Felis 2023-07-09 22:17:55 +02:00
parent 1397c0a7eb
commit 42d7658d03
10 changed files with 191 additions and 113 deletions

View File

@ -83,22 +83,15 @@ public class NavigationComponent : Spatial
private Vector3 _currentGoalPositionWorld = Vector3.Zero;
private Quat _currentGoalOrientationWorld = Quat.Identity;
private Area _pathCollisionQueryVolume;
private CollisionShape _pathCollisionQueryShape;
private List<NavigationPoint> _pathWorldNavigationPoints;
private List<NavigationPoint> _pathWorldNavigationPoints = new List<NavigationPoint>();
private List<NavigationPoint> _smoothedPathWorldNavigationPoints = new List<NavigationPoint>();
private HexCell[] _path;
public override void _Ready()
{
base._Ready();
_pathWorldNavigationPoints = new List<NavigationPoint>();
_pathCollisionQueryVolume = (Area)FindNode("PathCollisionQueryVolume");
Debug.Assert(_pathCollisionQueryVolume != null);
_pathCollisionQueryShape = (CollisionShape)_pathCollisionQueryVolume.FindNode("CollisionShape");
Debug.Assert(_pathCollisionQueryShape != null);
}
public override void _Process(float delta)
@ -107,7 +100,7 @@ public class NavigationComponent : Spatial
}
public void PlanGridPath(Vector3 fromPositionWorld, Vector3 toPositionWorld)
public void PlanGridPath(KinematicBody body, Vector3 fromPositionWorld, Vector3 toPositionWorld)
{
Vector2 fromPositionOffset = TileWorld.WorldToOffsetCoords(fromPositionWorld);
Vector2 toPositionOffset = TileWorld.WorldToOffsetCoords(toPositionWorld);
@ -122,6 +115,9 @@ public class NavigationComponent : Spatial
Debug.Assert(_path.Length > 0);
_pathWorldNavigationPoints = new List<NavigationPoint>();
_pathWorldNavigationPoints.Add(
new NavigationPoint(TileWorld.GetHexCenterFromOffset(fromPositionOffset)));
foreach (int index in Enumerable.Range(1, _path.Length - 1))
{
_pathWorldNavigationPoints.Add(
@ -139,40 +135,43 @@ public class NavigationComponent : Spatial
}
_pathWorldNavigationPoints.Add(new NavigationPoint(toPositionWorld));
if (_pathWorldNavigationPoints.Count > 2)
{
_smoothedPathWorldNavigationPoints = SmoothPath(body, _pathWorldNavigationPoints);
_pathWorldNavigationPoints = _smoothedPathWorldNavigationPoints;
}
UpdateCurrentGoal();
}
public void PlanGridPath(Vector3 fromPositionWorld, Vector3 toPositionWorld, Quat toWorldOrientation)
public void PlanGridPath(KinematicBody body, Vector3 fromPositionWorld, Vector3 toPositionWorld, Quat toWorldOrientation)
{
PlanGridPath(fromPositionWorld, toPositionWorld);
PlanGridPath(body, fromPositionWorld, toPositionWorld);
_pathWorldNavigationPoints.Add(new NavigationPoint(toWorldOrientation));
}
public void PlanGridPath(Transform fromTransformWorld, NavigationPoint navigationPoint)
public void PlanGridPath(KinematicBody body, Transform fromTransformWorld, NavigationPoint navigationPoint)
{
if (navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Position)
&& navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Orientation))
{
PlanGridPath(fromTransformWorld.origin, navigationPoint.WorldPosition, navigationPoint.WorldOrientation);
PlanGridPath(body, fromTransformWorld.origin, navigationPoint.WorldPosition, navigationPoint.WorldOrientation);
}
else if (navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Position))
{
PlanGridPath(fromTransformWorld.origin, navigationPoint.WorldPosition);
PlanGridPath(body, fromTransformWorld.origin, navigationPoint.WorldPosition);
}
else
{
throw new NotImplementedException();
}
CheckPathCollision(fromTransformWorld.origin, navigationPoint.WorldPosition);
}
public void PlanDirectPath(Vector3 fromPositionWorld, Vector3 toPositionWorld)
public void PlanDirectPath(KinematicBody body, Vector3 fromPositionWorld, Vector3 toPositionWorld)
{
_pathWorldNavigationPoints.Clear();
_pathWorldNavigationPoints.Add(new NavigationPoint(toPositionWorld));
@ -181,82 +180,75 @@ public class NavigationComponent : Spatial
}
public void PlanDirectPath(Vector3 fromPositionWorld, Vector3 toPositionWorld, Quat toWorldOrientation)
public void PlanDirectPath(KinematicBody body, Vector3 fromPositionWorld, Vector3 toPositionWorld, Quat toWorldOrientation)
{
PlanDirectPath(fromPositionWorld, toPositionWorld);
PlanDirectPath(body, fromPositionWorld, toPositionWorld);
_pathWorldNavigationPoints.Add(new NavigationPoint(toWorldOrientation));
}
bool CheckPathCollision(Vector3 fromPositionWorld, Vector3 toPositionWorld)
public bool HasPathCollision(KinematicBody body, Vector3 fromPositionWorld, Vector3 toPositionWorld)
{
Vector3 fromPositionLocal = GlobalTransform.XformInv(fromPositionWorld);
Vector3 toPositionLocal = GlobalTransform.XformInv(toPositionWorld);
float distance = (toPositionLocal - fromPositionLocal).Length();
Vector3 direction = (toPositionLocal - fromPositionLocal) / distance;
Vector3 side = Vector3.Up.Cross(direction);
Basis orientation = new Basis(side, Vector3.Up, direction);
_pathCollisionQueryVolume.Transform =
new Transform(orientation, 0.5f * toPositionLocal + 0.5f * fromPositionLocal).Scaled(new Vector3(0.5f, 0.5f, distance));
var collisionBodies = _pathCollisionQueryVolume.GetOverlappingBodies();
if (collisionBodies.Count > 0)
KinematicCollision moveCollision = body.MoveAndCollide(toPositionLocal + Vector3.Up * 0.5f, true, true, true);
if (moveCollision != null)
{
GD.Print("There is a body: " + collisionBodies[0]);
GD.Print("Found collision: " + moveCollision.Collider);
return true;
}
if ((fromPositionWorld - toPositionWorld).LengthSquared() < 0.001)
{
return false;
}
// TODO: Complete Implementation
Debug.Assert(false);
return true;
return false;
}
public List<NavigationPoint> SmoothPath(List<NavigationPoint> navigationPoints)
public List<NavigationPoint> SmoothPath(KinematicBody body, List<NavigationPoint> navigationPoints)
{
Debug.Assert(navigationPoints.Count > 2);
List<NavigationPoint> smoothedPath = new List<NavigationPoint>();
int startIndex = 0;
int endIndex = navigationPoints.Count > 1 ? 1 : 0;
smoothedPath.Add(navigationPoints[startIndex]);
int endIndex = navigationPoints.Count > 1 ? 1 : 0;
while (endIndex != navigationPoints.Count - 1)
{
Vector3 startPoint = navigationPoints[startIndex].WorldPosition;
Vector3 endPoint = navigationPoints[endIndex].WorldPosition;
if (CheckPathCollision(startPoint, endPoint))
if (HasPathCollision(body, startPoint, endPoint))
{
smoothedPath.Add(navigationPoints[endIndex-1]);
smoothedPath.Add(navigationPoints[endIndex]);
startIndex = endIndex;
endIndex += 1;
if (endIndex - startIndex == 1)
{
smoothedPath.Add(navigationPoints[endIndex]);
startIndex = endIndex;
}
else
{
smoothedPath.Add(navigationPoints[endIndex - 1]);
startIndex = endIndex - 1;
continue;
}
}
endIndex += 1;
}
smoothedPath.Add(navigationPoints[endIndex]);
return smoothedPath;
}
public void PlanDirectPath(Transform fromTransformWorld, NavigationPoint navigationPoint)
public void PlanDirectPath(KinematicBody body, Transform fromTransformWorld, NavigationPoint navigationPoint)
{
if (navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Position)
&& navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Orientation))
{
PlanDirectPath(fromTransformWorld.origin, navigationPoint.WorldPosition, navigationPoint.WorldOrientation);
PlanDirectPath(body, fromTransformWorld.origin, navigationPoint.WorldPosition, navigationPoint.WorldOrientation);
}
else if (navigationPoint.Flags.HasFlag(NavigationPoint.NavigationFlags.Position))
{
PlanDirectPath(fromTransformWorld.origin, navigationPoint.WorldPosition);
PlanDirectPath(body, fromTransformWorld.origin, navigationPoint.WorldPosition);
}
else
{
@ -362,6 +354,16 @@ public class NavigationComponent : Spatial
previousPoint = point.WorldPosition;
}
previousPoint = parentNode.GlobalTranslation;
foreach (NavigationPoint point in _smoothedPathWorldNavigationPoints)
{
debugGeometry.SetColor(Colors.Green);
debugGeometry.AddVertex(previousPoint + yOffset);
debugGeometry.AddVertex(point.WorldPosition + yOffset);
previousPoint = point.WorldPosition;
}
debugGeometry.End();
}
}

View File

@ -12,8 +12,7 @@ public class Player : Entity, IInteractionInterface
// public members
[Export] public NodePath TileWorldNode;
public Vector3 TargetPosition = Vector3.Zero;
public int goldCount = 0;
public int GoldCount = 0;
public TaskQueueComponent TaskQueueComponent;
@ -109,14 +108,14 @@ public class Player : Entity, IInteractionInterface
TaskQueueComponent.NavigationTask navigationTask = currentTask as TaskQueueComponent.NavigationTask;
if (navigationTask != null && navigationTask.PlanningComplete == false)
{
_navigationComponent.PlanGridPath(GlobalTransform, navigationTask.NavigationPoint);
_navigationComponent.PlanGridPath(this, GlobalTransform, navigationTask.NavigationPoint);
navigationTask.PlanningComplete = true;
}
_navigationComponent.UpdateCurrentGoal(GlobalTransform);
_groundMotion.PhysicsProcess(delta, this, _navigationComponent.CurrentGoalPositionWorld, _navigationComponent.CurrentGoalOrientationWorld, _worldInfo.TileWorld);
}
}
_navigationComponent.UpdateCurrentGoal(GlobalTransform);
_groundMotion.PhysicsProcess(delta, this, _navigationComponent.CurrentGoalPositionWorld, _navigationComponent.CurrentGoalOrientationWorld, _worldInfo.TileWorld);
}
@ -186,8 +185,8 @@ public class Player : Entity, IInteractionInterface
if (body is GoldBar)
{
goldCount++;
EmitSignal("GoldCountChanged", goldCount);
GoldCount++;
EmitSignal("GoldCountChanged", GoldCount);
}
body.QueueFree();

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=13 format=2]
[gd_scene load_steps=10 format=2]
[ext_resource path="res://entities/Player.cs" type="Script" id=1]
[ext_resource path="res://components/NavigationComponent.cs" type="Script" id=2]
@ -8,16 +8,8 @@
[ext_resource path="res://components/MovableComponent.tscn" type="PackedScene" id=7]
[sub_resource type="CapsuleShape" id=7]
radius = 0.3
height = 0.2
[sub_resource type="BoxShape" id=26]
[sub_resource type="CubeMesh" id=27]
[sub_resource type="SpatialMaterial" id=28]
flags_transparent = true
albedo_color = Color( 1, 1, 1, 0.0470588 )
radius = 0.27
height = 0.4
[sub_resource type="CylinderShape" id=24]
height = 0.2
@ -26,7 +18,6 @@ height = 0.2
radius = 0.1
[node name="Player" type="KinematicBody"]
collision_layer = 0
collision_mask = 0
axis_lock_motion_y = true
move_lock_y = true
@ -45,17 +36,6 @@ World = NodePath("../../TileWorld")
[node name="Navigation" type="Spatial" parent="."]
script = ExtResource( 2 )
[node name="PathCollisionQueryVolume" type="Area" parent="Navigation"]
transform = Transform( 0.5, 0, 0, 0, 0.5, 0, 0, 0, 2, 0, 1, 2 )
collision_layer = 0
[node name="CollisionShape" type="CollisionShape" parent="Navigation/PathCollisionQueryVolume"]
shape = SubResource( 26 )
[node name="MeshInstance" type="MeshInstance" parent="Navigation/PathCollisionQueryVolume"]
mesh = SubResource( 27 )
material/0 = SubResource( 28 )
[node name="ItemAttractorArea" type="Area" parent="."]
collision_layer = 0
collision_mask = 8

9
scenes/Camera.tscn Normal file
View File

@ -0,0 +1,9 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://scenes/DebugCamera.gd" type="Script" id=1]
[node name="Camera" type="Camera"]
transform = Transform( 1, 0, 0, 0, 0.60042, 0.799685, 0, -0.799685, 0.60042, -4.76837e-07, 6.37557, 4.57224 )
current = true
fov = 60.0
script = ExtResource( 1 )

View File

@ -293,7 +293,7 @@ public class Game : Spatial
playerStartTransform.origin.y = height;
_player.Transform = playerStartTransform;
_player.TaskQueueComponent.Reset();
_player.Navigation.PlanDirectPath(playerStartTransform.origin, playerStartTransform.origin,
_player.Navigation.PlanDirectPath(_player, playerStartTransform.origin, playerStartTransform.origin,
playerStartTransform.basis.Quat());
_goldCountLabel.Text = "0";

View File

@ -39,7 +39,7 @@ transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 0 )
[node name="TileWorld" parent="." instance=ExtResource( 8 )]
GenerationMapType = 0
Size = 128
Size = 120
[node name="GameUI" type="HBoxContainer" parent="."]
anchor_left = 1.0
@ -301,4 +301,5 @@ input_ray_pickable = false
[connection signal="value_changed" from="Generator Container/WorldGeneratorContainer/HBoxContainer/WorldSizeSlider" to="Generator Container/WorldGeneratorContainer" method="_on_HSlider_value_changed"]
[connection signal="toggled" from="Generator Container/WorldGeneratorContainer/ShowTexturesCheckButton" to="Generator Container/WorldGeneratorContainer" method="_on_ShowTexturesCheckButton_toggled"]
[editable path="TileWorld"]
[editable path="Entities/Chest"]

View File

@ -19,6 +19,7 @@ public class StreamContainer : Spatial
// exports
[Export] public Vector2 Dimensions = new Vector2(8, 4);
[Export] public NodePath World;
[Export] private bool ShowHexTiles = false;
[Signal]
delegate void TileClicked(HexTile3D tile3d);
@ -194,6 +195,12 @@ public class StreamContainer : Spatial
tile3D.Connect("TileClicked", this, nameof(OnTileClicked));
tile3D.Connect("TileHovered", this, nameof(OnTileHovered));
if (ShowHexTiles)
{
MeshInstance HexTileMesh = tile3D.GetNode<MeshInstance>("Mesh");
HexTileMesh.Transform = HexTileMesh.Transform.Scaled(new Vector3(0.95f, 1, 0.95f));
}
Transform tileTransform = tile3D.Transform;
tileTransform.origin.y = _tileWorld.GetHeightAtOffset(offsetCoords);
tile3D.Transform = tileTransform;

View File

@ -51,6 +51,7 @@ texture = ExtResource( 2 )
[node name="Camera2D" type="Camera2D" parent="HeightmapOffscreenViewport"]
[node name="Assets" type="Spatial" parent="."]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -5, 0 )
visible = false
[node name="Rocks" type="Spatial" parent="Assets"]

View File

@ -1,13 +1,9 @@
using Godot;
using System;
using System.Diagnostics;
public class NavigationTests : Spatial
{
// Declare member variables here. Examples:
// private int a = 2;
// private string b = "text";
private StaticBody _groundLayer;
private HexGrid _hexGrid;
private HexCell _currentTile;
private HexCell _lastTile;
@ -19,8 +15,8 @@ public class NavigationTests : Spatial
private TileWorld _tileWorld;
private StreamContainer _streamContainer;
private Player _player;
private NavigationComponent _playerNavigationComponent;
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
_hexGrid = new HexGrid();
@ -29,8 +25,6 @@ public class NavigationTests : Spatial
_tileMaterial = GD.Load<ShaderMaterial>("materials/HexTileTextureLookup.tres");
_groundLayer = GetNode<StaticBody>("GroundLayer");
_mouseHighlight = GetNode<Spatial>("MouseHighlight");
_editorUi = GetNode<EditorUI>("EditorUI");
@ -41,9 +35,10 @@ public class NavigationTests : Spatial
_streamContainer.SetCenterTile(_currentTile);
_player = GetNode<Player>("Player");
_playerNavigationComponent = _player.GetNode<NavigationComponent>("Navigation");
// input handling
_groundLayer.Connect("input_event", this, nameof(OnGroundLayerInputEvent));
// _groundLayer.Connect("input_event", this, nameof(OnGroundLayerInputEvent));
_streamContainer.Connect("TileClicked", this, nameof(OnTileClicked));
_streamContainer.Connect("TileHovered", this, nameof(OnTileHovered));
}
@ -111,5 +106,9 @@ public class NavigationTests : Spatial
public void OnTileHovered(HexTile3D tile)
{
UpdateCurrentTile(tile.Cell);
Debug.Assert(_playerNavigationComponent != null);
_playerNavigationComponent.PlanGridPath(_player, _player.GlobalTranslation, tile.GlobalTranslation);
}
}

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=12 format=2]
[gd_scene load_steps=14 format=2]
[ext_resource path="res://entities/Player.tscn" type="PackedScene" id=1]
[ext_resource path="res://scenes/TileWorld.tscn" type="PackedScene" id=2]
@ -7,19 +7,79 @@
[ext_resource path="res://scenes/StreamContainer.tscn" type="PackedScene" id=5]
[ext_resource path="res://scenes/Camera.tscn" type="PackedScene" id=6]
[ext_resource path="res://scenes/tests/EditorUI.cs" type="Script" id=7]
[ext_resource path="res://entities/Tree.tscn" type="PackedScene" id=8]
[ext_resource path="res://scenes/HexTile3DPatch.tscn" type="PackedScene" id=9]
[sub_resource type="ButtonGroup" id=4]
resource_local_to_scene = false
resource_name = "TileTypeButtonGroup"
[sub_resource type="BoxShape" id=1]
extents = Vector3( 50, 1, 50 )
[sub_resource type="Animation" id=5]
resource_name = "Idle"
loop = true
tracks/0/type = "transform"
tracks/0/path = NodePath("Geometry")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/keys = PoolRealArray( 0, 1, 0, 0, 0, 0, 0, 0, 1, 1.5, 1.00063, 1.49958 )
[sub_resource type="SpatialMaterial" id=3]
albedo_color = Color( 0.180392, 0.384314, 0.0235294, 1 )
[sub_resource type="Animation" id=6]
length = 0.001
tracks/0/type = "bezier"
tracks/0/path = NodePath("Geometry:rotation_degrees:x")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/keys = {
"points": PoolRealArray( -10.23, -0.25, 0, 0.25, 0 ),
"times": PoolRealArray( 0 )
}
tracks/1/type = "bezier"
tracks/1/path = NodePath("Geometry:rotation_degrees:y")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/keys = {
"points": PoolRealArray( 0, -0.25, 0, 0.25, 0 ),
"times": PoolRealArray( 0 )
}
tracks/2/type = "bezier"
tracks/2/path = NodePath("Geometry:rotation_degrees:z")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/keys = {
"points": PoolRealArray( 0, -0.25, 0, 0.25, 0 ),
"times": PoolRealArray( 0 )
}
[sub_resource type="CubeMesh" id=2]
material = SubResource( 3 )
[sub_resource type="Animation" id=7]
resource_name = "TreeShake"
length = 0.8
loop = true
step = 0.05
tracks/0/type = "transform"
tracks/0/path = NodePath("Geometry")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/keys = PoolRealArray( 0, 1, 0, 0, 0, 0, 0, 0, 1, 1.5, 1, 1.5, 0.3, 1, 0, 0, 0, 0, 0, 0, 1, 1.5, 1, 1.5, 0.301, 1, 0, 0, 0, 0, 0, 0, 1, 1.5, 0.671551, 1.46936, 0.302, 1, 0, 0, 0, 0, 0, 0, 1, 1.5, 0.647776, 1.48, 0.303, 1, 0, 0, 0, 0, 0, 0, 1, 1.5, 1, 1.5, 0.304, 1, 0, 0, 0, 0, 0, 0, 1, 1.5, 1.00045, 1.4997, 0.6, 1, 0, 0, 0, 0, 0, 0, 1, 1.5, 1, 1.5 )
tracks/1/type = "bezier"
tracks/1/path = NodePath("Geometry:rotation_degrees:x")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/keys = {
"points": PoolRealArray( 7.4006, -0.05, -6.00801, 0.05, 6.00801, 16.7033, -0.05, -0.232568, 0.05, 0.232568, -11.011, -0.05, -0.833332, 0.05, 0.833332, 7.7294, -0.05, 0.833333, 0.05, -0.833333, -5.77825, -0.05, 0.232568, 0.05, -0.232568, 4.10589, -0.05, 0.465136, 0.05, -0.465136, -0.157859, -0.1, 0, 0.1, 0 ),
"times": PoolRealArray( 0, 0.05, 0.15, 0.3, 0.4, 0.5, 0.6 )
}
[node name="NavigationTests" type="Spatial"]
script = ExtResource( 4 )
@ -74,18 +134,10 @@ group = SubResource( 4 )
text = "Sand"
[node name="Player" parent="." instance=ExtResource( 1 )]
collision_layer = 1
collision_mask = 1
TileWorldNode = NodePath("../TileWorld")
[node name="GroundLayer" type="StaticBody" parent="."]
[node name="CollisionShape" type="CollisionShape" parent="GroundLayer"]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, 0 )
shape = SubResource( 1 )
[node name="MeshInstance" type="MeshInstance" parent="GroundLayer"]
transform = Transform( 50, 0, 0, 0, 1, 0, 0, 0, 50, 0, -1.05, 0 )
mesh = SubResource( 2 )
[node name="TileWorld" parent="." instance=ExtResource( 2 )]
GenerationMapType = 2
Size = 20
@ -94,8 +146,36 @@ DebugMap = true
[node name="MouseHighlight" parent="." instance=ExtResource( 3 )]
[node name="StreamContainer" parent="." instance=ExtResource( 5 )]
ShowHexTiles = true
[node name="Area" parent="StreamContainer" index="2"]
collision_layer = 0
[node name="Camera" parent="." instance=ExtResource( 6 )]
transform = Transform( 1, 0, 0, 0, 0.60042, 0.799685, 0, -0.799685, 0.60042, -4.76837e-07, 9.56665, 7.86873 )
[node name="Tree" parent="." instance=ExtResource( 8 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -1.54934, 0.00752521, 2.60764 )
[node name="Geometry" parent="Tree" index="0"]
transform = Transform( 1.5, 0, 0, 0, 0.984722, 0.266325, 0, -0.177712, 1.47574, 0, 0, 0 )
[node name="Tree2" parent="." instance=ExtResource( 8 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 3.73489, 0, 0.451849 )
[node name="Geometry" parent="Tree2" index="0"]
transform = Transform( 1.5, 0, 0, 0, 0.984722, 0.266325, 0, -0.177712, 1.47574, 0, 0, 0 )
[node name="AnimationPlayer" parent="Tree2" index="3"]
anims/Idle = SubResource( 5 )
anims/RESET = SubResource( 6 )
anims/TreeShake = SubResource( 7 )
[node name="Spatial" parent="." instance=ExtResource( 9 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.14454, 0 )
[editable path="Player"]
[editable path="TileWorld"]
[editable path="StreamContainer"]
[editable path="Tree"]
[editable path="Tree2"]