「手作りRPG」の開発方法とゲームバランスと収益の話
- 2016.12.22
- テクノロジー
この記事は CAMPHOR- Advent Calendar 2016 の22日目の記事です。
「自分でRPGを作ろう!」そんな想いは小学生の頃からあって、実際に紙でRPGを自作して友達や妹に遊ばせたことが何度かあった。
大学生になり、遂にSwiftという武器を手に入れた僕はiPhoneで遊ぶことが出来るRPGを開発することが出来るようになったのだが、プログラミング初心者の僕がどのように「手作りRPG」を開発してリリースしたのか、リリースした後の反響や収益は実際にどのようなものだったのかを振り返ろうと思う。
本題に入る前に一つ注意がある。この記事で「手作りRPG」が丸裸になる。戦闘の設定やゲームバランスもほぼ全て具体的に解説してある。もしも全てを知る前に一度遊んでみたいと思うなら、こちらからダウンロードして遊んでみて欲しい。
ゲームの枠組みを決める
まず技術的な問題から、そこまで複雑なRPGを作ることはできなかった。そして何か目を引くようなものを作らなければいけないと思っていた。
そこで僕が考えたのが、
・自由度が高いRPG
・難易度が高いRPG
・シンプルなRPG
である。
まず自由度が高いRPGというのは色んな方向性が考えられるが、僕は「全ての名称を自分で考えるRPG」というものを思いついた。このゲームでは、自分の名前はもちろん、敵の名前や技の名前も自分で考えなくてはならない。
難易度が高いというのとシンプルであるというのは両立が難しかったかもしれない。一見シンプルだが、ゲームバランスにこだわっていて簡単にはクリアできないゲームを目指した。
僕が作ったRPGはマップの上を進んでいくという形ではなくて、1階の敵から10階の敵までを全て連続で倒すことができたらクリアというゲームにした。マップを進んでいくのはRPGの楽しいところのような気もするが、戦闘シーンにフォーカスしたものでもいいかなと考えたのだ。それにマップを作るとなると手間も格段に増えていただろう。
戦闘シーンのみと決まったので、育成要素や裏設定にはこだわろうと決めた。
ゲーム開発においてこのステップは非常に楽しくて重要だ。大まかな流れや具体的な設定を、ノートに書き出しながら考えていくのが良い。
ゲームをシーン毎に分けて画面設計をする
画面設計の手順を3つのSTEPで解説する。
STEP 1. 画面遷移の流れを決める
自分が作るゲームに必要な画面を書き出していく。ゲームを始めからプレイするようなイメージで流れを全て書き出す。僕の場合はこんな感じになった。
STEP 2. それぞれの画面のデザインを決める
それぞれの画面の具体的なデザインを決めていく。
草案段階ではこんな感じにしようと思っていた。
実際はこんな感じになった。
シンプルで懐かしいRPGというイメージだったので、この雑なUIはギリギリセーフということにしておきたい。
STEP 1で大まかな流れを書き出していたところに書き足していくようにする。
STEP 3. 実装する
僕はstoryboardを使わずに実装した。つまり画面の長さの比率などによってボタンのサイズなどを全て調節した。
このRPGは画面にボタンを張り巡らせるスタイルで実装した。なかなかうまく行かずに困った時、ボタンを押したら呼び出される関数の中に処理を書くだけでうまくいくことがあり、便利だと感じた。
戦闘の設定を考える
ゲームの遊び方だが、1から10階までの敵を全て倒せばクリアということにした。敵が複数出て来たり二周目には敵が強化していたりという要素は排除した。理由は実装がややこしくなりそうだったからだ。シンプルなRPGが目的だし、それでもいいかなと思った。
主人公と敵の基本的な能力は攻撃力と体力とスピードである。守備力を作らなかったのは、攻撃力があるのだからわざわざ必要ではないだろうと考えたからだ。パラメーターを二つ作るより、攻撃力という一つのパラメーターだけでゲームバランスを調節しようと試みた。
そして僕がこだわりたかった所、単純なゲームでありながらもやり込み要素があるような設定を考えた。その根幹にあるのは三つの属性だ。赤と緑と青である。どんなRPGにも属性はあると言っても過言ではないが、シンプルな中にも徹底して属性を利用しているのがこのRPGである。
「赤は緑に勝ち、緑は青に勝ち、青は赤に勝つ。」これが基本的な法則である。1階から10階の敵には全て属性を割り振っている。ノーマル・赤・緑・青の4種類だ。そして、ユーザーが使える技は三つの攻撃技と一つの回復技であるがこの三つの技にはそれぞれこの三つの属性を割り当てている。例えば緑の敵に赤の技を使うとダメージが大きくなる。
またレベルアップをすると一つの技を開発することが出来るのだが、ノーマルの敵を倒した時に回復技、赤属性の敵を倒した時に赤の技、緑属性の敵を倒した時に緑の技、青属性の敵を倒した時に青の技を開発すると通常よりも大幅に開発される。
さらにそれぞれの技で攻撃すると半分の確率で効果が発動される。赤属性の場合は追加でダメージを与え、緑属性の場合は先攻の時は相手の攻撃を無効化して後攻の時は自分のスピードをアップし、青属性の場合は体力を回復する。
以上が基本的な戦闘の設定である!
ゲームバランスを考える
「赤は緑に勝ち、緑は青に勝ち、青は赤に勝つ。」とあるが具体的にどのくらいのダメージの変化をつけるのかとか敵の攻撃力や体力を具体的に考える必要がある。
まず、プレイのデータは以下のような形で保存した。
var data = [1,1,1,2,1,20,0,1] //[0.red,1.green,2.blue,3.recovery,4.nowonfloor,5.leftlife,6.expstored,7.userlevel]
nowonfloorというのは途中でセーブした時のために現在何階にいるのかを示すもので、leftlifeは途中開始の時に体力が幾つから始まるかを示すものでexpstoredというのは経験値がどれだけ溜まったかを示すものだ。
戦闘に関するデータとして保存しているものはこれだけである。
基礎攻撃力や基礎体力は全てレベルによって決定している。
例えば体力の場合は
var life = [0] func lifeset(){ for n in 1...300 { if n < 10 { life.append(6*n+14) } else if < 20 { life.append(11*n-36) }else if n < 40 { life.append(20*n-216) } else if n < 60 { life.append(11*n+144) } else { life.append(5*n+504) } } }
として、体力の配列を生成した後
thelevel = data[7] userlife = life[thelevel]
のようにして基礎体力を決定している。
実際には回復技を開発した分も体力に加えるので
userlife = life[thelevel]+data[3]
のような形にしている。攻撃力に関しては
var power = [0] func powerset(){ for i in 1...300{ if i < 60 { power.append(i+3) } else if < 100 { power.append(63) } else { power.append(70) } } }
のように設定してあり、敵に与えるダメージは
thepower = power[thelevel] howmuchdamage = thepower + data[typeofattack-1] + rand(1+(tlevel/4)) //rand(i)で i 未満のランダムな数字を取得 if enemytype[nowonfloor-1] == -1 { //敵のタイプと同じ技を発動した時 //ダメージに変化なし } else if (enemytype[nowonfloor-1]-1)%3 == typeofattack%3 { //相性の良い属性の技で攻撃した時 howmuchdamage += data[typeofattack-1] } else if (enemytype[nowonfloor-1]+1)%3 == typeofattack%3 { //相性の悪い技で攻撃した時 if nowonfloor &amp;lt; 7 { howmuchdamage -= data[typeofattack-1] } else { howmuchdamage = 0 } }
ここで、
var enemytype = [-1,-1,1,2,3,-1,3,2,1,-1] // 1.red 2.green 3.blue -1.normal var typeofattack = 0 // 1.red 2.green 3.blue 4.normal
としてある。これによって相性の良い技を使用した際は技の開発度分だけダメージが増えるようになっている。
技の開発度は、レベルアップした時にうまく開発した時は3上がって、そうでないときは1上がるようにしてある。
ちなみに回復技は開発度×10の体力を回復するが一度使う度に回復力が半減していくのでいつまでも回復し続けることはできない。
技の開発度はdata[0]からdata[3]までに格納されている。
ちなみに敵の能力値は以下のようになっている。
var enemyinfo = [[6,22,8,2],[10,48,15,5],[21,100,32,13],[35,310,68,27],[60,600,90,58],[85,1000,170,100],[170,1400,100,121],[290,790,120,144],[150,3100,40,169],[190,2016,175,1]] // [power, life, speed, experience]
である。これはこれまでの細かい設定とクリアの難易度調整の末に辿り着いた完璧なゲームバランスである!
また10階の敵だが、攻撃を受ける度に
enemytype[9] = typeofattack+2
と再設定し直される。主人公が不利な状況になるように属性が変更されるのである。例えば赤属性で攻撃したら次のターンには青属性になるのだ。7階以上の敵は不利な属性での攻撃はダメージが0になることも注意が必要だ。
以上のゲームバランスを決定するのに本当に長い時間がかかった。開発期間が二週間でゲームバランス決定に二週間程かかった印象だ。
実装する
NSUserDefaultsだけでデータ管理
NSUserDefaultsは本当に便利!手軽にデータの格納ができるので、助かった。
var dataarray :[[Int]] = [[1,1,1,2,1,20,0,1],[1,1,1,2,1,20,0,1],[1,1,1,2,1,20,0,1]] let defaults = UserDefaults.standard defaults.set(dataarray, forKey: "dataarray") //dataarrayを格納 dataarray = defaults.object(forKey: "dataarray") as! [[Int]] //dataarrayを取り出す
こんな簡単な操作でデータを格納したり操作したりできるのだ!ここで格納したデータは、アプリを閉じても残っている。
クラスの使い方
クラスをうまく使用するということのイメージが湧きにくかったのだが、今回はクラスを継承することも出来て、少しは理解できた気がする。
ズバリ、クラスを継承させるタイミングというのは共通で使いたい変数や関数がある時である!親クラスにそういった変数や関数を用意しておいて、複数の子クラスに親クラスを継承させることで、共通部分のコードを何度も書く手間を省くことができるのだ!
自分ができる範囲で実装する。
このゲームアプリはSwiftの基本的な知識だけで実装されている。実際、コードを見返してみるとif文だらけで何がなんだかわからないし、何回も同じコードを書いているところもあるし、ボタンは沢山あるし、とても綺麗なコードとは呼べない。
まあクラッシュもほぼなく無事にアプリが動いているので、ひとまずはこれで良いではないだろうか。
自分が知っている知識だけで一つのアプリを開発できたことは楽しい経験だったし、わからないことにぶつかりそれを乗り越えてきたことはすごくためになった。
アプリを広報する
アプリの審査が無事に通り、遂にリリースされた。
SNSでの拡散をしたものの全く知らない人にも遊んで欲しいなと考えた僕は、街に出て紙切れを配ることにした。
「500人に配るぞー!」とか意気込んでいたのに、50人ぐらいにしか配れなかった。
しかも途中から修学旅行で京都に来ている中学生に雑に話し掛けて雑に紙切れを配りまくっていた。
次の日、Twitterを開くとメッセージが来ていた。
誰が不審者やwww
反響と収益
ゲームを作ったことで知り合いにも結構遊んでもらっていたのだが、リリースしたばかりの頃はレベル上げに手こずるという声が多く、アップデートを繰り返して非常に不安定な時期もあった。だがゲームの設定がよくできていると褒められることもあった。
正直、めっちゃ嬉しかった。
ちなみに全クリ報告をしてくれた人は結局いなかった。僕が考えた設定に全て気づいて、全クリまでたどり着くのは半端なく難しかったのだろう。
5月14日にリリースしてから12月20日までの間で864件のダウンロードがあり、合計2088円の収入があった。アプリをリリースする前の大金持ちになる妄想は当然のように打ち砕かれた。
収入はGoogleのAdMobでバナー広告を出すことによって生まれた。
ダウンロード数は初めの1ヶ月でほぼピークを迎え、2ヶ月後には週で10から20のダウンロードとなったが、今でもたまに収入は生じている感じだ。
まとめ
多分伝わったと思うが、戦闘の設定にはこだわっているので満足のいく作品となった。RPGを作るのは、やっぱり楽しかった!
-
前の記事
プログラミング初心者がSwiftに出会ってからアプリをリリースするまで 2016.12.15
-
次の記事
今すぐ覚えろ、京都の通り名! 2016.12.27