Отказ от ответственности: это мой первый опыт кодирования и создания игры с помощью SwiftUI, поэтому знания, которыми я делюсь здесь, основаны на моем опыте.

На днях мне предложили сделать игру. Я родом из дизайнера, и мысль о создании приложения уже достаточно сложна для меня. Я решил сделать игру «Камень, ножницы, бумага», потому что думал, что это будет легко. Это не 🥹.

Итак, это моя история о выходе из зоны комфорта, о том, как я справилась со своей борьбой, и я дам несколько ресурсов, которые могут помочь и вам!

Концепция игры

Моя игра — Камень, ножницы, бумага, но в ней один игрок. Те же правила и геймплей, что и в классической игре «камень, ножницы, бумага». Но здесь игроки не будут принимать решения давать противнику камень, бумагу или ножницы, потому что противника нет.

Будет 10 раундов, и игроки должны выбрать победителя из отображаемого результата игры «камень, ножницы, бумага» как можно быстрее. По сути, это отзывчивая игра.

Как работает игра

Вот некоторые вещи, которые я сделал, чтобы сделать игру играбельной.

Составьте список всех возможных условий выигрыша

Чтобы создать ощущение неожиданности, результат игры должен быть случайным. Мне нужно было записать все условия победы в игре «камень, ножницы, бумага» как aList. Есть 6 условий, но так как есть кнопка [Draw], то 9 условий.

Я положил эти conditions в list . Вот так выглядит код.

import Foundation

class WinningConditions: Identifiable {
    var id = UUID()
    let result: GameResult
    let left: SceneKitView
    let right: SceneKitView
    
    init(result: GameResult, left: SceneKitView, right: SceneKitView) {
        self.id = UUID()
        self.result = result
        self.left = left
        self.right = right
    }
}

struct winningConditionsLists {
    static let lists: [WinningConditions] = [
        WinningConditions(
            result: .leftWins,
            left: SceneKitView(modelName: "Rock"),
            right: SceneKitView(modelName: "Scissors")
        ),
        WinningConditions(
            result: .leftWins,
            left: SceneKitView(modelName: "Scissors"),
            right: SceneKitView(modelName: "Paper")
        ),
        WinningConditions(
            result: .leftWins,
            left: SceneKitView(modelName: "Paper"),
            right: SceneKitView(modelName: "Rock")
        ),
        WinningConditions(
            result: .rightWins,
            left: SceneKitView(modelName: "Scissors"),
            right: SceneKitView(modelName: "Rock")
        ),
        WinningConditions(
            result: .rightWins,
            left: SceneKitView(modelName: "Rock"),
            right: SceneKitView(modelName: "Paper")
        ),
        WinningConditions(
            result: .rightWins,
            left: SceneKitView(modelName: "Paper"),
            right: SceneKitView(modelName: "Scissors")
        ),
        WinningConditions(
            result: .draw,
            left: SceneKitView(modelName: "Rock"),
            right: SceneKitView(modelName: "Rock")
        ),
        WinningConditions(
            result: .draw,
            left: SceneKitView(modelName: "Scissors"),
            right: SceneKitView(modelName: "Scissors")
        ),
        WinningConditions(
            result: .draw,
            left: SceneKitView(modelName: "Paper"),
            right: SceneKitView(modelName: "Paper")
        )
    ]
}

Класс WinningConditions представляет набор условий в игре. Он имеет такие свойства, как result, left и right, которые содержат экземпляры перечисления GameResult и SceneKitView.

result: Это свойство типа GameResult содержит результат игры.

enum GameResult: String, CaseIterable {
    case leftWins = "Left Wins"
    case rightWins = "Right Wins"
    case draw = "Draw"
}

Тип необработанного значения установлен на String, а протокол CaseIterable принят, чтобы разрешить итерацию по всем случаям.

Выберите случайным образом условие победы из списка

Чтобы отобразить условие случайного выигрыша, мне нужно было заставить function случайным образом выбрать один condition из list.

func randomizeWinningCondition() {
        let randomWinningCondition = winningConditionsLists.lists.randomElement()!
        print("Correct answer:", randomWinningCondition.result,
              "Left", randomWinningCondition.left,
              "Right", randomWinningCondition.right
        )
        winningCondition = randomWinningCondition
    }

randomizeWinningCondition() предназначен для случайного выбора условия выигрыша из массива winningConditionsLists.lists и присвоения его свойству winningCondition. Это похоже на удачный розыгрыш, чтобы решить, какое условие победит!

Перечисление GameResult используется в функции handleResultButtonPressed для обработки выбора пользователя.

Отобразите условие выигрыша с помощью 3 кнопок, чтобы выбрать выигрыш слева, выигрыш справа или ничью.

По сути, это код пользовательского интерфейса из трех кнопок, представляющих различные результаты игры. При нажатии кнопки вызывается функция handleResultButtonPressed с соответствующим значением GameResult.

HStack {
   Spacer()
   // Left wins button
   CustomButton(action: {
      handleResultButtonPressed(userSelection: .leftWins)
                                
   }, label: "Left Wins", color: .red, padding: 
      EdgeInsets(top: 10, leading: 16, bottom: 10, trailing: 16))
                            
   Spacer()
   // Draw button
   CustomButton(action: {
      handleResultButtonPressed(userSelection: .draw)
                                
   }, label: "Draw", color: .yellow, padding: 
      EdgeInsets(top: 30, leading: 16, bottom: 30, trailing: 16))
                            
   Spacer()
   // Right wins button
   CustomButton(action: {
      handleResultButtonPressed(userSelection: .rightWins)
                                
   }, label: "Right Wins", color: .blue, padding: 
      EdgeInsets(top: 10, leading: 16, bottom: 10, trailing: 16))
                            
   Spacer()
   }
}

Сравните ввод пользователя с выбранным условием выигрыша и соответственно добавьте или удалите баллы

Эта функция обрабатывает действия и логику, связанные с выбором пользователя.

func handleResultButtonPressed(userSelection: GameResult) {
        if round >= 10 {
            finishGame()
        } else {
            round += 1
            lastMS = timerValue
        }
        
        if winningCondition?.result == userSelection {
            print("You won")
            
            if timerValue <= 150 {
                score += 4
                point = "+4"
             
            } else {
                score += 1
                point = "+1"
               
            }
        } else {
            print("You lost")
            score -= 3
            point = "-3"
        }
        
        randomizeWinningCondition()
        
        isShow = true
        DispatchQueue.main.asyncAfter(deadline: .now() + 1){
            isShow = false
            timerValue = 0
        }
        
    }

Этот код представляет реализацию функции handleResultButtonPressed. Он также управляет подсчетом раундов, обновляет счет, генерирует новые условия выигрыша и запускает обновления или анимацию пользовательского интерфейса.

Случайным образом выберите другое условие победы и повторите в течение 10 раундов.

Этот код по существу управляет игрой. Эти функции обеспечивают необходимую функциональность для запуска новой игры и завершения игры.

func startGame() {
    round = 1
    randomizeWinningCondition()
    timerValue = 0
}
    
func finishGame() {
    isFinished = true
    timerValue = 0
    print("Game over!")
}

В функции startGame() Он вызывает функцию randomizeWinningCondition(), которая случайным образом выбирает набор условий выигрыша для текущего раунда. Эта функция отвечает за обновление переменной winningCondition новым набором условий.

Сводка

Подводя итог, я начну со списка условий выигрыша в игре. Затем я рандомизирую эти выигрышные условия, выбираю одно условие из списка и отображаю его с помощью 3D-ресурсов. Каждый раунд будет иметь случайный результат игры, и игрок может нажать любую кнопку, чтобы указать, какая сторона побеждает. После 10 раундов я накапливаю очки, которые игрок получил в качестве окончательного результата. Когда игра закончится, будет страница, на которой будет отображаться оценка, которую вы получите в зависимости от вашего счета.

Моя борьба

Работая над этой игрой, я столкнулся с проблемами, с которыми никогда раньше не сталкивался.

Я впервые делаю игру

Будучи новичком, я справился с процессом создания идеи, у меня не возникло проблем с созданием прототипа игры, и я могу определить основной цикл игровой механики. Однако я совершенно не представлял, с чего начать в своем коде. У меня были проблемы с программированием, так как я делал это впервые.

Единственное, что пришло в голову, это создать функцию для кнопки и настроить таймер. Также был момент, когда случайно сгенерированное условие не соответствовало соответствующим отображаемым активам. Игра помечала правильные ответы как неправильные, а неправильные ответы как правильные. После отладки путем печати условия выигрыша до и после ввода пользователя я понял, что рандомизировал условие выигрыша перед оценкой ввода пользователя.

Мне нужно было 2 наставника и 3 друга, чтобы помочь мне отладить эту проблему 😭. Поздоровайтесь с ними 🫡

Нехватка ресурсов

Мне также было трудно найти адекватные ресурсы. У меня были проблемы с пониманием и реализацией игровой логики. Это включает в себя определение условий выигрыша, отслеживание счета, управление раундами и правильную обработку выбора пользователя.

Ключевой проблемой была рандомизация. Создание случайных условий выигрыша и обеспечение честного игрового процесса необходимы, но у меня действительно были проблемы с интерпретацией и выполнением функции randomElement(). Спасибо моим друзьям, которые помогли мне понять мою игровую логику и логический процесс рандомизации условий выигрыша. После решения этой проблемы я смог справиться с некоторыми из своих проблем с кодированием, включая пользовательский интерфейс.

Трудности с поиском активов

Первый план состоял в том, чтобы использовать ассеты 2D-игры с использованием фреймворка SpriteKit. Но вот в чем дело: мой наставник сказал мне: «Это недостаточно сложно». Поэтому они порекомендовали мне оживить ситуацию, используя 3D-ресурсы. Таким образом, я мог глубже изучить SceneKit и лучше понять, на что он способен.

Чтобы использовать 3D-активы, у меня было два варианта; сделать самому или получить бесплатные коммерческие активы от Sketchfab. Я пробовал оба.

Я попытался сделать свои собственные 3D-ассеты, используя LiDAR для сканирования рук с помощью приложения Polycam. Отсканировать всю руку и палец, не получив разрозненных результатов, довольно сложно. Я сдался и попытался найти доступные 3D-ресурсы в Sketchfab. Как я и ожидал, никто специально не делал моделей жестов рук для игры камень-ножницы-бумага. Тем не менее, я нашел именно то, что мне было нужно, но он требовал оплаты. Итак, что я сделал? Вместо этого у меня возникла идея использовать 3D-модели из камня, бумаги и ножниц. Задача решена!

Я нашел модели, которые соответствуют моим предпочтениям. Обязательно загрузите формат файла .usdz, поскольку SceneKit поддерживает исключительно формат файла .usdz.

Использование 3D-моделей в качестве игровых ресурсов — отличная идея. По словам игроков, с 3D-объектами им приходилось дважды думать, прежде чем нажимать нужную кнопку.

Прототип игры

У меня есть только одна неделя, чтобы эта игра заработала и была готова к показу. К счастью, мне удалось запустить игровой процесс всего за день до выставки приложений. К сожалению, у меня было мало времени, и я не мог приложить достаточно усилий, чтобы сделать пользовательский интерфейс таким же визуально привлекательным, как в моем высокоточном дизайне приложения.

Я использую Figma для создания высококачественного дизайна. Прототип приложения выглядит так.

Мой игровой репозиторий

Создавая эту игру, я многому научился. Должен признаться, раньше я боялся пробовать то, что не было моей компетенцией. Меня удерживал страх провалиться, не получить никакой помощи или получить игру, в которую невозможно играть. Меня сдерживали многочисленные «а что, если» и страхи. Однако, как только я решился и начал работать над этим, я обнаружил, что это на самом деле очень весело!

Удивительно, но и мои друзья, и наставник разделяли волнение, когда играли в мою игру. Я решил продолжить этот проект как свой индивидуальный проект и выложу его на TestFlight.

Надеюсь, предоставленная информация была вам полезна. Если вам интересно, пожалуйста, не стесняйтесь проверить мой репозиторий GitHub для получения полного кода. Ваша поддержка и отзывы очень ценятся!