Fixed Object Pooling and cascading

This commit is contained in:
2025-12-14 19:19:40 +08:00
parent 68046b8960
commit 95b43ed772
13 changed files with 154 additions and 40 deletions

View File

@@ -19,13 +19,13 @@ MonoBehaviour:
gemsPrefabs:
- {fileID: 3808538059049426536, guid: 724e93e48c6cc0b4ab3d44e5ea34f2ec, type: 3}
- {fileID: 4490600519223577409, guid: 784323496d719684cb6201b200b95864, type: 3}
- {fileID: 745607475630438949, guid: 93bd623174244c047af9ce43cc254c32, type: 3}
- {fileID: 6213027626580313688, guid: 473855e3d0d3c8143836b678c9a1b8b5, type: 3}
- {fileID: 1845825807271331471, guid: 91ba2370328500d4db689dad894b1602, type: 3}
- {fileID: 745607475630438949, guid: 93bd623174244c047af9ce43cc254c32, type: 3}
destroyEffectPrefabs: []
bonusAmount: 0.5
bombChance: 2
dropHeight: 0
dropHeight: 1
gemSpeed: 7
scoreSpeed: 5
width: 7

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 126a4d7fcb1147ed869399287b5b9d18
timeCreated: 1765696722

View File

@@ -0,0 +1,30 @@
using Services;
using UnityEngine;
using Utils;
using VContainer.Unity;
using Views;
namespace Presenter {
public class GemPresenter {
private Gem gem;
private GemView gemView;
public Gem Gem => this.gem;
public GemView GemView => this.gemView;
public GemPresenter(Gem gem, GemView gemView) {
this.gem = gem;
this.gemView = gemView;
}
public void Tick() {
if (this.gemView == null) {
return;
}
if (!this.gem.Position.Compare(this.gemView.transform.localPosition)) {
this.gemView.UpdatePosition(this.gem.Position);
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a89c766a122b41faa5df785594db1b6e
timeCreated: 1765696732

View File

@@ -53,7 +53,6 @@
// this.scGameLogic = _ScGameLogic;
// }
//
// //Not every gem needs this
// private void OnMouseDown()
// {
// if (this.scGameLogic.CurrentState == GameState.Move)
@@ -63,7 +62,6 @@
// }
// }
//
// //Not every gem needs this
// private void CalculateAngle()
// {
// this.swipeAngle = Mathf.Atan2(this.finalTouchPosition.y - this.firstTouchPosition.y, this.finalTouchPosition.x - this.firstTouchPosition.x);
@@ -73,7 +71,6 @@
// MovePieces();
// }
//
// //Not every gem needs this, maybe
// private void MovePieces()
// {
// this.previousPos = this.posIndex;
@@ -110,7 +107,6 @@
// StartCoroutine(CheckMoveCo());
// }
//
// //Why are we checking matches on the Gem itself
// public IEnumerator CheckMoveCo()
// {
// this.scGameLogic.SetState(GameState.Wait);

View File

@@ -1,5 +1,6 @@
using Models;
using Models.Interfaces;
using Presenter;
using ScriptableObjects;
using Services;
using Services.Interfaces;
@@ -28,10 +29,10 @@ namespace Scopes
builder.Register<IScoreService, ScoreService>(Lifetime.Scoped);
builder.Register<IObjectPool<GemView>>(c =>
new ObjectPoolService(this.gameVariables.gemsPrefabs, this.gemsHolder, this.gameVariables.width * this.gameVariables.height),
new ObjectPoolService(this.gameVariables.gemsPrefabs, this.gemsHolder),
Lifetime.Scoped);
builder.Register<IGameBoardService, GameBoardService>(Lifetime.Scoped);
builder.Register<IGameBoardService, GameBoardService>(Lifetime.Scoped).AsImplementedInterfaces();
builder.RegisterEntryPoint<LevelEntryPoint>();
}

View File

@@ -14,7 +14,7 @@ namespace ScriptableObjects {
public float bonusAmount = 0.5f;
public float bombChance = 2f;
public int dropHeight = 1;
public float gemSpeed = 7;
public float gemSpeed = 0.1f;
public float scoreSpeed = 5;
public int width;
public int height;

View File

@@ -3,22 +3,26 @@ using System.Linq;
using Cysharp.Threading.Tasks;
using Enums;
using Models.Interfaces;
using Presenter;
using ScriptableObjects;
using Services.Interfaces;
using UnityEngine;
using Utils;
using VContainer.Unity;
using Views;
using Object = UnityEngine.Object;
using Random = UnityEngine.Random;
namespace Services {
public class GameBoardService : IGameBoardService {
private IGameBoard gameBoard;
private GameVariables gameVariables;
private IMatchService matchService;
private IScoreService scoreService;
private IObjectPool<GemView> objectPool;
private Transform gemsHolder;
public class GameBoardService : IGameBoardService, ITickable {
private readonly IGameBoard gameBoard;
private readonly GameVariables gameVariables;
private readonly IMatchService matchService;
private readonly IScoreService scoreService;
private readonly IObjectPool<GemView> objectPool;
private readonly Transform gemsHolder;
private readonly List<GemPresenter> gemPresenters = new List<GemPresenter>();
public GameBoardService(IGameBoard gameBoard, GameVariables gameVariables, IMatchService matchService, IScoreService scoreSerivce, IObjectPool<GemView> objectPool, Transform gemsHolder) {
this.gameBoard = gameBoard;
@@ -29,8 +33,17 @@ namespace Services {
this.gemsHolder = gemsHolder;
}
public void Tick() {
int i = 0;
foreach (GemPresenter gemPresenter in gemPresenters) {
gemPresenter.Tick();
i++;
}
}
//Instantiates background tiles and calls SpawnGems
//Uses MatchService.MatchesAt to avoid matching Gems
public void Setup() {
Debug.Log("Setting up the board");
for (int x = 0; x < this.gameBoard.Width; x++)
for (int y = 0; y < this.gameBoard.Height; y++)
{
@@ -52,24 +65,31 @@ namespace Services {
}
}
//Uses the ObjectPool to spawn a gem at the given position
public void SpawnGem(Vector2Int position, GemView gemPrefab, GemType gemType) {
Debug.Log("Spawning gem at " + position + " with type " + gemType + "");
if (Random.Range(0, 100f) < this.gameVariables.bombChance)
gemPrefab = this.gameVariables.bombPrefab;
GemView gemView = this.objectPool.Get(gemType, position, this.gameVariables.dropHeight);
gemView.name = "Gem - " + position.x + ", " + position.y;
SetGem(new Vector2Int(position.x,position.y), new Gem(gemType, position));
Gem gem = new Gem(gemType, position);
gemView.Bind(gem);
this.gemPresenters.Add(new GemPresenter(gem, gemView));
SetGem(new Vector2Int(position.x,position.y), gem);
}
//Sets the gem on the GameBoard
public void SetGem(Vector2Int position, Gem gem) {
this.gameBoard.SetGemAt(new Vector2Int(position.x, position.y), gem);
}
//Gets the gem from the GameBoard
public Gem GetGem(Vector2Int position) {
return this.gameBoard.GetGemAt(position);
}
//If there are matches, destroys them and moves the gems down
public void DestroyMatches() {
for (int i = 0; i < this.matchService.CurrentMatches.Count; i++)
if (this.matchService.CurrentMatches[i] != null)
@@ -83,8 +103,6 @@ namespace Services {
public async UniTask MoveGemsDown() {
await UniTask.Delay(2);
// why the delay though?
// yield return new WaitForSeconds(.2f);
int nullCounter = 0;
for (int x = 0; x < this.gameBoard.Width; x++)
@@ -141,6 +159,7 @@ namespace Services {
CheckMisplacedGems();
}
//Checks if there are gems that are not in the board
public void CheckMisplacedGems() {
List<GemView> gemsViews = GemsViews();
@@ -157,8 +176,12 @@ namespace Services {
}
}
foreach (GemView g in gemsViews)
Object.Destroy(g.gameObject);
foreach (GemView g in gemsViews) {
RemovePresenterFor(g);
g.Unbind();
this.objectPool.Release(g);
}
}
public void DestroyMatchedGems(Vector2Int position) {
@@ -167,14 +190,32 @@ namespace Services {
if (currentGem != null)
{
GemView gemView = gemsViews.FirstOrDefault(gv => gv.Gem == currentGem);
if (gemView is null) {
return;
}
//ToDo: Destroy effect
if(this.gameVariables.destroyEffectPrefabs.Length > 0)
Object.Instantiate(this.gameVariables.destroyEffectPrefabs[(int)currentGem.Type], new Vector2(position.x, position.y), Quaternion.identity);
Object.Destroy(gemView!.gameObject);
RemovePresenterFor(gemView);
gemView.Unbind();
this.objectPool.Release(gemView);
SetGem(position, null);
}
}
private void RemovePresenterFor(GemView gemView) {
if (gemView is null) {
return;
}
List<GemPresenter> presentersToRemove = this.gemPresenters.Where(p => p.GemView == gemView).ToList();
foreach (GemPresenter presenter in presentersToRemove) {
this.gemPresenters.Remove(presenter);
}
}
private List<GemView> GemsViews() {
return this.gemsHolder.GetComponentsInChildren<GemView>().ToList();
}

View File

@@ -18,9 +18,7 @@ namespace Services
public void Start()
{
Debug.Log("Level Entry Point");
this.gameBoardService.Setup();
}
}
}

View File

@@ -1,40 +1,37 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Enums;
using Services.Interfaces;
using UnityEngine;
using Utils;
using Views;
using Object = UnityEngine.Object;
using Random = UnityEngine.Random;
namespace Services {
public class ObjectPoolService:IObjectPool<GemView> {
private readonly GemView[] prefabs;
private readonly Transform parent;
private readonly int size;
private readonly Stack<GemView> pool = new Stack<GemView>();
public ObjectPoolService(GemView[] prefabs, Transform parent, int size = 5) {
public ObjectPoolService(GemView[] prefabs, Transform parent) {
this.prefabs = prefabs;
this.parent = parent;
this.size = size;
}
public GemView Get(GemType type, Vector2Int position, float dropHeight) {
int typeAsInt = (int) type;
GemView gemView;
float randomOffset = Random.Range(1f, 2.5f);
Vector2 vector2Position = new Vector2(position.x, position.y + dropHeight * randomOffset);
if (this.pool.Count > 0) {
gemView = this.pool.Pop();
gemView.transform.localPosition = new Vector2(position.x, position.y + dropHeight);
gemView.transform.localPosition = vector2Position;
return gemView;
}
gemView = Object.Instantiate(this.prefabs[typeAsInt], new Vector2(position.x, position.y + dropHeight), Quaternion.identity, this.parent);
gemView = Object.Instantiate(this.prefabs[typeAsInt], vector2Position, Quaternion.identity, this.parent);
return gemView;
}

View File

@@ -0,0 +1,14 @@
using UnityEngine;
namespace Utils {
public static class Vector2IntUtils {
public static bool Compare(this Vector2Int a, Vector2 b) {
Vector2 aVector2 = new Vector2(a.x, a.y);
return Vector2.Distance(aVector2, b) < 0.01f;
}
public static Vector2 ToVector2(this Vector2Int v) {
return new Vector2(v.x, v.y);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2a5066db9b0b4aadba9aa22854bb4cf9
timeCreated: 1765697266

View File

@@ -1,15 +1,43 @@
using Cysharp.Threading.Tasks;
using Enums;
using Services;
using UnityEngine;
using Utils;
namespace Views {
public class GemView : MonoBehaviour {
private Gem gem;
public Gem Gem => this.gem;
public void UpdatePosition(Vector2Int positionBasedOnIndex) {
if (Vector2.Distance(this.transform.position, positionBasedOnIndex) > 0.01f) {
this.transform.position = Vector2.Lerp(this.transform.position, positionBasedOnIndex, 0.1f);
private bool isFalling;
public void Bind(Gem gem) {
this.gem = gem;
this.gameObject.SetActive(true);
}
public void Unbind() {
this.gem = null;
this.gameObject.SetActive(false);
this.isFalling = false;
}
private async UniTask FallDelay() {
float randomDelay = Random.Range(0.05f, 0.5f);
await UniTask.WaitForSeconds(randomDelay);
this.isFalling = true;
}
public async UniTaskVoid UpdatePosition(Vector2Int positionBasedOnIndex) {
if (!this.isFalling) {
await FallDelay();
}
if (!this.isFalling)
return;
if (Vector2.Distance(this.transform.position, positionBasedOnIndex.ToVector2()) > 0.01f) {
this.transform.position = Vector2.Lerp(this.transform.position, positionBasedOnIndex.ToVector2(), 0.01f);
}
}
}