JavaDoc: Создаем окно и рисуем в нем

Содержание:
В этой статье мы создадим активное окно, в котором будет происходить действие и попробуем нарисовать в нем графические примитивы.

Создание проекта и первая программа

Для начала создайте новый проект и назовите его XGMWar (Этот процесс подробно описан в данной статье). Создайте новый Package (Правый клик по папке проекта слева -> New -> Package) и назовите его core.main. Создайте еще один Package с названием game.main. Их предназначение я подробнее опишу в следующей статье.
Создайте новый Class в game.main (Правый клик по нужному Package -> New -> Class). Назовите его Init и поместите внутрь класса следующий код:
public static void main(String args[]){
    System.out.println("Hello, World!");
}
» Разъяснение кода
Этот код объявляет главную точку входа в наше приложение - метод main. Единственный его аргумент - массив строк args, который передается приложению системой.
Вторая строка использует стандартный выходной поток класса System для вывода сообщения в консоль.
Нажмите зеленую кнопку Run вверху. В консоли внизу вы увидите надпись "Hello, World!". Наша первая программа готова.

Создание окна

Для создания окна мы будем использовать класс JFrame.
Поместите в класс Init следующий код:
private JFrame mainFrame;
	
public Init(){
	mainFrame = new JFrame();
	mainFrame.setTitle("XGMWar");
	mainFrame.setSize(800, 600);
	mainFrame.setResizable(false);
	mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	mainFrame.setVisible(true);
}
	
public static void main(String args[]){
	new Init();
}
» Разъяснение кода
Первая строка когда объявляет аттрибут класса Init - объект mainFrame типа JFrame (класс, использующийся для создания окон). В конструкторе класса Init мы сначала создаем JFrame и присваиваем его аттрибуту mainFrame, а далее устанавливаем его заголовок, размеры, запрещаем эти размеры изменять и устанавливаем действие при нажатии на кнопку закрытия окна - выход из программы. Далее мы показываем это окно пользователю.
Если мы сейчас запустим нашу программу, то увидим пустое окно, с параметрами заданными в коде. При нажатии на кнопку выхода процесс программы прекратит свое существование.

Подготавливаем "холст"

Добавим еще один класс в пакет game.main и назовем его GameGraphics.
Его код будет выглядеть примерно так:
package game.main;

import java.awt.Canvas;
import java.awt.Graphics2D;
import java.awt.image.BufferStrategy;

import javax.swing.JFrame;

public class GameGraphics extends Canvas{
	private static final long serialVersionUID = 1L;
	
	private Thread threadMain;
	private BufferStrategy bufferStrategy;

	public GameGraphics(JFrame frameMain){
		frameMain.setIgnoreRepaint(true);
		frameMain.getContentPane().add(this);
		frameMain.setVisible(true);

		this.createBufferStrategy(2);

		bufferStrategy = this.getBufferStrategy();
		
		threadMain = new Thread(){
			public void run(){
				Graphics2D g2;
				while(true){
					try{
						Thread.yield();
				                g2 = (Graphics2D) bufferStrategy.getDrawGraphics();
				        
				                // Здесь отрисовка
				        
						if(!bufferStrategy.contentsLost())
					          bufferStrategy.show();
					} catch (Exception e){
						
					}
				}
			}
		};
		
		threadMain.start();
	}
}
» Разъяснение кода
Как видите, этот класс наследует другой класс, именуемый Canvas, а значит он наследует и все его свойства. Главное свойство класса Canvas состоит в том, что он позволяет реализовывать внутри себя отрисовку графического буфера (Сам буфер определяется классом Graphics, или его более совершенным дочерним классом Graphics2D, который мы и будем использовать). Именно из-за этого свойства мы наследовали класс Canvas. Как видите, членами класса являются long serialVersionUID (Это нам не нужно, сейчас оно скорее для того, чтобы не ругался компилятор), Thread threadMain (Поток, в котором будет производиться отрисовка) и BufferStrategy bufferStrategy (Специальный класс, позволяющий производить в графический буфер отрисовку с двойной буферизацией). В конструкторе класса, принимающем JFrame как аргумент (Ведь Canvas необходимо куда-то поместить, не так ли?), мы запрещаем этому JFrame автоматическую отрисовку, добавляем этот самый Canvas в JFrame и показываем этот JFrame пользователю (Строчку, которая показывает JFrame пользователю в классе Init мы потом заменим на создание GameGraphics). Далее мы инициализируем BufferStrategy и, наконец, создаем анонимный класс, наследующий класс Thread и запускаем его. В теле этого класса находится метод run (Он будет исполняться, когда мы запустим поток), а внутри этого метода бесконечный цикл, который для начала ждет завершения других потоков, затем получает графический буфер g2, рисует что-то в него (Этот код мы добавим позже), а потом с помощью bufferStrategy показывает нарисованное пользователю.
Не торопитесь запускать программу, на данный момент ничего нового вы не увидите. Перейдем к отрисовке графических примитивов.

Рисуем!

Правильным архитектурным решением, по моему мнению, будет создание нового класса, отвечающего за отрисовку.
Создадим пакет core.graphics, а в нем новый класс, который назовем Graph. Поместим в тело класса следующий код:
public void draw(Graphics2D g){
	g.setColor(Color.black);
	g.fillRect(0, 0, 800, 600);
	g.setColor(Color.red);
	g.fillOval(50, 50, 20, 10);
	g.setColor(Color.green);
	g.drawLine(10, 80, 305, 90);
	g.setColor(Color.blue);
	g.drawRect(10, 10, 770, 550);
}
» Разъяснение кода
Код простейший. Это метод, который принимает графический буфер и отрисовывает в него различные графические примитивы различных цветов по заданным координатам.
Заменим в класс Init строчку
mainFrame.setVisible(true);
на
new GameGraphics(mainFrame, new Graph());
Не обращайте внимания на ошибки, переходите к классу GameGraphics. Нужно добавить к членам класса экземпляр Graph, в аргументы конструктора класса ссылку на Graph, а также присваивание атрибуту класса этой ссылки так, чтобы получился следующий код:
private BufferStrategy bufferStrategy;
private Graph graph;

public GameGraphics(JFrame frameMain, Graph g){
	graph = g;
		
	frameMain.setIgnoreRepaint(true);
И ,естественно, в цикл отрисовки добавляем использование graph, чтобы получился такой код:
g2 = (Graphics2D) bufferStrategy.getDrawGraphics();		        
graph.draw(g2);
Первая и последняя строчки кода не изменились, я добавил их, чтобы вам было проще сориентироваться.
Запустим нашу программу. Если вы видите черное окно с линией, кругом и квадратом - я вас поздравляю, мы заложили основы графики! Если же нет, значит что-то пошло не так и вам стоит свериться с кодом статьи. Исходный код статьи можно скачать из этого поста. В следующей статье мы погрузимся в архитектуру нашей будущей игры и настроим классы, с которыми будем работать в дальнейшем.

Скриншоты



Views: 3 448

Hate #1 - 8 лет назад 0
Голосов: +0 / -0
док, можешь немного пояснять комментами прямо в коде зачем мы это делаем и почему? а то как то не особо понятно когда обьясняется уже после того как все написано, лучше бы было если было типа
    frameMain.setIgnoreRepaint(true);
    frameMain.getContentPane().add(this);
    frameMain.setVisible(true); // тут мы задали такие то параметры, это нужно для того то... поэтому мы сделали так то.
Doc #2 - 8 лет назад 0
Голосов: +0 / -0
Hatsume_Hate, я рассчитывал что читатель немного уже прошарен и сам поймет что и к чему в коде, а если не поймет - добро пожаловать под кат.
Hate #3 - 8 лет назад 0
Голосов: +0 / -0
» док, ты наверное забыл упомянуть что нужно сделать так
threadMain = new Thread(){
		public void run(){
			Graphics2D g2;
			while(true){
				try{
					Thread.yield();
					g2 = (Graphics2D) bufferstrategy.getDrawGraphics();
					new Graph().draw(g2);
					// отрисовка	
					if (!bufferstrategy.contentsLost())
						bufferstrategy.show();
					} catch (Exception e){	
						}
			}
		}
			
	};
ибо иначе ничего не рисуется)
Doc #4 - 8 лет назад 0
Голосов: +0 / -0
Hatsume_Hate, упс, самое главное и забыл, спасибо =)
Только немного не так.
Hate #5 - 8 лет назад 0
Голосов: +0 / -0
ну да, я немного тупанул, надо туда graph.draw(g2); что бы он брал принимаемый аргумент метода а не создавал новый)
Doc #6 - 8 лет назад 0
Голосов: +0 / -0
Hatsume_Hate, разумеется, иначе будет постоянно утекать память. Да и не правильно это =)
Ладно, раз кто-то читает надо таки продолжать писать.