본문 바로가기

Java프로그래밍

Java로 구현한 초간단 교통 시뮬레이션 프로그램 Part 1

반응형



Java thread를 재미있게 학습하기 위해 GUI를 갖는 예제 프로그램을 만들어 보았습니다.

아주 초보적인 시뮬레이션 프로그램입니다. 자동차들이 움직이는 것을 구현해 보았습니다.

먼저 아래 동영상을 시청해 보세요.



화면에 3개의 사각형이 나타나, 움직이는 것을 볼 수 있습니다. 

너무 억지스럽긴 하지만 사각형들을 자동차라고 합시다.

빨간색, 노란색, 파란색의 자동차를 상상합시다.


파란색 자동차는 위에서 아래 방향으로 움직이고, 빨간색과 노란색은 좌에서 우로 이동합니다.

그리고 각 자동차는 움직이는 속도가 다릅니다. 

빨강 자동차는 속도가 가장 빠르니 스포츠카로 상상하고,

노랑 자동차가 제일 느리니, 유치원 통학버스쯤 되겠지요. 


위 프로그램은 Java언어로 구현하였고, thread를 사용하였습니다.

자세한 소스코드 설명은 아래 나옵니다.

2개의 클래스를 만들었는데, Road 클래스와 CarThread 클래스 입니다.


Road 클래스는 화면과 자동차들을 표시해주는 GUI 클래스입니다.


CarThread 클래스는 자동차의 움직임을 제어하는 클래스입니다. 

동영상 데모에서 3대의 자동차를 나타내기 위해, 3개의 CarThread 객체를 이용합니다.

특히, CarThread 클래스는 Thread를 상속받아 만들었습니다. 

각 자동차를 별도의 독립된 thread로 제어하기 위해서 입니다.


이제 Road 클래스부터 소스코드를 설명해 보겠습니다. 

우선 전체 소스코드는 아래와 아래와 같습니다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package univ.inu.embedded;

import java.awt.Color;

import javax.swing.*;

public class Road extends JFrame 
{

	private void launchCar(int x, int y, Color c, boolean horizontal, int interval)
	{
		JButton b = new JButton();
		b.setBackground(c);
		b.setSize(10, 10);
		b.setLocation(x, y);
		this.add(b);
		
		(new CarThread(b, horizontal, interval)).start();
	}
	
	public Road()
	{
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setTitle("Road Environment");
		this.setSize(600, 400);
		this.setLayout(null);
		
		launchCar(20, 100, Color.red, true, 10);
		launchCar(50, 200, Color.YELLOW, true, 60);
		launchCar(300, 20, Color.blue, false, 20);	
		
		this.setVisible(true);
	}
}


 라인 7

public class Road extends JFrame 

{

 GUI를 표시해야 하니 Swing의 JFrame을 상속받아 Road클래스를 만듭니다.


 라인 21~26

public Road()
	{
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setTitle("Road Environment");
		this.setSize(600, 400);
		this.setLayout(null);

화면에 창을 생성하기 전에 크기 등을 설정하는 코드입니다. 

라인 26에서 Layout Manager를 null로 설정합니다. Layout manager를 사용하지 않는다는 의미입니다. 이렇게 되면 우리가 원하는 위치에 자동차를 배치시킬 수 있습니다.  


 라인 28~32

		launchCar(20, 100, Color.red, true, 10);
		launchCar(50, 200, Color.YELLOW, true, 60);
		launchCar(300, 20, Color.blue, false, 20);	
		
	

 메소드 launchCar를 호출하여 자동차를 생성합니다. 자동차가 3대이니 3번 호출합니다. 

각 인수는 다음과 같습니다.

처음 2개의 숫자는 자동차가 생성되는 최초의 (x,y)좌표입니다. 

세번째 인수는 자동차의 색을 나타냅니다.

네번째 인수는 뭘까요? 빨강과 노랑 자동차는 true이고, 파랑 자동차만 false입니다. 힌트드립니다. 파랑 자동차만 위에서 아래로 움직였지요? true는 자동차가 좌에서 우로 움직임을 나타냅니다. false는 위에서 아래로.

마지막 인수는 또 뭘까요? 빨강 자동차의 숫자가 가장 작습니다. 이건 자동차가 움직이는 시간간격입니다. 간격이 작을수록 빨리 움직입니다. 



 라인 10 ~ 19

	private void launchCar(int x, int y, Color c, boolean horizontal, int interval)
	{
		JButton b = new JButton();
		b.setBackground(c);
		b.setSize(10, 10);
		b.setLocation(x, y);
		this.add(b);
		
		(new CarThread(b, horizontal, interval)).start();
	}


 메소드 launchCar를 살펴봅시다.


자동차를 나타내는 JButton객체를 생성하여 객체변수 b에 저장합니다.

b.setBackground( )로 색깔도 지정하구요,

자동차 크기는 10x10 픽셀로 b.setSize( )를 이용하여 설정합니다.

b.setLocation ( )으로 자동차의 초기 위치를 설정합니다.


this.add(b)는 화면에 버튼을 표시합니다.


마지막에는 이 버튼 (자동차)를 움직일 CarThread를 만들고, 실행시킵니다.

CarThread에 대해서는 아래에서 아주 자세히 설명합니다.



이제 CarThread 클래스 소스코드를 설명해 보겠습니다. 

우선 전체 소스코드는 아래와 아래와 같습니다.

다시 한 번 강조하지만 CarThread의 1개 객체는 자동차 1대를 의미합니다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package univ.inu.embedded;

import javax.swing.*;

public class CarThread extends Thread 
{
	JButton b = null;
	int x;
	int y;
	boolean horizontal = true;
	int interval;
	
	public CarThread(JButton _b, boolean _horizontal, int _interval)
	{
		horizontal = _horizontal;
		b = _b;
		interval = _interval;
	}
	
	private void moveLocation(int _x, int _y)
	{
		b.setLocation(_x,_y);
	}
	
	private void removeCar()
	{
		b.setVisible(false);
		
	}
	
	public void run()
	{
		x = b.getLocation().x;
		y = b.getLocation().y;
		
		while (x < 590 && y < 390)
		{
			if (horizontal == true)
			{	
				x = x+1;
			}
			else
			{
				y = y+1;
			}
			
			SwingUtilities.invokeLater(new Runnable()
			{
				public void run()
				{
					moveLocation(x, y);
				}
			});
			try {
				Thread.sleep(interval);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		SwingUtilities.invokeLater(new Runnable()
		{
			public void run()
			{
				removeCar();			
			}
		});
	}
}



 라인 5

public class CarThread extends Thread


 이 클래스는 Thread를 상속받아 만들어집니다.



 라인 7 ~ 11

	JButton b = null;
	int x;
	int y;
	boolean horizontal = true;
	int interval;


클래스의 변수들을 설명하겠습니다.

우선 자동차는 버튼으로 화면에 표시합니다. 이를 위해 JButton 형의 변수 b가 필요합니다.

x, y 변수는 당연히 자동차의 위치좌표를 나타내는 거구요.

boolean 변수 horizontal은 자동차가 움직이는 방향을 나타냅니다. Horizontal의 의미는 "수평"이니 이것이 true이면 좌에서 우로 움직이는 겁니다. false면 위에서 아래로.

int변수 interval은 자동차가 움직이는 시간간격을 나타냅니다. 값이 작을수록 빠르게 움직입니다.



 라인 13 ~ 18

	public CarThread(JButton _b, boolean _horizontal, int _interval)
	{
		horizontal = _horizontal;
		b = _b;
		interval = _interval;
	}


생성자입니다. 특별한 것은 없습니다.

이 객체가 제어를 담당할 버튼 _b를 인수로 받아, 이것을 내부변수 b에 저장합니다.

보통 메소드의 인수 앞에는 '_' (underscore라고 읽습니다.)를 붙이면, 객체 변수와 쉽게 구분이 가능합니다.



 라인 20 ~ 23

private void moveLocation(int _x, int _y)

	{
		b.setLocation(_x,_y);
	}


버튼의 위치를 바꾸는 메소드로, 자동차가 움직이는 것처럼 보이게 합니다.

인수 _x와 _y로 위치를 설정합니다.

이 메소드는 JButton의 setLocation 메소드를 호출합니다.



라인 25 ~ 29 

private void removeCar() { b.setVisible(false); }


자동차를 지우는 메소드입니다. 변수 b는 JButton객체이므로 setVisible(false)를 호출하여 자동차를 화면에서 지웁니다.   



 라인 31 ~ 34

	public void run()
	{
		x = b.getLocation().x;
		y = b.getLocation().y;


Thread가 처리해야 하는 일을 나타내는 run( ) 메소드입니다.

우선 자동차의 현재 좌표를 알아냅니다.



 라인 36 ~ 45

		while (x < 590 && y < 390)
		{
			if (horizontal == true)
			{	
				x = x+1;
			}
			else
			{
				y = y+1;
			}


 자동차가 화면 안의 적정한 영역에 있을 동안에만 thread가 동작해야 합니다.

그래서 x 좌표와 y 좌표의 최대 영역을 while 문으로 지정했습니다.

화면크기가 가로 600, 세로 400이기 때문에, 그것들보다는 약간 작게 지정했습니다.


자동차가 가로로 움직이는 경우, 아닌 경우로 나누어 해당 좌표를 1 픽셀씩 갱신합니다.



 라인 47 ~ 58

			SwingUtilities.invokeLater(new Runnable()
			{
				public void run()
				{
					moveLocation(x, y);
				}
			});
			try {
				Thread.sleep(interval);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}


 이 부분은 상당히 중요합니다.


순서로 보자면 자동차를 새로운 좌표로 옮겨야 합니다. 

그런데, Java에서는 JButton같은 GUI요소들을 갱신할 때는 일반 thread가 할 수 없습니다.

대신 event thread라는 특별한 thread에게 부탁해야 합니다.

다행인 것은 event thread는 이미 생성되어 있기 때문에 따로 만들거나 할 필요는 없습니다.


요약하자면, CarThread가 JButton의 새로운 위치를 계산할 순 있지만, 그 위치에 버튼을 옮겨 그리는 것은

CarThread에게는 금지된 일입니다. 대신 event thread에게 부탁해야 됩니다.


어떻게 부탁하냐면,

라인 47 ~ 53 처럼 하면 됩니다. 길고 복잡해 보인다구요? 걱정하지 마세요.

핵심은 라인 51. 거기에 내가 부탁하고자 하는 일을 써 넣으면 됩니다.

여기서 부탁한 일은 메소드 moveLocation ( ) 입니다. 위에서 설명했지요. 버튼을 새로운 위치에 그리는 일이라구요.


라인 54 ~ 58은 잠시 쉬어가는 시간을 나타냅니다. 그래야 자동차가 움직이는 것처럼 보이니까요.

이 간격을 조절해서 자동차가 움직이는 속도를 다양하게 할 수 있습니다.

 



 라인 60 ~ 66

		SwingUtilities.invokeLater(new Runnable()
		{
			public void run()
			{
				removeCar();			
			}
		});


 이 부분은while 문 종료 후에 실행됩니다. 즉 자동차가 화면 영역을 벗어날 때 입니다.

메소드 removeCar ( )를 호출하는데, event thread에게 부탁해서 처리합니다.

JButton을 화면에서 사라지게 하는 것도 GUI에 관련된 일이기 때문입니다.











반응형