public void ObjectClick(GameBoardObject clickedObject) { Debug.WriteLine($"CLICKED ON {clickedObject.GetType()}"); // Если нет выбранного объекта if (SelectedObject is null) { SelectObject(clickedObject); } // Если выбирается тот же объект else if (clickedObject == SelectedObject) { ClearSelection(); } // Если выбирается другой объект else { // Если соседний объект if ((clickedObject.worldPos - SelectedObject.worldPos).Magnitude == 1.0) { // Запоминаем какие объекты меняем, чтобы потом поменять их обратно objectSwap1 = clickedObject; objectSwap2 = SelectedObject; // Меняем местами их позиции SwapObjectPositions(clickedObject, SelectedObject); ClearSelection(); currentGamePhase = GamePhase.ElementSwap; } // Если не соседний объект else { ClearSelection(); } } }
/// <summary> /// Конструктор. /// </summary> /// <param name="linkedObject">Привязанный обеъект на игровом поле.</param> /// <param name="blocking">Блокирует ли анимация переход в следующее состояние игры.</param> public PulseAnimation(GameBoardObject linkedObject, bool blocking = false) { this.linkedObject = linkedObject; this.blocking = blocking; timePassed = 0.0; active = true; }
/// <summary> /// Конструктор. /// </summary> /// <param name="baseObject">Базовый объект.</param> /// <param name="pos">Клетка игрового поля.</param> /// <param name="spritePos">Позиция спрайта.</param> public BombBonus(GameBoardObject baseObject, Vector2Int pos, Vector2 spritePos) : base(pos, spritePos) { sprite = baseObject.sprite; spriteScale = baseObject.spriteScale; objectType = baseObject.objectType; bombSprite = Game1.bombSprite; }
/// <summary> /// Обновляет состояние игрового поля. /// </summary> public void Update(GameTime gameTime) { // Пытаемся изменить состояние игры ChangeState(); // Действие разрушителей List <Destroyer> destroyerListCopy = new List <Destroyer>(destroyerList); foreach (Destroyer destroyer in destroyerListCopy) { foreach (GameBoardObject gameBoardObject in objectList) { Rectangle boundingBox = gameBoardObject.GetScreenBoundingBox(); bool boundingBoxHit = boundingBox.Contains(GameBoardObject.WorldToScreen(destroyer.spriteWorldPos)); bool alreadyImploding = implodingObjects.Contains(gameBoardObject); if (boundingBoxHit && !alreadyImploding) { implodingObjects.Add(gameBoardObject); ScaleAnimation implodeAnimation = new ScaleAnimation( gameBoardObject, beginScale: 1.0, endScale: 0.0, delay: 0.0, blocking: true, finishedCallback: _ => objectList.Remove(gameBoardObject) ); activeAnimations.Add(implodeAnimation); score++; // Если это LineBonus if (gameBoardObject.GetType() == typeof(LineBonus)) { TriggerLineBonus((LineBonus)gameBoardObject); } // Если это BombBonus if (gameBoardObject.GetType() == typeof(BombBonus)) { TriggerBombBonus((BombBonus)gameBoardObject); } } } } // Обновляем состояние анимаций activeAnimations.ForEach(animation => animation.Update(gameTime)); // Удаляем завершившиеся анимации List <Animation> animationsToDelete = activeAnimations.FindAll(animation => !animation.active); animationsToDelete.ForEach(animation => animation.OnDelete()); activeAnimations = activeAnimations.Except(animationsToDelete).ToList(); // Синхронизируем список implodingObjects с основным списком implodingObjects = implodingObjects.Intersect(objectList).ToList(); // Уменьшаем остаток времени if (currentGamePhase != GamePhase.MainMenu && currentGamePhase != GamePhase.GameOver) { timeRemaining -= gameTime.ElapsedGameTime.TotalSeconds; } }
/// <summary> /// Конструктор. /// </summary> /// <param name="baseObject">Базовый объект.</param> /// <param name="vertical">Направление бонуса.</param> /// <param name="pos">Клетка игрового поля.</param> /// <param name="spritePos">Позиция спрайта.</param> public LineBonus(GameBoardObject baseObject, bool vertical, Vector2Int pos, Vector2 spritePos) : base(pos, spritePos) { sprite = baseObject.sprite; spriteScale = baseObject.spriteScale; objectType = baseObject.objectType; this.vertical = vertical; lineSprite = Game1.lineBonusSprite; }
/// <summary> /// Превращает объект в заданной позиции в Line. /// </summary> public void TurnIntoLine(Vector2Int pos) { GameBoardObject obj = GetObjectAtPosition(pos); LineBonus bomb = new LineBonus(obj, true, obj.worldPos, obj.worldPos); objectList.Remove(obj); objectList.Add(bomb); }
/// <summary> /// Превращает объект в заданной позиции в круг. /// </summary> public void TurnIntoCircle(Vector2Int pos) { GameBoardObject obj = GetObjectAtPosition(pos); CircleObject circleObject = new CircleObject(obj.worldPos, obj.worldPos); objectList.Remove(obj); objectList.Add(circleObject); }
/// <summary> /// Превращает объект в заданной позиции в квадрат. /// </summary> public void TurnIntoSquare(Vector2Int pos) { GameBoardObject obj = GetObjectAtPosition(pos); SquareObject squareObject = new SquareObject(obj.worldPos, obj.worldPos); objectList.Remove(obj); objectList.Add(squareObject); }
/// <summary> /// Конструктор. /// </summary> public GameBoard() { // Создание объектов на игровом поле for (int x = 0; x < 8; x++) { for (int y = 0; y < 8; y++) { Vector2Int pos = new Vector2Int(x, y); GameBoardObject randomObject = CreateRandomElement(pos, pos); objectList.Add(randomObject); } } // Удаление комбинаций образовавшихся после случайной генерации объектов ComboList comboList = GetComboList(); int iteration = 1; while (comboList.Count > 0) { Debug.WriteLine($"Iteration: {iteration}"); GameBoardObject newObject = null; int objectsChanged = 0; foreach (List <GameBoardObject> combination in comboList) { GameBoardObject middleObject = combination[combination.Count / 2]; switch (combination[0].GetType().Name) { case nameof(SquareObject): newObject = new CircleObject(middleObject.worldPos, middleObject.worldPos); break; case nameof(CircleObject): newObject = new TriangleObject(middleObject.worldPos, middleObject.worldPos); break; case nameof(TriangleObject): newObject = new HexagonObject(middleObject.worldPos, middleObject.worldPos); break; case nameof(HexagonObject): newObject = new DiamondObject(middleObject.worldPos, middleObject.worldPos); break; case nameof(DiamondObject): newObject = new SquareObject(middleObject.worldPos, middleObject.worldPos); break; } objectList.Remove(middleObject); objectList.Add(newObject); objectsChanged++; } Debug.WriteLine($"Objects changed: {objectsChanged}"); comboList = GetComboList(); iteration++; } //TurnIntoSquare(new Vector2Int(5, 2)); //TurnIntoBomb(new Vector2Int(5, 2)); //TurnIntoSquare(new Vector2Int(4, 2)); //TurnIntoSquare(new Vector2Int(2, 3)); //TurnIntoSquare(new Vector2Int(3, 3)); //TurnIntoSquare(new Vector2Int(4, 3)); //TurnIntoSquare(new Vector2Int(6, 3)); //TurnIntoSquare(new Vector2Int(4, 4)); //TurnIntoSquare(new Vector2Int(5, 4)); }
/// <summary> /// Создать бонус Bomb. /// </summary> /// <param name="baseObject">Базовый объект.</param> public void CreateBombBonus(GameBoardObject baseObject) { // Создаем объект BombBonus newBombBonus = new BombBonus(baseObject, baseObject.worldPos, baseObject.worldPos); objectList.Add(newBombBonus); // Запускаем анимацию появления ScaleAnimation spawnAnimation = new ScaleAnimation(newBombBonus, 0.0, 1.0, blocking: true); activeAnimations.Add(spawnAnimation); }
/// <summary> /// Рисует объект на экране. /// </summary> public virtual void Draw(SpriteBatch spriteBatch) { // Позиция спрайта на экране Vector2 spriteScreenPos = GameBoardObject.WorldToScreen(spriteWorldPos); // Центр спрайта Vector2 spriteOffset = new Vector2(sprite.Width / 2, sprite.Height / 2); // Масштабирование спрайта float finalSpriteScale = Game1.cellSize / sprite.Width * spriteScale * spriteAnimatedScale * Game1.globalSpriteScale; // Отрисовка спрайта spriteBatch.Draw(sprite, spriteScreenPos, null, Color.White, 0f, spriteOffset, finalSpriteScale, SpriteEffects.None, 0f); }
/// <summary> /// Конструктор. /// </summary> /// <param name="linkedObject">Привязанный обеъект на игровом поле.</param> /// <param name="blocking">Блокирует ли анимация переход в следующее состояние игры.</param> public ScaleAnimation(GameBoardObject linkedObject, double beginScale, double endScale, double delay = 0.0, bool blocking = false, Action <GenericObject> finishedCallback = null) { this.linkedObject = linkedObject; this.beginScale = beginScale; this.endScale = endScale; this.blocking = blocking; this.finishedCallback = finishedCallback; this.delay = delay; duration = 0.3; timePassed = -delay; active = true; }
/// <summary> /// Активирует бонус Bomb. /// </summary> public void TriggerBombBonus(BombBonus bombBonus) { // Объекты вокруг бомбы for (int x = bombBonus.worldPos.x - 1; x <= bombBonus.worldPos.x + 1; x++) { for (int y = bombBonus.worldPos.y - 1; y <= bombBonus.worldPos.y + 1; y++) { if (x == bombBonus.worldPos.x && y == bombBonus.worldPos.y) { continue; } GameBoardObject obj = GetObjectAtPosition(x, y); if (obj is null) { continue; } implodingObjects.Add(obj); ScaleAnimation implodeAnimation = new ScaleAnimation( obj, beginScale: 1.0, endScale: 0.0, delay: 0.25, blocking: true, finishedCallback: _ => objectList.Remove(obj) ); activeAnimations.Add(implodeAnimation); score++; // Если это LineBonus if (obj.GetType() == typeof(LineBonus)) { TriggerLineBonus((LineBonus)obj); } // Если это BombBonus if (obj.GetType() == typeof(BombBonus)) { TriggerBombBonus((BombBonus)obj); } } } // Сама бомба implodingObjects.Add(bombBonus); ScaleAnimation bombImplodeAnimation = new ScaleAnimation( bombBonus, beginScale: 1.0, endScale: 0.0, delay: 0.0, blocking: true, finishedCallback: _ => objectList.Remove(bombBonus) ); activeAnimations.Add(bombImplodeAnimation); score++; }
/// <summary> /// Меняет местами позиции объектов. /// </summary> public void SwapObjectPositions(GameBoardObject object1, GameBoardObject object2) { Debug.WriteLine($"Swapping {object1} and {object2}"); // Меняем позиции Vector2Int object1Pos = object1.worldPos; Vector2Int object2Pos = object2.worldPos; object1.worldPos = object2Pos; object2.worldPos = object1Pos; // Запускаем анимации MoveAnimation moveAnimation1 = new MoveAnimation(object1, object1Pos, object2Pos, duration: 0.3, blocking: true); MoveAnimation moveAnimation2 = new MoveAnimation(object2, object2Pos, object1Pos, duration: 0.3, blocking: true); activeAnimations.Add(moveAnimation1); activeAnimations.Add(moveAnimation2); }
/// <summary> /// Возвращает список комбинаций (в определенном направлении) элементов, стоящих в ряд. /// </summary> /// <param name="vertical">Вертикальное направление, иначе горизонтальное.</param> /// <returns>Список комбинаций в определенном направлении, где каждая комбинация это список объектов, входящих в комбинацию.</returns> private ComboList CheckCombo(bool vertical) { string directionString = vertical ? "Вертикальное" : "Горизонтальное"; // Определение комбо ComboList allComboList = new ComboList(); List <GameBoardObject> tempComboList = new List <GameBoardObject>(); for (int i = 0; i < 8; i++) { tempComboList.Clear(); GameBoardObject.GameBoardObjectType comboType = GameBoardObject.GameBoardObjectType.None; for (int j = 0; j < 8; j++) { GameBoardObject obj = vertical ? GetObjectAtPosition(i, j) : GetObjectAtPosition(j, i); if (obj is null) { comboType = GameBoardObject.GameBoardObjectType.None; continue; } if (obj.objectType == comboType) { tempComboList.Add(obj); } else { if (tempComboList.Count >= 3) { allComboList.Add(new List <GameBoardObject>(tempComboList)); Debug.WriteLine($"{directionString} комбо {comboType} x{tempComboList.Count}"); } tempComboList.Clear(); tempComboList.Add(obj); comboType = obj.objectType; } } if (tempComboList.Count >= 3) { allComboList.Add(new List <GameBoardObject>(tempComboList)); Debug.WriteLine($"{directionString} комбо {comboType} x{tempComboList.Count}"); } } return(allComboList); }
/// <summary> /// Создает случайный элемент. /// </summary> /// <param name="pos">Позиция объекта на игровом поле.</param> public GameBoardObject CreateRandomElement(Vector2Int pos, Vector2 spritePos) { int randomNumber = random.Next(5); GameBoardObject newGameBoardObject = null; switch (randomNumber) { case 0: newGameBoardObject = new SquareObject(pos, spritePos); break; case 1: newGameBoardObject = new CircleObject(pos, spritePos); break; case 2: newGameBoardObject = new TriangleObject(pos, spritePos); break; case 3: newGameBoardObject = new HexagonObject(pos, spritePos); break; case 4: newGameBoardObject = new DiamondObject(pos, spritePos); break; } return(newGameBoardObject); }
/// <summary> /// Создает новые объекты сверху. /// </summary> public void CreateNewObjects() { Debug.WriteLine("Creating new objects"); for (int x = 0; x < 8; x++) { // Сдвигаем висящие элементы int objectsUnder = 0; for (int y = 7; y >= 0; y--) { GameBoardObject gameBoardObject = GetObjectAtPosition(x, y); if (gameBoardObject != null) { Vector2Int newPos = new Vector2Int(gameBoardObject.worldPos.x, 7 - objectsUnder); if (gameBoardObject.worldPos != newPos) { MoveAnimation moveAnimation = new MoveAnimation(gameBoardObject, gameBoardObject.worldPos, newPos, duration: 0.3, blocking: true); activeAnimations.Add(moveAnimation); gameBoardObject.worldPos = newPos; } objectsUnder++; } } // Добавляем новые элементы int newElementCount = 8 - objectsUnder; for (int new_i = 0; new_i < newElementCount; new_i++) { // Позиция объекта и спрайта Vector2Int pos = new Vector2Int(x, new_i); Vector2 spritePos = pos - new Vector2(0, newElementCount); // Создание объекта GameBoardObject randomObject = CreateRandomElement(pos, spritePos); objectList.Add(randomObject); // Запуск анимации MoveAnimation moveAnimation = new MoveAnimation(randomObject, spritePos, pos, duration: 0.3, blocking: true); activeAnimations.Add(moveAnimation); } } }
/// <summary> /// Убрать выделение объекта. /// </summary> public void ClearSelection() { selectedObjectPulseAnimation?.OnDelete(); selectedObjectPulseAnimation = null; SelectedObject = null; }
/// <summary> /// Удаление комбинаций. /// </summary> public void DeleteCombos(ComboList comboList) { // Список удаляемых объектов List <GameBoardObject> objectsToDelete = comboList.SelectMany(tempList => tempList).ToList(); score += objectsToDelete.Count; // Срабатывание бонусов List <LineBonus> lineBonuses = objectsToDelete.FindAll(obj => obj.GetType() == typeof(LineBonus)).Cast <LineBonus>().ToList(); foreach (LineBonus lineBonus in lineBonuses) { TriggerLineBonus(lineBonus); currentGamePhase = GamePhase.Bonus; } List <BombBonus> bombBonuses = objectsToDelete.FindAll(obj => obj.GetType() == typeof(BombBonus)).Cast <BombBonus>().ToList(); foreach (BombBonus bombBonus in bombBonuses) { TriggerBombBonus(bombBonus); currentGamePhase = GamePhase.Bonus; } // Если бонусы активны, то новые создавать нельзя. if (currentGamePhase == GamePhase.Bonus) { return; } // Запуск анимации исчезновения implodingObjects.Clear(); foreach (GameBoardObject obj in objectsToDelete) { implodingObjects.Add(obj); ScaleAnimation implodeAnimation = new ScaleAnimation(obj, 1.0, 0.0, blocking: true, finishedCallback: _ => objectList.Remove(obj)); activeAnimations.Add(implodeAnimation); } // Бонус Bomb // Комбинации из 5 и более List <Vector2Int> newBombPositions = new List <Vector2Int>(); ComboList combinationsOf5AndMore = comboList.FindAll(combination => combination.Count >= 5); List <GameBoardObject> combination = combinationsOf5AndMore.Find(combination => combination.Contains(objectSwap2)); if (combination != null) { CreateBombBonus(objectSwap2); newBombPositions.Add(objectSwap2.worldPos); } // Перекрестные комбинации из 3 и более CrossList crosses = new CrossList(); ComboList verticalCombinations = comboList.FindAll(combination => combination[0].worldPos.x == combination[1].worldPos.x); ComboList horizontalCombinations = comboList.FindAll(combination => combination[0].worldPos.y == combination[1].worldPos.y); foreach (List <GameBoardObject> verticalCombination in verticalCombinations) { foreach (List <GameBoardObject> horizontalCombination in horizontalCombinations) { List <GameBoardObject> intersection = verticalCombination.Intersect(horizontalCombination).ToList(); if (intersection.Count > 0) { GameBoardObject objectAtIntersection = intersection[0]; if (!newBombPositions.Contains(objectAtIntersection.worldPos)) { CreateBombBonus(objectAtIntersection); newBombPositions.Add(objectAtIntersection.worldPos); } } } } // Бонус Line ComboList combinationsOf4 = comboList.FindAll(combination => combination.Count == 4); combination = combinationsOf4.Find(combination => combination.Contains(objectSwap2)); if (combination != null && !newBombPositions.Contains(objectSwap2.worldPos)) { Debug.WriteLine($"Creating line bonus in {objectSwap2.worldPos}"); bool vertical = combination[0].worldPos.x == combination[1].worldPos.x; // Создаем объект LineBonus newLineBonus = new LineBonus(objectSwap2, vertical, objectSwap2.worldPos, objectSwap2.worldPos); objectList.Add(newLineBonus); // Запускаем анимацию появления ScaleAnimation spawnAnimation = new ScaleAnimation(newLineBonus, 0.0, 1.0, blocking: true); activeAnimations.Add(spawnAnimation); } }
/// <summary> /// Выбор объекта. /// </summary> public void SelectObject(GameBoardObject gameBoardObject) { SelectedObject = gameBoardObject; selectedObjectPulseAnimation = new PulseAnimation(SelectedObject); activeAnimations.Add(selectedObjectPulseAnimation); }