Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Game.MVC.Core.Enums;
using Game.MVC.Core.Scenes;
using Game.Shared.Extensions;
using Game.Shared.Services.Interfaces;
using R3;

namespace Game.Horror.Dialogs
Expand All @@ -15,6 +16,7 @@ public class HorrorConfirmDialog : GameDialogScene<HorrorConfirmDialog, HorrorCo

private readonly IInputSystemService _inputService = GameServiceManager.Resolve<IInputSystemService>();
private readonly IHorrorUISoundService _uiSoundService = GameServiceManager.Resolve<IHorrorUISoundService>();
private readonly ILocalizationService _localizationService = GameServiceManager.Resolve<ILocalizationService>();
private string _messageLocalizeKey;

public static async UniTask<bool> RunAsync(string messageLocalizeKey)
Expand Down Expand Up @@ -50,7 +52,8 @@ public override UniTask Startup()
.Subscribe(x => TrySetResult(x))
.AddTo(Disposables);

SceneComponent.Initialize(_messageLocalizeKey);
string message = _localizationService.GetStringByMessages(_messageLocalizeKey);
SceneComponent.Initialize(message);

return base.Startup();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using Game.Core.Services;
using Game.MVC.Core.Scenes;
using Game.Shared.Services.Interfaces;
using R3;
using TMPro;
using UnityEngine;
Expand All @@ -17,10 +15,6 @@ public class HorrorConfirmDialogComponent : GameSceneComponent
public Observable<bool> OnSubmit => _submitButton.OnClickAsObservable().Select(_ => true);
public Observable<bool> OnCancel => _cancelButton.OnClickAsObservable().Select(_ => false);

public void Initialize(string messageLocalizeKey)
{
var localization = GameServiceManager.Resolve<ILocalizationService>();
_message.text = localization.GetStringByMessages(messageLocalizeKey);
}
public void Initialize(string message) => _message.text = message;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ public class HorrorEquipmentShortcutDialog : GameDialogScene<HorrorEquipmentShor

private readonly IInputSystemService _inputService = GameServiceManager.Resolve<IInputSystemService>();
private readonly IHorrorUISoundService _uiSoundService = GameServiceManager.Resolve<IHorrorUISoundService>();
private readonly IHorrorEquipmentService _equipmentService = GameServiceManager.Resolve<IHorrorEquipmentService>();
private IObjectInfo _target;
private int _currentIndex; // 現在選択中のスロット。初期フォーカス(先頭スロット)と初期値 0 が対応する

public static async UniTask<bool> RunAsync(IObjectInfo target)
{
Expand All @@ -36,7 +38,6 @@ public UniTask SetArg(IObjectInfo target)

public override UniTask Startup()
{
// キャンセルで閉じる(親がインベントリ表示中は Menu をブロックしているため Cancel のみ)
_inputService.UI.Cancel.OnPerformedAsObservable()
.Where(_ => State.IsProcessing())
.Subscribe(_ =>
Expand All @@ -46,13 +47,25 @@ public override UniTask Startup()
})
.AddTo(Disposables);

// Del で現在スロットの登録を外す
_inputService.UI.Remove.OnPerformedAsObservable()
.Where(_ => State.IsProcessing())
.Subscribe(_ => SceneComponent.RemoveCurrent())
.Subscribe(_ => _equipmentService.ClearSlot(_currentIndex))
.AddTo(Disposables);

SceneComponent.Initialize(_target);
SceneComponent.OnSlotSelected
.Subscribe(index => _currentIndex = index)
.AddTo(Disposables);

SceneComponent.OnSlotClicked
.Where(_ => State.IsProcessing())
.Subscribe(index =>
{
if (_target == null) return;
_equipmentService.TryAssignSlot(index, _target.ObjectCategory, _target.ObjectId);
})
.AddTo(Disposables);

SceneComponent.Initialize();

return base.Startup();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,46 +1,40 @@
using System.Linq;
using Game.Core.Services;
using Game.Horror.Equipment;
using Game.Horror.Services.Interfaces;
using Game.MVC.Core.Scenes;
using Game.Shared.Interfaces;
using R3;
using UnityEngine;

namespace Game.Horror.Dialogs
{
/// <summary>
/// ショートカット登録ダイアログのビュー。D-Pad 4スロットへの登録/解除と現在スロットの追跡を担う。
/// スロット並びは 1=左 / 2=上 / 3=右 / 4=下(index 0-3)。
/// ショートカット登録ダイアログのビュー
/// </summary>
public class HorrorEquipmentShortcutDialogComponent : GameSceneComponent
{
[SerializeField] private HorrorEquipmentSlotView[] _slots;

private IInputSystemService _inputService;
private IHorrorEquipmentService _equipmentService;
private IObjectInfo _target;
private int _currentIndex;

public void Initialize(IObjectInfo target)
/// <summary>スロット決定(クリック)。値は決定されたスロット index。</summary>
public Observable<int> OnSlotClicked
=> _slots.Select((slot, index) => slot.OnClick.Select(_ => index)).Merge();

/// <summary>スロット選択(フォーカス移動)。値は選択されたスロット index。</summary>
public Observable<int> OnSlotSelected
=> _slots.Select((slot, index) => slot.OnSelect.Select(_ => index)).Merge();

public void Initialize()
{
_target = target;
_inputService = GameServiceManager.Resolve<IInputSystemService>();
_equipmentService = GameServiceManager.Resolve<IHorrorEquipmentService>();

for (int i = 0; i < _slots.Length; i++)
{
_slots[i].Initialize();

int index = i;
RefreshSlot(index);

_slots[i].OnSelect
.Subscribe(_ => _currentIndex = index)
.AddTo(Disposables);

_slots[i].OnClick
.Subscribe(_ => Register(index))
.AddTo(Disposables);
RefreshSlot(i);
}

// 初期フォーカスを先頭スロットへ
Expand All @@ -52,18 +46,7 @@ public void Initialize(IObjectInfo target)
.AddTo(Disposables);
}

/// <summary>現在選択中スロットの登録を外す(Dialog の Remove 入力から呼ぶ)。表示は EquipmentChanged の購読で追従する。</summary>
public void RemoveCurrent()
=> _equipmentService.ClearSlot(_currentIndex);

// 対象アイテムを指定スロットへ登録する。Assign は既登録なら移動/入替、未登録なら上書きで
// 影響が2スロットに及ぶが、表示は EquipmentChanged の購読(全スロット再表示)で追従する。
private void Register(int index)
{
if (_target == null) return;
_equipmentService.TryAssignSlot(index, _target.ObjectCategory, _target.ObjectId);
}

/// <summary>全スロットの表示を現在の装備状態から再構築する(Dialog の EquipmentChanged 購読から呼ぶ)。</summary>
private void RefreshAllSlots()
{
for (int i = 0; i < _slots.Length; i++)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System.Threading;
using Cysharp.Threading.Tasks;
using Game.Core.Services;
using Game.Horror.Constants;
using Game.Horror.Inventory;
using Game.Horror.Services.Interfaces;
using Game.MVC.Core.Enums;
using Game.MVC.Core.Scenes;
Expand Down Expand Up @@ -32,8 +35,10 @@ public class HorrorInventoryDialog : GameDialogScene<HorrorInventoryDialog, Horr
private readonly IHorrorInventoryService _inventoryService = GameServiceManager.Resolve<IHorrorInventoryService>();
private readonly IHorrorPlayerService _playerService = GameServiceManager.Resolve<IHorrorPlayerService>();
private readonly IHorrorUISoundService _uiSoundService = GameServiceManager.Resolve<IHorrorUISoundService>();
private readonly IHorrorCraftService _craftService = GameServiceManager.Resolve<IHorrorCraftService>();

private HorrorInventoryResult _result;
private bool _isCraftHolding; // 長押し進行中(Cancel・タブ切替・トグルの抑止用)

public static async UniTask<HorrorInventoryResult> RunAsync()
{
Expand All @@ -60,7 +65,7 @@ public override UniTask Startup()
.Where(_ => State.IsProcessing())
.Subscribe(_ =>
{
if (SceneComponent.IsCrafting()) return;
if (_isCraftHolding) return;

_uiSoundService.PlayCancelSfx();

Expand All @@ -73,18 +78,18 @@ public override UniTask Startup()

// インベントリトグルでダイアログを閉じる(サブメニュー展開中・クラフトの長押し中は無効)
_inputService.Player.Inventory.OnPerformedAsObservable()
.Where(_ => State.IsProcessing() && !SceneComponent.IsProcessing())
.Where(_ => State.IsProcessing() && !SceneComponent.IsSubmenuOpen() && !_isCraftHolding)
.Subscribe(_ => TrySetResult(_result))
.AddTo(Disposables);

// L1 (Previous) / R1 (Next) でタブ循環(サブメニュー展開中・クラフトの長押し中は無効)
_inputService.UI.Previous.OnPerformedAsObservable()
.Where(_ => State.IsProcessing() && !SceneComponent.IsProcessing())
.Where(_ => State.IsProcessing() && !SceneComponent.IsSubmenuOpen() && !_isCraftHolding)
.Subscribe(_ => SceneComponent.PreviousTab())
.AddTo(Disposables);

_inputService.UI.Next.OnPerformedAsObservable()
.Where(_ => State.IsProcessing() && !SceneComponent.IsProcessing())
.Where(_ => State.IsProcessing() && !SceneComponent.IsSubmenuOpen() && !_isCraftHolding)
.Subscribe(_ => SceneComponent.NextTab())
.AddTo(Disposables);

Expand Down Expand Up @@ -133,6 +138,17 @@ public override UniTask Startup()
})
.AddTo(Disposables);

// クラフトの長押し:決定またはレシピ行のポインタ押下で開始し、閾値到達で実行(ゲージの描画は View 側)
var craftView = SceneComponent.CraftView;
if (craftView != null)
{
Observable.Merge(_inputService.UI.Submit.OnPerformedAsObservable().AsUnitObservable(),
craftView.OnRecipePointerPressed)
.Where(_ => State.IsProcessing() && !_isCraftHolding && craftView.IsVisible)
.SubscribeAwait(async (_, ct) => await RunCraftHoldAsync(craftView, ct))
.AddTo(Disposables);
}

SceneComponent.Initialize();

return base.Startup();
Expand All @@ -143,5 +159,48 @@ public override async UniTask Terminate()
ApplicationEvents.ResumeTime();
await base.Terminate();
}

// 長押し1回分のフロー。エッジ(決定押下・行ポインタ押下)で開始し、中断または実行で終わる。
// 押しっぱなしでのタブ進入・実行直後の押し続けは新しいエッジを生まないため、再実行は構造的に起きない。
private async UniTask RunCraftHoldAsync(HorrorCraftView view, CancellationToken ct = default)
{
_isCraftHolding = true;
var elapsed = 0f;
try
{
// 押下と同フレームの選択更新(ポインタ押下による行選択)を待ってから対象を確定する
await UniTask.Yield(PlayerLoopTiming.Update, ct);

var craftId = view.SelectedCraftId;
if (craftId == null) return;

while (true)
{
if (!State.IsProcessing() || !view.IsVisible || view.SelectedCraftId != craftId) return;

bool held = _inputService.UI.Submit.IsPressed() || view.IsSelectedPointerHeld;

// 中断条件:押下解除・素材不足(他タブでの破棄などで実行不可へ変わった場合を含む)
if (!held || !_craftService.CanCraft(craftId.Value)) return;

elapsed += Time.unscaledDeltaTime;
view.SetHoldProgress(elapsed / HorrorCraftConstants.CraftHoldSeconds);

if (elapsed >= HorrorCraftConstants.CraftHoldSeconds)
{
view.SetHoldProgress(0f); // 実行前にゲージを消す(旧 Execute と同順)
_craftService.TryCraft(craftId.Value);
return;
}

await UniTask.Yield(PlayerLoopTiming.Update, ct);
}
}
finally
{
_isCraftHolding = false;
view.SetHoldProgress(0f); // 中断時の後始末(冪等。View 破棄後も SetHoldProgress 内の null ガードで安全)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ public Observable<HorrorInventoryContextActionInfo> OnContextActionClicked
SlotView = _selectedSlot
});

public HorrorCraftView CraftView => _craftView;

public void Initialize()
{
_inputService = GameServiceManager.Resolve<IInputSystemService>();
Expand Down Expand Up @@ -76,8 +78,6 @@ public void Initialize()
public void NextTab() => _tabGroup.NextTab();
public void PreviousTab() => _tabGroup.PreviousTab();

public bool IsProcessing() => IsSubmenuOpen() || IsCrafting();

#region InventorySlots

// 初期化と購読は寿命中 1 回のみ。再実行すると Disposables に購読が重複登録されるため、
Expand Down Expand Up @@ -191,9 +191,6 @@ private void BindKeyItems()

#region Craft

/// <summary>クラフトの長押しが進行中か(ダイアログを閉じる・タブを切り替える入力の抑止に使う)。</summary>
public bool IsCrafting() => _craftView != null && _craftView.IsCrafting;

private void BindCraft()
{
if (_craftView == null) return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ public class HorrorCraftRecipeView : MonoBehaviour, IPointerDownHandler, IPointe
/// <summary>この行の上でポインタが押されているか(長押し判定用)。</summary>
public bool IsPointerHeld { get; private set; }

/// <summary>この行の上でポインタが押された瞬間の通知(長押し開始のトリガー用)。</summary>
private readonly Subject<Unit> _pointerPressed = new();
public Observable<Unit> OnPointerPressed => _pointerPressed;

private ILocalizationService _localizationService;
private IHorrorIconService _iconService;

Expand Down Expand Up @@ -106,6 +110,7 @@ public void OnPointerDown(PointerEventData eventData)
{
if (eventData.button != PointerEventData.InputButton.Left) return;
IsPointerHeld = true;
_pointerPressed.OnNext(Unit.Default);
}

public void OnPointerUp(PointerEventData eventData)
Expand All @@ -116,5 +121,7 @@ public void OnPointerUp(PointerEventData eventData)

// タブ切替などで非表示になると PointerUp が届かないため、押下状態を持ち越さない
private void OnDisable() => IsPointerHeld = false;

private void OnDestroy() => _pointerPressed.Dispose();
}
}
Loading
Loading