Hatena::ブログ(Diary)

Hetの日記

2011-10-27

jMonkeyEngine第20回

※10/30イテレータ利用のfor文をforeach文に修正。

今回は画面内モデルの取得です。バイオハザード4&5でのアイテム取得や、ゴッドオブウォーの敵掴み、CIVのユニット移動などの動作をやりたい時に使います。

Mainクラスです。

import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.*;
import com.jme3.material.Material;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.*;
import com.jme3.scene.shape.Box;

public class Main extends SimpleApplication  implements ActionListener {
    BulletAppState bas; MyScene scene; Material mat;
    
    public static void main(String[] args) {
        Main app = new Main();
        app.start();
    }

    @Override
    public void simpleInitApp() {
        bas = new BulletAppState();
        getStateManager().attach(bas);
        
        scene = new MyScene();
        scene.simpleInitApp(this);
        
        mat = assetManager.loadMaterial("Materials/miku.j3m");
        for (int i = 0; i < 3; ++i) {
            Geometry box = new Geometry("box", new Box(0.25f, 0.25f, 0.25f));
            box.setMaterial(mat);
            box.setLocalTranslation(i, 5f, 5f);
            box.addControl(new RigidBodyControl(1f));
            rootNode.attachChild(box);
            bas.getPhysicsSpace().add(box);
        }
        
        inputManager.addMapping("pick", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
        inputManager.addListener(this, "pick");
    }
    
    public void onAction(String name, boolean keyPressed, float tpf) {
        if (name.equals("pick") && !keyPressed) {
            CollisionResults results = new CollisionResults();
            
            //Using Fixed Crosshairs.
            Ray ray = new Ray(cam.getLocation(), cam.getDirection());
            
            //Using Character.
//            Vector3f pos = scene.getPlayer().getWorldTranslation().clone();
//            pos.y -= 1f;
//            Ray ray = new Ray(pos,
//                    scene.getPlayer().getWorldRotation().getRotationColumn(2));
            
            //Using the Mouse Pointer.
//            Vector2f cp2 = inputManager.getCursorPosition().clone();
//            Vector3f cp3 = cam.getWorldCoordinates(cp2.clone(), 0f);
//            Vector3f dir = cam.getWorldCoordinates(cp2, 1f).subtractLocal(cp3);
//            Ray ray = new Ray(cp3, dir);
            
            rootNode.collideWith(ray, results);
            
            for (CollisionResult cr: results) {
                if (cr.getGeometry().getName().equals("box") &&
                        (cr.getDistance() < 2f)) {
                    Vector3f pt = cr.getContactPoint();
                    bas.getPhysicsSpace().remove(cr.getGeometry());
                    cr.getGeometry().removeFromParent();
                    scene.texter.setText("箱入手_" + pt);
                    break;
                }
            }
        }
    }
    
    @Override
    public void simpleUpdate(float tpf) {
        //TODO: add update code
    }

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }
}

CollisionResults(&CollisionResult)とRayというクラスを利用します。Rayは光線や放射線という意味です。取得の仕方ですが、ある位置から放射線を出してそれに接触したモデルを全て取得するという形になります。取得したモデルが入っているのがCollisionResultsです。

//Using Fixed Crosshairs.
Ray ray = new Ray(cam.getLocation(), cam.getDirection());

この部分はカメラの中心から放射線を出してます。ですので取得するのは画面中心のモデルになります。Rayの最初の引数放射線の放射開始位置、2番目の引数が角度になります。FPSでのアイテム入手がこのタイプですね。

//Using Character.
Vector3f pos = scene.getPlayer().getWorldTranslation().clone();
pos.y -= 1f;
Ray ray = new Ray(pos,
        scene.getPlayer().getWorldRotation().getRotationColumn(2));

上の文をコメントアウトしてこちらを使うと、キャラクターの前にあるモデルを取得します。3Dアクションなどがこのタイプです。posのyを1引いてるのは低い位置にある対象を取る為です。yはキャラクターの高さに応じて設定して下さい。

//Using the Mouse Pointer.
Vector2f cp2 = inputManager.getCursorPosition().clone();
Vector3f cp3 = cam.getWorldCoordinates(cp2.clone(), 0f);
Vector3f dir = cam.getWorldCoordinates(cp2, 1f).subtractLocal(cp3);
Ray ray = new Ray(cp3, dir);

上の2つをコメントアウトしてこちらを使うと、マウスポインタの位置にあるモデルを取得します。RTSのユニット選択などがコレです。

rootNode.collideWith(ray, results);

この文でモデルの取得を行っています。

for (CollisionResult cr : results) {
    if (cr.getGeometry().getName().equals("box") &&
            (cr.getDistance() < 2f)) {
        Vector3f pt = cr.getContactPoint();
        bas.getPhysicsSpace().remove(cr.getGeometry());
        cr.getGeometry().removeFromParent();
        scene.texter.setText("箱入手_" + pt);
        break;
    }
}

CollisionResultsはCollisionResultのリストになってます。近いものから順に収められています。ですのでforeach(for)で順にアクセスして、近い箱があったら画面から消して「箱入手」とテキストを表示する様にしています。CollisionResult#getGeometry()でモデルを、getDistance()でモデルまでの距離を、getContactPoint()で接触した点を取得できます。取得するのがNodeではなく、Geometryな点に注意して下さい。複数のGeometryが組み合わさったNodeですと上手く取得出来ない恐れがあります(腕だけ取得してしまうなど)。

実行してみます。

f:id:Het:20111027215448j:image:w640

クリックすると、画面中央の箱を入手し画面から箱が消えます。

f:id:Het:20111027215449j:image:w640

chototsuchototsu 2011/10/29 09:30 普通はfor(CollisionResult cr : results) { ....}って書くと思います。

HetHet 2011/10/30 22:49 すいません。コメントに今気が付きました。修正しました。ご指摘有難う御座いました。