CoRサンプル集: ミツバチ駆除
ミツバチの巣を破壊しよう。
遊び方はゲーム内で。
18/03/19 公開
18/03/20 画面上の数値の表示位置を調整, ゲームクリア画面を追加
18/03/27 蜂の巣が攻撃を受けて光るように変更
遊び方はゲーム内で。
18/03/19 公開
18/03/20 画面上の数値の表示位置を調整, ゲームクリア画面を追加
18/03/27 蜂の巣が攻撃を受けて光るように変更
プレー:832
(人数:128)
クリア:9
評価: 59 (7回)
#18/03/19 公開
#18/03/20 修正: 画面上の数値の表示位置を調整, ゲームクリア後にgame_clearメソッドを実行するように変更, コメントの誤字を修正, コードを読みやすく変更(UseNewSpriteモジュール)
#18/03/27 改良: Imageクラスに画像の変更/取得機能を追加, 蜂の巣が攻撃を受けた時に光るようにした
#18/04/17 修正: コードがあまりにひどいので多少書き直したが、設計が失敗していて、これ以上はあきらめた。。
$SCREEN_W, $SCREEN_H = 800, 450
$scene = nil #RmakeSceneオブジェクトのメソッドをどこでも使えるようにするために使う
$frame_count = 0
#全てのスプライト生成をこれで行う
module UseNewSprite
@sprite_count = 0 #ユニークなスプライト名をつけるためのカウント
class << self
attr_accessor :sprite_count
end
private
def new_sprite(image_name, x, y, layer, pos_origin)
$scene.sprite (UseNewSprite.sprite_count += 1).to_s do #(@@sprite_count += 1).to_sがスプライト名
image image_name
origin pos_origin #:center or :left_top
end
new_sprite = $scene.put_sprite(UseNewSprite.sprite_count.to_s) do
position x, y
end
layer.add new_sprite
new_sprite #細かい設定(scaleとか)は戻り値にメソッドを使ってする.
end
end
#画面上の、画像を1つしか持たないオブジェクト用
class Image
include UseNewSprite
PI_DIV4 = Math::PI / 4
@@div_w = {}; @@div_h = {}
@@loop_end = {}; @@animation_interval = {}
attr_reader :x, :y, :image_name
def angle
@radian_cw
end
#画像をアニメーションさせるときpreloadで使うメソッド
#元画像には、横に右, 下, 左, 上 を向いた画像(それぞれ幅div_w)
#縦に各コマの画像(幅div_h, loop_end個)を並べる。
def self.animation(image_name, div_w, div_h, loop_end, interval)
@@div_w[image_name], @@div_h[image_name] = div_w, div_h
@@loop_end[image_name] = loop_end
@@animation_interval[image_name] = interval
end
def initialize(image_name, x, y, layer, opt = {}) #まだキーワード引数が存在しないバージョンのようなので、擬似キーワード引数を使う。
@image_name = image_name
@x, @y = x, y
@layer, @pos_origin = layer, opt[:pos_origin] || :center
@sprite = new_sprite @image_name, @x, @y, @layer, @pos_origin
if @@div_w[@image_name] #アニメーションの有無を確認
@animation_frame = opt[:animation_frame] || 0
rotation opt[:rotation] || 0
end
end
def delete_sprite
@sprite.destroy #destroyの詳細がよくわからない。適切でない可能性もある(サンプルからコピペした)
@sprite = nil
end
def image_name=(new_image_name)
@sprite.destroy
@sprite = new_sprite new_image_name, @x, @y, @layer, @pos_origin
rotation if @@div_w[@image_name]
@image_name = new_image_name
end
def position(x, y)
@x, @y = x, y
@sprite.position @x, @y
end
def move(dx, dy)
@x += dx; @y += dy
@sprite.position @x, @y
end
def move_with_shift_y(dx, dy, shift_y)
@x += dx; @y += dy
@sprite.position @x, @y + shift_y
end
def scale(w, h)
@sprite.scale w, h
end
#引数無しのとき、画像の切り抜き範囲を再計算する
def rotation(radian_cw = nil)
if radian_cw
@radian_cw = radian_cw % (2 * Math::PI)
end
rect_x =
if @radian_cw < PI_DIV4 then 0 #right
elsif @radian_cw < PI_DIV4 * 3 then @@div_w[@image_name] #down
elsif @radian_cw < PI_DIV4 * 5 then 2 * @@div_w[@image_name] #left
elsif @radian_cw < PI_DIV4 * 7 then 3 * @@div_w[@image_name] #up
else 0 #right
end
@sprite.src_rect rect_x,
@animation_frame * @@div_h[@image_name],
@@div_w[@image_name],
@@div_h[@image_name]
end
def animation_frame(n)
@animation_frame = n
rotation
end
#各フレームで呼び出せば、縦方向に並べた絵でアニメーションする。
#呼び出さなければ、アニメーションもしない
def animation_update
return if $frame_count % @@animation_interval[@image_name] != 0
@animation_frame = (@animation_frame + 1) % @@loop_end[@image_name]
rotation
end
end
#数値または先頭に+がついた数値を表示するクラス
class Counter
CHAR_IMG_WIDTH, CHAR_IMG_HEIGHT = 20, 30
include UseNewSprite
attr_reader :value
def initialize(left, top, layer, opt = {}) #value: 0, num_chars: 4, color: :white, align: :left, scale = 1
@left = left
@top = top
value = opt[:value] || 0 #表示する値, 0123456789(先頭のみ+も可)で構成される値
@num_chars = opt[:num_chars] || 4 #最大文字数
color = opt[:color] || :white
@align = opt[:align] || :left #左揃えなら :left, 右揃えなら :right
@scale = opt[:scale] || 1.0
@max_value = 10 ** @num_chars - 1 #最大値を先に計算しておく
@max_value_when_has_plus = (@max_value + 1) / 10 - 1
@sprites = 0.upto(@num_chars - 1).map do |i|
new_sprite :number_image, left + i * CHAR_IMG_WIDTH * @scale, top, layer, pos_origin: :left_top
end
@sprites.each { |sprite| sprite.scale @scale, @scale }
@rect_y =
case color
when :yellow then 0
when :black then 30
when :purple then 60
when :blue then 90
when :red then 120
when :white then 150
when :green then 180
else raise ArgumentError, 'Unexpected value of "color" ' + color.inspect
end
self.value = value
end
#例: 指定 -> 表示値(num_chars = 3のとき)
# -10 -> 0, 300 -> 300, 10000 -> 999, "+10" -> "+10", "+10000", "+999", "" -> ""
#なお、表示上そうなるだけで、@valueの値は渡した引数と常に等しい
def value=(new_value)
if new_value == ""
@str = ""
elsif new_value.to_s[0] == "+"
new_value = new_value.to_s[1, new_value.size - 1].to_i
if new_value < 0 then new_value = 0
elsif new_value > @max_value_when_has_plus then new_value = @max_value_when_has_plus
end
@str = "+" + new_value.to_s
else
new_value = new_value.to_i
if new_value < 0 then new_value = 0
elsif new_value > @max_value then new_value = @max_value
end
@str = new_value.to_s
end
@sprites.each_with_index do |sprite, i|
if @align == :left
char = @str[i]
elsif @align == :right
char = @str[@num_chars - 1 - i]
else
raise ArgumentError, 'Unexpected value of "@align" ' + @align.inspect
end
rect_x =
if !char then 11 * CHAR_IMG_WIDTH # @str.size >= iのとき@str[i]はnilを返す
elsif char == "+" then 10 * CHAR_IMG_WIDTH
else char.to_i * CHAR_IMG_WIDTH
end
sprite.src_rect rect_x, @rect_y, CHAR_IMG_WIDTH, CHAR_IMG_HEIGHT
end
@value = new_value
end
def position(left, top)
@sprites.each_with_index do |sprite, i|
sprite.position left + i * CHAR_IMG_WIDTH * @scale, top
end
@left, @top = left, top
end
def move(x, y)
position @left + x, @top + y
end
end
class HPBar < Image
attr_reader :value
HPBAR_HEIGHT = 10; #幅は100で固定
def initialize(value, max_value, x, y, layer, opt = {}) #color: :red, :pos_origin: :center
@rect_y =
case opt[:color] || :red
when :red then 0
when :blue then 1
else raise ArgumentError, "Unexpected value of 'opt[\"color\"] " + opt["color"].inspect
end
super :bar_image, x, y, layer, pos_origin: opt[:pos_origiin] || :center
@max_value = max_value
self.value = value
end
def value=(new_value)
value_percent = (new_value / @max_value) * 100
value_percent = 100 if value_percent > 100
value_percent = 0 if value_percent < 0
@sprite.src_rect 100 - value_percent, @rect_y, 100, HPBAR_HEIGHT
@value = new_value
end
end
#====以下、メインループの処理を分割するためのメソッド====
def add_bee(x, y, layers, bees, bees_state)
bee = Image.new :bee_image, x, y, layers[:character], rotation: rand * 2 * Math::PI
bee.scale 0.5, 0.5
bees << bee
state = {speed: speed = 3 * rand + 4,
hp: rand < 0.1 ? 6 : 3, #ずっと巣に向かって攻撃をするだけでクリアされないように、たまに死ににくいのを混ぜる
move_x: speed * Math.cos(bee.angle),
move_y: speed * Math.sin(bee.angle),
deleted: false
}
bees_state << state
end
def update_bee(bee, bee_state, honeies, animation)
bee.move_with_shift_y bee_state[:move_x], bee_state[:move_y], animation[$frame_count % animation.size]
#画面の端で方向を反転
reverse_x, reverse_y = true, true
if bee.x < 0 then bee_state[:move_x] = bee_state[:move_x].abs
elsif bee.x > $SCREEN_W then bee_state[:move_x] = -bee_state[:move_x].abs
else reverse_x = false
end
if bee.y < 0 then bee_state[:move_y] = bee_state[:move_y].abs
elsif bee.y > $SCREEN_H then bee_state[:move_y] = -bee_state[:move_y].abs
else reverse_y = false
end
if reverse_x || reverse_y
bee.rotation Math.atan2(bee_state[:move_y], bee_state[:move_x])
end
end
def update_player(player, player_spd, bubbles)
player.animation_update
x = $scene.pointer.x - player.x
y = $scene.pointer.y - player.y
d = Math.sqrt(x * x + y * y)
##pointerとプレイヤーの距離が10を超えた時だけ、距離が10になるまで移動する
if d > 10 + player_spd
player.rotation Math.atan2(y, x)
player.move player_spd * Math.cos(player.angle), player_spd * Math.sin(player.angle)
elsif d > 10
player.rotation Math.atan2(y, x)
player.position $scene.pointer.x, $scene.pointer.y
end
end
def add_bubble(bubbles, bubbles_state, x, y, layers, radian_cw)
bubble = Image.new :bubble_image, x, y, layers[:character_bubble], animation_frame: rand(5)
scale = rand + 0.8
bubble.scale scale, scale
bubbles << bubble
rand_radian = 0.6283 * rand - 0.3142 #0.3142 = pi/10, 0.6283 = pi / 5
bubbles_state << {move_x: 20 * Math.cos(radian_cw + rand_radian),
move_y: 20 * Math.sin(radian_cw + rand_radian),
deleted: false
}
end
def update_bubbles(bubbles, bubbles_state, bees, bees_state, player_atk, beehive, beehive_hpbar, beehive_last_attacked_frame)
w_div2 = $SCREEN_W / 2.0
h_div2 = $SCREEN_H / 2.0
bubbles.delete_if.with_index do |bubble, i|
bubble.move bubbles_state[i][:move_x], bubbles_state[i][:move_y]
#画面外で焼失
if (bubble.x - w_div2).abs > w_div2 + 10 ||
(bubble.y - h_div2).abs > h_div2 + 10 #10は適当な値。攻撃が速くてよく見えないので、適当でいいと考えた。
next delete_bubble_ret_true bubble, bubbles_state[i]
end
#蜂の巣との衝突
if (beehive.x - bubble.x).abs < 50 && (beehive.y - bubble.y).abs < 50 #蜂が巣から出た瞬間に倒されないよう、蜂の当たり判定より大きく。
beehive_hpbar.value -= player_atk
beehive_last_attacked_frame = $frame_count
if beehive.image_name != :beehive_bright_image
beehive.image_name = :beehive_bright_image
end
next delete_bubble_ret_true bubble, bubbles_state[i]
end
#蜂との衝突
bubble_deleted = false
bees.each.with_index do |bee, j| #戻り値はEnumeratorオブジェクト
next unless (bee.x - bubble.x).abs < 20 && (bee.y - bubble.y).abs < 20 && bees_state[j][:hp] > 0
bees_state[j][:hp] -= player_atk
bubble_deleted = true
break delete_bubble_ret_true bubble, bubbles_state[i]
end
bubble_deleted
end
bubbles_state.delete_if { |state| state[:deleted] }
#巣の発光を元に戻す
if beehive.image_name != :beehive_image &&
$frame_count > beehive_last_attacked_frame + 2
beehive.image_name = :beehive_image
end
#蜂のhpが0以下なら、その蜂を消す
bees.delete_if.with_index do |bee, i|
next if bees_state[i][:hp] > 0
bee.delete_sprite
bees_state[i][:deleted] = true
true
end
bees_state.delete_if { |state| state[:deleted] }
end
#update_bubblesメソッド内でのみ使う
def delete_bubble_ret_true(bubble, bubble_state)
bubble.delete_sprite
bubble_state[:deleted] = true
true
end
#====以下、初期化とメインループ====
scene :main_scene do
#ここで代入しておくことで、変数のスコープがscene :main_scene do ~ end 全体になる
$scene = self
game_state = :initializing
last_frame_pointer_down = false
layers = Hash.new { |has, key| raise "unexpected layer name: " + key.inspect } #代入されたことのないキーを参照したらエラーを発生させる
#キャラクター
beehive = nil
beehive_hpbar = nil
beehive_last_attacked_frame = -9999
honeies = [] #本当は不可算名刺だけど...
honey_picked_up = 0
player = nil
player_atk = 1; player_spd = 3.5
bees = []
bees_state = [] #bees_state[id][state_name] bees_stateとbubbles_stateは削除のし忘れに注意
BEE_ANIMATION = (0...20).map { |n| 2.5 * Math.sin((n % 20) / 20.0 * 2 * Math::PI) }
bee_counter = nil
honey_counter = nil
bubbles = []
bubbles_state = []
#UI
purchase_counter = nil
last_purchased_frame = 0
upgrade_sound = nil
help_screen = nil
rainbow_screens = []
game_end_frame = 0
score_counter = nil
preload do #素材読み込み
image :grass_image , id: 321686
#キャラクター
image :bee_image , id: 321750
Image.animation :bee_image, 60, 70, 5, 5
image :player_image , id: 321748
Image.animation :player_image, 70, 70, 2, 5
image :bubble_image , id: 321749
Image.animation :bubble_image, 70, 70, 5, 5
image :honey_image , id: 321691
image :beehive_image, id: 321694
image :beehive_bright_image, id: 321768
image :bar_image , id: 321696
#UI
image :number_image , id: 321735
image :ui_image , id: 321718
image :help_image , id: 321717
image :game_clear_image, id: 321738
image :game_over_image , id: 321739
image :rainbow_image , id: 321740
music :bgm, id: 321734
sound :upgrade_sound, id: 321751
end
create do #初期化処理
add_music(:bgm).play
upgrade_sound = add_sound(:upgrade_sound)
("background,"+
"character_beehive,character_honey,character,character_bubble," +
"ui,ui_help," +
"endscreen_rainbow,endscreen,endscreen_counter").split(",").each { |layer_name| layers[layer_name.to_sym] = add_layer }
#インスタンスの生成(キャラクター)
#Image.new(image, x, y, layer, pos_origin = :center)
Image.new :grass_image, 0, 0, layers[:background], pos_origin: :left_top
beehive = Image.new :beehive_image, $SCREEN_W / 2, $SCREEN_H * 0.222, layers[:character_beehive]
#HPBar.new(value, max_value, x, y, layer, opt = {})
beehive_hpbar = HPBar.new 10000, 10000, $SCREEN_W / 2, $SCREEN_H * 0.222 - 45, layers[:ui], color: :red
#Image.new(image, x, y, layer, pos_origin = :center)
player = Image.new :player_image, $SCREEN_W / 2, $SCREEN_H * 0.444, layers[:character]
player.scale 0.8, 0.8
3.times { add_bee rand * $SCREEN_W, rand * $SCREEN_H, layers, bees, bees_state }
#インスタンスの生成(UI)
#Counter.new(x, y, layer, opt = {})
bee_counter = Counter.new(10, 20, layers[:ui], value: bees.size, num_chars: 3, color: :red, align: :left)
honey_counter = Counter.new(10, 70, layers[:ui], value: 0 , num_chars: 3, color: :yellow, align: :left)
purchase_counter = Counter.new(220, 68, layers[:ui], value: "" , num_chars: 5, color: :white, align: :left)
#Image.new(image, x, y, layer, pos_origin = :center)
Image.new :ui_image, 0, 0, layers[:ui], pos_origin: :left_top
help_screen = Image.new :help_image, 0, 0, layers[:ui], pos_origin: :left_top
game_state = :showing_help
wait_time 1 while pointer.down?
end
#本当はupdateの実行間隔をもっと開けたかったが、
#Timeクラスが実装されて無いっぽい(使うとエラーメッセージもなくゲームが強制終了する)から、諦めた
update do #メインループ(この内部の処理が自動的に無限ループされる)
$frame_count += 1
case game_state
when :showing_help
next unless pointer.down?
game_state = :playing
help_screen.delete_sprite
help_screen = nil
wait_time 1 while pointer.down?
when :game_clear
#虹色の画像を横へスライドさせる([0]と[1]で途切れずにループさせる)
rainbow_left = ($frame_count % 100) / 100.0 * $SCREEN_W * 2 - $SCREEN_W
rainbow_screens[0].position rainbow_left, 0
rainbow_screens[1].position rainbow_left % ($SCREEN_W * 2) - $SCREEN_W, 0
if $frame_count - game_end_frame == 90
send_activity_feed "ゲームをクリアしました! (タイム: #{game_end_frame}フレーム)"
game_clear
end
next
when :game_over
next
when :playing
else
raise "unexpected value of 'game_state' " + game_state.inspect
end
#キャラクター
if $frame_count % 4 == 0
add_bee beehive.x, beehive.y, layers, bees, bees_state
end
bees.each_with_index { |bee, i| update_bee bee, bees_state[i], honeies, BEE_ANIMATION}
update_player player, player_spd, bubbles
if $scene.pointer.down?
add_bubble bubbles, bubbles_state, player.x, player.y, layers, player.angle
end
update_bubbles bubbles, bubbles_state, bees, bees_state, player_atk, beehive, beehive_hpbar, beehive_last_attacked_frame
honeies.delete_if do |honey|
next if (honey.x - player.x).abs > 40 || (honey.y - player.y).abs > 40
honey_picked_up += 1
honey.delete_sprite
true
end
bees.each do |bee|
break if honeies.size > 30
next if rand(1000) > 4
honey = Image.new :honey_image, bee.x, bee.y, layers[:character_honey]
honey.scale 0.5, 0.5
honeies << honey
end
#UI
bee_counter.value = bees.size
honey_counter.value = honey_picked_up
#強化の購入
if !last_frame_pointer_down && pointer.down? && honey_picked_up > 0
0.upto(1) do |i| #i=0が上のボタン, i=1が下のボタン
#マウスポインタが各ボタンの範囲外なら次へスキップ
next if (pointer.x - 172).abs > 20 || (pointer.y - (i == 0 ? 85 : 171)).abs > 20
if i == 0
player_atk += 0.1 * honey_picked_up
else
player_spd += 0.3 * honey_picked_up
end
upgrade_sound.play
purchase_counter.position 220, i == 0 ? 68 : 154
purchase_counter.value = "+" + honey_picked_up.to_s
last_purchased_frame = $frame_count
honey_picked_up = 0
end
end
last_frame_pointer_down = pointer.down? #クリック検出のために、前フレームのマウスボタンの状態を参照する必要がある
#強化された量(+3など)の表示を一定時間で削除
if purchase_counter.value != "" && $frame_count > last_purchased_frame + 20
purchase_counter.value = ""
end
if beehive_hpbar.value <= 0
#ゲームクリアへの遷移
game_state = :game_clear
game_end_frame = $frame_count
Image.new :game_clear_image, 0, 0, layers[:endscreen], pos_origin: :left_top
rainbow_screens[0] = Image.new :rainbow_image, 0, 0, layers[:endscreen_rainbow], pos_origin: :left_top
rainbow_screens[1] = Image.new :rainbow_image, $SCREEN_W, 0, layers[:endscreen_rainbow], pos_origin: :left_top
score_counter = Counter.new 548, 285, layers[:endscreen_counter], value: game_end_frame, num_chars: 10, color: :purple, align: :left
end
if bees.size > 50
#ゲームオーバーへの遷移
game_state = :game_over
Image.new :game_over_image, 0, 0, layers[:endscreen], pos_origin: :left_top
end
end
render {} #描画処理 (使わなかった)
end
start_scene :main_scene
コード一覧
- start.rb
プレー内容を公開する
コメントする
コメントするには、ログインする必要があります。
コメント一覧
ハチが増えるとすぐに動作が重くなってしまう・・・
コメントありがとうございます。
重くなるのは対処できなかったので、最大で50匹とすることで諦めています。
動作が遅くなると攻撃が効かなくなるのは、こちらで確認できませんでした。
原因が現実時間上での攻撃hitの速度がひどく遅くなったことくらいしか思いつかないのですが、違ったら申し訳ないです。
重くなるのは対処できなかったので、最大で50匹とすることで諦めています。
動作が遅くなると攻撃が効かなくなるのは、こちらで確認できませんでした。
原因が現実時間上での攻撃hitの速度がひどく遅くなったことくらいしか思いつかないのですが、違ったら申し訳ないです。
動作が重くなると攻撃のアニメーションさえ出ないので、かなり初歩的な段階で遅延が起きているらしいです
どうしたらクリアまで持っていけるでしょうか?
どうしたらクリアまで持っていけるでしょうか?
ためしに蜂を150匹くらい集めてみても再現できませんでした。
蜂の巣とプレイヤーが重なっているとき、発射された瞬間に消滅して攻撃エフェクトが表示されないことをいま思い出して、
分かりづらいので巣を攻撃を受けた時に光るよう変更したのですが、それでしょうか。
蜂の巣とプレイヤーが重なっているとき、発射された瞬間に消滅して攻撃エフェクトが表示されないことをいま思い出して、
分かりづらいので巣を攻撃を受けた時に光るよう変更したのですが、それでしょうか。
お返事遅くなりました、対応ありがとうございます。
わかりやすくなりました!消えた攻撃のいくつかは巣に吸収されていたようです。
プレイしていてもう1つ疑問に思ったのですが、攻撃してもハチが消えないことがあります。攻撃力不足でしょうか?煙は消えるので当たっていると思うのですが。。。
そして25匹を超えたあたりからプレイヤーキャラの動きが遅くなり攻撃しても届かない笑
わかりやすくなりました!消えた攻撃のいくつかは巣に吸収されていたようです。
プレイしていてもう1つ疑問に思ったのですが、攻撃してもハチが消えないことがあります。攻撃力不足でしょうか?煙は消えるので当たっていると思うのですが。。。
そして25匹を超えたあたりからプレイヤーキャラの動きが遅くなり攻撃しても届かない笑
蜂にはランダムなHPがあります。(CoRサンプル集からコードを見れます。)
クリア数が全然増えないのがショックです。残念なことに、処理が速い環境じゃないとまともに遊べないのかもしれないですね。
クリア数が全然増えないのがショックです。残念なことに、処理が速い環境じゃないとまともに遊べないのかもしれないですね。
プレー履歴
-
井戸乃博士:
ゲームをクリアしました! (タイム: 18576フレーム)
(02/11 19:16)
-
てるてる:
ゲームをクリアしました! (タイム: 8147フレーム)
(12/21 15:42)
-
とりか:
ゲームをクリアしました! (タイム: 6717フレーム)
(11/19 00:33)
-
qhqh123:
ゲームをクリアしました! (タイム: 7728フレーム)
(05/03 18:02)
-
Erillas:
ゲームをクリアしました! (タイム: 8952フレーム)
(05/03 15:41)
-
クソザコ:
ゲームをクリアしました! (タイム: 4744フレーム)
(03/26 16:34)
-
キューマル:
ゲームをクリアしました! (タイム: 6662フレーム)
(03/21 02:44)
-
cfm_:
ゲームをクリアしました! (タイム: 5480フレーム)
(03/20 09:27)
-
cfm_:
ゲームをクリアしました! (タイム: 5783フレーム)
(03/19 18:30)
新着レビュー
レビューはまだ投稿されていません。 作品の感想を作者に伝えるためにレビューを投稿してみませんか?
フォロー/シェア
自分の実力だと なかなか厳しいバランスです