Как сделать игру на яве
Пишем 2d-игру на Java
Доброго времени суток всем!
В этой статье будет описываться создание 2D игры на Java. Сразу предупреждаю, вы должны хотя бы базово знать язык Java, поскольку на подробное объяснение каждой строки у меня нету времени. И очень прошу вас, не списывать просто код, а пытаться понять что означает каждая строка, и писать со смыслом. И еще, я использую Eclipse, но вы можете использовать любой IDE.
Задача:
Я планирую создать игру, напоминающую шутер с видом от 3 лица.
Начало:
Для начала создадим проект. Назовем его «Just game». И сразу создаем класс Display.java. В него пишем:
Теперь разберемся, что мы сделали.
мы создаем рамку, которая и будет отображаться при запуске нашей игры
устанавливаем операцию, которая будет происходить при нажатии на крестик. EXIT_ON_CLOSE — выйти из программы
устанавливаем нашей рамке максимальные размеры, убираем декорации(кнопки свернуть, закрыть, уменьшить/увеличить и т.п.), т.е. делаем игру на весь экран. Если вы хотите, чтобы игра не была бы на весь экран, то используйте:
— делаем рамку видимой
Только не забудьте, все настройки рамки надо писать до того, как вы сделаете её видимой
Ну чтож, теперь нажимаем «Run» и пробуем запустить нашу игру. Если все написано правильно, у вас не должны возникать ошибки и должно появиться пустое, серое окно.
Серое окно… Как скучно… Давайте создадим что-нибудь поинтереснее.
Создадим новый класс, под названием «Main». Main класс у нас будет являться панелью, которую мы вставим в рамку, по этому он должен расширять JPanel. (Для тех, кто не знает, расширять пишется как extends после названия класса)
Возвращаемся в класс Display и после настроек рамки, но перед установлением её видимости, пишем:
Вы спросите — «Ну и зачем мы это сделали?». Представьте себе картину. Эта картина и является конечная наша игра. А теперь представьте рамку. Без ничего внутри, просто пустую рамку. На ней ничего нельзя нарисовать, она бесполезна. Для этого, мы вставили в картину пустой лист, на котором программа в дальнейшем может рисовать картину. На этом закончим наше лирическое отступление и вернемся к классу Main.
Нам нужно осуществить отрисовку, по этому мы должны добавить метод paint. Для этого пишем:
Ну и для начала, можем написать внутри этого метода отрисовку линии. Для этого пишем:
Теперь запускаем программу, и видим:
Давайте отрисуем какую-нибудь картинку. Например эту:
Для начала, нам нужно указать путь к картинке. Для этого не в методе paint, пишем:
(предварительно надо в наш проект скинуть картинку и назвать ее 2.png)
После этого удаляем строчку отрисовки линии, а вместо нее в метод paint пишем:
Разберемся поближе с методом drawImage, так как мы будем часто его затрагивать.
drawImage(картинка которую мы будем рисовать, которую мы объявили раннее, координата X с которой будет рисоваться картинка, координата Y с которой будет рисоваться картинка, paint);
Отдельно хочу поговорить о параметре paint. Лучше всего оставляйте его null. Я только однажды сталкивался, когда мне нужно было использовать paint. Это было когда я отрисовывал текст, и задавал ему размер шрифта. Но советую не лезть туда и использовать null.
Теперь запускаем программу, и видим:
Чего-то она маленькая, не правда ли? Давайте научимся увеличивать её размеры. Добавляем к drawImage() параметры так, чтобы вышло:
Что мы сейчас добавили? Эти два параметра растягивают картинку, до координат 1920 и 1080. Получается, что картинка на весь экран. Давайте запустим программу и это проверим.
Ну наконец-то. Теперь мы умеем любые картинки растягивать на весь экран. Но вот проблема. Метод paint вызывается только один раз. И как же его обновлять постоянно? Для этого существует очень полезная вещь — таймер. Давайте создадим его.
(20 это частота с которой обновляется таймер, this- где выполнять метод при обновлении таймера
Это мы должны вписать сразу после строки определения класса, т.е. после:
Также, надо дополнить строку определения класса таким образом:
После прописывания этой строки, у вас название класса должно подчеркнуться красным. Чтобы это исправить, в самом конце класса добавьте метод:
Этот метод будет выполняться при обновлении таймера. В него мы должны написать repaint(); чтобы при каждом обновлении таймера у нас все элементы бы стирались, и нарисовывались заново.
Дальше, мы должны запустить таймер. Для этого, создаем конструктор класса Main и в него пишем:
После этого, можете не запускать программу, ведь в ней ничего не изменится. Давайте заменим текстуру домика на нормальную текстуру карты. Её вы можете нарисовать сами, либо скопировать у меня пробную:
Размер картинки может быть любой, все равно её размер будет подгоняться прямо в программе. Ах да, разрешения компьютеров могут быть разные, так что добавим-ка в конструктор такие вещи:
И перед конструктором добавим:
И сходим еще в класс Display.java и там немного изменяем метод frame.add:
Таким образом, наша рамка будет передаваться в класс Main.java. Переходим в этот класс, и там где у нас метод paint() меняем строку drawImage() на:
Таким образом, теперь наша игра будет отрисовывать картинку на весь экран, в независимости от его разрешения. Запускаем:
На сегодня все. Оставляю код, для тех, кто запутался:
Создание игры на Java без сторонних библиотек, часть первая
Введение и подготовка
Привет хаброжители. Данный пост является «рерайтом» моего поста для песочницы. На этот раз я постараюсь охватить больше тем, чем тогда.
Почему Java?
Ничего объективного я тут не скажу, а скажу лишь то, что я люблю этот язык, и мне нравиться писать на нем. Да, на Java нет игр AAA-класса, но Java предоставляет огромные возможности, больше кол-во встроенных средств и быстроту написания кода.
Начнем с выбора IDE. Я являюсь фанатом Eclipse и посоветую вам его.
Если же почему-то вам он не понравился, вы можете использовать NetBeans, Intellij IDEA или командную строку и ваш любимый редактор.
И скачаем JDK последней версии: JDK 7u4
Скорее всего проблем с установкой IDE у вас не возникнет, а если у вас 64-битная система, все же посоветую устанавливать 32-битный Eclipse, так как иногда бывают ошибки и Eclipse у вас просто не запустится.
Под катом мы приступим к созданию игры.
Класс Game
Итак, создаем проект, в нем класс Game(попутно создав в нем точку входа). Данный класс должен наследовать класс Canvas и реализовать интерфейс Runnable:
Создадим переменную running типа Boolean, которая, как вы уже догадались будет показывать нам запущена ли игра, или нет.
Создадим функцию start() и в ней мы будем создавать новый поток и переводить running в true:
Создадим три функции — update(long delta), render() и init(). Я надеюсь что их значение вам понятно. В функции run() создадим главный игровой цикл, перед ним будем вызывать init(), а в нем самом render() и update(). Так же мы будем вычислять разницу между кадрами(delta time).
Пока поработаем над функцией render().
Вам наверное уже не терпится запустить и попробовать, но не спешите. Мы должны создать фрейм и добавить наш холст на него. Заодно и объявим три переменных.
Примерно вот так выглядит наш класс Game сейчас.
Класс Sprite
Создадим новый класс Sprite. Поскольку этот класс небольшой, я сразу приведу весь его код с комментариями:
Сразу же проверим работоспособность. Возьмем эту картинку и скопируем ее в папку с нашим классом Sprite. Добавим функцию getSprite() в класс Game(временно).
Добавим нашу картинку в папку assets(папку создать в корне проекта), саму папку надо добавить в build path.
Далее создаем переменную hero типа Sprite. В функции init() инициализируем ее. В Функции render() рисуем:
Input
Для обработки инпута мы создадим класс, наследующий KeyAdapter:
Тут же и объявим две переменных в шапке класса Game:
Внутри класса KeyInputHandler создадим две функции:
Теперь в функции init() добавим следующее:
Создадим переменные x и y для героя(так как пока что мы еще не написали класс Entity). Сделаем чтобы герой всегда рисовался на этих координатах.
А теперь в функции update() будем проверять нажаты ли клавиши и изменять x-координату.
Спасибо за внимание.
Ой, у вас баннер убежал!
Читают сейчас
Редакторский дайджест
Присылаем лучшие статьи раз в месяц
Скоро на этот адрес придет письмо. Подтвердите подписку, если всё в силе.
Похожие публикации
Окна «неправильной» формы, Java 6 & 7 ed
На чём и как писать (часть 1. Eclipse и Java)
Вышел GAE Java SDK 1.2.1
Вопросы и ответы
Есть ли tokenizer/parser для regex?
Как прервать работу клиента Кафки если не доступен брокер?
Проблема в сворачивании и разворачивании приложения Cordova как можно сиправить?
Как подключиться к hdfs через java?
Как изменять текст в ReplyKeyboardMarkup?
AdBlock похитил этот баннер, но баннеры не зубы — отрастут
Минуточку внимания
Комментарии 50
Статья понравилась, очень жаль что в свое время эксперементировал не с Java, а с Delphi, в результате через некоторое время ему настал конец в геймдеве.
P.S. При комбинации клавиш «Alt + Print Screen» будет захвачено только активное окно и не будет необходимости резать скриншоты.
А я как раз таки наоборот, очень рад что не связался с джавой =) на делфи, кстати, можно легко писать казуалки, в том числе и тридешные (ну это я еще о том времени, когда люди ставили игры на комп, а не играли в социалках), да и в любом случае, нативный код будет быстрее чем джавовский через ВМ, а в играх это довольно критично.
И в качестве оффтопика (надеюсь меня не забрасают тапками, но меня действительно волнует эта тема). Кроме того что в играх важна скорость реакции, она так же важна и в любых других приложениях, а в особенности мне, как программисту, хочется скорости и гибкости среды разработки, в которой я работаю (пишу на флеше и js). Так вот, я не понимаю, как стали столь популярными IDE на основе еклипса? С огромнейшим контекстным меню, с кучей всяких не особо полезных и не всегда работающих штук, аля комбайн. Может быть я как то не правильно пользуюсь ими? Даже вебшторм подтормаживает, когда в процессе прокрутки попробовать открыть контекстное меню (MBA 2011). Быть может правильно использовать данные программы для того же C++ или самой Java? Но непонятно, почему тогда те же JetBrains используют ее для своих продуктов? И самым недоразумением во всей этой компании для меня является Flash Builder. Почему Adobe не написали на том же ейр удобный и заточенный именно под флеш инструмент? Еще в эклипсе предусмотрен механизм установки плагинов, но почему он такой не прозрачный и непонятный? почему нет маркета какого-то по категориям например?
Заранее благодарю за ответ на мою эмоциональную простыню и готов признать, что я предосудительно отношусь к ПО на java в случае приведения рациональных аргументов)
Пишем 2-d игру на Java #2
Доброго времени суток всем! Продолжение 1-ого урока (ссылка на первый).
Задачи
Начало
Приступим. Для начала создадим новый класс Player.java.
Создадим несколько переменных, типа int. Первая это x, вторая это y, и третья speed. Пускай они будут приватными, во избежание дальнейших ошибок(установите сразу им значения, x=0, y=0, speed = любая скорость, я буду ставить 2). Также, создадим три метода — getSpeed(), getX() и getY(). Они выглядят так:
Они будут возвращать нам текущие x и y координаты нашего игрока, а также скорость игрока.
Теперь, нам нужен метод, отвечающий за движение. Так его и назовем — move. Пока у нас нету слушателя клавиш, по этому, просто напишем:
Возвращаемся в наш класс Main.java и в нем, создаем объект нашего игрока:
Далее, в методе actionPerformed() пишем:
Теперь, у нас при каждом обновлении таймера будет двигаться игрок. Но сейчас мы это движение не увидим, т.к. координаты игрока мы нигде не используем. Сейчас я коротко объясню как происходит движение на экране. Двигается не сама картинка нашего игрока, а задний план, который имитирует движение игрока. Так что, переходим в метод paint() и там, вместо координат отрисовки, вписываем player.getX() и player.getY(). Должно выйти так:
Запускаем, и видим что наш фон двигается.
Теперь я объясню, что мы не совсем корректно сделали.
Мы должны иметь две отдельные координаты — координата карты и координата игрока. Для чего это нужно? Сейчас мы слой отрисовываем относительно координаты игрока. В дальнейшем, у нас появятся объекты, находящиеся на карте, и проверять, взаимодействует ли с ними игрок будет проще, имея координаты карты. Надеюсь, объяснил понятно, а теперь мы это реализуем.
По-хорошему, мы должны иметь отдельный объект, Universe (Вселенная) в котором и будет содержаться информация о нашей карте(время суток, координаты и т.п.). В последующих уроках мы это сделаем, а пока, просто добавим в классе игрока две новые переменные: mapX и mapY, и сразу установим им значения, 0 и 0. И, как в случае с x и y, создадим 2 getter’a.
Теперь подкорректируем наш метод move().
Напишем вместо x+=speed это:
И вернемся к методу paint и поменяем
Запускаем, и видим что ничего не поменялось. Это хороший знак, значит мы все правильно сделали.
Но мы видим, что картинка одна, и за ней остается след. И вот мы подошли к такой развилке. Мы можем создать игру, в которой мир будет постоянно расширяться, в зависимости оттого, в каком направлении идет игрок. Либо, сделать игру с определенного размера локацией, за пределы которой выйти нельзя, но локации смогут меняться. С какими проблемами я столкнулся, при создании этих двух вариантов. Если будем создавать игру без границ, то:
Хорошо, пока обойдемся без дополнительных слоев, создадим слушателя нажатий на клавиши.
В конструкторе класса Main.java допишем такую строку:
Некоторые могут спросить, почему мы используем метод «добавить слушателя», а добавляем адаптер? Просто адаптер позволяет не использовать все три метода класса слушателя, а допустим, как в нашем случае, мы реализуем в нем только два:
Первый метод отвечает за нажатие на клавишу, а второй за ее отпускание. Третий метод реагирует на короткое нажатие клавиши, а не задерживание её, как в первом методе. Теперь, чтобы у нас не получилась белеберда, и чтобы не усложнять себе жизнь, сделаем точно такие же два метода в классе игрока. И теперь в наших методах, находящихся в классе Main.java напишем:
Готово! Теперь у нас есть слушатель нажатий на клавиши. Давайте проверим его работоспособность, просто написав в каждом из методов в классе Player.java такую строку:
Запускаем программу, смотрим в нашу консоль, и нажимаем на клавиши.
Если при каждом нажатии на клавишу выписываются цифры в консоль, это означает что вы сделали все правильно. Не бойтесь, запоминать все эти клавиши не надо, за нас это уже сделал класс KeyEvent.
Теперь создадим энум, который будет отвечать за направление игрока. Для тех, кто не знает, энум, это такая переменная, которая может иметь несколько значений, которые мы заранее прописали. Создаем энум, под названием Direction. В него, через запятую, пишем такие значения: UP, DOWN, LEFT, RIGHT, NONE. Теперь, в классе Player создаем новую переменную типа Direction, которую назовем playerDirection (сразу поставьте значение Direction.NONE). Теперь, сделаем так, чтобы при нажатии соответствующих кнопок, направление игрока изменялось. Кстати, мы всегда будем работать с методами keyPressed и keyReleased из класса Player.java, в классе Main.java их не трогайте. Так вот, для начала, в методе keyPressed объявим переменную, типа int, которая равна e.getKeyCode(). Мы делаем это для удобства, чтобы в дальнейшем каждый раз не прописывать одно и то же. Так вот, дальше пишем:
Это условия, как будет меняться наше направление игрока в зависимости от нажатых клавиш.
Далее, изменим метод move. Он должен будет выглядеть так:
Для тех, кто не знает, switch это тоже самое что и if, только в более удобной форме. Запускаем программу, и любуемся!
На сегодня все. Опять же, оставляю весь код, для тех, кто запутался.
Player.java
Main.java
Display.java
Никак не поменялся.
Спасибо за внимание! Не забудьте проголосовать.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Как написать свою 2048 на Java за 15 минут
В предыдущих статьях этой серии мы уже писали сапёра и змейку, а теперь попробуем написать десктопный клон игры 2048.
Нам, как обычно, понадобятся:
FAQ по опыту предыдущих статей
В: Вы что! Это большой проект, за 15 минут такое нельзя накодить!
О: Разумеется, чтобы придумать всё это и написать, у меня ушёл целый вечер и даже немножко ночи. Но прочитать статью и, скопировав из неё код, запустить работающую игру (при этом понимая, что происходит) вполне реально и за 15 минут.
В: Зачем тянуть ради такого простого проекта LWJGL? Гвозди микроскопом!
О: Вся работа с отображением вынесена в два с половиной метода в 2 интерфейса. Вы можете реализовать их на чём хотите, мне же удобнее на LWJGL.
В: А почему код выкладываете архивом? На гитхаб нужно!
О: Да, теперь весь проект выложен на GitHub.
В: У меня не получается подключить твою эту LWJGL! Что делать?
О: В прошлые разы у многих возникли с этим вопросом проблемы, поэтому мне показалось уместным посвятить этому немного времени.
Во-первых, выше я дал ссылку на папку с библиотеками на GitHub, которые использую я, чтобы не было путаницы с версиями и вопросов, где найти что-то. Папку из архива требуется поместить в папку проекта и подключить через вашу IDE.
Во-вторых, у многих пользователей InteliJ IDEA возникли проблемы как раз с их подключением. Я нашёл в сети следующий видеогайд:
После того, как я сделал всё в точности по нему, у меня библиотеки подключились корректно и всё заработало.
В: А почему на Java?
О: На чём бы я не написал, можно было бы спросить «Почему именно на X?». Если в комментариях будет реально много желающих увидеть код на каком-то другом языке, я перепишу игру на нём и выложу (только не Brainfuck, пожалуйста).
С чего начать?
Создаём игровое поле
Всё наше поле – матрица чисел и методы, позволяющие их изменять (геттеры и сеттеры). Договоримся только, что пустую ячейку мы будем обозначать числом 0. Выглядеть этот класс будет так:
Создаём в поле первые две ячейки
Совсем очевидно, что нам нужно просто вызвать два раза метод создания одной ячейки.
Заметьте, я не пишу вызов одного метода два раза. Для программистов существует одна максима: «Существует только два числа: один и много». Чаще всего, если что-то нужно сделать 2 раза, то со временем может возникнуть задача сделать это и 3, и 4 и куда больше раз. Например, если вы решите сделать поле не 4х4, а 10х10, то разумно будет создавать не 2, а 10 ячеек.
Теперь постараемся решить вопрос – как в матрице создать ячейку вместо одного из нулей? Я решил пойти по такому пути: мы выбираем случайные координаты, и если там находится пустая ячейка, то создаём новую плитку там. Если там уже есть плитка с числом, то пытаемся создать в следующей клетке (двигаемся вправо и вниз). Обратите внимание, что после хода не может не быть пустых клеток, т.к. ход считается сделанным, когда клетки либо переместились (т.е. освободили какое-то место), либо соединились (т.е. клеток стало меньше, и место снова высвободилось).
Реализуем пользовательский ввод
Отсюда нам нужно запомнить только, какие интерфейсы (графический и клавиатурный модули) нам нужно создать и какие методы в них определить. Если не запомнили – не волнуйтесь, ворнинги вашей IDE особо забыть не дадут.
Интерфейсы для клавиатурного и графического модулей
Так как многим не нравится, что я пишу эти модули на LWJGL, я решил в статье уделить время только интерфейсам этих классов. Каждый может написать их с помощью той GUI-библиотеки, которая ему нравится (или вообще сделать консольный вариант). Я же по старинке реализовал их на LWJGL, код можно посмотреть здесь в папках graphics/lwjglmodule и keyboard/lwjglmodule.
Интерфейсы же, после добавления в них всех упомянутых выше методов, будут выглядеть следующим образом:
Графический модуль
Клавиатурный модуль
Метод логики
Вполне понятно, что если было определено направление сдвига, то нужно произвести в этом направлении сдвиг – в этом вся суть игры. Также, если сдвиг произвести удалось, необходимо создать новую ячейку. Направление для нового сдвига должно снова стать неопределённым – до следующего пользовательского ввода.
Вы могли заметить, что мы часто используем enum Direction для определения направления. Т.к. его используют различные классы, он вынесен в отдельный файл и выглядит так:
Давай уже серьёзно. Как нам сдвинуть это чёртово поле?
Так как этот магический метод с алгоритмом должен будет по сути вернуть два объекта (новую линию и boolean, который будет говорить о наличии изменений в ней), создадим в начале класса Main для такого результата обёртку:
Самое сердце программы. Метод shiftRow()
Если подумать, то вам предстоит решить задачку — как за наименьшее (линейно зависящее от количества поступающих данных) время произвести с рядом чисел следующие последовательные операции: (1) если в ряде есть нули, их необходимо удалить, (2) если любые два соседних числа равны, то вместо них должно остаться одно число, равное сумме двух равных чисел. И (3) — если число получено через пункт (2), оно не может совмещаться с другими числами.
Если представить себе алгоритм таким образом, то придумать линейное решение будет гораздо легче. Вот какой алгоритм должен получиться:
При этом нам необходимо хранить место в возвращаемом массиве, на которое необходимо произвести запись.