[Android 게임개발] 메모리 게임 만들기(Building A Memory-Game)!

설명: 게임의 룰은 간단하다. 스테이지가 하나씩 올라갈때 마다 도형이 하나씩 추가 되는데, 이전에 존재했던 도형을 터치하면 fail, 새롭게 생긴 도형을 터치하면 다음 스테이지로 넘어갈수 있게된다. 즉, 한마디로 정리하자면 [기억력 테스트 게임] 정도 되겠다.
이번 프로젝트에서도 activity_main은 사용하지 않는다. 대신 java 파일을 독립적으로 생성하여 MainActivity에서 인플레이트 하는식이다.
(사실 MainActivity에서도 하는거라곤 인플레이트가 ‘전부’다.)
밑의 코드와 함께 설명을 참고하자.

-MyGameView.java

package com.example.mymemorygame;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Message;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.NonNull;

import java.util.ArrayList;
import java.util.Random;

public class MyGameView extends View {

    private static final int BLANK = 0; // 상태값 (대기상태)
    private static final int PLAY = 1; //게임이 진행중
    private static final int DELAY = 1500; // 도형 생성 시간 (1500 = 1.5초)

    private  int status;

    private ArrayList<Shape> arShape = new ArrayList<Shape>();
    private Random rnd = new Random(); //난수값을 구하는 클래스
    private Activity mParent;

    public MyGameView(Context context){
        super(context);
        mParent = (Activity)context;
        status = BLANK;

        //핸들러 실행
        handler.sendEmptyMessageDelayed(0,DELAY);
    }

    //도형의 색상, 모양 등의 속성을 관리할수있는 중첩클래스 생성
    class Shape{
        static final int RECT = 0; // 사각형
        static final int CIRCLE = 1; //원
        static final int TRIANGLE = 2; //사각형

        int what;
        int color;
        Rect rt;

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //캔버스를 검정색으로 두자
        canvas.drawColor(Color.BLACK);

        //대기상태 (BLANK(0)) 일때 리턴시키겠다.
        if(status == BLANK){
            return;
        }
        int idx = 0;

        for(idx = 0; idx < arShape.size(); idx++){
            Paint pnt = new Paint();

            pnt.setColor(arShape.get(idx).color);

            Rect rt = arShape.get(idx).rt;

            switch (arShape.get(idx).what){
                case Shape.RECT:
                    canvas.drawRect(rt,pnt);
                    break;
                    case Shape.CIRCLE:
                        canvas.drawCircle
                                (rt.left+rt.width()/2,
                                rt.top+rt.height()/2,
                                rt.width()/2, pnt);
                        break;

                        case Shape.TRIANGLE:
                            Path path = new Path();
                            path.moveTo(rt.left + rt.width()/2,rt.top);
                            path.lineTo(rt.left,rt.bottom);
                            path.lineTo(rt.right,rt.bottom);
                            canvas.drawPath(path,pnt);
                            break;
                            default:
                                break;
            }
        }
    }

    //일정 간격으로 도형을 생성하기 위한 핸들러
    Handler handler = new Handler(){

        @Override
        public void handleMessage(@NonNull Message msg) {

            AddNewShape();
            status = PLAY;
            invalidate();

            String title = "stage ~ " + arShape.size();
            mParent.setTitle(title);
        }
    };

    //새로운 도형을 추가하기 위한 메소드
    public void AddNewShape(){
        Shape shape = new Shape();
        int idx;

        //도형이 중복되었는지 판별하는 변수
        boolean bFindIntersect;

        Rect rt = new Rect();

        //랜덤으로 도형 사이즈 생성 - 50~150 사이의 난수를 생성해서 만든다.
        while (true){

            int size = rnd.nextInt(101)+50;

            //사각형의 범위
            rt.left = rnd.nextInt(getWidth());
            rt.top = rnd.nextInt(getHeight());
            rt.right = rt.left + size;
            rt.bottom = rt.top + size;

            //도형이 화면을 벗어날경우 새로 그리라는 메소드
            if(rt.right >= getWidth() || rt.bottom >= getHeight()){

                // if문 안에있는 케이스가 될 경우 continue 하라, 즉 처음부터 다시해라
                continue;
            }

            bFindIntersect = false;

            for(idx = 0; idx <arShape.size();idx++){
                if(rt.intersect(arShape.get(idx).rt)==true){

                    //겹치면 true
                    bFindIntersect = true;

                }
            }
if(bFindIntersect == false){
    break;
}
        }

        //도형 모양
        shape.what = rnd.nextInt(3);

        //도형 컬러 지정
        switch (rnd.nextInt(5)){
            case 0:
                shape.color = Color.WHITE;
                break;
            case 1:
                shape.color = Color.RED;
                break;
            case 2:
                shape.color = Color.GREEN;
                break;
            case 3:
                shape.color = Color.BLUE;
                break;
            case 4:
                shape.color = Color.MAGENTA;
                break;

        }
        shape.rt = rt;
        arShape.add(shape);

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(event.getAction() == MotionEvent.ACTION_DOWN){

            //도형의 위치를 제대로 클릭했는지 판별하기 위한 메소드 호출
            int sel;

            sel = findShapeIdx((int)event.getX(),(int)event.getY());

            if(sel == -1){
                return true;
            }

            //완료가 되면 다음스테이지로 넘기겠다.
            if(sel == arShape.size() -1){
                status = BLANK;
                invalidate();
                handler.sendEmptyMessageDelayed(0,DELAY);

                //게임 종료시 alertDialog 생성
            }else{
                AlertDialog.Builder builder = new AlertDialog.Builder(mParent);
                builder.setMessage("restart Game~?");
                builder.setTitle("Game Over~");
                builder.setNegativeButton("OK", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {

                        //게임 초기화
                        arShape.clear();
                        status = BLANK;
                        invalidate();
                        handler.sendEmptyMessageDelayed(0,DELAY);

                    }
                });

                builder.setPositiveButton("exit", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        mParent.finish(); // 종료
                    }
                });

                builder.setCancelable(false);
                builder.show();

            }

        }

        return false;
    }

    public int findShapeIdx(int x,int y) {
        for (int idx = 0; idx < arShape.size(); idx++) {

            //arShape에 담긴 도형중에 터치된 x,y좌표를 가진 rect객체의 인덱스값을 반환
            if (arShape.get(idx).rt.contains(x, y)) { // contains? 객체가 있는지 없는지에 따라 명령
                return idx;
            }
        }
        return findShapeIdx(x,y);
    }
}

안드로이드에서의 코드를 그룹화시켰는데 4단계로 정도 나눠보았습니다!

(1) 게임의 모든 이벤트는 하나의 부모 class속 하나의 부모 view에서 일어난다.

public class MyGameView extends View {}

(2) 게임은 총 2가지의 상태값을 가진다: 게임의 대기상태 / 진행상태

 private static final int BLANK = 0; // 상태값 (대기상태)
 private static final int PLAY = 1; //게임이 진행중

(3) 게임은 총 5가지의 기본 멤버변수를 가진다: DELAY / status / ArrayList / Random / Activity

private static final int DELAY = 1500; // 도형 생성 시간 (1500 = 1.5초)
private int status;
private ArrayList<Shape> arShape = new ArrayList<Shape>();
private Random rnd = new Random(); //난수값을 구하는 클래스
private Activity mParent;

(4) 게임은 총 6가지의 상위 객체(메소드)를 가진다: MyGameView / onDraw / Handler / AddNewShape / onTouchEvent / FindShapeIdx

1. public MyGameView(Context context){    }

2. @Override protected void onDraw(Canvas canvas) {    }

3. Handler handler = new Handler(){ public void handleMessage(@NonNull Message msg) {	}}

4. public void AddNewShape(){    }

5. @Override public boolean onTouchEvent(MotionEvent event) {	}

6. public int findShapeIdx(int x,int y) {    }

-MainActivity.java

package com.example.mymemorygame;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);

        setContentView(new MyGameView(this));
    }
}

Main쪽 내용은 코드가 간단합니다! 안드로이드스튜디오나 자바 또는 코틀린에서 만든 MainActivity에서 인플레이트만 해주면 마무리가 된다고 봐야합니다!ㅎㅎ

1 thought on “[Android 게임개발] 메모리 게임 만들기(Building A Memory-Game)!”

Leave a Comment