この記事はtraP Advent Calendar 2017年11月29日のmimikkuが担当する記事です。
はじめに
突然ですがみなさん、Minecraftというゲームを知っていますか?
簡単に説明すると、Minecraftは広大な世界の中戦闘・探索・建築など様々な行動ができ遊び方が自由に決められるゲームで、現在ではPCのほかスマートフォンやNintendoSwitchなど様々なプラットフォームで遊ぶことができます。
じゃあ今回は、何か適当な建物を建ててそれを敵から防衛するの?と思うかもしれませんが、違います。
というか ほぼプログラミングしかしてません
(正確に言うと、建物はあるけど自分が作っていないです。)
ゾンビディフェンスの仕様
奥から手前に向かって近づいてくるゾンビをひたすら倒していきます。
弓で矢を撃ち、ゾンビに当たると音が鳴り、倒すとさらに違う音が鳴ります。
ゾンビを倒すとポイントがもらえて、このポイントで、村人から強い弓や投げられる高火力の瓶を買えたり、自動で矢を発射する機構・進路を妨害する柵を出したりできます。
自分の待機場所の下に入られるとライフが減り、0になるとゲームオーバーです。
2人以上同時プレイも可能です。
プログラミング
上の仕様を頑張って実装していきます。
おそらくMinecraftでコマンドを書きまくってる人でないとコード見ても何も理解できないと思うので、コメントをほぼ全行つけておきました
音を鳴らす
ゾンビを倒した時
この場合は簡単で、各プレイヤーが倒した数だけ変数の値を増やす機能が標準で実装されているので、その数値が1以上になったら音を鳴らして0に戻せばいいです。
コードでは、
execute @a[score_Kill_min=1] ~ ~ ~ playsound block.anvil.place master @s ~ ~ ~ 0.5 0.5 #倒した数が1以上だったら音を鳴らす
scoreboard players set @a Kill 0 #倒した数をリセット
こうなります。
矢が当たった時
こちらの場合は複雑です。というのも、矢が当たったかどうかの判定を自分で行わなければならないからです。
今回は以下のようにして判定しています。
- ゲームが始まったら各プレイヤーに番号(IDみたいなもの)を1から順につける
- プレイヤーが矢を撃ったらそのすぐ近くにいる矢にも同じ番号をつける
- 各プレイヤーの番号がついた矢の数を常に数えて記録する
- 地面に刺さった矢の数だけ値を減らす
- 前回矢の数を記録したものと数値を比較し、数が減っていたら音を鳴らす
実際のコードでは、
#NumberInput.mcfunction
scoreboard players add @e[tag=master] Temp 1 #今回の処理で指定する番号を前回より1大きい数字にする
scoreboard players operation @s Number = @e[tag=master] Temp #今回の処理対象者に番号をつける
execute @p[score_Number=0] ~ ~ ~ function ZD:Gamefirst/NumberInput #まだ番号がついていないプレイヤーがいたらその中から1人選び同じ処理を実行する
これで1を行い、
#NumberOutput_1.mcfunction
scoreboard players add @e[tag=master] Number 1
execute @a ~ ~ ~ scoreboard players operation @s Bow = @s Number #各プレイヤーの番号をBow変数にコピー
execute @a ~ ~ ~ scoreboard players operation @s Bow -= @e[tag=master] Number #各プレイヤーのBow変数と今回処理をしたい番号を比較
execute @p[score_Bow=0,score_Bow_min=0] ~ ~ ~ function ZD:Gameroop/NumberOutput_2 #上の処理で差が0になったプレイヤーに対し2〜5の処理を実行
execute @a[score_Bow_min=1] ~ ~ ~ function ZD:Gameroop/NumberOutput_1 #上の処理で差が1以上(まだ処理が行われていない)プレイヤーがいたら同じ処理を実行する
#NumberOutput_2.mcfunction
execute @s[score_isUseBow_min=1] ~ ~ ~ scoreboard players operation @e[type=Arrow,dy=2] Number = @p[score_Bow_min=0,score_Bow=0] Number #プレイヤーの近くにある矢のNumber変数をプレイヤーのものと同じにする
scoreboard players set @s isUseBow 0 #プレイヤーが弓を引いた判定をなくす
execute @e[tag=Bow] ~ ~ ~ scoreboard players operation @s Bow = @s Number #現在飛んでいる矢のScoreTemp変数に矢自身のNumber変数を代入
scoreboard players operation @e[tag=Bow] Bow -= @s Number #ScoreTemp変数とプレイヤーのNumber変数を比較
testfor @e[tag=Bow,score_Bow=0,score_Bow_min=0] #プレイヤーとNumber変数が一致している飛んでいる矢の数をSuccess変数に記録
scoreboard players operation @s Arrow = @s Success #Arrow変数にSuccess変数を代入
scoreboard players operation @s Temp -= @s Arrow #Temp変数(前回この処理を実行した時のArrow変数が記録)とArrow変数を比較
execute @s[score_Temp_min=1] ~ ~ ~ playsound entity.arrow.hit_player master @s ~ ~ ~ 4.0 #上の処理で1以上になった(前回より指定の飛んでいる矢の数が減った、つまりゾンビに当たった)場合は音を出す
scoreboard players operation @s Temp = @s Arrow #今回のArrow変数をTemp変数に保存(次回比較で使用)
scoreboard players set @e[tag=Bow,score_Bow=0,score_Bow_min=0] Temp 1 {inGround:true} #プレイヤーが撃った矢で地面に刺さった矢のTemp変数を1にする
testfor @e[tag=Bow,score_Temp_min=1] #プレイヤーが撃った矢で地面に刺さった矢の数をSuccess変数に記録
scoreboard players operation @s Arrow = @s Success #Arrow変数にSuccess変数を代入
scoreboard players tag @e[score_Temp_min=1,tag=Bow] add Miss
scoreboard players tag @e[score_Temp_min=1,tag=Bow] remove Bow #地面に刺さった矢にMissタグを付け、Bowタグを消す(以後地面に刺さった矢はこの関数の対象にならない)
scoreboard players operation @s Temp -= @s Arrow #プレイヤーのTemp変数から地面に刺さった矢の数を引く(矢のデスポーンに誤作動することを防止)
scoreboard players operation @s MissCount += @s Arrow #MissCount(ミスした回数)をArrow変数分増やす
これで2〜5を行っています。Minecraftでは変数同士の比較ができず第三者の変数を使う必要があるので少し面倒です。
ポイント処理
入手部分
これも敵を倒したら数値が増える機能を使えば簡単に実装できます。
scoreboard players add @a[score_OneKill_min=1] Point 1 # 1ポイントの敵を倒した数が1以上ならポイントを増やす
xp 1l @a[score_OneKill_min=1] #所持ポイントを自分の経験値レベルとして表すことでわかりやすくする
scoreboard players remove @a[score_OneKill_min=1] OneKill 1 #処理した分倒した数を減らす
購入部分
ここでは村人から 最強の弓 を買う処理を書いていきます。
村人から物を買う機能は物々交換がMinecraft標準であるのでそれを使います。所持ポイントが足りているかどうか判定する機能は新たに付ける必要があります。
また、他プレイヤーにもチャットで購入したことを知らせる機能を付けるために、村人からはダミーの弓をもらい処理を実施後、正しい弓と取り替えるようになっています。
つまり、
- 村人に適当な物を渡してダミーの弓を交換する(適当な物は定期的に補給させておく)
- ダミーの弓を検知、もし所持ポイントが不足していたら弓を削除
- ポイントが十分だったら購入したことをチャットに出し、ポイントを引き、ダミー弓を正しい弓に交換
という処理を行います。
#Gameroop/trade.mcfunction
execute @a ~ ~ ~ clear @s minecraft:bow 0 1 {display:{Name:"Yoichinoyumi",Lore:["必要ポイント:128ポイント"]}} #各プレイヤーがダミーの弓を持っていたら、Success変数を1にしてダミー弓を削除
execute @a[score_Success_min=1] ~ ~ ~ function ZD:Gameroop/Villager/Yoichinoyumi #Success変数が1だったら取引用の処理を実行
#Gameroop/Villager/Yoichinoyumi.mcfunction
execute @s[score_Point_min=128] ~ ~ ~ give @s minecraft:bow 1 0 {display:{Name:"Yoichinoyumi"},ench:[{id:48,lvl:4},{id:49,lvl:3},{id:50,lvl:1},{id:51,lvl:1}]} #所持ポイントが十分なら、所定のアイテムを与える
execute @s[score_Point_min=128] ~ ~ ~ tellraw @a ["",{"selector":"@s","color":"aqua"},{"text":"が128ポイントで","color":"yellow"},{"text":"Yoichinoyumi","color":"aqua"},{"text":"を購入しました。","color":"yellow"}] #所持ポイントが十分なら、購入したことをチャットで知らせる
execute @s[score_Point=127] ~ ~ ~ tellraw @s ["",{"text":"ポイントが不足しています(必要ポイント:128 現在","color":"red"},{"selector":"@s","color":"red"},{"text":"の所持ポイント:","color":"red"},{"score":{"name":"@s","objective":"Point"},"color":"red"},{"text":")","color":"red"}] #所持ポイント不足なら、チャットそのことを表示
xp -128l @s[score_Point_min=128] #所持ポイントが十分なら、経験値を減らす
execute @s[score_Point_min=128] ~ ~ ~ scoreboard players remove @s Point 128 #所持ポイントが十分なら、所持ポイントを減らす
実際のコードはこうなります。
完成
他にもいろいろ処理があるけれどなんとか実装していき完成しました。
↓実際にやってみた動画(上は適当、下は本気でやった動画)
https://www.youtube.com/watch?v=iyTt3TuliQ0
https://www.youtube.com/watch?v=vJW_Sp5PKkg
さいごに
実は、このゾンビディフェンスは昔あるMinecraftマルチサーバーにあったものを可能な限り再現しようとしたものです。
そのサーバー内では比較的人気だったのですが数年前に無くなってしまい、それをなんとか再現しようと友人が建物をつくったものの挫折していたものを、今年夏にそのデータをもらい、何とか出来ないかと思って作ったものなんです。
ちなみに今回書いたコードは全て Minecraft Java Edition ver1.12.2 で書いています。ver1.11ではコマンドをTXTファイルに書くだけで動く神機能がなく、ver1.13では、コマンドの機能が変わったり、引数の順序や数が変わったり、そもそもコマンドが廃止されたりして体感8割くらい書き直しの必要があります。というわけで、 上のコードは1.12系でしか動きません! Minecraftでのプログラミングはまだまだ続きそうです...
明日 2017年11月30日 の記事は、erukkuさん・kakuoさん・phi16さん、の3人が担当します。