今回はAndengineの物理演算用のライブラリを使用してみようと思います。
これを使用すると、物体が重力の影響を受けて落下・加速、バウンドしたりする動きを特別な処理をする必要なく行ってくれます。
物理エンジンを適用するSceneを作成
まずは、Sceneに重力エリアの設定をします。
[java]
MainScene.Java
private PhysicsWorld physicsWorld;
@Override
public void init() {
this.physicsWorld = new PhysicsWorld(new Vector2(0, 0), false);
this.registerUpdateHandler(this.physicsWorld);
}
[/java]
これだけ。
PhysicsWorldのインスタンスを生成して、registerUpdateHandlerにセットするだけです。
>new PhysicsWorld(new Vector2(0, 0), false);
ここでセットしているVector2の値で重力を設定できます。
例えば
・Vector2(0, 9.8f)
Y軸に9.8設定をすると、地球と同じような重力が働きます。
・さらにVector2(2.0f, 9.8f)
上記のようにすると、x軸にも重力が加わり、Y軸に落下しつつも、X軸にも動くので”風”のような力が加わるような動きができます。
※今回は、無重力にしたいため、X・Y軸ともに0を指定しています。
上下左右に壁を作成
壁がないと、重力の影響をうけて動いた物体が画面端まで移動しても止まってくれないため、画面外にいってしまいます。
なので、壁を作成します。
[java]
MainScene.Java
@Override
public void init() {
・・・省略
final VertexBufferObjectManager vertexBufferObjectManager = getBaseActivity().getVertexBufferObjectManager();
final Rectangle ground = new Rectangle(0, camera.getHeight() – 2, camera.getWidth(), 2, vertexBufferObjectManager);
final Rectangle roof = new Rectangle(0, 0, camera.getWidth(), 2, vertexBufferObjectManager);
final Rectangle left = new Rectangle(0, 0, 2, camera.getHeight(), vertexBufferObjectManager);
final Rectangle right = new Rectangle(camera.getWidth() – 2, 0, 2, camera.getHeight(), vertexBufferObjectManager);
final FixtureDef wallFixtureDef = PhysicsFactory.createFixtureDef(0, 0.8f, 0.1f);
PhysicsFactory.createBoxBody(this.mPhysicsWorld, ground, BodyDef.BodyType.StaticBody, wallFixtureDef);
PhysicsFactory.createBoxBody(this.mPhysicsWorld, roof, BodyDef.BodyType.StaticBody, wallFixtureDef);
PhysicsFactory.createBoxBody(this.mPhysicsWorld, left, BodyDef.BodyType.StaticBody, wallFixtureDef);
PhysicsFactory.createBoxBody(this.mPhysicsWorld, right, BodyDef.BodyType.StaticBody, wallFixtureDef);
this.attachChild(ground);
this.attachChild(roof);
this.attachChild(left);
this.attachChild(right);
}
[/java]
>final Rectangle ground = new Rectangle(0, camera.getHeight() – 2, camera.getWidth(), 2, vertexBufferObjectManager);
これで画面下部の地面に当たる部分の線を生成します。
引数1:X軸のポジション
引数2:Y軸のポジション
引数3:図形のX軸の長さ
引数4:図形のY軸の長さ
引数5:よく分からないのでおまじない的な。。
>PhysicsFactory.createBoxBody(this.physicsWorld, ground, BodyDef.BodyType.StaticBody, wallFixtureDef);
これを設定しないとattachChildで画面に表示させたとしても、物体との衝突が発生しないですり抜けてしまいます。
引数1:生成したメンバ変数に保持しているPhysicsWorld
引数2:対象の物体(RectangleでもSpriteでもOK)
引数3:図形に持たせる特性。
・BodyDef.BodyType.StaticBody:重力の影響を受けないで画面上に固定で表示される。今回は壁は動かないのでこれを使用します。
・BodyDef.BodyType.KinematicBody:重力の影響は受けないが、プログラム上の制御により移動する。(コントローラによる操作や、一定速度で動く物体)
・BodyDef.BodyType.DynamicBody:重力の影響を受けて移動する。
引数4:物体の密度、弾力性、摩擦の設定。
・PhysicsFactory.createFixtureDef(0, 0.8f, 0.1f)で生成した値を設定する。
>this.attachChild(ground);
画面上にセットする。
これをしないと画面に表示されません。
重力の影響を受ける物体を作成
重力エリアと壁を作成したので、次は画面に重力の影響を受ける物体を表示させます。
壁の作成した手順と同じように物体を作成して、それを設定します。
今回はボールの絵を使用したSpriteを使用します。
[java]
MainScene.Java
private Body ballBody;
private Sprite ballSprite;
@Override
public void init() {
・・・省略
// ボールの設定
addBall();
}
private void addBall() {
ballSprite = ResourceUtil.getInstance(getBaseActivity()).getSprite("ball.png");
ballSprite.setPosition(camera.getWidth() / 2, camera.getHeight() – camera.getHeight() / 3);
FixtureDef ballFixtureDef = PhysicsFactory.createFixtureDef(1, 0.8f, 0.2f);
ballBody = PhysicsFactory.createCircleBody(this.physicsWorld, ballSprite, BodyDef.BodyType.DynamicBody, ballFixtureDef);
this.physicsWorld.registerPhysicsConnector(new PhysicsConnector(ballSprite, ballBody, true, true));
this.attachChild(ballSprite);
}
[/java]
ボールは重力の影響を受け、移動・加速・バウンドをしてほしいので「BodyDef.BodyType.DynamicBody」を使用します。
※「PhysicsFactory.createCircleBody」で物体の形状を設定します。ボールは丸いので「createCircleBody」を使用します。
四角の場合は「createBoxBody」を使用します。
この時点でアプリを起動すると、「PhysicsWorld」に重力値をセットしている場合は、その力により
ボールが移動します。
壁までいくとぶつかってバウンドすると思います。
画面タッチ&スライドで移動処理
今回は無重力状態とするので、ボールに対して、別の物体からの衝突で力を加えるようにします。
更に、その物体を画面のタッチした時点の座標と移動させている地点の座標で移動処理を行います。
(前回のシューティングではコントローラを使用しましたが、今回は画面タッチでも移動です。)
[java]
MainScene.Java
private Body playerBody;
private AnimatedSprite playerSprite;
private float downX = 0;
private float downY = 0;
@Override
public void init() {
・・・省略
// プレイヤーの設定
addPlayer();
this.setOnSceneTouchListener(new IOnSceneTouchListener() {
@Override
public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
if (pSceneTouchEvent.getAction() == MotionEvent.ACTION_DOWN) {
downX = pSceneTouchEvent.getX();
downY = pSceneTouchEvent.getY();
}
if (pSceneTouchEvent.getAction() == MotionEvent.ACTION_MOVE) {
float[] firstXY = {downX, downY};
float[] secondXY = {pSceneTouchEvent.getX(), pSceneTouchEvent.getY()};
float[] startXY = {0, 0};
float[] endXY = {secondXY[0] – firstXY[0], secondXY[1] – firstXY[1]};
if (endXY[0] > 150) {
endXY[0] = 150;
}
if (endXY[0] < -150) {
endXY[0] = -150;
}
if (endXY[1] > 150) {
endXY[1] = 150;
}
if (endXY[1] < -150) {
endXY[1] = -150;
}
double angle = getAngleByTwoPosition(startXY, endXY);
// 移動距離と角度からx方向、y方向の移動量を求める
double distance = getDistanceTwoPosition(startXY, endXY) * 0.02f;
float x = -(float) (distance * Math
.cos(angle * Math.PI / 180.0));
float y = -(float) (distance * Math
.sin(angle * Math.PI / 180.0));
movePlayer(x, y);
}
if (pSceneTouchEvent.getAction() == MotionEvent.ACTION_UP) {
stopPlayer(0, 0);
}
return true;
}
});
}
private void addPlayer() {
final FixtureDef fixtureDef = PhysicsFactory.createFixtureDef(2, 0.2f, 0f);
playerSprite = ResourceUtil.getInstance(getBaseActivity()).getAnimatedSprite("main.png", 1, 2);
playerSprite.setPosition(camera.getWidth() / 2, camera.getHeight() – camera.getHeight() / 4);
playerSprite.setRotation(playerSprite.getRotation() + 90);
playerBody = PhysicsFactory.createCircleBody(this.physicsWorld, playerSprite, BodyDef.BodyType.KinematicBody, fixtureDef);
this.physicsWorld.registerPhysicsConnector(new PhysicsConnector(playerSprite, playerBody, true, false));
this.attachChild(playerSprite);
}
public void movePlayer(float x, float y){
final Body body = playerBody;
Vector2 velocity = null;
if (null != playerSprite) {
if(x > 0 && camera.getWidth() – 2 < playerSprite.getX() + playerSprite.getWidth()){
x = 0;
}
if(x < 0 && playerSprite.getX() < 2){
x = 0;
}
if(y > 0 && camera.getHeight() – 2 < playerSprite.getY() + playerSprite.getHeight()){
y = 0;
}
if(y < 0 && playerSprite.getY() < 2){
y = 0;
}
velocity = Vector2Pool.obtain(x * 5, y * 5);
}
body.setLinearVelocity(velocity);
Vector2Pool.recycle(velocity);
final float rotationInRad = (float)Math.atan2(-x, y);
playerBody.setTransform(playerBody.getWorldCenter(), rotationInRad);
playerSprite.setRotation(MathUtils.radToDeg(rotationInRad – 100));
}
public void stopPlayer(float x, float y){
final Body body = playerBody;
Vector2 velocity = null;
if (null != playerSprite) {
velocity = Vector2Pool.obtain(x, y);
}
body.setLinearVelocity(velocity);
Vector2Pool.recycle(velocity);
final float rotationInRad = (float)Math.atan2(-x, y);
playerBody.setTransform(playerBody.getWorldCenter(), rotationInRad);
playerSprite.setRotation(MathUtils.radToDeg(rotationInRad + 90));
}
// 2点間の角度を求める
private static double getAngleByTwoPosition(float[] start, float[] end) {
double result = 0;
float xDistance = end[0] – start[0];
float yDistance = end[1] – start[1];
result = Math.atan2((double) yDistance, (double) xDistance) * 180
/ Math.PI;
result += 180;
return result;
}
// 2点間の移動距離を求める
private static double getDistanceTwoPosition(float[] start, float[] end){
float xDistance = end[0] – start[0];
float yDistance = end[1] – start[1];
double distance = Math.sqrt(Math.pow(xDistance, 2)
+ Math.pow(yDistance, 2));
return distance;
}
[/java]
>addPlayer()
ボールと同じようにSpriteを設定します。
画面操作により移動させたいので「BodyDef.BodyType.KinematicBody」を利用します。
>this.setOnSceneTouchListener(new IOnSceneTouchListener()
画面のタップイベントのリスナーを登録します。
・画面がタッチされた時の処理
【MotionEvent.ACTION_DOWN】:タッチした時点の座標を保持します。
・画面がタッチ後移動された時の処理
【MotionEvent.ACTION_MOVE】:
>if (endXY[0] > 150) {
タッチした時点とタッチ後に移動された地点の差が150以上であれば、150固定とする。
※これは移動対象のPlayerが異様に早く移動しないようにするため、移動距離判定の値の上限を設けてます。
>getAngleByTwoPosition
移動距離を算出するための角度を算出します。
>getDistanceTwoPosition
2点間の座標から移動距離を算出します。
>movePlayer
算出したX,Yの移動量を元にPlayerを移動します。
>if(x > 0 && camera.getWidth() – 2 < playerSprite.getX() + playerSprite.getWidth()){
端まで来た時に壁をすり抜けないように、x軸が端まできたらx軸の移動量を0とします。
※Y軸も同じ。
>velocity = Vector2Pool.obtain(x * 5, y * 5);
端まできてない場合は、移動量を元に移動(*5の部分は実際の画面の移動をみて調整する。)
>final float rotationInRad = (float)Math.atan2(-x, y);
>playerBody.setTransform(playerBody.getWorldCenter(), rotationInRad);
>playerSprite.setRotation(MathUtils.radToDeg(rotationInRad + 100));
移動する方向から画像の向きを変更するための角度を求めてセットします。
・画面から指が離されたときの処理MotionEvent.ACTION_UP
この処理をしないと指を離しても直前の移動距離にしたがって壁を突き抜けて移動してしまった。。。
ので、移動を終了するための処理を実装。
>stopPlayer
「movePlayer」と同じような感じですが、移動量を{0,0}にして移動をとめます。
まとめ
これで、無重力状態の画面で、ボールがPlayerの衝突により跳ね飛ばされる動きの実装ができました。
まだ、ボールの跳ね返り具合や、移動速度などの問題があるので、そこら辺は今後微調整します。