FC2ブログ

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

ジャンプしたときの座標をVerlet法で求める

ジャンル別ゲームの作り方とアルゴリズムまとめで紹介されていた
マリオのジャンプ実装法とVerlet積分を読んだが、マリオのジャンプ実装法とVerlet積分(実践編)を読んでもVerlet法がよくわからなかった。

Verlet法について調べたところ、みその計算物理学 というすごいサイトを見つけた。
ここの Verlet法(PDF形式) を読んでVerlet法を理解できたので、ジャンプしたときの座標を求める方法について書く。

1.座標の計算方法
時刻 t における物体の座標を r(t)、物体に作用する力を f(t)、物体の質量を m とする。
時間の間隔を h とすると、次の時間の座標 r(t+h) は、現在の座標 r(t) と 前の座標 r(t-h) を用いて
  Verlet法の式1
と表すことができる。(近似式だが、等式と考えて実用上問題無い)
物体にかかる力が重力のみの場合、重力加速度を g とすると f(t)=mg のため、
  Verlet法の式2
となる。
gh^2は定数のため、初期値r(0)と1フレーム目の値r(h)が与えられれば、加減算とループのみで座標を計算して軌道を描くことができる。

自由落下の場合、t=0 のときの速度を v0 とすると
  Verlet法の式3
となるので、これからr(h)を求めることができる。
hが十分に小さければ r(h)=v0h+r(0) でも実用上問題無いが、「前回の値から次回の値を求める」というVerlet法の性質から、r(h)の精度が描かれる軌道の精度に大きく影響するので注意が必要。

2.サンプルプログラム
y軸方向にボールをジャンプさせてボールが弾むプログラムを HTML5 Canvas で作成した。
y座標を求める部分
  this.charObj.y += this.charObj.y - this.charObj.y0 + gh2;
でVerlet法を用いている。

ソースコード全文は以下のとおり。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Verlet法で計算した、ボールが弾むアニメーション</title>
<!--[if lt IE 9]>
<script type="text/javascript" src="excanvas.js" charset="UTF-8"></script>
<![endif]-->
<script type="text/javascript">
<!--
var fps = 24;  // 1秒あたりのフレーム数
var MpPX = 10;  // 1メートルを何ピクセルとするか
var ga = 9.8 * this.MpPX;  // 重力加速度
var gh2 = ga * Math.pow((1/fps),2);  // 重力加速度 × フレーム表示間隔(秒)の2乗
var timeoutID;  // タイマー用ID

/* 画面を表示する
 * context: getContextで取得したcontext
 * w: 表示領域の幅
 * h: 表示領域の高さ
 */
function playgame(context, w, h) {
  this.PI2 = Math.PI * 2;  // 2π
  this.area = {w:w, h:h};  // 表示エリア
  this.context = context;
  this.jumping = true;  // ジャンプ中はtrue

  /* キャラクターデータ */
  this.charR = 5;  // キャラクターの半径
  this.charColor = "green";  // 色
  this.charX0 = 50;  // 中心のx座標初期値
  this.charY0 = this.area.h * 0.9 - this.charR ;  // 中心のy座標初期値
  this.peakY = 2 * this.charR + 5;  // ジャンプピーク時のy座標
  this.v0 = -1 * Math.sqrt(2 * ga * (this.charY0 - this.peakY));  // 初速度
  // 中心の座標 (x0,y0)は前回の座標
  this.charObj = {x0: this.charX0, y0:this.charY0, x: this.charX0, y: this.charY0};

  this.context.fillStyle = this.charColor;

  /* 画面更新 */
  this.update = function(){
    // ジャンプ処理
    this.jump();

    // キャラクターを描画
    this.context.clearRect(0, 0, this.area.w, this.area.h);
    this.context.beginPath();
    this.context.arc(this.charObj.x, this.charObj.y, this.charR, 0, this.PI2, false);
    this.context.fill();
    
    if(!this.jumping){
      // ジャンプ終了
      window.clearInterval(timeoutID);
    }
  }

  /* ジャンプする */
  this.jump = function(){
    var backy = this.charObj.y;  // 前回のy座標を待避
    if(this.charObj.y == this.charObj.y0){
      // 初速度から最初の座標を算出
      this.charObj.y = ga / (2 * Math.pow(fps,2)) + this.v0 / fps + this.charY0;
    }else{
      this.charObj.y += this.charObj.y - this.charObj.y0 + gh2;
    }
    // 前回の座標格納
    this.charObj.y0 = backy;

    // y座標初期値に着いたら跳ね返る
    // (落ちる速度が大きく、1秒あたりのフレーム数が小さい場合は、このロジックでは
    //  着地がぎこちなく見える場合があります)
    if(this.charObj.y >= this.charY0){
      this.v0 *= 0.8;  // 反発係数をかける
      if(this.v0 >= -20){
        // ジャンプ終了
        this.charObj.y = this.charY0;
        this.jumping = false;
      }else{
        // 初速度から最初の座標を算出
        this.charObj.y = ga / (2 * Math.pow(fps,2)) + this.v0 / fps + this.charY0;
        this.charObj.y0 = this.charY0;
      }
    }
  }
}

/* スタート */
start = function(){
  // canvasのDOM elementとcontext取得
  var canvas = document.getElementById("cv1");
  if ( ! canvas || ! canvas.getContext ) { return false; }
  var ctx = canvas.getContext("2d");
  ctx.lineWidth = 1;
  ctx.globalAlpha = 1;
  ctx.globalCompositeOperation = "source-over";

  // 画面表示
  PG = new playgame(ctx, canvas.width, canvas.height);
  timeoutID = window.setInterval("PG.update()", 1000 / fps);
}

-->
</script>
<style TYPE="text/css">
<!--
.title {
  margin: 10px;
  text-align: center;
  font-weight: bold;
  font-size: 20px;
  font-family: "MS Pゴシック", "Osaka", sans-serif;
}
.body {
  display: block;
  position: relative;
  width: 100px;
  margin-left: auto;
  margin-right: auto;
}
.cv {
  border-style: solid;
  border-width: 1px;  
  border-color: #808080;
  background-color: #F8F8FF;
}
-->
</style>
</head>
<body onload="start();">
<div class="title">Verlet法で計算した、ボールが弾むアニメーション</div>
<div class="body">
<canvas id="cv1" class="cv" width="100" height="150"></canvas>
</div>
</body>
</html>
スポンサーサイト

コメント

コメントの投稿

非公開コメント

プロフィール

himax64

Author: 南西
30代後半の無職です。
就活もせずダラダラ生きてます。
作ったもの

最新記事
人気記事
検索フォーム
カテゴリ
月別アーカイブ
最新コメント
最新トラックバック
RSSリンクの表示
QRコード
QRコード
カウンター
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。