Unity中检测字母GameObject位置及组成单词的实现求助
Hey there! Let's tackle that word detection problem you're stuck on for your Tetris-style word game. I’ve broken down a practical, step-by-step solution that fits with your existing code structure.
First, Fix Your Grid & Letter Data Structure
Right now your grid stores Transform references, but we need to track the actual letter each block represents. Let's add a small component to each letter GameObject to hold this data:
using UnityEngine; public class LetterBlock : MonoBehaviour { public char letter; // Set this in the Inspector for each letter block public bool isLocked = false; // Marks if the block has settled and can't move }
Next, update your grid to store LetterBlock references instead of transforms, and fix the grid size (your current width=160/height=280 are world units—since you move blocks by 20 units, we need grid cells based on that):
using System.Collections; using System.Collections.Generic; using UnityEngine; public class TetrisBlock : MonoBehaviour { private float previousTime; public float fallTime = 0.8f; // Adjust grid size to match your 20-unit block spacing public static int gridWidth = 8; // 160 world units / 20 per cell public static int gridHeight = 14; // 280 world units / 20 per cell private static LetterBlock[,] grid = new LetterBlock[gridWidth, gridHeight]; private LetterBlock myLetter; void Start() { myLetter = GetComponent<LetterBlock>(); } // Rest of your Update code stays mostly the same—we'll adjust position checks below
Update your ValidMove and AddToGrid methods to use proper grid coordinates (converting world position to grid cells):
Vector2Int GetGridPosition(Vector3 worldPos) { // Convert 20-unit world steps to 0-based grid indices int x = Mathf.RoundToInt(worldPos.x / 20); int y = Mathf.RoundToInt(worldPos.y / 20); return new Vector2Int(x, y); } bool ValidMove() { Vector2Int gridPos = GetGridPosition(transform.position); int x = gridPos.x; int y = gridPos.y; // Check if we're outside grid bounds if (x < 0 || x >= gridWidth || y < 0 || y >= gridHeight) return false; // Check if the grid cell is already occupied if (grid[x, y] != null) return false; return true; } void AddToGrid() { Vector2Int gridPos = GetGridPosition(transform.position); int x = gridPos.x; int y = gridPos.y; if (x >= 0 && x < gridWidth && y >= 0 && y < gridHeight) { grid[x, y] = myLetter; myLetter.isLocked = true; // Lock the block once it settles } }
Core Word Detection Logic
You have two main options for detecting words: player-initiated selection (e.g., clicking/dragging to select letters) or automatic scanning (after a block settles). Let's cover both.
1. Player-Initiated Word Selection
If you want players to manually select adjacent letters to form words:
- Track the sequence of letters the player selects
- Verify the letters are adjacent (adjust rules for diagonal adjacency if needed)
- Check if the resulting string is in your valid word list
Add these methods to your TetrisBlock class (or a dedicated GameManager):
// Store your valid words here (load from a text file in production!) public HashSet<string> validWords = new HashSet<string> { "sea", "weed", "seaweed", "eat", "seed" }; public string targetWord = "seaweed"; // Your win condition word public bool CheckSelectedWord(List<LetterBlock> selectedLetters) { if (selectedLetters.Count < 2) return false; // Ignore single letters // Ensure all selected blocks are locked (settled) foreach (var letter in selectedLetters) { if (!letter.isLocked) return false; } // Check if each letter is adjacent to the previous one Vector2Int prevPos = GetGridPosition(selectedLetters[0].transform.position); for (int i = 1; i < selectedLetters.Count; i++) { Vector2Int currentPos = GetGridPosition(selectedLetters[i].transform.position); if (!AreAdjacent(prevPos, currentPos)) return false; prevPos = currentPos; } // Build the word string (lowercase to avoid case mismatches) string word = ""; foreach (var letter in selectedLetters) { word += letter.letter.ToString().ToLower(); } // Check if the word is valid if (validWords.Contains(word)) { ScoreAndRemoveWord(word, selectedLetters); return true; } return false; } // Define adjacency (adjust to include diagonals if you want) bool AreAdjacent(Vector2Int pos1, Vector2Int pos2) { return (Mathf.Abs(pos1.x - pos2.x) == 1 && pos1.y == pos2.y) || (Mathf.Abs(pos1.y - pos2.y) == 1 && pos1.x == pos2.x); } void ScoreAndRemoveWord(string word, List<LetterBlock> selectedLetters) { // Calculate score (customize this to your rules) int score = word.Length * 10; if (word == targetWord.ToLower()) { score += 1000; Debug.Log("YOU WIN!"); // Trigger win condition logic here } // Update your UI score here (e.g., UIManager.Instance.AddScore(score)) // Remove the selected blocks from the grid and destroy them foreach (var letter in selectedLetters) { Vector2Int pos = GetGridPosition(letter.transform.position); grid[pos.x, pos.y] = null; Destroy(letter.gameObject); } // Make blocks above fall into empty spaces (like Tetris) HandleFallingBlocks(); } void HandleFallingBlocks() { // Iterate each column from bottom to top for (int x = 0; x < gridWidth; x++) { int emptyY = -1; for (int y = 0; y < gridHeight; y++) { if (grid[x, y] == null) { emptyY = y; } else if (emptyY != -1) { // Move the block down to the empty space grid[x, emptyY] = grid[x, y]; grid[x, y] = null; grid[x, emptyY].transform.position = new Vector3(emptyY * 20, x * 20, 0); emptyY++; } } } }
2. Automatic Word Scanning
If you want to automatically detect words after a block settles, use a backtracking algorithm to scan all possible adjacent letter sequences:
void AutoDetectWords() { bool[,] visited = new bool[gridWidth, gridHeight]; foreach (int x in Enumerable.Range(0, gridWidth)) { foreach (int y in Enumerable.Range(0, gridHeight)) { if (grid[x, y] != null && grid[x, y].isLocked && !visited[x, y]) { BacktrackScan(x, y, visited, ""); } } } } void BacktrackScan(int x, int y, bool[,] visited, string currentWord) { if (x < 0 || x >= gridWidth || y < 0 || y >= gridHeight || visited[x, y] || grid[x, y] == null) return; visited[x, y] = true; currentWord += grid[x, y].letter.ToString().ToLower(); // Check if current string is a valid word if (validWords.Contains(currentWord)) { // For automatic detection, you'll need to track which blocks form the word to remove them // (This requires modifying the backtrack to track positions instead of just the string) Debug.Log($"Found word: {currentWord}"); ScoreAndRemoveWord(currentWord, GetBlocksForWord(currentWord)); // Implement GetBlocksForWord based on your needs } // Scan all 4 adjacent directions (add diagonals if needed) int[] dx = { -1, 1, 0, 0 }; int[] dy = { 0, 0, -1, 1 }; for (int i = 0; i < 4; i++) { BacktrackScan(x + dx[i], y + dy[i], visited, currentWord); } // Unmark for backtracking visited[x, y] = false; }
Call AutoDetectWords() right after AddToGrid() in your Update loop:
if (!ValidMove()) { transform.position -= new Vector3(0, -20, 0); AddToGrid(); this.enabled = false; FindObjectOfType<SpawnerTetromino>().NewTetromino(); AutoDetectWords(); // Scan for words after a block settles }
Key Notes
- Word List: In production, load your valid words from a text file (e.g., a newline-separated list) instead of hardcoding them.
- Adjacency Rules: Adjust the
AreAdjacentmethod if you want to allow diagonal letter connections. - Performance: For large grids, optimize the automatic scan by limiting word length (e.g., only scan up to 10 letters) to avoid slowdowns.
内容的提问来源于stack exchange,提问作者mycelium net

