Component that spawns props (barrel, tar, anvil, spring). It exposes prefab and cost properties, checks score via ScoreManager, computes a spawn position using the scene camera and a trace, then clones the selected prefab and deducts cost on success.
using Sandbox;
using System.Linq;
public sealed class PropSpawner : Component
{
public static PropSpawner Instance { get; private set; }
[Property] public GameObject BarrelPrefab { get; set; }
[Property] public GameObject TarPrefab { get; set; }
[Property] public GameObject AnvilPrefab { get; set; }
[Property] public GameObject SpringPrefab { get; set; }
[Property] public int BarrelCost { get; set; } = 15;
[Property] public int TarCost { get; set; } = 20;
[Property] public int AnvilCost { get; set; } = 25;
[Property] public int SpringCost { get; set; } = 20;
[Property] public float MaxSpawnDistance { get; set; } = 500f;
protected override void OnAwake()
{
Instance = this;
}
public bool SpawnByIndex( int index )
{
return index switch
{
0 => BuyAndSpawn( BarrelPrefab, BarrelCost ),
1 => BuyAndSpawn( TarPrefab, TarCost ),
2 => BuyAndSpawn( AnvilPrefab, AnvilCost ),
3 => BuyAndSpawn( SpringPrefab, SpringCost ),
_ => false
};
}
public bool BuyAndSpawn( GameObject prefab, int cost )
{
if ( !prefab.IsValid() )
{
Log.Warning( "[PropSpawner] Ошибка: Префаб не назначен в Inspector!" );
return false;
}
int currentScore = ScoreManager.Instance?.Score ?? 999; // Если ScoreManager нет — даем спавнить для теста
if ( currentScore < cost )
{
Log.Warning( $"[PropSpawner] Недостаточно очков! Нужно: {cost}, есть: {currentScore}" );
return false;
}
Vector3 spawnPos = CalculateSpawnPosition();
var spawnedObj = prefab.Clone( spawnPos );
if ( spawnedObj.IsValid() )
{
if ( ScoreManager.Instance != null )
{
ScoreManager.Instance.AddScore( -cost );
}
Log.Info( $"[PropSpawner] Заспавнен {prefab.Name} в позиции {spawnPos}" );
return true;
}
return false;
}
private Vector3 CalculateSpawnPosition()
{
var cam = Scene.GetAllComponents<CameraComponent>().FirstOrDefault( c => !c.IsProxy );
Vector3 startPos = cam.IsValid() ? cam.WorldPosition : WorldPosition;
Vector3 forward = cam.IsValid() ? cam.WorldTransform.Forward : WorldTransform.Forward;
// Смещаем точку старта луча на 60 единиц вперёд, чтобы не упираться в модель персонажа
Vector3 rayStart = startPos + forward * 60f;
Vector3 rayEnd = startPos + forward * MaxSpawnDistance;
var ray = Scene.Trace.Ray( rayStart, rayEnd )
.IgnoreGameObject( GameObject )
.Run();
if ( ray.Hit )
{
return ray.HitPosition + ray.Normal * 15f;
}
return rayStart + forward * 150f;
}
}