This commit is contained in:
Oscar
2025-06-28 19:17:59 +03:00
parent 23a35fe3cd
commit 3cb6514f78
9 changed files with 216 additions and 251 deletions

View File

@@ -91,7 +91,7 @@ public class Inventar : Component
while ( toAdd > 0 && (UnlimitedSlots || Items.Count < MaxInventorySlots) )
{
int stackCount = Math.Min( toAdd, item.Definition.MaxCount );
var newStack = new InventoryItem { Definition = item.Definition, Count = stackCount };
var newStack = new InventoryItem { Definition = item.Definition, Count = stackCount, MagazineAmmo = item.MagazineAmmo };
Items.Add( newStack );
toAdd -= stackCount;
OnChanged?.Invoke();
@@ -161,19 +161,14 @@ public class Inventar : Component
if ( item == null || !Items.Contains( item ) )
return;
// // Создаем копию предмета для выбрасывания
// var droppedItem = new InventoryItem
// {
// Definition = item.Definition,
// Count = item.Count // Выбрасываем всю стопку
// };
GameObject gO = item.Definition.Prefab.Clone( position );
if ( gO.Components.TryGet<InventoryItem>( out var inventoryItem ) )
{
inventoryItem.Count = item.Count;
inventoryItem.Definition = item.Definition;
// Копируем патроны из оригинального предмета
inventoryItem.MagazineAmmo = item.MagazineAmmo;
}
gO.NetworkSpawn();

View File

@@ -8,6 +8,9 @@ public class InventoryItem : Component
[Property] public BaseItemDefinition Definition { get; set; }
[Sync, Property] public int Count { get; set; } = 1;
[Sync] public bool Equipped { get; set; } = false;
// Сохранение патронов в магазине для оружия
[Sync] public int MagazineAmmo { get; set; } = 0;
protected override void OnStart()
{
@@ -64,7 +67,7 @@ public class InventoryItem : Component
public InventoryItem Clone()
{
var clone = new InventoryItem { Definition = Definition, Count = Count, Equipped = false };
var clone = new InventoryItem { Definition = Definition, Count = Count, Equipped = false, MagazineAmmo = MagazineAmmo };
return clone;
}

View File

@@ -61,6 +61,8 @@ public sealed partial class Dedugan : Component
// Получаем компонент оружия и вызываем экипировку
if ( go.Components.TryGet<BaseWeapon>( out var weapon ) )
{
// Передаем ссылку на InventoryItem в оружие
weapon.SetInventoryItem( item );
weapon.OnEquipped();
_useableCache[weaponDef.Slot] = (go, weapon);
}
@@ -212,6 +214,16 @@ public sealed partial class Dedugan : Component
if ( weapon != null && !weapon.IsReloading )
{
weapon.StartReload();
// Анимация перезарядки персонажа (скорость уже установлена в weapon.StartReload())
// Дополнительно устанавливаем здесь на случай, если weapon.StartReload() не вызвался
var weaponDef = weapon.GetWeaponDefinition();
if ( weaponDef != null )
{
float reloadSpeed = weaponDef.ReloadTime > 0 ? 1f / weaponDef.ReloadTime : 1f;
Renderer.Set( "b_reload", true );
Renderer.Set( "speed_reload", reloadSpeed );
}
}
}

View File

@@ -73,15 +73,7 @@ public sealed partial class Dedugan : Component, IUseContext, Component.INetwork
EyeAngles = EyeAngles.WithPitch( MathX.Clamp( EyeAngles.pitch, -89f, 89f ) );
NetworkedEyeAngles = EyeAngles;
var targetRotation = Rotation.LookAt( Rotation.FromYaw( EyeAngles.yaw ).Forward, -_directionToAxis );
var currentForward = Renderer.LocalRotation.Forward;
float angleDiff = currentForward.Angle( targetRotation.Forward );
if ( angleDiff > 15f && Controller.Velocity.Length > 10f )
{
Renderer.LocalRotation = Rotation.Slerp( Renderer.LocalRotation, Rotation.FromYaw( EyeAngles.yaw ),
Time.Delta * 3f );
}
UpdateBodyRotation();
RotateCamera();
InteractionsUpdate();
@@ -90,15 +82,7 @@ public sealed partial class Dedugan : Component, IUseContext, Component.INetwork
{
EyeAngles = NetworkedEyeAngles;
var targetRotation = Rotation.LookAt( Rotation.FromYaw( EyeAngles.yaw ).Forward, -_directionToAxis );
var currentForward = Renderer.LocalRotation.Forward;
float angleDiff = currentForward.Angle( targetRotation.Forward );
if ( angleDiff > 15f && Controller.Velocity.Length > 10f )
{
Renderer.LocalRotation = Rotation.Slerp( Renderer.LocalRotation, Rotation.FromYaw( EyeAngles.yaw ),
Time.Delta * 3f );
}
UpdateBodyRotation();
// Renderer.LocalRotation = Rotation.Slerp(Renderer.LocalRotation, Rotation.FromYaw(EyeAngles.yaw), Time.Delta * 5f);
@@ -133,4 +117,40 @@ public sealed partial class Dedugan : Component, IUseContext, Component.INetwork
// Возвращаем пустой список, так как теперь оружие управляется через новую систему
return Enumerable.Empty<IUseable>();
}
/// <summary>
/// Обновление поворота тела с учетом режима прицеливания
/// </summary>
private void UpdateBodyRotation()
{
var targetRotation = Rotation.LookAt( Rotation.FromYaw( EyeAngles.yaw ).Forward, -_directionToAxis );
var currentForward = Renderer.LocalRotation.Forward;
float angleDiff = currentForward.Angle( targetRotation.Forward );
// Проверяем, нужно ли поворачивать тело
bool shouldRotateBody = false;
if ( InAds )
{
// В режиме прицеливания - мертвая зона 85 градусов
if ( angleDiff > 85f )
{
shouldRotateBody = true;
}
}
else
{
// В обычном режиме - поворот при движении и угле больше 15 градусов
if ( angleDiff > 15f && Controller.Velocity.Length > 10f )
{
shouldRotateBody = true;
}
}
if ( shouldRotateBody )
{
Renderer.LocalRotation = Rotation.Slerp( Renderer.LocalRotation, Rotation.FromYaw( EyeAngles.yaw ),
Time.Delta * 3f );
}
}
}

View File

@@ -22,7 +22,6 @@ public class BaseWeapon : InventoryItem, IUseable
[Property] public GameObject EjectTransform { get; set; }
// Состояние оружия
[Sync] public int CurrentAmmo { get; set; }
[Sync] public bool IsReloading { get; set; }
[Sync] public TimeSince TimeSinceLastShot { get; set; }
[Sync] public TimeSince TimeSinceReloadStart { get; set; }
@@ -35,6 +34,7 @@ public class BaseWeapon : InventoryItem, IUseable
// Кэш для производительности
private WeaponItemDefinition _weaponDefinition;
private bool _isInitialized;
private InventoryItem _inventoryItem;
// IUseable реализация - Cooldown вычисляется на основе FireRate
public float Cooldown
@@ -46,6 +46,32 @@ public class BaseWeapon : InventoryItem, IUseable
}
}
/// <summary>
/// Установить ссылку на InventoryItem
/// </summary>
public void SetInventoryItem( InventoryItem item )
{
_inventoryItem = item;
// Инициализируем оружие после установки InventoryItem
InitializeWeapon();
}
/// <summary>
/// Получить количество патронов в магазине из InventoryItem
/// </summary>
public int CurrentAmmo
{
get => _inventoryItem?.MagazineAmmo ?? 0;
set
{
if ( _inventoryItem != null )
{
_inventoryItem.MagazineAmmo = value;
}
}
}
protected override void OnStart()
{
base.OnStart();
@@ -54,8 +80,7 @@ public class BaseWeapon : InventoryItem, IUseable
_sound = GameObject.GetComponent<SoundPointComponent>( true );
_pickupItem = GameObject.Components.Get<PickupItem>();
// Инициализируем оружие
InitializeWeapon();
// Инициализация оружия будет вызвана после установки InventoryItem
}
protected override void OnUpdate()
@@ -88,8 +113,7 @@ public class BaseWeapon : InventoryItem, IUseable
return;
}
// Оружие начинается с пустым магазином - патроны нужно зарядить
CurrentAmmo = 0;
// Инициализация завершена
_isInitialized = true;
}
@@ -308,6 +332,24 @@ public class BaseWeapon : InventoryItem, IUseable
IsReloading = true;
TimeSinceReloadStart = 0;
var weaponDef = GetWeaponDefinition();
float reloadSpeed = weaponDef?.ReloadTime > 0 ? 1f / weaponDef.ReloadTime : 1f;
// Анимация перезарядки оружия
if ( GunRenderer != null )
{
GunRenderer.Set( "b_reload", true );
GunRenderer.Set( "speed_reload", reloadSpeed );
}
// Анимация перезарядки персонажа
var player = Dedugan.Local;
if ( player?.Renderer != null )
{
player.Renderer.Set( "b_reload", true );
player.Renderer.Set( "speed_reload", reloadSpeed );
}
// Эффекты перезарядки
PlayReloadEffects();
}
@@ -319,6 +361,21 @@ public class BaseWeapon : InventoryItem, IUseable
{
IsReloading = false;
// Сбрасываем анимацию перезарядки оружия
if ( GunRenderer != null )
{
GunRenderer.Set( "b_reload", false );
GunRenderer.Set( "speed_reload", 1f );
}
// Сбрасываем анимацию перезарядки персонажа
var player = Dedugan.Local;
if ( player?.Renderer != null )
{
player.Renderer.Set( "b_reload", false );
player.Renderer.Set( "speed_reload", 1f );
}
var weaponDef = GetWeaponDefinition();
if ( weaponDef == null ) return;

View File

@@ -45,12 +45,16 @@
### 3. Система патронов
- **Оружие начинается с пустым магазином** - патроны нужно зарядить
- **Сохранение патронов в магазине** - при выбрасывании оружия патроны остаются
- **Патроны сохраняются при снятии/экипировке** - патроны в магазине не теряются при переключении оружия
- Патроны хранятся в инвентаре как отдельные предметы
- **Стрельба зависит ТОЛЬКО от патронов в магазине** - CurrentAmmo
- **HUD показывает патроны в магазине** - не зависит от инвентаря
- **Патроны тратятся только из магазина при выстреле** - CurrentAmmo уменьшается
- **Патроны тратятся из инвентаря только при перезарядке** - загружаются в магазин
- **Перезарядка только вручную** - кнопка R или автоматически при пустом магазине
- **Анимация перезарядки оружия** - `b_reload` параметр анимации оружия
- **Анимация перезарядки персонажа** - `b_reload` параметр анимации персонажа
- **Скорость анимации перезарядки** - `speed_reload` параметр для контроля скорости анимации (рассчитывается как 1/ReloadTime)
- **Безопасная обработка неполных магазинов** - загружается столько патронов, сколько есть в инвентаре
### 4. HUD и UI
@@ -136,130 +140,4 @@ player.Inventory.AddItem(flashlightItem);
### 3. Создание собственного предмета
```csharp
// Создайте новый класс
public sealed class Medkit : Component, IUseable
{
[Property] public float Cooldown { get; set; } = 5f;
[Property] public int HealAmount { get; set; } = 50;
private TimeSince _lastUseTime;
public bool CanUse()
{
return IsValid && _lastUseTime >= Cooldown;
}
public void Use()
{
if (!CanUse()) return;
_lastUseTime = 0;
// Логика лечения
var player = Dedugan.Local;
if (player != null)
{
player.Health = Math.Min(player.Health + HealAmount, 100);
Log.Info($"Игрок вылечен на {HealAmount} HP");
}
}
}
```
## WeaponFactory - подробное руководство
### Основные методы:
#### 1. `CreateUseableItem(BaseItemDefinition, InventoryItem)`
Создает любой используемый предмет:
```csharp
var itemDef = ResourceLibrary.Get<BaseItemDefinition>("path/to/item");
var item = WeaponFactory.CreateUseableItem(itemDef, inventoryItem);
```
#### 2. `CreateWeapon(WeaponItemDefinition, InventoryItem)`
Создает оружие (для обратной совместимости):
```csharp
var weaponDef = ResourceLibrary.Get<WeaponItemDefinition>("path/to/weapon");
var weapon = WeaponFactory.CreateWeapon(weaponDef, inventoryItem);
```
#### 3. `CreateItem(BaseItemDefinition)`
Создает предмет для инвентаря:
```csharp
var itemDef = ResourceLibrary.Get<BaseItemDefinition>("path/to/item");
var inventoryItem = WeaponFactory.CreateItem(itemDef);
```
### Автоматическое определение типа
WeaponFactory автоматически определяет тип предмета по имени:
- **Оружие**: содержит "pistol", "rifle", "gun" → создается `BaseWeapon`
- **Фонарик**: содержит "flashlight", "light" → создается `Flashlight`
- **По умолчанию**: создается `BaseWeapon`
### Примеры использования
#### Создание пистолета:
```csharp
// 1. Создаем предмет для инвентаря
var pistolDef = ResourceLibrary.Get<WeaponItemDefinition>("Items/pistol.weapon");
var pistolItem = WeaponFactory.CreateWeaponItem(pistolDef);
// 2. Добавляем в инвентарь
player.Inventory.AddItem(pistolItem);
// 3. Экипируем
player.Inventory.EquipItem(pistolItem);
```
#### Создание фонарика:
```csharp
// 1. Создаем предмет
var flashlightDef = ResourceLibrary.Get<BaseItemDefinition>("Items/flashlight.item");
var flashlightItem = WeaponFactory.CreateItem(flashlightDef);
// 2. Добавляем в инвентарь
player.Inventory.AddItem(flashlightItem);
```
#### Создание аптечки:
```csharp
// 1. Создаем предмет
var medkitDef = ResourceLibrary.Get<BaseItemDefinition>("Items/medkit.item");
var medkitItem = WeaponFactory.CreateItem(medkitDef);
// 2. Добавляем в инвентарь
player.Inventory.AddItem(medkitItem);
```
## Настройка в префабах
### Для оружия:
1. Добавьте компонент `BaseWeapon`
2. Настройте все необходимые ссылки (рендерер, звуки, эффекты)
3. Установите параметры стрельбы (Cooldown, FireRate и т.д.)
### Для других предметов:
1. Добавьте компонент, реализующий `IUseable`
2. Настройте специфичные параметры
3. Реализуйте логику в методах `CanUse()` и `Use()`
## Преимущества системы
1. **Универсальность** - один интерфейс для всех предметов
2. **Гибкость** - каждый предмет может иметь свою логику
3. **Простота** - настройка в префабах, без создания отдельных файлов
4. **Расширяемость** - легко добавлять новые типы предметов
5. **Производительность** - кэширование и оптимизация
6. **Читаемость** - понятная структура и документация
## Примеры предметов
- **Оружие** - стрельба, перезарядка, патроны
- **Фонарик** - включение/выключение света
- **Аптечка** - лечение игрока
- **Инструменты** - ремонт, строительство
- **Еда** - восстановление здоровья/голода
```