안녕하세요. 호랑인 입니다.



이번 포스팅은 쉽습니다. 저번 포스팅이 조금 많은 내용을 우겨넣은 듯한 감이 없지 않아 있었는데, 한 가지 정말 다행인 점은 저 부분은 앞으로 거의 모든 앱을 만드는데에 있어서 필수적이고 공통적인 부분이라는 점입니다.



우선 저번에 만들었던 두개의 함수인 draw( ) 와 update( ) 를 만들어 봅시다.



당장은 draw( ) 부터 만들겠습니다. 업데이트를 할 그림이 우선 있어야 하니까요.




1
2
3
4
5
6
7
8
9
10
    @Override
    public void draw(Canvas canvas){
        super.draw(canvas);
        if(canvas != null){
            canvas.drawColor(Color.WHITE);
            Paint paint = new Paint();
            paint.setColor(Color.rgb(25000));
            canvas.drawRect(00100100, paint);
        }
    }
cs



위의 코드를 넣어봅시다. 코드는 크게 두 가지 역할을 갖고 있습니다.


1. canvas.drawColor(Color.WHITE) 는 배경을 하얗게 칠해줍니다.


2. Paint object를 하나 만들어줍니다. 이는 어떤 object (Rectangle, Circle 등등)에 색을 입힐 때 사용합니다.


3. Paint object에 색을 입력해줍니다. 저는 빨간색을 넣어줬습니다. 색은 rgb (alpha도 추가 가능) 을 넣어줄 수 있습니다.


4. canvas.draw... 까지만 치시면 다양한 함수들과 그 overriding 이 보이실 겁니다. 일단 slide라는 앱은 가장 단순한 앱을 만드는 것이 목적이므로, drawRect 를 씁니다.


drawRect 함수는 왼쪽위의 x, y, 오른쪽 아래의 x, y, 마지막으로 paint object를 input으로 받습니다.




자, 여기까지 하셨으면, 이제 빨간 사각형이 하나 나왔을 것입니다. 이제 이걸 움직여봅시다.



객체를 움직이는 등의 활동을 할 때에 사용하는 것이 바로 update 함수입니다. 이 함수를 통해 이 다음 frame 에서는 어떤 변화가 만들어져야 하는지를 계산합니다.


하.지.만 이 전에 android 에서의 좌표계를 아셔야 합니다.




그림으로 그릴 필요조차 없이 간단합니다. 왼쪽 위가 (0,0) 입니다. 그리고 오른쪽이 +x, 아래쪽이 +y 입니다. 아래쪽이 +y이라는 점이 가끔 헷갈려서 문제가 자주 발생합니다. (혼천의 앱에서 북극성이 아래쪽에 나타났었던 현상이 바로 이 예입니다.)




이제 update 함수를 만들어봅시다.



만들기에 앞서, 이 GameView object에 상자의 위치를 저장할 변수를 만들어 줍시다. 상자의 크기도 말이죠.



1
2
3
4
5
public class GameView extends SurfaceView implements SurfaceHolder.Callback {
 
    private int x;
    private int y;
    private int box_size;
cs



그리고 이들을 초기화 해주어야죠. (생성자에 추가)



1
2
3
4
5
6
7
8
    public GameView(Context context){
        super(context);
        getHolder().addCallback(this);
        thread = new MainThread(getHolder(), this);
 
        x = 0;
        y = 0;
        box_size = 100;
cs




자, 우선 update 함수를 아래와 같이 만들어 봅시다.



1
2
3
4
    public void update(int width, int height){
        x += 1;
        y += 1;
    }
cs



그리고 실행 시켜 보시면, 상자가 오른쪽 아래로 이동할 껍니다.



슬슬 게임 같아 지지 않나요?


----------------------------------------------------------------------------------------



원래 여기에서 마치려 했지만, 조금 더 추가하도록 하겠습니다.



현재 완성된 "게임"을 보면, 뭔가 아쉬운 부분이 딱 보입니다. 바로, 상자가 화면 밖으로 나간 뒤에는 아무것도 없다는 점입니다. 이를 보완해보죠. 단순하게, 튀겨 다니게 하는 걸 만들고, 이번 포스팅을 마치도록 하겠습니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
public class GameView extends SurfaceView implements SurfaceHolder.Callback {
 
    private final int MAX_TOUCH_COUNT = 100000;
    private MainThread thread;
 
    private int x;
    private int y;
    private float vel_x;
    private float vel_y;
    private int box_size;
 
    private int VIEW_WIDTH;
    private int VIEW_HEIGHT;
cs




변수를 조금 추가했습니다. 생성자 초기화도 해주세요 ~!


vel_x, vel_y 는 상자의 x 방향과 y 방향 속도를 저장하는 데에 쓰일 것입니다. 왜냐하면, 튀긴 다음에는 속도가 바귀어야 하기 때문이죠. 한 가지 말씀드리자면, vel_x, vel_y 는 10 정도로 초기화하는 것이 예쁩니다.


VIEW_WIDTH, VIEW_HEIGHT 는 화면 크기를 저장하는데에 사용될 것입니다.



이제, 벽에 튀기는 연산을 해주는 함수를 만들어 봅시다. 직사각형이기 때문에 매우 간단한데요, x 좌표, y 좌표, 상자 크기, WIDTH, HEIGHT 값을 사용해서 다음과 같이 설정해주시면 됩니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    public void check_boundary(){
        if(this.x >= this.VIEW_WIDTH-this.box_size){
            this.x = this.VIEW_WIDTH - this.box_size;
            this.vel_x = - this.vel_x;
        }
        if(this.y >= this.VIEW_HEIGHT-this.box_size){
            this.y = this.VIEW_HEIGHT - this.box_size;
            this.vel_y = - this.vel_y;
        }
        if(this.x <= 0){
            this.x = 0;
            this.vel_x = - this.vel_x;
        }
        if(this.y <= 0){
            this.y = 0;
            this.vel_y = - this.vel_y;
        }
    }
cs



내용을 보면, x 나 y 좌표가 0보다 작으면, 왼쪽이나 위의 벽에 부딪힌 것이므로, 속도의 방향을 뒤집고, 그 반대일 때에도 그리하라는 내용입니다. 주의하실 점은 오른쪽과 아래의 벽은 상자의 크기를 고려하셔야 한다는 점입니다.


또한, 이 경우 this. 은 모두 다 빼셔도 문제 없을 것입니다. python 코딩하던 습관이 나와버린 것이니, 무시해주세요.



위의 함수를 update() 함수에서 실행하면 됩니다.



1
2
3
4
5
6
7
    public void update(int width, int height){
        VIEW_WIDTH = width;
        VIEW_HEIGHT = height;
        x += vel_x;
        y += vel_y;
        check_boundary();
    }
cs



위처럼, this. 부분은 생략하셔도 됩니다.



자, 이제 통통 튀어다니는 사각형을 만들었습니다 ! (와아아아아)



이제 곰곰히 생각을 보십쇼. 창의력이 중요해지는 순간입니다. 여러분은 이제 뭘 할 수 있나요?



1. 많은 종류의 다각형을 원하는 크기로 그릴 수 있습니다.


2. 다각형에게 원하는 색을 줄 수 있고, 이를 시간에 따라 바꿀 수 있습니다.


3. 다각형의 크기를 원하는 데로 바꿀 수 있습니다.


4. 다각형의 운동을 변화시킬 수 있으며, 특히나 가장자리와의 충돌을 조절할 수 있습니다.




이것만으로도 거의 모든 것을 만들 수 있습니다. 하지만, 분명히 더 필요한 능력들이 존재합니다.



1. 사용자의 입력을 받을 수가 없습니다. - 터치, 키보드 등


2. 사진을 넣고 싶으면, 이를 다각형으로 그려야 하나요? (당연하지만 아닙니다.)


3. 소리를 넣고 싶습니다.



위의 "하지 못하는 것들" 은 차차 다루겠지만, 보시다시피 이제 남은 것은 상상력 뿐입니다. slide 게임의 미래를 결정하는 것은 당신의 상상력 하나에 모든 것이 걸려 있습니다.



1. 색이 변하는 것을 예쁘게 하고 싶다. 배경색도 같이 변화시켜서 예쁘게 하자. - 요새 뜨는 디자인 좋은 게임들의 기초.


2. 터치를 통해 원하는 방향으로 토스하면서 놀자 - 에어 하키와 같은 게임의 기초, objective를 넣어서 게임성을 더 살릴 수도 있음.


3. 크기를 키우거나, 더 많은 다각형을 쉽게 그리고 놀 수 있게 하자 - 그림판의 기초


4. 이것으로 물건을 부수고, 중력 효과 등을 넣어보자 - 앵그리버드의 기초



등등 여러분은 모든 것을 할 수 있습니다. 대신 좋은 아이디어를 낼 사람, 그림을 그릴 사람 등 많은 사람이 필요로 합니다.



실제로 이 draw() 기능을 어떻게 활용하는지 궁금하시다면 여기로 가주세요.





결론: 창의력이 가장 중요하다. 하지만 인맥이 없으면, 이 또한 부질 없다. 코딩 실력은 그저 부수적인 것일 뿐이다.



감사합니다. 이만 줄이겠습니다.

어제 KTX 타고 집에 오는 길에 드디어 별지도 앱을 다 만들었다. 역시 코딩은 KTX 에서 긴급함 속에서 만드는 것이 가장 잘 된다.



별지도에 있는 좌표값들을 계산하는 방법이 바로 떠오르진 않았다. 어떤 별들은 보이면 안되는데, 보이는 별들과 안보이는 별들을 어떻게 구별할까... 부터 시작해서 한번에 모든 걸 하려고 하니깐 딱히 좋은 생각이 나오지 않았다.


그래서 저번에 그냥 포기하고 한 단계씩 다 코드로 구현하기로 마음 먹은 후, 진짜 그렇게 했다. 3D 로 옮기고, 회전 행렬로 돌리고, 위도를 위해서 다시 행렬로 돌리고, 고도 일정 이하 애들은 다 -1 고정값으로 바꿔버리고, 고도 일정 이하 애들만 다시 2D로 사영하고.


했더니 딱 2시간 일꺼리였나 보다. GameView 만들고, Thread 만들고 이것저것 하니, 딱 행신역 도착하니깐 다 만들었다. 근데 집에 오면서 실행해 보니, 북극성이 아랫쪽에 있어서 그것만 방향 제대로 맞춰줬다.


중간에 가지고 있는 별 데이터가 내가 알고 있는 것과 반대 순서여서 (적위, 적경) v/s (적경, 적위) 별 데이터 뽑아준 친구한테 잔소리 좀 했다.



하지만, 결과적으로는 다 잘 돌아간다. 아직 별이 너무 적고, 등급을 적용시키지 않아서 그런지 물고기 자리는 똷하고 존재감을 내세우는데 정작 카시오페이아는 별 하나가 없다... 빨리 마저 완성해야겠다.



** 이는 조만간 혼천의 프로젝트에 게시될 부분입니다.

'잡다한 생각' 카테고리의 다른 글

글 작성  (0) 2018.10.24
속도  (0) 2018.04.17
대통령 장학금  (0) 2018.04.13
대통령 장학금  (0) 2018.03.20
윈도우 업데이트  (0) 2018.02.04

안녕하세요. 호랑인 입니다.


오늘은 거의 모든 안드로이드 게임을 만드는 데에 필요한 부분인 GameView 만들기와 Thread 생성을 끝맞췄습니다.


이 다음부터는 쉽습니다. 그저 어떤 게임을 만들고 싶은지에 대한 상상력과 창의력이 중요한 단계에 이르렀습니다. 우선, Slide 게임을 마저 만들 것이고요, 한 단계만 더 늘려서 제가 말하는 "창의력이 더 중요하다" 는 말이 무엇인가를 조금 더 말씀드리겠습니다.



바로가기

안녕하세요. 호랑인 입니다.


오늘은 Slide 라고, 제가 KTX 기다리는 동안 심심해서 만들기 시작해서 KTX 내에서 갖고 논, 아주아주 간단한 (게임이라고 부르기도 애매한) 앱을 만든 과정을 소개시켜드리려고 합니다.


사실 과정의 대부분은 안드로이드에서 게임을 개발할 때마다 매번 반복해야 하는 과정이기 때문에, 어려워 보이더라도, 우선 맥락을 이해하는데에 초점을 두고 보시기 바랍니다.



우선, Slide 는 빨간색 사각형이 움직이면서 모서리에 맞으면 튕겨지는, 매우 간단한 게임입니다. 바로 시작해봅시다.



안드로이드 스튜디오 (Android Studio) 에서 새로운 프로젝트를 시작하시고, Empty Activity 를 선택하시기 바랍니다.


그리고 아래와 같이 바꾸시면 됩니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package // 자신만의 package 정보를 넣어주시면 됩니다.;
 
import android.app.Activity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
 
public class MainActivity extends Activity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
 
        setContentView(new GameView(this));
    }
}
cs



중점을 두셔야 할 부분을 말씀드리겠습니다.


1. MainActivity 가 이제 Activity 에서 상속 받습니다.


2. getWindow() ... 부분은 잘 보시면 전체화면으로 설정하는 부분입니다.


3. this.requestWindowFeature ... 부분은 앱을 만들어보시면 알겠지만, 항상 제일 상단에 파란색(색상은 변경 가능) 띠가 있어, 앱의 이름을 적어놓는다는 것을 볼 수 있습니다. 게임을 할 때에는 이것이 필요가 없으므로, NO_TITLE 을 사용하는 것입니다.


4. setContextView ... 부분은 이제 게임 화면을 만들기 위한 부분입니다. 당장은 GameView 라는 class 를 정의한 적이 없으므로, 빨간 줄이 뜰 것입니다.



한 가지 팁을 드리자면, 위에 있는 import 부분은 굳이 옮기지 않으셔도 됩니다. import 부분을 제외하고 복사 붙여넣기를 하시면, 알아서 Android Studio가 import 를 해줄 것입니다. GameView 는 새로운 class 를 만들어야 하는 것이기 때문에 어쩔 수가 없고요.





이제 GameView 라는 java file을 만들어봅시다. MainActivity.java과 같은 폴더 안에 생성하시면 됩니다. superclass 를 미리 설정해주시고 싶다면, GameView의 superclass 는 android.view.SurfaceView 이고, interface는 android.view.SurfaceHolder.Callback 을 넣으시면 됩니다.


그리고 MainThread 라는 java flie도 만듭시다. 다만, MainThread는 superclass 가 Thread (java.lang) 입니다.



이 둘읭 역할은 사뭇 당연한데, MainThread는 다음 화면을 만드는데에 필요한 계산과 화면을 그리는 부분은 담당합니다. 그리고 결과적으로 GameView class 안에 있는 draw() 라는 함수를 실행시킵니다. 그러면 GameView 의 화면이 갱신되게 되고, 이는 MainActivity 에서 setContextView 로 설정해 놓은 것이기 때문에, 저희 화면에 출력되게 됩니다.




GameView 에 들어가, 아래의 것들을 모두 넣읍시다.




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private MainThread thread;
 
public GameView(Context context) {
    super(context);
 
    getHolder().addCallback(this);
}
 
@Override
public void surfaceChanged(SurfaceHolder holder, int formatint width, int height) {
 
}
 
@Override
public void surfaceCreated(SurfaceHolder holder) {
 
}
 
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
 
}
cs



1. 우선, MainThread를 하나 만들었습니다. 이것을 통해 thread 에 대한 접근을 할 것입니다.


2. getHolder().addCallback(this) 는 이 화면에서 Event 처리를 가능하게 해주는 부분입니다.


3. 나머지는 Override 하는 부분입니다. 많이 해보셨을 것이라 생각합니다. 우선 당장은 해줄 것이 없습니다. 조금 있다가 surfaceDestroyed 는 고쳐야 하는데, 파괴된 이후에는 thread가 더이상 계산을 진행할 필요가 없기 때문에, 이를 꺼줘야 하기 때문입니다.




이제 가장 중요한, thread 를 만들어야 합니다. 우선, 완성본을 보여드리겠습니다.



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
import android.graphics.Canvas;
import android.view.SurfaceHolder;
 
public class MainThread extends Thread {
 
    private SurfaceHolder surfaceholder;
    private GameView gameview;
    private boolean running;
    public static Canvas canvas;
 
    public int VIEW_WIDTH;
    public int VIEW_HEIGHT;
 
    public MainThread (SurfaceHolder surfaceholder, GameView gameview){
 
        super();
        this.surfaceholder = surfaceholder;
        this.gameview = gameview;
        this.VIEW_WIDTH = 0;
        this.VIEW_HEIGHT = 0;
    }
 
    @Override
    public void run(){
        while(running){
            canvas = null;
 
            try{
                canvas = this.surfaceholder.lockCanvas();
                this.VIEW_WIDTH = canvas.getWidth();
                this.VIEW_HEIGHT = canvas.getHeight();
                synchronized (surfaceholder){
                    this.gameview.update(this.VIEW_WIDTH, this.VIEW_HEIGHT);
                    this.gameview.draw(canvas);
                }
            }
            catch (Exception e) {}
            finally{
                if (canvas != null){
                    try{
                        surfaceholder.unlockCanvasAndPost(canvas);
                    }catch(Exception e){
                        e.printStackTrace();
                    }
                }
            }
        }
    }
 
 
    public void setRunning(boolean isRunning){
        running = isRunning;
    }
}
cs



import를 무시하시고요, 제일 위에 package... 부분은 본래 있는 그대로 놔두세요.


천천히 뜯어 봅시다.


1. super(); 상위 클라스인 Thread의 생성 함수를 실행합니다.


2. surfaceholder 와 gameview object를 하나씩 선언했고요, VIEW_HEIGHT 는 나중에 필요해서 그냥 제 마음대로 선언한 겁니다.


3. 보면, 제일 밑에 setRunning 이라는 아이가 있습니다. 이것이 위에서 말했듯이, surfaceCreated 나 surfaceDestroyed 에서 사용될 예정입니다. thread를 끄고 켜는 부분이죠. running 이라는 bool 변수가 이를 조절합니다.


4. run() 이라는 커다란 함수가 보입니다. 아마 threading 보신 적이 있으신 분들은, 예상외로 별 얘기는 없다는 것을 아실 수 있을 것입니다.


5. while (running), 즉 running 변수가 열심히 일하는 것을 볼 수 있죠.


6. 다음에 있는 try 문이 사실 상 전부입니다. 우선, canvas는 lock 되어 있지 않으면, 그곳에 새로운 것을 그릴 수가 없습니다. 따라서, lockCanvas를 하고, 이 변수를 미리 null 로 초기화되어 있는 canvas에 저장합니다. VIEW_WIDTH 는 canvas.getWidth() 로 얻을 수 있습니다.


7. synchronized 가 있는데, 이는 한번에 오직 하나의 thread만 이 작업을 수행할 수 있다는 것입니다. 예를 들어 두개의 thread가 이를 관할하고 있는데, 한번에 하나라는 규칙을 주지 않으면, 같은 작업을 두번 하여 다음 작업이 뛰어 넘어가는 등의 일이 일어날 수 있습니다. 예를 들어, 두명이서 땅을 파고 있다 해봅시다. 그들에게 주어진 것은 (i 번째 땅을 파고), (i 에 1을 더해라) 라 하면, 1번 사람이 1번째 땅을 다 파는 동안에 2번이 2번째 땅을 다 못 팠으면, i 는 아직도 2이기 때문에 1번 사람도 2번째 땅을 파기 시작한다는 것입니다.


8. synchronized 안에 가장 중요한 문장인 update() 와 draw() 를 실행시키는 구문이 있습니다. 이것은 당장은 빨간 줄이 뜰 것입니다. 아직 만들지 않았기 때문이죠. 이제 바로 다시 만들러 갈 것입니다.


9. 그 뒤에 catch 부분은 제대로 실행되지 않았을 때 처리하는 부분입니다. 이들은 저 함수 이름을 google 에 치면 자세한 설명이 나오니, 참고하시기 바랍니다. 당장은 그리 중요하지 않아 넘어가도록 하겠습니다.




다시 GameView 로 돌아갑시다. 그리고 아래와 같이 고칠 것입니다.


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
public GameView(Context context) {
    super(context);
 
    getHolder().addCallback(this);
    thread = new MainThread(getHolder(), this);
    setFocusable(true);
}
 
@Override
public void surfaceCreated(SurfaceHolder holder) {
    thread.setRunning(true);
    thread.start();
 
}
 
@Override
public void surfaceDestroyed(SurfaceHolder holder) {   
    boolean retry = true;   
    while (retry) {       
        try {           
            thread.setRunning(false);           
            thread.join();              
        } catch (InterruptedException e) {       
            e.printStackTrace();   
        }   
        retry = false;
    }
}
 
public void update(int height, int width) {
 
}
 
public void draw(){
 
}
cs



1. 우선, thread 를 만들었습니다.


2. 말했던 데로, SurfaceCreated 되면, thread를 돌립니다.


3. surfaceDestroyed 부분은 thread를 멈추는 부분입니다. 멈추지 않으면, 계속 멈추는 겁니다.


4. update와 draw를 만들었습니다. update는 VIEW_HEIGHT와 WIDTH 가 들어올 수 있게, int input 두개를 넣었습니다.




이제 실행 시켜보세요. 검은 화면이 나올겁니다.  혹시 그렇지 않다면, 댓글 부탁드립니다.


축하드립니다! 이제 당신은 모든 종류의 Game 을 만들 수 있게 되었습니다. 이 다음 과정부터는 코딩 능력보다도 창의력이 중요합니다. 다음 게시글로 찾아뵙겠습니다.





+ Recent posts