исходный код java игры
Создание простой 2D игры на Android
Когда я писал эту «игру» у меня возникала масса вопросов по поводу зацикливания спрайтов так что бы они появлялись через определенное время, так же были проблемы с обнаружением столкновений двух спрайтов и более, все эти вопросы я сегодня хочу осветить в этом посте так как в интернете я не нашел нормального ответа на мои вопросы и пришлось делать самому. Пост ни на что не претендует, я новичок в разработке игр под android и пишу я для новичков в данной отрасли. Кому стало интересно прошу под кат.
Постановка задачи:
Игра должна представлять из себя поле (сцену) на котором располагается ниндзя и призраки. Нинзя должен защищать свою базу от этих призраков стреляя по ним.
Пример такой игры можно посмотреть в android market’e. Хотя я сильно замахнулся, у нас будет только похожая идея.
Вот как будет выглядеть игра:
Начало разработки
Создаем проект. Запускаем Eclipse — File — Android Project — Defens — Main.java.
Открываем наш файл Main.java и изменяем весь код на код который ниже:
Код ниже говорит нашей главной функции что запускать нужно не *.xml файл темы, а класс который у нас является самой сценой.
Дальше Вам нужно создать класс GameView.java который будет служить для нас главным классом на котором будет производится прорисовка всех объектов. Так же в этом классе будет находится и наш поток в котором будет обрабатываться прорисовка объектов в потоке для уменьшения нагрузки игры на процессор. Вот как будет выглядеть класс когда на сцене у нас ничего не происходит:
Из комментариев надеюсь понятно какая функция что делает. Этот класс является базовым по этому в нем мы будем производиться все действия (функции) которые будут происходить в игре, но для начало нам нужно сделать еще несколько классов Переходи к следующему пункту — создание спрайтов.
Создание спрайтов
Сейчас мы будем использовать не анимированные спрайты но в будущем я вставлю спрайты в проэкт, если тянет научиться делать спрайты прошу во второй урок по созданию игры под android.
Теперь загрузите эти картинки в папку res/drawable для того, чтобы Eclipse мог увидеть эти картинки и вставить в Ваш проект.
Следующий рисунок должен визуально помочь понять как будет располагаться игрок на экране.
Скучная картинка… Давайте лучше создадим этого самого игрока.
Нам нужно разместить спрайт на экране, как это сделать? Создаем класс Player.java и записываем в него следующее:
Создаем еще один файл классов и назовем его Bullet.java, этот класс будет определять координаты полета, скорость полета и другие параметры пули. И так, создали файл, и пишем в него следующее:
Из комментариев должно быть понятно что пуля выполняет только одно действие — она должна лететь по направлению указанному игроком.
Рисуем спрайты на сцене
Для того что бы нарисовать эти два класса которые мы создали, нам нужно отредактировать код в классе GameView.java, добавить несколько методов которые будут возвращать нам наши рисунки. Полностью весь код я писать не буду, буду приводить только код нужных мне методов.
Для начала нам нужно создать объекты классов Bullet и Player для того что бы отобразить их на экране, для этого создадим список пуль, что бы они у нас никогда не заканчивались, и обычный объект класса игрока.
Дальше нам нужно присвоить картинки нашим классам, находим конструктор GameView и вставляем в самый конец две строчки:
GameView.java — Конструктор GameView
И в методе onDraw(Canvas c); делаем видимыми эти спрайты. Проходим по всей коллекции наших элементов сгенерировавшихся в списке.
А для того что бы пули начали вылетать при нажатии на экран, нужно создать метод createSprites(); который будет возвращать наш спрайт.
Ну и в конце концов создаем еще один метод — onTouch(); который собственно будет отлавливать все касания по экрану и устремлять пулю в ту точку где было нажатия на экран.
Враги
Для того что бы нам не было скучно играться, нужно создать врагов. Для этого нам придется создать еще один класс который будет называться Enemy.java и который будет уметь отображать и направлять нашего врага на нашу базу. Класс довольно простой по этому смотрим код ниже:
И так что происходит в этом классе? Рассказываю: мы объявили жизненно важные переменные для нашего врага, высота ширина и координаты. Для размещения их на сцене я использовал класс Random() для того что бы когда они будут появляться на сцене, появлялись на все в одной точке, а в разных точках и на разных координатах. Скорость так же является у нас рандомной что бы каждый враг шел с разной скоростью, скорость у нас начинается с 0 и заканчивается 10, 10 — максимальная скорость которой может достигнуть враг. Двигаться они будут с права налево, для того что бы они не были сразу видны на сцене я закинул их на 900 пикселей за видимость экрана. Так что пока они дойдут можно уже будет подготовиться по полной к атаке.
Дальше нам нужно отобразить врага на сцене, для этого в классе GameView.java делаем следующее:
Создаем список врагов для того что бы они никогда не заканчивались и создаем битмап который будет содержать спрайт:
Далее создаем новый поток для задания скорости появления врагов на экране:
И имплементируем класс Runuble, вот как должна выглядеть инициализация класса GameView:
Теперь у Вас еклипс требует создать метод run(), создайте его, он будет иметь следующий вид:
В самом низу класса GameView
Здесь мы создаем поток который будет создавать спрайт от 0 до 2000 милисекунд или каждые 0, 1 или 2 секунды.
Теперь в конструкторе в самом конце пишем инициализируем наш спрайт с классом для отображения на сцене:
Ну и конечно же нам нужно объявить эти методы в onDraw(); Вот значит и пишем в нем следующее:
Метод onDraw() в GameView
Снова проходим по коллекции врагов с помощью итератора и проверяем — если враг зашел за предел в 1000 пикселей — удаляем его, так как если мы не будем удалять у нас пямять закакается и телефон зависнет, а нам такие проблемы не нужны. Все игра готова для запуска.
Запускаем нашу игру и что мы увидим? А вот что:
Но что я вижу? О нет. Пули никак не убивают наших призраков что же делать? А я Вам скажу что делать, нам нужно создать метод который будет образовывать вокруг каждого спрайта — прямоугольник и будет сравнивать их на коллизии. Следующая тема будет об этом.
Обнаружение столкновений
И так, у нас есть спрайт, у нас есть сцена, у нас все это даже движется красиво, но какая польза от всего этого когда у нас на сцене ничего не происходит кроме хождения туда сюда этих спрайтов?
С этой функцией я навозился по полной, даже как-то так выходило что психовал и уходил гулять по улице)) Самый трудный метод, хотя выглядеть совершенно безобидно…
Ладно, давайте уже создадим этот метод и не будем много разглагольствовать… Где то в конце класса GameView создаем метод testCollision() и пишем следующий код:
В самом низу класса GameView.java
И так, что у нас происходит в этом методе? Мы создаем один итератор и запускаем цикл для просмотра всей коллекции спрайтов, и говорим что каждый следующий спрайт пули будет первым.
Дальше создаем еще один итератор с другим списком спрайтов и снова переопределяем и говорим что каждый следующий спрайт врага будет первым. И создаем оператор ветвления — if() который собственно и проверяет на столкновения наши спрайты. В нем я использовал математическую функцию модуль (abs) которая возвращает мне абсолютное целое от двух прямоугольников.
Внутри ифа происходит сравнения двух прямоугольников Модуль от (Пуля по координате Х минус координата врага по координате Х меньше либо равен ширина пули плюс ширина врага / 2 (делим на два для нахождения центра прямоугольника)) и (Модуль от (Пуля по координате У минус координата врага по координате У меньше либо равен ширина пули плюс ширина врага / 2 (делим на два для нахождения центра прямоугольника)));
И в конце всего, если пуля таки достала до врага — мы удаляем его со сцены с концами.
Ну и для того что бы эта функция стала работать записываем её в метод run() который находится в классе GameThread, ниже нашего метода рисования onDraw().
Вот что у нас получается после запуска приложения:
Простая реализация игры 2048 в Java
Краткое и увлекательное руководство по реализации решателя 2048 на Java.
1. введение
Недавно мы рассмотрели алгоритм решения игры 2048. Мы обсуждали это с теоретической точки зрения, а не с каким-либо реальным кодом.
Здесь мы собираемся написать реализацию этого на Java. Это будет играть как человеческие, так и компьютерные игроки, показывая, насколько хорошо можно играть в более оптимальную игру.
2. Начальная настройка
Первое, что нам нужно, – это настройка, в которой мы можем играть в игру и видеть, как продвигается прогресс.
Это даст нам все конструкции, которые нам нужны, чтобы играть в игру, и полностью реализовать компьютерного игрока, который в любом случае размещает только случайные плитки. Это дает нам возможность реализовать “человеческого” игрока для игры в игру.
2.1. Игровое поле
Прежде всего, нам нужна игровая доска. Это сетка ячеек, в которые можно поместить числа.
Это неизменяемый класс, который представляет доску и позволяет нам опрашивать ее, чтобы узнать текущее состояние. Он также отслеживает текущий счет, к которому мы вернемся позже.
2.2. Компьютерный плеер и Размещение плиток
Теперь, когда у нас есть игровая доска, мы хотим иметь возможность играть с ней. Первое, что нам нужно, – это компьютерный плеер, потому что это чисто случайный игрок, и позже он будет точно таким, как нужно.
Компьютерный игрок не делает ничего, кроме как помещает плитку в ячейку, поэтому нам нужен какой-то способ добиться этого на нашей доске. Мы хотим, чтобы это оставалось неизменным, поэтому размещение плитки создаст совершенно новую доску в новом состоянии.
Затем мы добавим метод для размещения плитки. Это возвращает совершенно новую плату, которая идентична текущей, за исключением того, что она имеет заданный номер в данной ячейке:
Наконец, мы напишем новый класс, представляющий компьютерный плеер. У этого будет один метод, который возьмет текущую плату и вернет новую:
Он получает список каждой пустой ячейки с доски, выбирает случайную ячейку, а затем помещает в нее число. Мы случайным образом решим поместить “4” в ячейку в 10% случаев, а “2” – в остальные 90%.
2.2. Игрок “Человек” и перемещение плиток
Следующее, что нам нужно, – это “человеческий” игрок. Это будет не конечная цель, а чисто случайный игрок, который выбирает случайное направление, чтобы сдвигать плитки каждый раз, когда он делает ход. Это будет действовать как место, на котором мы можем построить наш оптимальный игрок.
Во-первых, нам нужно определить перечень возможных ходов, которые могут быть сделаны:
Затем нам нужно увеличить класс Board , чтобы поддерживать выполнение ходов, перемещая плитки в одном из этих направлений. Чтобы уменьшить сложность здесь, мы хотим повернуть доску так, чтобы мы всегда сдвигали плитки в одном и том же направлении.
Это означает, что нам нужно средство как для транспонирования, так и для реверса доски:
Транспонирование доски приведет к замене всех строк и столбцов таким образом, что верхний край станет левым краем. Реверсирование доски просто отражает ее таким образом, что левый край становится правым краем.
Затем мы добавим метод в Доска сделать ход в заданном направлении и вернуть новый Доска в новом государстве.
Мы начинаем с создания копии состояния доски, с которой затем можем работать:
Затем мы манипулируем нашей копией так, чтобы мы всегда сдвигали плитки вверх:
Нам нужен еще один массив плиток – на этот раз тот, в который мы встроим конечный результат, – и трекер для
Теперь, когда мы готовы начать сдвигать плитки, и мы манипулировали вещами так, чтобы мы всегда работали в одном и том же направлении, мы можем начать.
Мы можем сдвинуть каждый столбец независимо от других. Нам просто нужно перебрать столбцы и повторить, начиная с создания еще одной копии плиток, которые мы перемещаем.
Это обеспечивает наше смещение, но еще не слияние плиток:
Далее нам нужно объединить плитки. Нам нужно сделать это отдельно от вышеперечисленного; в противном случае мы рискуем объединить одну и ту же плитку несколько раз.
Это достигается путем создания еще одного Связанного списка плиток из приведенного выше, но на этот раз слияния по ходу работы:
Здесь мы также рассчитываем новый балл за этот ход. Это сумма плиток, созданных в результате слияний.
Теперь мы можем встроить это в результирующий массив. Как только у нас закончатся плитки из нашего списка, остальные будут заполнены значением “0”, чтобы указать, что они пусты:
Как только мы закончим смещать плитки, нам нужно снова манипулировать ими, возвращаясь к правильному вращению. Это полная противоположность тому, что мы делали раньше:
И, наконец, мы можем построить и вернуть новую доску с этим новым набором плиток и вновь рассчитанным счетом:
Теперь мы находимся в положении, когда мы можем написать нашего случайного “человеческого” игрока. Это не делает ничего, кроме генерации случайного хода и вызова приведенного выше метода для воспроизведения этого хода:
2.3. Игра в игру
Во-первых, нам нужен способ распечатать игровое поле.
В этом примере мы просто собираемся печатать на консоли, поэтому System.out.print достаточно хорош. Для реальной игры мы хотели бы сделать лучшую графику:
Мы почти готовы к отъезду. Нам просто нужно все уладить.
Это означает, что вы создаете доску, двух игроков и заставляете компьютер сделать два начальных хода, то есть разместить на доске два случайных числа:
И теперь у нас есть реальный игровой цикл. Это будет повторение того, как люди и компьютерные игроки по очереди останавливаются и останавливаются только тогда, когда не осталось пустых ячеек:
В этот момент, если бы мы запустили программу, мы бы увидели случайную игру 2048 года.
3. Реализация игрока 2048 года
Как только у нас будет база, с которой мы сможем играть в игру, мы сможем начать внедрять “человеческого” игрока и играть в лучшую игру, чем просто выбирать случайное направление.
3.1. Имитация Движений
Мы будем активно использовать потоки Java 8, чтобы помочь структурировать этот код, по причинам, которые мы увидим позже.
Мы начнем с переписывания метода makeMove() из нашего класса Human :
Для каждого возможного направления, в котором мы можем двигаться, мы генерируем новую доску, а затем запускаем алгоритм подсчета очков – прохождение этой доски и глубина 0. Затем мы выбираем ход, который имеет лучший результат.
Наш метод generateScore() затем имитирует каждое возможное перемещение компьютера – то есть помещает “2” или “4” в каждую пустую ячейку – и затем видит, что может произойти дальше:
Если мы достигли нашего предела глубины, то мы немедленно остановимся и рассчитаем окончательную оценку того, насколько хороша эта доска; в противном случае мы продолжим наше моделирование.
Наш метод calculateScore() является продолжением нашего моделирования, в котором выполняется часть уравнения движения человека.
Это очень похоже на метод makeMove() выше, но мы возвращаем текущую оценку вместо фактической доски:
3.2. Подсчет Итоговых досок
Сейчас мы находимся в ситуации, когда мы можем имитировать движения людей и компьютерных игроков вперед и назад, останавливаясь, когда мы смоделировали их достаточно. Нам нужно иметь возможность генерировать оценку для финальной доски в каждой ветви моделирования, чтобы мы могли видеть, какую ветвь мы хотим продолжить.
Наша оценка представляет собой комбинацию факторов, каждый из которых мы собираемся применить к каждой строке и каждому столбцу на доске. Все они суммируются вместе, и общая сумма возвращается.
Таким образом, нам нужно сгенерировать список строк и столбцов для оценки:
Затем мы берем список, который мы построили, оцениваем каждый из них и суммируем результаты вместе. Это заполнитель, который мы собираемся заполнить:
Наконец, нам действительно нужно сгенерировать наши оценки. Это происходит внутри вышеупомянутой лямбды и представляет собой несколько различных факторов, которые все вносят свой вклад :
Прежде чем мы сможем рассчитать баллы, нам нужно собрать некоторые дополнительные данные.
Во-первых, мы хотим, чтобы список чисел с пустыми ячейками был удален:
Затем мы можем сделать некоторые подсчеты из этого нового списка, указав количество соседних ячеек с одинаковым числом, со строго возрастающими числами и строго убывающими числами:
Теперь мы можем рассчитать наш счет для этой строки:
Числа, выбранные здесь, относительно произвольны. Разные цифры будут влиять на то, насколько хорошо игра идет, определяя приоритеты различных факторов в том, как мы играем.
4. Усовершенствования алгоритма
То, что у нас есть до сих пор, работает, и мы видим, что это хорошая игра, но она медленная. На каждое движение человека уходит около 1 минуты. Мы можем сделать лучше, чем это.
4.1. Параллельная обработка
Самое очевидное, что мы можем сделать, – это работать параллельно. Это огромное преимущество работы с потоками Java – мы можем сделать эту работу параллельной, просто добавив один оператор в каждый поток.
Одно только это изменение сокращает время до 20 секунд на ход.
4.2. Обрезка Неиграбельных Ветвей
Следующее, что мы можем сделать, – это обрезать все ветви, которые невозможно воспроизвести. То есть в любое время, когда ход человека приводит к неизменной доске. Это почти наверняка ветви, которые приведут к худшим результатам – они фактически дают компьютеру свободу действий, – но они стоят нам времени на обработку, чтобы продолжить их.
Затем мы можем добавить некоторые фильтры в наши потоковые конвейеры, чтобы остановить обработку всего, что не изменилось.
Это оказывает минимальное влияние на ранние части игры – когда очень мало заполненных ячеек, очень мало ходов, которые можно обрезать. Однако позже это начинает оказывать гораздо большее влияние, сокращая время перемещения всего до нескольких секунд.
5. Резюме
Почему бы не попробовать изменить правила, чтобы увидеть, как они влияют на игровой процесс.
Быстрый старт с Java: пишем «крестики-нолики»
Перед прочтением данной статьи рекомендую ознакомиться с предыдущей, «Быстрый старт с Java: начало», поскольку ожидается, что читатель владеет материалом, изложенным в ней — знает о переменных, условиях, циклах и импорте классов. Сегодня мы углублим знания о Java, создавая игру «Крестики-нолики», которая работает в командной строке (консоли). В процессе будет рассмотрена работа с массивами, а также некоторые аспекты объектно-ориентированного программирования (нестатические методы, нестатические поля, конструктор).
Массивы
При написании игры используется массив, поэтому давайте для начала рассмотрим, что это. Массивы хранят набор однотипных переменных. Если переменная похожа на коробочку, с написанным на боку типом, именем и со значением внутри, то массив похож на блок таких коробочек. И тип, и имя у блока одно, а доступ к той или иной коробочке (значению) происходит по номеру (индексу).
Методы
Решение одно — создать объект на основании класса. И затем вызывать метод через точку после имени объекта. В этом случае метод может быть нестатическим. Представленный ниже код это иллюстрирует.
Поля класса
Переменные существуют в рамках лишь того метода, где они объявлены. А если нужна переменная, доступная во всех методах класса, то пришло время использовать поля. Поля объявляются подобно переменным, с указанием типа и имени. Но располагаются они не в методах, а прямо в теле класса. Подобно методам, они могут быть статическими и нестатическими. Нестатические поля, как и методы, доступны только после создания объекта.
Крестики-нолики. Шаблон класса
Приступим к написанию кода игры. Начнём с шаблона класса и определения нужных полей. Именно это содержит приведённый ниже код. Первые две строки — импорт классов. Первыми в теле класса идут описания полей, затем методов. Метод main() используется для создания объекта (так как поля и методы нестатические) и вызова метода game() с игровой логикой.
Имена методов принято писать с маленькой буквы. Однако в коде мы видим метод TicTacToe() — есть ли тут нарушение? Нет, поскольку этот метод особенный и в объектно-ориентированном программировании называется конструктор. Конструктор вызывается сразу после того, как объект создан. Его имя, как видим, должно совпадать с именем класса. Мы используем конструктор для инициализации полей.
Игровая логика
Реализация вспомогательных методов
Заключение
На всякий случай прилагаю мой telegram — @biblelamp. Если вас заинтересовала тема, рекомендую почитать «Java-программирование для начинающих» Майка МакГрата и «Изучаем Java» Кэти Сьерра и Берт Бейтс.
Другие статьи из серии «Быстрый старт с Java»:
Если язык Java вас заинтересовал — приглашаем на факультет Java-разработки. Если ещё не совсем уверены — посмотрите истории успеха наших Java-выпускников:
Перед прочтением данной статьи рекомендую ознакомиться с предыдущей, «Быстрый старт с Java: начало», поскольку ожидается, что читатель владеет материалом, изложенным в ней — знает о переменных, условиях, циклах и импорте классов. Сегодня мы углублим знания о Java, создавая игру «Крестики-нолики», которая работает в командной строке (консоли). В процессе будет рассмотрена работа с массивами, а также некоторые аспекты объектно-ориентированного программирования (нестатические методы, нестатические поля, конструктор).
Массивы
При написании игры используется массив, поэтому давайте для начала рассмотрим, что это. Массивы хранят набор однотипных переменных. Если переменная похожа на коробочку, с написанным на боку типом, именем и со значением внутри, то массив похож на блок таких коробочек. И тип, и имя у блока одно, а доступ к той или иной коробочке (значению) происходит по номеру (индексу).
Методы
Решение одно — создать объект на основании класса. И затем вызывать метод через точку после имени объекта. В этом случае метод может быть нестатическим. Представленный ниже код это иллюстрирует.
Поля класса
Переменные существуют в рамках лишь того метода, где они объявлены. А если нужна переменная, доступная во всех методах класса, то пришло время использовать поля. Поля объявляются подобно переменным, с указанием типа и имени. Но располагаются они не в методах, а прямо в теле класса. Подобно методам, они могут быть статическими и нестатическими. Нестатические поля, как и методы, доступны только после создания объекта.
Крестики-нолики. Шаблон класса
Приступим к написанию кода игры. Начнём с шаблона класса и определения нужных полей. Именно это содержит приведённый ниже код. Первые две строки — импорт классов. Первыми в теле класса идут описания полей, затем методов. Метод main() используется для создания объекта (так как поля и методы нестатические) и вызова метода game() с игровой логикой.
Имена методов принято писать с маленькой буквы. Однако в коде мы видим метод TicTacToe() — есть ли тут нарушение? Нет, поскольку этот метод особенный и в объектно-ориентированном программировании называется конструктор. Конструктор вызывается сразу после того, как объект создан. Его имя, как видим, должно совпадать с именем класса. Мы используем конструктор для инициализации полей.
Игровая логика
Реализация вспомогательных методов
Заключение
На всякий случай прилагаю мой telegram — @biblelamp. Если вас заинтересовала тема, рекомендую почитать «Java-программирование для начинающих» Майка МакГрата и «Изучаем Java» Кэти Сьерра и Берт Бейтс.
Другие статьи из серии «Быстрый старт с Java»:
Если язык Java вас заинтересовал — приглашаем на факультет Java-разработки. Если ещё не совсем уверены — посмотрите истории успеха наших Java-выпускников: