티스토리 뷰

이 자료들은 팁스소프트에서 제공하는 [ 알짜배기 ] 프로그램을 이용하면 더 편리하게 볼수 있습니다.
* 알짜배기 프로그램 받기 - http://www.tipssoft.com/bulletin/tb.php/QnA/8406
* 안드로이드 강좌 목록 - http://www.tipssoft.com/bulletin/tb.php/old_bbs/501
이번 자료에서는 사용자 정의 뷰에 무작위로 배치된 원을 원에 쓰여진 숫자 순서대로 터치하여 제거하는
게임 예제를 다루도록 하겠습니다.
이 예제를 정확하고 쉽게 이해하기 위해서는 사용자 정의 뷰를 생성하고 배치하는 방법과 그림그리기의
기초적인 지식 그리고 터치 이벤트 핸들러에 대한 지식이 필요합니다. 또한 게임에 제한시간의 개념을
사용하기 때문에 타이머에 대한 공부도 해두시는게 좋습니다. 아래의 자료는 앞서 언급한 내용들이
정리된 자료들입니다.
사용자정의 뷰 생성 및 배치1 - http://www.tipssoft.com/bulletin/tb.php/FAQ/964
그림그리기의 기초와 구조 - http://www.tipssoft.com/bulletin/tb.php/FAQ/971
사용자조작(터치) 처리하기 - http://www.tipssoft.com/bulletin/tb.php/FAQ/973
TimerTask 클래스를 사용한 스톱워치 - http://www.tipssoft.com/bulletin/tb.php/FAQ/940
1. 터치 게임의 구성
이 예제에는 텍스트뷰와 버튼과 사용자 정의 뷰가 하나씩 배치되어 있습니다. 이 컨트롤들의 역할은
다음과 같습니다.
- 텍스트뷰
게임에서 사용하는 타이머가 현재 얼마의 시간을 남겨두고 있는지 표시합니다.
- 버튼
게임의 시작과 종료를 사용자로부터 입력받습니다. 아래의 좌측 그림처럼 Start!! 버튼을 클릭하면
오른쪽 그림처럼 텍스트뷰에 남은 게임 시간이 표시되고, 사용자 정의 뷰에는 게임을 위한 그리기
작업이 수행됩니다.
- 사용자 정의 뷰
실질적으로 게임을 할 수 있는 영역입니다. 게임을 시작하면 아래의 우측 그림처럼 뷰에 숫자가
쓰인 8개의 원이 그려지고 숫자가 쓰여진 순서대로 원을 터치하면 해당 원은 제거됩니다.
모든 원이 제거되면 게임에서 승리하게 됩니다.
게임에 이기는 방법은 제한 시간 내에 사용자 정의 뷰에 그려진 원을 순서대로 모두 제거하는 것이며
제한 시간 내에 원을 모두 제거하지 못하거나 순서에 맞지 않는 원을 터치하면 게임에서 지게 됩니다.
게임의 승패가 결정되면 아래와 같은 메세지가 사용자 정의 뷰에 출력됩니다.
2. 터치 게임 구현하기
이 예제는 액티비티를 정의하는 RandomCircleActivity 클래스와 게임을 진행하는 사용자 정의 뷰
클래스인 DrawCircleView 그리고 타이머의 테스크를 정의하는 UpdateTimeTask 클래스로 나누어
구성되어 있습니다. 이 강좌에서는 각 클래스 별로 중요한 부분만 설명하므로 전체 코드를 보시려면
첨부 파일을 참고하시면 됩니다.
2.1 RandomCircleActivity
액티비티에서는 전체적인 게임의 진행을 관리합니다. 그래서 사용자 정의 뷰가 출력하는 원의
좌표 정보를 구성하여 뷰로 전달하며 게임에서 사용하는 타이머도 액티비티가 관리합니다.
// 원이 출력될 위치의 인덱스를 랜덤으로 추출하는 메소드
// 뷰의 영역을 원 크기로 나누어 배치 가능한 공간들 중에 원이 배치될 공간을 랜덤으로 선택한다.
public int[] makeRandomIndex(int parm_index_count)
{
// 사용할 원 개수만큼 정수 배열을 선언한다.
int[] index_list = new int[GAME_DATA_COUNT];
// 랜덤 메소드를 사용할 수 있는 Random 객체를 생성한다.
Random r = new Random(System.currentTimeMillis());

int i, j;
for(i = 0; i < GAME_DATA_COUNT; i++) {
// 랜덤한 수를 발생시켜 index_list 배열에 저장한다.
index_list[i] = r.nextInt(parm_index_count);

for(j = 0; j < i; j++) {
// 이전 배열 인덱스에 현재 인덱스 값과 중복되는 값이 있는 경우
if(index_list[i] == index_list[j]) {
// 현재 인덱스 값을 다시 저장하기위해 i 값을 감소시킨다.
i--;
break;
}
}
}
return index_list;
}

// 게임 시작에 필요한 작업을 하는 메소드
public void startGame()
{
// 만약 게임중인 경우 메소드를 빠져나간다.
if(m_my_view.getGameState() == 1) return ;
// 텍스트뷰에 초기화 문자열을 설정한다.
m_text_view.setText("10 : 00");
// 버튼에 사용할 캡션명을 바꾼다.
m_button.setText("Stop!!");
// 현재 뷰에서 출력 가능한 가로, 세로의 원 개수를 구한다.
int x_count = m_my_view.getWidth()/(CIRCLE_RADIUS*2 + 2);
int y_count = m_my_view.getHeight()/(CIRCLE_RADIUS*2 + 2);

// 출력 가능한 원의 개수 중에서 랜덤으로 8개를 추출한다.
int[] index_list = makeRandomIndex(x_count*y_count);
// 원의 x, y 좌표가 저장될 배열을 생성한다.
int[] x_pos = new int[GAME_DATA_COUNT];
int[] y_pos = new int[GAME_DATA_COUNT];
// 모든 원이 출력될 위치를 x 좌표와 y 좌표로 구성한다.
// 저장되는 좌표는 각 원의 중점이다.

for(int i = 0; i < GAME_DATA_COUNT; i++) {
// 원의 중점 = 여백 + 원의 좌측 좌표 + 반지름
x_pos[i] = 2 + (index_list[i]%x_count) * (CIRCLE_RADIUS*2 + 2) + CIRCLE_RADIUS;
// 원의 중점 = 여백 + 원의 상측 좌표 + 반지름
y_pos[i] = 2 + (index_list[i]/x_count) * (CIRCLE_RADIUS*2 + 2) + CIRCLE_RADIUS;
}
// 뷰에 좌표 데이터를 전달한다.
m_my_view.setData(x_pos, y_pos);
// 게임을 시작시킨다.
m_my_view.startGame();
// 타이머를 수행한다.
startTimer();
}
// 게임 종료에 필요한 작업을 하는 메소드
public void stopGame()
{
// 타이머를 중지한다.
stopTimer();
// 뷰가 게임을 진행하고 있는 경우 진 게임으로 설정하고 끝낸다.
if(m_my_view.getGameState() == 1) m_my_view.FinishGame((byte)-1);
// 버튼에 사용할 캡션명을 바꾼다.
m_button.setText("Start!!");
}
2.2 DrawCircleView
사용자 정의 뷰는 실질적인 게임을 수행합니다. 원 좌표 정보를 이용하여 그림을 그리고, 터치
이벤트를 사용하여 원의 터치 여부를 판단하며 게임의 승패 여부도 판단합니다.
protected void onDraw(Canvas canvas)
{
canvas.drawColor(Color.WHITE);
// 게임이 진행중인 경우
if(m_game_state == 1) {
String str;

// 모든 원을 체크하여 제거된 원이 아니면 그리기를 수행한다.
for(int i = 0; i < RandomCircleActivity.GAME_DATA_COUNT; i++){
// m_circle_x_list[i] 가 -1 이면 제거된 원이므로 -1 이상인 값을 찾는다.
if(m_circle_x_list[i] > -1) {
// 지정된 좌표에 원을 그린다.
canvas.drawCircle(m_circle_x_list[i], m_circle_y_list[i],
RandomCircleActivity.CIRCLE_RADIUS, m_circle_paint);
// 각 원의 index 값을 문자로 변환하여 원 위에 출력한다.
str = Integer.toString(i + 1);
canvas.drawText(str, m_circle_x_list[i], m_circle_y_list[i], m_text_paint);
}
}
} else if(m_game_state == -1) { // 진 게임인 경우 아래와 같은 문자열을 출력한다.
canvas.drawText("Uu~~~ You are Loserrrrrr!!!!!!!!!!!!!!", 10, 30, m_circle_paint);
} else if(m_game_state == 0) { // 이긴 게임인 경우 아래와 같은 문자열을 출력한다.
canvas.drawText("Ye~~ Winner !!!!", 10, 30, m_circle_paint);
}
}

// 터치 이벤트를 처리하는 콜백 메소드
public boolean onTouchEvent(MotionEvent event) {
// 상위 클래스인 View 클래스에 발생한 이벤트를 전달한다.
super.onTouchEvent(event);

// 게임이 진행중이 아닌 경우 메소드를 빠져나간다.
if(m_game_state != 1) return false;
// 어떤 이벤트가 발생하였는지에 따라 처리가 달라진다.
switch (event.getAction()) {
// DOWN 가 발생한 경우
case MotionEvent.ACTION_DOWN:
// 터치가 발생한 X, Y 의 각 좌표를 얻어서 몇번째 원이 클릭되었는지 체크한다.
m_touch_index = checkTouchCircle((int)event.getX(), (int)event.getY());
break;

case MotionEvent.ACTION_UP: // UP 이 발생한 경우
// DOWN 에서 유효한 터치가 행해졌던 경우
if(m_touch_index != -1){

// DOWN 을 했던 원과 UP 을 했던 원이 동일한 원이라면...
if(m_touch_index == checkTouchCircle((int)event.getX(), (int)event.getY())){
// 게임 종료여부를 체크한다.
checkFinishGame();
// 뷰를 갱신한다.
invalidate();
}
}
m_touch_index = -1;

// 액티비티에서 UP 이벤트를 처리할 수 있도록 false 를 반환한다.
return false;
}
// true 를 반환하여 더이상의 이벤트 처리가 이루어지지 않도록 이벤트 처리를 완료한다.
return true;
}
// 게임이 종료되는 상황인지 판단하는 메소드
public void checkFinishGame()
{
// 순서에 맞게 터치한건지 확인
for(int i = 0; i < RandomCircleActivity.GAME_DATA_COUNT; i++) {
// 제거되지 않은 원 검색
if(m_circle_x_list[i] != -1) {
// 순서에 맞게 터치한 경우
if(i == m_touch_index) {
// 마지막 원 선택시 게임 종료
if(m_touch_index + 1 == RandomCircleActivity.GAME_DATA_COUNT) FinishGame((byte)0);
else m_circle_x_list[m_touch_index] = -1; // 마지막 원이 아니면 해당 원 제거
} else FinishGame((byte)-1); // 순서에 맞지 않는 원을 터치한 경우 게임 종료
break;
}
}
}
// 터치가 발생한 좌표가 원 내부인지 체크하는 메소드
public int checkTouchCircle(int parm_x, int parm_y)
{
for(int i = 0; i < RandomCircleActivity.GAME_DATA_COUNT; i++){
// 제거된 원이 아닌 경우
if(m_circle_x_list[i] != -1) {

// 원의 중점에서 선택 좌표를 뺀 나머지가 반지름보다 작으면 원 내부를 선택한 것이다.
if(Math.abs(m_circle_x_list[i] - parm_x) < RandomCircleActivity.CIRCLE_RADIUS){
if(Math.abs(m_circle_y_list[i] - parm_y) < RandomCircleActivity.CIRCLE_RADIUS) {
return i;
}
}
}
}
return -1;
}
2.3 UpdateTimeTask
이 게임은 한 세트당 10초의 제한 시간이 있기때문에 타이머를 사용합니다. 이 클래스에서는
210 ms 마다 남은 시간을 계산하여 텍스트뷰에 출력하는데 10 초부터 0 초까지 감소되는 형태를
가집니다.
// 객체 생성자
public UpdateTimeTask(RandomCircleActivity parm_parent, TextView parm_view)
{
super();
// 객체가 생성된 시간 정보(현재시각 + 10초)를 저장한다.
m_start_time = System.currentTimeMillis() + 10000;

// 액티비티 객체를 저장한다.
m_parent = parm_parent;
// 텍스트뷰 객체를 저장한다.
m_text_view = parm_view;
}
// 타이머 쓰레드가 수행할 작업을 정의하는 메소드
public void run()
{
// 현재 시간을 밀리초 단위로 얻어온다.
m_current_time = System.currentTimeMillis();

// 현재 시간에서 시작 시간을 빼서 경과시간을 얻는다.
long millis = m_start_time - m_current_time;
// 밀리초에 1000을 나누어 초단위로 변경한다.
int seconds = (int) (millis / 1000);

// 0초 이하인 경우 게임을 종료한다.
if(millis <= 0) m_text_view.post(m_game_over_run);
else {
// 밀리초 단위의 시간정보를 10으로 나눈 몫에 100으로 나머지 연산을 하여
// 100의 자리와 10의 자리만 출력될 수 있도록 한다.
millis = (millis / 10) % 100;

// 출력할 문자열을 구성한다.
m_display_string = String.format("%02d : %02d", seconds, millis);
// 해당 문자열을 출력하도록 메인 쓰레드에 전달한다.
m_text_view.post(m_display_run);
}
}
댓글