Игры на c builder 6. Создание двумерных игр при помощи Game API. Использование спрайтов для анимации персонажа

Игры на c builder 6. Создание двумерных игр при помощи Game API. Использование спрайтов для анимации персонажа

24.03.2019

Пакет MIDP 2.0 содержит Game API, упрощающий создание двумерных игр. Этот API является компактным и состоит всего из пяти классов в пакете javax.microedition.lcdui.game . Эти классы обеспечивают две основные возможности:

  • Новый класс GameCanvas предоставляет функции прорисовки экрана и реакции на входное воздействие в теле цикла программы игры, не используя системные входные потоки и потоки прорисовки.
  • Мощный и гибкий уровень API облегчает создание сложных сцен.

Построение цикла игры при помощи GameCanvas

GameCanvas представляет собой класс Canvas с дополнительными возможностями; он предоставляет методы для непосредственной прорисовки, а также для контроля за состоянием клавиатуры. Эти новые методы делают возможным заключение всей функциональности игры в одном цикле под управлением одного потока. Для того чтобы увидеть преимущества этого подхода, подумайте, как бы вы могли реализовать типовую игру с использованием Canvas:

Public void MicroTankCanvas extends Canvas implements Runnable { public void run() { while (true) { // Обновить состояние игры. repaint(); // Задержка на один шаг времени. } } public void paint(Graphics g) { // Код прорисовки. } protected void keyPressed(int keyCode) { // Реакция на нажатия клавиш. } }

Это не совсем удачная схема. Метод run() , выполняющийся в потоке приложения, обновляет игру в каждом цикле. Типичной задачей может быть обновление позиции мяча или звездолета и анимация персонажа или объекта. Для обновления экрана метод repaint() вызывается в каждом цикле. Система передает события от клавиатуры в метод keyPresed() , который соответствующим образом обновляет состояние игры.

Проблема заключается в том, что все работает в различных потоках и код игры распределяется по трем различным методам. При вызове в основном цикле анимации в main() метода repaint() нет способа точно определить время вызова системой метода paint() . При вызове системой метода keyPressed() нет способа узнать, что происходит в других частях приложения. Если ваша программа в keyPressed() производит обновление состояния игры одновременно с прорисовкой экрана в paint() , экран может выглядеть не так, как предполагалось. Если прорисовка экрана выполняется дольше времени одного цикла программы в run() , то анимация может выглядеть прерывистой или необычной.

GameCanvas дает вам возможность обойти обычные механизмы прорисовки и обработки событий клавиатуры, и вся логика игры может быть сосредоточена в одном цикле. Во-первых, GameCanvas позволяет вам обратиться непосредственно к своему объекту Graphics при помощи метода getGraphics() . Вся прорисовка на полученном объекте Graphics выполняется в неэкранном буфере. Вы можете затем скопировать буфер на экран, используя flushGraphics() , который не возвращает управления до тех пор, пока экран не будет обновлен. Такой подход обеспечивает более тонкий контроль, чем вызов repaint() . Метод repaint() сразу возвращает управление, и ваше приложение не может точно определить, когда система вызовет paint() для обновления экрана.

GameCanvas также содержит метод для получения текущего состояния клавиатуры, который можно применить для так называемого опроса. Вместо ожидания вызова системой keyPressed() вы можете непосредственно определить, какие клавиши нажаты, вызвав метод GameCanvas getKeyStates() .

Типичный цикл игры с использованием GameCanvas может выглядеть примерно так:

Public void MicroTankCanvas extends GameCanvas implements Runnable { public void run() { Graphics g = getGraphics(); while (true) { // Обновить состояние игры. int keyState = getKeyStates(); // Реакция на нажатия клавиш. // Код прорисовки. flushGraphics(); // Задержка на один шаг времени. } } }

В следующем примере демонстрируется основной цикл игровой программы. Она показывает вращающийся символ X , который вы можете перемещать по экрану при помощи клавиш перемещения курсора. Метод run() является чрезвычайно ясным, благодаря объекту GameCanvas .

Import javax.microedition.lcdui.*; import javax.microedition.lcdui.game.*; public class SimpleGameCanvas extends GameCanvas implements Runnable { private boolean mTrucking; private long mFrameDelay; private int mX, mY; private int mState; public SimpleGameCanvas() { super(true); mX = getWidth() / 2; mY = getHeight() / 2; mState = 0; mFrameDelay = 20; } public void start() { mTrucking = true; Thread t = new Thread(this); t.start(); } public void stop() { mTrucking = false; } public void run() { Graphics g = getGraphics(); while (mTrucking == true) { tick(); input(); render(g); try { Thread.sleep(mFrameDelay); } catch (InterruptedException ie) {} } } private void tick() { mState = (mState + 1) % 20; } private void input() { int keyStates = getKeyStates(); if ((keyStates & LEFT_PRESSED) != 0) mX = Math.max(0, mX - 1); if ((keyStates & RIGHT_PRESSED) != 0) mX = Math.min(getWidth(), mX + 1); if ((keyStates & UP_PRESSED) != 0) mY = Math.max(0, mY - 1); if ((keyStates & DOWN_PRESSED) != 0) mY = Math.min(getHeight(), mY + 1); } private void render(Graphics g) { g.setColor(0xffffff); g.fillRect(0, 0, getWidth(), getHeight()); g.setColor(0x0000ff); g.drawLine(mX, mY, mX - 10 + mState, mY - 10); g.drawLine(mX, mY, mX + 10, mY - 10 + mState); g.drawLine(mX, mY, mX + 10 - mState, mY + 10); g.drawLine(mX, mY, mX - 10, mY + 10 - mState); flushGraphics(); } }

Исходный код примера в данной статье содержит мидлет, который использует эту канву. Попробуйте запустить SimpleGameMIDlet для того, чтобы посмотреть, как все это работает. Вы увидите что-то похожее на морскую звезду, выполняющую вольные упражнения (возможно, компенсируя отсутствие ног).


Образ экрана SimpleGameMIDlet

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

Game API обеспечивает гибкую поддержку уровней при помощи четырех классов:

  • Layer является абстрактным предком всех уровней. Он определяет основные атрибуты уровня, а именно - позицию, размер и видимость уровня. Каждый подкласс Layer должен определить метод paint() для прорисовки уровня на рабочей поверхности Graphics . Два конкретных подкласса TiledLayer и Sprite должны удовлетворить ваши требования в программировании двумерных игр.
  • TiledLayer используется для создания фоновых изображений. Вы можете использовать небольшой набор графических элементов для эффективного построения больших изображений.
  • Sprite представляет собой уровень анимации. Вы предоставляете исходные фреймы и получаете полный контроль над анимацией. Спрайты также обеспечивают возможность получения зеркального отображения и вращения исходных фреймов на углы, кратные 90 градусам.
  • LayerManager является очень подходящим классом для отслеживания всех уровней вашей сцены. Единичного вызова метода LayerManager paint() достаточно для прорисовки всех связанных с ним уровней.

Использование TiledLayer

Класс TiledLayer является простым для понимания и в то же время имеет некоторые не видимые на первый взгляд внутренние нюансы. Основная его идея - исходное изображение предоставляет набор элементов, которые могут быть размещены при формирования большой сцены. Например, следующее изображение имеет размер 64 х 48 пикселей.

Это изображение может быть разделено на 12 элементов размером 16 х 16 пикселей. Класс TiledLayer присваивает каждому элементу изображения номер, начиная с 1 в верхнем левом углу. Элементы в исходном изображении пронумерованы следующим образом:


Нумерация элементов изображения

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

Image image = Image.createImage("/board.png"); TiledLayer tiledLayer = new TiledLayer(10, 10, image, 16, 16);

В данном примере новый объект TiledLayer имеет 10 столбцов и 10 строк. Взятые из изображения элементы представляют собой квадраты размером 16 пикселей.

Создание сцены при помощи этих элементов является простой задачей. Для присвоения элемента какой-либо ячейке вызовите setCall() . Вы должны указать номер столбца и строки ячейки и номер элемента. Например, вы можете присвоить элемент 5 третьей ячейке во второй строке, вызвав метод setCell(2, 1, 5) . Эти параметры могут показаться вам не правильными. Но обратите внимание, что индекс элемента начинается с 1, а номера столбца и строки начинаются с 0. По умолчанию все ячейки нового объекта TiledLayer имеют значение номера элемента равное 0. Это значит, что они пусты.

Следующий фрагмент демонстрирует один из способов заполнения TiledLayer при помощи массива чисел, имеющих тип integer . В реальной игре TiledLayers может определяться из файлов ресурсов, что обеспечит большую гибкость в построении фона и развитии игры за счет новых игровых полей или уровней.

Private TiledLayer createBoard() { Image image = null; try { image = Image.createImage("/board.png"); } catch (IOException ioe) { return null; } TiledLayer tiledLayer = new TiledLayer(10, 10, image, 16, 16); int map = { 1, 1, 1, 1, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 11, 0, 0, 0, 0, 0, 0, 0, 7, 6, 0, 0, 0, 0, 0, 0, 0, 7, 6, 0, 0, 0 }; for (int i = 0; i < map.length; i++) { int column = i % 10; int row = (i - column) / 10; tiledLayer.setCell(column, row, map[i]); } return tiledLayer; }

Для отображения объекта TiledLayer на экране необходимо передать объект Graphics в его метод paint() .

TiledLayer поддерживает также анимированные элементы, упрощающие перемещение набора ячеек по последовательности элементов. Более детальная информация приведена в документации по API для TiledLayer .

Использование спрайтов для анимации персонажа

Другим конкретным объектом Layer , предоставляемым в Game API, является Sprite . В некотором смысле, Sprite является концептуальной инверсией TileLayer . В то время как TiledLayer использует палитру элементов исходного изображения для формирования большой сцены, Sprite использует последовательность фреймов исходного изображения для анимации.

Все, что вам необходимо для создания объекта Sprite - исходное изображение и размер каждого фрейма . В TiledLayer исходное изображение разделяется на одинаковые по размеру элементы; в Sprite элементы изображения называются фреймами. В приведенном ниже примере для создания объекта Sprite с размером фрейма 32 х 32 пикселя используется исходное изображение tank.png .

Private MicroTankSprite createTank() { Image image = null; try { image = Image.createImage("/tank.png"); } catch (IOException ioe) { return null; } return new MicroTankSprite(image, 32, 32); }

Каждый фрейм исходного изображения имеет номер, начинающийся с нуля и выше. (Не путайтесь! Помните, что номера элементов изображения начинаются с 1.) Объект Sprite содержит последовательность фреймов , определяющую порядок отображения фреймов. По умолчанию последовательность фреймов в новом объекте Sprite начинается с 0 и продолжается дальше по всем доступным фреймам.

Для перехода к следующему или предыдущему фрейму в последовательности фреймов используются методы Sprite nextFrame() и prevFrame() . Эти методы выполняют циклические переходы в начале или конце последовательности фреймов. Например, если объект Sprite показывает последний фрейм в последовательности фреймов, вызов метода nextFrame() вызовет показ первого фрейма последовательности.

Для указания отличной от принятой по умолчанию последовательности фреймов, передайте последовательность в виде массива целых чисел в метод setFrameSequence() .

Есть возможность перейти к определенному элементу текущей последовательности фреймов при помощи вызова метода setFrame() . Нет способа перейти к конкретному фрейму по его номеру. Вы можете только перейти к определенному элементу в последовательности фреймов.

Изменения фрейма станут видимыми только при следующей прорисовке объекта Sprite при помощи метода paint() , наследованного от Layer .

Объект Sprite может также трансформировать исходные фреймы. Их можно вращать на углы, кратные 90 градусам, зеркально отображать, либо комбинировать эти операции. Для этого применяются константы класса Sprite . Текущая трансформация объекта Sprite может быть установлена путем передачи одной из этих констант в метод setTransform() . В следующем примере текущий фрейм зеркально отображается вокруг вертикальной оси и поворачивается на 90 градусов:

// Sprite sprite = ... sprite.setTransform(Sprite.TRANS_MIRROR_ROT90);

Трансформации действуют так, чтобы опорный пиксель объекта Sprite не перемещался. По умолчанию опорный пиксель расположен по координатам 0,0 в координатной сетке объекта Sprite в его верхнем левом углу. При выполнении трансформации расположение опорного пикселя также трансформируется. Месторасположение объекта Sprite корректируется так, чтобы опорный пиксель остался на том же самом месте.

Вы можете изменить месторасположение опорного пикселя при помощи метода defineReferencePixel(). Для многих видов анимации вы будете определять опорный пиксель в центре спрайта.

И, наконец, класс Sprite предоставляет несколько методов collidesWith() для обнаружения конфликтов с другими объектами Sprite , TiledLayer или Image . Вы можете обнаружить конфликт при помощи конфликтных прямоугольников (быстро, но грубо) или на уровне пикселей (медленно, но точно). Нюансы использования этих методов приведены в документации по API.

Пример muTank демонстрирует использование объектов TiledLayer , Sprite и LayerManager .

Важнейшими классами являются MicroTankCanvas , содержащий большую часть кода, и MicroTankSprite , который инкапсулирует поведение танка.

MicroTankSprite интенсивно применяет трансформации. Используя исходное изображение только с тремя фреймами, MicroTankSprite может отобразить танк, указывающий на 16 различных направлений. Два открытых public метода turn() и forward() облегчают управление танком.

MicroTankCanvas представляет собой подкласс GameCanvas и содержит цикл анимации в методе run() , который должен быть вам знаком. Метод tick() определяет факт столкновения танка с границей поля. Если это обнаружено, последнее перемещение отменяется при помощи метода MicroTankSprite undo() . Метод input() просто контролирует нажатия клавиш и соответствующим образом корректирует направление или позицию танка. Метод render() использует объект LayerManager для управления прорисовкой. Объект LayerManager содержит два уровня - один для танка, один для игрового поля.

Метод debug() , вызываемый из цикла программы, сравнивает время цикла программы с желаемым временем цикла (80 миллисекунд) и отображает процент использованного времени на экране. Этот процесс выполняется только в диагностических целях и должен быть удален перед передачей игры пользователям.

Синхронизация цикла игры является более сложной, чем в предыдущем примере SimpleGameCanvas . Для того чтобы выполнить одну итерацию цикла игры точно за 80 миллисекунд, программа MicroTankCanvas измеряет время, затраченное на выполнение tick() , input() и render() . Затем она останавливается на оставшееся до 80 миллисекунд время цикла, поддерживая общее время выполнения каждой итерации как можно ближе к значению в 80 миллисекунд.

Резюме

Game API пакета MIDP 2.0 предоставляет среду, упрощающую разработку двумерных игр. Во-первых, класс GameCanvas обеспечивает методы прорисовки и ввода событий клавиатуры, которые делают возможным создание компактного цикла игровой программы. Далее, система уровней дает возможность создавать сложные сцены. Класс TiledLayer собирает большой фон или сцену из палитры элементов исходного изображения. Класс Sprite подходит для анимированных персонажей и способен обнаруживать конфликты с другими объектами в игре. Класс LayerManager является связующим элементом, собирающим вместе различные уровни. Пример muTank предоставляет основу рабочего кода, демонстрирующего использование Game API.

Об авторе: Jonathan Knudsen [e-mail ] [домашняя страница ] является автором нескольких книг, в том числе "Беспроводная Java, второе издание ", "Неофициальное руководство по роботам LEGO MINDSTORMS ", "Изучение Java, второе издание " и "2D-графика в Java ". Джонатан написал много статей о Java и роботах Lego, в том числе статьи для JavaWorld, EXE, NZZ Folio и O"Reilly Network. Джонатан имеет степень Принстонского университета по машиностроению.

ОСТОРОЖНО! Концентрированная ностальгия! Описанное здесь морально устарело много-много лет назад. Более того, оно устарело до того, как было реализовано. Из статьи вы не узнаете ничего нового.
В давние-давние времена, когда Embarcadero CodeGear Borland Delphi RAD Studio C++ Builder был версии 4, когда Autodesk 3ds Max Discreet 3dsmax 3D Studio Max был версии 3, когда 80 Гб и 200 МГц были не в холодильниках, а в компьютерах, когда Интернет был неведомым и таинственным существом, каждое прикосновение к которому было магическим… я учился в старших классах школы и учился программировать. А также моделировать. И ещё всякое-разное по мелочи.
Подозрительный скриншот:

Папа был ментом-программистом (сейчас просто мент коп). И купил он комп - теоретически для подработок, но вертолётный завод как-то слишком резко усох, и комп использовался (по назначению) редко. Несколько раз наблюдал картину: всякие кнопочки, формочки, всякое-разное нажимается, а потом оно работает (был это C++ Builder). Глаза загорелись: это же круто - рисуешь кнопочки, а потом оно само что-то делает! Меня пустили формочки порисовать.
Однако возникла проблема: кнопочки рисуются, но вот что-то работать не хотят! Внезапно выяснилось, что в чудо-программе одним рисованием кнопки не задвигаются, нужно писать какой-то странный непонятный код. Мне сунули книжку по Билдеру. Внезавно выяснилось, что перед её прочтением весьма полезно хоть немного знать Си… Мне сунули книжку по Си.
Получилось странное: книжка по Си с программами, которые в Билдере нифига не работают (понятно, что запустить при умении можно, однако такого рода шаманствам я был необучен), и книжка по Билдеру, которая требует познаний в Плюсах (вот здесь класс, вот здесь метод, вот здесь заголовочный файл...). Но что-то начинало шевелиться. Перепечатывание кода из книжки - занятие не для слабонервных (это потом я уже узнал, что к книжке ещё диск полагался...), но жажда познания тащила. И самое интересное: в самом конце книжки по Билдеру была программа, работающая с графикой. Выводились спрайты! Цветные картинки, фигурно обрезанные, с анимацией! (Все, наверное, уже поняли, что за книжка у меня была - в те времена её кривой скан прилагался к каждому пиратскому диску с Билдером.)
Суть метода: берём картинку, будущую прозрачную область заполняем чёрным цветом, рядом располагаем маску: объект - чёрным, фон - белым. Выводим на канву сначала маску в режиме SrcAnd, потом картинку в режиме SrcPaint. Чтобы это всё не умерцалось, сначала рисуем всё в картинку в памяти, потом выводим на канву на форме. Был раскрыт секрет магии! И понеслось…

Battlecruiser

Краткая сводка:
Жанр: side-scroller
Графика: спёрта из StarCraft (юниты), спёрта из Motoracer (экран победы)
Звуки: спёрты шут знает откуда (у меня валялась куча звуков, выковырянных из игр)
Музыка: спёрта шут знает откуда (кто-нибудь знает, откуда мог взяться hitgm.mid?)
Используемый софт: C++ Builder, Sound Recorder, MS Paint


Скрин из игры:


Попробовал C++ Builder 6 - эта программа завелась! Правда в режиме совместимости со хрюшкой (старый TMediaPlayer накрывается медным тазом без режима совместимости). Несмотря на наличие подробной инструкции к каждому уровню, пройти толком не удалось… сложно.
Скачать игру Battlecruiser (770 KB, сорцы и исполняемый файл прилагаются, запускать в режиме совместимости)
Однако моим любимым жанром был RTS, поэтому довольно скоро я сел писать свою версию угадайте чего…

DeathCraft

Краткая сводка:
Жанр: real-time strategy
Графика: спёрта из StarCraft, нарисована самостоятельно
Звуки: спёрты шут знает откуда
Речь: записана своя
Музыка: спёрта шут знает откуда
Используемый софт: C++ Builder, 3D Studio Max, MS Paint, Sound Forge
В процессе написания игры я учился 3D-моделированию.
Знакомый скриншот?


Вы когда-нибудь пробовали пользоваться 3D Studio Max на Pentium 200 MHz, 32 MB RAM? Скажу я вам, процесс хорошо тренирует нервы. Студия только грузится минут 10-15… рендеринг самых простых моделек по минуте… рендеринг одного кадра космического полёта из прилагаемого примера минут по десять… некоторые вещи типа кисточки весов модификатора кожи просто подвешивали компьютер… Но! Можно рисовать крутые вещи!
Изначально к игрушке прилагался ролик-заставка с двумя роботами, идущими с дулами друг на друга. Перед тем, как показать почтенной публике, решил перерендерить видео, но его не нашёл. Нашёл сцену с другим роботом, примерно той же эпохи. Теперь на заставке этот робот.
Последний кадр анимации, пора жать Start.


Вы когда-нибудь пробовали пользоваться C++ Builder на Pentium 200 MHz, 32 MB RAM? Это, конечно, не 3D Studio Max, но компиляция элементарного проекта типа моего (хотя он тогда для меня казался совсем не элементарным) могла занимать под минуту.
C++ Builder 6 со старым проектом:


Вы когда-нибудь… а хотя не, Sound Forge на древних компах работал вполне себе шустро. Теперь к эффектам эха, ускорения, замедления, разворота из стандартной Sound Recorder добавились эффекты с непонятными названиями типа Flanger и Wah-wah. Звуки можно было нормально увязывать друг за дружкой. Звучало круто. Даже динозавр говорит моим голосом.
Давайте запустим:

Некоторые юниты выдернуты из StarCraft. Тогда я не знал, что графику можно выдёргивать кошерными способами, и пользовался старым-добрым Print Screen. И редактировал в MS Paint. Старался принтскринить в зимних локациях, чтобы пиксели было легче отковыривать. Вы спросите: MS Paint и 3D Studio Max - не странный ли наборчик? Странный. Однако откуда мне было знать про другие редакторы - про эти ваши интернеты я тогда и не слыхивал. Я ж ни одно решение загуглить не мог - или сам, или никак. Папа со своими базами данных в моих игрушках тоже ни в зуб ногой…
Некоторые юниты нарисованы в 3D Studio Max. Как и весь софт, брался он на «чеховском рынке» (казанская версия «горбушки»). Книжку мне родители купили. Туториал к программе страшный, ужасный, на английском и без исходников прилагался (или это уже в следующей версии было?..). Что-то наковырялось. В основном роботы.
В игре есть AI. Противник строит здания (по списку, располагает по кругу относительно командного центра), строит собирающие юниты (до лимита), строит атакующие юниты (до лимита), атакует игрока (при наборе минимума атакующих юнитов). AI никакой, в общем-то, но своё дело знает, и игрока выносит весьма эффективно.
Противник строит бараки всей охапкой SCV и тренирует подкрепление:


Алгоритм поиска пути отсутствует, юниты упираются в первое же препятствие, так что их лучше отправлять по диагонали - если все препятствия прямоугольные, то их можно будет обойти. Юниты имеют неприятную особенность слипаться в одну точку. Для юнитов игрока задаётся разброс, а вот комп иногда ходит одной неубиваемой стопкой (привет Civilization).
Роботы на защите родной базы:


Иногда эффекты рисовались на совесть, иногда было лень, и лепились графические примитивы.
Яичница:


Код… код ужасный. Если обычно программист видит свой код после пары лет и ужасается, то представьте, каково мне…
for(i=0;iCells.x/16+x].y/16+y]="0"; Boom[j].type=0; Boom[j].npic = (boomcount==1)?(0):(random(11)-10); } Building[i].alive=false; } if(Building[i].type==3){ int bx = (Building[i].x+Building[i].sqleft*16+Building[i].sqwidth*8)/16, by = (Building[i].y+Building[i].sqtop*16+Building[i].sqheight*8)/16, ux, uy; if(Building[i].atkunt==-1){ Building[i].atkpos=0; Building[i].npic=0; for(j=0;jBuilding[i].atkr|| Unit[j].player!=0||!Unit[j].alive||!Unit[j].visible){ Unit.atkunt].atked=false; Building[i].atkunt=-1; Building[i].atking=false; Building[i].atkpos=0; Building[i].npic=0; } } if(Building[i].atkunt!=-1){ Building[i].turn=-1; j=Building[i].atkunt; float x=Unit[j].x+Unit[j].centerx-Building[i].x-Building[i].sqleft*16- Building[i].sqwidth*8, y=Unit[j].y+Unit[j].centery-Building[i].y-Building[i].sqtop*16- Building[i].sqheight*8; int sval=41, lval=241, A=(y==0)?(99999999):(x/y*100); if(y==0)y=0.1; if(A>=-sval && A<=sval && y<=0) Building[i].turn=0.1; if(A>=-lval && A<=-sval && x>=0 && y<=0) Building[i].turn=1.1; if(abs(A)>=lval && x>=0) Building[i].turn=2.1; if(A>=sval && A<=lval && x>=0 && y>=0) Building[i].turn=3.1; if(A>=-sval && A<=sval && y>=0) Building[i].turn=4.1; if(A>=-lval && A<=-sval && x<=0 && y>=0) Building[i].turn=5.1; if(abs(A)>=lval && x<=0) Building[i].turn=6.1; if(A>=sval && A<=lval && x<=0 && y<=0) Building[i].turn=7.1; if(Building[i].turn<=-0.9&&Building[i].turn>=-1.1)Building[i].turn=0.1; } else{ Building[i].turn+=0.25; if((int)Building[i].turn>=8)Building[i].turn=0.1; Building[i].turn = int(Building[i].turn/0.25)*0.25; } if(Building[i].atking){ Building[i].atkpos++; if(Building[i].atkpos>=10) Building[i].atkpos=0; if(Building[i].atkpos==1||Building[i].atkpos==3||Building[i].atkpos==5){ sndPlaySound("Sound\\Special\\Atk1",SND_ASYNC+SND_NODEFAULT); j=Building[i].atkunt; Building[i].npic=1; Unit[j].hits-=Building[i].atkfrc; if(Unit[j].hits<=0){ Unit[j].hits=0; Unit[j].dying=true; } } else Building[i].npic=0; } } ...

И вот такая простыня - на две с лишним тысячи строк.

DeathCraft: инструкция

Юниты:

  • 0 SCV: собирает ресурсы, строит здания, производится в Command Center. И минералы, и газы добываются в Refinery.
  • 1 Vulture: боевой юнит противника, стреляет лазером, скоростной, производится в Barracks.
  • 2 Wraith: летающий юнит игрока, не атакует, летает, скоростной, производит высадку Dinosaur, производится в Barracks.
  • 3 Car: ничего не делающий юнит игрока.
  • 5 Robot: боевой юнит игрока, стрелят яичницей, медленный, производится в Barracks.
  • 6 Dinosaur: боевой юнит игрока, не атакует, мало жизней, при взрыве сносит к чертям все юниты в радиусе взрыва.

Здания:

  • 0 Command Center: производит SCV, сюда они носят добытые ресурсы.
  • 1 Refinery: место добычи ресурсов с помощью SCV.
  • 2 Barracks: место постройки боевых юнитов.
  • 3 Gun: боевое здание противника.

Управление мышью: выделение юнитов и зданий левым кликом, команда идти и атаковать правым кликом. Выделения области нет, но есть выделение всех юнитов выбранного типа на экране с помощью Ctrl.
Управление клавиатурой:

  • Стрелки: перемещение по карте
  • H: следование за выбранным юнитом
  • P: пауза
  • Delete: удаление юнита
  • C+U+F12: чит-код для создания юнита. Потом нужно ввести на нумпаде две цифры - код юнита и (опционально) две цифры - номер игрока. Например, C+U+F12, 0500 переключит в режим создания роботов игроку с помощью мыши. Чтобы отключить, снова нажать C+U+F12.

Подсказки:

  • Защищайте Refinery! В первую очередь противник будет сносить это здание.
  • Если не успели построить армию, а противник уже напал, то сброшенные с самолёта динозавры вас спасут.
  • Стройте много, стройте быстро.
#include //Подключаем графическую библиотеку
#include //Библиотека содержит функцию exit
//(см. GraphInit)
#include //Библиотека содержит функцию printf
//(см. GraphInit)
#include //Библиотека содержит функцию getch

Enum Direction {LEFT, UP, RIGHT, DOWN};
//Перечисляемый тип - направления движения

Const int Width = 15; //Ширина поля в клетках
const int Height = 15; //Высота поля в клетках
const int CellSize = 30; //Размер клетки в пикселах

Int Cell={ //Задаем массив клеток поля

{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1},
{1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1},
{1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1},
{1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1},
{1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1},
{0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0},
{1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1},
{1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1},
{1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1},
{1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1},
{1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1},
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
}; //Ноль - клетка свободна, единица - клетка занята

Void DrawField() //Функция, рисующая поле
{
setcolor(WHITE); //Устанавливаем цвет линий
rectangle(0, 0, 30 * Width, 30 * Height); //Внешняя граница поля
for (int i = 1; i //Рисуем вертикальные линии сетки
line(i * CellSize, 1, i * CellSize, Height * CellSize);
for (i = 1; i line(1, i * CellSize, Width * CellSize, i * CellSize);
setfillstyle(1, WHITE); //Устанавливаем стиль заливки
for (i = 0; i for (int j = 0; j if (Cell[i][j] == 1) //Рисуем все занятые клетки
bar(i * CellSize, j * CellSize, (i + 1) * CellSize,
(j + 1) * CellSize);
}

Class CharacterClass //Класс "Персонажа"
{
int Radius; //Радиус круга
int X, Y; //Положение на поле
public:
CharacterClass(); //Конструктор класса
void Draw(); //Функция, рисующая "персонаж"
void Erase(); //Функция, стирающая "персонаж"
void Move(int Cell, Direction dir);
//Движение "персонажа". Аргументы - массив клеток поля и
//направление движения
};
CharacterClass::CharacterClass()
{
Radius = 15;
X = 7; Y = 0; //Устанавливаем начальные координаты -
//на одной из свободных клеток поля
}
void CharacterClass::Draw()
{
setcolor(BLUE); //Устанавливаем цвет линии окружности...
setfillstyle(1, BLUE); //...и цвет внутренней заливки
Radius); //Окружность с закрашенной внутренней областью
}
void CharacterClass::Erase()
{
setcolor(BLACK); //Устанавливаем цвета фона
setfillstyle(1, BLACK);
fillellipse((2 * X + 1) * Radius, (2 * Y + 1) * Radius, Radius,
Radius);
}
void CharacterClass::Move(int Cell, Direction dir)
{
Erase(); //Перед перемещением закрашиваем текущее положение
switch(dir) //В зависимости от направлении движения...
{ //...если клетка-цель пуста и не будет выхода за пределы поля,
//перемещаем "персонаж"
case LEFT: if ((X > 0) && Cell[Y] != 1) X--; break;
case UP: if (Y > 0 && Cell[X] != 1) Y--; break;
case RIGHT: if (X case DOWN: if (Y }
Draw(); //Рисуем в новом положении
}

Void main()
{
//Текст функции расположен по ссылке.
//Функция инициализирует графику

Int exitOk = 0; //Флаг, означающий выход из программы
char c; //Считываемый с клавиатуры символ

DrawField(); //Сначала рисуем поле

CharacterClass Character;
Character.Draw(); //Рисуем "персонаж"

While (!exitOk)
{
c = getch(); //Считываем с клавиатуры символ
switch (c) //Если была нажата клавиша-стрелка,..
{ //...перемещаем "персонаж"
case 27: exitOk = !0; //Выход по "Escape"
case 75: Character.Move(Cell, LEFT); break;
case 72: Character.Move(Cell, UP); break;
case 77: Character.Move(Cell, RIGHT); break;
case 80: Character.Move(Cell, DOWN); break;
}
DrawField(); //После перемещения вновь рисуем поле
}
closegraph();



© 2024 beasthackerz.ru - Браузеры. Аудио. Жесткий диск. Программы. Локальная сеть. Windows