一键导入
match3
Match-3 puzzle game architecture — grid system, tile matching, cascade/gravity, special tiles, combo chains, level objectives, lives/energy system.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Match-3 puzzle game architecture — grid system, tile matching, cascade/gravity, special tiles, combo chains, level objectives, lives/energy system.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
DOTween animation library — sequence composition, tween lifecycle, easing, kill strategies. CRITICAL: Always kill tweens in OnDestroy to prevent leaks and errors.
Dialogue tree patterns — ScriptableObject graph, node types (text, choice, condition, event), typewriter effect, localization-ready. Load when implementing NPC conversations.
Generic state machine patterns — IState interface, StateMachine<T>, game state management (menu/gameplay/pause), enemy AI states, hierarchical FSM. Load when implementing state-driven behavior.
2D platformer architecture — tight controls (coyote time, input buffer, variable jump), level design patterns, collectibles, checkpoints, hazards, boss patterns.
Unity physics — non-allocating queries, collision layers, FixedUpdate discipline, continuous collision detection, character controllers, joints.
How the atomic instinct learning system works — observations, distillation, confidence scoring, project vs global scope, and promotion/evolution workflows.
| name | match3 |
| description | Match-3 puzzle game architecture — grid system, tile matching, cascade/gravity, special tiles, combo chains, level objectives, lives/energy system. |
| globs | ["**/Match*.cs","**/Grid*.cs","**/Tile*.cs","**/Board*.cs","**/Puzzle*.cs"] |
public sealed class Board : MonoBehaviour
{
[SerializeField] private int _width = 7;
[SerializeField] private int _height = 9;
[SerializeField] private float _cellSize = 1f;
[SerializeField] private TileDefinition[] _tileTypes;
private Tile[,] _grid;
private void Awake()
{
_grid = new Tile[_width, _height];
}
public Vector3 GridToWorld(int x, int y)
{
float offsetX = (_width - 1) * 0.5f;
float offsetY = (_height - 1) * 0.5f;
return new Vector3((x - offsetX) * _cellSize, (y - offsetY) * _cellSize, 0f);
}
public bool IsValidPosition(int x, int y)
{
return x >= 0 && x < _width && y >= 0 && y < _height;
}
public Tile GetTile(int x, int y)
{
if (!IsValidPosition(x, y)) return null;
return _grid[x, y];
}
public void SetTile(int x, int y, Tile tile)
{
_grid[x, y] = tile;
if (tile != null)
{
tile.GridX = x;
tile.GridY = y;
}
}
}
[CreateAssetMenu(menuName = "Match3/Tile Definition")]
public sealed class TileDefinition : ScriptableObject
{
[SerializeField] private string _tileId;
[SerializeField] private Sprite _sprite;
[SerializeField] private Color _color = Color.white;
[SerializeField] private TileType _type = TileType.Normal;
public string TileId => _tileId;
public Sprite Sprite => _sprite;
public Color Color => _color;
public TileType Type => _type;
}
public enum TileType
{
Normal,
StripedHorizontal,
StripedVertical,
Wrapped,
ColorBomb,
Blocker,
Ice,
Chain
}
public sealed class MatchDetector
{
private readonly Board _board;
private readonly List<MatchResult> _matchBuffer = new(16);
public MatchDetector(Board board) { _board = board; }
public List<MatchResult> FindAllMatches()
{
_matchBuffer.Clear();
FindHorizontalMatches();
FindVerticalMatches();
return _matchBuffer;
}
private void FindHorizontalMatches()
{
for (int y = 0; y < _board.Height; y++)
{
int matchStart = 0;
for (int x = 1; x <= _board.Width; x++)
{
bool matches = x < _board.Width &&
_board.GetTile(x, y) != null &&
_board.GetTile(matchStart, y) != null &&
_board.GetTile(x, y).Definition.TileId ==
_board.GetTile(matchStart, y).Definition.TileId;
if (!matches)
{
int length = x - matchStart;
if (length >= 3)
{
MatchResult match = new MatchResult();
match.Direction = MatchDirection.Horizontal;
for (int mx = matchStart; mx < x; mx++)
{
match.Tiles.Add(_board.GetTile(mx, y));
}
_matchBuffer.Add(match);
}
matchStart = x;
}
}
}
}
private void FindVerticalMatches()
{
for (int x = 0; x < _board.Width; x++)
{
int matchStart = 0;
for (int y = 1; y <= _board.Height; y++)
{
bool matches = y < _board.Height &&
_board.GetTile(x, y) != null &&
_board.GetTile(x, matchStart) != null &&
_board.GetTile(x, y).Definition.TileId ==
_board.GetTile(x, matchStart).Definition.TileId;
if (!matches)
{
int length = y - matchStart;
if (length >= 3)
{
MatchResult match = new MatchResult();
match.Direction = MatchDirection.Vertical;
for (int my = matchStart; my < y; my++)
{
match.Tiles.Add(_board.GetTile(x, my));
}
_matchBuffer.Add(match);
}
matchStart = y;
}
}
}
}
}
After matches are cleared:
// Coroutine-based cascade loop
private IEnumerator CascadeLoop()
{
List<MatchResult> matches = _detector.FindAllMatches();
int comboCount = 0;
while (matches.Count > 0)
{
comboCount++;
yield return StartCoroutine(DestroyMatches(matches, comboCount));
yield return StartCoroutine(ApplyGravity());
yield return StartCoroutine(RefillBoard());
matches = _detector.FindAllMatches();
}
CheckLevelObjective();
EnableInput();
}
| Match | Creates |
|---|---|
| 4 in a row | Striped tile (clears row or column) |
| L or T shape | Wrapped tile (explodes 3x3 area) |
| 5 in a row | Color bomb (clears all of one color) |
| Combo | Effect |
|---|---|
| Striped + Striped | Cross clear (row + column) |
| Striped + Wrapped | 3-row or 3-column clear |
| Wrapped + Wrapped | 5x5 explosion |
| Color bomb + any | Clears all tiles of that color |
| Color bomb + Color bomb | Clears entire board |
public sealed class LivesSystem : MonoBehaviour
{
[SerializeField] private int _maxLives = 5;
[SerializeField] private int _regenMinutes = 30;
private int _currentLives;
private System.DateTime _lastLifeLostTime;
public int CurrentLives => _currentLives;
public bool HasLives => _currentLives > 0;
public void UseLive()
{
if (_currentLives <= 0) return;
_currentLives--;
if (_currentLives < _maxLives)
{
_lastLifeLostTime = System.DateTime.UtcNow;
}
Save();
}
public void CheckRegen()
{
if (_currentLives >= _maxLives) return;
System.TimeSpan elapsed = System.DateTime.UtcNow - _lastLifeLostTime;
int livesRegened = (int)(elapsed.TotalMinutes / _regenMinutes);
if (livesRegened > 0)
{
_currentLives = Mathf.Min(_maxLives, _currentLives + livesRegened);
_lastLifeLostTime = _lastLifeLostTime.AddMinutes(livesRegened * _regenMinutes);
Save();
}
}
private void Save()
{
PlayerPrefs.SetInt("Lives", _currentLives);
PlayerPrefs.SetString("LastLifeLost", _lastLifeLostTime.ToBinary().ToString());
PlayerPrefs.Save();
}
}