Initial commit: Unity WordConnect project
This commit is contained in:
@ -0,0 +1,131 @@
|
||||
// // ©2015 - 2025 Candy Smith
|
||||
// // All rights reserved
|
||||
// // Redistribution of this software is strictly not allowed.
|
||||
// // Copy of this software can be obtained from unity asset store only.
|
||||
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using DG.Tweening;
|
||||
|
||||
namespace WordsToolkit.Scripts.Gameplay
|
||||
{
|
||||
[RequireComponent(typeof(RectTransform))]
|
||||
public class ConnectionCircle : MonoBehaviour
|
||||
{
|
||||
[Header("Animation Settings")]
|
||||
public float pulseDuration = 0.5f;
|
||||
public float pulseScale = 1.2f;
|
||||
public float appearDuration = 0.2f;
|
||||
|
||||
private RectTransform rectTransform;
|
||||
private Image image;
|
||||
private Sequence pulseSequence;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
rectTransform = GetComponent<RectTransform>();
|
||||
image = GetComponent<Image>();
|
||||
|
||||
if (image == null)
|
||||
image = gameObject.AddComponent<Image>();
|
||||
|
||||
// Start with circle invisible
|
||||
if (image != null)
|
||||
{
|
||||
Color color = image.color;
|
||||
color.a = 0;
|
||||
image.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
public void Appear()
|
||||
{
|
||||
// Stop any running animations
|
||||
if (pulseSequence != null)
|
||||
pulseSequence.Kill();
|
||||
|
||||
// Reset scale
|
||||
rectTransform.localScale = Vector3.zero;
|
||||
|
||||
// Make sure it's visible
|
||||
gameObject.SetActive(true);
|
||||
|
||||
// Animate appear with pop effect
|
||||
rectTransform.DOScale(Vector3.one, appearDuration)
|
||||
.SetEase(Ease.OutBack);
|
||||
|
||||
// Fade in
|
||||
if (image != null)
|
||||
{
|
||||
Color color = image.color;
|
||||
Color targetColor = new Color(color.r, color.g, color.b, 1f);
|
||||
image.DOColor(targetColor, appearDuration);
|
||||
}
|
||||
|
||||
// Start pulsing
|
||||
StartPulse();
|
||||
}
|
||||
|
||||
public void Disappear()
|
||||
{
|
||||
// Stop any running animations
|
||||
if (pulseSequence != null)
|
||||
pulseSequence.Kill();
|
||||
|
||||
// Animate disappear
|
||||
rectTransform.DOScale(Vector3.zero, appearDuration)
|
||||
.SetEase(Ease.InBack)
|
||||
.OnComplete(() => gameObject.SetActive(false));
|
||||
|
||||
// Fade out
|
||||
if (image != null)
|
||||
{
|
||||
Color color = image.color;
|
||||
Color targetColor = new Color(color.r, color.g, color.b, 0f);
|
||||
image.DOColor(targetColor, appearDuration);
|
||||
}
|
||||
}
|
||||
|
||||
private void StartPulse()
|
||||
{
|
||||
// Create a gentle pulse animation
|
||||
pulseSequence = DOTween.Sequence();
|
||||
|
||||
pulseSequence.Append(rectTransform.DOScale(Vector3.one * pulseScale, pulseDuration / 2)
|
||||
.SetEase(Ease.InOutSine));
|
||||
|
||||
pulseSequence.Append(rectTransform.DOScale(Vector3.one, pulseDuration / 2)
|
||||
.SetEase(Ease.InOutSine));
|
||||
|
||||
pulseSequence.SetLoops(-1); // Loop indefinitely
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
// Kill animations when disabled
|
||||
if (pulseSequence != null)
|
||||
pulseSequence.Kill();
|
||||
}
|
||||
|
||||
// Set circle color
|
||||
public void SetColor(Color newColor)
|
||||
{
|
||||
if (image != null)
|
||||
image.color = newColor;
|
||||
}
|
||||
|
||||
// Set circle size
|
||||
public void SetSize(float size)
|
||||
{
|
||||
if (rectTransform != null)
|
||||
rectTransform.sizeDelta = new Vector2(size, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f1c903b433834a738c3164bf6597844
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,21 @@
|
||||
// // ©2015 - 2025 Candy Smith
|
||||
// // All rights reserved
|
||||
// // Redistribution of this software is strictly not allowed.
|
||||
// // Copy of this software can be obtained from unity asset store only.
|
||||
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WordsToolkit.Scripts.Gameplay
|
||||
{
|
||||
public abstract class FillAndPreview : MonoBehaviour
|
||||
{
|
||||
public abstract void FillIcon(ScriptableData iconScriptable);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5bc0cae2446540b8847b5cf5c15742c1
|
||||
timeCreated: 1746685265
|
||||
@ -0,0 +1,90 @@
|
||||
// // ©2015 - 2025 Candy Smith
|
||||
// // All rights reserved
|
||||
// // Redistribution of this software is strictly not allowed.
|
||||
// // Copy of this software can be obtained from unity asset store only.
|
||||
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.EventSystems;
|
||||
using WordsToolkit.Scripts.Enums;
|
||||
using WordsToolkit.Scripts.System;
|
||||
using WordsToolkit.Scripts.System.Haptic;
|
||||
|
||||
namespace WordsToolkit.Scripts.Gameplay
|
||||
{
|
||||
public class LetterButton : MonoBehaviour, IPointerDownHandler, IPointerEnterHandler, IPointerUpHandler
|
||||
{
|
||||
public TextMeshProUGUI letterText;
|
||||
public Image circleImage;
|
||||
|
||||
private WordSelectionManager wordSelectionManager;
|
||||
private bool isSelected = false;
|
||||
private Color color;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Get a reference to the WordSelectionManager
|
||||
wordSelectionManager = GetComponentInParent<WordSelectionManager>();
|
||||
}
|
||||
|
||||
public void OnPointerDown(PointerEventData eventData)
|
||||
{
|
||||
// Start selection process when the user taps/clicks on a letter
|
||||
if (wordSelectionManager != null && EventManager.GameStatus == EGameState.Playing)
|
||||
{
|
||||
wordSelectionManager.StartSelection(this);
|
||||
SetSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPointerEnter(PointerEventData eventData)
|
||||
{
|
||||
// When dragging over this letter, add it to the selection
|
||||
if (wordSelectionManager != null && wordSelectionManager.IsSelecting && EventManager.GameStatus == EGameState.Playing)
|
||||
{
|
||||
wordSelectionManager.AddToSelection(this);
|
||||
SetSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPointerUp(PointerEventData eventData)
|
||||
{
|
||||
// End selection process when the user lifts finger/mouse
|
||||
if (wordSelectionManager != null && EventManager.GameStatus == EGameState.Playing)
|
||||
{
|
||||
wordSelectionManager.EndSelection();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSelected(bool selected)
|
||||
{
|
||||
HapticFeedback.TriggerHapticFeedback(HapticFeedback.HapticForce.Light);
|
||||
isSelected = selected;
|
||||
circleImage.color = new Color(color.r, color.g, color.b, selected ? 1f : 0);
|
||||
letterText.color = selected ? Color.white : Color.black;
|
||||
}
|
||||
|
||||
public string GetLetter()
|
||||
{
|
||||
return letterText.text;
|
||||
}
|
||||
|
||||
public void SetText(string toString)
|
||||
{
|
||||
letterText.text = toString.ToUpper();
|
||||
}
|
||||
|
||||
public void SetColor(Color color)
|
||||
{
|
||||
this.color = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 01a0c10078fb4995bb8ca1b753a8a62e
|
||||
timeCreated: 1741690665
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 201b442989dd4b90bdfe248deda29441
|
||||
timeCreated: 1730996862
|
||||
@ -0,0 +1,36 @@
|
||||
// // ©2015 - 2025 Candy Smith
|
||||
// // All rights reserved
|
||||
// // Redistribution of this software is strictly not allowed.
|
||||
// // Copy of this software can be obtained from unity asset store only.
|
||||
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
using WordsToolkit.Scripts.Levels;
|
||||
using WordsToolkit.Scripts.System;
|
||||
|
||||
namespace WordsToolkit.Scripts.Gameplay.Managers
|
||||
{
|
||||
public class DefaultGameStateManager : IGameStateManager
|
||||
{
|
||||
private readonly GameManager gameManager;
|
||||
private readonly Level levelData;
|
||||
|
||||
public DefaultGameStateManager(GameManager gameManager, Level levelData)
|
||||
{
|
||||
this.gameManager = gameManager;
|
||||
this.levelData = levelData;
|
||||
}
|
||||
|
||||
public string CurrentLanguage => gameManager?.language;
|
||||
|
||||
public string[] GetLevelWords()
|
||||
{
|
||||
return levelData?.GetWords(CurrentLanguage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 51726f82dad7405dbff26d0cd8974025
|
||||
timeCreated: 1745821654
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce27424fabf24a21a7c79ef4b949a233
|
||||
timeCreated: 1727533261
|
||||
@ -0,0 +1,20 @@
|
||||
// // ©2015 - 2025 Candy Smith
|
||||
// // All rights reserved
|
||||
// // Redistribution of this software is strictly not allowed.
|
||||
// // Copy of this software can be obtained from unity asset store only.
|
||||
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
namespace WordsToolkit.Scripts.Gameplay.Managers
|
||||
{
|
||||
public interface IGameStateManager
|
||||
{
|
||||
string CurrentLanguage { get; }
|
||||
string[] GetLevelWords();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 549c3179f3434c0b818fa99432205a43
|
||||
timeCreated: 1745821637
|
||||
@ -0,0 +1,105 @@
|
||||
// // ©2015 - 2025 Candy Smith
|
||||
// // All rights reserved
|
||||
// // Redistribution of this software is strictly not allowed.
|
||||
// // Copy of this software can be obtained from unity asset store only.
|
||||
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
using UnityEngine;
|
||||
using WordsToolkit.Scripts.Enums;
|
||||
using WordsToolkit.Scripts.Popups;
|
||||
using WordsToolkit.Scripts.System;
|
||||
using DG.Tweening;
|
||||
using VContainer;
|
||||
using WordsToolkit.Scripts.Levels;
|
||||
|
||||
namespace WordsToolkit.Scripts.Gameplay.Managers
|
||||
{
|
||||
public partial class LevelManager
|
||||
{
|
||||
[Inject]
|
||||
private MenuManager menuManager;
|
||||
|
||||
private void HandleGameStateChange(EGameState newState)
|
||||
{
|
||||
switch (newState)
|
||||
{
|
||||
case EGameState.PrepareGame:
|
||||
EventManager.GameStatus = EGameState.Playing;
|
||||
break;
|
||||
case EGameState.Playing:
|
||||
if (HasTimer)
|
||||
{
|
||||
StartTimer();
|
||||
}
|
||||
break;
|
||||
|
||||
case EGameState.PreWin:
|
||||
DOVirtual.DelayedCall(0.5f, () =>
|
||||
{
|
||||
ShowPreWinPopup();
|
||||
});
|
||||
break;
|
||||
case EGameState.Win:
|
||||
if (_levelData.GroupIsFinished())
|
||||
ShowMenuFinished();
|
||||
else
|
||||
ShowMenuPlay();
|
||||
break;
|
||||
case EGameState.PreFailed:
|
||||
menuManager.ShowPopup<PreFailed>(null, PrefailedResult);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowMenuFinished()
|
||||
{
|
||||
menuManager.ShowPopup<Finish>(null, _=>ShowMenuPlay());
|
||||
}
|
||||
|
||||
private void PrefailedResult(EPopupResult obj)
|
||||
{
|
||||
if (obj == EPopupResult.Continue)
|
||||
{
|
||||
TimerLimit += gameSettings.continueTime;
|
||||
EventManager.GameStatus = EGameState.Playing;
|
||||
}
|
||||
else if(obj == EPopupResult.Cancel)
|
||||
{
|
||||
sceneLoader.GoMain();
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowPreWinPopup()
|
||||
{
|
||||
var p = menuManager.ShowPopup<PreWin>(null, _ =>
|
||||
{
|
||||
PanelWinAnimation();
|
||||
DOVirtual.DelayedCall(1f, () =>
|
||||
{
|
||||
EventManager.GameStatus = EGameState.Win;
|
||||
});
|
||||
});
|
||||
p.transform.position = bubbleAnchor.position;
|
||||
}
|
||||
|
||||
private void ShowMenuPlay()
|
||||
{
|
||||
if (Resources.LoadAll<Level>("Levels").Length <= _levelData.number)
|
||||
{
|
||||
menuManager.ShowPopup<ComingSoon>();
|
||||
}
|
||||
else
|
||||
{
|
||||
menuManager.ShowPopup<MenuPlay>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4994490efe84783a1374aaa5135c3c4
|
||||
timeCreated: 1729094280
|
||||
@ -0,0 +1,390 @@
|
||||
// // ©2015 - 2025 Candy Smith
|
||||
// // All rights reserved
|
||||
// // Redistribution of this software is strictly not allowed.
|
||||
// // Copy of this software can be obtained from unity asset store only.
|
||||
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.Serialization;
|
||||
using WordsToolkit.Scripts.Enums;
|
||||
using WordsToolkit.Scripts.GUI;
|
||||
using WordsToolkit.Scripts.Levels;
|
||||
using WordsToolkit.Scripts.System;
|
||||
using Object = UnityEngine.Object;
|
||||
using VContainer;
|
||||
using WordsToolkit.Scripts.GUI.Buttons;
|
||||
using WordsToolkit.Scripts.Infrastructure.Service;
|
||||
using WordsToolkit.Scripts.NLP;
|
||||
using WordsToolkit.Scripts.Popups;
|
||||
using WordsToolkit.Scripts.Settings;
|
||||
|
||||
namespace WordsToolkit.Scripts.Gameplay.Managers
|
||||
{
|
||||
public partial class LevelManager : MonoBehaviour
|
||||
{
|
||||
public int currentLevel;
|
||||
private Level _levelData;
|
||||
private FieldManager field;
|
||||
public UnityEvent<Level> OnLevelLoaded;
|
||||
protected float gameTimer = 0f;
|
||||
private bool isTimerRunning = false;
|
||||
// Dictionary to store special items in the level
|
||||
private Dictionary<Vector2Int, GameObject> specialItems = new Dictionary<Vector2Int, GameObject>();
|
||||
|
||||
// Event that fires when a special item is collected
|
||||
public UnityEvent<Vector2Int> OnSpecialItemCollected = new UnityEvent<Vector2Int>();
|
||||
|
||||
[SerializeField]
|
||||
private Transform giftButton;
|
||||
|
||||
public bool hammerMode;
|
||||
|
||||
public float GameTime { get => gameTimer; private set => gameTimer = value; }
|
||||
public bool HasTimer { get; private set; }
|
||||
|
||||
// Timer limit in seconds, -1 means unlimited time
|
||||
public float TimerLimit { get; private set; } = -1f;
|
||||
|
||||
// Event that fires when the timer runs out
|
||||
public UnityEvent OnTimerExpired = new UnityEvent();
|
||||
private StateManager stateManager;
|
||||
private SceneLoader sceneLoader;
|
||||
private GameManager gameManager;
|
||||
private DebugSettings debugSettings;
|
||||
private ILevelLoaderService levelLoaderService;
|
||||
private ButtonViewController buttonController;
|
||||
private GameSettings gameSettings;
|
||||
[SerializeField]
|
||||
private Transform bubbleAnchor;
|
||||
|
||||
private ICustomWordRepository customWordRepository;
|
||||
|
||||
// Debug panel for web demo
|
||||
private bool showDebugPanel = true;
|
||||
private bool isDebugPanelExpanded = false;
|
||||
private Vector2 scrollPosition = Vector2.zero;
|
||||
|
||||
[Inject]
|
||||
public void Construct(FieldManager fieldManager, StateManager stateManager,
|
||||
SceneLoader sceneLoader, GameManager gameManager, DebugSettings debugSettings, ILevelLoaderService levelLoaderService, ButtonViewController buttonController, GameSettings gameSettings, ICustomWordRepository customWordRepository)
|
||||
{
|
||||
this.debugSettings = debugSettings;
|
||||
this.gameManager = gameManager;
|
||||
this.field = fieldManager;
|
||||
this.stateManager = stateManager;
|
||||
this.sceneLoader = sceneLoader;
|
||||
this.levelLoaderService = levelLoaderService;
|
||||
this.buttonController = buttonController;
|
||||
this.gameSettings = gameSettings;
|
||||
this.customWordRepository = customWordRepository;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
EventManager.GameStatus = EGameState.PrepareGame;
|
||||
EventManager.GetEvent(EGameEvent.RestartLevel).Subscribe(RestartLevel);
|
||||
EventManager.OnGameStateChanged += HandleGameStateChange;
|
||||
|
||||
if (field != null)
|
||||
{
|
||||
field.OnAllTilesOpened.AddListener(HandleAllTilesOpened);
|
||||
field.OnAllRequiredWordsFound.AddListener(HandleAllRequiredWordsFound);
|
||||
}
|
||||
|
||||
Load();
|
||||
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
EventManager.GetEvent(EGameEvent.RestartLevel).Unsubscribe(RestartLevel);
|
||||
EventManager.OnGameStateChanged -= HandleGameStateChange;
|
||||
|
||||
if (field != null)
|
||||
{
|
||||
field.OnAllTilesOpened.RemoveListener(HandleAllTilesOpened);
|
||||
field.OnAllRequiredWordsFound.RemoveListener(HandleAllRequiredWordsFound);
|
||||
}
|
||||
|
||||
if (stateManager != null)
|
||||
{
|
||||
stateManager.OnStateChanged.RemoveListener(HandleStateChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleAllTilesOpened()
|
||||
{
|
||||
SetWin();
|
||||
}
|
||||
|
||||
private void RestartLevel()
|
||||
{
|
||||
GameDataManager.SetLevel(_levelData);
|
||||
sceneLoader.StartGameScene();
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
// check the level is loaded
|
||||
if (EventManager.GameStatus == EGameState.Playing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
field.Clear();
|
||||
// currentLevel = GameDataManager.GetLevelNum();
|
||||
_levelData = GameDataManager.GetLevel();
|
||||
currentLevel = _levelData.number;
|
||||
if (_levelData == null)
|
||||
{
|
||||
// Try to find previous level
|
||||
int previousLevel = currentLevel - 1;
|
||||
while (previousLevel > 0)
|
||||
{
|
||||
GameDataManager.SetLevelNum(previousLevel);
|
||||
_levelData = GameDataManager.GetLevel();
|
||||
if (_levelData != null)
|
||||
{
|
||||
currentLevel = previousLevel;
|
||||
break;
|
||||
}
|
||||
previousLevel--;
|
||||
}
|
||||
|
||||
// If still null after trying previous levels
|
||||
if (_levelData == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear special items collection when loading a new level
|
||||
ClearSpecialItems();
|
||||
|
||||
levelLoaderService.NotifyBeforeLevelLoaded(_levelData);
|
||||
LoadLevel(_levelData);
|
||||
Invoke(nameof(StartGame), 0.5f);
|
||||
}
|
||||
|
||||
private void StartGame()
|
||||
{
|
||||
buttonController.ShowButtons();
|
||||
levelLoaderService.NotifyLevelLoaded(_levelData);
|
||||
EventManager.GameStatus = EGameState.Playing;
|
||||
EventManager.GetEvent<Level>(EGameEvent.Play).Invoke(_levelData);
|
||||
if (stateManager != null)
|
||||
{
|
||||
stateManager.OnStateChanged.RemoveListener(HandleStateChanged);
|
||||
stateManager.OnStateChanged.AddListener(HandleStateChanged);
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadLevel(Level levelData)
|
||||
{
|
||||
// Get the current language setting
|
||||
string language = gameManager.language;
|
||||
|
||||
// Check if level data contains saved crossword for this language
|
||||
var languageData = levelData.GetLanguageData(language);
|
||||
|
||||
// Generate the field with level data
|
||||
// Use the new specialItems list instead of filtering placements
|
||||
var specialItems = languageData?.crosswordData?.specialItems ?? new List<SerializableSpecialItem>();
|
||||
field.GenerateWithSpecialItems(levelData, language, specialItems);
|
||||
|
||||
|
||||
// Initialize timer settings from level data
|
||||
HasTimer = levelData.enableTimer;
|
||||
TimerLimit = levelData.enableTimer ? levelData.timerDuration : -1f;
|
||||
|
||||
// Reset timer for new level
|
||||
ResetTimer();
|
||||
}
|
||||
|
||||
public void SetWin()
|
||||
{
|
||||
customWordRepository.ClearExtraWords();
|
||||
GameDataManager.UnlockLevel(currentLevel + 1);
|
||||
EventManager.GameStatus = EGameState.PreWin;
|
||||
}
|
||||
|
||||
private void PanelWinAnimation()
|
||||
{
|
||||
buttonController.HideAllForWin();
|
||||
}
|
||||
|
||||
private void SetLose()
|
||||
{
|
||||
EventManager.GameStatus = EGameState.PreFailed;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Keyboard.current != null)
|
||||
{
|
||||
if (Keyboard.current[debugSettings.Win].wasPressedThisFrame)
|
||||
{
|
||||
SetWin();
|
||||
}
|
||||
|
||||
if (Keyboard.current[debugSettings.Lose].wasPressedThisFrame)
|
||||
{
|
||||
SetLose();
|
||||
}
|
||||
|
||||
if (Keyboard.current[debugSettings.Restart].wasPressedThisFrame)
|
||||
{
|
||||
gameManager.RestartLevel();
|
||||
}
|
||||
|
||||
#if UNITY_WEBGL && !UNITY_EDITOR
|
||||
// Quick win shortcut for web demo testing
|
||||
if (Keyboard.current[Key.W].wasPressedThisFrame)
|
||||
{
|
||||
SetWin();
|
||||
}
|
||||
|
||||
// Toggle debug panel with T key
|
||||
if (Keyboard.current[Key.T].wasPressedThisFrame)
|
||||
{
|
||||
showDebugPanel = !showDebugPanel;
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
// Update timer if it's running
|
||||
if (isTimerRunning && HasTimer && !menuManager.IsAnyPopupOpened())
|
||||
{
|
||||
// Timer now decreases instead of increases
|
||||
gameTimer += Time.deltaTime;
|
||||
|
||||
// Check if timer has expired (if there's a limit)
|
||||
if (TimerLimit > 0 && gameTimer >= TimerLimit)
|
||||
{
|
||||
TimerExpired();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void StartTimer()
|
||||
{
|
||||
isTimerRunning = true;
|
||||
}
|
||||
|
||||
private void StopTimer()
|
||||
{
|
||||
isTimerRunning = false;
|
||||
}
|
||||
|
||||
private void ResetTimer()
|
||||
{
|
||||
gameTimer = 0f;
|
||||
isTimerRunning = false;
|
||||
}
|
||||
|
||||
private void TimerExpired()
|
||||
{
|
||||
// Stop the timer
|
||||
StopTimer();
|
||||
|
||||
// Notify listeners that the timer has expired
|
||||
OnTimerExpired.Invoke();
|
||||
|
||||
// Just check if all tiles are opened
|
||||
if (field != null && field.AreAllTilesOpen())
|
||||
{
|
||||
SetWin();
|
||||
return;
|
||||
}
|
||||
|
||||
// If not all tiles are opened, player loses
|
||||
SetLose();
|
||||
}
|
||||
|
||||
public Level GetCurrentLevel()
|
||||
{
|
||||
return _levelData;
|
||||
}
|
||||
|
||||
public string GetCurrentLanguage()
|
||||
{
|
||||
return gameManager?.language ?? "en";
|
||||
}
|
||||
|
||||
private void HandleAllRequiredWordsFound()
|
||||
{
|
||||
EventManager.GameStatus = EGameState.PreWin;
|
||||
}
|
||||
// Register a special item instance with its position
|
||||
public void RegisterSpecialItem(Vector2Int position, GameObject itemInstance)
|
||||
{
|
||||
if (itemInstance == null)
|
||||
return;
|
||||
|
||||
specialItems[position] = itemInstance;
|
||||
}
|
||||
|
||||
// Collect a special item at the given position
|
||||
public bool CollectSpecialItem(Vector2Int position)
|
||||
{
|
||||
if (specialItems.TryGetValue(position, out GameObject item))
|
||||
{
|
||||
// Fire event before removing the item
|
||||
OnSpecialItemCollected.Invoke(position);
|
||||
|
||||
// Remove from dictionary and destroy the instance
|
||||
specialItems.Remove(position);
|
||||
Destroy(item);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clear all special items
|
||||
private void ClearSpecialItems()
|
||||
{
|
||||
foreach (var item in specialItems.Values)
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
Destroy(item);
|
||||
}
|
||||
}
|
||||
|
||||
specialItems.Clear();
|
||||
}
|
||||
|
||||
// Keep the SerializableStringArray class as it might be used elsewhere or for future serialization
|
||||
[Serializable]
|
||||
private class SerializableStringArray
|
||||
{
|
||||
public string[] words;
|
||||
}
|
||||
|
||||
public Vector3 GetSpecialItemCollectionPoint()
|
||||
{
|
||||
return this.giftButton.transform.position;
|
||||
}
|
||||
|
||||
private void HandleStateChanged(EScreenStates newState)
|
||||
{
|
||||
if (newState == EScreenStates.Game)
|
||||
{
|
||||
Load();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e40a00e9a36a45f0bbc7a894cfab08c1
|
||||
timeCreated: 1727346788
|
||||
@ -0,0 +1,69 @@
|
||||
// // ©2015 - 2025 Candy Smith
|
||||
// // All rights reserved
|
||||
// // Redistribution of this software is strictly not allowed.
|
||||
// // Copy of this software can be obtained from unity asset store only.
|
||||
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
using UnityEngine;
|
||||
using WordsToolkit.Scripts.System;
|
||||
using VContainer;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace WordsToolkit.Scripts.Gameplay.Managers
|
||||
{
|
||||
public class StateManager : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private GameObject[] mainMenus;
|
||||
|
||||
[SerializeField]
|
||||
private GameObject[] maps;
|
||||
|
||||
[SerializeField]
|
||||
private GameObject[] games;
|
||||
|
||||
private EScreenStates _currentState;
|
||||
|
||||
public UnityEvent<EScreenStates> OnStateChanged = new UnityEvent<EScreenStates>();
|
||||
|
||||
public EScreenStates CurrentState
|
||||
{
|
||||
get => _currentState;
|
||||
set
|
||||
{
|
||||
_currentState = value;
|
||||
SetActiveState(mainMenus, _currentState == EScreenStates.MainMenu);
|
||||
SetActiveState(games, _currentState == EScreenStates.Game);
|
||||
OnStateChanged?.Invoke(_currentState);
|
||||
}
|
||||
}
|
||||
|
||||
public void HideMain()
|
||||
{
|
||||
SetActiveState(mainMenus, false);
|
||||
}
|
||||
|
||||
private void SetActiveState(GameObject[] gameObjects, bool isActive)
|
||||
{
|
||||
foreach (var gameObject in gameObjects)
|
||||
{
|
||||
if (gameObject != null && gameObject.activeSelf != isActive)
|
||||
{
|
||||
gameObject.SetActive(isActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum EScreenStates
|
||||
{
|
||||
MainMenu,
|
||||
Game
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 630490a36ddd4c0581994f1efaf73255
|
||||
timeCreated: 1731134799
|
||||
@ -0,0 +1,178 @@
|
||||
// // ©2015 - 2025 Candy Smith
|
||||
// // All rights reserved
|
||||
// // Redistribution of this software is strictly not allowed.
|
||||
// // Copy of this software can be obtained from unity asset store only.
|
||||
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
using VContainer;
|
||||
using VContainer.Unity;
|
||||
using WordsToolkit.Scripts.Enums;
|
||||
using WordsToolkit.Scripts.GUI.Buttons;
|
||||
using WordsToolkit.Scripts.GUI.Tutorials;
|
||||
using WordsToolkit.Scripts.Levels;
|
||||
using WordsToolkit.Scripts.Localization;
|
||||
using WordsToolkit.Scripts.Popups;
|
||||
using WordsToolkit.Scripts.Settings;
|
||||
using WordsToolkit.Scripts.System;
|
||||
|
||||
namespace WordsToolkit.Scripts.Gameplay.Managers
|
||||
{
|
||||
public class TutorialManager : IStartable, IDisposable
|
||||
{
|
||||
private readonly TutorialSettings settings;
|
||||
private readonly MenuManager menuManager;
|
||||
private readonly ILocalizationService localizationManager;
|
||||
private readonly GameManager gameManager;
|
||||
private readonly IObjectResolver _resolver;
|
||||
private TutorialPopupBase tutorial;
|
||||
|
||||
public TutorialManager(
|
||||
TutorialSettings settings,
|
||||
MenuManager menuManager,
|
||||
ILocalizationService localizationManager,
|
||||
GameManager gameManager,
|
||||
IObjectResolver resolver)
|
||||
{
|
||||
this.settings = settings;
|
||||
this.menuManager = menuManager;
|
||||
this.localizationManager = localizationManager;
|
||||
this.gameManager = gameManager;
|
||||
this._resolver = resolver;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
EventManager.GetEvent<Level>( EGameEvent.Play).Subscribe(OnLevelLoaded);
|
||||
EventManager.GetEvent(EGameEvent.WordAnimated).Subscribe(OnWordOpened);
|
||||
EventManager.GetEvent<string>(EGameEvent.ExtraWordFound).Subscribe(ExtraWordFound);
|
||||
EventManager.GetEvent(EGameEvent.SpecialItemCollected).Subscribe(OnSpecialItemCollected);
|
||||
EventManager.GetEvent<CustomButton>( EGameEvent.ButtonClicked).Subscribe(OnCustomButtonClicked);
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
EventManager.GetEvent<Level>( EGameEvent.Play).Unsubscribe(OnLevelLoaded);
|
||||
EventManager.GetEvent(EGameEvent.WordAnimated).Unsubscribe(OnWordOpened);
|
||||
EventManager.GetEvent<string>(EGameEvent.ExtraWordFound).Unsubscribe(ExtraWordFound);
|
||||
EventManager.GetEvent(EGameEvent.SpecialItemCollected).Unsubscribe(OnSpecialItemCollected);
|
||||
EventManager.GetEvent<CustomButton>( EGameEvent.ButtonClicked).Unsubscribe(OnCustomButtonClicked);
|
||||
|
||||
if (tutorial != null)
|
||||
{
|
||||
tutorial.OnCloseAction -= OnTutorialClosed;
|
||||
tutorial = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSpecialItemCollected()
|
||||
{
|
||||
ShowTutorialPopup(t => t.showCondition.showCondition == ETutorialShowCondition.Event && t.kind == TutorialKind.GiftButton);
|
||||
}
|
||||
|
||||
private void OnCustomButtonClicked(CustomButton obj)
|
||||
{
|
||||
CloseTutorial();
|
||||
}
|
||||
|
||||
private void CloseTutorial()
|
||||
{
|
||||
if (tutorial != null)
|
||||
{
|
||||
tutorial.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void ExtraWordFound(string obj)
|
||||
{
|
||||
ShowTutorialPopup(t => t.showCondition.showCondition == ETutorialShowCondition.Event && t.kind == TutorialKind.ExtraWordsButton);
|
||||
}
|
||||
|
||||
private void OnLevelLoaded(Level obj)
|
||||
{
|
||||
DOVirtual.DelayedCall(0.2f, () => UpdateTutorialAppearance(obj), false);
|
||||
}
|
||||
|
||||
private void UpdateTutorialAppearance(Level obj)
|
||||
{
|
||||
var tutorialShown = ShowTutorialPopup(t => t.showCondition.showCondition == ETutorialShowCondition.Level && t.showCondition.level == obj.number|| t.showCondition.showCondition == ETutorialShowCondition.FirstAppearance);
|
||||
if (tutorialShown)
|
||||
return;
|
||||
var hasSpecialItem = obj.GetLanguageData(gameManager.language).crosswordData.placements.Any(i => i.isSpecialItem);
|
||||
if (hasSpecialItem)
|
||||
{
|
||||
ShowTutorialPopup(t => t.showCondition.showCondition == ETutorialShowCondition.FirstAppearance && t.kind == TutorialKind.RedGem);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWordOpened()
|
||||
{
|
||||
UpdateTutorialAppearance(GameDataManager.GetLevel());
|
||||
}
|
||||
|
||||
private bool ShowTutorialPopup(Func<TutorialSettingsData, bool> predicate)
|
||||
{
|
||||
// Only show tutorials when game is in playing state
|
||||
if (EventManager.GameStatus != EGameState.Playing)
|
||||
return false;
|
||||
|
||||
var tutorialDatas = settings.tutorialSettings.Where(predicate);
|
||||
foreach (var tutorialData in tutorialDatas)
|
||||
{
|
||||
bool notShow = false;
|
||||
foreach (var tag in tutorialData.tagsToShow)
|
||||
{
|
||||
var obj = GameObject.FindGameObjectWithTag(tag);
|
||||
if (obj == null || !obj.activeSelf || (obj.TryGetComponent(out CanvasGroup cg) && cg.alpha <= 0))
|
||||
{
|
||||
notShow = true;
|
||||
break; // If any tag is not active, do not show the tutorial
|
||||
}
|
||||
}
|
||||
if (notShow)
|
||||
continue; // Skip to the next tutorial if any tag is not active
|
||||
if (!PlayerPrefs.HasKey(tutorialData.GetID()) || PlayerPrefs.GetInt(tutorialData.GetID()) != 1)
|
||||
{
|
||||
ShowTutorial(tutorialData);
|
||||
return true; // Indicate that a tutorial was shown
|
||||
}
|
||||
}
|
||||
return false; // No tutorial was shown
|
||||
}
|
||||
|
||||
private void ShowTutorial(TutorialSettingsData tutorialData)
|
||||
{
|
||||
if (tutorialData != null)
|
||||
{
|
||||
tutorial = (TutorialPopupBase)menuManager.ShowPopup(tutorialData.popup);
|
||||
tutorial.SetData(tutorialData);
|
||||
tutorial.SetTitle(localizationManager.GetText(tutorialData.kind.ToString(), "Use this booster"));
|
||||
tutorial.OnCloseAction += OnTutorialClosed;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTutorialClosed(EPopupResult obj)
|
||||
{
|
||||
if (tutorial != null)
|
||||
{
|
||||
PlayerPrefs.SetInt(tutorial.GetData().GetID(), 1); // Mark as shown
|
||||
PlayerPrefs.Save();
|
||||
tutorial.OnCloseAction -= OnTutorialClosed;
|
||||
if (tutorial)
|
||||
{
|
||||
tutorial = null; // Clear the reference
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c9e6cfe060f541efae6bffd5b37c316
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 2000
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/WordConnectGameToolkit/Scripts/Gameplay/Pool.meta
Normal file
8
Assets/WordConnectGameToolkit/Scripts/Gameplay/Pool.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d8df1c2d30c44722a0fd2377a4cd790
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,39 @@
|
||||
// // ©2015 - 2025 Candy Smith
|
||||
// // All rights reserved
|
||||
// // Redistribution of this software is strictly not allowed.
|
||||
// // Copy of this software can be obtained from unity asset store only.
|
||||
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WordsToolkit.Scripts.Gameplay.Pool
|
||||
{
|
||||
public class AutoReturnToPool : MonoBehaviour
|
||||
{
|
||||
public float timeToReturn;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (timeToReturn > 0)
|
||||
{
|
||||
Invoke(nameof(ReturnToPool), timeToReturn);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReturnToPool()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
PoolObject.Return(gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ce269b49d1a4b688c230dc3d12140b7
|
||||
timeCreated: 1689440777
|
||||
@ -0,0 +1,33 @@
|
||||
// // ©2015 - 2025 Candy Smith
|
||||
// // All rights reserved
|
||||
// // Redistribution of this software is strictly not allowed.
|
||||
// // Copy of this software can be obtained from unity asset store only.
|
||||
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WordsToolkit.Scripts.Gameplay.Pool
|
||||
{
|
||||
internal class InitialAmountPool : PoolObject
|
||||
{
|
||||
[SerializeField]
|
||||
private int initialCapacity;
|
||||
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
for (var i = 0; i < initialCapacity; i++)
|
||||
{
|
||||
var item = Create();
|
||||
item.SetActive(false);
|
||||
pool.Enqueue(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 79a987ed03514465bd23e6269d9022d4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: -4
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,118 @@
|
||||
// // ©2015 - 2025 Candy Smith
|
||||
// // All rights reserved
|
||||
// // Redistribution of this software is strictly not allowed.
|
||||
// // Copy of this software can be obtained from unity asset store only.
|
||||
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using WordsToolkit.Scripts.System;
|
||||
|
||||
namespace WordsToolkit.Scripts.Gameplay.Pool
|
||||
{
|
||||
public class PoolObject : MonoBehaviour
|
||||
{
|
||||
protected static readonly Dictionary<string, PoolObject> pools = new();
|
||||
|
||||
public GameObject prefab;
|
||||
|
||||
protected Queue<GameObject> pool = new();
|
||||
|
||||
public virtual void Awake()
|
||||
{
|
||||
if (prefab != null)
|
||||
{
|
||||
SetPrefab(prefab);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPrefab(GameObject newPrefab)
|
||||
{
|
||||
pools[newPrefab.name] = this;
|
||||
prefab = newPrefab;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
pools.Clear();
|
||||
}
|
||||
|
||||
protected GameObject Create()
|
||||
{
|
||||
var item = Instantiate(prefab, transform);
|
||||
item.name = prefab.name;
|
||||
return item;
|
||||
}
|
||||
|
||||
private GameObject Get()
|
||||
{
|
||||
var item = pool.Count == 0 ? Create() : pool.Dequeue();
|
||||
if (item.activeSelf && pool.Count > 0)
|
||||
{
|
||||
item = pool.Dequeue();
|
||||
}
|
||||
|
||||
item.SetActive(true);
|
||||
return item;
|
||||
}
|
||||
|
||||
public static void Return(GameObject item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
item.SetActive(false);
|
||||
if (pools.TryGetValue(item.name, out var pool))
|
||||
{
|
||||
pool.pool.Enqueue(item);
|
||||
}
|
||||
}
|
||||
|
||||
public static GameObject GetObject(GameObject prefab)
|
||||
{
|
||||
return GetPool(prefab);
|
||||
}
|
||||
|
||||
public static GameObject GetObject(GameObject prefab, Vector3 position)
|
||||
{
|
||||
var item = GetPool(prefab);
|
||||
item.transform.position = position;
|
||||
return item;
|
||||
}
|
||||
|
||||
private static GameObject GetPool(GameObject prefab)
|
||||
{
|
||||
var prefabName = prefab.name;
|
||||
if (pools.TryGetValue(prefabName, out var pool))
|
||||
{
|
||||
return pool.Get();
|
||||
}
|
||||
|
||||
var poolObject = new GameObject(prefabName).AddComponent<PoolObject>();
|
||||
poolObject.transform.SetParent(GameObject.Find("FXPool").transform);
|
||||
poolObject.prefab = prefab;
|
||||
poolObject.transform.localScale = Vector3.one;
|
||||
pools.Add(prefabName, poolObject);
|
||||
return poolObject.Get();
|
||||
}
|
||||
|
||||
public static GameObject GetObject(string prefabName)
|
||||
{
|
||||
if (pools.TryGetValue(prefabName, out var pool))
|
||||
{
|
||||
return pool.Get();
|
||||
}
|
||||
|
||||
Debug.LogError($"Pool with name {prefabName} not found");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 453af7daec95404ba2ffca4676d791d8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: -5
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
28
Assets/WordConnectGameToolkit/Scripts/Gameplay/ScoreText.cs
Normal file
28
Assets/WordConnectGameToolkit/Scripts/Gameplay/ScoreText.cs
Normal file
@ -0,0 +1,28 @@
|
||||
// // ©2015 - 2025 Candy Smith
|
||||
// // All rights reserved
|
||||
// // Redistribution of this software is strictly not allowed.
|
||||
// // Copy of this software can be obtained from unity asset store only.
|
||||
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WordsToolkit.Scripts.Gameplay
|
||||
{
|
||||
public class ScoreText : MonoBehaviour
|
||||
{
|
||||
public TextMeshProUGUI scoreText;
|
||||
|
||||
public void ShowScore(int value, Vector3 transformPosition)
|
||||
{
|
||||
scoreText.transform.position = transformPosition;
|
||||
scoreText.text = "+" + value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 51758275a8aa4f4f8fcf16ea27912e97
|
||||
timeCreated: 1728488609
|
||||
@ -0,0 +1,31 @@
|
||||
// // ©2015 - 2025 Candy Smith
|
||||
// // All rights reserved
|
||||
// // Redistribution of this software is strictly not allowed.
|
||||
// // Copy of this software can be obtained from unity asset store only.
|
||||
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using WordsToolkit.Scripts.Attributes;
|
||||
|
||||
namespace WordsToolkit.Scripts.Gameplay
|
||||
{
|
||||
public abstract class ScriptableData : ScriptableObject
|
||||
{
|
||||
[IconPreview]
|
||||
public FillAndPreview prefab;
|
||||
|
||||
public virtual void OnValidate()
|
||||
{
|
||||
OnChange?.Invoke();
|
||||
}
|
||||
|
||||
public event Action OnChange;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 60337560ccc04a68b7b96cf29bff97a7
|
||||
timeCreated: 1746685197
|
||||
122
Assets/WordConnectGameToolkit/Scripts/Gameplay/SpecialItem.cs
Normal file
122
Assets/WordConnectGameToolkit/Scripts/Gameplay/SpecialItem.cs
Normal file
@ -0,0 +1,122 @@
|
||||
// // ©2015 - 2025 Candy Smith
|
||||
// // All rights reserved
|
||||
// // Redistribution of this software is strictly not allowed.
|
||||
// // Copy of this software can be obtained from unity asset store only.
|
||||
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
using UnityEngine;
|
||||
using DG.Tweening;
|
||||
using System;
|
||||
using UnityEngine.UI;
|
||||
using VContainer;
|
||||
using WordsToolkit.Scripts.Audio;
|
||||
using WordsToolkit.Scripts.System;
|
||||
using WordsToolkit.Scripts.Enums;
|
||||
using WordsToolkit.Scripts.Gameplay.Pool;
|
||||
|
||||
namespace WordsToolkit.Scripts.Gameplay
|
||||
{
|
||||
public class SpecialItem : MonoBehaviour
|
||||
{
|
||||
[Header("Animation Settings")]
|
||||
[SerializeField] private float moveDuration = 0.7f; // Matched with AnimateExtraWord
|
||||
[SerializeField] private float fadeOutDuration = 0.2f;
|
||||
[SerializeField] private AudioClip collectSound; // Sound to play when item is collected
|
||||
[SerializeField] private AudioClip bounceSound; // Sound to play on bounce effect
|
||||
[Inject]
|
||||
private IAudioService audioService; // Audio service for playing sounds
|
||||
private Sequence animationSequence;
|
||||
[SerializeField]
|
||||
private ParticleSystem collectEffect; // Optional particle effect to play on collection
|
||||
|
||||
/// <summary>
|
||||
/// Animates the special item to a target position using an arc path animation
|
||||
/// </summary>
|
||||
/// <param name="targetPosition">The world position to move to</param>
|
||||
/// <param name="onComplete">Optional callback when animation completes</param>
|
||||
public void FlyToPosition(Vector3 targetPosition, Action onComplete = null)
|
||||
{
|
||||
// Kill any existing animation
|
||||
animationSequence?.Kill();
|
||||
|
||||
// Make sure the item is visible
|
||||
gameObject.SetActive(true);
|
||||
|
||||
// audioService?.PlaySound(collectSound);
|
||||
// Create animation sequence
|
||||
animationSequence = DOTween.Sequence();
|
||||
|
||||
// Store original scale
|
||||
Vector3 originalScale = transform.localScale;
|
||||
audioService?.PlaySound(bounceSound);
|
||||
|
||||
// Initial move up animation
|
||||
Vector3 startPosition = transform.position;
|
||||
animationSequence.Append(transform.DOMoveY(startPosition.y + 0.5f, 0.3f)
|
||||
.SetEase(Ease.OutCubic));
|
||||
|
||||
// Add delay after move up
|
||||
animationSequence.AppendInterval(0.2f);
|
||||
|
||||
// Calculate a mid-point for the arc
|
||||
startPosition = transform.position;
|
||||
Vector3 midPoint = (startPosition + targetPosition) / 2f;
|
||||
|
||||
// Determine arc height based on the distance between points (30% of distance)
|
||||
float arcHeight = Vector3.Distance(startPosition, targetPosition) * 0.3f;
|
||||
midPoint.y += arcHeight;
|
||||
|
||||
// Calculate half duration for scaling
|
||||
float halfDuration = moveDuration / 2f;
|
||||
|
||||
// Create path animation
|
||||
var pathTween = transform.DOMove(
|
||||
targetPosition,
|
||||
moveDuration)
|
||||
.SetEase(Ease.OutQuad);
|
||||
|
||||
// Add the path animation to the sequence
|
||||
animationSequence.Append(pathTween);
|
||||
|
||||
// Scale down during the second half (matches AnimateExtraWord)
|
||||
animationSequence.Append(transform.DOScale(originalScale * 0.8f, .2f).SetLoops(1, LoopType.Yoyo)
|
||||
.SetEase(Ease.InOutQuad));
|
||||
// Fade out at the end
|
||||
Image image = GetComponent<Image>();
|
||||
if (image != null)
|
||||
{
|
||||
animationSequence.Append(image.DOFade(0, fadeOutDuration)
|
||||
.SetEase(Ease.InQuad));
|
||||
}
|
||||
|
||||
// Set completion callback
|
||||
animationSequence.OnComplete(() => {
|
||||
// Fire event to notify that a special item was collected
|
||||
EventManager.GetEvent(EGameEvent.SpecialItemCollected).Invoke();
|
||||
|
||||
var fx = PoolObject.GetObject(collectEffect.gameObject, transform.position);
|
||||
|
||||
// Invoke the callback if provided
|
||||
onComplete?.Invoke();
|
||||
audioService?.PlaySound(collectSound);
|
||||
|
||||
// Clean up
|
||||
Destroy(gameObject);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Stop any ongoing animation when destroyed
|
||||
private void OnDestroy()
|
||||
{
|
||||
animationSequence?.Kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cecf8650bf08408282be9a42762cc254
|
||||
timeCreated: 1742467855
|
||||
351
Assets/WordConnectGameToolkit/Scripts/Gameplay/Tile.cs
Normal file
351
Assets/WordConnectGameToolkit/Scripts/Gameplay/Tile.cs
Normal file
@ -0,0 +1,351 @@
|
||||
// // ©2015 - 2025 Candy Smith
|
||||
// // All rights reserved
|
||||
// // Redistribution of this software is strictly not allowed.
|
||||
// // Copy of this software can be obtained from unity asset store only.
|
||||
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
using DG.Tweening;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using WordsToolkit.Scripts.Settings;
|
||||
using WordsToolkit.Scripts.Gameplay.Managers;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.InputSystem;
|
||||
using WordsToolkit.Scripts.Enums;
|
||||
using WordsToolkit.Scripts.System;
|
||||
using VContainer;
|
||||
using VContainer.Unity;
|
||||
using WordsToolkit.Scripts.Audio;
|
||||
using WordsToolkit.Scripts.System.Haptic;
|
||||
|
||||
namespace WordsToolkit.Scripts.Gameplay
|
||||
{
|
||||
public class Tile : FillAndPreview, IPointerClickHandler
|
||||
{
|
||||
public TextMeshProUGUI character;
|
||||
public Image[] images;
|
||||
|
||||
[Header("Tile Colors")]
|
||||
public Color[] closedColors;
|
||||
private Color[] openColors = new Color[3];
|
||||
|
||||
private bool isSelected = false;
|
||||
private int wordNumber = -1;
|
||||
private bool isOpen = false;
|
||||
|
||||
[Header("Special Item")]
|
||||
[SerializeField] private GameObject specialItemPrefab; // Direct reference to the special item prefab
|
||||
|
||||
[Header("Hammer Animation")]
|
||||
[SerializeField] private GameObject hammerAnimationPrefab; // Reference to hammer animation prefab
|
||||
|
||||
// Added fields for special item support
|
||||
private bool hasSpecialItem = false;
|
||||
private Vector2Int specialItemPosition;
|
||||
private GameObject specialItemInstance; // Reference to the instantiated special item
|
||||
|
||||
// Simple selection state
|
||||
|
||||
private LevelManager levelManager;
|
||||
private FieldManager fieldManager;
|
||||
[SerializeField]
|
||||
private GameObject fx;
|
||||
|
||||
private IAudioService audioService;
|
||||
private IObjectResolver objectResolver;
|
||||
|
||||
[Inject]
|
||||
public void Construct(LevelManager levelManager, FieldManager fieldManager, IAudioService audioService, IObjectResolver objectResolver)
|
||||
{
|
||||
this.levelManager = levelManager;
|
||||
this.fieldManager = fieldManager;
|
||||
this.audioService = audioService;
|
||||
this.objectResolver = objectResolver;
|
||||
}
|
||||
|
||||
public void SetColors(ColorsTile colorsTile)
|
||||
{
|
||||
if (colorsTile == null)
|
||||
return;
|
||||
|
||||
openColors[0] = colorsTile.faceColor;
|
||||
openColors[1] = colorsTile.topColor;
|
||||
openColors[2] = colorsTile.bottomColor;
|
||||
}
|
||||
|
||||
// Set the tile to closed state
|
||||
public void SetTileClosed()
|
||||
{
|
||||
isOpen = false;
|
||||
|
||||
// Hide character
|
||||
if (character != null)
|
||||
{
|
||||
character.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
// Apply closed color to all images
|
||||
for (var i = 0; i < images.Length; i++)
|
||||
{
|
||||
var img = images[i];
|
||||
if (img != null)
|
||||
{
|
||||
img.color = closedColors[i];
|
||||
}
|
||||
}
|
||||
|
||||
transform.SetAsFirstSibling();
|
||||
}
|
||||
|
||||
// Set the tile to open state
|
||||
public void SetTileOpen()
|
||||
{
|
||||
isOpen = true;
|
||||
HapticFeedback.TriggerHapticFeedback(HapticFeedback.HapticForce.Light);
|
||||
|
||||
// Show character
|
||||
if (character != null)
|
||||
{
|
||||
character.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
// Apply open color to all images
|
||||
for (var i = 0; i < images.Length; i++)
|
||||
{
|
||||
var img = images[i];
|
||||
if (img != null)
|
||||
{
|
||||
img.color = openColors[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Add bounce animation
|
||||
transform.DOScale(1.2f, 0.1f)
|
||||
.SetEase(Ease.OutQuad)
|
||||
.OnComplete(() => {
|
||||
transform.DOScale(1f, 0.15f)
|
||||
.SetEase(Ease.Linear);
|
||||
});
|
||||
|
||||
transform.SetAsLastSibling();
|
||||
|
||||
// If this tile has a special item, animate it and ensure it stays on top
|
||||
if (hasSpecialItem && specialItemInstance != null)
|
||||
{
|
||||
specialItemInstance.transform.SetAsLastSibling();
|
||||
|
||||
SpecialItem specialItem = specialItemInstance.GetComponent<SpecialItem>();
|
||||
if (specialItem != null)
|
||||
{
|
||||
// Try to find a target position - use level manager collection point if available
|
||||
Vector3 targetPosition = transform.position + new Vector3(0, 300, 0); // Default fallback
|
||||
|
||||
// Get special item collection point if available
|
||||
if (levelManager != null)
|
||||
{
|
||||
var collectionPoint = levelManager.GetSpecialItemCollectionPoint();
|
||||
targetPosition = collectionPoint;
|
||||
}
|
||||
|
||||
// Start the animation
|
||||
specialItem.FlyToPosition(targetPosition, () => {
|
||||
// Notify level manager that item was collected
|
||||
if (levelManager != null)
|
||||
{
|
||||
levelManager.CollectSpecialItem(specialItemPosition);
|
||||
}
|
||||
// Clear the reference since the item destroys itself
|
||||
specialItemInstance = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Check if tile is open
|
||||
public bool IsOpen()
|
||||
{
|
||||
return isOpen;
|
||||
}
|
||||
|
||||
// Set the character for this tile
|
||||
public void SetCharacter(char c)
|
||||
{
|
||||
if (character != null)
|
||||
{
|
||||
character.text = c.ToString().ToUpper();
|
||||
}
|
||||
}
|
||||
|
||||
public void ShakeTile()
|
||||
{
|
||||
RectTransform rectTransform = GetComponent<RectTransform>();
|
||||
if (rectTransform == null)
|
||||
return;
|
||||
|
||||
Vector2 originalPosition = rectTransform.anchoredPosition;
|
||||
|
||||
Sequence shakeSequence = DOTween.Sequence();
|
||||
|
||||
float shakeAmount = 5f;
|
||||
float shakeDuration = 0.05f;
|
||||
int shakeCount = 4;
|
||||
|
||||
for (int i = 0; i < shakeCount; i++)
|
||||
{
|
||||
float xOffset = (i % 2 == 0) ? shakeAmount : -shakeAmount;
|
||||
|
||||
shakeSequence.Append(rectTransform.DOAnchorPos(
|
||||
new Vector2(originalPosition.x + xOffset, originalPosition.y),
|
||||
shakeDuration).SetEase(Ease.OutQuad));
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < images.Length; i++)
|
||||
{
|
||||
if (images[i] != null)
|
||||
{
|
||||
shakeSequence.Join(images[i].DOColor(Color.white, shakeDuration)
|
||||
.SetLoops(2, LoopType.Yoyo));
|
||||
}
|
||||
}
|
||||
shakeSequence.Append(rectTransform.DOAnchorPos(originalPosition, shakeDuration));
|
||||
}
|
||||
|
||||
// Enhanced method to associate a special item with this tile
|
||||
public void AssociateSpecialItem(Vector2Int position)
|
||||
{
|
||||
hasSpecialItem = true;
|
||||
specialItemPosition = position;
|
||||
|
||||
// Create the special item instance if we have a prefab
|
||||
if (specialItemPrefab != null && specialItemInstance == null)
|
||||
{
|
||||
InstantiateSpecialItem();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Associate with a specific prefab (override the default)
|
||||
public void AssociateSpecialItem(Vector2Int position, GameObject itemPrefab)
|
||||
{
|
||||
// Set the prefab
|
||||
specialItemPrefab = itemPrefab;
|
||||
|
||||
// Call the regular association method
|
||||
AssociateSpecialItem(position);
|
||||
}
|
||||
|
||||
// Create the special item instance
|
||||
private void InstantiateSpecialItem()
|
||||
{
|
||||
if (specialItemPrefab == null)
|
||||
{
|
||||
// Try to load a default prefab if none is assigned
|
||||
specialItemPrefab = Resources.Load<GameObject>("Prefabs/DefaultSpecialItem");
|
||||
|
||||
if (specialItemPrefab == null)
|
||||
{
|
||||
Debug.LogWarning("No special item prefab assigned to tile and no default found.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
specialItemInstance = objectResolver.Instantiate(specialItemPrefab, transform);
|
||||
specialItemInstance.transform.SetParent(transform.parent);
|
||||
specialItemInstance.transform.SetAsLastSibling();
|
||||
// fit size to tile
|
||||
RectTransform rectTransform = specialItemInstance.GetComponent<RectTransform>();
|
||||
if (rectTransform != null)
|
||||
{
|
||||
rectTransform.sizeDelta = GetComponent<RectTransform>().sizeDelta / 1.2f;
|
||||
}
|
||||
if (levelManager != null)
|
||||
{
|
||||
levelManager.RegisterSpecialItem(specialItemPosition, specialItemInstance);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove special item association and destroy instance
|
||||
public void RemoveSpecialItem()
|
||||
{
|
||||
hasSpecialItem = false;
|
||||
|
||||
if (specialItemInstance != null)
|
||||
{
|
||||
Destroy(specialItemInstance);
|
||||
specialItemInstance = null;
|
||||
}
|
||||
}
|
||||
// Check if this tile has a special item and return its position
|
||||
public bool HasSpecialItem(out Vector2Int position)
|
||||
{
|
||||
position = specialItemPosition;
|
||||
return hasSpecialItem;
|
||||
}
|
||||
|
||||
// Play hammer animation and open tile with delay
|
||||
private void PlayHammerAnimationAndOpen()
|
||||
{
|
||||
if (hammerAnimationPrefab != null)
|
||||
{
|
||||
// Instantiate hammer animation on this tile
|
||||
var offset = Vector3.right * 1.5f + Vector3.up * 0.5f;
|
||||
GameObject hammer = Instantiate(hammerAnimationPrefab, transform.position + offset, Quaternion.identity);
|
||||
DOVirtual.DelayedCall(.6f, OpenTileAfterAnimation);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If no hammer animation, just open immediately
|
||||
OpenTileAfterAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
// Open the tile after animation completes
|
||||
private void OpenTileAfterAnimation()
|
||||
{
|
||||
SetTileOpen();
|
||||
EventManager.GetEvent<Tile>(EGameEvent.TileSelected).Invoke(this);
|
||||
}
|
||||
|
||||
// Implement UI touch interface method instead of OnMouseDown
|
||||
public void OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
// Only respond if the tile is selectable and closed
|
||||
if (!isOpen && levelManager != null && levelManager.hammerMode)
|
||||
{
|
||||
// Instead of opening immediately, play hammer animation first
|
||||
PlayHammerAnimationAndOpen();
|
||||
}
|
||||
}
|
||||
|
||||
public override void FillIcon(ScriptableData iconScriptable)
|
||||
{
|
||||
UpdateColor((ColorsTile)iconScriptable);
|
||||
}
|
||||
|
||||
private void UpdateColor(ColorsTile itemTemplate)
|
||||
{
|
||||
images[0].color = itemTemplate.faceColor;
|
||||
images[1].color = itemTemplate.topColor;
|
||||
images[2].color = itemTemplate.bottomColor;
|
||||
}
|
||||
|
||||
public void ShowEffect()
|
||||
{
|
||||
fx.SetActive(true);
|
||||
}
|
||||
|
||||
public GameObject GetSpecialItemInstance()
|
||||
{
|
||||
return specialItemInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f1ff910e0235466b92d2570fc0b1f487
|
||||
timeCreated: 1741668588
|
||||
214
Assets/WordConnectGameToolkit/Scripts/Gameplay/VirtualMouseUI.cs
Normal file
214
Assets/WordConnectGameToolkit/Scripts/Gameplay/VirtualMouseUI.cs
Normal file
@ -0,0 +1,214 @@
|
||||
// // ©2015 - 2025 Candy Smith
|
||||
// // All rights reserved
|
||||
// // Redistribution of this software is strictly not allowed.
|
||||
// // Copy of this software can be obtained from unity asset store only.
|
||||
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
using UnityEngine.InputSystem.UI;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace WordsToolkit.Scripts.Gameplay
|
||||
{
|
||||
public class VirtualMouse : MonoBehaviour
|
||||
{
|
||||
private VirtualMouseInput virtualMouseInput;
|
||||
private bool isInitialized = false;
|
||||
|
||||
[SerializeField]
|
||||
private Canvas canvas;
|
||||
|
||||
private RectTransform canvasRectTransform;
|
||||
private Camera uiCamera;
|
||||
|
||||
[SerializeField]
|
||||
private float cursorHideDelay = 1.0f; // Time in seconds before hiding cursor
|
||||
[SerializeField]
|
||||
private float fadeSpeed = 3.0f; // How fast the cursor fades in/out
|
||||
[SerializeField]
|
||||
private float minCursorAlpha = 0f; // Minimum alpha when hidden
|
||||
[SerializeField]
|
||||
private float maxCursorAlpha = .5f; // Maximum alpha when visible
|
||||
|
||||
private float idleTimer = 0f;
|
||||
private Vector2 lastPosition;
|
||||
public Image cursorImage; // Reference to cursor image
|
||||
private bool isCursorVisible = true;
|
||||
private float targetAlpha;
|
||||
private float currentAlpha;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
virtualMouseInput = GetComponent<VirtualMouseInput>();
|
||||
if (virtualMouseInput == null)
|
||||
{
|
||||
Debug.LogError("VirtualMouseInput component not found! Please add it to the same GameObject.");
|
||||
return;
|
||||
}
|
||||
|
||||
canvasRectTransform = canvas.GetComponent<RectTransform>();
|
||||
|
||||
if (canvas.renderMode == RenderMode.ScreenSpaceCamera || canvas.renderMode == RenderMode.WorldSpace)
|
||||
{
|
||||
uiCamera = canvas.worldCamera;
|
||||
}
|
||||
|
||||
targetAlpha = maxCursorAlpha;
|
||||
currentAlpha = maxCursorAlpha;
|
||||
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
StartCoroutine(WaitForVirtualMouseInitialization());
|
||||
}
|
||||
|
||||
private IEnumerator WaitForVirtualMouseInitialization()
|
||||
{
|
||||
float timeout = 2.0f;
|
||||
float timer = 0f;
|
||||
|
||||
while (timer < timeout)
|
||||
{
|
||||
if (virtualMouseInput != null && virtualMouseInput.virtualMouse != null)
|
||||
{
|
||||
lastPosition = virtualMouseInput.virtualMouse.position.value;
|
||||
isInitialized = true;
|
||||
Debug.Log("Virtual mouse initialized successfully.");
|
||||
yield break;
|
||||
}
|
||||
|
||||
timer += 0.1f;
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
}
|
||||
|
||||
Debug.LogError("Failed to initialize virtualMouse within timeout period.");
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!isInitialized || virtualMouseInput == null || virtualMouseInput.virtualMouse == null)
|
||||
{
|
||||
return; // Skip if not initialized
|
||||
}
|
||||
|
||||
transform.SetAsLastSibling();
|
||||
|
||||
if (canvas.renderMode == RenderMode.ScreenSpaceOverlay ||
|
||||
canvas.renderMode == RenderMode.ScreenSpaceCamera)
|
||||
{
|
||||
transform.localScale = new Vector3(1f / canvas.scaleFactor, 1f / canvas.scaleFactor, 1f);
|
||||
}
|
||||
|
||||
// Check if cursor moved - with null checks
|
||||
try
|
||||
{
|
||||
Vector2 currentPosition = virtualMouseInput.virtualMouse.position.value;
|
||||
if (Vector2.Distance(currentPosition, lastPosition) > 0.5f) // Small threshold to detect real movement
|
||||
{
|
||||
idleTimer = 0f;
|
||||
targetAlpha = maxCursorAlpha; // Set target to maximum alpha rather than 1.0
|
||||
lastPosition = currentPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No movement, increment timer
|
||||
idleTimer += Time.deltaTime;
|
||||
|
||||
// Start fading cursor after delay
|
||||
if (idleTimer >= cursorHideDelay)
|
||||
{
|
||||
targetAlpha = minCursorAlpha; // Set target to minimum alpha
|
||||
}
|
||||
}
|
||||
|
||||
// Smoothly fade the cursor
|
||||
if (cursorImage != null)
|
||||
{
|
||||
// Gradually interpolate current alpha toward target alpha
|
||||
currentAlpha = Mathf.Lerp(currentAlpha, targetAlpha, Time.deltaTime * fadeSpeed);
|
||||
|
||||
// Apply the alpha to the cursor image
|
||||
Color cursorColor = cursorImage.color;
|
||||
cursorColor.a = currentAlpha;
|
||||
cursorImage.color = cursorColor;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning("Error accessing virtual mouse position: " + e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (!isInitialized || virtualMouseInput == null || virtualMouseInput.virtualMouse == null)
|
||||
{
|
||||
return; // Skip if not initialized
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Get the current virtual mouse position
|
||||
var virtualMousePosition = virtualMouseInput.virtualMouse.position.value;
|
||||
|
||||
// Clamp to screen boundaries
|
||||
virtualMousePosition.x = Mathf.Clamp(virtualMousePosition.x, 0, Screen.width);
|
||||
virtualMousePosition.y = Mathf.Clamp(virtualMousePosition.y, 0, Screen.height);
|
||||
|
||||
if (canvas != null)
|
||||
{
|
||||
if (canvas.renderMode == RenderMode.ScreenSpaceOverlay)
|
||||
{
|
||||
float scaleFactor = canvas.scaleFactor;
|
||||
Rect canvasRect = canvasRectTransform.rect;
|
||||
|
||||
float canvasWidth = canvasRect.width * scaleFactor;
|
||||
float canvasHeight = canvasRect.height * scaleFactor;
|
||||
|
||||
float xOffset = (Screen.width - canvasWidth) / 2;
|
||||
float yOffset = (Screen.height - canvasHeight) / 2;
|
||||
|
||||
virtualMousePosition.x = Mathf.Clamp(virtualMousePosition.x, xOffset, xOffset + canvasWidth);
|
||||
virtualMousePosition.y = Mathf.Clamp(virtualMousePosition.y, yOffset, yOffset + canvasHeight);
|
||||
}
|
||||
else if (canvas.renderMode == RenderMode.ScreenSpaceCamera && uiCamera != null)
|
||||
{
|
||||
Vector3[] corners = new Vector3[4];
|
||||
canvasRectTransform.GetWorldCorners(corners);
|
||||
|
||||
Vector2 min = new Vector2(float.MaxValue, float.MaxValue);
|
||||
Vector2 max = new Vector2(float.MinValue, float.MinValue);
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
Vector2 screenPos = RectTransformUtility.WorldToScreenPoint(uiCamera, corners[i]);
|
||||
min.x = Mathf.Min(min.x, screenPos.x);
|
||||
min.y = Mathf.Min(min.y, screenPos.y);
|
||||
max.x = Mathf.Max(max.x, screenPos.x);
|
||||
max.y = Mathf.Max(max.y, screenPos.y);
|
||||
}
|
||||
|
||||
virtualMousePosition.x = Mathf.Clamp(virtualMousePosition.x, min.x, max.x);
|
||||
virtualMousePosition.y = Mathf.Clamp(virtualMousePosition.y, min.y, max.y);
|
||||
}
|
||||
}
|
||||
|
||||
InputState.Change(virtualMouseInput.virtualMouse.position, virtualMousePosition);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning("Error in virtual mouse LateUpdate: " + e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 22ad4e27333e4e7da8ff07ce4de6c4c1
|
||||
timeCreated: 1741938131
|
||||
@ -0,0 +1,748 @@
|
||||
// // ©2015 - 2025 Candy Smith
|
||||
// // All rights reserved
|
||||
// // Redistribution of this software is strictly not allowed.
|
||||
// // Copy of this software can be obtained from unity asset store only.
|
||||
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using WordsToolkit.Scripts.Gameplay.Managers;
|
||||
using WordsToolkit.Scripts.GUI;
|
||||
using WordsToolkit.Scripts.Levels;
|
||||
using WordsToolkit.Scripts.System;
|
||||
using TMPro;
|
||||
using UnityEngine.Serialization;
|
||||
using VContainer;
|
||||
using WordsToolkit.Scripts.Audio;
|
||||
using WordsToolkit.Scripts.Enums;
|
||||
using WordsToolkit.Scripts.GUI.Buttons;
|
||||
using WordsToolkit.Scripts.Infrastructure.Service;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.InputSystem.UI;
|
||||
|
||||
namespace WordsToolkit.Scripts.Gameplay
|
||||
{
|
||||
public class WordSelectionManager : MonoBehaviour, IFadeable
|
||||
{
|
||||
// Event for when selected letters change
|
||||
public event Action<List<LetterButton>> OnSelectionChanged;
|
||||
// Event for when selection is completed
|
||||
public event Action<string> OnSelectionCompleted;
|
||||
|
||||
[Header("References")]
|
||||
public FieldManager fieldManager;
|
||||
public LetterButton letterButtonPrefab;
|
||||
|
||||
[Header("Circle Layout Settings")]
|
||||
public float radius = 200f;
|
||||
public Vector2 circleCenter = Vector2.zero;
|
||||
|
||||
private List<LetterButton> selectedLetters = new List<LetterButton>();
|
||||
private bool isSelecting = false;
|
||||
private Vector2 currentMousePosition;
|
||||
private VirtualMouseInput virtualMouseInput;
|
||||
|
||||
private float parallelThreshold = 0.9f; // Dot product threshold for small angle detection (0.9 = ~25 degrees)
|
||||
private float minBackwardDistance = 100f; // Minimum distance to move backward before unselecting
|
||||
private float maxAngleForUnselect = .5f; // Maximum angle in degrees between lines to trigger unselect
|
||||
private bool isGameWon = false;
|
||||
|
||||
public bool IsSelecting => isSelecting;
|
||||
|
||||
public UILineRenderer lineRenderer;
|
||||
[SerializeField]
|
||||
private Transform parentLetters;
|
||||
[SerializeField]
|
||||
private CanvasGroup panelCanvasGroup;
|
||||
|
||||
[Header("UI References")]
|
||||
[SerializeField] private float characterSpacing = 30f; // Spacing between characters
|
||||
public Image backgroundSelectedWord; // Reference to the background image
|
||||
[SerializeField]
|
||||
private TextMeshProUGUI selectedWordText;
|
||||
|
||||
[SerializeField]
|
||||
private HorizontalLayoutGroup layout;
|
||||
|
||||
[Header("Rearrange Settings")]
|
||||
[SerializeField] private float swapAnimationDuration = 0.5f;
|
||||
[SerializeField] private Ease swapAnimationEase = Ease.InOutQuad;
|
||||
|
||||
[Header("Shuffle Button")]
|
||||
[SerializeField] private Button shuffleButton;
|
||||
|
||||
private GameManager gameManager;
|
||||
|
||||
private ILevelLoaderService levelLoaderService;
|
||||
private IAudioService soundBase;
|
||||
[SerializeField]
|
||||
private CanvasGroup canvasGroup;
|
||||
|
||||
[Inject]
|
||||
public void Construct(ILevelLoaderService levelLoaderService, GameManager gameManager, IAudioService soundBase, ButtonViewController buttonViewController)
|
||||
{
|
||||
this.soundBase = soundBase;
|
||||
this.gameManager = gameManager;
|
||||
this.levelLoaderService = levelLoaderService;
|
||||
this.levelLoaderService.OnLevelLoaded += OnLevelLoaded;
|
||||
buttonViewController.RegisterButton(this);
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Try to find VirtualMouseInput component in the scene
|
||||
virtualMouseInput = FindObjectOfType<VirtualMouseInput>();
|
||||
OnSelectionCompleted += ValidateWordWithModel;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (shuffleButton != null)
|
||||
{
|
||||
shuffleButton.onClick.AddListener(RearrangeRandomLetters);
|
||||
}
|
||||
|
||||
if (fieldManager != null)
|
||||
{
|
||||
fieldManager.OnAllTilesOpened.AddListener(OnGameWon);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (shuffleButton != null)
|
||||
{
|
||||
shuffleButton.onClick.RemoveListener(RearrangeRandomLetters);
|
||||
}
|
||||
if (levelLoaderService != null)
|
||||
{
|
||||
levelLoaderService.OnLevelLoaded -= OnLevelLoaded;
|
||||
}
|
||||
if (fieldManager != null)
|
||||
{
|
||||
fieldManager.OnAllTilesOpened.RemoveListener(OnGameWon);
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Update mouse position and line renderer during selection
|
||||
if (isSelecting)
|
||||
{
|
||||
// Check if input is still active, if not, end selection
|
||||
if (!IsAnyInputActive())
|
||||
{
|
||||
EndSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateMousePosition();
|
||||
CheckForBackwardMovement();
|
||||
UpdateLineRenderer();
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckForBackwardMovement()
|
||||
{
|
||||
if (selectedLetters.Count < 2) return;
|
||||
|
||||
// Get the current line segment (from last selected letter to mouse position)
|
||||
Vector2 lastLetterPos = selectedLetters[selectedLetters.Count - 1].GetComponent<RectTransform>().anchoredPosition;
|
||||
Vector2 secondLastLetterPos = selectedLetters[selectedLetters.Count - 2].GetComponent<RectTransform>().anchoredPosition;
|
||||
Vector2 currentMovement = currentMousePosition - lastLetterPos;
|
||||
|
||||
// Calculate minBackwardDistance as half the distance between the two latest letters
|
||||
float distanceBetweenLatest = Vector2.Distance(lastLetterPos, secondLastLetterPos);
|
||||
float dynamicMinBackwardDistance = distanceBetweenLatest * 0.5f;
|
||||
|
||||
// Check if we've moved back far enough to consider unselecting
|
||||
if (currentMovement.magnitude < dynamicMinBackwardDistance) return;
|
||||
|
||||
// Check only the most recent line segment for backward movement
|
||||
if (selectedLetters.Count >= 2)
|
||||
{
|
||||
int i = selectedLetters.Count - 2; // Only check the last segment
|
||||
Vector2 letterPos = selectedLetters[i].GetComponent<RectTransform>().anchoredPosition;
|
||||
Vector2 previousSegment = selectedLetters[i + 1].GetComponent<RectTransform>().anchoredPosition - letterPos;
|
||||
|
||||
// Check if current movement is backward and at small angle to previous segment
|
||||
if (IsMovingBackwardWithSmallAngle(currentMovement, previousSegment, letterPos))
|
||||
{
|
||||
// Unselect letters from this point forward
|
||||
UnselectLettersFromIndex(i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsMovingBackwardWithSmallAngle(Vector2 currentMovement, Vector2 previousSegment, Vector2 segmentStart)
|
||||
{
|
||||
// Normalize the segments
|
||||
Vector2 currentDir = currentMovement.normalized;
|
||||
Vector2 prevDir = previousSegment.normalized;
|
||||
|
||||
// Calculate the angle between the directions
|
||||
float dotProduct = Vector2.Dot(currentDir, prevDir);
|
||||
float angleInRadians = Mathf.Acos(Mathf.Clamp(dotProduct, -1f, 1f));
|
||||
float angleInDegrees = angleInRadians * Mathf.Rad2Deg;
|
||||
|
||||
// Check if we're moving backward (opposite direction) with small angle
|
||||
bool isOppositeDirection = dotProduct < 0; // Negative dot product means opposite directions
|
||||
bool isSmallAngle = angleInDegrees > (180f - maxAngleForUnselect) && angleInDegrees < (180f + maxAngleForUnselect);
|
||||
|
||||
return isOppositeDirection && isSmallAngle;
|
||||
}
|
||||
|
||||
private void UnselectLettersFromIndex(int fromIndex)
|
||||
{
|
||||
// Remove letters from the specified index to the end
|
||||
for (int i = selectedLetters.Count - 1; i >= fromIndex; i--)
|
||||
{
|
||||
selectedLetters[i].SetSelected(false);
|
||||
selectedLetters.RemoveAt(i);
|
||||
}
|
||||
|
||||
// Update UI
|
||||
UpdateSelectedWordText();
|
||||
OnSelectionChanged?.Invoke(selectedLetters);
|
||||
}
|
||||
|
||||
private void UpdateMousePosition()
|
||||
{
|
||||
Vector2 screenPosition = Vector2.zero;
|
||||
|
||||
// Check for touch input first (mobile devices)
|
||||
if (Touchscreen.current != null && Touchscreen.current.primaryTouch.press.isPressed)
|
||||
{
|
||||
screenPosition = Touchscreen.current.primaryTouch.position.ReadValue();
|
||||
}
|
||||
// Check virtual mouse input
|
||||
else if (virtualMouseInput != null && virtualMouseInput.virtualMouse != null && virtualMouseInput.virtualMouse.leftButton.isPressed)
|
||||
{
|
||||
screenPosition = virtualMouseInput.virtualMouse.position.ReadValue();
|
||||
}
|
||||
// Fallback to regular mouse input
|
||||
else if (Mouse.current != null)
|
||||
{
|
||||
screenPosition = Mouse.current.position.ReadValue();
|
||||
}
|
||||
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
parentLetters.GetComponent<RectTransform>(),
|
||||
screenPosition,
|
||||
Camera.main,
|
||||
out currentMousePosition
|
||||
);
|
||||
}
|
||||
|
||||
private bool IsAnyInputActive()
|
||||
{
|
||||
// Check touch input
|
||||
if (Touchscreen.current != null && Touchscreen.current.primaryTouch.press.isPressed)
|
||||
return true;
|
||||
|
||||
// Check virtual mouse
|
||||
if (virtualMouseInput != null && virtualMouseInput.virtualMouse != null && virtualMouseInput.virtualMouse.leftButton.isPressed)
|
||||
return true;
|
||||
|
||||
// Check regular mouse
|
||||
if (Mouse.current != null && Mouse.current.leftButton.isPressed)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnLevelLoaded(Level level)
|
||||
{
|
||||
if (lineRenderer != null)
|
||||
{
|
||||
lineRenderer.color = level.colorsTile.faceColor;
|
||||
}
|
||||
|
||||
layout.GetComponent<Image>().color = level.colorsTile.faceColor;
|
||||
|
||||
// Clean up previous level
|
||||
CleanupLevel();
|
||||
|
||||
var letters = level.GetLetters(gameManager.language);
|
||||
int letterCount = letters.Length;
|
||||
var letterSize = 132 - Mathf.Max(0, letterCount - 6) * 10;
|
||||
|
||||
for (int i = 0; i < letterCount; i++)
|
||||
{
|
||||
// Calculate the angle for this letter (in radians)
|
||||
// Start from the top (90 degrees or π/2) and go clockwise
|
||||
float angle = ((float)i / letterCount) * 2 * Mathf.PI - Mathf.PI/2;
|
||||
|
||||
// Calculate position on the circle
|
||||
float x = circleCenter.x + radius * Mathf.Cos(angle);
|
||||
float y = circleCenter.y + radius * Mathf.Sin(angle);
|
||||
|
||||
// Instantiate button
|
||||
var button = Instantiate(letterButtonPrefab, parentLetters);
|
||||
button.SetColor(level.colorsTile.faceColor);
|
||||
RectTransform rectTransform = button.GetComponent<RectTransform>();
|
||||
|
||||
// Set position
|
||||
rectTransform.anchoredPosition = new Vector2(x, y);
|
||||
|
||||
// Set text
|
||||
button.SetText(letters[i].ToString());
|
||||
button.letterText.fontSize = letterSize;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up the current level state, including selections and UI elements
|
||||
/// </summary>
|
||||
public void CleanupLevel()
|
||||
{
|
||||
// Clear any active selection
|
||||
ClearSelection();
|
||||
|
||||
// Clear any existing letters
|
||||
ClearExistingLetters();
|
||||
|
||||
// Reset UI elements
|
||||
if (selectedWordText != null)
|
||||
{
|
||||
selectedWordText.text = "";
|
||||
}
|
||||
|
||||
// Reset background
|
||||
SetBackgroundAlpha(0f);
|
||||
|
||||
// Reset selection state
|
||||
isSelecting = false;
|
||||
selectedLetters.Clear();
|
||||
|
||||
// Reset game won state and re-enable panel
|
||||
isGameWon = false;
|
||||
SetPanelBlockRaycast(true);
|
||||
}
|
||||
|
||||
private void ClearExistingLetters()
|
||||
{
|
||||
// Find and destroy all existing letter buttons under parentLetters
|
||||
if (parentLetters != null)
|
||||
{
|
||||
LetterButton[] existingButtons = parentLetters.GetComponentsInChildren<LetterButton>();
|
||||
foreach (LetterButton button in existingButtons)
|
||||
{
|
||||
Destroy(button.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void StartSelection(LetterButton button)
|
||||
{
|
||||
// Clear any previous selection
|
||||
ClearSelection();
|
||||
|
||||
// Start a new selection
|
||||
isSelecting = true;
|
||||
selectedLetters.Add(button);
|
||||
soundBase.PlayIncremental(selectedLetters.Count);
|
||||
|
||||
// Initialize mouse position
|
||||
UpdateMousePosition();
|
||||
|
||||
// Fade out rearrange button during selection
|
||||
if (shuffleButton != null)
|
||||
{
|
||||
shuffleButton.GetComponent<CanvasGroup>().DOFade(0.0f, 0.3f);
|
||||
}
|
||||
|
||||
// Make background visible when selection starts
|
||||
SetBackgroundAlpha(1f);
|
||||
|
||||
// Start drawing the line
|
||||
UpdateLineRenderer();
|
||||
|
||||
// Update the selected word text
|
||||
UpdateSelectedWordText();
|
||||
|
||||
// Notify listeners about the selection change
|
||||
OnSelectionChanged?.Invoke(selectedLetters);
|
||||
}
|
||||
|
||||
public void AddToSelection(LetterButton button)
|
||||
{
|
||||
if (!isSelecting) return;
|
||||
|
||||
// Don't add if it's already the last selected letter
|
||||
if (selectedLetters.Count > 0 && selectedLetters[selectedLetters.Count - 1] == button)
|
||||
return;
|
||||
|
||||
// Check if this letter is already selected elsewhere in the chain
|
||||
if (selectedLetters.Contains(button))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
soundBase.PlayIncremental(selectedLetters.Count);
|
||||
// Add this letter to our selection
|
||||
selectedLetters.Add(button);
|
||||
// Update the line to include the new letter
|
||||
UpdateLineRenderer();
|
||||
// Update the selected word text
|
||||
UpdateSelectedWordText();
|
||||
// Notify listeners about the selection change
|
||||
OnSelectionChanged?.Invoke(selectedLetters);
|
||||
}
|
||||
|
||||
private void UpdateLineRenderer()
|
||||
{
|
||||
if (lineRenderer == null) return;
|
||||
|
||||
// Create array of points for the line renderer
|
||||
// Add one extra point for the current mouse position when selecting
|
||||
int pointCount = selectedLetters.Count + (isSelecting ? 1 : 0);
|
||||
Vector2[] points = new Vector2[pointCount];
|
||||
|
||||
// Update each selected letter position
|
||||
for (int i = 0; i < selectedLetters.Count; i++)
|
||||
{
|
||||
RectTransform letterRect = selectedLetters[i].GetComponent<RectTransform>();
|
||||
// Use anchoredPosition since that's what we set in OnLevelLoaded
|
||||
points[i] = letterRect.anchoredPosition;
|
||||
}
|
||||
|
||||
// Add current mouse position as the last point if we're actively selecting
|
||||
if (isSelecting && selectedLetters.Count > 0)
|
||||
{
|
||||
points[points.Length - 1] = currentMousePosition;
|
||||
}
|
||||
|
||||
lineRenderer.points = points;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void EndSelection()
|
||||
{
|
||||
if (!isSelecting) return;
|
||||
|
||||
isSelecting = false;
|
||||
|
||||
// Process the word that was formed
|
||||
if (selectedLetters.Count > 0)
|
||||
{
|
||||
string word = GetSelectedWord();
|
||||
// Notify listeners that a word selection is completed
|
||||
OnSelectionCompleted?.Invoke(word);
|
||||
|
||||
// Here you would check if the word is valid and handle scoring
|
||||
// For now, just clear selection after a short delay
|
||||
Invoke("ClearSelection", 0.1f);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetSelectedWord()
|
||||
{
|
||||
string word = "";
|
||||
foreach (var letter in selectedLetters)
|
||||
{
|
||||
word += letter.GetLetter();
|
||||
}
|
||||
return word;
|
||||
}
|
||||
|
||||
// Update the selected word display using character prefabs
|
||||
private void UpdateSelectedWordText()
|
||||
{
|
||||
if (selectedWordText != null)
|
||||
{
|
||||
selectedWordText.color = new Color(selectedWordText.color.r, selectedWordText.color.g, selectedWordText.color.b, 1f);
|
||||
selectedWordText.text = GetSelectedWord();
|
||||
UpdateHorizontalLayout(layout);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearSelection()
|
||||
{
|
||||
// Clear all visual selection indicators
|
||||
foreach (var letter in selectedLetters)
|
||||
{
|
||||
letter.SetSelected(false);
|
||||
}
|
||||
|
||||
selectedLetters.Clear();
|
||||
isSelecting = false;
|
||||
|
||||
// Fade in rearrange button when selection ends
|
||||
if (shuffleButton != null)
|
||||
{
|
||||
shuffleButton.GetComponent<CanvasGroup>().DOFade(1f, 0.3f);
|
||||
}
|
||||
|
||||
// Clear the line renderer
|
||||
if (lineRenderer != null)
|
||||
{
|
||||
lineRenderer.points = new Vector2[0];
|
||||
}
|
||||
|
||||
if (selectedWordText != null)
|
||||
{
|
||||
selectedWordText.text = "";
|
||||
}
|
||||
// Make background invisible when selection is cleared
|
||||
SetBackgroundAlpha(0f);
|
||||
}
|
||||
|
||||
// Updated method that gets character positions and delegates validation to FieldManager
|
||||
private void ValidateWordWithModel(string word)
|
||||
{
|
||||
if (string.IsNullOrEmpty(word)) return;
|
||||
|
||||
// Get the positions from the actual text characters
|
||||
List<Vector3> letterPositions = new List<Vector3>();
|
||||
|
||||
if (selectedWordText != null && !string.IsNullOrEmpty(selectedWordText.text))
|
||||
{
|
||||
// Force text to update
|
||||
selectedWordText.ForceMeshUpdate();
|
||||
|
||||
// Get mesh info which contains character positions
|
||||
TMP_TextInfo textInfo = selectedWordText.textInfo;
|
||||
|
||||
// Go through each character in the text
|
||||
for (int i = 0; i < word.Length && i < textInfo.characterCount; i++)
|
||||
{
|
||||
// Make sure character is visible (not a space or control character)
|
||||
if (!textInfo.characterInfo[i].isVisible) continue;
|
||||
|
||||
// Get the center position of the character in world space
|
||||
Vector3 bottomLeft = selectedWordText.transform.TransformPoint(textInfo.characterInfo[i].bottomLeft);
|
||||
Vector3 topRight = selectedWordText.transform.TransformPoint(textInfo.characterInfo[i].topRight);
|
||||
Vector3 charCenter = (bottomLeft + topRight) / 2f;
|
||||
|
||||
letterPositions.Add(charCenter);
|
||||
}
|
||||
}
|
||||
|
||||
// Delegate the word validation to FieldManager
|
||||
if (fieldManager != null)
|
||||
{
|
||||
// If the word is already open, show the shake animation
|
||||
if (fieldManager.IsWordOpen(word))
|
||||
{
|
||||
ShakeSelectedWordBackground();
|
||||
soundBase.PlayWrong();
|
||||
}
|
||||
|
||||
// If the word is valid and was successfully opened (or was already open),
|
||||
// animate the UI elements
|
||||
if (fieldManager.ValidateWord(word, letterPositions))
|
||||
{
|
||||
SetPanelBlockRaycast(false);
|
||||
AnimateAlphaDown(backgroundSelectedWord);
|
||||
EventManager.GetEvent<string>(EGameEvent.WordOpened).Invoke(word);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGameWon()
|
||||
{
|
||||
isGameWon = true;
|
||||
SetPanelBlockRaycast(false);
|
||||
}
|
||||
|
||||
private void ShakeSelectedWordBackground()
|
||||
{
|
||||
if (backgroundSelectedWord == null) return;
|
||||
|
||||
RectTransform rectTransform = backgroundSelectedWord.GetComponent<RectTransform>();
|
||||
if (rectTransform == null) return;
|
||||
|
||||
// Store original position
|
||||
Vector2 originalPosition = rectTransform.anchoredPosition;
|
||||
|
||||
// Create shake sequence
|
||||
Sequence shakeSequence = DOTween.Sequence();
|
||||
|
||||
// Create quick, small shakes (offset from original position)
|
||||
float shakeAmount = 10f;
|
||||
float shakeDuration = 0.08f;
|
||||
int shakeCount = 3;
|
||||
|
||||
for (int i = 0; i < shakeCount; i++)
|
||||
{
|
||||
// Alternate directions: right, left, right
|
||||
float xOffset = (i % 2 == 0) ? shakeAmount : -shakeAmount;
|
||||
|
||||
shakeSequence.Append(rectTransform.DOAnchorPos(
|
||||
new Vector2(originalPosition.x + xOffset, originalPosition.y),
|
||||
shakeDuration).SetEase(Ease.OutQuad));
|
||||
}
|
||||
|
||||
shakeSequence.Append(rectTransform.DOAnchorPos(originalPosition, shakeDuration));
|
||||
}
|
||||
|
||||
private void AnimateAlphaDown(Image image)
|
||||
{
|
||||
image.DOFade(0f, 0.3f);
|
||||
selectedWordText.DOFade(0f, 0.3f);
|
||||
DOVirtual.DelayedCall(1f, () => {
|
||||
if (!isGameWon)
|
||||
SetPanelBlockRaycast(true);
|
||||
});
|
||||
}
|
||||
|
||||
private void SetPanelBlockRaycast(bool blockRaycast)
|
||||
{
|
||||
if (panelCanvasGroup != null)
|
||||
{
|
||||
panelCanvasGroup.blocksRaycasts = blockRaycast;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to set background alpha
|
||||
private void SetBackgroundAlpha(float alpha)
|
||||
{
|
||||
if (backgroundSelectedWord != null)
|
||||
{
|
||||
Color color = backgroundSelectedWord.color;
|
||||
color.a = alpha;
|
||||
backgroundSelectedWord.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
private void ForceUpdateLayout(RectTransform layoutRectTransform)
|
||||
{
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(layoutRectTransform);
|
||||
}
|
||||
|
||||
private void UpdateHorizontalLayout(HorizontalLayoutGroup layout)
|
||||
{
|
||||
ForceUpdateLayout(layout.GetComponent<RectTransform>());
|
||||
}
|
||||
|
||||
public void RearrangeRandomLetters()
|
||||
{
|
||||
if (parentLetters == null) return;
|
||||
|
||||
// Get all letter buttons
|
||||
var letterButtons = parentLetters.GetComponentsInChildren<LetterButton>();
|
||||
if (letterButtons.Length < 2) return;
|
||||
|
||||
int swapCount = letterButtons.Length;
|
||||
var lettersToSwap = letterButtons;
|
||||
|
||||
// Create a list of positions and shuffle them
|
||||
var positions = lettersToSwap.Select(btn => btn.GetComponent<RectTransform>().anchoredPosition).ToList();
|
||||
|
||||
// Shuffle positions using Fisher-Yates algorithm
|
||||
for (int i = positions.Count - 1; i > 0; i--)
|
||||
{
|
||||
int randomIndex = UnityEngine.Random.Range(0, i + 1);
|
||||
Vector2 temp = positions[i];
|
||||
positions[i] = positions[randomIndex];
|
||||
positions[randomIndex] = temp;
|
||||
}
|
||||
|
||||
// Create a single sequence for all animations
|
||||
var sequence = DOTween.Sequence();
|
||||
|
||||
// Add all animations to the sequence simultaneously using a single Join group
|
||||
var joinGroup = sequence.Join(DOTween.Sequence());
|
||||
for (int i = 0; i < lettersToSwap.Length; i++)
|
||||
{
|
||||
joinGroup.Join(
|
||||
lettersToSwap[i].GetComponent<RectTransform>()
|
||||
.DOAnchorPos(positions[i], swapAnimationDuration)
|
||||
.SetEase(swapAnimationEase)
|
||||
);
|
||||
}
|
||||
|
||||
sequence.Play();
|
||||
}
|
||||
|
||||
private List<LetterButton> GetRandomLetters(LetterButton[] allLetters, int count)
|
||||
{
|
||||
List<LetterButton> randomLetters = new List<LetterButton>();
|
||||
List<int> availableIndices = new List<int>();
|
||||
|
||||
// Initialize available indices
|
||||
for (int i = 0; i < allLetters.Length; i++)
|
||||
{
|
||||
availableIndices.Add(i);
|
||||
}
|
||||
|
||||
// Pick random letters
|
||||
for (int i = 0; i < count && availableIndices.Count > 0; i++)
|
||||
{
|
||||
int randomIndex = UnityEngine.Random.Range(0, availableIndices.Count);
|
||||
int selectedIndex = availableIndices[randomIndex];
|
||||
randomLetters.Add(allLetters[selectedIndex]);
|
||||
availableIndices.RemoveAt(randomIndex);
|
||||
}
|
||||
|
||||
return randomLetters;
|
||||
}
|
||||
|
||||
public List<LetterButton> GetLetters(string wordForTutorial)
|
||||
{
|
||||
var letters = parentLetters.GetComponentsInChildren<LetterButton>();
|
||||
List<LetterButton> letterButtons = new List<LetterButton>();
|
||||
List<LetterButton> usedLetters = new List<LetterButton>();
|
||||
|
||||
for (int i = 0; i < wordForTutorial.Length; i++)
|
||||
{
|
||||
var letter = wordForTutorial[i];
|
||||
var availableLetter = letters.FirstOrDefault(l =>
|
||||
l.GetLetter().ToLower() == letter.ToString().ToLower() &&
|
||||
!usedLetters.Contains(l));
|
||||
|
||||
if (availableLetter != null)
|
||||
{
|
||||
letterButtons.Add(availableLetter);
|
||||
usedLetters.Add(availableLetter);
|
||||
}
|
||||
else
|
||||
{
|
||||
letterButtons.Add(null);
|
||||
}
|
||||
}
|
||||
return letterButtons;
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
canvasGroup.DOFade(0f, 0.3f);
|
||||
}
|
||||
|
||||
public void InstantHide()
|
||||
{
|
||||
canvasGroup.alpha = 0f;
|
||||
}
|
||||
|
||||
public void HideForWin()
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
canvasGroup.DOFade(1f, 0.3f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manually set the VirtualMouseInput reference for controller support
|
||||
/// </summary>
|
||||
/// <param name="virtualMouse">The VirtualMouseInput component to use</param>
|
||||
public void SetVirtualMouseInput(VirtualMouseInput virtualMouse)
|
||||
{
|
||||
virtualMouseInput = virtualMouse;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e3f9c13430c80486384a5ba266dadfb2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fca769902e6c474d8f4b132521f640a9
|
||||
timeCreated: 1745821503
|
||||
@ -0,0 +1,46 @@
|
||||
// // ©2015 - 2025 Candy Smith
|
||||
// // All rights reserved
|
||||
// // Redistribution of this software is strictly not allowed.
|
||||
// // Copy of this software can be obtained from unity asset store only.
|
||||
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
using WordsToolkit.Scripts.Levels;
|
||||
using WordsToolkit.Scripts.NLP;
|
||||
|
||||
namespace WordsToolkit.Scripts.Gameplay.WordValidator
|
||||
{
|
||||
public class DefaultWordValidator : IWordValidator
|
||||
{
|
||||
private readonly IModelController modelController;
|
||||
private readonly ICustomWordRepository customWordRepository;
|
||||
private readonly Level levelData;
|
||||
|
||||
public DefaultWordValidator(IModelController modelController, ICustomWordRepository customWordRepository, Level levelData)
|
||||
{
|
||||
this.modelController = modelController;
|
||||
this.customWordRepository = customWordRepository;
|
||||
this.levelData = levelData;
|
||||
}
|
||||
|
||||
public bool IsWordKnown(string word, string currentLanguage)
|
||||
{
|
||||
if (string.IsNullOrEmpty(word))
|
||||
return false;
|
||||
|
||||
word = word.ToLower();
|
||||
return (modelController != null && modelController.IsWordKnown(word, currentLanguage)) ||
|
||||
(customWordRepository != null && customWordRepository.ContainsWord(word));
|
||||
}
|
||||
|
||||
public bool IsExtraWordValid(string word, string language)
|
||||
{
|
||||
return customWordRepository.AddExtraWord(word);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d8d6a0260fc24efda1fc101f2cd8b6ba
|
||||
timeCreated: 1745821510
|
||||
@ -0,0 +1,20 @@
|
||||
// // ©2015 - 2025 Candy Smith
|
||||
// // All rights reserved
|
||||
// // Redistribution of this software is strictly not allowed.
|
||||
// // Copy of this software can be obtained from unity asset store only.
|
||||
// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// // THE SOFTWARE.
|
||||
|
||||
namespace WordsToolkit.Scripts.Gameplay.WordValidator
|
||||
{
|
||||
public interface IWordValidator
|
||||
{
|
||||
bool IsWordKnown(string word, string currentLanguage);
|
||||
bool IsExtraWordValid(string word, string language);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c58952109371407183beab35c1dcddb7
|
||||
timeCreated: 1745821517
|
||||
Reference in New Issue
Block a user