今回話す内容のキーワード
#インターン #設計 #MV(R)P #マネジメント #スクラム開発 #コミュニケーション #WebGLビルド
はじめに
はじめまして、京都産業大学の山内龍我です!
この度、サイバーエージェントさんが主催する CA Tech Challenge プロトスプリントリーグに参加してきました!
実は参加は二回目で前回の参加と比較しながら技術的な事、挑戦したかった事についてここに記していきます。
プロトスプリントリーグとは?
3days ゲームクライアント向け 開発型インターンシップ(通称プロトスプリントリーグ)では、3日間でお題に沿ったゲームをチームで開発し、ゲームとしての総合的な完成度の高さを競って頂きます。
(引用元 : https://www.cyberagent.co.jp/careers/students/event/detail/id=26117)
2021/9/18 ~ 2021/9/21の期間で開催されました。
チームメンバー発表などもあり、数日前から準備することもできたためそれも踏まえて話していきます。
事前準備
開始約5日前にメンバー発表がありました。自己紹介や企画すり合わせ等を行いました。
合計で3回話し合いの場を設けて企画を練っていきます。
第一回会議
主に自己紹介・顔合わせ・コンセプト出しを行いました。
第一印象はイケメンと美女。。。緊張する…うう…
でも、趣味で全員APEXをしてたり、住んでる所が近かったり、インターン参加が2回目だった方がもう一名いたりと、割と早めに打ち解けることができたと思います。
自己紹介しつつ雑談をして色々な案を出し合いました。
- ツムツムのパクリゲー作りたいよね~
- voodooみたいなカジュアルな感じがいいよね~
- クッキーランとかもいいかも
- テトリス好き
- 時事ネタで菅さんが総理大臣をやめるゲームとかw
そしてなんとなく案は出たものの決まり切らなかったので次回までに各々案を練ってくるように。と解散しました。
第1回議事録(隠すことなんてないので全部見せます!怒られたらリンク消します!)
イケメン→ @enaena128(以下えなちゃん)
美女→@shiori_hara(以下はらし)
第二回会議
それぞれが案を練ってきてプレゼン大会をしました。
僕「ツムツム案」
えなちゃん「フォートナイト2D案」
はらし「3蜜を避けてハチミツ集め案」
話し合いは白熱しました。
やっぱり企画を練る人って自分の作ったゲームが一番面白いと感じるものです。
客観的に判断するためにも、一旦お預けとし第三回の会議で決めることにしました。
第三回会議
前回出した
「メイドインワリオ案」はアセットの作りこみが可能なのか
「ツムツム案」は独自性はあるのか
「3蜜案」は3Dアセットやリアルタイム通信周りは大丈夫なのか
等々たくさんのことを話し合いました。
話し合いの結果「ツムツム案」が採用されました。
ここから仕様書の作成に取り掛かります。
その日十に1,2,3日目でそれぞれどんなタスクをするのか大まかに割り振ってこの会議は終了します。
開発終了時点からみて、ほとんどこの通りに進んでいました。
【大まかなタスク割ふり】うっちーが僕のタスク
[1日目] プロトタイプ:
・うっちー:ツムツムの基盤、フレームワーク
・えなちゃん:オンライン周り
・はらし:デザイン、UI実装、素材探し
[2日目] α版:最低限できるところまでは完成
・うっちー:素材、UI周り、はらしのサポート、エフェクトまわり
・えなちゃん:オンライン周りをツムツムに組み込む
・はらし:スキルなどの実装
[3日目] RC版
・うっちー:バグ修正、最終詰め
・えなちゃん:バグ修正、最終詰め
・はらし:2日目の続き、最終資料の枠組み
ということで開発に…ちょっと待ってください!!
ここで今回懸念していた「WebGLビルド」についてと、僕の挑戦したいこと「設計」「マネジメント」について話させてください。
WebGLビルドは大変
僕はWebGLでのビルドをしたことがなかったので、事前準備ということでWebGLのビルドについて調査しました。
WebGLビルドについての調査の詳細(Issue)
調査と結果をいかに簡単に書きます。
-
スマホ(モバイル機器)での解像度対応はデフォルトで問題ないのか
- →問題ありだった。
Unityの公式フォーラムでも最近まで話してたみたいだけど最終出力先のhtmlファイルをいじらないといけないらしい
- →問題ありだった。
-
ビルドに時間がかかるだろうから、CloudBuildかGitHubActionsで自動化できないか
- →CloudBuild + UnityCollaborateを用いてビルドができた。
しかしCloudBuildはWebGLのcustom templateを使用できないため無理やりcs側から書き換える必要がある。
手元で6:30秒かかるビルドをCloud上で10:51秒でビルド可能に、純粋に作業時間が増えます。やったね。
- →CloudBuild + UnityCollaborateを用いてビルドができた。
-
デプロイ時に起動できない問題が起こりそうだから、自前でデプロイ先を用意しておきたい
- →herokuにデプロイすることで対応。開発開始前からデプロイ先を用意して一定のタイミングでデプロイする。
はい。ビルドについては事前に調べていて正解でした。
だって絶対問題起きると思ったもん!!!!!ほら実際この調査がなかったらスマホビルドできなかったし。えらい!
設計について
一つ目の挑戦としてMV(R)Pアーキテクチャを用いたフレームワークの構築を考えていました。
これを読んだそこのあなたはこう思うかもしれません
「えっ、短期間のハッカソンでMV(R)Pとかするん?」
わかります。おっしゃることは承知しております。。
そこまで規模の大きくない、しかも短期間のプロジェクトでメンバーのレベル感もわからないまま採用するべきではないかもしれません。
でも前回このインターンに参加した時にもメンバーに知らないことをたくさん教えていただきました。
また、設計をまともにしていないコードはインターンが終わった先に枯れていくことになるでしょう。
だからこそ、短期間であれ設計をし大規模開発である先を見据えた開発をしなければならないと思います。
3日間のインターンだからといって、手を抜いていいものではないです。
MV(R)Pを使ったことがない、UniRxを使ったことがないメンバーに使い方を教えてコミュニケーションする。
それは決して簡単なことではないですし、メンバーの負担にもなります。
でもそういう機会があるのがインターンの良さなのかな、と思います。
結果の話になりますが、この取り組みは最終発表の結果に大きな影響を及ぼしたと思っています。
マネジメントについて
二つ目の挑戦としてマネジメントを考えていました。
なぜマネジメントなのかというと、あまり詳しくは言えないのですが「マネジメントができなかった失敗を知っているから」です。
今回はスクラム開発を導入しました。
目的は「全員が何をしていて」「どんな責任を持ってて」「不安なことがあればすぐに共有できる」機会を設けるためです。
オンラインである以上、ロスコミュニケーションは避けられません。
また、コミュニケーションが少ないということは信頼関係の構築にも影響を及ぼします。
誰かがまとめて先導していかないとプロジェクトの崩壊は免れません。
僕がスクラムマスターとして裏の仕事はやるので、メンバーの負荷はそれほど無いようにしました。
具体的にやったことは以下です
- 開発開始前にスクラム開発の概要を共有
- スクラムマスター、チームとしてやることの共有
- 毎日決まった時間にKPT、プランニングの実施
- タスクにストーリーポイントを割り振って各々のタスクを細分化、見える化
これが開発前に作成した資料です。見てもらえると喜びます。
実際に行っていた各種イベント
イベント | 日時 | 備考 |
---|---|---|
スプリント | 1日 | - |
(オープニング) | 10:00~10:30 | 初日のみ実施 |
5分面談 | 毎日 15:00~16:00 | メンターさんとの個別面談 |
レトロスペクティブ(KPT) | 毎日 18:00~18:30 | jamboardを用いる |
夕会 | 毎日 18:30~19:00 | メンターさん含みで不安要素など共有 |
プランニング | 毎日 19:00 ~ 次の日まで | 次の日のタスク、ストーリーポイントを決める |
チーム懇親会 | 毎日?19:30~20:30 | 酒飲む! |
また、この取り組みも最終発表の結果に大きな影響を及ぼしたと思っています。
1日目
ここまで長くなりましたが、ついに開発1日目が始まりました。
一番最初にやったことは「フレームワーク作成」です。
12:30までを目標に
- MV(R)Pで使うabstractなベースclassの作成
- シーン遷移・オブジェクト生成のルートを統一
- UnityのStart(), Update()などを呼ばないように独自ライフサイクルメソッドを定義
- それらのフレームワークを利用したサンプルコードの作成
をmainにマージしました。
ちなみにBaseClassはこんな感じのことをします。
GameObjecet生成もシーンも型引数で行えるようにして、それぞれの定義クラスのAttributeで文字列と紐づけるようにします。
public abstract class PresenterBase
{
private RootViewBase _rootViewBase;
protected async UniTask<T> CreateViewAsync<T>(Transform parent = null) where T : ViewBase
{
var path = PrefabPath.GetPrefabPath(typeof(T));
var obj = await Resources.LoadAsync<T>(path) as T;
var instance = Object.Instantiate(obj, parent, false);
instance.SetLoading(true);
await instance.LoadAsync(); // view側のライフサイクルメソッド呼び出し
await instance.DidLoadAsync();
instance.SetLoading(false);
instance.SetLoaded(true);
return instance;
}
protected async UniTask<T> ChangeScene<T>(IParameter parameter = null) where T : RootViewBase
{
var type = typeof(T);
var name = RootSceneName.GetRootSceneName(type);
SceneManager.sceneLoaded += GetRootViewBase<T>;
await SceneManager.LoadSceneAsync(name);
SceneManager.sceneLoaded -= GetRootViewBase<T>;
var rootViewBase = (T) _rootViewBase;
rootViewBase.SetParameter(parameter);
rootViewBase.SetLoading(true);
await _rootViewBase.LoadAsync(); // view側のライフサイクルメソッド呼び出し
await rootViewBase.DidLoadAsync();
rootViewBase.SetLoading(false);
rootViewBase.SetLoaded(true);
return (T) _rootViewBase;
}
}
viewのベースクラスはこんな感じ
public abstract class ViewBase : MonoBehaviour, IDisposable, IViewBase
{
protected bool IsLoading;
protected bool IsLoaded;
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
protected CancellationToken _cancellationToken => _cancellationTokenSource.Token;
public void SetLoading(bool state)
{
IsLoading = state;
}
public void SetLoaded(bool state)
{
IsLoaded = state;
}
public virtual UniTask OnLoadAsync()
{
return UniTask.CompletedTask;
}
public virtual UniTask OnDidLoadAsync()
{
return UniTask.CompletedTask;
}
public async UniTask LoadAsync()
{
await OnLoadAsync();
}
public async UniTask DidLoadAsync()
{
await OnDidLoadAsync();
}
public void Dispose()
{
_cancellationTokenSource.Cancel();
Destroy(gameObject);
}
}
ここで先に言っておきたいことが一つ。
ViewでPresenterを作成しています。
これは明確な意図が二つあります。
- 一つ目はメンバーに教えきれなかった場合にviewだけで完結するコードを最悪書けるように
- 二つ目は最終日絶対ごたごたするので、viewだけで完結できるコードを書けるように
最終目的は完成のためリスクマネジメントも兼ねて一部これが可能な状態になっています。
基本的にはこのルールは適用しない方針ですが、リファクタリングが可能な状態でのviewべた書きは最終アリということにしました。
これも最終的には功をなしました。
設計的には×ですが、最終日にちょっとだけ設計を崩すことでフレームワークに囚われることなくスピード優先の開発ができました。
もちろんリファクタリング可能です。
フレームワークのマージが終わると、主にインゲーム(パズル)の実装を進めていました。
目標はツムツムと同じレベルで消せることだったので、本日のタスクは達成しました。
そんなこんなで1日目のタスクがあらかた終わって、18:30の振り返りのタイムになります。
第一回KPT
Keep
僕「〇〇分までにKeep, Problem書いてください。」
僕「まず僕から共有します!」
僕「実はお昼にはらし誘ってAPEXした!えなちゃんも夜やろう!」
僕「コミュニケーションがめちゃめちゃ取れてるのいいね」
はらし「すり合わせできててめっちゃいい。」
はらし「UniRx使えるようになった!ありがとう!」
えなちゃん「疑問点すぐに聞けた」
えなちゃん「15分寝れた」
Problem
僕「メンバーにスクラムとかMV(R)Pとか導入してオラオラになってないか心配…」
僕「エフェクト周りが心配かも…」
はらし「一つ一つの作業に時間かけすぎたかもしれない」
はらし「技術面で与えられてばかりだから、自分もgiveしていきたい」
えなちゃん「もっと開発スピード上げられた」
えなちゃん「眠たい」
僕「ありがとうございます!〇〇分までにProblemからTry書いてください!」
Try
僕「みんなの意見を聞きながら開発しやすいようにしていきたい」
僕「エフェクト触ったことないからやってみる」
はらし「今日学んだことを明日以降も使っていきたい」
はらし「早寝早起きする!」
えなちゃん「メリハリつける」
えなちゃん「遅延処理実装したい」
とってもいいんじゃないでしょうか。
個人的な話を雑談とともにし、不安事項や共有事項をフランクに聞いたり話したりできる環境。
自分ひとりで悩んでいることも他の人からしたら解決策を知っているかもしれません。
そして、これが信頼に繋がっていき、プロダクトを成功に導く機会になると思っています。
(個人的には一人ひとりと1on1をしてもっと深くまで関わっていきたかったけど、時間の問題や「メンターさんの仕事はとらない…!」と今回はやりませんでした)
第一回夕回
メンターのぼらさん、まみーさんを含んで5名で一日の内容を振り返りました。
この時点ですでに振り返りの「KPT」を済ませていたため、メンターさんは驚いている様子でした。
メンターさん「夕回の時にKPTみたいなことをする予定だったんだけど、もう終わってるの!?」
僕「はい!!!全部自分たちでやりました!!」
こうしてKPTで話したことを夕回で共有することになりました。
第一回プランニング
その後、メンバーだけ残って明日やることのプランニングを行いました。
この0.5、1はそれぞれのタスクが30分、1時間で終わるタスクの時間見積を示しています。
一人一日8時間作業するとして、大体6ポイントくらいまでタスクを積んで作業に励むわけですね。
本来はこのポイントを毎週集計してタスクに偏りが出ていないか調べたりします。
その後メンバーとAPEXして、大体夜2:20くらいまで気になるところのブラッシュアップやビルド周りの確認、手触り改善などをして一日目の作業は終了です。
2日目
二日目。 僕が今日やることはこんな感じです。
- スキル(横一列消す)の実装
- スキルの拡張方法をはらしに共有
- エフェクト周りの調査、実装
スキルを拡張可能な状態で設計するのが大変でした。
実装はこんな感じです。
public class DeleteLineSkill : ISkill
{
//画面の真ん中1列のツムを削除する、
private readonly int YRange = 100;
private readonly int NeedValue = 30;
public async UniTask ExecuteAsync(TsumuRootPresenter tsumuRootPresenter)
{
// 実際の処理を書く
}
}
ISkillがExecuteAsyncのメソッドを定義していて、スキルボタンを押された通知からPresenterがExecuteAsyncを読んで自分自身を渡しています。
SkillとPresenterが蜜結合になっており、設計的にはよろしくない感じになっていたので、メンターさんに質問しました。
僕「この部分なんですけど、拡張はできるようになってるもののSkillが増えるとPresenterとずぶずぶな関係になっちゃって…」
僕「Presenterを渡さずにModelとViewだけ渡すようにするのがよさそうですか…?」
メンターさん「そうだね、、、TsumuRootPresenterにinterface切って疎結合な状態にするのがよさそうかも!」
僕「だとすると、presenterがファットになっていく問題が発生しててそれを解決するには…」
メンターさん「presenterじゃなくて、Factoryみたいな生成だけに責任を持つクラスとかを作っちゃえばいいかな、、?」
僕「天才ですね!!?!?!?!?!?」
文字に起こすと何を話しているかあんまりわからなくなりましたが、とにかくメンターさんのすごさと設計力にびっくりしました。
すごいです、さすがです。
Factoryみたいなクラスを作るとすると、Presenterだけでは足りなくなってMV(R)P+レイヤードアーキテクチャみたいな構成になっていきそうだったので、時間の問題もあり今回は上に書いているようなSKillとPresentergaずぶずぶの関係で開発を進めました。
スキルの実装が終わったので、メンバーのはらしにスキルの拡張方法を共有してペアプロをしました。
Discordでも画面共有しながらいけちゃうものなんですね。今の時代ってすごい!
えなちゃんもリアルタイムマッチングの部分とインゲーム部分をうまく合わせられてすごかったです。
天才プログラマーだ。
あと、シーン遷移時のパラメータも実装しました。
public sealed class MainRootView : RootViewBase
{
public class Paramater : IParameter
{
public int MaxTsumuCount;
public int MaxHp;
public bool IsSingleMode;
public Paramater(int maxTsumuCount, int maxHp, bool isSingleMode)
{
MaxTsumuCount = maxTsumuCount;
MaxHp = maxHp;
IsSingleMode = isSingleMode;
}
}
}
簡単ですが、遷移される側のクラスのインナークラスにIParameterを実装したParameterを実装し、遷移時にこのインスタンスを渡すだけです。
中間発表
お昼くらいに中間発表がありました。
軽くどんなゲームなのか、スライドを作って全体に共有します。
スライドはほとんどはらしが作ってくれました。ありがとう。
僕が作ったのはこのくらい…
発表のフィードバックの時間で、UniRxに(乱用しない!)って書いてあることに
「いいねぇ」と突っ込まれました。
設計をちゃんと考えていることも高感触だったイメージです。
第二回KPT & 夕回
そんなこんなでKPTのお時間です。 前回は夕回でも同じことを話したので、タイミングを一つにしました。
僕「中間発表成功した!!!!」
僕「実機で動いたー!!!!!!!」
はらし「中間発表頑張った!!」
はらし「デザイン系はほぼほぼ完成!」
えなちゃん「はらしのデザインいいね!」
えなちゃん「リアルタイム通信のベースクラスとの兼ね合いの設計を相談できた」
僕「今日中にエフェクトいけるかな、、」
僕「技術の押し売りになってませんか!大丈夫かな~」
はらし「スキルの実装で頭パンクした」
はらし「難しいけどinterfaceとLinq理解した!」
えなちゃん「設計崩してしまった」
えなちゃん「途中退出処理とか実装してスキルアップしたい」
僕「優勝する!」
僕「完成させる!!!」
はらし「Linqとインターフェイスの理解頑張る!」
はらし「エフェクト、オーディオ回りやる」
えなちゃん「最低限の機能実装終わらせて遅延処理、途中退出スキルアップしたい」
みんな自分の目標とともに誰が何を目指しているのか明確になってきています。
オンラインだから喜びを共有しあうタイミングも少ないですし、こういった場面はモチベーションの向上にも繋がります。
第二回 プランニング
夕回後明日何をやるのか、タスクの優先度付け、実装の最低ラインを決めました。
この時割とみんな疲れているご様子だったけど、最終3:00くらいまで作業してたと思います。
3日目
ついに最終日です。
僕の予定していたタスクはこんな感じです。
- 手触りのブラッシュアップ
- エフェクト実装
実際はブラッシュアップでこんなタスクが割り込んできました
- レート機能の実装
- コンボ機能の実装
15:00にデプロイ〆だったので、14:30までを基準にブラッシュアップできるところをひたすら実装していました。
メンターさんも協力のもとこのゲームがどうやったら面白くなるか、体力が減ると画面が赤くなったり、レート機能の実装だったり、コンボ機能、途中退出の処理が実装されていきました。
ビルドも時間がかかる中、最後の最後までみんなで良くしようと努力していたのが今でも思い出せます。
最終的におじゃまスキルの実装が間に合わず、本番デプロイにいれることができませんでした。
今回の開発で一番悔しかったところです。これは絶対にインターンが終わっても実装すると心に決めました。
15:00 デプロイ完了
実際に3日間で出来上がったゲームをご紹介します。
ルールはいたって簡単です。
ルール説明
1vs1の対戦型で、パズをつなげて攻撃。HPが0になったら負けです。
単純に戦うだけではなく以下のような要素があります。
- 「ピンクのパズは回復ができる」
- 「長くパズをつなげると攻撃力があがる」
- 「スキルを使うとたくさんパズを消せる」
マッチングはリアルタイムで行われます。
結果
そこから資料作成の時間を経て結果発表です。
第1位~第3位までの順位のチームが発表されます。
気になる結果は。。。。。。。。。。。。。。。。。。。。。。。
3位🥉でした!!!!!!!!!!!!!!!
受賞者のひとことの時間があり、その時に何を話したかあまり覚えていませんが「悔しい」という気持ちはとても覚えています。
ここまで本気で頑張ってきた分、優勝じゃない現実が悔しくて悔しくて仕方ありませんでした。
ただ、もう一つ特別賞を受賞することができました!🎖
設計面やメンバーとともに頑張ったスクラム開発、リアルタイム通信の実装やUIなど評価されたのだと思います。
当初挑戦したかった「設計」「マネジメント」はメンバーの協力なしには成し遂げられない挑戦でした。
ハードルも高かったと思います。本当にうれしかったです。はらし、えなちゃん、メンターのぼらさん、マミーさん本当にありがとう。。。
第三回 KPT & 夕回
最後のKPTはメンターさんも含んだ5人で実施しました。
よかったところ、改善すべきだったこと、これから頑張りたいこと、みんなが自分の目標をもって、成長を感じられたと思います。
3位と特別賞の喜びを分ちあい、開発をクローズすることができました! この3日間本当に楽しかった、、
ここで、簡単にですが開発終了後の一人KPTを実施します
Keep
- 設計を考えたフレームワークの実装ができてよかった
- ビルド周りを事前に調べられてよかった
- スクラム開発ができた、信頼とはどうやって出来上がっていくのか感じられた
- 最高のコミュニケーションが取れた。二人ともありがとうb
- 3位という結果だけでなくそれ以上のことに気づけた
Problem
- おじゃまスキル間に合わなかった
- エフェクト面が弱かった
- スマホでの動作で解像度対応が最低ラインしかできなかった
- スマホでの動作でパフォーマンス改善がもっとできた
- UXの改善も余地があった
Try
- リアルタイム通信挑戦してみる
- エフェクト周りももっと強くなる
- 最適化のためにできることの知識を増やす
- ユーザに届いたときにどんな体験になるか寄り添って実装する(UXの改善)
懇親会
KPTのあと、全体でのオンライン懇親会が行われました。
おいしいごはん食べて、お酒も飲んで、メンバーともっと仲良くなれたし、本当に最高の懇親会だったと思います。
2時くらいまでAPEXしながら話してました、最高!
さいごに
インターンが終わった今、達成感に満ち溢れています。
2年前の自分と比べて、間違いなく成長していることを感じました。
もっともっと強くならないといけないという自覚も強くなりました。
チームで開発する以上、そして一緒に目標に進んでいく以上、支えあって頑張ることが大事だと気づけました。
それが信頼に繋がり、結果的に良いものになるんだと思います。
最終発表の時に優勝できなくて「悔しい」という思いでいっぱいでしたが、
今思い返すと優勝よりももっと大事なものがあって、それを優先した結果が今ならそれが自分にとって一番の宝物で持って帰るべきものだったのかなと思っています。
技術的にも、人としてももっと成長していこうと思います。
一緒に開発をしてくれた はらし、えなちゃん、本当にいい開発ができて楽しかったですありがとう。 二人がいてくれなかったら二つの賞は取れていなかったし、ここまで頑張れなかったと思っています。
メンターをつとめてくださった ぼらさん、マミーさんブラッシュアップや設計の相談に乗っていただきありがとうございました。
本当に楽しくてアツい3日間でした!!!!!!!!!!!!!!!
また参加したいなぁと思ってます。
以上で参加記は終わりです。ここまで長々と読んでくださりありがとうございました。
是非、まだ参加したことが無い方は参加してみてください!
作ったゲームはこちらから遊べます(ランダムマッチなので二台以上いないと遊べないです)
https://prototeama.herokuapp.com/