目次へもどる

横に動かそう

入力を処理する準備が整ったのでコントロールブロックを横に動かそう.

冷静に考察

ここで,横に移動するために冷静な考察を行う. コントロールブロックはキー入力によって任意のタイミングで横に移動することができる.

このとき,移動先にすでにブロックが存在するときは移動できない. が,少し重なる程度ならばすでに存在するブロックの上に乗る形で横に移動できる.

ここで問題になるのが,現在のブロックの落下処理は1マスごとにmove_yをセットしているので,座標単位での処理が扱いづらい.

そこで今回はいっそのことすでに表示されているブロックとコントロールブロックの処理を分けてしまう. 今のブロックのクラスであるBlockには共通の機能のみを残し,すでに配置済みのブロックをStableBlock?,コントロールブロックをFreeBlock?とし,それぞれスーパークラスはBlockとする.

class Block
  attr_accessor :color, :row, :line
  attr_reader :draw_pos
  def initialize(col, block_s)
    @color = col
    @row = -1
    @line = -1
    @block_s = block_s
    @draw_pos = [0,0]
  end

  def inspect
    sprintf("|(%2d,%2d):%s|",@row,@line,@color.to_s)
  end

  def to_s
    sprintf("|(%2d,%2d):%s|",@row,@line,@color.to_s)
  end

  def get_color(alpha = 255)
    case @color
    when :r
      StarRuby::Color.new(255,128,128,alpha)
    when :g
      StarRuby::Color.new(128,255,128,alpha)
    when :b
      StarRuby::Color.new(128,128,255,alpha)
    when :y
      StarRuby::Color.new(255,255,128,alpha)
    when :p
      StarRuby::Color.new(255,128,255,alpha)
    end
  end

  def update
    @draw_pos[0] = @row * @block_s
    @draw_pos[1] = @line * @block_s
  end

  def draw(ox,oy,alpha=255)
    x = ox + @draw_pos[0]
    y = oy - @draw_pos[1]
    screen = GameMain.screen
    screen.render_rect(x, y, @block_s, @block_s, get_color(alpha))
  end
end
class StableBlock < Block
  def initialize(col, block_s)
    super
    init_animation
  end
  def init_animation
    @move_x = nil
    @move_y = nil
    @collapse = nil
    @draw_pos = [0,0]
  end

  def set_move_x(from, to, speed)
    @move_x = {
      :from => from,
      :to => to,
      :speed => speed,
      :counter => 0
    }
  end
  def set_move_y(from, to, speed)
    @move_y = {
      :from => from,
      :to => to,
      :speed => speed,
      :counter => 0
    }
  end
  def set_collapse(time)
    @collapse = {
      :time => time,
      :counter => time
    }
  end

  def update_move(param)
    return nil unless param
    pos = param[:from] + param[:counter] + param[:speed]
    param[:counter] += param[:speed]
    if param[:speed] > 0
      (pos = param[:to]; yield) if pos > param[:to]
    else
      (pos = param[:to]; yield) if pos < param[:to]
    end
    return pos
  end

  def update_collapse
    return unless @collapse
    if @collapse[:counter] == 0
      @collapse = nil
    else
      @collapse[:counter] -= 1
    end
  end

  def update
    # update move_x move_y
    x = update_move(@move_x){@move_x = nil}
    y = update_move(@move_y){@move_y = nil}
    @draw_pos[0] = x ? x : @row * @block_s
    @draw_pos[1] = y ? y : @line * @block_s
    # update collapse
    update_collapse
  end

  def move_x?; !@move_x.nil?; end
  def move_y?; !@move_y.nil?; end
  def move?; move_x? || move_y?; end
  def collapse?; !@collapse.nil?; end
  def animation?; move? || collapse?; end
  def reasonable_collapse?
    return false unless @collapse
    @collapse[:counter] >= @collapse[:time] / 5
  end

  def draw(ox,oy)
    if @collapse
      alpha = @collapse[:counter] * 255 / @collapse[:time]
    else
      alpha = 255
    end
    super(ox, oy, alpha)
  end
end
# row : base on @row [ draw_pos < row ]
# line : base on @draw_pos[1] [ draw_pos > line ]
class FreeBlock < Block
  def initialize(col, block_s)
    super
    init_animation
  end

  def init_animation
    @move_x = nil
  end

  def line=(line)
    @draw_pos[1] = line * @block_s
    @line = line
  end

  def set_move_x(from, to, speed)
    @move_x = {
      :from => from,
      :to => to,
      :speed => speed,
      :counter => 0
    }
  end

  def update_move(param)
    return nil unless param
    pos = param[:from] + param[:counter] + param[:speed]
    param[:counter] += param[:speed]
    if param[:speed] > 0
      (pos = param[:to]; yield) if pos > param[:to]
    else
      (pos = param[:to]; yield) if pos < param[:to]
    end
    return pos
  end

  def update
    # update move_x
    x = update_move(@move_x){@move_x = nil}
    @draw_pos[0] = x ? x : @row * @block_s
    # update line
    @line = (@draw_pos[1].truncate + @block_s / 2) / @block_s
  end

  def move_x?; !@move_x.nil?; end
  def move?; move_x?; end
  def animation?; move?; end

  def convert_stable
    sb = StableBlock.new(@color, @block_s)
    sb.row = @row
    sb.line = @line
    sb
  end
end

ステーブルブロックとフリーブロックの主な違いはy座標の扱いである. ステーブルブロックはlineの値を主としてy座標を決定している. 一方フリーブロックではy座標を主としてlineの値を決定している.

また,着地したときにフリーブロックをステーブルブロックに変換するconvert_stableメソッドを持つ.

ついでにブロック自身もブロックサイズを保持するようにした.

フィールドと落下処理

Fieldクラスではステーブルブロックとフリーブロックの生成,落下処理に変更を行った.

  def set(r,l,col)
    if @table[r][l]
      @active_blocks.delete @table[r][l]
    end
    block = StableBlock.new(col, @block_s)
    block.row = r
    block.line = l
    @table[r][l] = block
    @active_blocks.push block
  end
  def start_control_block(colors)
    pivot = FreeBlock.new(colors.sample, @block_s)
    belong = FreeBlock.new(colors.sample, @block_s)
    @ctrl_block.set(pivot, belong, 80)
    @ctrl_block.start
  end
  def update_control_block_move_y(iff)
    fall_y = @ctrl_block.can_falldown?(@table, @block_s)
    Debug.print fall_y
    if fall_y > 0 # fall
      @ctrl_block.falldown(fall_y > 0.8 ? 0.8 : fall_y) # fall_y > speed ? speed : fall_y
    elsif fall_y < 0 # dent
      @ctrl_block.fix_dent(-fall_y)
    else # postpone or land
      if @ctrl_block.postpone?
        @ctrl_block.update_postpone
      else
        control_block_land
        return false
      end
    end
    return true
  end
end

update_control_block_move_yメソッドではコントロールブロックのメソッドcan_fall?により落下可能かどうか,落下可能ならばどれだけ落下できるのか,もしくはめり込んでいるときはどれだけ上に上げればめり込みが解消されるのかを返す.

can_fall?メソッドを詳しく見てみよう.

class ControlBlock
  def can_falldown?(table, block_s)
    fall_ys = blocks.group_by{|block| block.row}.map{|row, blks|
      min_y = blks.min_by{|blk| blk.draw_pos[1]}.draw_pos[1]
      min_y - table[row].size * block_s
    }
    # fall_ys == 0 : can not fall
    #         >  0 : fall y
    #         <  0 : dent y
    return fall_ys.min
  end
  def falldown(y)
    blocks.each do |block|
      block.draw_pos[1] -= y
    end
  end
end

まず列ごとにコントロールブロックを分類し,各グループについて最小のy座標を求める.とはテーブルと見比べてちょうど着地状態なのか,どれくらい落下できるのか,もしくはめり込んでいるのか(マイナスの値)を求めて,各列の結果のうちもっとも小さい値を返す. あとはフィールド側の処理で,1フレームに移動できる距離に制限した上でブロックを落下させる.

横に動かす

まずは入力があったときにブロックを横に動かすことが可能かどうかを判断するメソッドcan_move_row?を定義する.

class ControlBlock
  def can_move_row?(imr, table, row_s)
    return false if imr == 0 # no move
    blocks.group_by{|block| block.line}.each do |line, blks|
      if imr > 0 # move right
        row = blks.max_by{|blk| blk.row}.row
        return false if row >= row_s - 1
        return false if table[row + 1][line]
      else # move left
        row = blks.min_by{|blk| blk.row}.row
        return false if row <= 0
        return false if table[row - 1][line]
      end
    end
    return true
  end
  def move_row(imr, speed, block_s)
    blocks.each do |block|
      x1 = block.row * block_s
      x2 = x1 + imr * block_s
      block.set_move_x(x1, x2, imr * speed)
      block.row += imr
    end
  end
end

引数imrは右移動が入力されれば1が,左なら-1,それいがいなら0が入っている. さて,横移動なので今度は行についてブロックをグループ分けする. そしてそれぞれのグループについて移動方向によって最も小さいもしくは大きい行をもつブロックの行の値を取得し,それがフィールドの端か隣のマスが埋まっていれば移動できないので即座にfalseを返す. 実際に移動するmove_rowメソッドは単純にアニメーションをセットしつつ移動するだけである.

ここで,ポイントとなる部分を見てみよう.

class FreeBlock < Block
  def update
    # update move_x
    x = update_move(@move_x){@move_x = nil}
    @draw_pos[0] = x ? x : @row * @block_s
    # update line
    @line = (@draw_pos[1].truncate + @block_s / 2) / @block_s
  end
end

フリーブロックの更新処理中に, @draw_pos[1]に基づいて@lineを更新している部分がある. これによればフリーブロックの行は

(描画位置+ブロックサイズの半分)/ブロックサイズ

である. つまり,ブロックサイズの半分まで描画位置が下がっても,属する行は変わらないということである. これによりcan_move_row?はブロックの位置が少し下がってもtrueを返す. この状態で移動すればブロックは少しめり込む事になるが,落下処理によってめり込みはコントロールブロック側が上にずれることで修正されるため, 最初に想定した仕様通りの動きをすることになる.

最後にフィールド側の横移動の処理をのせておく.

class Field
  def update_control_block_move_x(imr)
    return true if @ctrl_block.move_x?
    return false unless @ctrl_block.can_move_row?(imr,@table,@row_s)
    @ctrl_block.move_row(imr, 4, @block_s)
    return true
  end
end

実行

Loading the player ...

回転はできないがとりあえず横に動かせる.また,少し下に下がっていても上にずれて横に動かせていることがわかる.

ちなみに上に表示されている数字は,ブロックが落下可能な距離である. すなわちcan_fall?メソッドの戻り値である.


添付ファイル: filePuyoPuyoChap14.flv 239件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2013-08-30 (金) 22:11:14 (1785d)