search
LoginSignup
19

More than 5 years have passed since last update.

posted at

updated at

100行で始める、2D Game 用 Physicsエンジン の自作 @Dart

Dartを使って、一緒にPhysicsエンジンを自作してみましょう。Physicsエンジンを利用することで、複雑でリアリティのある動きをするアプリを作成する事ができるようになります。

複雑な動作をするので、一見、難しい事をしているかのようですが、そんな事はありません。大変ではあるのでずが...。

本文では、Physicsエンジンで対象とするPrimitiveをCircleに
絞る事で問題を単純化しました。さらっと実装してみましょう。

100行くらいかくだけで、物理っぽい見た目を実現できる事を理解してもらえれば幸いです。

こんな感じで

about.png

WebGL x Html5 x Dart でのデモ

クリックすると重力の向きが変わります。




Physicsでは、エネルギーを扱う。

Physicsでは、エネルギーの変異を扱います。このエネルギーは「巨大な物体が空間を歪ませる」事によって重力に変わったり。物体同士が衝突する事によって、熱やその物体の速度に変化していきます。

本文で扱うもエネルギーの変換先は以下です。

class CirclePrimitive {
  Vector3 xy = new Vector3.zero(); //現在位置
  Vector3 dxy = new Vector3(0.0, 0.0, 0.0); //速度
  double mass = 1.0; // 重さ
  double radius = 10.0; //半径
  bool isFixing = false; // 動かな物体はtrue
  double angle = 0.0; //次回
  double dangle = 0.0; //次回
}

今回は速度だけです。




Circleは扱い易い

Circleはもっとも扱いやすいPrimitiveです。
特に、Circleは、「重心の求め方」「衝突した時の当たり判定」「そのエモルギーが伝わる向き」等の計算が簡単にできます。

なので、単純な仕組みが、複雑な現象へと結びついている事を、サラッと確認するのにうってつけです。

(A)重心はいつも中央

(B) 衝突判定も簡単。ピタゴラスの定理だけで求まる

(B-1) 物体同士の重心どうしの距離

  double calcXYDistance(Primitive p) {
    double dX = math.pow(p.xy.x - this.xy.x, 2);
    double dY = math.pow(p.xy.y - this.xy.y, 2);
    double distance = math.sqrt(dX + dY);
    return distance;
  }

(B-1) 衝突判定

重心から見たCircleどうしの距離と、その半径から衝突したかが判定できます。

  bool checkCollision(Primitive p) {
    CirclePrimitive c = p;
    double distance = calcXYDistance(p);
    double boundary = this.radius + c.radius;
    if (boundary > distance) {
      return true;
    } else {
      return false;
    }
  }
(B-2) 衝突した時に衝撃が走る方向

重心に向かってエネルギーが流れていきます。Circleどうしが衝突した場合、
衝突した点と、重心どうしは必ず一直線上に存在するので計算が楽です。

  Vector3 calcXYDistanceDirection(Primitive p) {
    Vector3 vv = p.xy - this.xy;
    return vv.normalize();
  }




衝突後のエネルギーの変換

物体同士が衝突するとエネルギーが発生します。発生したエネルギーは、「物体への加速度」や熱へ変換されます。

void collision(CirclePrimitive c) {
      double distance = calcXYDistance(c);
      double boundary = this.radius + c.radius;
      Vector3 distanceDirection = calcXYDistanceDirection(p);
      Vector3 collisionDirection = calcXYDistanceDirection(p);
      Vector3 relativeSpeed = p.dxy - this.dxy;

      // e is 0-1
      // J = -(v1p- v2p) * (e+1) / (1/m1 + 1/m2)
      double bounce = 1.0;
      double j1 = -1.0 * (1.0 + bounce) * (relativeSpeed.dot(collisionDirection));
      double j2 = (1.0 / p.mass + 1.0 / this.mass);
      double j = j1 / j2;

      Vector3 p_dv = (collisionDirection * j) / p.mass;
      Vector3 t_dv = (collisionDirection * -1.0 * j) / this.mass;

      if (this.isFixing == false) {
        this.dxy += t_dv;
      }
      if (p.isFixing == false) {
        p.xy += distanceDirection * (boundary - distance + 0.1);
        p.dxy += p_dv;
      }
}




めしあがれ

全てのPrimitiveに対して、衝突判定をしているかをチェックして。衝突しているならば、その処理をする。
速度に応じて移動する。を繰り替えせば、簡易のシミュレーションができます。

class World {
  Vector3 gravity = new Vector3(0.0, -9.8 / 500.0, 0.0);
  List<Primitive> primitives = [];
  next(double time) {
    primitives.shuffle();
    for (Primitive a in primitives) {
      for (Primitive b in primitives) {
        if (a != b && a.checkCollision(b)) {
          a.collision(b);
        }
      }
      if (a.isFixing == false) {
        a.dxy.x += gravity.x;
        a.dxy.y += gravity.y;
      }
      a.next(time);
    }
  }
}

これで完成です。以下にサンプル実装を置きました。
複数のCircleがお互いに衝突したり、跳ね返ったり、重なり合ったりしてる事が見て取れます。

about.png

今後、解説しながら徐々に複雑にしていきますが、本文ではこのくらいで。




ソース

ノウハウ

以下にまとめていきます。
http://kyorohiro.github.io/umiuni2d/web/index.html




Thanks!

thanks.png

最後まで、読んでいただきまして、ありがとうございました。
(ref http://pixiv.me/kyorohiro)
(ref https://play.google.com/store/apps/details?id=info.kyorohiro.umiuni2d.demo.mino)
(ref http://kyorohiro.github.io/umiuni2d/mino2/web/main.html)



Kyorohiro Work
http://kyorohiro.strikingly.com/
http://kyorohiro.github.io/

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
19