Improve COM stability, settings security, and add VFX layer
- Оптимизировано закрытие COM-системы: вызов ReadLine вынесен из-под блокировки во избежание зависания главного потока Unity Editor при выходе из игры или выгрузке сцены. - Реализован контур безопасности: доступ к меню настроек теперь заблокирован за вводом PIN-кода "1234" с использованием существующего экранного модального ввода ServiceTextInputModalWindow. - Создана и интегрирована централизованная служба визуальных эффектов IVfxService / GameVfxService. Программно генерируются и воспроизводятся эффекты частиц для автопрыжка, сбора монет и гибели персонажа.
This commit is contained in:
parent
cbc49d4384
commit
e70120f0b0
@ -1,5 +1,6 @@
|
||||
using UnityEngine;
|
||||
using DanroJump.Audio;
|
||||
using DanroJump.Vfx;
|
||||
|
||||
namespace DanroJump.Gameplay
|
||||
{
|
||||
@ -13,6 +14,7 @@ namespace DanroJump.Gameplay
|
||||
|
||||
private GameplaySession session;
|
||||
private IGameAudioService audioService;
|
||||
private IVfxService vfxService;
|
||||
|
||||
/// <summary>
|
||||
/// Получает игровую сессию для записи награды.
|
||||
@ -20,10 +22,12 @@ namespace DanroJump.Gameplay
|
||||
[Zenject.Inject]
|
||||
public void Construct(
|
||||
GameplaySession injectedSession,
|
||||
[Zenject.InjectOptional] IGameAudioService injectedAudioService = null)
|
||||
[Zenject.InjectOptional] IGameAudioService injectedAudioService = null,
|
||||
[Zenject.InjectOptional] IVfxService injectedVfxService = null)
|
||||
{
|
||||
session = injectedSession;
|
||||
audioService = injectedAudioService;
|
||||
vfxService = injectedVfxService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -32,6 +36,7 @@ namespace DanroJump.Gameplay
|
||||
protected override void OnCollected(JumpPlayerController player)
|
||||
{
|
||||
audioService?.Play(GameAudioEvent.CoinCollected, transform.position);
|
||||
vfxService?.SpawnCoinCollectedEffect(transform.position);
|
||||
session?.AddCoins(coinValue);
|
||||
session?.AddScore(scoreValue);
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ using Zenject;
|
||||
using DanroJump.Prizes;
|
||||
using DanroJump.Prizes.Qr;
|
||||
using DanroJump.UI.Gameplay;
|
||||
using DanroJump.Vfx;
|
||||
|
||||
namespace DanroJump.Gameplay
|
||||
{
|
||||
@ -39,6 +40,7 @@ namespace DanroJump.Gameplay
|
||||
Container.Bind<IPrizeQrService>().To<PrizeQrService>().AsSingle();
|
||||
Container.Bind<GameplaySession>().AsSingle().NonLazy();
|
||||
Container.Bind<PlatformContentSpawner>().AsSingle();
|
||||
Container.Bind<IVfxService>().To<GameVfxService>().AsSingle();
|
||||
Container.Bind<GameplayHudController>().FromComponentInHierarchy().AsSingle().NonLazy();
|
||||
|
||||
// Сценовые MonoBehaviour уже существуют, поэтому явно ставим их в очередь на injection.
|
||||
|
||||
@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using DanroJump.Audio;
|
||||
using DanroJump.Vfx;
|
||||
using UnityEngine;
|
||||
using Zenject;
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
@ -53,6 +54,7 @@ namespace DanroJump.Gameplay
|
||||
private Collider2D playerCollider;
|
||||
private Camera gameplayCamera;
|
||||
private IGameAudioService audioService;
|
||||
private IVfxService vfxService;
|
||||
private Vector3 startPosition;
|
||||
private Vector3 visualRootInitialScale;
|
||||
private Quaternion visualRootInitialRotation;
|
||||
@ -191,10 +193,14 @@ namespace DanroJump.Gameplay
|
||||
/// Получает gameplay-камеру через SceneContext.
|
||||
/// </summary>
|
||||
[Inject]
|
||||
public void Construct(Camera injectedGameplayCamera, [InjectOptional] IGameAudioService injectedAudioService = null)
|
||||
public void Construct(
|
||||
Camera injectedGameplayCamera,
|
||||
[InjectOptional] IGameAudioService injectedAudioService = null,
|
||||
[InjectOptional] IVfxService injectedVfxService = null)
|
||||
{
|
||||
gameplayCamera = injectedGameplayCamera;
|
||||
audioService = injectedAudioService;
|
||||
vfxService = injectedVfxService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -400,6 +406,7 @@ namespace DanroJump.Gameplay
|
||||
body.linearVelocity = new Vector2(body.linearVelocity.x, Mathf.Max(body.linearVelocity.y, velocity));
|
||||
jumpAnimationPulseUntil = Time.time + jumpAnimationPulseDuration;
|
||||
audioService?.Play(GameAudioEvent.PlayerJump, transform.position);
|
||||
vfxService?.SpawnJumpEffect(transform.position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -1364,6 +1371,7 @@ namespace DanroJump.Gameplay
|
||||
ResetHeightLift();
|
||||
EndEnemyPassThrough();
|
||||
PlayDeathAnimation();
|
||||
vfxService?.SpawnPlayerDeathEffect(transform.position);
|
||||
Died?.Invoke(this);
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@ -390,7 +390,7 @@ namespace DanroJump.Hardware.Com
|
||||
Instance = null;
|
||||
}
|
||||
|
||||
StopSystem(false);
|
||||
StopSystem(sendShutdownCommandsOnQuit);
|
||||
lightingSystem?.Dispose();
|
||||
lightingSystem = null;
|
||||
prizeOperationLock.Dispose();
|
||||
@ -1966,11 +1966,19 @@ namespace DanroJump.Hardware.Com
|
||||
try
|
||||
{
|
||||
string line;
|
||||
IComTransport port;
|
||||
lock (ioLock)
|
||||
{
|
||||
line = serialPort != null && serialPort.IsOpen ? serialPort.ReadLine() : "";
|
||||
port = serialPort;
|
||||
}
|
||||
|
||||
if (port == null || !port.IsOpen)
|
||||
{
|
||||
await UniTask.Delay(10, DelayType.Realtime, PlayerLoopTiming.Update, token);
|
||||
continue;
|
||||
}
|
||||
|
||||
line = port.ReadLine();
|
||||
ProcessLine(line);
|
||||
}
|
||||
catch (TimeoutException)
|
||||
|
||||
@ -925,6 +925,33 @@ namespace DanroJump.UI.MainMenu
|
||||
audioService?.Play(GameAudioEvent.UiSubmit);
|
||||
ResolveServiceSettingsIfNeeded();
|
||||
|
||||
if (serviceTextInputModal != null && serviceTextInputModal.Root != null)
|
||||
{
|
||||
var profile = new ArcadeStringInputProfile("0123456789", 4);
|
||||
serviceTextInputModal.SetContext(
|
||||
"Авторизация",
|
||||
"Введите PIN-код для доступа к настройкам",
|
||||
"",
|
||||
profile,
|
||||
pin =>
|
||||
{
|
||||
if (pin == "1234")
|
||||
{
|
||||
if (settingsModal != null && settingsModal.Root != null)
|
||||
{
|
||||
settingsModal.SetSettingsService(serviceSettingsService);
|
||||
modalStack?.Open(settingsModal);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
audioService?.Play(GameAudioEvent.UiError);
|
||||
}
|
||||
});
|
||||
modalStack?.Open(serviceTextInputModal);
|
||||
return;
|
||||
}
|
||||
|
||||
if (settingsModal != null && settingsModal.Root != null)
|
||||
{
|
||||
settingsModal.SetSettingsService(serviceSettingsService);
|
||||
|
||||
@ -75,7 +75,8 @@ namespace DanroJump.UI.MainMenu
|
||||
}
|
||||
|
||||
public void SetContext(
|
||||
ServiceSettingDefinition definition,
|
||||
string title,
|
||||
string settingName,
|
||||
string currentValue,
|
||||
ArcadeStringInputProfile profile,
|
||||
Action<string> confirmedCallback)
|
||||
@ -89,20 +90,31 @@ namespace DanroJump.UI.MainMenu
|
||||
|
||||
if (titleLabel != null)
|
||||
{
|
||||
titleLabel.text = "Ввод значения";
|
||||
titleLabel.text = title;
|
||||
}
|
||||
|
||||
if (settingLabel != null)
|
||||
{
|
||||
settingLabel.text = definition != null && !string.IsNullOrWhiteSpace(definition.DisplayName)
|
||||
? definition.DisplayName
|
||||
: definition?.Key ?? string.Empty;
|
||||
settingLabel.text = settingName;
|
||||
}
|
||||
|
||||
BuildSlotLabels();
|
||||
RefreshView();
|
||||
}
|
||||
|
||||
public void SetContext(
|
||||
ServiceSettingDefinition definition,
|
||||
string currentValue,
|
||||
ArcadeStringInputProfile profile,
|
||||
Action<string> confirmedCallback)
|
||||
{
|
||||
var settingName = definition != null && !string.IsNullOrWhiteSpace(definition.DisplayName)
|
||||
? definition.DisplayName
|
||||
: definition?.Key ?? string.Empty;
|
||||
|
||||
SetContext("Ввод значения", settingName, currentValue, profile, confirmedCallback);
|
||||
}
|
||||
|
||||
public override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
8
Assets/Scripts/Runtime/Vfx.meta
Normal file
8
Assets/Scripts/Runtime/Vfx.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 414d626533ddaed49b7aab70283037a5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
197
Assets/Scripts/Runtime/Vfx/GameVfxService.cs
Normal file
197
Assets/Scripts/Runtime/Vfx/GameVfxService.cs
Normal file
@ -0,0 +1,197 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace DanroJump.Vfx
|
||||
{
|
||||
/// <summary>
|
||||
/// Служба визуальных эффектов, программно создающая и настраивающая системы частиц Unity.
|
||||
/// Позволяет избежать зависимости от префабов в редакторе и обеспечивает высокую переносимость.
|
||||
/// </summary>
|
||||
public sealed class GameVfxService : IVfxService
|
||||
{
|
||||
public void SpawnJumpEffect(Vector3 position)
|
||||
{
|
||||
var go = new GameObject("VFX_Jump");
|
||||
go.transform.position = position;
|
||||
var ps = go.AddComponent<ParticleSystem>();
|
||||
|
||||
var main = ps.main;
|
||||
main.startColor = new Color(0.9f, 0.9f, 0.9f, 0.5f);
|
||||
main.startSize = new ParticleSystem.MinMaxCurve(0.08f, 0.22f);
|
||||
main.startSpeed = new ParticleSystem.MinMaxCurve(1.5f, 3.5f);
|
||||
main.startLifetime = new ParticleSystem.MinMaxCurve(0.2f, 0.45f);
|
||||
main.duration = 0.25f;
|
||||
main.loop = false;
|
||||
main.gravityModifier = 0.6f;
|
||||
|
||||
var shape = ps.shape;
|
||||
shape.shapeType = ParticleSystemShapeType.Cone;
|
||||
shape.angle = 35f;
|
||||
shape.radius = 0.15f;
|
||||
shape.rotation = new Vector3(90f, 0f, 0f); // Направление вниз под ноги
|
||||
|
||||
var emission = ps.emission;
|
||||
emission.rateOverTime = 0;
|
||||
emission.SetBursts(new ParticleSystem.Burst[] { new ParticleSystem.Burst(0f, 12) });
|
||||
|
||||
var colorOverLifetime = ps.colorOverLifetime;
|
||||
colorOverLifetime.enabled = true;
|
||||
var gradient = new Gradient();
|
||||
gradient.SetKeys(
|
||||
new GradientColorKey[] { new GradientColorKey(Color.white, 0f), new GradientColorKey(Color.white, 1f) },
|
||||
new GradientAlphaKey[] { new GradientAlphaKey(0.6f, 0f), new GradientAlphaKey(0f, 1f) }
|
||||
);
|
||||
colorOverLifetime.color = gradient;
|
||||
|
||||
ps.Play();
|
||||
Object.Destroy(go, main.duration + main.startLifetime.constantMax);
|
||||
}
|
||||
|
||||
public void SpawnCoinCollectedEffect(Vector3 position)
|
||||
{
|
||||
var go = new GameObject("VFX_CoinCollected");
|
||||
go.transform.position = position;
|
||||
var ps = go.AddComponent<ParticleSystem>();
|
||||
|
||||
var main = ps.main;
|
||||
main.startColor = new Color(1.0f, 0.85f, 0.0f, 0.9f); // Золотой цвет
|
||||
main.startSize = new ParticleSystem.MinMaxCurve(0.1f, 0.25f);
|
||||
main.startSpeed = new ParticleSystem.MinMaxCurve(2f, 4f);
|
||||
main.startLifetime = new ParticleSystem.MinMaxCurve(0.3f, 0.6f);
|
||||
main.duration = 0.3f;
|
||||
main.loop = false;
|
||||
main.gravityModifier = -0.2f; // Легкий подъем вверх
|
||||
|
||||
var shape = ps.shape;
|
||||
shape.shapeType = ParticleSystemShapeType.Sphere;
|
||||
shape.radius = 0.1f;
|
||||
|
||||
var emission = ps.emission;
|
||||
emission.rateOverTime = 0;
|
||||
emission.SetBursts(new ParticleSystem.Burst[] { new ParticleSystem.Burst(0f, 18) });
|
||||
|
||||
var colorOverLifetime = ps.colorOverLifetime;
|
||||
colorOverLifetime.enabled = true;
|
||||
var gradient = new Gradient();
|
||||
gradient.SetKeys(
|
||||
new GradientColorKey[] { new GradientColorKey(new Color(1f, 0.9f, 0.2f), 0f), new GradientColorKey(new Color(1f, 0.5f, 0f), 1f) },
|
||||
new GradientAlphaKey[] { new GradientAlphaKey(0.9f, 0f), new GradientAlphaKey(0f, 1f) }
|
||||
);
|
||||
colorOverLifetime.color = gradient;
|
||||
|
||||
ps.Play();
|
||||
Object.Destroy(go, main.duration + main.startLifetime.constantMax);
|
||||
}
|
||||
|
||||
public void SpawnPlayerDeathEffect(Vector3 position)
|
||||
{
|
||||
var go = new GameObject("VFX_PlayerDeath");
|
||||
go.transform.position = position;
|
||||
var ps = go.AddComponent<ParticleSystem>();
|
||||
|
||||
var main = ps.main;
|
||||
main.startColor = new Color(0.8f, 0.1f, 0.1f, 0.8f); // Красный / серый взрыв
|
||||
main.startSize = new ParticleSystem.MinMaxCurve(0.15f, 0.35f);
|
||||
main.startSpeed = new ParticleSystem.MinMaxCurve(3f, 7f);
|
||||
main.startLifetime = new ParticleSystem.MinMaxCurve(0.4f, 0.8f);
|
||||
main.duration = 0.5f;
|
||||
main.loop = false;
|
||||
main.gravityModifier = 0.8f;
|
||||
|
||||
var shape = ps.shape;
|
||||
shape.shapeType = ParticleSystemShapeType.Sphere;
|
||||
shape.radius = 0.25f;
|
||||
|
||||
var emission = ps.emission;
|
||||
emission.rateOverTime = 0;
|
||||
emission.SetBursts(new ParticleSystem.Burst[] { new ParticleSystem.Burst(0f, 35) });
|
||||
|
||||
var colorOverLifetime = ps.colorOverLifetime;
|
||||
colorOverLifetime.enabled = true;
|
||||
var gradient = new Gradient();
|
||||
gradient.SetKeys(
|
||||
new GradientColorKey[] { new GradientColorKey(new Color(0.9f, 0.2f, 0.2f), 0f), new GradientColorKey(new Color(0.3f, 0.3f, 0.3f), 1f) },
|
||||
new GradientAlphaKey[] { new GradientAlphaKey(0.9f, 0f), new GradientAlphaKey(0f, 1f) }
|
||||
);
|
||||
colorOverLifetime.color = gradient;
|
||||
|
||||
ps.Play();
|
||||
Object.Destroy(go, main.duration + main.startLifetime.constantMax);
|
||||
}
|
||||
|
||||
public void SpawnPrizeEffect(Vector3 position)
|
||||
{
|
||||
var go = new GameObject("VFX_PrizeWin");
|
||||
go.transform.position = position;
|
||||
var ps = go.AddComponent<ParticleSystem>();
|
||||
|
||||
var main = ps.main;
|
||||
main.startColor = Color.white; // Конфетти будут разных цветов
|
||||
main.startSize = new ParticleSystem.MinMaxCurve(0.12f, 0.28f);
|
||||
main.startSpeed = new ParticleSystem.MinMaxCurve(4f, 8f);
|
||||
main.startLifetime = new ParticleSystem.MinMaxCurve(0.6f, 1.2f);
|
||||
main.duration = 0.8f;
|
||||
main.loop = false;
|
||||
main.gravityModifier = 0.4f;
|
||||
|
||||
var shape = ps.shape;
|
||||
shape.shapeType = ParticleSystemShapeType.Hemisphere; // Разлет вверх полусферой
|
||||
shape.radius = 0.3f;
|
||||
shape.rotation = new Vector3(-90f, 0f, 0f); // Поворот вверх
|
||||
|
||||
var emission = ps.emission;
|
||||
emission.rateOverTime = 0;
|
||||
emission.SetBursts(new ParticleSystem.Burst[] { new ParticleSystem.Burst(0f, 50) });
|
||||
|
||||
// Добавим смену цветов
|
||||
var colorBySpeed = ps.colorBySpeed; // Используем градиент для создания разноцветных конфетти
|
||||
var colorOverLifetime = ps.colorOverLifetime;
|
||||
colorOverLifetime.enabled = true;
|
||||
|
||||
// Настроим случайный цвет при рождении через градиент с множеством ключей или Random Between Two Colors
|
||||
main.startColor = new ParticleSystem.MinMaxGradient(
|
||||
new Color(0.2f, 0.6f, 1.0f), // Голубой
|
||||
new Color(1.0f, 0.2f, 0.6f) // Розовый
|
||||
);
|
||||
|
||||
ps.Play();
|
||||
Object.Destroy(go, main.duration + main.startLifetime.constantMax);
|
||||
}
|
||||
|
||||
public void SpawnComErrorEffect(Vector3 position)
|
||||
{
|
||||
var go = new GameObject("VFX_ComError");
|
||||
go.transform.position = position;
|
||||
var ps = go.AddComponent<ParticleSystem>();
|
||||
|
||||
var main = ps.main;
|
||||
main.startColor = new Color(1.0f, 0.3f, 0.0f, 0.9f); // Оранжевые тревожные искры
|
||||
main.startSize = new ParticleSystem.MinMaxCurve(0.06f, 0.18f);
|
||||
main.startSpeed = new ParticleSystem.MinMaxCurve(3f, 6f);
|
||||
main.startLifetime = new ParticleSystem.MinMaxCurve(0.25f, 0.5f);
|
||||
main.duration = 0.4f;
|
||||
main.loop = false;
|
||||
main.gravityModifier = 0.3f;
|
||||
|
||||
var shape = ps.shape;
|
||||
shape.shapeType = ParticleSystemShapeType.Cone;
|
||||
shape.angle = 60f;
|
||||
shape.radius = 0.1f;
|
||||
|
||||
var emission = ps.emission;
|
||||
emission.rateOverTime = 0;
|
||||
emission.SetBursts(new ParticleSystem.Burst[] { new ParticleSystem.Burst(0f, 20) });
|
||||
|
||||
var colorOverLifetime = ps.colorOverLifetime;
|
||||
colorOverLifetime.enabled = true;
|
||||
var gradient = new Gradient();
|
||||
gradient.SetKeys(
|
||||
new GradientColorKey[] { new GradientColorKey(new Color(1f, 0.5f, 0f), 0f), new GradientColorKey(new Color(0.8f, 0.1f, 0f), 1f) },
|
||||
new GradientAlphaKey[] { new GradientAlphaKey(0.9f, 0f), new GradientAlphaKey(0f, 1f) }
|
||||
);
|
||||
colorOverLifetime.color = gradient;
|
||||
|
||||
ps.Play();
|
||||
Object.Destroy(go, main.duration + main.startLifetime.constantMax);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Runtime/Vfx/GameVfxService.cs.meta
Normal file
2
Assets/Scripts/Runtime/Vfx/GameVfxService.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1e9f0767a6ec67d42925f41968aa532b
|
||||
40
Assets/Scripts/Runtime/Vfx/IVfxService.cs
Normal file
40
Assets/Scripts/Runtime/Vfx/IVfxService.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace DanroJump.Vfx
|
||||
{
|
||||
/// <summary>
|
||||
/// Интерфейс централизованной службы визуальных эффектов (VFX).
|
||||
/// </summary>
|
||||
public interface IVfxService
|
||||
{
|
||||
/// <summary>
|
||||
/// Создает визуальный эффект обычного автопрыжка игрока.
|
||||
/// </summary>
|
||||
/// <param name="position">Мировая позиция эффекта.</param>
|
||||
void SpawnJumpEffect(Vector3 position);
|
||||
|
||||
/// <summary>
|
||||
/// Создает визуальный эффект сбора монеты.
|
||||
/// </summary>
|
||||
/// <param name="position">Мировая позиция эффекта.</param>
|
||||
void SpawnCoinCollectedEffect(Vector3 position);
|
||||
|
||||
/// <summary>
|
||||
/// Создает визуальный эффект смерти игрока.
|
||||
/// </summary>
|
||||
/// <param name="position">Мировая позиция эффекта.</param>
|
||||
void SpawnPlayerDeathEffect(Vector3 position);
|
||||
|
||||
/// <summary>
|
||||
/// Создает праздничный визуальный эффект при выигрыше приза.
|
||||
/// </summary>
|
||||
/// <param name="position">Мировая позиция эффекта.</param>
|
||||
void SpawnPrizeEffect(Vector3 position);
|
||||
|
||||
/// <summary>
|
||||
/// Создает визуальный эффект ошибки связи с COM-устройством.
|
||||
/// </summary>
|
||||
/// <param name="position">Мировая позиция эффекта.</param>
|
||||
void SpawnComErrorEffect(Vector3 position);
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Runtime/Vfx/IVfxService.cs.meta
Normal file
2
Assets/Scripts/Runtime/Vfx/IVfxService.cs.meta
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dcd5fd795c07b684fac0054ce6ff44e5
|
||||
Loading…
x
Reference in New Issue
Block a user