Added a RTS camera, and some assorted changes.

This commit is contained in:
2026-07-08 13:18:13 -04:00
parent 0fa7ba766b
commit c9ac4641a0
39 changed files with 982 additions and 211 deletions

View File

@@ -0,0 +1,253 @@
namespace FoodFactory;
using System;
using System.Collections.Generic;
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using FoodFactory.Equipment;
using Godot;
using GodotHelpers.Raycasts;
using SJK.Math;
[Meta(typeof(IAutoNode))]
public partial class ItemPlacerTest : Node
{
[Export] public string PlaceAction { get; set; } = "";
[Export] public float Length { get; set; } = 10f;
public override void _Notification(int what) => this.Notify(what);
[Export] public EquipmentEntry[] EquipmentEntrys { get; set; } = [];
private InputGestureManager _inputThing = default!;
public override void _Ready()
{
base._Ready();
_inputThing = new InputGestureManager.Builder()
.AddAction(PlaceAction)
.HoldTime(0.1f)
.OnQuickPress(TryPlaceItem)
.BuildAction()
.Build();
}
public void TryPlaceItem()
{
var camera = GetViewport().GetCamera3D();
var space = camera.GetWorld3D().DirectSpaceState;
var orgin = camera.ProjectRayOrigin(GetViewport().GetMousePosition());
var to = camera.ProjectRayNormal(GetViewport().GetMousePosition());
var result = space.RaycastEx(orgin, orgin + to * Length);
if (result is not null)
{
DebugDraw3D.DrawLine(orgin, result.Position, Colors.Red, 1);
var rounderPos = new Vector3I(Mathf.RoundToInt(result.Position.X), Mathf.RoundToInt(result.Position.Y), Mathf.RoundToInt(result.Position.Z));
DebugDraw3D.DrawBox(rounderPos, Quaternion.Identity, Vector3.One, Colors.Blue, true, 1);
var instance = EquipmentEntrys[0].VisualScene.Instantiate<Node3D>();
instance.Position = rounderPos;
GetParent().GetParent().AddChild(instance);
}
}
public override void _UnhandledInput(InputEvent @event) => _inputThing.HandleInput(@event);
public override void _Process(double delta) => _inputThing.Update((float)delta);
}
public sealed class InputGestureManager
{
private readonly Dictionary<string, GestureBinding> _bindings;
private InputGestureManager(Dictionary<string, GestureBinding> bindings)
{
_bindings = bindings;
}
public void HandleInput(InputEvent inputEvent)
{
foreach (var binding in _bindings.Values)
{
binding.HandleInput(inputEvent);
}
}
public void Update(float delta)
{
foreach (var binding in _bindings.Values)
{
binding.Update(delta);
}
}
#region Builder
public sealed class Builder
{
private readonly Dictionary<string, GestureBinding> _bindings = new();
public GestureBuilder AddAction(string actionName)
{
var binding = new GestureBinding(actionName);
return new GestureBuilder(
this,
binding,
completed => _bindings[actionName] = completed);
}
public InputGestureManager Build() => new(_bindings);
}
public sealed class GestureBuilder
{
private readonly Builder _parent;
private readonly GestureBinding _binding;
private readonly Action<GestureBinding> _onComplete;
internal GestureBuilder(
Builder parent,
GestureBinding binding,
Action<GestureBinding> onComplete)
{
_parent = parent;
_binding = binding;
_onComplete = onComplete;
}
public GestureBuilder HoldTime(float seconds)
{
_binding.HoldThreshold = seconds;
return this;
}
public GestureBuilder Predicate(Func<bool> predicate)
{
_binding.Predicate = predicate;
return this;
}
public GestureBuilder OnQuickPress(Action callback)
{
_binding.OnQuickPress = callback;
return this;
}
public GestureBuilder OnHoldStart(Action callback)
{
_binding.OnHoldStart = callback;
return this;
}
public GestureBuilder OnHolding(Action<float> callback)
{
_binding.OnHolding = callback;
return this;
}
public GestureBuilder OnDrag(Action<Vector2> callback)
{
_binding.OnDrag = callback;
return this;
}
public GestureBuilder OnRelease(Action callback)
{
_binding.OnRelease = callback;
return this;
}
public Builder BuildAction()
{
_onComplete(_binding);
return _parent;
}
}
#endregion
internal sealed class GestureBinding
{
public string ActionName { get; }
public float HoldThreshold = 0.25f;
public Func<bool>? Predicate;
public Action? OnQuickPress;
public Action? OnHoldStart;
public Action<float>? OnHolding;
public Action<Vector2>? OnDrag;
public Action? OnRelease;
private bool _pressed;
private bool _holding;
private float _heldTime;
public GestureBinding(string actionName)
{
ActionName = actionName;
}
public void HandleInput(InputEvent inputEvent)
{
if (Predicate != null && !Predicate())
{
return;
}
if (inputEvent.IsActionPressed(ActionName))
{
_pressed = true;
_holding = false;
_heldTime = 0f;
return;
}
if (inputEvent.IsActionReleased(ActionName))
{
if (_holding)
{
OnRelease?.Invoke();
}
else
{
OnQuickPress?.Invoke();
}
_pressed = false;
_holding = false;
_heldTime = 0f;
return;
}
if (_holding &&
inputEvent is InputEventMouseMotion motion)
{
OnDrag?.Invoke(motion.Relative);
}
}
public void Update(float delta)
{
if (!_pressed)
{
return;
}
_heldTime += delta;
if (!_holding &&
_heldTime >= HoldThreshold)
{
_holding = true;
OnHoldStart?.Invoke();
}
if (_holding)
{
OnHolding?.Invoke(delta);
}
}
}
}

View File

@@ -0,0 +1 @@
uid://0yvk53xc1dix

View File

@@ -0,0 +1,149 @@
namespace FoodFactory;
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using Godot;
[Meta(typeof(IAutoNode))]
public partial class RTSCameraController : Node3D
{
public override void _Notification(int what) => this.Notify(what);
[Export] public float MoveSpeed = 20f;
[Export] public float RotationSpeed = 90f;
[Export] public float ZoomSpeed = 5f;
[Export] public float MinZoom = 5f;
[Export] public float MaxZoom = 50f;
[Export] public float HeightSpeed = 10f;
[Node("YawPivot")]
private Node3D _yawPivot{get;set;} = default!;
[Node("YawPivot/PitchPivent")]
private Node3D _pitchPivot{get;set;} = default!;
[Node("YawPivot/PitchPivent/Camera3D")]
private Camera3D _camera{get;set;} = default!;
private float _zoomDistance = 20f;
private bool _freeLook = false;
public override void _Ready()
{
// _yawPivot = GetNode<Node3D>("YawPivot");
// _pitchPivot = _yawPivot.GetNode<Node3D>("PitchPivot");
// _camera = _pitchPivot.GetNode<Camera3D>("Camera3D");
UpdateZoom();
}
public override void _Process(double delta)
{
float dt = (float)delta;
HandleMovement(dt);
HandleRotation(dt);
HandleHeight(dt);
}
private void HandleMovement(float dt)
{
Vector3 move = Vector3.Zero;
if (Input.IsActionPressed("move_forward"))
move -= _yawPivot.GlobalBasis.Z;
if (Input.IsActionPressed("move_back"))
move += _yawPivot.GlobalBasis.Z;
if (Input.IsActionPressed("move_left"))
move -= _yawPivot.GlobalBasis.X;
if (Input.IsActionPressed("move_right"))
move += _yawPivot.GlobalBasis.X;
move.Y = 0;
move = move.Normalized();
GlobalPosition += move * MoveSpeed * dt;
}
private void HandleRotation(float dt)
{
if (Input.IsActionPressed("rotate_left"))
RotateY(Mathf.DegToRad(RotationSpeed * dt));
if (Input.IsActionPressed("rotate_right"))
RotateY(Mathf.DegToRad(-RotationSpeed * dt));
}
private void HandleHeight(float dt)
{
Vector3 pos = GlobalPosition;
if (Input.IsActionPressed("camera_up"))
pos.Y += HeightSpeed * dt;
if (Input.IsActionPressed("camera_down"))
pos.Y -= HeightSpeed * dt;
GlobalPosition = pos;
}
private void UpdateZoom()
{
_camera.Position = new Vector3(0, 0, _zoomDistance);
}
public override void _UnhandledInput(InputEvent @event)
{
if (@event is InputEventMouseButton mouseButton)
{
if (mouseButton.ButtonIndex == MouseButton.WheelUp)
{
_zoomDistance -= ZoomSpeed;
_zoomDistance = Mathf.Clamp(
_zoomDistance,
MinZoom,
MaxZoom
);
UpdateZoom();
}
if (mouseButton.ButtonIndex == MouseButton.WheelDown)
{
_zoomDistance += ZoomSpeed;
_zoomDistance = Mathf.Clamp(
_zoomDistance,
MinZoom,
MaxZoom
);
UpdateZoom();
}
if (mouseButton.ButtonIndex == MouseButton.Middle)
{
_freeLook = mouseButton.Pressed;
}
}
if (_freeLook && @event is InputEventMouseMotion motion)
{
_yawPivot.RotateY(
Mathf.DegToRad(-motion.Relative.X * 0.2f)
);
Vector3 rot = _pitchPivot.Rotation;
rot.X += Mathf.DegToRad(-motion.Relative.Y * 0.2f);
rot.X = Mathf.Clamp(
rot.X,
Mathf.DegToRad(-80),
Mathf.DegToRad(-15)
);
_pitchPivot.Rotation = rot;
}
}
}

View File

@@ -0,0 +1 @@
uid://wlid36cxbse0

View File

@@ -0,0 +1,15 @@
[gd_scene format=3 uid="uid://dr5gfg25sjr04"]
[ext_resource type="Script" uid="uid://bdvwvdqrbks7c" path="res://RTSCamera.cs" id="1_0un4r"]
[node name="RTS" type="Node3D" unique_id=1383983755 node_paths=PackedStringArray("camera")]
script = ExtResource("1_0un4r")
camera = NodePath("Camera3D")
[node name="YawPivot" type="Node3D" parent="." unique_id=1634769952]
[node name="PitchPivent" type="Node3D" parent="YawPivot" unique_id=1035768574]
[node name="Camera3D" type="Camera3D" parent="." unique_id=938232118]
transform = Transform3D(1, 0, 0, 0, 0.8646082, 0.5024466, 0, -0.5024466, 0.8646082, 1.1920929e-07, 1.8606924, 2.2873116)
current = true

View File

@@ -2,17 +2,24 @@ namespace FoodFactory.Voxel;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using Arch.Core;
using Arch.LowLevel;
using Arch.System;
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using Chickensoft.SaveFileBuilder;
using Chickensoft.Serialization;
using Chickensoft.Serialization.Godot;
using FoodFactory;
using FoodFactory.Blueprints;
using FoodFactory.Conveyors;
using FoodFactory.Math;
using FoodFactory.Recipes;
using Godot;
using SharpYaml;
public class BacterialSystem : BaseSystem<World, float>
{
private QueryDescription _desc = new QueryDescription().WithAll<Bacteria, Temperature>();
@@ -44,7 +51,14 @@ public class BacterialSystem : BaseSystem<World, float>
//Will Liklely be the game instead of a node like this
[Meta(typeof(IAutoNode))]// [Tool]
public partial class VoxelGridNode : Node3D, IProvide<IVoxelGridRegistry>, IProvide<IItemRenderer>, IProvide<IRecipes>, IProvide<IBlueprintManger>, IProvide<World>, IProvide<IFoodFactoryApi>
public partial class VoxelGridNode : Node3D,
IProvide<IVoxelGridRegistry>,
IProvide<IItemRenderer>,
IProvide<IRecipes>,
IProvide<IBlueprintManger>,
IProvide<World>,
IProvide<IFoodFactoryApi>,
IProvide<ISaveChunk<GameData>>
{
public override void _Notification(int what) => this.Notify(what);
@@ -60,6 +74,62 @@ public partial class VoxelGridNode : Node3D, IProvide<IVoxelGridRegistry>, IProv
IRecipes IProvide<IRecipes>.Value() => _recipes;
private IItemRenderer _itemRenderer = default!;
IItemRenderer IProvide<IItemRenderer>.Value() => _itemRenderer;
public ISaveChunk<GameData> Value() => SaveFile.Root;
public static string SavePath => $"{OS.GetUserDataDir()}/SaveFile.json";
JsonSerializerOptions _options;
public void Setup()
{
GodotSerialization.Setup();
_options = new JsonSerializerOptions
{
WriteIndented = true,
TypeInfoResolver = new SerializableTypeResolver(),
Converters = { new SerializableTypeConverter() }
};
var test = "gg";
GD.Print(SavePath);
SaveFile = new SaveFile<GameData>(
new SaveChunk<GameData>(
onSave: (chunk) =>
{
var gameData = new GameData()
{
World = _world,
// Equipments = chunk.GetChunkSaveData<EquipmentsData>()
};
return gameData;
},
onLoad: (chunk, data) =>
{
World.Destroy(_world);
_world = data.World;
// chunk.LoadChunkSaveData(data.Equipments);
}
),
onSave: async data =>
{
var yaml = JsonSerializer.Serialize(data, _options);
GD.Print(SavePath);
await File.WriteAllTextAsync(SavePath, yaml);
},
onLoad: async () =>
{
if (!File.Exists(SavePath))
{
GD.PushWarning("Save does nto exist");
return null;
}
var data = JsonSerializer.Deserialize<GameData>(await File.ReadAllTextAsync(SavePath), _options);
return data;
}
);
}
public ISaveFile<GameData> SaveFile { get; set; } = default!;
Group<float> _systems;
public override void _Ready()
{
@@ -118,6 +188,15 @@ public partial class VoxelGridNode : Node3D, IProvide<IVoxelGridRegistry>, IProv
}
}
}
if (@event.IsActionPressed("ui_right"))
{
SaveFile.Save();
}
if (@event.IsActionPressed("ui_left"))
{
SaveFile.Load();
}
}
public override void _ExitTree()
{
@@ -125,6 +204,7 @@ public partial class VoxelGridNode : Node3D, IProvide<IVoxelGridRegistry>, IProv
_systems.Dispose();
_world.Dispose();
}
}
public static class BacteriaModel
{

View File

@@ -10,6 +10,9 @@
[ext_resource type="Script" uid="uid://opbkqoaa7x2n" path="res://src/Equipment/Balancer.cs" id="7_2wkfx"]
[ext_resource type="Script" uid="uid://yec84plemjv1" path="res://src/Equipment/SlicerTest.cs" id="8_2lg7i"]
[ext_resource type="Script" uid="uid://culjdbwllmsyk" path="res://src/Equipment/StackerTest.cs" id="8_e2skk"]
[ext_resource type="PackedScene" uid="uid://dr5gfg25sjr04" path="res://src/VoxelGrid/RtsController.tscn" id="11_21ota"]
[ext_resource type="Script" uid="uid://0yvk53xc1dix" path="res://src/VoxelGrid/ItemPlacerTest.cs" id="12_6whqj"]
[ext_resource type="Resource" uid="uid://cus0vfrtgh0uh" path="res://src/VoxelGrid/test_equipment.tres" id="13_njjff"]
[sub_resource type="BoxMesh" id="BoxMesh_2wkfx"]
size = Vector3(1, 1, 2)
@@ -21,12 +24,15 @@ _data = {
}
point_count = 2
[sub_resource type="BoxMesh" id="BoxMesh_6whqj"]
size = Vector3(25, 1, 25)
[sub_resource type="BoxShape3D" id="BoxShape3D_njjff"]
size = Vector3(25, 1, 25)
[node name="VoxelGridNode" type="Node3D" unique_id=825696340]
script = ExtResource("1_tsdpe")
[node name="Camera3D" type="Camera3D" parent="." unique_id=75458542]
transform = Transform3D(0.6279494, -0.32487354, 0.70720345, -8.896721e-09, 0.908705, 0.4174389, -0.77825415, -0.26213053, 0.5706208, 4.635174, 2.6038146, 2.739409)
[node name="ConveyorBeltStraight5" parent="." unique_id=975225936 instance=ExtResource("6_mxaon")]
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 1, 0, 0)
@@ -345,3 +351,19 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.9604645e-08, 0, -0.3998499
[node name="door5" parent="Slicer/Node3D5" unique_id=770270315 instance=ExtResource("5_wk2t5")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.9604645e-08, 0, -0.3998499)
[node name="RTS" parent="." unique_id=1383983755 instance=ExtResource("11_21ota")]
[node name="Node" type="Node" parent="RTS" unique_id=1499015368]
script = ExtResource("12_6whqj")
PlaceAction = "place"
EquipmentEntrys = [ExtResource("13_njjff")]
[node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=1598166493]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, 0)
mesh = SubResource("BoxMesh_6whqj")
[node name="StaticBody3D" type="StaticBody3D" parent="MeshInstance3D" unique_id=919680718]
[node name="CollisionShape3D" type="CollisionShape3D" parent="MeshInstance3D/StaticBody3D" unique_id=622210784]
shape = SubResource("BoxShape3D_njjff")

View File

@@ -77,79 +77,3 @@ public sealed class VoxelRegistry : IVoxelGridRegistry
});
}
public enum Direction
{
Up, Down, Left, Right, Front, Back
}
public static class DirectionExtession
{
public static Vector3I ToVector(this Direction direction) => direction switch
{
Direction.Up => Vector3I.Up,
Direction.Down => Vector3I.Down,
Direction.Left => Vector3I.Left,
Direction.Right => Vector3I.Right,
Direction.Front => Vector3I.Forward,
Direction.Back => Vector3I.Back,
_ => throw new NotSupportedException($"{nameof(direction)} does not support value {direction}")
};
public static Direction Reverse(this Direction direction) => direction switch
{
Direction.Up => Direction.Down,
Direction.Down => Direction.Up,
Direction.Left => Direction.Right,
Direction.Right => Direction.Left,
Direction.Front => Direction.Back,
Direction.Back => Direction.Front,
_ => throw new NotSupportedException($"{nameof(direction)} does not support value {direction}")
};
public static Direction RotateClockWise(this Direction direction) => direction switch
{
Direction.Up => Direction.Up,
Direction.Down => Direction.Down,
Direction.Left => Direction.Back,
Direction.Right => Direction.Front,
Direction.Front => Direction.Right,
Direction.Back => Direction.Left,
_ => throw new NotSupportedException($"{nameof(direction)} does not support value {direction}")
};
public static Direction RotateCounterClockWise(this Direction direction) => direction switch
{
Direction.Up => Direction.Up,
Direction.Down => Direction.Down,
Direction.Right => Direction.Front,
Direction.Left => Direction.Back,
Direction.Back => Direction.Right,
Direction.Front => Direction.Left,
_ => throw new NotSupportedException($"{nameof(direction)} does not support value {direction}")
};
}
public interface IStorage<T> where T : class
{
StorageDefinition Definition { get; }
IEnumerable<T> GetAllItems();
void Remove(T item);
bool TryInsert(T item);
record StorageDefinition(string Name, ItemType Type);
}
public record ItemType(string Name);
public partial class ItemStoragePool : Node, IStorage<IBeltItem>
{
[Export] public int MaxAmount { get; set; } = 5;
public IStorage<IBeltItem>.StorageDefinition Definition { get; set; } = default!;
private readonly List<IBeltItem> _items = [];
public IEnumerable<IBeltItem> GetAllItems() => _items;
public bool TryInsert(IBeltItem item)
{
if (_items.Count >= MaxAmount)
{
return false;
}
_items.Add(item);
return true;
}
public void Remove(IBeltItem item) => _items.Remove(item);
}

View File

@@ -0,0 +1,8 @@
[gd_resource type="Resource" format=3 uid="uid://cus0vfrtgh0uh"]
[ext_resource type="PackedScene" uid="uid://c4h7mwnfrdesg" path="res://src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.tscn" id="1_ipx2f"]
[ext_resource type="Script" uid="uid://x8adhd57sup8" path="res://src/Equipment/EquipmentEntry.cs" id="2_82d1t"]
[resource]
script = ExtResource("2_82d1t")
VisualScene = ExtResource("1_ipx2f")