kedzkiestの日記

普段の発見や開発の進捗について書いていきます。

サイバーエージェントさんのプロトスプリントリーグに参加してきました!

サイバーエージェントさんの開催する、3days ゲームクライアント向け 開発型インターンシップ ONLINE プロトスプリントリーグ | 株式会社サイバーエージェントに参加したため、インターン中に思ったことなどを書きました。

はじめに

このインターンでは参加者がチームを組んでUnityでのゲーム制作をします。3daysと銘打ってはいますが、実際には10日ほどかけたイベントとなっています。

大まかな流れとして、前半(キックオフ、チーム・テーマ発表、企画、設計、素材集め)、後半(コーディング、中間発表、最終発表、懇親会)となっており、前半部分に1週間ほどかけるため、残りの3日でUnityにアイデアや素材を組み込み、コーディングする必要がありました。

キックオフ

他のメンバーやメンターさんとの自己紹介、チーム名を決めました。3人チームでしたが、私が最もゲーム開発において経験が浅いようで、ちょっと尻込みしながらも柔らかな雰囲気でスタートしました。

チーム名は協議の結果、zero1(ゼロイチ)に決定しました。メンバーの共通点としてボカロがあったことと、機械音声から0と1の2進数を連想したことが由来です。

この日はチーム発表と同時にテーマ発表もあり、本インターンにおける開発テーマは
「無限」
でした。また、この日は事前課題も出され、gitの基本的な操作方法などを復習しました。GitHubの2段階認証を初めて行ったのですが、専用のQRコード読み取りアプリが必要なことを知らず、しばらくスマホ標準のスキャナーで頑張っていました・・。お恥ずかしい・・。

企画

まずGoogleスプレッドシートを使い、「無限」に関連する言葉でブレストを行いました。その後、ブレストで出た案をもとに各メンバーが1つずつゲーム案を考えました。
私の考えたゲーム案は
docs.google.com
こんな感じです。3人ともそれぞれ良いところのある案を持ち寄ったのですが、最終的に他のメンバーの「無限音ゲー」というアイデアを採用することにしました。プレイヤーには体力があり、左から右へなるべく長い距離を移動することが目的で、道中リズムよくノーツを叩くことで障害物を回避し、体力の消費を抑えることができます。この段階ではMuse DashやGeometry Dashといったゲームを思い描いていました。

Muse Dashのプレイ画面 画像はSteamより
Geometry Dashのプレイ画面。画像はmob.orgより

設計

ここに関してはあまり語ることがありません。というのも私は設計にほとんど関わっていないからです。

ホワイトボードを使ってゲームの進行の仕方を確認したり、音ゲーの特徴であるノーツの流れをどう管理するかといったことをチームメンバーと議論したりはしましたが、クラス図を作るなどの具体的な設計は他のメンバーがやってくれました。

その方はチーム開発の経験があり設計を進んで行ってくれたので、私は準備期間の間、素材集め・素材作成に集中することができました。

素材集め

最後の3日間まではコーディングができないので、それまでに素材集めをしました。

実現したい「無限音ゲー」に必要なものを考えると、BGM、SE、画像系(ノーツ、背景、ボタンなど)、アニメーションなどが挙げられるため、集める素材を分担しました。

BGMに関してはNCSの曲を使う方向で話がまとまっていたので、YouTubeSpotifyで曲を探していたのですが、なかなかイメージ通りの曲と出会えず苦戦しました。2、3時間ほどぶっ続けでNCSを聴き続けたため、後半は全部同じ曲に聞こえてきました・・。

最終的に私がセレクトした曲はTobuのLifeという曲です。
youtu.be
BGMに比べるとSEは比較的簡単に拾ってくることができ、mixkit効果音ラボ無料効果音で遊ぼう!といったサイトにお世話になりました。一部ダウンロードしたSEをAudacityを使って加工しました。ノーツヒット時のカーン!という気持ち良い音が、もともと皿を積む効果音だったことに気づけた人はすごいです。

画像系の素材に関しては、思い描いているようなノーツや背景を見つけられなかったため、PhotopeaやFigmaを使って自作しました。下に載せてあるのが自作画像素材です。(透明背景に白素材なのでクリックしないと目に見えない素材があります。)

自作ノーツ1
自作ノーツ2
自作ノーツ3
自作背景1
自作背景2

コーディング

本番1日目 (8/5)
想定しているゲームに必要な機能を書き出し、チームで担当箇所を分担しました。私が担当したのは「キーが押されたときのノーツの判定」と、「タイトル・リザルト画面」です。

一日目の朝会が終わった段階からコーディングを始められるのですが、1日目の夕会までにはゲームループ完成まではいかないものの、3人のコードをつなぎ合わせて動いているゲーム画面を作るところまでは行きました。

この段階でのゲームシステムとしては、上下キーを押すことでプレイヤーは上下レーン間を移動でき、プレイヤーが今いるレーンのノーツをタイミングよく叩くことを、ミスするまでどこまでも続ける・・というものでした。つまり、思い描いているゲームにするためにスコアやコンボ、アニメーションや障害物といった新規要素を2日目以降に実装するつもりでした。

しかし1日目の夕会直前になって、「本当にこのゲームは無限というテーマを消化できているのか」という疑問がチームに挙がりました。さらにその後の夕会にて、「このゲームの面白さはどこなのか?」といったことや、「3日間という限られた期間では、機能を増やして面白くするよりも、自分が面白いと思うポイントを極限まで突き詰める方が良い」といったご指摘をメンターのお二方から頂き、夕会終了後にもう一度、このゲームはどうあるべきかをチームで議論しました。

その結果大きく変更したこととして以下の3点があります。

①曲全体を使い、クリアしたら次へ(1イテレーションに2、3分) → 曲の一部を切り取ったものをフレーズとしてたくさん用意し、それらをランダムにループ(1イテレーションに5~7秒)
②どこが無限なのかよく分からない → ループするたびに曲も譜面も早くなる、どこまで耐えられるかな?(明確な無限要素の追加)
③ゲームの面白さをすぐに(簡潔に)答えられない → 一言でいうと「リズムさえ取れればどこまでもいける!」

これらの変更をもとに、2日目以降のコーディングを行うことにしました。

本番2日目 (8/6)
仕様変更に対応すべくこの日も気合充分にコーディングを開始しましたが、実のところ、私が担当していたノーツの判定プログラムは仕様変更の影響を受けませんでした。キー入力がされた際の判定線とノーツの位置関係によって正しく打てているかを判定していたため、大幅な変更をする必要はありませんでした。しかし、一定条件下でノーツ格納用の配列からIndexOutOfRangeエラーが相変わらず出ていましたので、その修正にあたりました。

また、この日にコンボ表示機能と空打ち(ノーツがない時にレーンを叩くこと)したときのエフェクトを追加しました。空打ちエフェクトは、ノーツヒット時にエフェクトを出すスクリプトを再利用することで、実現までの時間を短縮できました。

お昼ごろにはゲームのメインループとは行かないまでも、リロードすれば一回分のプレイができるようになったので、WebGLビルドを実行して動作を確認しました。順調に動作しているのを見て、少しだけ完成形が見えたように感じました。

その後はノーツが重なっているとうまく判定できない問題や、ゲームオーバー後にクリアに遷移する問題を解消して、判定面でのストレス削減を目指しました。

私がひたすら判定関係の向上に努める一方で、メンバーの一人はゲームのメインループをこの日の夜に完成させ、もう一人はサウンドやエフェクトを追加してゲームの表現をリッチにするなど、役割をうまく分担できていました。

中間発表

本番2日目の昼過ぎに中間発表&それに対するメンター陣からのフィードバック会がありました。

昼過ぎの段階ではまだゲームループも完成しておらず、見た目も寂しい状態で、さらに判定にもバグが残っているなど、どのチームよりも恐らく完成度が低い状態だったと思います。他のチームの発表を見ていく中で焦る気持ちを感じましたが、1日目夜の軌道修正と、メンターさんからフィードバックを頂いたことでこれから何を改善していくかが明確になったと共に、ゴールが見えている安心感もあったように思います。

とはいえとにかく時間が足りないので、この中間発表を境にチームの開発速度を上げていきました。


本番3日目 (8/7)
いよいよ最終日を迎え、開発に最後のスパートを掛けました。メインループが完成しているのでシステム部分にはこれ以上手を加えず、エフェクトやフィードバックに力を入れる方針にシフトしました。

この日私が担当したことは大きく3つあります。

1つ目は、今までレーンを叩くためのキーバインドが上下キーしかなかったことを受け、上下レーンを叩くためのキーをそれぞれ3種類加えたことです。

2つ目は、コンボやフェーズが増えるたびにちょっとしたアニメーションを再生してインゲームの表現を豊かにすることです。

3つ目に、ノーツを叩いたときの判定をPerfect、Good、Badに分類し、「連打していればどこまでもいける!」ではなく、「リズムさえ取れればどこまでもいける!」という当初のコンセプトに沿ってゲームをプレイしてもらえるよう改良しました。

特に3つ目の作業に関してはシステム面を変更することになるため、開発終了間際になって不具合が出ないか心配でしたが、意外なほどすんなり実装できました。

ただしこの変更含め、このインターン中に私が書いたコードはとりあえず目的を達成するためのものになっていて、可読性や保守性の観点からは良くないコードになっているため、改善の余地があると考えています。ひとまずはデザインパターンを勉強することで、メンテナンスしやすいコード設計ができるようにしたいです。

さて、昼過ぎにはほぼ最終版のゲームをビルドできており、15時に開発終了となるため、あと2時間ほどで何を改善できるかをチームで考えていました。この最後の2時間では、私はゲーム全体を何度もプレイしてバグがないかの確認(なんと判定面にはまだバグが残っていました・・見つかってよかった!)、他メンバーは音量バーの実装や、フェーズ更新後に背景の上部と下部の色合いをグラデーションで変化するようシェーダーを書いて、チーム全員でゲームを良くするために最後まで粘り強く取り組みました。

そうして開発終了時刻となり、最終発表用のスライドを作りながら、ひとまずお疲れ様と、お互いを労いました。

最終発表

私のチームの最終成果物はこのようになりました。
youtu.be
(公開に関して、使用楽曲の利用規約にカットして組み合わせるような用途に関して明記されていないため、現在確認中です。ひとまずBGMなしバージョンをお楽しみ下さい・・)


他のチームとの完成度の違いにドキドキしていた中間発表に比べ、最終発表では他チームの作品にも負けていないぞと、自信を持って臨むことができました。

計7チームのうち、上位3チームが表彰されることとなり、残念ながら入賞はできませんでした。しかし、作ったゲームの出来に後悔はしていませんし、この10日間ほどを他のチームも全力で走り切ったことが分かっているからこそ、素直な気持ちで称賛できました。

最終発表後はまずチームでのフィードバック会があり、ゲーム内容に関して改善できることと、さらには開発の進め方やコミュニケーションの部分でもアドバイスを頂きました。

この最後のフィードバック会に限らず、メンターさんからの助言はよく刺さるものばかりで、恐らくはこれまでの経験に加え、第三者からの視点がそうさせているのだと思います。

開発側が面白いと思いつつも、自己満足になってしまわないようゲームを作ることは今後心に刻み続けます。

懇親会

チーム懇親会では最終発表で入賞できなかったせいか、ちょっと暗い雰囲気になってしまいましたが、改善点だけでなく自分たちの良かったところを話すのも大事だと感じました。

今回のインターンではnonpi foodbox™というフードデリバリーサービスによって最終日のご飯が支給され、懇親会ではそれを食べながら過ごしたのですが、私が注文したコースに入っていた鶏肉の炭火焼きが今までに食べたことのないレベルで美味しかったです!

チーム懇親会が終わると一度全体で集まり、2人のメンターさんからLTにご登壇いただきました。残念ながら内容はお話できないのですが、私がインターン中に感じたことと一部一致していて共感できる部分がありました。

LT後はシャッフル懇親会といって、学生とメンター陣をシャッフルしての懇親会も行われました。こちらではお互いのバックグラウンドやインターンに対する感想などを交換しました。

驚いたことに、私が割り振られたグループには高校生がおり、このとき初めてインターン参加者が専門学校生と大学生だけでないことを知りました。自分のスタートの遅さを若干恨みつつも、早いうちから積極的にインターンに参加する姿勢に刺激を受け、自分も今できることをやろうと決めました。

余談

インターン中に、Unityパフォーマンスチューニングバイブルという書籍が参加者全員にプレゼントされました!表紙がカッコいい!

Unityパフォーマンスチューニングバイブルの表紙

その名の通り、Unityでの開発におけるパフォーマンスの向上や計測について300ページほどに渡って書かれており、ハードの話からソフトの話までカバーしています。書店などで取り扱っていないようなので、大事に読んでいきたいと思います。

おわりに

伝えたいことを書いているうちにこんなに長くなってしまいました。この文章自体は準備期間のときから少しずつ書き進めており、インターン終了から2日後に完成した次第です。書いているうちにその時の気持ちや言われたことが思い出されてきて、言語化することは大事だと改めて感じました。

今回のインターンでは、楽しみながら開発できたことが最も良かったことです。得た学びを今後の開発にも活かし、さらにエンジニアとして成長したいと考えています!

チーム開発の経験を積みたい方やエンジニアとして成長したい方、ゲーム開発の面白さを味わいたい方はぜひサイバーエージェントさんのプロトスプリントリーグに参加してみて下さい!

pythonのturtleモジュールを使ってお絵かきしてみる

pythonのturtleを使っていくつか絵を書いてみました。

はじめに

turtleとは、初心者や子供向けのグラフィカルなpython学習環境です。(ソースは古いですがこのサイトを参考にしました)
これを使うと簡単に図形を描くことができます。公式ドキュメントはおそらくここです。

では、どんな事ができるのか見ていきましょう。

サンプルプロジェクト

from turtle import *
color('red', 'yellow')
begin_fill()
while True:
    forward(200)
    left(170)
    if abs(pos()) < 1:
        break
end_fill()
done()

解説
公式ドキュメントに載っているプログラムです。
begin_fill()からend_fill()までの間にペンが動いてできた領域の内側を塗りつぶしています。停止条件のabs(pos()) < 1は、はじめペンの位置が(x, y) = (0, 0)から始まることを考えると、「はじめの位置に戻ってきたら処理を停止」と読み替えても良いかもしれません。

実行結果
youtu.be

ケース1

from turtle import *
import time
import random

pendown()
ht()
speed(0)
begin = time.time()

while True:
    end = time.time()
    if end-begin > 30:
        break
        
    num = random.random()
    if(num < 0.5):
        left(90)
        color('red')
    else:
        right(90)
        color('blue')
        
    forward(10)
    
penup()
done()

解説
ランダムウォーク的なやつです。30秒間処理が続き、その間半々の確率で右か左に向きを変え、塗る色を変えたあと10進むプログラムになっています。この後も出てくるht関数ですが、hide turtleの略で、ペン先(向きを変えて進む三角形)を描画しないよう指定できます。

実行結果
youtu.be

ケース2

from turtle import *
import math

ht()
penup()
setx(-300)
sety(-300)
pendown()
speed(10)
t = 0
while True:
    t = t + 1
    if t > 150:
        break
    
    forward(40)
    left(math.sin(t/360) * 180)
    
    
penup()
done()

解説
三角関数を取り入れてらせんのような動きを表現しました。初めにsetx関数とsety関数で明示的にペン先のスタート位置を指定してからwhileループで描画を始めることで、空間を広く使っています。

実行結果
youtu.be

ケース3

from turtle import *
import random
import time

pendown()
ht()
begin = time.time()
t = 100
speed(0)
while True:
    end = time.time()
    if(end-begin > 100):
        break
    
    num = random.uniform(-300, 300)
    num2 = random.uniform(-300, 300)
    
    goto(num, num2)

penup()
done()

解説
x、yともに-300から300の範囲でランダムにペン先を移動させることで、徐々に空間が線によって埋め尽くされ、全体の輪郭が正方形に近づいていくプログラムです。

実行結果
youtu.be

ケース4

from turtle import *

pendown()
ht()
speed(0)
for i in range(500):
    if i % 2 == 0:
        left(i)
    else:
        right(i)
        
    if i % 4 == 0:
        left(180)
        
    forward(i)
    
penup()
done()

解説
このプログラムだけはwhileループでなくforループの中で描画をしています。ループ変数iが奇数のときは右に、偶数のときは左にiだけ回転し、4の倍数のときは向きを反転させ、いつでもiだけ前に進むプログラムになっています。実行結果を見るとなんだか立体的に見える気もします。

実行結果
youtu.be

おわりに

どうでしたか?20行ほどの簡潔なプログラムで複雑な図形を描画できるのがturtleのすごいところです!思い通りにペンを動かすのは難しいかもしれませんが、ランダム要素や数学関数などを取り入れることで表現の幅を広げられそうです。ぜひお試しあれ。

pyautoguiを使って超速タイパーのふりをする

pythonのpyautoguiを使って色々面白いことができそうなので、とりあえず自分が発見した使い方について書きました。


まずpyautoguiとは、クロスプラットフォームGUI操作を自動化できるpythonの外部モジュール(1)です。簡単に言えば、プログラムからマウスやキーボードを操作できるということです。

今回私が注目したpyautoguiの機能は、typewrite関数です。typewrite(text, interval)のように引数を渡すと、現在マウスカーソルのある位置に、指定したtextを時間感覚intervalで一文字ずつ打ってくれます。

例えば、typewrite('Hello, world!', 1)としたときの実行結果は
youtu.be



このように自動で打ってくれます。後から思ったのですが、実行結果だけ見ると人力で打っているのか自動で打っているのか判別できませんね・・。

さて、ここから本題に入ります。typewrite関数の2つの引数のうち、textの方を長い文章にして、intervalを短くすれば、ものすごいタイピングを見せつけることができます。

import pyautogui as ag

#type.txtに自動でタイピングしたい内容を書く
f = open('type.txt', 'r', encoding='utf-8')
text = f.read()
f.close()
ag.typewrite(text, 0.001)

長い文章を引数に渡すことを考えると、自動タイピングしたい内容をテキストファイルに書いておき、それを読み込むと簡潔に書ける気がします。
例えばtype.txtの内容を、微分法の英語版ウィキペディアから取ってくると
youtu.be
このようになります。

上手いタイミングで.pyファイルを実行することができれば、傍から見ると恐ろしく早いタイピングの使い手になれること間違いありません!

ABCでコンテスト時間中に初めてC問題が解けた

大学三年生で灰色コーダーのkedzkiestです。

AtCoder Beginner Contest 261にて初めてC問題が解けたので報告記事を書きました。

 

はじめに自分のAtCoderプロフィールがこのようになっています。

 

コンテスト自体には2021年の10月に初参加し、以降気が向いたら参加するくらいのモチベーションで取り組んできましたが、自分のアルゴリズム力に不足を感じ、2022年5月ほどから積極的にコンテストに参加しています。

今までA問題とB問題は解けてもC問題がコンテスト中に解けないことに悩まされていましたが、ABC261にて、ついに解くことができました!(しかも20分ほどで)

しかし、遅れて参加したことでunrated扱いになってしまったことが残念・・。