출처 : http://artbrain.co.kr/
- 1-
이번에는 간단하게 안드로이드 게임 예제를 하나 만들어 보겠습니다.
재미있는 강좌가 되길 바랬는데, 샘플을 잘못 설정한것 같기도 하고 그렇네요 ^^;
안드로이드 개발 환경에 대해서는 쉽게 찾으실 수 있으리라 생각합니다.
먼저, Eclipse 에서 File/New/OTher... 를 들어가서, Android Project를 선택합니다.
<그림 1>
그리고 기본 프로젝트 설정을 합니다.
셈플 소스를 복사해서 작업하기 좋게.. 이름을 그대로 맞춰 주기 바랍니다. 그리고 다음화면에서 아무 설정없이
Finish 합니다.
Project name : Android Game
Application name : Game
Package name : com.game
Create Activity : GameMain
<그림 2>
그리고 프로젝트 파일을 관리하는 창을 파일 뷰어로 설정합니다.
보통 현대적인 개발툴들은 클래스 개념으로 프로젝트를 관리할 수 있고, 파일 개념으로 관리할 수 있는데, 처음
대할때는 파일형식이 더 직관적입니다.
이클립스 / 메뉴 / Window / Show View / Other... 선택후, General / Navigator 을 선택합니다.
<그림 3>
실행을 먼저 해보도록 하죠.
실행하는 방법은, Navigator 창에서 Android Game 프로젝트를 선택후, 오른쪽 마우스를 클랙해서
Run As / Android Application 을 선택합니다.
<그림 4>
그런데, 실행하면 오류가 발생 합니다. Yes 를 선택합니다. 그러면 Android SDK and AVD Manager 윈도우가
나타나게 됩니다.
<그림 5>
실행은 안드로이드 OS를 가상으로 띄워서 그곳에서 하게 되는데, 안드로이드 버전과 화면 해상도라든지.. 그런
설정이 되지 않아, 어떤 가상 OS를 띄워야 할지 몰라서 문제가 생기게 됩니다.
우리가 <그림 2> 에서 프로젝트 설정할때, 빌드 타겟을 Android 2.2 버전을 설정 했습니다.
그러므로, Android SDK and AVD Manager 윈도우의 Virtual Device 메뉴에서 New로 가상 안드로이드 OS를
설정 합니다.
Name : Android2.2
Target : Android 2.2 - API Level 8
그러면 화면 리스트에 지금 설정한 Android2.2가 추가 되는데, 선택후 Start 버튼을 눌러 실행합니다.
Launch Options 창이 나오면 아무 설정없이 Launch 버튼을 클릭합니다.
정확하게 따라 했으면 위 처럼 보이게 될 것입니다.
- 2 -
먼저, 간단하게 메인 페이지를 만들겠습니다.
"Start Game" 버튼만 나오는 간단한 페이지죠
아래처럼 Nevigator 에서
Android Game / res / layout / main.xml
마우스로 선택한 뒤 Ctrl C , Ctrl + v 해서 그 자리에 부쳐 넣기를 시도합니다.
그러면 파일 명이 충돌한다고 메시지가 나옵니다.
intro.xml
로 이름을 설정하고 확인합니다.
그러면 레이아웃에 intro.xml 파일이 생성 됩니다. 이 파일을 더블클릭 해서 Linearlayout -> AbsoluteLayout로
다음처럼 변경합니다. 그리고 TextLayout는 삭제하구요.. 다음처럼 모양이 나오면 됩니다.
<?xml version="1.0" encoding="utf-8"?>
<AbsoluteLayout xmlns:android="http:http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
</AbsoluteLayout>
<AbsoluteLayout xmlns:android="http:http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
</AbsoluteLayout>
참고로, 개발 능력이 늘어가면 절대 AbsoluteLayout를 사용하면 안됩니다.
절대 좌표계를 사용하는 레이아웃(바탕화면)을 사용 하겠단 겁니다. 디자인 할때는 편리하지만... 안드로이드 OS는
여러 타입의 폰에 탑재되기 때문에, 화면 크기, 해상도.. 이런게 차이가 많이 납니다. - 우리나라면 겔럭시 S
사이즈로 개발하면 되겠지만요..
여튼.. 버튼을 하나 놔 보겠습니다.
이클립스에.. 메인 창에 보면 Layout라는 탭을 클릭해서 좌측 View 항목의 Button 선택, 드래그 해서 중간쯤에
놓습니다.
그리고 아래 Properties 탭에서, 버튼 이름을 정해 줍니다.
Start Game
로 설정하죠..
그러면.. 프로그램이 시작할때, 우리가 생성한 intro.xml 이 처음에 보여져야 겠죠?
Navigator 에서
Android Game / src / com / game / GameMain.java 을 다음처럼 수정합니다.
package com.game;
import android.app.Activity;
import android.os.Bundle;
public class GameMain extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.intro);
}
}
import android.app.Activity;
import android.os.Bundle;
public class GameMain extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.intro);
}
}
휴.. 잘 따라하셨습니다.
저번 강좌에 이어 실행해 보겠습니다.
Android Game 폴더를 선택하고, 우측 마우스 클릭후, Run As / Android Application 을 눌러줍니다.
다소 지루한 기다림 다음에.. 안드로이드가 완전히 로딩되면, 폰 화면에 있는 menu 버튼을 누릅니다.
잘 나오나요?
ㅎㅎ 수고하셨습니다
- 3 -
오늘은.. 배경, 몹, 타워, 미사일을 그려 보겠습니다.
먼저 첨부파일을 다운 받으시면.. 발로 오려낸 디펜스 게임 이미지가 있습니다.
중간에 하면 헷갈리니 일단 몽땅 프로젝트에 추가합니다.
파일을 드레그로 전부 선택해서, Android Game / res / drawable-hdpi 에 끌어 놓습니다.
그러면 파일 구성이 다음과 같이 됩니다.
그리고 게임 레이아웃(배경화면) 을 변경합니다. main.xml 을 배경화면으로 사용할 것이므로
Android Game / res / layout / main.xml 을 선택해서 LinearLayout를 FrameLayout 으로 수정합니다.
조금이라도 효율을 높이기 위해서입니다.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http:http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
</FrameLayout>
<FrameLayout xmlns:android="http:http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
</FrameLayout>
그러면 일단, "Start Game" 를 누르면 지금 작성한 main.xml 레이아웃(배경화면)으로 이동되게 하겠습니다.
소스는 Android Game / src / com / game / GameMain.java 입니다. 더블클릭해서 아래와 같이 되게 편집하세요
public class GameMain extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.intro);
Button btnStart = (Button)findViewById(R.id.Button01);
btnStart.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
setContentView(R.layout.main);
}
});
}
}
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.intro);
Button btnStart = (Button)findViewById(R.id.Button01);
btnStart.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
setContentView(R.layout.main);
}
});
}
}
사실 가운데 빨간색 부분만 추가했습니다. 그러면 소스에 밑줄이 생기면서 오류가 생기게 됩니다.
해당 클래스가 어느 네임 스페이스인지 모르기 때문에 생기는 것인데.. CTRL + SHIFT + O 를 누르면 자동으로
없는 네임스페이스는 소스에 추가됩니다.
한번 실행을 해보면, 버튼을 누르면 아무것도 없는 빈 화면으로 이동합니다.
페이지가 이동될때, 게임 화면을 하나 올려놓고, 자동으로 로딩되게 해 보겠습니다.
main.xml 에 추가되는 엘리먼트... 말이 어려운데.. 구성요소 들은 해당 화면을 로딩할때 모두 new로 클래스 생성
하게 됩니다.. 물론 제약조건은 있습니다. 화면에서 보여질 수 있는 특정 클래스를 상속받아야 하는것이죠.
GameRun 이라는 클래스를 만들어서 자동으로 로딩되게 소스를 수정하겠습니다.
Android Game / gen / com / game 에서 마우스 우측클릭후
New / Class 를 선택합니다.
그럼 GameRun 이라는 클래스를 아래와 같이 만들어 봅시다.
Name : GameRun
Superclass : android.view.SurfaceView
Interface : android.view.SurfaceHolder.Callback
좀전에 생성한 파일을 열어보면 GameRun에 빨간줄이 있다.
생성자를 추가하지 않아서인데, 마우스를 잠시 두면, 생성자를 선택할 수 있습니다. 두번째 것을 선택합니다.
자 그러면, XML을 수정합니다. main.xml 이 로딩될때, 지금 준비한 클래스도 함께 나타나서, 그 위에 덮어지게
됩니다. GameRun 을 new를 한것과 같은 맥락입니다.
Android Game / res / layout / main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http:http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<com.game.GameRun
android:id="@+id/gamerun"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</FrameLayout>
<FrameLayout xmlns:android="http:http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<com.game.GameRun
android:id="@+id/gamerun"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</FrameLayout>
자.. 그럼 게임 이미지를 올려 보도록 하겠습니다.
그런데, 몹이라든지, 총알, 터렛 같은것들은 정지 이미지로 있지 않고, 시간에 따라 변화하며, 사용자의
행위에 반응해야 합니다. 한마디로 Thread에서 이미지를 뿌려 주도록 하겠습니다.
GameRun.java 전체 소스입니다.
public class GameRun extends SurfaceView implements Callback {
private GameThread m_GameThread;
class GameThread extends Thread {
private int m_nDisplayWidth; // 화면 가로 크기
private int m_nDisplayHeight; // 화면 세로 크기
private Bitmap m_bmpBg; // 배경
private Bitmap m_bmpMop; // 상대방 유닛
private Bitmap m_bmpMissile; // 미사일
private Bitmap m_bmpTower; // 수비건물
private SurfaceHolder m_SurfaceHolder;
public boolean m_bStop;
public GameThread (SurfaceHolder surfaceholder, Context context){
m_SurfaceHolder = surfaceholder;
Resources res = context.getResources();
// png 그림 이미지를 로딩
m_bmpBg = BitmapFactory.decodeResource(res,R.drawable.bg);
m_bmpMop = BitmapFactory.decodeResource(res,R.drawable.mop);
m_bmpMissile = BitmapFactory.decodeResource(res,R.drawable.missile);
m_bmpTower = BitmapFactory.decodeResource(res,R.drawable.tower);
// 화면의 가로, 세로 사이즈를 구함.
Display display =
((WindowManager)context.getSystemService(context.WINDOW_SERVICE)).getDefaultDisplay();
m_nDisplayWidth = display.getWidth();
m_nDisplayHeight = display.getHeight();
// 쓰레드가 작동하게 설정
m_bStop = false;
}
public void run(){
while(!m_bStop){
Canvas canvas = null;
try {
canvas = m_SurfaceHolder.lockCanvas(null);
synchronized(m_SurfaceHolder){
Rect rcSrc = new Rect();
Rect rcDest = new Rect();
rcSrc.set(0,0,470,498);
rcDest.set(0,0,m_nDisplayHeight,m_nDisplayHeight);
// 배경 (화면 가득 채우게 크기조절)
canvas.drawBitmap(m_bmpBg , rcSrc, rcDest, null);
// 몹,미사일,타워 이미지
canvas.drawBitmap(m_bmpMop , 50, 50, null);
canvas.drawBitmap(m_bmpMissile , 80, 80, null);
canvas.drawBitmap(m_bmpTower , 100, 100, null);
sleep(100);
}
}
catch (InterruptedException e){
e.printStackTrace();
}
finally{
if(canvas != null){
m_SurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
}
public GameRun(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
SurfaceHolder holder = getHolder();
holder.addCallback(this);
m_GameThread = new GameThread(holder, context);
}
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
// TODO Auto-generated method stub
}
public void surfaceCreated(SurfaceHolder arg0) {
// TODO Auto-generated method stub
m_GameThread.start();
}
public void surfaceDestroyed(SurfaceHolder arg0) {
// TODO Auto-generated method stub
}
}
private GameThread m_GameThread;
class GameThread extends Thread {
private int m_nDisplayWidth; // 화면 가로 크기
private int m_nDisplayHeight; // 화면 세로 크기
private Bitmap m_bmpBg; // 배경
private Bitmap m_bmpMop; // 상대방 유닛
private Bitmap m_bmpMissile; // 미사일
private Bitmap m_bmpTower; // 수비건물
private SurfaceHolder m_SurfaceHolder;
public boolean m_bStop;
public GameThread (SurfaceHolder surfaceholder, Context context){
m_SurfaceHolder = surfaceholder;
Resources res = context.getResources();
// png 그림 이미지를 로딩
m_bmpBg = BitmapFactory.decodeResource(res,R.drawable.bg);
m_bmpMop = BitmapFactory.decodeResource(res,R.drawable.mop);
m_bmpMissile = BitmapFactory.decodeResource(res,R.drawable.missile);
m_bmpTower = BitmapFactory.decodeResource(res,R.drawable.tower);
// 화면의 가로, 세로 사이즈를 구함.
Display display =
((WindowManager)context.getSystemService(context.WINDOW_SERVICE)).getDefaultDisplay();
m_nDisplayWidth = display.getWidth();
m_nDisplayHeight = display.getHeight();
// 쓰레드가 작동하게 설정
m_bStop = false;
}
public void run(){
while(!m_bStop){
Canvas canvas = null;
try {
canvas = m_SurfaceHolder.lockCanvas(null);
synchronized(m_SurfaceHolder){
Rect rcSrc = new Rect();
Rect rcDest = new Rect();
rcSrc.set(0,0,470,498);
rcDest.set(0,0,m_nDisplayHeight,m_nDisplayHeight);
// 배경 (화면 가득 채우게 크기조절)
canvas.drawBitmap(m_bmpBg , rcSrc, rcDest, null);
// 몹,미사일,타워 이미지
canvas.drawBitmap(m_bmpMop , 50, 50, null);
canvas.drawBitmap(m_bmpMissile , 80, 80, null);
canvas.drawBitmap(m_bmpTower , 100, 100, null);
sleep(100);
}
}
catch (InterruptedException e){
e.printStackTrace();
}
finally{
if(canvas != null){
m_SurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
}
public GameRun(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
SurfaceHolder holder = getHolder();
holder.addCallback(this);
m_GameThread = new GameThread(holder, context);
}
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
// TODO Auto-generated method stub
}
public void surfaceCreated(SurfaceHolder arg0) {
// TODO Auto-generated method stub
m_GameThread.start();
}
public void surfaceDestroyed(SurfaceHolder arg0) {
// TODO Auto-generated method stub
}
}
실행해 보세요.. 아.. 그리고, 오류가 나면 import 가 되지 않아서 입니다. ctrl + shift + o 한번 누르면 됩니다.
잘 되나요? ^^
다음에는 움직이게 해보겠습니다.
수고하셨습니다
- 4 -
이번에는 몹을 길따라 움직이게 해 보겠습니다.
먼저 아래 소스를 복사해서 GameRun.java 에 부쳐 넣습니다.
import 오류가 나면 역시 ctrl + shift + o 로 자동 import 합니다.
package com.game;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.Display;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;
import android.view.SurfaceHolder.Callback;
public class GameRun extends SurfaceView implements Callback {
private GameThread m_GameThread;
private CMopMgr m_cMopMgr = new CMopMgr();
class CMop{
public int m_nX;
public int m_nY;
public long m_lBeforTime; // 이전에 움직였던 밀리세컨드
public long m_nSleep; // 이전과 다음이동사이의 밀리세컨드
public int m_nSpeed; // 한 이동 단위에 움직일 pixel 숫자
public int m_nMoveArea; // 이동할 구역
public int m_nMopHealth; // 몹체력
public boolean m_bUsed; // 생성되었나
public boolean m_bDie; // 몹이 죽었나 - 재생되지 않음
public int m_nDirection; // 이전 몹의 방향
// x, y, 방향(동쪽이 1이며 시계방향으로 증가함)
public int m_aMovePos[][] =
{
{ 93, 0, 2}, // 시작점임 남쪽 으로
{ 93, 19, 3}, // 서쪽 으로
{ 23, 19, 2}, // 남쪽 으로
{ 23, 143, 1}, // 동쪽 으로
{ 94, 143, 4}, // 북쪽 으로
{ 94, 95, 1}, // 동쪽 으로
{206, 95, 2}, // 남쪽 으로
{206, 215, 3}, // 서쪽 으로
{ 24, 215, 2}, // 남쪽 으로
{ 24, 286, 1}, // 동쪽 으로
{282, 286, 4}, // 북쪽 으로
{282, 19, 3}, // 서쪽 으로
{175, 19, 4}, // 위쪽 으로
{175, 0, 0} // 종착지
};
CMop(){
m_nX = 93;
m_nY = 0;
m_lBeforTime = System.currentTimeMillis(); // 현재 시간을 설정한다
m_nSleep = 10; // 10 밀리세컨드마다 움직인다.
m_nSpeed = 2; // 2pixel 씩 움직인다
m_nMoveArea = 1; // 처음 시작은 위 중간즈음이다
m_bUsed = false;// 생성되지 않은 상태임
m_nMopHealth = 100; // 몹체력은 100으로 초기화
m_bDie = false;// 몹은 살아있는걸로 초기화됨
m_nDirection = -1;
}
// 다음 포지션으로 이동
public void MovePosition(){
// 이전 움직인 시간 이후 m_nSleep 이상 흘렀다면 움직인다.
if( (System.currentTimeMillis() - m_lBeforTime) > m_nSleep ){
m_lBeforTime = System.currentTimeMillis();
}
else{
return;
}
// 방향 전환 인가
int nDir = m_aMovePos[m_nMoveArea-1][2];
if(m_nDirection != nDir){
m_nDirection = nDir;
}
// 마지막 위치면 몹에대해 특별처리함
if(m_nMoveArea > 13){
m_nDirection = -1;
m_nX = 93;
m_nY = 0;
m_cMopMgr.m_nOverCount += 1; // 오버 카운트를 1 증가 시킨다
m_nMoveArea = 1;
return;
}
// 방향에 따라 X,Y 좌표를 각각 이동 시킨다
if(nDir == 1){
m_nX += m_nSpeed;
if(m_aMovePos[m_nMoveArea][0] < m_nX) m_nMoveArea++;
}
else if(nDir == 2){
m_nY += m_nSpeed;
if(m_aMovePos[m_nMoveArea][1] < m_nY) m_nMoveArea++;
}
else if(nDir == 3){
m_nX -= m_nSpeed;
if(m_aMovePos[m_nMoveArea][0] > m_nX) m_nMoveArea++;
}
else if(nDir == 4){
m_nY -= m_nSpeed;
if(m_aMovePos[m_nMoveArea][1] > m_nY) m_nMoveArea++;
}
}
}
class CMopMgr{
public static final int m_nMopCnt = 20; // 몹 개수
public CMop mop[] = new CMop[m_nMopCnt]; // 몹 Count
public int m_nUsedMopCnt = 0; // 생성되었던 몹 Count
public int m_nDieMopCnt = 0; // 죽은 몹 count
public long m_lRegen = 1000; // 다음몹 생성되는 시간(1초)
public int m_nOverCount = 0; // 몹이 한바퀴 도달하면 1 증가
public long m_lBeforRegen = System.currentTimeMillis();
CMopMgr(){
for(int n = 0 ; n < m_nMopCnt ; n++){
mop[n] = new CMop();
}
}
// 신규몹 추가 - m_bStart 플래그만 true 로 설정
public void AddMop(){
// 리젠 시간에 도달하면 몹 추가를 시도함
if( (System.currentTimeMillis() - m_lBeforRegen) > m_lRegen){
m_lBeforRegen = System.currentTimeMillis();
}
else return;
if(m_nUsedMopCnt >= (m_nMopCnt-1)) return;
mop[m_nUsedMopCnt].m_bUsed = true;
m_nUsedMopCnt ++;
}
// 이동 가능한 모든 몹들을 이동시킨다
public void MoveMop(){
for(int n = 0 ; n < m_nMopCnt ; n++){
if(mop[n].m_bUsed == true){
mop[n].MovePosition();
}
}
}
}
class GameThread extends Thread {
private int m_nDisplayWidth; // 화면 가로 크기
private int m_nDisplayHeight; // 화면 세로 크기
private Bitmap m_bmpBg; // 배경
private Bitmap m_bmpMop; // 상대방 유닛
private Bitmap m_bmpMissile; // 미사일
private Bitmap m_bmpTower; // 수비건물
private SurfaceHolder m_SurfaceHolder;
public boolean m_bStop;
public GameThread (SurfaceHolder surfaceholder, Context context){
m_SurfaceHolder = surfaceholder;
Resources res = context.getResources();
// png 그림 이미지를 로딩
m_bmpBg = BitmapFactory.decodeResource(res,R.drawable.bg);
m_bmpMop = BitmapFactory.decodeResource(res,R.drawable.mop);
m_bmpMissile = BitmapFactory.decodeResource(res,R.drawable.missile);
m_bmpTower = BitmapFactory.decodeResource(res,R.drawable.tower);
// 화면의 가로, 세로 사이즈를 구함.
Display display =
((WindowManager)context.getSystemService(context.WINDOW_SERVICE)).getDefaultDisplay();
m_nDisplayWidth = display.getWidth();
m_nDisplayHeight = display.getHeight();
// 쓰레드가 작동하게 설정
m_bStop = false;
}
public void run(){
while(!m_bStop){
Canvas canvas = null;
try {
canvas = m_SurfaceHolder.lockCanvas(null);
synchronized(m_SurfaceHolder){
Rect rcSrc = new Rect();
Rect rcDest = new Rect();
rcSrc.set(0,0,470,498);
rcDest.set(0,0,m_nDisplayHeight,m_nDisplayHeight);
// 배경 (화면 가득 채우게 크기조절)
canvas.drawBitmap(m_bmpBg , rcSrc, rcDest, null);
// 몹을 추가하고 이동한다(다음 몹과의 간격이 안되면 생성안됨)
m_cMopMgr.AddMop();
m_cMopMgr.MoveMop();
// 몹을 그린다
for(int n = 0 ; n < m_cMopMgr.m_nMopCnt ; n++){
if(m_cMopMgr.mop[n].m_bUsed == true){
canvas.drawBitmap(m_bmpMop ,m_cMopMgr.mop[n].m_nX,m_cMopMgr.mop[n].m_nY, null);
}
}
sleep(10);
}
}
catch (InterruptedException e){
e.printStackTrace();
}
finally{
if(canvas != null){
m_SurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
}
public GameRun(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
SurfaceHolder holder = getHolder();
holder.addCallback(this);
m_GameThread = new GameThread(holder, context);
}
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
// TODO Auto-generated method stub
}
public void surfaceCreated(SurfaceHolder arg0) {
// TODO Auto-generated method stub
m_GameThread.start();
}
public void surfaceDestroyed(SurfaceHolder arg0) {
// TODO Auto-generated method stub
}
}
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.Display;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;
import android.view.SurfaceHolder.Callback;
public class GameRun extends SurfaceView implements Callback {
private GameThread m_GameThread;
private CMopMgr m_cMopMgr = new CMopMgr();
class CMop{
public int m_nX;
public int m_nY;
public long m_lBeforTime; // 이전에 움직였던 밀리세컨드
public long m_nSleep; // 이전과 다음이동사이의 밀리세컨드
public int m_nSpeed; // 한 이동 단위에 움직일 pixel 숫자
public int m_nMoveArea; // 이동할 구역
public int m_nMopHealth; // 몹체력
public boolean m_bUsed; // 생성되었나
public boolean m_bDie; // 몹이 죽었나 - 재생되지 않음
public int m_nDirection; // 이전 몹의 방향
// x, y, 방향(동쪽이 1이며 시계방향으로 증가함)
public int m_aMovePos[][] =
{
{ 93, 0, 2}, // 시작점임 남쪽 으로
{ 93, 19, 3}, // 서쪽 으로
{ 23, 19, 2}, // 남쪽 으로
{ 23, 143, 1}, // 동쪽 으로
{ 94, 143, 4}, // 북쪽 으로
{ 94, 95, 1}, // 동쪽 으로
{206, 95, 2}, // 남쪽 으로
{206, 215, 3}, // 서쪽 으로
{ 24, 215, 2}, // 남쪽 으로
{ 24, 286, 1}, // 동쪽 으로
{282, 286, 4}, // 북쪽 으로
{282, 19, 3}, // 서쪽 으로
{175, 19, 4}, // 위쪽 으로
{175, 0, 0} // 종착지
};
CMop(){
m_nX = 93;
m_nY = 0;
m_lBeforTime = System.currentTimeMillis(); // 현재 시간을 설정한다
m_nSleep = 10; // 10 밀리세컨드마다 움직인다.
m_nSpeed = 2; // 2pixel 씩 움직인다
m_nMoveArea = 1; // 처음 시작은 위 중간즈음이다
m_bUsed = false;// 생성되지 않은 상태임
m_nMopHealth = 100; // 몹체력은 100으로 초기화
m_bDie = false;// 몹은 살아있는걸로 초기화됨
m_nDirection = -1;
}
// 다음 포지션으로 이동
public void MovePosition(){
// 이전 움직인 시간 이후 m_nSleep 이상 흘렀다면 움직인다.
if( (System.currentTimeMillis() - m_lBeforTime) > m_nSleep ){
m_lBeforTime = System.currentTimeMillis();
}
else{
return;
}
// 방향 전환 인가
int nDir = m_aMovePos[m_nMoveArea-1][2];
if(m_nDirection != nDir){
m_nDirection = nDir;
}
// 마지막 위치면 몹에대해 특별처리함
if(m_nMoveArea > 13){
m_nDirection = -1;
m_nX = 93;
m_nY = 0;
m_cMopMgr.m_nOverCount += 1; // 오버 카운트를 1 증가 시킨다
m_nMoveArea = 1;
return;
}
// 방향에 따라 X,Y 좌표를 각각 이동 시킨다
if(nDir == 1){
m_nX += m_nSpeed;
if(m_aMovePos[m_nMoveArea][0] < m_nX) m_nMoveArea++;
}
else if(nDir == 2){
m_nY += m_nSpeed;
if(m_aMovePos[m_nMoveArea][1] < m_nY) m_nMoveArea++;
}
else if(nDir == 3){
m_nX -= m_nSpeed;
if(m_aMovePos[m_nMoveArea][0] > m_nX) m_nMoveArea++;
}
else if(nDir == 4){
m_nY -= m_nSpeed;
if(m_aMovePos[m_nMoveArea][1] > m_nY) m_nMoveArea++;
}
}
}
class CMopMgr{
public static final int m_nMopCnt = 20; // 몹 개수
public CMop mop[] = new CMop[m_nMopCnt]; // 몹 Count
public int m_nUsedMopCnt = 0; // 생성되었던 몹 Count
public int m_nDieMopCnt = 0; // 죽은 몹 count
public long m_lRegen = 1000; // 다음몹 생성되는 시간(1초)
public int m_nOverCount = 0; // 몹이 한바퀴 도달하면 1 증가
public long m_lBeforRegen = System.currentTimeMillis();
CMopMgr(){
for(int n = 0 ; n < m_nMopCnt ; n++){
mop[n] = new CMop();
}
}
// 신규몹 추가 - m_bStart 플래그만 true 로 설정
public void AddMop(){
// 리젠 시간에 도달하면 몹 추가를 시도함
if( (System.currentTimeMillis() - m_lBeforRegen) > m_lRegen){
m_lBeforRegen = System.currentTimeMillis();
}
else return;
if(m_nUsedMopCnt >= (m_nMopCnt-1)) return;
mop[m_nUsedMopCnt].m_bUsed = true;
m_nUsedMopCnt ++;
}
// 이동 가능한 모든 몹들을 이동시킨다
public void MoveMop(){
for(int n = 0 ; n < m_nMopCnt ; n++){
if(mop[n].m_bUsed == true){
mop[n].MovePosition();
}
}
}
}
class GameThread extends Thread {
private int m_nDisplayWidth; // 화면 가로 크기
private int m_nDisplayHeight; // 화면 세로 크기
private Bitmap m_bmpBg; // 배경
private Bitmap m_bmpMop; // 상대방 유닛
private Bitmap m_bmpMissile; // 미사일
private Bitmap m_bmpTower; // 수비건물
private SurfaceHolder m_SurfaceHolder;
public boolean m_bStop;
public GameThread (SurfaceHolder surfaceholder, Context context){
m_SurfaceHolder = surfaceholder;
Resources res = context.getResources();
// png 그림 이미지를 로딩
m_bmpBg = BitmapFactory.decodeResource(res,R.drawable.bg);
m_bmpMop = BitmapFactory.decodeResource(res,R.drawable.mop);
m_bmpMissile = BitmapFactory.decodeResource(res,R.drawable.missile);
m_bmpTower = BitmapFactory.decodeResource(res,R.drawable.tower);
// 화면의 가로, 세로 사이즈를 구함.
Display display =
((WindowManager)context.getSystemService(context.WINDOW_SERVICE)).getDefaultDisplay();
m_nDisplayWidth = display.getWidth();
m_nDisplayHeight = display.getHeight();
// 쓰레드가 작동하게 설정
m_bStop = false;
}
public void run(){
while(!m_bStop){
Canvas canvas = null;
try {
canvas = m_SurfaceHolder.lockCanvas(null);
synchronized(m_SurfaceHolder){
Rect rcSrc = new Rect();
Rect rcDest = new Rect();
rcSrc.set(0,0,470,498);
rcDest.set(0,0,m_nDisplayHeight,m_nDisplayHeight);
// 배경 (화면 가득 채우게 크기조절)
canvas.drawBitmap(m_bmpBg , rcSrc, rcDest, null);
// 몹을 추가하고 이동한다(다음 몹과의 간격이 안되면 생성안됨)
m_cMopMgr.AddMop();
m_cMopMgr.MoveMop();
// 몹을 그린다
for(int n = 0 ; n < m_cMopMgr.m_nMopCnt ; n++){
if(m_cMopMgr.mop[n].m_bUsed == true){
canvas.drawBitmap(m_bmpMop ,m_cMopMgr.mop[n].m_nX,m_cMopMgr.mop[n].m_nY, null);
}
}
sleep(10);
}
}
catch (InterruptedException e){
e.printStackTrace();
}
finally{
if(canvas != null){
m_SurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
}
public GameRun(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
SurfaceHolder holder = getHolder();
holder.addCallback(this);
m_GameThread = new GameThread(holder, context);
}
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
// TODO Auto-generated method stub
}
public void surfaceCreated(SurfaceHolder arg0) {
// TODO Auto-generated method stub
m_GameThread.start();
}
public void surfaceDestroyed(SurfaceHolder arg0) {
// TODO Auto-generated method stub
}
}
실행시켜 보면 20마리가 길따라 계속 반복해서 움직이는것을 보실 수 있습니다.
적절한 주석을 참고하시기 바랍니다.
이번에는 길따라 움직이는 몹을 타워를 건설해서 잡아 보도록 하겠습니다.
먼저 아래 소스를 복사해서 GameRun.java 에 부쳐 넣습니다.
import 오류가 나면 역시 ctrl + shift + o 로 자동 import 합니다.
package com.game;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.Display;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;
import android.view.SurfaceHolder.Callback;
public class GameRun extends SurfaceView implements Callback {
private GameThread m_GameThread;
private CMopMgr m_cMopMgr = new CMopMgr();
private CTowerMgr m_cTowerMgr = new CTowerMgr();
class CMop{
public int m_nX;
public int m_nY;
public long m_lBeforTime; // 이전에 움직였던 밀리세컨드
public long m_nSleep; // 이전과 다음이동사이의 밀리세컨드
public int m_nSpeed; // 한 이동 단위에 움직일 pixel 숫자
public int m_nMoveArea; // 이동할 구역
public int m_nMopHealth; // 몹체력
public boolean m_bUsed; // 생성되었나
public boolean m_bDie; // 몹이 죽었나 - 재생되지 않음
public int m_nDirection; // 이전 몹의 방향
// x, y, 방향(동쪽이 1이며 시계방향으로 증가함)
public int m_aMovePos[][] =
{
{ 93, 0, 2}, // 시작점임 남쪽 으로
{ 93, 19, 3}, // 서쪽 으로
{ 23, 19, 2}, // 남쪽 으로
{ 23, 143, 1}, // 동쪽 으로
{ 94, 143, 4}, // 북쪽 으로
{ 94, 95, 1}, // 동쪽 으로
{206, 95, 2}, // 남쪽 으로
{206, 215, 3}, // 서쪽 으로
{ 24, 215, 2}, // 남쪽 으로
{ 24, 286, 1}, // 동쪽 으로
{282, 286, 4}, // 북쪽 으로
{282, 19, 3}, // 서쪽 으로
{175, 19, 4}, // 위쪽 으로
{175, 0, 0} // 종착지
};
CMop(){
m_nX = 93;
m_nY = 0;
m_lBeforTime = System.currentTimeMillis(); // 현재 시간을 설정한다
m_nSleep = 10; // 10 밀리세컨드마다 움직인다.
m_nSpeed = 2; // 2pixel 씩 움직인다
m_nMoveArea = 1; // 처음 시작은 위 중간즈음이다
m_bUsed = false;// 생성되지 않은 상태임
m_nMopHealth = 100; // 몹체력은 100으로 초기화
m_bDie = false;// 몹은 살아있는걸로 초기화됨
m_nDirection = -1;
}
// 다음 포지션으로 이동
public void MovePosition(){
// 이전 움직인 시간 이후 m_nSleep 이상 흘렀다면 움직인다.
if( (System.currentTimeMillis() - m_lBeforTime) > m_nSleep ){
m_lBeforTime = System.currentTimeMillis();
}
else{
return;
}
// 방향 전환 인가
int nDir = m_aMovePos[m_nMoveArea-1][2];
if(m_nDirection != nDir){
m_nDirection = nDir;
}
// 마지막 위치면 몹에대해 특별처리함
if(m_nMoveArea > 13){
m_nDirection = -1;
m_nX = 93;
m_nY = 0;
m_cMopMgr.m_nOverCount += 1; // 오버 카운트를 1 증가 시킨다
m_nMoveArea = 1;
return;
}
// 방향에 따라 X,Y 좌표를 각각 이동 시킨다
if(nDir == 1){
m_nX += m_nSpeed;
if(m_aMovePos[m_nMoveArea][0] < m_nX) m_nMoveArea++;
}
else if(nDir == 2){
m_nY += m_nSpeed;
if(m_aMovePos[m_nMoveArea][1] < m_nY) m_nMoveArea++;
}
else if(nDir == 3){
m_nX -= m_nSpeed;
if(m_aMovePos[m_nMoveArea][0] > m_nX) m_nMoveArea++;
}
else if(nDir == 4){
m_nY -= m_nSpeed;
if(m_aMovePos[m_nMoveArea][1] > m_nY) m_nMoveArea++;
}
}
}
class CMopMgr{
public static final int m_nMopCnt = 20; // 몹 개수
public CMop mop[] = new CMop[m_nMopCnt]; // 몹 Count
public int m_nUsedMopCnt = 0; // 생성되었던 몹 Count
public int m_nDieMopCnt = 0; // 죽은 몹 count
public long m_lRegen = 1000; // 다음몹 생성되는 시간(1초)
public int m_nOverCount = 0; // 몹이 한바퀴 도달하면 1 증가
public long m_lBeforRegen = System.currentTimeMillis();
CMopMgr(){
for(int n = 0 ; n < m_nMopCnt ; n++){
mop[n] = new CMop();
}
}
// 신규몹 추가 - m_bStart 플래그만 true 로 설정
public void AddMop(){
// 리젠 시간에 도달하면 몹 추가를 시도함
if( (System.currentTimeMillis() - m_lBeforRegen) > m_lRegen){
m_lBeforRegen = System.currentTimeMillis();
}
else return;
if(m_nUsedMopCnt >= (m_nMopCnt-1)) return;
mop[m_nUsedMopCnt].m_bUsed = true;
m_nUsedMopCnt ++;
}
// 이동 가능한 모든 몹들을 이동시킨다
public void MoveMop(){
for(int n = 0 ; n < m_nMopCnt ; n++){
if(mop[n].m_bUsed == true){
mop[n].MovePosition();
}
}
}
// 공격 가능한 몹 번호를 반환한다
public int FindMop(int nTowerIndex){
for(int n = 0 ; n < m_nMopCnt ; n++){
// 죽었거나 생성되지 않은 몹이면 continue..
if(mop[n].m_bUsed == false){
continue;
}
if(mop[n].m_bDie == true){
continue;
}
int nX = m_cTowerMgr.tower[nTowerIndex].m_nX - mop[n].m_nX;
int nY = m_cTowerMgr.tower[nTowerIndex].m_nY - mop[n].m_nY;
int nDistance = nX * nX + nY * nY;
if(nDistance < 3000) return n;
}
return -1;
}
}
class CTower{
public int m_nX = 0; // 타워 X좌표
public int m_nY = 0; // 타워 Y좌표
public int m_nMopIndex; // 공격 몹의 Index
public long m_lAttackTime = 0; // 공격시간
public long m_lAttackSleep = 1500; // 공격 간격 1.5초
public boolean m_bUsed = false; // 사용되는 타워여부
public boolean m_bUsedMissale = false; // 미사일이 발사 되었나
public int m_nMissalePos = 0; // 미사일 진행 거리(0~5)
public long m_lMissaleSleep = 8; // 미사일 1틱 간격(0.2초)
public long m_lMissaleTime = 0; // 이전 미사일 진행했던 시간
public int m_nMissaleX = 0; // 미사일 X좌표
public int m_nMissaleY = 0; // 미사일 Y좌표
void AttackMop(int nMopIndex){
// 사용중인 타워가 아니라면 return;
if(m_bUsed == false) return;
// 공격 간격이 안되었다면 return;
if( (System.currentTimeMillis() - m_lAttackTime) > m_lAttackSleep){
m_lAttackTime = System.currentTimeMillis();
}
else return;
// 미사일이 발사된 상태면 return
if(m_bUsedMissale == true) return;
// 미사일 발사
m_bUsedMissale = true;
// 타겟몹 설정
m_nMopIndex = nMopIndex;
}
void MoveMissale(){
// 사용중인 타워가 아니면 return
if(m_bUsed == false) return;
// 미사일이 발사되지 않았으면 return
if(m_bUsedMissale == false) return;
// 미사일 진행 간격이 아니면 return
if( (System.currentTimeMillis() - m_lMissaleTime) > m_lMissaleSleep ){
m_lMissaleTime = System.currentTimeMillis();
}
else return;
// 미사일에 맞았다
if(m_nMissalePos > 6){
// 미사일은 사용되지 않은 상태다
m_nMissalePos = 0;
m_bUsedMissale = false;
// 이미 죽어있다.
if(m_cMopMgr.mop[m_nMopIndex].m_nMopHealth < 0){
return;
}
m_cMopMgr.mop[m_nMopIndex].m_nMopHealth -= 30;
if(m_cMopMgr.mop[m_nMopIndex].m_nMopHealth <= 0){
// 몹은 죽었다
m_cMopMgr.mop[m_nMopIndex].m_bUsed = false;
m_cMopMgr.m_nDieMopCnt += 1;
}
return;
}
int nMopX = m_cMopMgr.mop[m_nMopIndex].m_nX;
int nMopY = m_cMopMgr.mop[m_nMopIndex].m_nY;
// 몹과의 거리
int nWidth = m_nX - nMopX;
int nHeight = m_nY - nMopY;
// 현재 타워에서 몹까지의 비율을 나눠서 구해 더한다
m_nMissaleX = m_nX - (nWidth / (8 - m_nMissalePos) );
m_nMissaleY = m_nY - (nHeight / (8 - m_nMissalePos) );
m_nMissalePos++;
}
}
class CTowerMgr{
public static final int m_nTowerCnt = 10; // 타워 숫자
public CTower tower[] = new CTower[m_nTowerCnt]; // 타워 객체
public int m_nUsedTowerCnt = 0; // 사용되는 타워 Count
CTowerMgr(){
for(int n = 0 ; n < m_nTowerCnt ; n++){
tower[n] = new CTower();
}
}
// 신규타워 추가 - m_bUsed 플래그만 true로 설정
public void AddTower(int x, int y){
if(m_nUsedTowerCnt >= m_nTowerCnt) return;
tower[m_nUsedTowerCnt].m_bUsed = true;
tower[m_nUsedTowerCnt].m_nX = x;
tower[m_nUsedTowerCnt].m_nY = y;
m_nUsedTowerCnt ++;
}
public int GetTowerCount(){
return m_nUsedTowerCnt;
}
}
class GameThread extends Thread {
private int m_nDisplayWidth; // 화면 가로 크기
private int m_nDisplayHeight; // 화면 세로 크기
private Bitmap m_bmpBg; // 배경
private Bitmap m_bmpMop; // 상대방 유닛
private Bitmap m_bmpMissile; // 미사일
private Bitmap m_bmpTower; // 수비건물
private SurfaceHolder m_SurfaceHolder;
public boolean m_bStop;
public GameThread (SurfaceHolder surfaceholder, Context context){
m_SurfaceHolder = surfaceholder;
Resources res = context.getResources();
// png 그림 이미지를 로딩
m_bmpBg = BitmapFactory.decodeResource(res,R.drawable.bg);
m_bmpMop = BitmapFactory.decodeResource(res,R.drawable.mop);
m_bmpMissile = BitmapFactory.decodeResource(res,R.drawable.missile);
m_bmpTower = BitmapFactory.decodeResource(res,R.drawable.tower);
// 화면의 가로, 세로 사이즈를 구함.
Display display =
((WindowManager)context.getSystemService(context.WINDOW_SERVICE)).getDefaultDisplay();
m_nDisplayWidth = display.getWidth();
m_nDisplayHeight = display.getHeight();
// 쓰레드가 작동하게 설정
m_bStop = false;
}
public void run(){
while(!m_bStop){
Canvas canvas = null;
try {
canvas = m_SurfaceHolder.lockCanvas(null);
synchronized(m_SurfaceHolder){
Rect rcSrc = new Rect();
Rect rcDest = new Rect();
rcSrc.set(0,0,470,498);
rcDest.set(0,0,m_nDisplayHeight,m_nDisplayHeight);
// 배경 (화면 가득 채우게 크기조절)
canvas.drawBitmap(m_bmpBg , rcSrc, rcDest, null);
// 몹을 추가하고 이동한다(다음 몹과의 간격이 안되면 생성안됨)
m_cMopMgr.AddMop();
m_cMopMgr.MoveMop();
// 몹을 그린다
for(int n = 0 ; n < m_cMopMgr.m_nMopCnt ; n++){
if(m_cMopMgr.mop[n].m_bUsed == true){
canvas.drawBitmap(m_bmpMop ,m_cMopMgr.mop[n].m_nX,m_cMopMgr.mop[n].m_nY, null);
}
}
for(int n = 0 ; n <m_cTowerMgr.m_nTowerCnt ; n++){
if(m_cTowerMgr.tower[n].m_bUsed == true){
canvas.drawBitmap(m_bmpTower ,m_cTowerMgr.tower[n].m_nX,m_cTowerMgr.tower[n].m_nY, null);
// 미사일을 그린다.
if(m_cTowerMgr.tower[n].m_bUsedMissale){
canvas.drawBitmap(m_bmpMissile ,m_cTowerMgr.tower[n].m_nMissaleX,m_cTowerMgr.tower[n].m_nMissaleY, null);
}
else{ // 근처의 몹을 검색하여 공격한다
int nMop = m_cMopMgr.FindMop(n);
if(nMop >= 0){
m_cTowerMgr.tower[n].AttackMop(nMop);
}
}
m_cTowerMgr.tower[n].MoveMissale();
}
}
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.WHITE); //화면 캔버스를 하얀색으로 표현한다.
canvas.drawText( " 타워 설치 가능 : " + (m_cTowerMgr.m_nTowerCnt - m_cTowerMgr.GetTowerCount() ) ,5,20, paint);
canvas.drawText( " 아웃 카운트 : " + (10 - m_cMopMgr.m_nOverCount),5,40, paint);
sleep(10);
}
}
catch (InterruptedException e){
e.printStackTrace();
}
finally{
if(canvas != null){
m_SurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
void AddTower(int x, int y){
m_cTowerMgr.AddTower(x-8,y-12);
}
}
public GameRun(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
SurfaceHolder holder = getHolder();
holder.addCallback(this);
m_GameThread = new GameThread(holder, context);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
if(event.getAction() == MotionEvent.ACTION_UP){
m_GameThread.AddTower((int)event.getX(), (int)event.getY());
}
return true;
}
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
// TODO Auto-generated method stub
}
public void surfaceCreated(SurfaceHolder arg0) {
// TODO Auto-generated method stub
m_GameThread.start();
}
public void surfaceDestroyed(SurfaceHolder arg0) {
// TODO Auto-generated method stub
}
}
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.Display;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;
import android.view.SurfaceHolder.Callback;
public class GameRun extends SurfaceView implements Callback {
private GameThread m_GameThread;
private CMopMgr m_cMopMgr = new CMopMgr();
private CTowerMgr m_cTowerMgr = new CTowerMgr();
class CMop{
public int m_nX;
public int m_nY;
public long m_lBeforTime; // 이전에 움직였던 밀리세컨드
public long m_nSleep; // 이전과 다음이동사이의 밀리세컨드
public int m_nSpeed; // 한 이동 단위에 움직일 pixel 숫자
public int m_nMoveArea; // 이동할 구역
public int m_nMopHealth; // 몹체력
public boolean m_bUsed; // 생성되었나
public boolean m_bDie; // 몹이 죽었나 - 재생되지 않음
public int m_nDirection; // 이전 몹의 방향
// x, y, 방향(동쪽이 1이며 시계방향으로 증가함)
public int m_aMovePos[][] =
{
{ 93, 0, 2}, // 시작점임 남쪽 으로
{ 93, 19, 3}, // 서쪽 으로
{ 23, 19, 2}, // 남쪽 으로
{ 23, 143, 1}, // 동쪽 으로
{ 94, 143, 4}, // 북쪽 으로
{ 94, 95, 1}, // 동쪽 으로
{206, 95, 2}, // 남쪽 으로
{206, 215, 3}, // 서쪽 으로
{ 24, 215, 2}, // 남쪽 으로
{ 24, 286, 1}, // 동쪽 으로
{282, 286, 4}, // 북쪽 으로
{282, 19, 3}, // 서쪽 으로
{175, 19, 4}, // 위쪽 으로
{175, 0, 0} // 종착지
};
CMop(){
m_nX = 93;
m_nY = 0;
m_lBeforTime = System.currentTimeMillis(); // 현재 시간을 설정한다
m_nSleep = 10; // 10 밀리세컨드마다 움직인다.
m_nSpeed = 2; // 2pixel 씩 움직인다
m_nMoveArea = 1; // 처음 시작은 위 중간즈음이다
m_bUsed = false;// 생성되지 않은 상태임
m_nMopHealth = 100; // 몹체력은 100으로 초기화
m_bDie = false;// 몹은 살아있는걸로 초기화됨
m_nDirection = -1;
}
// 다음 포지션으로 이동
public void MovePosition(){
// 이전 움직인 시간 이후 m_nSleep 이상 흘렀다면 움직인다.
if( (System.currentTimeMillis() - m_lBeforTime) > m_nSleep ){
m_lBeforTime = System.currentTimeMillis();
}
else{
return;
}
// 방향 전환 인가
int nDir = m_aMovePos[m_nMoveArea-1][2];
if(m_nDirection != nDir){
m_nDirection = nDir;
}
// 마지막 위치면 몹에대해 특별처리함
if(m_nMoveArea > 13){
m_nDirection = -1;
m_nX = 93;
m_nY = 0;
m_cMopMgr.m_nOverCount += 1; // 오버 카운트를 1 증가 시킨다
m_nMoveArea = 1;
return;
}
// 방향에 따라 X,Y 좌표를 각각 이동 시킨다
if(nDir == 1){
m_nX += m_nSpeed;
if(m_aMovePos[m_nMoveArea][0] < m_nX) m_nMoveArea++;
}
else if(nDir == 2){
m_nY += m_nSpeed;
if(m_aMovePos[m_nMoveArea][1] < m_nY) m_nMoveArea++;
}
else if(nDir == 3){
m_nX -= m_nSpeed;
if(m_aMovePos[m_nMoveArea][0] > m_nX) m_nMoveArea++;
}
else if(nDir == 4){
m_nY -= m_nSpeed;
if(m_aMovePos[m_nMoveArea][1] > m_nY) m_nMoveArea++;
}
}
}
class CMopMgr{
public static final int m_nMopCnt = 20; // 몹 개수
public CMop mop[] = new CMop[m_nMopCnt]; // 몹 Count
public int m_nUsedMopCnt = 0; // 생성되었던 몹 Count
public int m_nDieMopCnt = 0; // 죽은 몹 count
public long m_lRegen = 1000; // 다음몹 생성되는 시간(1초)
public int m_nOverCount = 0; // 몹이 한바퀴 도달하면 1 증가
public long m_lBeforRegen = System.currentTimeMillis();
CMopMgr(){
for(int n = 0 ; n < m_nMopCnt ; n++){
mop[n] = new CMop();
}
}
// 신규몹 추가 - m_bStart 플래그만 true 로 설정
public void AddMop(){
// 리젠 시간에 도달하면 몹 추가를 시도함
if( (System.currentTimeMillis() - m_lBeforRegen) > m_lRegen){
m_lBeforRegen = System.currentTimeMillis();
}
else return;
if(m_nUsedMopCnt >= (m_nMopCnt-1)) return;
mop[m_nUsedMopCnt].m_bUsed = true;
m_nUsedMopCnt ++;
}
// 이동 가능한 모든 몹들을 이동시킨다
public void MoveMop(){
for(int n = 0 ; n < m_nMopCnt ; n++){
if(mop[n].m_bUsed == true){
mop[n].MovePosition();
}
}
}
// 공격 가능한 몹 번호를 반환한다
public int FindMop(int nTowerIndex){
for(int n = 0 ; n < m_nMopCnt ; n++){
// 죽었거나 생성되지 않은 몹이면 continue..
if(mop[n].m_bUsed == false){
continue;
}
if(mop[n].m_bDie == true){
continue;
}
int nX = m_cTowerMgr.tower[nTowerIndex].m_nX - mop[n].m_nX;
int nY = m_cTowerMgr.tower[nTowerIndex].m_nY - mop[n].m_nY;
int nDistance = nX * nX + nY * nY;
if(nDistance < 3000) return n;
}
return -1;
}
}
class CTower{
public int m_nX = 0; // 타워 X좌표
public int m_nY = 0; // 타워 Y좌표
public int m_nMopIndex; // 공격 몹의 Index
public long m_lAttackTime = 0; // 공격시간
public long m_lAttackSleep = 1500; // 공격 간격 1.5초
public boolean m_bUsed = false; // 사용되는 타워여부
public boolean m_bUsedMissale = false; // 미사일이 발사 되었나
public int m_nMissalePos = 0; // 미사일 진행 거리(0~5)
public long m_lMissaleSleep = 8; // 미사일 1틱 간격(0.2초)
public long m_lMissaleTime = 0; // 이전 미사일 진행했던 시간
public int m_nMissaleX = 0; // 미사일 X좌표
public int m_nMissaleY = 0; // 미사일 Y좌표
void AttackMop(int nMopIndex){
// 사용중인 타워가 아니라면 return;
if(m_bUsed == false) return;
// 공격 간격이 안되었다면 return;
if( (System.currentTimeMillis() - m_lAttackTime) > m_lAttackSleep){
m_lAttackTime = System.currentTimeMillis();
}
else return;
// 미사일이 발사된 상태면 return
if(m_bUsedMissale == true) return;
// 미사일 발사
m_bUsedMissale = true;
// 타겟몹 설정
m_nMopIndex = nMopIndex;
}
void MoveMissale(){
// 사용중인 타워가 아니면 return
if(m_bUsed == false) return;
// 미사일이 발사되지 않았으면 return
if(m_bUsedMissale == false) return;
// 미사일 진행 간격이 아니면 return
if( (System.currentTimeMillis() - m_lMissaleTime) > m_lMissaleSleep ){
m_lMissaleTime = System.currentTimeMillis();
}
else return;
// 미사일에 맞았다
if(m_nMissalePos > 6){
// 미사일은 사용되지 않은 상태다
m_nMissalePos = 0;
m_bUsedMissale = false;
// 이미 죽어있다.
if(m_cMopMgr.mop[m_nMopIndex].m_nMopHealth < 0){
return;
}
m_cMopMgr.mop[m_nMopIndex].m_nMopHealth -= 30;
if(m_cMopMgr.mop[m_nMopIndex].m_nMopHealth <= 0){
// 몹은 죽었다
m_cMopMgr.mop[m_nMopIndex].m_bUsed = false;
m_cMopMgr.m_nDieMopCnt += 1;
}
return;
}
int nMopX = m_cMopMgr.mop[m_nMopIndex].m_nX;
int nMopY = m_cMopMgr.mop[m_nMopIndex].m_nY;
// 몹과의 거리
int nWidth = m_nX - nMopX;
int nHeight = m_nY - nMopY;
// 현재 타워에서 몹까지의 비율을 나눠서 구해 더한다
m_nMissaleX = m_nX - (nWidth / (8 - m_nMissalePos) );
m_nMissaleY = m_nY - (nHeight / (8 - m_nMissalePos) );
m_nMissalePos++;
}
}
class CTowerMgr{
public static final int m_nTowerCnt = 10; // 타워 숫자
public CTower tower[] = new CTower[m_nTowerCnt]; // 타워 객체
public int m_nUsedTowerCnt = 0; // 사용되는 타워 Count
CTowerMgr(){
for(int n = 0 ; n < m_nTowerCnt ; n++){
tower[n] = new CTower();
}
}
// 신규타워 추가 - m_bUsed 플래그만 true로 설정
public void AddTower(int x, int y){
if(m_nUsedTowerCnt >= m_nTowerCnt) return;
tower[m_nUsedTowerCnt].m_bUsed = true;
tower[m_nUsedTowerCnt].m_nX = x;
tower[m_nUsedTowerCnt].m_nY = y;
m_nUsedTowerCnt ++;
}
public int GetTowerCount(){
return m_nUsedTowerCnt;
}
}
class GameThread extends Thread {
private int m_nDisplayWidth; // 화면 가로 크기
private int m_nDisplayHeight; // 화면 세로 크기
private Bitmap m_bmpBg; // 배경
private Bitmap m_bmpMop; // 상대방 유닛
private Bitmap m_bmpMissile; // 미사일
private Bitmap m_bmpTower; // 수비건물
private SurfaceHolder m_SurfaceHolder;
public boolean m_bStop;
public GameThread (SurfaceHolder surfaceholder, Context context){
m_SurfaceHolder = surfaceholder;
Resources res = context.getResources();
// png 그림 이미지를 로딩
m_bmpBg = BitmapFactory.decodeResource(res,R.drawable.bg);
m_bmpMop = BitmapFactory.decodeResource(res,R.drawable.mop);
m_bmpMissile = BitmapFactory.decodeResource(res,R.drawable.missile);
m_bmpTower = BitmapFactory.decodeResource(res,R.drawable.tower);
// 화면의 가로, 세로 사이즈를 구함.
Display display =
((WindowManager)context.getSystemService(context.WINDOW_SERVICE)).getDefaultDisplay();
m_nDisplayWidth = display.getWidth();
m_nDisplayHeight = display.getHeight();
// 쓰레드가 작동하게 설정
m_bStop = false;
}
public void run(){
while(!m_bStop){
Canvas canvas = null;
try {
canvas = m_SurfaceHolder.lockCanvas(null);
synchronized(m_SurfaceHolder){
Rect rcSrc = new Rect();
Rect rcDest = new Rect();
rcSrc.set(0,0,470,498);
rcDest.set(0,0,m_nDisplayHeight,m_nDisplayHeight);
// 배경 (화면 가득 채우게 크기조절)
canvas.drawBitmap(m_bmpBg , rcSrc, rcDest, null);
// 몹을 추가하고 이동한다(다음 몹과의 간격이 안되면 생성안됨)
m_cMopMgr.AddMop();
m_cMopMgr.MoveMop();
// 몹을 그린다
for(int n = 0 ; n < m_cMopMgr.m_nMopCnt ; n++){
if(m_cMopMgr.mop[n].m_bUsed == true){
canvas.drawBitmap(m_bmpMop ,m_cMopMgr.mop[n].m_nX,m_cMopMgr.mop[n].m_nY, null);
}
}
for(int n = 0 ; n <m_cTowerMgr.m_nTowerCnt ; n++){
if(m_cTowerMgr.tower[n].m_bUsed == true){
canvas.drawBitmap(m_bmpTower ,m_cTowerMgr.tower[n].m_nX,m_cTowerMgr.tower[n].m_nY, null);
// 미사일을 그린다.
if(m_cTowerMgr.tower[n].m_bUsedMissale){
canvas.drawBitmap(m_bmpMissile ,m_cTowerMgr.tower[n].m_nMissaleX,m_cTowerMgr.tower[n].m_nMissaleY, null);
}
else{ // 근처의 몹을 검색하여 공격한다
int nMop = m_cMopMgr.FindMop(n);
if(nMop >= 0){
m_cTowerMgr.tower[n].AttackMop(nMop);
}
}
m_cTowerMgr.tower[n].MoveMissale();
}
}
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.WHITE); //화면 캔버스를 하얀색으로 표현한다.
canvas.drawText( " 타워 설치 가능 : " + (m_cTowerMgr.m_nTowerCnt - m_cTowerMgr.GetTowerCount() ) ,5,20, paint);
canvas.drawText( " 아웃 카운트 : " + (10 - m_cMopMgr.m_nOverCount),5,40, paint);
sleep(10);
}
}
catch (InterruptedException e){
e.printStackTrace();
}
finally{
if(canvas != null){
m_SurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
void AddTower(int x, int y){
m_cTowerMgr.AddTower(x-8,y-12);
}
}
public GameRun(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
SurfaceHolder holder = getHolder();
holder.addCallback(this);
m_GameThread = new GameThread(holder, context);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
if(event.getAction() == MotionEvent.ACTION_UP){
m_GameThread.AddTower((int)event.getX(), (int)event.getY());
}
return true;
}
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
// TODO Auto-generated method stub
}
public void surfaceCreated(SurfaceHolder arg0) {
// TODO Auto-generated method stub
m_GameThread.start();
}
public void surfaceDestroyed(SurfaceHolder arg0) {
// TODO Auto-generated method stub
}
}
주요 로직은 주석으로 첨부하였습니다.
다음번에 좀더 자세한 사항과 추가 기능들에 대해 알아보도록 하겠습니다.
전체 소스 첨부합니다.
반응형
'Program > Android Java' 카테고리의 다른 글
- interpolator 속성 - (0) | 2011.05.09 |
---|---|
[Android] 새로운 Activity 실행 시 애니메이션 효과능 (0) | 2011.04.21 |
안드로이드 게임 엔진 ROKON - Using Modifiers (0) | 2011.04.15 |
안드로이드 게임 엔진 ROKON - Using Touch Input (0) | 2011.04.15 |
안드로이드 게임 엔진 ROKON - Using Sprites (0) | 2011.04.15 |