N2-Works
WEB企画・制作/システム開発 大阪拠点

シューティングゲームの追尾レーザー

前回の追尾弾に続き、追尾レーザーを解説します。

まずサンプルプログラムを紹介します。

サンプル※要FlashPlayer

ソースコード(本体)

// 初期化
if (!flg_init) {
  // レーザークラスをimport
  import Laser;
  
  // sinを設定
  var val_sin:Array = new Array();
  for (i = 0; i <= 360; i++) {
    val_sin[i] = Math.sin(i * Math.PI / 180);
  }
  
  // cosを設定
  var val_cos:Array = new Array();
  for (i = 0; i <= 360; i++) {
    val_cos[i] = Math.cos(i * Math.PI / 180);
  }
  
  // 追跡用変数宣言
  var vx2:Number;
  var vy2:Number;
  var vx3:Number;
  var vy3:Number;
  var px:Number;
  var py:Number;
  
  // 弾スピード設定
  var sp:Number = 5;
  
  // 追跡限界角度設定
  var pur_theta:Number = 5;
  
  // 判定フレーム設定
  var judge:Number = 10;
  var frame:Number = judge + 1;
  
  // レーザーを作成
  var l:Laser = new Laser(100);
  
  // レーザー速度を算出
  var d:Number = GetDistancePointToPoint(jiki._x, jiki._y, enemy._x, enemy._y);
  var vx:Number = (jiki._x - enemy._x) / d;
  var vy:Number = (jiki._y - enemy._y) / d;
  l.vx = vx * sp;
  l.vy = vy * sp;
  
  // レーザー角度を算出
  l.info[0]["x"] = enemy._x;
  l.info[0]["y"] = enemy._y;
  l.info[0]["angle"] = (Math.atan(vy / vx) * (180 / Math.PI)) + 90;
  l.info[0]["enable"] = true;
  
  // 角度設定
  l.View();
  
  // 初期化フラグオン
  flg_init = true;
}

// レーザー先頭消失確認
if (l.mc[0]._x < 0 || l.mc[0]._x > 300 || l.mc[0]._y < 0 || l.mc[0]._y > 300) {
  if (!l.flg_vanish) {
    l.idx = 0;
  }
  l.flg_vanish = true;
}

// 尾の後処理と初期化
if (l.flg_vanish) {
  // 無効化していく
  if (l.idx < l.tnum) {
    l.info[l.idx++]["enable"] = false;
  }

  // ローテーション
  l.Rotate();
  
  // 全部削除で初期化
  if (l.idx >= l.tnum) {
    // フレーム数を初期化
    frame = 0;
    
    // レーザー速度を算出
    d = GetDistancePointToPoint(jiki._x, jiki._y, enemy._x, enemy._y);
    vx = (jiki._x - enemy._x) / d;
    vy = (jiki._y - enemy._y) / d;
    l.vx = vx * sp;
    l.vy = vy * sp;
    
    // 初期化
    l.Initialize();
    
    // レーザー角度を算出
    l.info[0]["x"] = enemy._x;
    l.info[0]["y"] = enemy._y;
    l.info[0]["angle"] = (Math.atan(vy / vx) * (180 / Math.PI)) + 90;
    l.info[0]["enable"] = true;
  }
}
else {
  // 追跡処理
  if (frame++ % judge == 0) {
    // 自機とレーザー先頭位置との相対ベクトル
    px = jiki._x - l.mc[0]._x;
    py = jiki._y - l.mc[0]._y;
    
    // 現在自機位置への速度を算出
    d = GetDistancePointToPoint(jiki._x, jiki._y, l.mc[0]._x, l.mc[0]._y);
    vx1 = px / d;
    vy1 = py / d;
    
    // 元速度からの限界角度速度を算出(オイラー回転)
    vx2 = val_cos[pur_theta] * l.vx - val_sin[pur_theta] * l.vy;
    vy2 = val_sin[pur_theta] * l.vx + val_cos[pur_theta] * l.vy;
    
    // 2つのベクトルの内積を比較
    if (l.vx * vx1 + l.vy * vy1 >= l.vx * vx2 + l.vy * vy2) {
      l.vx = vx1 * sp;
      l.vy = vy1 * sp;
    }
    else {
      // 負の限界角度の速度を算出(オイラー回転, 負の角の公式)
      vx3 = val_cos[pur_theta] * l.vx + val_sin[pur_theta] * l.vy;
      vy3 = -val_sin[pur_theta] * l.vx + val_cos[pur_theta] * l.vy;
      
      // 2つのベクトルの内積を比較
      if (px * vx2 + py * vy2 >= px * vx3 + py * vy3) {
        l.vx = vx2;
        l.vy = vy2;
      }
      else {
        l.vx = vx3;
        l.vy = vy3;
      }
    }
  }
  // レーザーの移動
  l.Move();
}

// レーザーの表示
l.View();

// 距離計算関数
function GetDistancePointToPoint(p1_x:Number, p1_y:Number, p2_x:Number, p2_y:Number) : Number
{
  // X
  var x:Number = Math.pow((p1_x - p2_x), 2);
  
  // Y
  var y:Number = Math.pow((p1_y - p2_y), 2);
  
  // 距離計算
  return Math.sqrt(x + y);
}

Laser.as

// レーザークラス
class Laser
{
  // MC配列
  public var mc:Array;
  
  // レーザー情報配列
  public var info:Array;
  
  // 尾の数
  public var tnum:Number;
  
  // 表示インデックス
  public var idx:Number;
  
  // 速度
  public var vx:Number;
  public var vy:Number;
  
  // 先頭消失フラグ
  public var flg_vanish:Boolean;
  
  // コンストラクタ
  public function Laser(tnum:Number)
  {
    // 尾の数を取得
    this.tnum = tnum;
    
    // MCを作成
    this.mc = new Array();
    this.info = new Array();
    for (var i:Number = 0; i < this.tnum; i++) {
      this.mc[i] = _root.attachMovie("laser", "laser" + i, i);
    }
    
    // 初期化
    this.Initialize();
  }
  
  // 初期化
  public function Initialize()
  {
    for (var i:Number = 0; i < this.tnum; i++) {
      this.mc[i]._visible = false;
      // 過去情報オブジェクトを作成
      this.info[i] = new Object();
      this.info[i]["x"] = 0;
      this.info[i]["y"] = 0;
      this.info[i]["angle"] = 0;
      this.info[i]["enable"] = false;
    }
    
    // 表示インデックスを初期化
    this.idx = 0;
    
    // 先頭消失フラグを初期化
    this.flg_vanish = false;
  }
  
  // 移動処理
  public function Move()
  {
    // ローテーション
    this.Rotate();
    
    // 先頭レーザーの処理
    this.info[0]["x"] += this.vx;
    this.info[0]["y"] += this.vy;
    this.info[0]["angle"] = (Math.atan(this.vy / this.vx) * (180 / Math.PI)) + 90;
    
    // 次の尾を有効にする
    if (this.idx + 1 < this.tnum) {
      this.info[++this.idx]["enable"] = true;
    }
  }
  
  // 表示処理
  public function View()
  {
    for (var i:Number = 0; i < tnum; i++) {
      if (this.info[i]["enable"]) {
        this.mc[i]._x = this.info[i]["x"];
        this.mc[i]._y = this.info[i]["y"];
        this.mc[i]._visible = true;
        this.mc[i]._rotation = this.info[i]["angle"];
      }
      else {
        this.mc[i]._visible = false;
      }
    }
  }
  
  // ローテーション
  public function Rotate()
  {
    // レーザーの情報をローテーションする(後ろから)
    for (var i:Number = (this.tnum - 1); i > 0; i--) {
      this.info[i]["x"] = this.info[i - 1]["x"];
      this.info[i]["y"] = this.info[i - 1]["y"];
      this.info[i]["angle"] = this.info[i - 1]["angle"];
    }
  }
}

基本的な考え方は追尾弾と同じです。

追尾弾の後にレーザーの画像が続いているだけです。

弾の軌道算出のアルゴリズム自体は追尾弾と全く同じになります。

ただ先頭の弾の通った座標とその時の自身の角度を記録しておく必要があります。

何フレーム毎に記録するかは自由ですが、レーザーの長さ分のログが必要となります。レーザー画像何個分という考え方ですね。

サンプルは100インスタンス作成しています。100インスタンス分の座標と角度を内部で記録し、フレーム毎にその記録をローテーションして移動させているのです。

あと、速度ベクトルから自身の角度を算出する公式を記載しておきます。

// アークタンジェントを使用する(引数はy/x=タンジェント)
(Math.atan(vy / vx) * (180 / Math.PI));

また前回は負の角度を(360-pur_theta)としましたが、負の公式があります。

sin(-α) = -sin(α);
cos(-α) = cos(α);
tan(-α) = -tan(α);

[Flash]2009年09月01日 21時51分52秒

※1000文字以内で入力してください

captcha
TOP