This commit is contained in:
2026-01-31 23:24:13 +02:00
parent be0c819c8b
commit 12c1360ae7
58 changed files with 1902 additions and 238 deletions
+69
View File
@@ -0,0 +1,69 @@
using System;
using Godot;
public partial class CameraController : Camera2D
{
[Export] public float ZoomStep = 0.1f;
[Export] public float MinZoom = 0.5f;
[Export] public float MaxZoom = 3.0f;
[Export] private float minX = 0f;
[Export] private float maxX = 0f;
[Export] private float minY = 0f;
[Export] private float maxY = 0f;
private bool _dragging;
private Vector2 _grabWorldPos;
public override void _Input(InputEvent @event)
{
// Middle mouse drag
if (@event is InputEventMouseButton mb)
{
if (mb.ButtonIndex == MouseButton.Middle)
{
_dragging = mb.Pressed;
if (_dragging)
_grabWorldPos = GetGlobalMousePosition();
}
// Scroll zoom
if (mb.Pressed &&
(mb.ButtonIndex == MouseButton.WheelUp ||
mb.ButtonIndex == MouseButton.WheelDown))
{
Vector2 mouseBeforeZoom = GetGlobalMousePosition();
float zoomFactor = mb.ButtonIndex == MouseButton.WheelUp
? -ZoomStep
: ZoomStep;
float newZoom = Mathf.Clamp(Zoom.X + zoomFactor, MinZoom, MaxZoom);
Zoom = new Vector2(newZoom, newZoom);
// keep cursor locked to same world point
Vector2 mouseAfterZoom = GetGlobalMousePosition();
GlobalPosition += mouseBeforeZoom - mouseAfterZoom;
ClampPos();
}
}
if (_dragging && @event is InputEventMouseMotion)
{
Vector2 currentWorld = GetGlobalMousePosition();
GlobalPosition += _grabWorldPos - currentWorld;
ClampPos();
}
}
private void ClampPos()
{
var t = GetCanvasTransform();
var camrect = t.AffineInverse().BasisXform(GetViewportRect().Size);
GlobalPosition = new Vector2(
Mathf.Clamp(GlobalPosition.X, LimitLeft + camrect.X / 2, LimitRight - camrect.X / 2),
Mathf.Clamp(GlobalPosition.Y, LimitTop + camrect.Y / 2, LimitBottom - camrect.Y / 2)
);
}
}
+1
View File
@@ -0,0 +1 @@
uid://df0osorq00h7v
+5 -1
View File
@@ -5,7 +5,11 @@ public partial class GameController : Node
{
public static GameController Instance;
[Export] public Node2D Player;
[Export] private float _ShrineStartHP;
public int Wave { get; private set; }
public int Currency { get; private set; }
public float ShrineHealth { get; private set; }
public override void _Ready()
{
+13
View File
@@ -0,0 +1,13 @@
using Godot;
namespace fgj26.Scripts.Common;
public partial class ProjectileParent : Node2D
{
public static ProjectileParent Instance;
public override void _Ready()
{
Instance = this;
}
}
+1
View File
@@ -0,0 +1 @@
uid://dxx6nyrs12mr6
+39
View File
@@ -0,0 +1,39 @@
using Godot;
using System;
public partial class Health : Node
{
[Export] private Node2D _parent;
[Export] private float _baseMaxHp;
public float MaxHP { get; private set; }
public float CurHP { get; private set; }
[Signal]
public delegate void DeathEventHandler(Node2D node);
public override void _EnterTree()
{
CurHP = MaxHP = _baseMaxHp * Mathf.Pow(GameController.Instance.Wave, 1.067f);
}
public void Substract(float v)
{
v = Mathf.Abs(v);
CurHP -= v;
if (CurHP <= 0)
{
EmitSignalDeath(_parent);
}
}
public void Add(float v)
{
v = Mathf.Abs(v);
CurHP = Math.Clamp(CurHP + v, 0f, MaxHP);
}
public void Reset()
{
CurHP = MaxHP = _baseMaxHp * Mathf.Pow(GameController.Instance.Wave, 1.067f);
}
}
+1
View File
@@ -0,0 +1 @@
uid://dvkd1keu854so
+26
View File
@@ -0,0 +1,26 @@
using System;
using Godot;
public partial class Enemy : PathFollow2D
{
[Export] public EnemyType Type;
[Export] public Health Health;
public event Action<Enemy> Died;
public override void _Ready()
{
Health.Death += _ => Died?.Invoke(this);
}
public void ResetEnemy()
{
GlobalPosition = Vector2.Zero;
Rotation = 0f;
Visible = true;
ProcessMode = ProcessModeEnum.Inherit;
Health.Reset();
// reset velocity, animation, AI, etc here
}
}
+1
View File
@@ -0,0 +1 @@
uid://bj52mq7uip7di
+6
View File
@@ -0,0 +1,6 @@
using Godot;
public partial class EnemyArea : Area2D
{
[Export] public Enemy Enemy { get; set; }
}
+1
View File
@@ -0,0 +1 @@
uid://c247m8m3qmlk0
+9 -15
View File
@@ -3,24 +3,18 @@ using System;
public partial class EnemyMovement : Node
{
[Export] private NavigationAgent2D _agent2D;
[Export] private CharacterBody2D _body2D;
[Export] private PathFollow2D _pathFollow2D;
[Export] private float _speed;
private double _time = 0;
public override void _PhysicsProcess(double delta)
public override void _EnterTree()
{
_time += delta;
if (_time > 0.2)
{
_time = 0;
_agent2D.SetTargetPosition(GameController.Instance.Player.GlobalPosition);
}
// _parent.Position = _agent2D.GetNextPathPosition();
var gpos = _agent2D.GetNextPathPosition();
var dir = (gpos - _body2D.GlobalPosition).Normalized();
// _body2D.SetVelocity(dir * _speed * (float)delta);
_body2D.GlobalPosition += dir * _speed * (float)delta;
_pathFollow2D.ProgressRatio = 0;
}
public override void _Process(double delta)
{
_pathFollow2D.ProgressRatio += (float)delta * (_speed / 1000f);
}
}
+59
View File
@@ -0,0 +1,59 @@
using Godot;
using System.Collections.Generic;
public enum EnemyType
{
Stone,
Stump,
Liquid
}
public partial class EnemyPool : Node
{
[Export] private Godot.Collections.Dictionary<EnemyType, PackedScene> _scenes;
[Export] private int _poolSize = 10;
private readonly Dictionary<EnemyType, Queue<Enemy>> _pool = new();
public override void _Ready()
{
foreach (var kvp in _scenes)
{
var q = new Queue<Enemy>();
_pool[kvp.Key] = q;
for (int i = 0; i < _poolSize; i++)
q.Enqueue(CreateEnemy(kvp.Key));
}
}
private Enemy CreateEnemy(EnemyType type)
{
var e = _scenes[type].Instantiate<Enemy>();
e.Died += ReturnToPool;
return e;
}
public Enemy Get(EnemyType type)
{
if (!_pool.TryGetValue(type, out var q))
return null;
var e = q.Count > 0 ? q.Dequeue() : CreateEnemy(type);
e.ResetEnemy();
return e;
}
private void ReturnToPool(Enemy e)
{
e.GetParent()?.RemoveChild(e);
e.ProcessMode = ProcessModeEnum.Disabled;
_pool[e.Type].Enqueue(e);
}
public EnemyType GetRandomType()
{
var keys = new List<EnemyType>(_pool.Keys);
return keys[GD.RandRange(0, keys.Count - 1)];
}
}
+1
View File
@@ -0,0 +1 @@
uid://bhxyvnirfeipr
+39
View File
@@ -0,0 +1,39 @@
using Godot;
using System.Collections.Generic;
public partial class EnemySpawner : Node
{
[Export] private Node _pathParent;
[Export] private EnemyPool _pool;
private readonly List<Path2D> _paths = new();
public override void _Ready()
{
foreach (var c in _pathParent.GetChildren())
if (c is Path2D p)
_paths.Add(p);
for (int i = 0; i < 10; i++)
SpawnWithDelay(i * 0.33f);
}
private async void SpawnWithDelay(float t)
{
await ToSignal(GetTree().CreateTimer(t), SceneTreeTimer.SignalName.Timeout);
Spawn();
}
private void Spawn()
{
if (_paths.Count == 0) return;
var path = _paths[GD.RandRange(0, _paths.Count - 1)];
var type = _pool.GetRandomType();
var enemy = _pool.Get(type);
if (enemy == null) return;
path.AddChild(enemy);
}
}
+1
View File
@@ -0,0 +1 @@
uid://cds2rrl4yjexf
+26
View File
@@ -0,0 +1,26 @@
using System;
using Godot;
namespace fgj26.Scripts.Helpers;
public static class Helpers
{
public static Node2D GetClosest(Node2D from, Node2D[] targets)
{
ArgumentNullException.ThrowIfNull(from);
ArgumentNullException.ThrowIfNull(targets);
if (targets.Length == 0) throw new ArgumentNullException(nameof(targets));
float dist = float.PositiveInfinity;
Node2D closest = null;
foreach (var t in targets)
{
var td = t.GlobalPosition.DistanceTo(from.GlobalPosition);
if (td < dist)
{
dist = td;
closest = t;
}
}
return closest;
}
}
+1
View File
@@ -0,0 +1 @@
uid://ef7d25f50nut
+40
View File
@@ -0,0 +1,40 @@
using System;
public static class RandomHelper
{
// Single Random instance for the whole app
private static readonly Random _rand = new Random();
// Random integer [min, max] inclusive
public static int Int(int min, int max)
{
return _rand.Next(min, max + 1); // max is inclusive
}
// Random float [min, max)
public static float Float(float min, float max)
{
return (float)(_rand.NextDouble() * (max - min) + min);
}
// Random double [min, max)
public static double Double(double min, double max)
{
return _rand.NextDouble() * (max - min) + min;
}
// Random element from an array
public static T Choice<T>(T[] array)
{
if (array == null || array.Length == 0)
throw new ArgumentException("Array cannot be null or empty.");
return array[_rand.Next(array.Length)];
}
// Random enum value
public static T ChoiceEnum<T>() where T : Enum
{
Array values = Enum.GetValues(typeof(T)); // returns System.Array
return (T)values.GetValue(_rand.Next(values.Length)); // use GetValue
}
}
+1
View File
@@ -0,0 +1 @@
uid://dy2yccrqc1ho3
+52
View File
@@ -0,0 +1,52 @@
using System;
using Godot;
using Godot.Collections;
public partial class Projectile : Node2D
{
[Export] private Array<EnemyType> _superEffective;
[Export] private float _baseDamage;
[Export] private float _speed;
[Export] private Area2D _hurtBox;
private float _ttl = 10f;
public event Action<Projectile> OnDespawn;
public Vector2 Direction;
public override void _Ready()
{
_hurtBox.AreaEntered += AreaEntered;
_hurtBox.BodyEntered += BodyEntered;
}
public override void _PhysicsProcess(double delta)
{
GlobalPosition += Direction * _speed * (float)delta;
_ttl -= (float)delta;
if (_ttl > 0f) return;
CallDeferred(nameof(Despawn));
}
private void AreaEntered(Area2D area)
{
if (area is EnemyArea earea)
{
float isSe = _superEffective.Contains(earea.Enemy.Type) ? 2f : 1f;
earea.Enemy.Health.Substract(_baseDamage * isSe);
}
CallDeferred(nameof(Despawn));
}
private void BodyEntered(Node2D node)
{
CallDeferred(nameof(Despawn));
}
private void Despawn()
{
OnDespawn?.Invoke(this);
}
}
+1
View File
@@ -0,0 +1 @@
uid://ln2lt16fi47q
+39
View File
@@ -0,0 +1,39 @@
using System.Collections.Generic;
using Godot;
public partial class ProjectilePool : Node
{
[Export] private PackedScene _scene;
[Export] private int _poolSize;
private readonly Queue<Projectile> _pool = new();
public override void _Ready()
{
for (int i = 0; i < _poolSize; i++)
{
_pool.Enqueue(CreateProjectile());
}
}
private Projectile CreateProjectile()
{
var p = _scene.Instantiate<Projectile>();
p.OnDespawn += ReturnToPool;
return p;
}
public Projectile Get()
{
var p = _pool.Count > 0 ? _pool.Dequeue() : CreateProjectile();
return p;
}
private void ReturnToPool(Projectile p)
{
p.GetParent()?.RemoveChild(p);
p.ProcessMode = ProcessModeEnum.Disabled;
_pool.Enqueue(p);
}
}
@@ -0,0 +1 @@
uid://cy00plvsi6vkn
+56
View File
@@ -0,0 +1,56 @@
using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using fgj26.Scripts.Common;
using fgj26.Scripts.Helpers;
public partial class Turret : Node
{
[Export] private Area2D _Attackrange;
[Export] private float _fireRate;
[Export] private Node2D _parent;
[Export] private ProjectilePool _projectilePool;
private float _fireTimer = 0f;
private HashSet<Enemy> _enemiesInRange = new HashSet<Enemy>();
public override void _EnterTree()
{
_Attackrange.AreaEntered += EnemyEntered;
_Attackrange.AreaExited += EnemyExited;
}
private void EnemyEntered(Area2D enemyHitBox)
{
if (enemyHitBox is EnemyArea earea)
{
_enemiesInRange.Add(earea.Enemy);
GD.Print(earea.Name);
}
}
private void EnemyExited(Area2D enemyHitBox)
{
if (enemyHitBox is EnemyArea earea)
{
_enemiesInRange.Remove(earea.Enemy);
GD.Print(earea.Name);
}
}
public override void _Process(double delta)
{
if (_enemiesInRange.Count == 0) return;
_fireTimer += (float)delta;
if (!(_fireTimer >= 1f / _fireRate)) return;
_fireTimer = 0;
var t = Helpers.GetClosest(_parent,_enemiesInRange.ToArray());
var dir = (t.GlobalPosition - _parent.GlobalPosition).Normalized();
var proj = _projectilePool.Get();
proj.Direction = dir;
proj.GlobalPosition = _parent.GlobalPosition;
proj.Rotation = dir.Angle();
ProjectileParent.Instance.AddChild(proj);
}
}
+1
View File
@@ -0,0 +1 @@
uid://cxrhl65cjw4yr
+8
View File
@@ -0,0 +1,8 @@
using Godot;
namespace fgj26.Scripts.Turrets;
public partial class TurretController : Node2D
{
}
+1
View File
@@ -0,0 +1 @@
uid://b0uosq6r5dbqa