Разработка компьютерной игры на языке Java

Создание экшн-игры в космосе в жанре двухмерных игр, изучение особенностей отображения графики средствами JavaFX в языке Java. Система взаимодействий объектов в виртуальном двумерном пространстве. Создание иерархической структуры игровых классов.

Рубрика Программирование, компьютеры и кибернетика
Вид дипломная работа
Язык русский
Дата добавления 07.08.2018
Размер файла 5,0 M

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

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

play.setOnMouseClicked(event -> {

newGame(width, heigh, primaryStage);

});

Чтобы при клике на кнопку Play запускалось наше приложение, необходимо добавить слушатель, который бы создавал новый экземпляр класса. Для этого будет создан отдельный класс, который называется Game. Этот класс будет управлять всеми событиями происходящими во время игры.

Так как наше приложение полноэкранное, необходимо реализовать кнопку выхода. Делается данная кнопка по аналогии кнопки Play. Только вместо создания нового экземпляра класса необходимо вызывать завершение приложения.

exit.setOnMouseClicked(event ->

System.exit(0)

);

Рис. 3.8 - Итоговый вид

Стоит уделить особое внимание подсчету места при повороте корабля. Так как, если корабль не поместится в отведенное ему место и отступ между ячейками, он автоматически расширит первый столбик на необходимое число пикселей. Во время анимации поворота картинка будет дергаться, а меню «прыгать» в стороны на ощутимое число пикселей. Удовольствия от просмотра такого меню нет, а значит необходимо рассчитать это заранее. Чтобы узнать максимальную ширину необходимо вычислить диагональ прямоугольника размерами тридцать пять на пятьдесят. Эти размеры взяты исходя из размеров, которые мы задали кораблю на первом шаге создания меню. На данном этапе создание меню закончено. Последним штрихом является удаление строки, отвечающей за отрисовку границ таблицы. МетодыsetOnMouseиsetOffMouseописаныдалее.

public void setOnMouse() {

FillTransitionfillTransition = new FillTransition(Duration.millis(500), bg);

fillTransition.setToValue(javafx.scene.paint.Color.AQUA);

fillTransition.play();

FillTransitionfadeTransition = new FillTransition(Duration.millis(500), text);

fadeTransition.setToValue(javafx.scene.paint.Color.YELLOW);

fadeTransition.play();

bg.setOpacity(0.7);

}

public void setOffMouse() {

FillTransitionfillTransition = new FillTransition(Duration.millis(500), bg);

fillTransition.setToValue(javafx.scene.paint.Color.BLACK);

fillTransition.play();

FillTransitionfadeTransition = new FillTransition(Duration.millis(500), text);

fadeTransition.setToValue(javafx.scene.paint.Color.WHITE);

fadeTransition.play();

bg.setOpacity(0.5);

}

Код почти не отличается от кода трансформаций в классе Menu. Исключение составляет лишь то, что проверки упущены в данных методах, а единственная новая строка трансформации заключается в плавном изменении цвета. Проверка упущена из-за особенности заливки, так как в данном варианте она автоматически прерывает другие трансформации. За изменение цвета в JavaFX8 отвечает класс FillTransition.

3.3 Создание игрового поля

Для создания уровня необходимо много действий. Изначально необходимо добавить корабль на нашу сцену аналогичным образом. Как и в случае с меню необходимо сразу загрузить фон, установить размер сцены и т.д. Выделим абстрактный класс SpaceShips для наследования от него других кораблем, которые будут делиться на союзные и вражеские. При столкновении собственных пуль с союзными кораблями не будет вызываться никаких событий. В случае обычного столкновения будет вызываться метод, не позволяющий «цеплять» объект другим объектом. Также необходимо подумать об управлении нашим кораблем. Выберем модель перемещения WASD, вращение в направлении курсора. Чтобы проверить столкновения каждого объекта на экране создадим заранее списки ArrayList для пуль и кораблей. Также при уничтожении корабля или пули необходимо их удалять. Для этого создадим два списка, которые будут содержать объекты необходимые для удаления. Чтобы управлять кораблем необходимо создать коллекции HashMap, которые будут хранить клавишу и ее состояние в момент отрисовки кадра.

EnemySpaceShipenemyShip;

MySpaceshipmyShip;

Space backgroud;

public static HashMap<KeyCode, Boolean>keys = new HashMap<>();

public static HashMap<MouseButton, Boolean>mouse = new HashMap<>();

public static ArrayList<SpaceShips>ships = new ArrayList();

public static ArrayList<Bullets>bullets = new ArrayList();

public static ArrayList<SpaceShips>shipsForRemove= new ArrayList();

public static ArrayList<Bullets>bulletsForRemove= new ArrayList();

Далее необходимо добавить два тестовых корабля, на основе которых будет произведена разработка.

myShip = new MySpaceship();

enemyShip = new EnemySpaceShip();

backgroud = new Space();

ships.add(myShip);

ships.add(enemyShip);

root.getChildren().addAll(backgroud, myShip, enemyShip);

scene.setOnKeyPressed(event -> {

keys.put(event.getCode(), true);

});

scene.setOnKeyReleased(event -> {

keys.put(event.getCode(), false);

});

Для обработки движения мыши опишем слушатель, который будет с помощью лямбда-выражения передавать свои координаты в объект myship.

scene.setOnMouseMoved(event -> {

myShip.pointer = new Point2D(event.getSceneX(), event.getSceneY());

});

Стоит учитывать момент, что при зажатии кнопки мыши (Drag&Drop) или нажатии (Pressed), вызываются другие обработчики, которые также необходимо реализовать, чтобы избежать блокирования угла поворота. Чтобы активировать режим стрельбы у корабля или дезактивировать его, необходимо также описать два слушателя.

scene.setOnMousePressed(event -> {

mouse.put(event.getButton(), true);

myShip.pointer = new Point2D(event.getSceneX(), event.getSceneY());

});

scene.setOnMouseReleased(event -> {

mouse.put(event.getButton(), false);

});

Чтобы отрисовывать сцену каждый момент времени, а не только при нажатии кнопок добавим реализацию класса AnimationTimer. В дальнейшем необходимо вместо обновления конкретных кораблей обновлять содержимое списка ships, который должен содержать все корабли. Так как каждый корабль унаследован от SpaceShips, проблем с безопасностью приложения не возникает, а значит компиляция пройдет успешно.

AnimationTimer timer = new AnimationTimer() {

@Override

public void handle(long now) {

myShip.update();

enemyShip.update();

}

};

timer.start();

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

myship = newImageView(newImage("img/spaceships/own_1/ship_1.png"));

myship.setFitWidth(width);

myship.setFitHeight(height);

myship.setX(myship.getX() - (width / 2.0));

myship.setY(myship.getY() - (height / 2.0));

myship.setTranslateX(Launcher.width/ 2.0);

myship.setTranslateY(Launcher.heigh/ 2.0);

В данном коде загружается скин корабля, устанавливается его высота и ширина. В методах setX и setY переносится центр изображения из левого верхнего левого угла в математический центр. Это сделано для того, чтобы вращение и перемещение нашего корабля было относительно центра. В методе setTranslateX и setTranslateY происходит смещение изображение от левого верхнего угла. В данном фрагменте изображение перемещается на центр сцены. Теперь необходимо научить корабль перемещаться по нажатию клавиши. В основном классе (Game) вызывается при отрисовке каждого кадра метод update(), необходимо описать реализацию этого метода на примере корабля. Для отображения нужного нам двигателя при считывании активных клавиш составим небольшой алгоритм на основе рисунка 3.9

Рис. 3.9 - Активные двигатели при выбранном отклонении, цвет означает нажатую клавишу

if (isPressed(KeyCode.D)) {

engieMaster("D");

myship.setTranslateX(myship.getTranslateX() + 5);

right_engie.setTranslateX(right_engie.getTranslateX() + 5);

left_engie.setTranslateX(left_engie.getTranslateX() + 5);

bottom_engie.setTranslateX(bottom_engie.getTranslateX() + 5);

}

Так как необходимо перемещать объект вместе с двигателями, описываем перемещение этих объектов.

За выбор нужных двигателей отвечает метод engieMaster, который принимает клавишу. Зная угол наклона корабля, метод включает необходимый двигатель.

Рис. 3.10 - Пример графических файлов корабля

Для просчета угла отклонения необходимо взять точку центра корабля и точку в которой находится курсор. Далее необходимо повернуть корабль на нужный угол. Так как в JavaFX отсчет начинается не в верхней точке окружности, а в левой, необходимо отнять от получившегося угла девяносто градусов.

angle = Math.atan2(y1 - y2, x1 - x2) / Math.PI* 180;

angle = angle < 0 ? angle + 360: angle;

myship.setRotate(angle - 90);

Рис. 3.11 - Хитбоксы корабля

Для просчета хитбокса нашего самолета воспользуемся полигонами. Полигон для нашего корабля построим по трем точкам. Верхняя точка в основании корабля. Нижние точки рассчитываются с условием смещения общей высоты на тридцать пять пикселей. Данное действие необходимо для того, чтобы не вносить огонь из двигателей в хитбокс.

doublediag = Math.sqrt((height / 2) * (height / 2) + (width / 2) * (width / 2));

double secondAngle;

double offsetY = diag * Math.cos(Math.toRadians(angle - 90));

double offsetX = diag * Math.sin(Math.toRadians(angle - 90));

p1 = new Point2D(myship.getTranslateX() + offsetX, myship.getTranslateY() - offsetY);

height = height - 35;

diag = Math.sqrt((height / 2) * (height / 2) + (width / 2) * (width / 2));

secondAngle = Math.toDegrees(Math.cos(height / 2 / diag));

offsetY = diag * Math.cos(Math.toRadians(angle + 90 + secondAngle));

offsetX = diag * Math.sin(Math.toRadians(angle + 90 + secondAngle));

p2 = new Point2D(myship.getTranslateX() + offsetX, myship.getTranslateY() - offsetY);

secondAngle = Math.toDegrees(Math.cos(height / 2 / diag));

offsetY = diag * Math.cos(Math.toRadians(angle + 90 - secondAngle));

offsetX = diag * Math.sin(Math.toRadians(angle + 90 - secondAngle));

p3 = new Point2D(myship.getTranslateX() + offsetX, myship.getTranslateY() - offsetY);

/***/height = height + 35;

hitbox.getPoints().clear();

hitbox.getPoints().addAll(

p1.getX(), p1.getY(),

p2.getX(), p2.getY(),

p3.getX(), p3.getY()

Для просчета попадания снаряда в цель необходимо проверить пересечение границ, после проверять координаты снаряда. Двойная проверка сделана для сокращения времени на обработку одного объекта. Такая проверка обеспечивает большую экономию времени на обработку кадра.

if (hitbox.intersects(enemyHitbox.getBoundsInLocal())) {

for (inti = 0; i<enemy.length; i++) {

if (hitbox.contains(enemy[i].getX(), enemy[i].getY())) {

Рис. 3.12 - Спрайт попадания

При пересечении координат корабля с координатами пули проигрывается анимация попадания. Также можно запрограммировать любую анимацию при данном событии. Для отображения данной анимации необходимо к ImageView применять метод setViewport, который изменит смещение на данном изображении.

dmg2.setViewport(new Rectangle2D(offsetXanimation, 0, 82, 82));

За проигрывание анимации отвечает дополнительный класс Remover. Данный класс реализует обычный цикл внутри потока. После того, как отыгрывается анимация через цикл for, вызывается метод removeDmg.

class Remover extends Thread {

Bullet b;

public Remover(Bullet b) {

this.b = b;

}

public void run() {

try {

for (int i = 0; i < 9; i++) {

b.incAnimation();

TimeUnit.MILLISECONDS.sleep(50);

}

} catch (InterruptedException e) {

e.printStackTrace();

}

b.removeDmg();

}

}

В данном классе происходит увеличение значения поля offsetXanimation, которое задает смещение на спрайте с попаданием. Так как в JavaFX8 нельзя влиять на графическое содержимое класса, необходимо создать метод внутри класс Bullet.

public void incAnimation() {

Platform.runLater(() -> {

dmg2.setViewport(new Rectangle2D(offsetXanimation, 0, 82, 82));

System.out.println("OFFSET " + offsetXanimation);

System.out.println(dmg2.getViewport() + "\n");

offsetXanimation = offsetXanimation + 82;

});

}

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

publicvoidremoveDmg() {

Platform.runLater(() -> {

getChildren().remove(dmg2);

});

}

Метод removeDmg удаляет спрайтовый объект отображающий урон со сцены. Важно не забыть удалить изображение пули, которая присутствует на сцене до регистрации попадания. Данную задачу решает метод onScreen, который проверяет координаты пули.

publicbooleanonScreen() {

return (

(bullet.getTranslateX() > -100)

&& (bullet.getTranslateX() <Game.width+ 100)

&& (bullet.getTranslateY() <Game.height+ 100)

&& (bullet.getTranslateY() > -100));

}

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

synchronized (Game.bulletsForRemove) {

Game.bullets.removeAll(Game.bulletsForRemove);

Game.bulletsForRemove.clear();

}

getChildren.remove(bullets1);

Метод обновления координат пули приведен ниже.

public void update() {

if (!hited) {

double offsetY = bulletSpeed * Math.sin(Math.toRadians(this.angle + 90));

double offsetX = bulletSpeed * Math.cos(Math.toRadians(this.angle + 90));

bullet.setTranslateX(bullet.getTranslateX() + offsetX);

bullet.setTranslateY(bullet.getTranslateY() + offsetY);

position = new Point2D(bullet.getTranslateX(), bullet.getTranslateY());

if (Game.ships.get(1).getHitbox().contains(position)) {

hited = true;

dmg2.setTranslateX(position.getX());

dmg2.setTranslateY(position.getY());

dmg2.setOpacity(1);

dmg2.setRotate(new Random(System.currentTimeMillis()).nextInt(360));

getChildren().add(dmg2);

new Remover(this).start();

getChildren().remove(bullet);

Game.bulletsForRemove.add(this);

}

}

}

Код, отвечающий за включение и отключение двигателей в зависимости от угла наклона корабля:

public void engieMaster(String key) {

if (key.equals("A")) {

if (angle>=0 && angle <= 225){

right_engie.setOpacity(1);

}

if (angle >=135 && angle <= 0){

left_engie.setOpacity(1);

}

if (angle >=315 || angle <= 45){

bottom_engie.setOpacity(1);

}

} else if (key.equals("W")) {

if ((angle>=0 && angle <= 45) ||angle>=225){

left_engie.setOpacity(1);

}

if (angle>=135 && angle<=315){

right_engie.setOpacity(1);

}

if (angle>=45 && angle <=135){

bottom_engie.setOpacity(1);

}

} else if (key.equals("D")) {

if (angle >= 315 || (angle>=0 && angle <= 180)){

left_engie.setOpacity(1);

}

if (angle <= 45 || angle>=180){

right_engie.setOpacity(1);

}

if (angle>=135 && angle<=225){

bottom_engie.setOpacity(1);

}

} else if (key.equals("S")) {

if(angle>=45 && angle <=225){

left_engie.setOpacity(1);

}

if (angle>=315 || (angle>=0 && angle<=135)){

right_engie.setOpacity(1);

}

if(angle>=225 && angle <= 315){

bottom_engie.setOpacity(1);

}

}

}

Движок игры завершен.

4. Результаты разработки

Во время разработки были использованы графические пакеты JavaFX8, теория спрайтовой анимации, а также графические приложения для разработки графики. В ходе оптимизации были выявлены небольшие «баги» в коде, которые были исправлены. При большом количестве пуль на сцене, ArrayList содержащий пули вынужден был увеличить собственный объем, что приводило к кратковременным зависаниям. Также была произведена оптимизация изображений. Вместо загрузки нового изображения при каждом выстреле, все изображения загружались в статический класс. Данное изменение также вызвало сокращение затрат на обработку сцены.

В результате был разработан проект, который соответствует уровню инди игр 2017 года. Данный проект в жанре 2D аркады. Смысл игры заключается в уничтожении вражеских кораблей и баз, а также прокачки собственного корабля. Проект разработан по стандартам ООП. В разработке была использована четкая и продуманная иерархия, которая позволит расширить приложение, а также легко портировать его на мобильные платформы. Также в игру были добавлены сохранения и начата разработка открытого мира, который в совокупности с мультиплеером сделает проект легко адаптируемым под другие жанры. Например, при добавление нескольких полей на корабль и добавлении алгоритма выпадения вещей с убитых противников игру можно превратить в Action-RPG. Так как данный жанр один из самых популярных на данный момент, это обеспечит игре дополнительную популярность, а игра по сети добавит «реиграбельности» проекту. Также из-за грамотного ООП подхода к разработке в игру можно добавить косметику, которая является основным способом монетизации большинства современных игровых проектов.

Проект разрабатывался как дипломная работа, но будет выставлен на торговую площадку Steam, что подчеркнет статус сильной игры. Также проект будет выставлен на продажу в розничный магазин Roxen.

Рис. 4.1 - Итоговое изображение геймплея

После конца разработки была проведена работа по сборке данного приложения. Был создан установочный файл.exe для операционной системы Windows. В дальнейшем будет создан игровой клиент, отвечающий за проверку версии приложения и за автообновление его. Антипиратской защиты на приложении нет, так как данная система затратная в финансовом плане.

Заключение

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

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

И в заключение были продемонстрированы примеры, в которых были использованы концепция обмена сообщения, различные способы взаимодействия и состояния объектов.

Размещено на Allbest.ru

...

Подобные документы

  • История развития языка программирования Java. История тетриса - культовой компьютерной игры, изобретённой в СССР. Правила проведения игры, особенности начисления очков. Создание интерфейса программы, ее реализация в среде Java, кодирование, тестирование.

    курсовая работа [168,1 K], добавлен 27.09.2013

  • Сетевые возможности языков программирования. Преимущества использования Java-апплетов. Классы, входящие в состав библиотеки java.awt. Создание пользовательского интерфейса. Сокетное соединение с сервером. Графика в Java. Значения составляющих цвета.

    курсовая работа [508,1 K], добавлен 10.11.2014

  • Разработка и создание игры "Змейка". Использование динамически-активных принципов языка Java. Графические объекты программы. Описание игры, правила, теоретические сведения. Классы приложения. Типы данных. Реализация. Метод. Объект. Блок-схема игры.

    курсовая работа [12,4 K], добавлен 18.06.2008

  • Описание алгоритма хода ЭВМ в режиме "пользователь-компьютер" в игре "Морской бой". Описание совокупности классов, их полей и методов. Разработка интерфейса и руководства пользователя по проведению игры. Листинг программы, написанной на языке Java.

    курсовая работа [645,0 K], добавлен 26.03.2014

  • Описание языков программирования Java и JavaFX. Среда разработки NetBeans и класс численных методов. Архитектура и принцип работы апплета с понятным пользовательским интерфейсом. Разработка алгоритма программы на примере модели межвидовой конкуренции.

    курсовая работа [1023,2 K], добавлен 19.09.2012

  • Разработка графического редактора для рисования двухмерной и трехмерной графики, используя язык программирования Java и интерфейсы прикладного программирования Java 2D и Java 3D. Создание графического редактора 3D Paint. Основные методы класса Graphics.

    курсовая работа [197,5 K], добавлен 19.11.2009

  • Изучение объектно-ориентированного языка программирования Java, его функциональные возможности. Создание программного кода. Описание классов и методов, использованных в программе. Руководство пользователя, запуск сервера и клиентского приложения.

    курсовая работа [1,8 M], добавлен 16.09.2015

  • Принципы написания консольных приложений на языке Java в среде Eclipse. Составление программы завтрака на основе списка продуктов, передаваемых в качестве параметров в командной строке. Создание пакета для классов, интерфейса, базового класса иерархии.

    лабораторная работа [1,2 M], добавлен 01.05.2014

  • Разработка логической схемы базы данных автомобилестроительного предприятия. Инфологическое моделирование системы. Создание графического интерфейса пользователя для базы данных средствами языка программирования Java. Тестирование программных средств.

    курсовая работа [2,3 M], добавлен 16.12.2013

  • История создания языка Java. Основные принципы объектно-ориентированного программирования. Структура, особенности синтаксиса и примеры прикладных возможностей использования языка Java, его преимущества. Перспективы работы программистом на языке Java.

    курсовая работа [795,9 K], добавлен 14.12.2012

  • Java Script как язык управления сценарием отображения документа. Отличие world wide web от остальных инструментов для работы с Internet. Использование каскадных таблиц стилей в рамках разработки спецификации HTML. Элементы программы Netscape Navigator.

    контрольная работа [1,1 M], добавлен 02.12.2009

  • Кратка историческая справка развития языка Java. Анализ предметной области. Java platform, enterprise and standart edition. Апплеты, сервлеты, gui-приложения. Розработка программного кода, консольное приложение. Результаты работы апплета, сервлета.

    курсовая работа [549,2 K], добавлен 23.12.2015

  • Создание языка программирования с помощью приложения "Java". История названия и эмблемы Java. Обзор многообразия современных текстовых редакторов. Обработка строки. Методы в классе String. Java: задачи по обработке текста. Примеры программирования.

    курсовая работа [276,1 K], добавлен 19.07.2014

  • Архитектура Java и Java RMI, их основные свойства, базовая система и элементы. Безопасность и виртуальная Java-машина. Интерфейс Java API. Пример использования приложения RMI. Работа с программой "Calculator". Универсальность, портативность платформ.

    курсовая работа [208,6 K], добавлен 03.12.2013

  • Моделирование различных систем событий. Особенности мультиагентной платформы JADE. Использование агентов, нарушающих принятый порядок работы системы. Реализация программы на языке Java. Вычислительная модель агента. Моделирование игры в "наперстки".

    курсовая работа [423,6 K], добавлен 30.01.2016

  • Основа пользовательского интерфейса. Возможности пакетов java.awt.geom, java.awt, классов java.awt.Graphics и java.awt.Graphics2D. Основные графические примитивы и работа с потоками. Листинг программы и составление композиции аффинных преобразований.

    методичка [525,3 K], добавлен 30.06.2009

  • Архитектура уровня команд платформы Java, формат файла класса Java. Компилятор ассемблероподобного языка, позволяющий создавать файлы классов, корректно обрабатываемые реальной JVM, поддерживающий все команды байт-кода Java и важнейшие возможности JVM.

    курсовая работа [292,6 K], добавлен 17.09.2008

  • Принцип работы Java. Аплеты как особенность Java-технологии, характеристика методов их защиты. Модель безопасности JDK1.2 и концепция "песочницы". Иерархия криптографических сервисов, алгоритмов. Объектная организация криптографической подсистемы Java.

    реферат [54,8 K], добавлен 09.09.2015

  • Общее понятие о пакете "java.net". Логическая структура соединений через сокеты. Создание объекта Socket, соединение между узлами Internet. Способы создания потока. Алгоритм работы системы клиент-сервер. Листинг ServerForm.java, запуск подпроцесса.

    лабораторная работа [174,6 K], добавлен 27.11.2013

  • Понятие пакета как объединения классов (java.awt, java.lang). Способы импорта, проблема конфликта (пакеты содержат классы с одинаковым именем). Особенности реализации интерфейса, его поля. Понятие наследования интерфейса. Общие методы классов-оболочек.

    презентация [140,1 K], добавлен 21.06.2014

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.