Enemy and nest

This commit is contained in:
2025-08-30 02:06:58 +08:00
parent 630dbf0800
commit 32f96d488d
12 changed files with 327 additions and 10 deletions

View File

@@ -21,6 +21,15 @@
"layer": 1,
"size": [1, 1]
},
"enemy_nest": {
"scene": "res://Scenes/Tiles/EnemyNest.tscn",
"cost": {},
"durability": 200,
"buildTime": 0.0,
"allowedRotations": [0],
"layer": 1,
"size": [1, 1]
},
"ground": {
"scene": "res://Scenes/Tiles/GroundTile.tscn",
"cost": {},

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dlhpiyxtmp707"
path="res://.godot/imported/Enemy.jpg-862b79047d7834ee48aa6bbd7e126824.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Scenes/Entities/Enemy.jpg"
dest_files=["res://.godot/imported/Enemy.jpg-862b79047d7834ee48aa6bbd7e126824.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View File

@@ -0,0 +1,17 @@
[gd_scene load_steps=4 format=3 uid="uid://b3ffcucytwmk"]
[ext_resource type="Texture2D" uid="uid://dlhpiyxtmp707" path="res://Scenes/Entities/Enemy.jpg" id="1_8q37v"]
[ext_resource type="Script" uid="uid://cvsmy820b8dwl" path="res://Scripts/Entities/Enemy.cs" id="1_jajit"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_jajit"]
size = Vector2(60, 74)
[node name="Enemy" type="CharacterBody2D"]
script = ExtResource("1_jajit")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("RectangleShape2D_jajit")
[node name="Sprite2D" type="Sprite2D" parent="."]
scale = Vector2(0.1, 0.1)
texture = ExtResource("1_8q37v")

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://vwfs68ftvjr4"
path="res://.godot/imported/EnemyNest.jpg-9a1f582f2843b75fdeff38422d3798b9.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Scenes/Tiles/EnemyNest.jpg"
dest_files=["res://.godot/imported/EnemyNest.jpg-9a1f582f2843b75fdeff38422d3798b9.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View File

@@ -0,0 +1,19 @@
[gd_scene load_steps=5 format=3 uid="uid://dup2su0s3ybcy"]
[ext_resource type="Script" uid="uid://26hl5mk4mqur" path="res://Scripts/Tiles/EnemyNestTile.cs" id="1_4g0ff"]
[ext_resource type="Texture2D" uid="uid://vwfs68ftvjr4" path="res://Scenes/Tiles/EnemyNest.jpg" id="1_id484"]
[ext_resource type="PackedScene" uid="uid://b3ffcucytwmk" path="res://Scenes/Entities/Enemy.tscn" id="2_pka71"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_id484"]
size = Vector2(54, 54)
[node name="EnemyNest" type="CharacterBody2D"]
script = ExtResource("1_4g0ff")
EnemyScene = ExtResource("2_pka71")
[node name="Sprite2D" type="Sprite2D" parent="."]
scale = Vector2(0.056, 0.056)
texture = ExtResource("1_id484")
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("RectangleShape2D_id484")

View File

@@ -9,7 +9,7 @@ size = Vector2(54, 54)
[node name="OreIronTile" type="StaticBody2D"]
collision_layer = 0
script = ExtResource("1_exnim")
TileId = "stone_iron"
TileId = "ore_iron"
[node name="Sprite2D" type="Sprite2D" parent="."]
position = Vector2(1.49012e-08, -9.53674e-07)

89
Scripts/Entities/Enemy.cs Normal file
View File

@@ -0,0 +1,89 @@
using Godot;
namespace AceFieldNewHorizon.Scripts.Entities;
public partial class Enemy : CharacterBody2D
{
[Export] public float MoveSpeed = 150.0f;
[Export] public float DetectionRadius = 300.0f;
[Export] public float AttackRange = 50.0f;
[Export] public int Damage = 10;
[Export] public float AttackCooldown = 1.0f;
private Player _player;
private float _attackTimer = 0;
private bool _isPlayerInRange = false;
private Area2D _detectionArea;
public override void _Ready()
{
// Create detection area
_detectionArea = new Area2D();
var collisionShape = new CollisionShape2D();
var shape = new CircleShape2D();
shape.Radius = DetectionRadius;
collisionShape.Shape = shape;
_detectionArea.AddChild(collisionShape);
AddChild(_detectionArea);
// Connect signals
_detectionArea.BodyEntered += OnBodyEnteredDetection;
_detectionArea.BodyExited += OnBodyExitedDetection;
}
public override void _Process(double delta)
{
if (_player != null && _isPlayerInRange)
{
// Face the player
LookAt(_player.GlobalPosition);
// Move towards player if not in attack range
var direction = GlobalPosition.DirectionTo(_player.GlobalPosition);
var distance = GlobalPosition.DistanceTo(_player.GlobalPosition);
if (distance > AttackRange)
{
Velocity = direction * MoveSpeed;
}
else
{
Velocity = Vector2.Zero;
TryAttackPlayer(delta);
}
MoveAndSlide();
}
}
private void TryAttackPlayer(double delta)
{
_attackTimer += (float)delta;
if (_attackTimer >= AttackCooldown)
{
_attackTimer = 0;
// Here you can implement the attack logic
// For example: _player.TakeDamage(Damage);
GD.Print("Enemy attacks player!");
}
}
private void OnBodyEnteredDetection(Node2D body)
{
if (body is Player player)
{
_player = player;
_isPlayerInRange = true;
}
}
private void OnBodyExitedDetection(Node2D body)
{
if (body == _player)
{
_isPlayerInRange = false;
Velocity = Vector2.Zero;
}
}
}

View File

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

View File

@@ -25,6 +25,10 @@ public partial class NaturalResourceGenerator : Node2D
[Export] public int MaxIronVeinSize = 3;
[Export] public int Seed;
[Export] public bool SpawnEnemyNest = true;
[Export] public int MinDistanceFromOrigin = 20; // Minimum distance from world origin (0,0)
[Export] public int MaxDistanceFromOrigin = 50; // Maximum distance from world origin
private const string LogPrefix = "[NaturalGeneration]";
private RandomNumberGenerator _rng;
@@ -41,6 +45,32 @@ public partial class NaturalResourceGenerator : Node2D
{
_rng = new RandomNumberGenerator();
_rng.Seed = (ulong)(Seed != 0 ? Seed : (int)GD.Randi());
// Test if building registry is assigned
if (Registry == null)
{
GD.PrintErr($"{LogPrefix} BuildingRegistry is not assigned!");
return;
}
// Test if enemy_nest is in the registry
var testBuilding = Registry.GetBuilding("enemy_nest");
if (testBuilding == null)
{
GD.PrintErr($"{LogPrefix} 'enemy_nest' is not found in BuildingRegistry!");
}
else
{
GD.Print($"{LogPrefix} Found enemy_nest in registry!");
}
GD.Print($"{LogPrefix} NaturalResourceGenerator ready, SpawnEnemyNest = {SpawnEnemyNest}");
if (SpawnEnemyNest)
{
GD.Print($"{LogPrefix} Attempting to spawn enemy nest...");
SpawnRandomEnemyNest();
}
}
public override void _Process(double delta)
@@ -361,12 +391,52 @@ public partial class NaturalResourceGenerator : Node2D
GD.Print($"{LogPrefix} Finished placing vein - placed {placedCount}/{maxSize} {tileType} tiles");
}
private void SpawnRandomEnemyNest()
{
// Generate a random position within the specified distance from origin
var angle = _rng.Randf() * Mathf.Pi * 2;
var distance = _rng.RandfRange(MinDistanceFromOrigin, MaxDistanceFromOrigin);
var offset = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * distance;
var nestPosition = new Vector2I((int)offset.X, (int)offset.Y);
// Try to find a valid position for the nest
int attempts = 0;
const int maxAttempts = 10;
while (attempts < maxAttempts)
{
if (PlaceTile("enemy_nest", nestPosition))
{
GD.Print($"{LogPrefix} Placed enemy nest at {nestPosition}");
return;
}
// Try a different position if placement failed
angle = _rng.Randf() * Mathf.Pi * 2;
distance = _rng.RandfRange(MinDistanceFromOrigin, MaxDistanceFromOrigin);
offset = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * distance;
nestPosition = new Vector2I((int)offset.X, (int)offset.Y);
attempts++;
}
GD.PrintErr($"{LogPrefix} Failed to place enemy nest after {maxAttempts} attempts");
}
private bool PlaceTile(string tileType, Vector2I cell)
{
try
{
// First, remove any existing tile at this position
Grid.FreeArea(cell, Vector2I.One, 0f, GridLayer.Ground);
// For enemy nest, we want to place it on top of existing ground
if (tileType != "enemy_nest")
{
// Original behavior for other tile types
Grid.FreeArea(cell, Vector2I.One, 0f, GridLayer.Ground);
}
else
{
// For enemy nest, check if there's ground below (skip for now)
GD.Print($"{LogPrefix} Attempting placing nest at {cell}.");
}
var building = Registry.GetBuilding(tileType);
if (building == null)
@@ -374,6 +444,7 @@ public partial class NaturalResourceGenerator : Node2D
GD.PrintErr($"{LogPrefix} Building type not found in registry: {tileType}");
return false;
}
// GD.Print($"{LogPrefix} Found building in registry: {tileType}");
var scene = building.Scene;
if (scene == null)
@@ -381,27 +452,35 @@ public partial class NaturalResourceGenerator : Node2D
GD.PrintErr($"{LogPrefix} Scene is null for building type: {tileType}");
return false;
}
// GD.Print($"{LogPrefix} Scene loaded for {tileType}");
if (scene.Instantiate() is not BaseTile instance)
{
GD.PrintErr($"{LogPrefix} Failed to instantiate scene for: {tileType}");
return false;
}
// GD.Print($"{LogPrefix} Successfully instantiated {tileType}");
// Use the same positioning logic as PlacementManager
var rotatedSize = building.GetRotatedSize(0f); // 0f for no rotation
var offset = GridUtils.GetCenterOffset(rotatedSize, 0f); // 0f for no rotation
instance.GlobalPosition = GridUtils.GridToWorld(cell) + offset;
var rotatedSize = building.GetRotatedSize(0f);
var offset = GridUtils.GetCenterOffset(rotatedSize, 0f);
instance.ZIndex = (int)building.Layer;
instance.GlobalPosition = GridUtils.GridToWorld(cell) + offset;
instance.Grid = Grid;
AddChild(instance);
Grid.OccupyArea(cell, instance, building.Size, 0f, building.Layer);
// GD.Print($"{LogPrefix} Successfully placed {tileType} at {cell}");
// For enemy nest, use Building layer
var layer = tileType == "enemy_nest" ? GridLayer.Building : building.Layer;
Grid.OccupyArea(cell, instance, building.Size, 0f, layer);
// GD.Print($"{LogPrefix} Successfully placed {tileType} at {cell} on layer {layer}");
return true;
}
catch (Exception e)
{
GD.PrintErr($"{LogPrefix} Error placing {tileType} at {cell}: {e.Message}");
GD.Print($"{LogPrefix} Stack trace: {e.StackTrace}");
return false;
}
}

View File

@@ -0,0 +1,35 @@
using Godot;
namespace AceFieldNewHorizon.Scripts.Tiles;
public partial class EnemyNestTile : BaseTile
{
[Export] public PackedScene EnemyScene;
private Timer _spawnTimer;
public override void _Ready()
{
base._Ready();
// Create and configure the timer
_spawnTimer = new Timer();
AddChild(_spawnTimer);
_spawnTimer.Timeout += OnSpawnTimerTimeout;
_spawnTimer.Start(1.0f); // Start with 1 second interval
}
private void OnSpawnTimerTimeout()
{
if (EnemyScene != null)
{
var enemy = EnemyScene.Instantiate();
GetParent().AddChild(enemy);
// Position the enemy at the nest's position
if (enemy is Node2D enemy2D)
{
enemy2D.GlobalPosition = GlobalPosition;
}
}
}
}

View File

@@ -0,0 +1 @@
uid://26hl5mk4mqur

View File

@@ -77,8 +77,7 @@ switch_tile={
}
toggle_build={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":66,"key_label":0,"unicode":98,"location":0,"echo":false,"script":null)
]
"events": []
}
[rendering]