하이 라이트된 37: 라인처럼 PrepareOrientationSensor() 메소드를 호출하여 센서를 준비하도록 하면 됩니다. 센서에 대한 구현 코드는 JoyStick 클래스 내부에 캡슐화되어 있기 때문에 더 이상 신경 쓸 것이 없습니다.
실제 게임은 이 코드를 이용하여 작성해 나가도록 하겠습니다.
JoyStick 클래스 작성
이제 핵심 주인공인 JoyStick 클래스를 구현해보도록 하겠습니다. 우선 키보드 이벤트, 모션 이벤트 그리고 센서의 처리가 게임 마다 그 구현이 달라질 수 있을 것이라 예상이 됩니다. 따라서, 달라지는 부분과 달라지지 않는 부분을 [그림 3]과 같이 분리하도록 하겠습니다.
즉, 각각의 이벤트 처리부분만 다른 클래스로 떼내는 작업입니다.
[그림 3] 변하지 않는 인터페이스 부분과 변동이 예상되는 구현 부분을 분리
그리고, 실제 구현은 [소스 4]와 [소스 5]와 같습니다. 지금까지 설명되지 않은 것들이 다소 섞여 있습니다. 설명을 간략하게 하기 위해서, 중요한 것만을 간추려서 설명했기 때문입니다.
또한, 지금 설명한 모든 이벤트 처리가 모두 구현되어 있습니다. 그리고, 그 중에서 실제 사용하는 것은 방향 센서 뿐임을 기억하시기 바랍니다. 여러분들이 키보드 이벤트를 적용하고 싶다면, 위에서 설명한 대로 Ship.java의 소스만 수정하시면 됩니다.
[소스 4] JoyStickInterface.java
package app.main;
public abstract class JoyStickInterface {
public JoyStickInterface() {
super();
}
public JoyStickInterface(int speed) {
super();
_Scroll.setSpeed(speed);
}
protected int _X = 0;
protected int _Y = 0;
private int _LeftLimit = 0;
private int _TopLimit = 0;
private int _RightLimit = 0;
private int _BottomLimit = 0;
private int _ObjectWidth = 0;
private int _ObjectHeight = 0;
private boolean _UseBoundaryLimit = false;
protected int _DX = 0;
protected int _DY = 0;
private Scroll _Scroll = new Scroll();
public void setBoundaryLimit(boolean useBoundaryLimit,
int leftLimit, int topLimit,
int rightLimit, int bottomLimit,
int objectWidth, int objectHeight) {
_UseBoundaryLimit = useBoundaryLimit;
_LeftLimit = leftLimit;
_TopLimit = topLimit;
_RightLimit = rightLimit;
_BottomLimit = bottomLimit;
_ObjectWidth = objectWidth;
_ObjectHeight = objectHeight;
}
public void Tick(long tick) {
int temp = _Scroll.Move(tick);
_X = _X + (_DX * temp);
_Y = _Y + (_DY * temp);
if (_UseBoundaryLimit == false) return;
if (_X < _LeftLimit) {
_X = _LeftLimit;
} else if (_X > (_RightLimit-_ObjectWidth)) {
_X = _RightLimit-_ObjectWidth;
}
if (_Y < _TopLimit) {
_Y = _TopLimit;
} else if (_Y > (_BottomLimit-_ObjectHeight)) {
_Y = _BottomLimit-_ObjectHeight;
}
}
public int getX() {
return _X;
}
public void setX(int value) {
_X = value;
}
public int getY() {
return _Y;
}
public void setY(int value) {
_Y = value;
}
public int getSpeed() {
return _Scroll.getSpeed();
}
public void setSpeed(int value) {
_Scroll.setSpeed(value);
}
public void setLeftLimit(int value) {
this._LeftLimit = value;
}
public int getLeftLimit() {
return _LeftLimit;
}
public void setTopLimit(int value) {
this._TopLimit = value;
}
public int getTopLimit() {
return _TopLimit;
}
public void setRightLimit(int value) {
this._RightLimit = value;
}
public int getRightLimit() {
return _RightLimit;
}
public void setBottomLimit(int value) {
this._BottomLimit = value;
}
public int getBottomLimit() {
return _BottomLimit;
}
public void setObjectWidth(int value) {
this._ObjectWidth = value;
}
public int getObjectWidth() {
return _ObjectWidth;
}
public void setObjectHeight(int value) {
this._ObjectHeight = value;
}
public int getObjectHeight() {
return _ObjectHeight;
}
public void setUseBoundaryLimit(boolean value) {
this._UseBoundaryLimit = value;
}
public boolean isUseBoundaryLimit() {
return _UseBoundaryLimit;
}
}
15-16: 객체를 표시 할 (X, Y) 좌표 입니다.
18-21: 객체가 벗어 날 수 없는 제한 공간 지정입니다. (주로 화면 크기)
23-24: 객체의 가로/세로 크기 입니다.
26: 객체의 제한 공간을 사용 할 것인지를 나타냅니다.
28-29: 객체가 움직이는 방향을 표시합니다. 현재 2D 게임을 만들고 있기 때문에, 두 개의 변수만이 필요합니다. (미분의 dx, dy 를 생각해서 작명하였습니다) 부호만 결정하면 되기 때문에 크기는 항상 1 또는 0 입니다.
48-50: 속도에 맞춰서 객체의 위치 좌표를 변경합니다. 이때, 방향을 알리는 _DX, _DY를 곱해서 더합니다.
52: 제한 공간을 사용하지 않는다면 다음 코드들을 무시합니다.
54-64: 객체가 제한 공간을 넘어서면 한계 값으로 변경하여, 벗어나지 못하도록 합니다.
[소스 5] JoyStick.java
package app.main;
import ryulib.game.GamePlatformInfo;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.view.KeyEvent;
import android.view.MotionEvent;
public class JoyStick extends JoyStickInterface implements SensorEventListener {
private static final int _ANGLE_LIMIT = 10;
public JoyStick() {
super();
}
public JoyStick(int speed) {
super(speed);
}
public void PrepareOrientationSensor(Context context) {
SensorManager _SensorManager;
Sensor _Sensor;
_SensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
_Sensor = _SensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
if (_Sensor != null) {
_SensorManager.registerListener(this, _Sensor, SensorManager.SENSOR_DELAY_GAME);
}
}
private int _TargetY = 0;
private boolean _IsMoving = false;
@Override
public void Tick(long tick) {
super.Tick(tick);
if (_IsMoving) {
if ((_DY < 0) && (_Y <= _TargetY)) {
_IsMoving = false;
_DY = 0;
_Y = _TargetY;
}
if ((_DY > 0) && (_Y >= _TargetY)) {
_IsMoving = false;
_DY = 0;
_Y = _TargetY;
}
}
}
public void onKeyDown(GamePlatformInfo platformInfo, int keyCode, KeyEvent msg) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_LEFT: _DX = -1; break;
case KeyEvent.KEYCODE_DPAD_RIGHT: _DX = +1; break;
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_Q: _DY = -1; break;
case KeyEvent.KEYCODE_DPAD_DOWN:
case KeyEvent.KEYCODE_W: _DY = +1; break;
}
}
public void onKeyUp(GamePlatformInfo platformInfo, int keyCode, KeyEvent msg) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_LEFT: if (_DX == -1) _DX =0; break;
case KeyEvent.KEYCODE_DPAD_RIGHT: if (_DX == +1) _DX =0; break;
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_Q: if (_DY == -1) _DY =0; break;
case KeyEvent.KEYCODE_DPAD_DOWN:
case KeyEvent.KEYCODE_W: if (_DY == +1) _DY =0; break;
}
}
public void onTouchEvent(GamePlatformInfo platformInfo, MotionEvent event) {
_TargetY = (int) event.getY();
_IsMoving = true;
if (_TargetY > _Y) _DY = 1;
else if (_TargetY < _Y) _DY = -1;
else _IsMoving = false;
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
@Override
public void onSensorChanged(SensorEvent event) {
int x = (int) event.values[SensorManager.DATA_Y];
int y = (int) event.values[SensorManager.DATA_Z];
if (x < -_ANGLE_LIMIT) _DX = -1;
else if (x > _ANGLE_LIMIT) _DX = 1;
else _DX = 0;
if (y < -_ANGLE_LIMIT) _DY = 1;
else if (y > _ANGLE_LIMIT) _DY = -1;
else _DY = 0;
}
}
14: 방향 센서가 얼마나 기울어져야 인식하는 가를 나타냅니다. 최소 10도 정도 기울여야만 인식합니다.
26-36: 방향 센서를 사용하겠다는 선언입니다. 추후 취소 메소드를 추가 할 예정입니다.
38: 앞에서 설명한 모션 이벤트를 통해서 어디까지 이동해야 하는 지를 저장합니다. Y 축으로만 움직이기 때문에 하나의 변수만 선언하였습니다. 눌러진 곳으로 바로 우주선을 이동하면 순간 이동처럼 보이기 때문에 목표를 저장해 두고, 목표를 향해서 속도만큼씩 접근하는 방법을 사용합니다.
39: 목표를 향해서 움직이고 있는 중인지 여부를 나타냅니다.
41-58: 모션 이벤트를 통해서 이동 할 때의 처리를 추가하기 위해서 부모 클래스의 Tick() 메소드를 재정의 하고 있습니다.
45-57: 모션 이벤트를 통해서 이동 중일 때만 실행합니다.
46: _DY가 음수 즉, 위로 이동 중이면서, 목표보다 멀리 갔거나 같을 경우에는 47-49: 라인을 실행합니다. 즉, 더 이상 이동 중이 아님을 선언하고, _Y를 목표치로 지정합니다.
52-56: 반대로 아래 쪽으로 움직일 경우도 유사하게 처리합니다.
60-72: 방향키가 눌러졌을 때, (_DX, _DY) 값을 변경하여 방향을 설정합니다. 위/아래 방향키의 경우에는 Q와 W키가 눌러졌을 때도 반응하게 합니다.
74-86: 키가 눌려졌을 때의 지정을 취소합니다. 키마다 개별적으로 지정하고 취소해야 합니다. 키를 여러 개 중복해서 누를 수가 있기 때문입니다. 예를 들어 위쪽과 오른쪽을 함께 누르면 대각선 방향으로 움직여야 합니다.
88-95: 모션 이벤트 처리 부분입니다. 목표 위치가 현재 위치보다 크냐 작으냐를 통해서 방향을 설정하고 있습니다.
103-114: 센서의 처리 과정입니다. 게임이 가로로 표시되기 때문에 좌표가 서로 얼갈려 있음을 유의하시기 바랍니다. (DATA_Y-->x, DATA_Z --> y)
정리
여기까지 이동에 관한 세 가지 방법을 살펴보고 적용해 봤습니다. 다음에는 충돌 테스트를 통해서 우주선과 소행선이 서로 부디쳤을 경우를 구현 해보도록 하겠습니다.