С Новым годом! Форум программистов, компьютерный форум, киберфорум
Unity, Unity3D
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 5.00/3: Рейтинг темы: голосов - 3, средняя оценка - 5.00
0 / 0 / 0
Регистрация: 29.03.2023
Сообщений: 4

Реализация функций таймера и отмены хода для игры "Три в ряд или Match3"

29.03.2023, 20:00. Показов 885. Ответов 6

Студворк — интернет-сервис помощи студентам
Захотел попробовать поработать в Unity, увидел подробный гайд по разработке игры "Три в ряд" на С#, в котором уже есть почти все данные и код, только его необходимо было доделать. Доделал игру по гайду, все работает, всё отлично. Захотел реализовать следующие функции:
- Если перестановки не нашелся ни один ряд, который мог бы уничтожиться, ход отменяется
- Игроку дается на ход определенное количество времени.
Если игрок не делает верную перестановку в отведенное время, игра заканчивается и набранные игроком очки записываются в рекорды
Так как в С# я новичок, хотелось бы узнать, как реализовать эти функции для игры, желательно в виде кода с разъяснениями.

В работе есть 2 документа с кодом.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
 
public class Tile : MonoBehaviour {
    private static Color selectedColor = new Color(.5f, .5f, .5f, 1.0f);
    private static Tile previousSelected = null;
 
    private SpriteRenderer render;
    private bool isSelected = false;
 
    private Vector2[] adjacentDirections = new Vector2[] { Vector2.up, Vector2.down, Vector2.left, Vector2.right };
 
    void Awake() {
        render = GetComponent<SpriteRenderer>();
    }
 
    private void Select() {
        isSelected = true;
        render.color = selectedColor;
        previousSelected = gameObject.GetComponent<Tile>();
        SFXManager.instance.PlaySFX(Clip.Select);
    }
 
    private void Deselect() {
        isSelected = false;
        render.color = Color.white;
        previousSelected = null;
    }
 
    void OnMouseDown() {
        // Not Selectable conditions
        if (render.sprite == null || BoardManager.instance.IsShifting) {
            return;
        }
 
        if (isSelected) { // Is it already selected?
            Deselect();
        } else {
            if (previousSelected == null) { // Is it the first tile selected?
                Select();
            } else {
                if (GetAllAdjacentTiles().Contains(previousSelected.gameObject)) { // Is it an adjacent tile?
                    SwapSprite(previousSelected.render);
                    previousSelected.ClearAllMatches();
                    previousSelected.Deselect();
                    ClearAllMatches();
                } else {
                    previousSelected.GetComponent<Tile>().Deselect();
                    Select();
                }
            }
        }
    }
 
    public void SwapSprite(SpriteRenderer render2) {
        if (render.sprite == render2.sprite) {
            return;
        }
 
        Sprite tempSprite = render2.sprite;
        render2.sprite = render.sprite;
        render.sprite = tempSprite;
        SFXManager.instance.PlaySFX(Clip.Swap);
        GUIManager.instance.MoveCounter--; // Add this line here
    }
 
    private GameObject GetAdjacent(Vector2 castDir) {
        RaycastHit2D hit = Physics2D.Raycast(transform.position, castDir);
        if (hit.collider != null) {
            return hit.collider.gameObject;
        }
        return null;
    }
 
    private List<GameObject> GetAllAdjacentTiles() {
        List<GameObject> adjacentTiles = new List<GameObject>();
        for (int i = 0; i < adjacentDirections.Length; i++) {
            adjacentTiles.Add(GetAdjacent(adjacentDirections[i]));
        }
        return adjacentTiles;
    }
 
    private List<GameObject> FindMatch(Vector2 castDir) {
        List<GameObject> matchingTiles = new List<GameObject>();
        RaycastHit2D hit = Physics2D.Raycast(transform.position, castDir);
        while (hit.collider != null && hit.collider.GetComponent<SpriteRenderer>().sprite == render.sprite) {
            matchingTiles.Add(hit.collider.gameObject);
            hit = Physics2D.Raycast(hit.collider.transform.position, castDir);
        }
        return matchingTiles;
    }
 
    private void ClearMatch(Vector2[] paths) {
        List<GameObject> matchingTiles = new List<GameObject>();
        for (int i = 0; i < paths.Length; i++) { matchingTiles.AddRange(FindMatch(paths[i])); }
        if (matchingTiles.Count >= 2) {
            for (int i = 0; i < matchingTiles.Count; i++) {
                matchingTiles[i].GetComponent<SpriteRenderer>().sprite = null;
            }
            matchFound = true;
        }
    }
 
    private bool matchFound = false;
    public void ClearAllMatches() {
        if (render.sprite == null)
            return;
 
        ClearMatch(new Vector2[2] { Vector2.left, Vector2.right });
        ClearMatch(new Vector2[2] { Vector2.up, Vector2.down });
        if (matchFound) {
            render.sprite = null;
            matchFound = false;
            StopCoroutine(BoardManager.instance.FindNullTiles()); //Add this line
            StartCoroutine(BoardManager.instance.FindNullTiles()); //Add this line
            SFXManager.instance.PlaySFX(Clip.Clear);
        }
    }
 
}
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
 
public class BoardManager : MonoBehaviour {
    public static BoardManager instance;
    public List<Sprite> characters = new List<Sprite>();
    public GameObject tile;
    public int xSize, ySize;
 
    private GameObject[,] tiles;
 
    public bool IsShifting { get; set; }
 
    void Start () {
        instance = GetComponent<BoardManager>();
 
        Vector2 offset = tile.GetComponent<SpriteRenderer>().bounds.size;
        CreateBoard(offset.x, offset.y);
    }
 
    private void CreateBoard (float xOffset, float yOffset) {
        tiles = new GameObject[xSize, ySize];
 
        float startX = transform.position.x;
        float startY = transform.position.y;
 
        Sprite[] previousLeft = new Sprite[ySize]; // Add this line
        Sprite previousBelow = null; // Add this line
 
        for (int x = 0; x < xSize; x++) {
            for (int y = 0; y < ySize; y++) {
                GameObject newTile = Instantiate(tile, new Vector3(startX + (xOffset * x), startY + (yOffset * y), 0), tile.transform.rotation);
                tiles[x, y] = newTile;
                newTile.transform.parent = transform; // Add this line
 
                List<Sprite> possibleCharacters = new List<Sprite>();
                possibleCharacters.AddRange(characters);
 
                possibleCharacters.Remove(previousLeft[y]);
                possibleCharacters.Remove(previousBelow);
 
                Sprite newSprite = possibleCharacters[Random.Range(0, possibleCharacters.Count)];
                newTile.GetComponent<SpriteRenderer>().sprite = newSprite;
                previousLeft[y] = newSprite;
                previousBelow = newSprite;
            }
        }
    }
 
    public IEnumerator FindNullTiles() {
        for (int x = 0; x < xSize; x++) {
            for (int y = 0; y < ySize; y++) {
                if (tiles[x, y].GetComponent<SpriteRenderer>().sprite == null) {
                    yield return StartCoroutine(ShiftTilesDown(x, y));
                    break;
                }
            }
        }
 
        for (int x = 0; x < xSize; x++) {
            for (int y = 0; y < ySize; y++) {
                tiles[x, y].GetComponent<Tile>().ClearAllMatches();
            }
        }
    }
 
    private IEnumerator ShiftTilesDown(int x, int yStart, float shiftDelay = .03f) {
        IsShifting = true;
        List<SpriteRenderer> renders = new List<SpriteRenderer>();
        int nullCount = 0;
 
        for (int y = yStart; y < ySize; y++) {
            SpriteRenderer render = tiles[x, y].GetComponent<SpriteRenderer>();
            if (render.sprite == null) {
                nullCount++;
            }
            renders.Add(render);
        }
 
        for (int i = 0; i < nullCount; i++) {
            GUIManager.instance.Score += 50; // Add this line here
            yield return new WaitForSeconds(shiftDelay);
            for (int k = 0; k < renders.Count - 1; k++) {
                renders[k].sprite = renders[k + 1].sprite;
                renders[k + 1].sprite = GetNewSprite(x, ySize - 1);
            }
        }
        IsShifting = false;
    }
 
    private Sprite GetNewSprite(int x, int y) {
        List<Sprite> possibleCharacters = new List<Sprite>();
        possibleCharacters.AddRange(characters);
 
        if (x > 0) {
            possibleCharacters.Remove(tiles[x - 1, y].GetComponent<SpriteRenderer>().sprite);
        }
        if (x < xSize - 1) {
            possibleCharacters.Remove(tiles[x + 1, y].GetComponent<SpriteRenderer>().sprite);
        }
        if (y > 0) {
            possibleCharacters.Remove(tiles[x, y - 1].GetComponent<SpriteRenderer>().sprite);
        }
 
        return possibleCharacters[Random.Range(0, possibleCharacters.Count)];
    }
 
 
}
0
Лучшие ответы (1)
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
29.03.2023, 20:00
Ответы с готовыми решениями:

Реализация таймера для нескольких участников игры
Вот мини игра: #include &lt;iostream&gt; #include &lt;ctime&gt; using namespace std; int my_point = 100; int virus_point = 100; int...

MVVM Light реализация вывода хода выполнения программы в TextBox или др. контролл
Здравствуйте. Стоит задача реализовать простой вывод хода выполнения программы (Открытие, закрытие, ошибка). Примерно то что мне нужно...

Создание игры три в ряд
Здравствуйте! Хочу создать игру три в ряд, но уже в самом начале столкнулся с проблемой. Мне нужно, чтобы при генерации массива по циклу...

6
 Аватар для samana
2639 / 1567 / 853
Регистрация: 23.02.2019
Сообщений: 3,876
31.03.2023, 21:23
Цитата Сообщение от fayen Посмотреть сообщение
- Игроку дается на ход определенное количество времени.
Если игрок не делает верную перестановку в отведенное время, игра заканчивается
Здесь ещё дополняется нюанс - нужно убедиться, что верная перестановка существует.
0
 Аватар для samana
2639 / 1567 / 853
Регистрация: 23.02.2019
Сообщений: 3,876
01.04.2023, 17:03
Лучший ответ Сообщение было отмечено fayen как решение

Решение

Цитата Сообщение от fayen Посмотреть сообщение
- Если перестановки не нашелся ни один ряд, который мог бы уничтожиться, ход отменяется
Тогда сначала нужно предварительно поменять спрайты местами, затем проверить наличие комбинаций и если комбинаций не нашлось, то сделать отмену и обратно поменять спрайты.

Функционала, который может просто подсчитать комбинации в коде проекта нет. Там при наличии комбинации происходят сразу всякие удаления и перестановки. А вам нужно просто узнать - есть или нет комбинация.

Для этого напишите публичный метод, который кустарным путём проверяет наличие комбинаций.
В классе Tile добавьте

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public bool CombinationExists()
{
    var horizontal = new[] { Vector2.left, Vector2.right };
    var vertical = new[] { Vector2.up, Vector2.down };
 
    List<GameObject> matchingTiles = new List<GameObject>();
 
    for (int i = 0; i < horizontal.Length; i++)
        matchingTiles.AddRange(FindMatch(horizontal[i]));
 
    if (matchingTiles.Count >= 2)
        return true;
 
    matchingTiles.Clear();
    for (int i = 0; i < vertical.Length; i++)
        matchingTiles.AddRange(FindMatch(vertical[i]));
 
    if (matchingTiles.Count >= 2)
        return true;
 
    return false;
 
}
И в том же классе Tile измените главный метод OnMouseDown на следующее

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
void OnMouseDown()
{
    // Not Selectable conditions
    if (render.sprite == null || BoardManager.instance.IsShifting)
    {
        return;
    }
 
    // Is it already selected?
    if (isSelected)
    {
        Deselect();
        return;
    }
 
 
    // Is it the first tile selected?
    if (previousSelected == null)
    {
        Select();
        return;
    }
 
 
    // Is it an adjacent tile?
    if (GetAllAdjacentTiles().Contains(previousSelected.gameObject))
    {
        Sprite prev = previousSelected.render.sprite;
        Sprite current = render.sprite;
 
        previousSelected.render.sprite = current;
        render.sprite = prev;
 
        if (CombinationExists() || previousSelected.CombinationExists())
        {
            //print("exist");
            previousSelected.render.sprite = prev;
            render.sprite = current;
 
            SwapSprite(previousSelected.render);
            previousSelected.ClearAllMatches();
            previousSelected.Deselect();
            ClearAllMatches();
        }
        else
        {
            //print("not exist");
            previousSelected.render.sprite = prev;
            render.sprite = current;
 
            previousSelected.GetComponent<Tile>().Deselect();
            Select();
        }
    }
    else
    {
        previousSelected.GetComponent<Tile>().Deselect();
        Select();
    }
 
}
Это должно работать. То-есть когда происходит клик по второму спрайту и если перестановка не соберёт комбинацию, то перестановка и не произойдёт. Но возможно какие-то подводные камни не учтены, надо тестировать глубже. Да и вообще можно улучшать до бесконечности.
1
0 / 0 / 0
Регистрация: 29.03.2023
Сообщений: 4
01.04.2023, 21:28  [ТС]
Интегрировал в код, всё работает, спасибо. Есть ещё вопрос, как реализовать конец игры, если на поле не осталось возможных сыгровок?
0
 Аватар для samana
2639 / 1567 / 853
Регистрация: 23.02.2019
Сообщений: 3,876
02.04.2023, 06:22
Думаю, что делать конец игры из-за отсутствия комбинаций - плохая идея. Ведь игрок не виноват, что алгоритм неудачно расположил элементы и не оставил возможности для хода. Ведь такой "конец" может наступить в любой момент, например сразу в начале игры.
Обычно в таких тупиковых ситуациях поле просто перемешивается, что бы появились ходы и игрок продолжил.
Для этого напишите алгоритм, который проверит наличие хотя бы одной возможной комбинации на поле. Как это сделать? Хороший вопрос) здесь даже проблема не в с#, а в логике. У вас есть идеи?
0
0 / 0 / 0
Регистрация: 29.03.2023
Сообщений: 4
02.04.2023, 15:05  [ТС]
Почитал в интернете, что можно использовать алгоритм Фишера-Йетса (Fisher-Yates shuffle), который случайным образом переставляет элементы массива. Но не понял, как его использовать.
0
 Аватар для samana
2639 / 1567 / 853
Регистрация: 23.02.2019
Сообщений: 3,876
02.04.2023, 15:44
Так это ведь здесь не причём. Перемешать множество довольно легко. Суть в том, чтобы из перемешанных элементов гарантированно можно было собрать хотя бы одну комбинацию.
Думаю будет легче просто перемешивать поле до тех пор, пока не появится нужное условие. Но тогда нужно все таки написать алгоритм, которые проверит существование на поле возможной комбинации о котором я неоднократно повторяю.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
02.04.2023, 15:44
Помогаю со студенческими работами здесь

Посредством определения соответствующей функций для n членов ряда определить, сходится или расходится числовой ряд
Посредством определения соответствующей функций для n членов ряда определить, сходится или расходится числовой ряд a1+a2+a3...+an+... ...

как написать кнопку для очистки или отмены действий
Не могу написать правильно код для кнопки &quot; очистка&quot;. Мне нужно чтобы я вводила оценки, он мне подсчитывал средний бал, после чего я...

Реализация отмены....!!!
Привет всем.....у меня такой вопрос: Я делаю графический редактор..!!!! Может кто нибудь знает как реализовать отмену действий в нём??? ...

Реализация таймера для программы тетрис
Здравствуйте. Возникла необходимость написать таймер обратного отсчета для реализации движения фигуры в тетрисе с определенным интервалом...

Реализация отмены через CTRL + Z в Python
Данный код реализует рисование прямоугольников посредством движения мыши. Помогите реализовать отмену действия через CTRL + Z, как показано...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
7
Ответ Создать тему
Новые блоги и статьи
изучаю kubernetes
lagorue 13.01.2026
А пригодятся-ли мне знания kubernetes в России?
сукцессия микоризы: основная теория в виде двух уравнений.
anaschu 11.01.2026
https:/ / rutube. ru/ video/ 7a537f578d808e67a3c6fd818a44a5c4/
WordPad для Windows 11
Jel 10.01.2026
WordPad для Windows 11 — это приложение, которое восстанавливает классический текстовый редактор WordPad в операционной системе Windows 11. После того как Microsoft исключила WordPad из. . .
Classic Notepad for Windows 11
Jel 10.01.2026
Old Classic Notepad for Windows 11 Приложение для Windows 11, позволяющее пользователям вернуть классическую версию текстового редактора «Блокнот» из Windows 10. Программа предоставляет более. . .
Почему дизайн решает?
Neotwalker 09.01.2026
В современном мире, где конкуренция за внимание потребителя достигла пика, дизайн становится мощным инструментом для успеха бренда. Это не просто красивый внешний вид продукта или сайта — это. . .
Модель микоризы: классовый агентный подход 3
anaschu 06.01.2026
aa0a7f55b50dd51c5ec569d2d10c54f6/ O1rJuneU_ls https:/ / vkvideo. ru/ video-115721503_456239114
Owen Logic: О недопустимости использования связки «аналоговый ПИД» + RegKZR
ФедосеевПавел 06.01.2026
Owen Logic: О недопустимости использования связки «аналоговый ПИД» + RegKZR ВВЕДЕНИЕ Введу сокращения: аналоговый ПИД — ПИД регулятор с управляющим выходом в виде числа в диапазоне от 0% до. . .
Модель микоризы: классовый агентный подход 2
anaschu 06.01.2026
репозиторий https:/ / github. com/ shumilovas/ fungi ветка по-частям. коммит Create переделка под биомассу. txt вход sc, но sm считается внутри мицелия. кстати, обьем тоже должен там считаться. . . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru