スクリーンショット_2019-01-03_14

【長文】プログラミング初心者が40時間ゲーム開発ハッカソンをやってきた

あけましておめでとうございます。
サトウダイスケです。

年末の12月28日~30日にかけて、長野県の佐久の古民家をAirbnbで借りて40時間ゲーム開発ハッカソンをやってきました。こちらのnote記事はその記録です。めっちゃ楽しかった!


どんな流れでハッカソンをしたのか...

長野に向かったメンバーは「夜空と交差する森の映画祭」の森の映画祭実行委員会で出会った仲間たちを中心に、主に開発組である挑戦者3名審査員の5名。2チームに別れて三日間を過ごしました。

開発挑戦者は自分サトウダイスケと、たくにしむSHRIMPの3名です。3人ともゲーム開発は初心者。3週間前の森の映画祭の忘年会で「いつかゲームを作りたいね〜」という話になって、Kewpeaさんの勢いで実施されたハッカソンであります。

なので、3人ともその飲み会以降に仕事やらの合間を縫ってプログラミングやドット絵などを勉強しはじめたという...ハッカソン合宿の2週間前に開始の火蓋は切って落とされたということになります。

選んだゲームエンジンは、たくにしむSHRIMPの2名はUnityサトウダイスケは過去記事にもある通り、Luaを使ったLOVE2dを選択。

ちなみに自分がプログラミングの勉強をはじめた日はこちらの記事です。

残りの審査員組は何をしていたかと言うと、開発者たちが心地よく開発に集中できるための交通や飲食のインフラを整えてくださいました。とても美味しい鍋や手巻き寿司などをみんなでつつきました。幸せな環境をありがとう。

あとの時間は、温泉や蕎麦やスマブラSPやマリオパーティーやお酒を楽しまれておられたようです。旅行組です。そりゃそうだ!

ふすまを一枚挟んで、世界が異なっていたそうです。開発部屋内では各々が完全に自分の世界に入り込んでいたため、5時間ほど会話が一切生まれないことなんてザラ。隣から「ファルコーンパンチ!」や「日本酒空いた」が聞こえようとも、一切気にしません。審査員のみなさんが用意してくれたホットコーヒーを飲みながら没入です。

腕立て勝負をしてても知りません。(LINEグループのアルバムにあった画像で、どんな文脈で腕立てしてるかは知らないけど笑)

開発組、審査員組共にめいいっぱい楽しむことが出来た合宿でした。よき2018年の締めくくりをできた!感謝!


ゲーム開発ハッカソンのゴール

40時間でゲーム開発をするといってもどんな内容だったのかというと...

1)制限時間
28日15時から30日8時までの41時間(約40時間)でゲームをつくりあげる

2)ごはん
28日晩飯 / 29日晩 / 30日朝(審査兼)は全員でごはんを食べること
※29日朝 / 29日昼 は好きなタイミングでOK

3)レクリエーション

一部のタイミングでレクリエーションが行われペナルティが課される

4)発表

最終日の朝8時に一人10分の持ち時間で発表

5)評価基準

-プロダクトの出来(グラフィック、音楽、ストーリー、演出、楽しさ)
-自分でいかに手を動かして作っているか
-プレゼン

といった感じで、最終日の朝の発表に向けてゲームをつくる感じ。もちろんゲームの内容やプログラミング、イラストまで全部お手製。音楽をつくる開発者もいたり。総合格闘技感がつよかった!



開発組でどんなことが起きていたのか(自分視点)



1日目の出来事


さて、開発部屋ではどんなことが起きていたのか覚えている限りで思い出して記していきたいなと思います。もちろん自分視点で。

上にもある通り、それぞれ会話すらしていない時間帯もあるし、情報交換もあまりしないので、たくにしむSHRIMPは別で記事を書いてくれることを楽しみにしています。


 ■ 1日目 12月28日 9:30

高田馬場駅前のロータリーに集合。
ワゴンで古民家に向かいます。


 ■ 1日目 12月28日 12:00

「あ〜はらへった」という不思議な名前の定食屋さんでお昼を食べる。


 ■ 1日目 12月28日 13:00

古民家に到着!めっちゃいい家だった。
さすがスーパーホストさん。細かい気遣いがありがたい。


 ■ 1日目 12月28日 15:00

いよいよゲーム開発開始です。
まだ部屋には陽の空気が漂っていますね!


 ■ 1日目 12月28日 15:10

それぞれ作る内容は考えてはいたので、その実現のために勉強しなおしたり素材やらをつくりはじめます。

自分はとりあえず、「自動販売機に缶を補充するパズルアクションゲーム」をつくりたいと思っていたので、缶を掴むグラブシステムの構築からスタート。

事前につくっていたタスクリストがあるので載せておきます。
達成できたものとできてないものがあります。

 1)画面範囲の定義付け
 2)グラブシステムの構築
 3)缶のグラブ判定範囲とその一般化
 4)拾うアニメーションの作成
 5)落とした際のアニメーション作成
 6)画面全体のラフデザインを作成
 7)缶ダンボールのマウスオーバー対応
 8)4種類の缶に対応
 9)自動販売機の開閉とそのアテンション
10)自動販売機の飲み物の種類がランダムで設定
11)それに紐付いて開いた際の補充場所の種類も決定
12)缶を持った状態でドロップで缶が入る
13)そのアニメーションを作成
14)誤った缶を入れた場合のNG判定
15)それぞれの補充場所に補充可能個数を設定
16)最大個数と1個前の場合のイラストを設定
17)全部埋まった場合に音や文字が出て自動的に閉まる
18)次の自動販売機が出てくる仕様
19)自動販売機のアニメーションや種類を詰める
   ※自販機の広告枠にネタをいっぱい入れる
20)制限時間とスコアを設定する
   ※ここでゲームとしてはある程度成立
21)タイトル画面をつくる
22)スタートする際のカウントダウン
23)制限時間アウトした際の演出
24)スコアの第三位までをタイトル画面に表示
25)最終的なゲームバランスを調整(上下変更)


 ■ 1日目 12月28日 15:15
「画面範囲の定義付け」

ひとまず画面のサイズ定義は、1360*640に設定。


 ■ 1日目 12月28日 15:30〜17:00
「グラブシステムの構築」
「缶のグラブ判定範囲とその一般化」

グラブシステムで使用するマウスカーソルの画像をAespriteで描いてスプライトを作成。Aespriteはかゆいところにも手が届く最高のツールです。

デフォルトのマウスカーソルを消します。

love.mouse.setVisible(false)

そしたら、上のグラブ用のカーソルに置き換えます。

local x, y = love.mouse.getPosition()

    love.graphics.setColor(255, 255, 255 , 255)
    love.graphics.draw(cursol , cursol_sprite, x , y , 0 , pixel_ratio , pixel_ratio , 8 , 8)

適当に作ったオブジェクトを掴めるようにします。

function love.load()

	love.graphics.setDefaultFilter('nearest', 'nearest')

    -- ピクセルの比率
    pixel_ratio = 3

    -- デフォルトのマウスカーソルを隠す
	love.mouse.setVisible(false)

	cursol = love.graphics.newImage('img/cursol.png')
	cursol_sprite = love.graphics.newQuad(0 , 0 , 16 , 16 , cursol:getDimensions())
	cursol_animation = {}
	cursol_animation = {status=1 , fps=30 , num_frames=3 , xoffset , timer=1 , frame=1}
    mouse_pressed = 0
    button = 0

	can = love.graphics.newImage('img/can.png')
	can_x = 100
	can_y = 100
	grab = 0

  	se_grab = love.audio.newSource("se/handshake1.mp3", "static")
  	se_grab2 = love.audio.newSource("se/socket-insert1.mp3", "static")

end

function love.update(dt)

	if dt > 0.035 then return end	--処理落ち対策

	-- カーソルのアニメーション
	if mouse_pressed == 0 then
			cursol_animation.timer = cursol_animation.timer - dt
			if cursol_animation.timer <= 0 then
				cursol_animation.timer = 1 / cursol_animation.fps
				cursol_animation.frame = cursol_animation.frame - 1
				if cursol_animation.frame < 1 then
					cursol_animation.frame = 1
					cursol_animation.status = 1
				end
				cursol_animation.xoffset = 16 * cursol_animation.frame
				cursol_sprite:setViewport(cursol_animation.xoffset-16, 0, 16, 16)
		end
	elseif mouse_pressed == 1 then
			cursol_animation.timer = cursol_animation.timer - dt
			if cursol_animation.timer <= 0 then
				cursol_animation.timer = 1 / cursol_animation.fps
				cursol_animation.frame = cursol_animation.frame + 1
				if cursol_animation.frame > cursol_animation.num_frames then
					cursol_animation.frame = cursol_animation.num_frames
					cursol_animation.status = 0
				end
				cursol_animation.xoffset = 16 * cursol_animation.frame
				cursol_sprite:setViewport(cursol_animation.xoffset-16, 0, 16, 16)
		end
	end

	-- マウスの右クリックが押された
	
	function love.mousepressed(x, y, button, istouch)
		if button == 1 then
		  	mouse_pressed = 1  -- 「押された」状態にする
		  	if can_x-pixel_ratio*8 < x and x < can_x+pixel_ratio*8 and
		  		can_y-pixel_ratio*8 < y and y < can_y+pixel_ratio*8 then 
			   grab = 1
			   love.audio.stop(se_grab2)
			   love.audio.play(se_grab2)
		  	end
		end
	end

	-- マウスの右クリックが離された
	function love.mousereleased(x, y, button, istouch)
		if button == 1 then
			mouse_pressed = 0  -- 「離された」状態にする
			if grab == 1 then
				can_x = x
				can_y = y
			end
			grab = 0
		end
	end

end

function  love.draw()

	-- 背景色
    love.graphics.setColor(255, 255, 255 , 255)
    love.graphics.rectangle("fill", 0, 0, window_w,  window_h) 

    -- カーソル
	local x, y = love.mouse.getPosition()

    love.graphics.setColor(255, 255, 255 , 255)
    love.graphics.draw(cursol , cursol_sprite, x , y , 0 , pixel_ratio , pixel_ratio , 8 , 8)

    -- 缶
    if grab == 1 then
		love.graphics.draw(can , x , y , 0 , pixel_ratio ,pixel_ratio , 8 , 8)
	elseif grab == 0 then
	    love.graphics.draw(can , can_x , can_y , 0 , pixel_ratio ,pixel_ratio , 8 , 8)
	end

	-- 確認用
    -- love.graphics.setColor(255, 0, 0 , 255)
    -- love.graphics.rectangle("fill", x , y , 0 , 2 , 2 , 1 , 1)
    -- love.graphics.print(button , 10 , 10)
    -- love.graphics.print(mouse_pressed , 10 , 30)
    -- love.graphics.print(x , 10 , 50)
    -- love.graphics.print(y , 10 , 70)

	-- command+F5で更新する関数
	love.keypressed = function(key, unicode)
	  if 'r' == key then
	    love.filesystem.load('main.lua')()
	    love.load()
	  end
	end

end

スプライトのアニメーションの切り替えとかは一番最初のLOVE2d記事で書いてました。

ひとまずこんな感じでものを掴めるようになりました。


 ■ 1日目 12月28日 17:00〜17:30
「拾うアニメーションの作成」

缶を拾うアニメーションをつくります。まあ、その前に缶を90度rotateしたりしなければなんですけど、π(パイ)使うんですね。ユータッチマイπ!

math.pi/4

rotateの引数に上記のコードで90度回転します。

右クリックされたとき、y座標を少し下方にズラしたところに缶の画像が現れて、マウスカーソルの現在位置のy座標まで-1ずつズラしていって、座標が一致したときに停止する感じで掴めるようにしました。


 ■ 1日目 12月28日 17:30〜18:00
「落とした際のアニメーションの作成」

これは上記と同様の手法でクリックが離されたら下方にy座標がズレていく感じで。このとき、9.8を加速度として重力加速度gに則ってみました。物理がこんなところで役に立つとは。


 ■ 1日目 12月28日 18:00〜19:30
「画面全体のラフデザインの作成」

ゲーム画面のドット絵を描きました。


■ 1日目 12月28日 19:30〜20:30 // 6時間ほど経過
晩ごはん

初日の夜は鍋でした!
美味しかった!

そして、長野県在住の森の映画祭スタッフも来てくれてレッドブルの差し入れをいただきました。


■ 1日目 12月28日 20:30〜21:00
「缶ダンボールのマウスオーバー対応」
「4種類の缶に対応」

ここはクリックできるエリアだよ!ということを視覚的にわかりやすくするために、マウスオーバーで色が変わるようにしました。ひとつの範囲でうまくできたので4つとも対応。


■ 1日目 12月28日 21:00〜21:30
「自動販売機の開閉とそのアテンション」
「自動販売機の飲み物の種類がランダムで設定」

自動販売機が開いた状態のイラストも描いたのでクリックで開閉できるようにしました。本当は「OPEN!」みたな吹き出しをつける予定でしたが面倒なので端折りました。

あと、飲み物の位置が自動的にランダムで配置されるようにしました。ランダムのはずなのに種類が結構被るのはなにゆえ...?

can_position = {pos11 = math.random(1,4), pos12 = math.random(1,4), pos13 = math.random(1,4),
		pos21 = math.random(1,4), pos22 = math.random(1,4), pos23 = math.random(1,4),
		pos31 = math.random(1,4), pos32 = math.random(1,4), pos33 = math.random(1,4),}

can_11 = love.graphics.newQuad(5*(can_position.pos11-1) , 0 , 5 , 10 , can:getDimensions())
can_12 = love.graphics.newQuad(5*(can_position.pos12-1) , 0 , 5 , 10 , can:getDimensions())
can_13 = love.graphics.newQuad(5*(can_position.pos13-1) , 0 , 5 , 10 , can:getDimensions())

can_21 = love.graphics.newQuad(5*(can_position.pos21-1) , 0 , 5 , 10 , can:getDimensions())
can_22 = love.graphics.newQuad(5*(can_position.pos22-1) , 0 , 5 , 10 , can:getDimensions())
can_23 = love.graphics.newQuad(5*(can_position.pos23-1) , 0 , 5 , 10 , can:getDimensions())

can_31 = love.graphics.newQuad(5*(can_position.pos31-1) , 0 , 5 , 10 , can:getDimensions())
can_32 = love.graphics.newQuad(5*(can_position.pos32-1) , 0 , 5 , 10 , can:getDimensions())
can_33 = love.graphics.newQuad(5*(can_position.pos33-1) , 0 , 5 , 10 , can:getDimensions())


■ 1日目 12月28日 21:30〜24:00
「それに紐付いて開いた際の補充場所の種類も決定」
「缶を持った状態でドロップで缶が入る」
「そのアニメーションを作成」
「誤った缶を入れた場合のNG判定」
「それぞれの補充場所に補充可能個数を設定」
「最大個数と1個前の場合のイラストを設定」

じれったいのでちょっとだけ一気に。
缶が決定した場所に準じて補充すべき場所と、既に入っている缶の個数をランダムで設定しました。缶が入るアニメーションは、z-indexというかレイヤーを使ってうまく中に入るように見せました。

-- リセット
if reset == true then
	-- can_position = {}
	can_position = {pos11 = math.random(1,4), pos12 = math.random(1,4), pos13 = math.random(1,4),
					pos21 = math.random(1,4), pos22 = math.random(1,4), pos23 = math.random(1,4),
					pos31 = math.random(1,4), pos32 = math.random(1,4), pos33 = math.random(1,4),}

	can_11 = love.graphics.newQuad(5*(can_position.pos11-1) , 0 , 5 , 10 , can:getDimensions())
	can_12 = love.graphics.newQuad(5*(can_position.pos12-1) , 0 , 5 , 10 , can:getDimensions())
	can_13 = love.graphics.newQuad(5*(can_position.pos13-1) , 0 , 5 , 10 , can:getDimensions())

	can_21 = love.graphics.newQuad(5*(can_position.pos21-1) , 0 , 5 , 10 , can:getDimensions())
	can_22 = love.graphics.newQuad(5*(can_position.pos22-1) , 0 , 5 , 10 , can:getDimensions())
	can_23 = love.graphics.newQuad(5*(can_position.pos23-1) , 0 , 5 , 10 , can:getDimensions())

	can_31 = love.graphics.newQuad(5*(can_position.pos31-1) , 0 , 5 , 10 , can:getDimensions())
	can_32 = love.graphics.newQuad(5*(can_position.pos32-1) , 0 , 5 , 10 , can:getDimensions())
	can_33 = love.graphics.newQuad(5*(can_position.pos33-1) , 0 , 5 , 10 , can:getDimensions())

	can_amount = {pos11 = math.random(0,2), pos12 = math.random(0,2), pos13 = math.random(0,2),
				  pos21 = math.random(0,2), pos22 = math.random(0,2), pos23 = math.random(0,2),
				  pos31 = math.random(0,2), pos32 = math.random(0,2), pos33 = math.random(0,2),}

	reset = false

end



2日目の出来事


■ 2日目 12月29日 0:00〜4:00 // 12時間ほど経過
「全部埋まった場合に音や文字が出て自動的に閉まる」
「次の自動販売機が出てくる仕様」

すべての自販機に缶の補充が完了した場合、FULLのアニメーションが出て自動的に閉まり、次の自動販売機が横から出てくる...という動きの実装をしました。

これが結構たいへんだった!

同時に新しい缶の配置にリセットされるんだけど、なぜかリセットすると次の自動販売機が出てこないというハマりをしてしまって4時間くらい費やした。ひとつずつ変数の代入やら消して原因探しをして、やっと解決。

もう深夜だったのでウトウトでした。
今思い返すとこのときが一番きつかった...
寒波でめっちゃ寒かったし...

3:00くらいまでは隣の部屋は楽しそうでしたけど、そのあとは静かになって、正直このあたりから記憶ないです。4:00くらいに寝落ちしたはず。お風呂いったのは覚えてます笑

■ 2日目 12月29日 未明

何時に起きたかあまり覚えてないけど、朝ごはん!


■ 2日目 12月29日 昼くらい
「自動販売機のアニメーションや種類を詰める」
 ※自販機の広告枠にネタをいっぱい入れる

要は自販機の外見とか広告の枠の内容のバリエーションをたくさん用意して見た目的にも飽きないようにしたかったんですけど、これは後回しにしました。広告枠には仲間がよく知っているような内輪ネタを織り込みたいという構想があったり...


■ 2日目 12月29日 昼くらい
「制限時間とスコアを設定する」
 ※ここでゲームとしてはある程度成立

制限時間とスコアの設定をしました。
これはそんなに難しくなったです。
スコアのつけかたは今度要検討かも...?

これである程度ゲームとしては成立するようになってきました。
なんとなく一安心。あとは形式を整えていく感じ。


■ 2日目 12月29日 15:00~16:00 // 24時間経過
現実逃避

開発者三人、やっと沈黙を破って、作ってもらったおにぎりや味噌汁をいただくことに。ここでやっと息抜きができた気がする。

別室のテーブルに座って食べる。

その頃、審査員のみなさんは温泉や蕎麦屋さんやお土産やさんにお出かけ中!

邪念が襲ってきた...

「スマブラSPがやりたい!!!!」

3ストックで5回戦のみという条件でこっそりニンテンドースイッチを立ち上げて大乱闘をすることに。

よい息抜きだったと思います。

「ニンテンドーすごい」

みたいなことを言いながら席に戻った気がする...笑


そして、同時にこのあたりから地面に座るのがきつくなってきたので、家中の色んな所で作業をするようになりました。


■ 2日目 12月29日 16:00
「タイトル画面をつくる」
「スタートする際のカウントダウン」
「制限時間アウトした際の演出」
「スコアの第三位までをタイトル画面に表示」

タイトル画面をつくろう...!そう思ったとき...「あ、このゲームはミニゲームとして、別のゲームの中に入れ込んでしまう」という発想が生まれてきました。

別のゲームというのは、いつか作りたいな〜と思っていた「21」という作品で、簡単に言うと「多層構造で成り立っているセカイで、レイヤーを上層や下層に移動して、イキモノが居なくなってしまった謎を紐解いていく」といったおはなし。

そんなことを思いつつ、とりあえず、スタートする際のカウントダウンと制限時間アウトをした際の演出、そして、スコアランキングの表示をタイトル画面ではなくリザルト画面で表示するのを実装しました。
※スコアランキングはプログラムが間違ってて発表時うまく表示できてなかったみたいです...


■ 2日目 12月29日 17:00〜
<「21」のドット絵を描く>

ここからは当初の計画外のプラスアルファ部分。

「21」の主人公のスプライトを描きました。
停止状態はある程度ラフで描いてあったので歩いている状態を主に。

人が歩くモーションは初めてだったので、結構時間掛かりました。
2時間は掛かったかな。

まだどことなく違和感があるけど、許容ということで。

ドット絵ってすごいよな...1ドットで全然印象が違うんだもん。


■ 2日目 12月29日 17:00〜19:00
<「21」のフィールドを描く>

自動販売機がありそうな路地を描きました。
あと、ゲーム画面のイメージも勢いで描きました。


■ 2日目 12月29日 19:00〜20:00

二日目の夜は手巻き寿司!

ゾーンに入ってたからビール飲みたいって邪念は容易に振り払えましたよ。


■ 2日目 12月29日 21:00〜22:00 // 30時間ほど経過
<歩けるようにする>

さっきまでまだまだ時間あると思ってたのに残り10時間という衝撃的な事実に気づいてしまう。なぜ新しいことを始めてしまったんだと自分を責めたのを思い出す...

以前の記事で猫を歩かせてみましたが、そのときの反省を生かして、画像の反転をプログラム上でするのではなく、反転した別の画像ファイルを用意して差し替える方式でいきました。

反転すると画像内の原点がwidth分ズレるんです...

function love.load()

	character_states = "stop_left"

        img_stop_left = love.graphics.newImage('img/stop_left_sprite.png')
	img_stop_right = love.graphics.newImage('img/stop_right_sprite.png')
	img_walk_left = love.graphics.newImage('img/walk_left_sprite.png')
	img_walk_right = love.graphics.newImage('img/walk_right_sprite.png')

	stop_left = { sprite = love.graphics.newQuad(0 , 0 , 20 , 47 , img_stop_left:getDimensions()) , fps = 3 , all_frames = 5 , xoffset = 0 , timer = 0 , frame = 1 }
	stop_right = { sprite = love.graphics.newQuad(0 , 0 , 20 , 47 , img_stop_right:getDimensions()) , fps = 3 , all_frames = 5 , xoffset = 0 , timer = 0 , frame = 1 }
	walk_left = { sprite = love.graphics.newQuad(0 , 0 , 20 , 47 , img_walk_left:getDimensions()) , fps = 12, all_frames = 6 , xoffset = 0 , timer = 0 , frame = 1 }
	walk_right = { sprite = love.graphics.newQuad(0 , 0 , 20 , 47 , img_walk_right:getDimensions()) , fps = 12, all_frames = 6 , xoffset = 0 , timer = 0 , frame = 1 }

	character_action = {}

end

function love.update(dt)

	if character_states == "stop_left" then
		character_action = stop_left
	elseif  character_states == "stop_right" then
		character_action = stop_right
	elseif  character_states == "walk_left" then
		character_action = walk_left
	elseif  character_states == "walk_right" then
		character_action = walk_right
	end

	character_action.timer = character_action.timer - dt
	if character_action.timer <= 0 then
		character_action.timer = 1 / character_action.fps
		character_action.frame = character_action.frame + 1
		if character_action.all_frames < character_action.frame then
			character_action.frame = 1
		end
		character_action.xoffset = 20 * character_action.frame
		character_action.sprite:setViewport(character_action.xoffset-20, 0, 20, 47)
	end

end

猫が歩いたときのコードよりだいぶスッキリした気がしますcharacter_actionのテーブルの中身を差し替えていく感じで簡略できた気がします。


■ 2日目 12月29日 22:00くらい
<歩くと画面がスクロールするようにする>

さっきのフィールドの画像は最初に設定した画面サイズちょうどで描いてしまったのですが、なんとなく歩いていると画面スクロールしたほうが世界の奥行きみたいなのを感じるかなと思って少しだけ左右に描き足しました。
※メニューの表示とかは固定なので伸ばしてないです。

キャラクターのx座標がある位置にくるとスクロールしだして、ある位置になると止まるようなシンプルな設定です。

love.graphics.setColor(255, 255, 255 , fade)
if x < -200 and -894 < x  then
	love.graphics.draw(field_layer2 , -240 - (x+200)/3 , 0 , 0 , pixel_ratio , pixel_ratio)
	love.graphics.draw(field_layer1 , -240 - (x+200)/3 , 0 , 0 , pixel_ratio , pixel_ratio)
	love.graphics.draw(field_layer3 , -240 - (x+200)/3 , 0 , 0 , pixel_ratio , pixel_ratio)
	love.graphics.draw(field_layer4 , -240 - (x+200)/3 , 0 , 0 , pixel_ratio , pixel_ratio)
elseif x < -894 then
	 love.graphics.draw(field_layer2 , -240 - (-894+200)/3, 0 , 0 , pixel_ratio , pixel_ratio)
	 love.graphics.draw(field_layer1 , -240 - (-894+200)/3, 0 , 0 , pixel_ratio , pixel_ratio)
	 love.graphics.draw(field_layer3 , -240 - (-894+200)/3, 0 , 0 , pixel_ratio , pixel_ratio)
	 love.graphics.draw(field_layer4 , -240 - (-894+200)/3, 0 , 0 , pixel_ratio , pixel_ratio)
else
	
	love.graphics.draw(field_layer2 , -240 , 0 , 0 , pixel_ratio , pixel_ratio)
	love.graphics.draw(field_layer1 , -240 , 0 , 0 , pixel_ratio , pixel_ratio)
	love.graphics.draw(field_layer3 , -240 , 0 , 0 , pixel_ratio , pixel_ratio)
	love.graphics.draw(field_layer4 , -240 , 0 , 0 , pixel_ratio , pixel_ratio)
end
love.graphics.setColor(255, 255, 255 , fade - 0.07)
love.graphics.draw(display , 0 , 0 , 0 , pixel_ratio , pixel_ratio)


■ 2日目 12月29日 23:00くらい
<メッセージを受け取れるようにする>

「21」では主人公は喋りません。RILA21(仮)という謎のキャラクターにデバイスを通してメッセージを受け取るようになっています。

汎用性のあるメッセージシステムを組みました。

if message_no == 1 then
		message = { text = "この世界にはかつて自動販売機というものがあったんだ。" , from = "RILA21" , time = 900 , pre_wait = 100 , post_wait = 10}
	elseif message_no == 2 then
		message = { text = "飲み物が出てくる機械で、ニンゲンは手で補充をしていたようだ。" , from = "RILA21" , time = 900 , pre_wait = 0 , post_wait = 10}
	elseif message_no == 3 then
		message = { text = "そうそう、ちょうど君の基礎能力を確認してみたかったんだ。" , from = "RILA21" , time = 900 , pre_wait = 0 , post_wait = 10}
	elseif message_no == 4 then
		message = { text = "缶の補充のお仕事をホログラムで再現してみるからやってみるかい?" , from = "RILA21" , time = 800 , pre_wait = 0 , post_wait = 200}		
end
if message_no ~= 0 and message_no ~= 5 then

love.graphics.setColor(255, 255, 255 , 255)
love.graphics.print(message.from , 85 , 36 + message_y)
love.graphics.setColor(0, 0, 0 , 255)
love.graphics.print(message.text , 210 , 53 + message_y)

	if message_received == true then
		if time < message.time + message.pre_wait then

			if message_y < 0 and time > message.pre_wait then
				message_y = message_y + 3
				if message_sound == true then
				    love.audio.play(se_message)
				    message_sound = false 
				end
			else
				time = time + 1
			end
		else
			message_y = message_y - 3
			if message_y < -150 then
				message_received = false
			end
		end
	end

	if next_message == false then
		if message_received == false then
			message_y = -150
			time = 0
			next_message = true
		end
	end

	if next_message == true then
		if time < message.post_wait then
			time = time + 1
		else
			message_received = true
			time = 0
			message_no = message_no + 1
			next_message = false
			message_sound = true
		end
	end

end

message_noが代入されると該当メッセージが流れます。
発信者や表示時間、待機時間なども設定できます。

message_noがに1を足していくと次のメッセージが現れるようになってます。


■ 2日目 12月29日 23:30くらい
ここでレクリエーション

鮮明に覚えています。メッセージのシステムがちょっと複雑で、一気に最後までコードを書ききりたかったときです。

ここで合宿の参加者が都内からやってきて1名加わって、ここで温存されていたレクリエーションが発動しました。

「イッツミーマ〜リオ!」

スイッチのマリオパーティーです。

いや、これがゲームとしてはめちゃくちゃ面白かったです。
勝った順で抜けてコーディングに戻れるというルールです。

自分は1回戦目は優勝したものの、練習みたいな扱いになったため抜けれず。そこからダメダメだったので、結局6戦くらい抜けられないという。たくにしむSHRIMPの二人は2戦目、3戦目で抜けて開発に戻ってしまいました笑 このときの自分の真顔っぷりやばかったです。

やっと戻れたとき、メッセージのシステムで何を書いてたか思い出すのに30分以上は掛かりました。地獄だったー!



3日目の出来事


■ 3日目 12月30日 1:00くらい // あと7時間ほど
<場面転換の演出をつくる>

このあたりからみんなハイテンションだった。

え?もうこんな時間?!みたいな話をしたのも覚えてるし、流れてくる音とかからお互いのゲームを推測して「早くなる系?!」「エンカウントする系?!」みたいな言葉が飛び交ってました。

この辺りが一番楽しかったかも。
※お互いが開発しているゲームの内容は知りません...!


フィールドから自販機のミニゲームに移行するために場面転換の演出をつくりました。

イメージとしては斜めの黒いマットが左右から迫ってきてガチャン!ってなる感じ笑

これも意外と時間が掛かって...

というのも、LOVE2d上で黒いマットをつくるのは簡単なんだけど、それを45度回転するのが何故かうまくいかず、結局aespriteで斜めの黒マットを作って、それをx軸方向に移動することでフェードアウトを表現しました。

※GIFアニメーションって斜めの動きに弱い?
 どうやっても映像がバグってしまう笑


■ 3日目 12月30日 3:00〜6:00 // あと2時間ほど
全体的に形になってきたので調整してくっつける

他にも細かい調整はありましたが、うまい具合に自販機のミニゲームとくっつけます。

最終的にはこんな感じに仕上がりました。


■ 3日目 12月30日  7:00 // あと1時間ほど

湯船入って朝風呂しました。
二日目は貫徹だったのでフワフワしてました。


■ 3日目 12月30日  8:00 // 終了〜

朝ごはんを食べて、いよいよ発表!

発表の順番はスマブラで勝った順位で決めていくスタイル!3ストック1戦!エイヤッ!と、ファイナルカッターがうざいと定評のあるカービィで勝利したので最後を選択。順番はSHRIMPたくにしむサトウダイスケに。


SHRIMPの作品は横スクロールのシューティングゲーム「MEGANE OF THE DEAD」。イラストからコンセプトまでめっちゃSHRIMPらしい作品だなと思った!

上の写真ではタイトル画面が全画面表示されているが、デフォルトサイズはおそらく300×300くらいで、画面の端っこで立ち上げておいても邪魔にならないサイズというコンセプト。

右に進みながらゾンビやゾンビ犬を倒していってヘリコプターを目指す。ドット絵はAespritedにて作成。


たくにしむの作品はスマホ対応を前提とした計算パズルゲーム「20」
たまたま作品名が1違いというプチ軌跡も起こる笑

言葉では表現しずらいけど、ランダムで表示される数字(1,2,3,5,10かな)を足していって20ピッタシでパッケージにして次々にGOし続けるゲーム。リズム感もよく中毒性が合って、音もGarageBandで作るというオリジナリティ溢れる作品。


そして、自分の「21」の発表。写真はないけど。

内容はさんざん上に書いてきたので割愛します。

ただ、イースターエッグで朝食での出来事(8:00を過ぎてる)をとある場所でENTERを押すと出るという隠し要素としてネタとして入れてしまったので、ルール違反と注意を受けました笑


さて、プレゼンまでは終わり実機プレイの時間。



果たして結果は...


・・・



・・・・・・



・・・・・・・・・・




1位:たくにしむ「20」

2位:サトウダイスケ「21」

3位:SHRIMP「MEGANE OF THE DEAD」


でした!

みんな40時間という限られた時間で、しかも真横に居て何を作っているかもわからず、最終的に完成させてこうやって披露できるってすごい面白いことじゃないですね...



初めてのハッカソンを終えて


そんなこんやで無事、40時間ゲーム開発ハッカソンは終了!

行き詰まって辛い瞬間はありつつ100楽しいって気分で終えることが出来た最高のイベントでした。2018年で一番楽しかったイベントのひとつ!

機会がなければ後回しになり続けていただろう夢のゲーム制作もまさかの2週間後に(ビギナーとしてだけど)できるようになっているとは思わなかったし、改めて人生は勢いからの密度だなと思いました。

開発組はいわずもがな自分と対峙してすべてを出し切ってただろうし、審査組もきっと旅行を楽しんでたし!

ご飯とか諸々のお世話してくれて感謝の気持ちでいっぱい。本当に開発に集中できた!総じて良い形式のイベントなのではと思いました!

また今年も実施できたら嬉しいなあ。

めちゃくちゃ長文でしたが以上です。
残しておきたかったので細かく書きました。


あ、

「21」の本編はできたら作りたいなって思ってます。

アニメのセル画みたいに重なったレイヤーを上層や下層に移動して、なぜイキモノがこのセカイから消えてしまったかを謎を解いていくアクションアドベンチャーゲームを予定しています。21枚目が一番上のレイヤーです。そこにすべての謎がある!

結構面白いオチが思いついたし、ゲーム開発もう少しだけ続けてみようかな。


夜空と交差する森の映画祭」関係も何かしら近いうち動きがあると思うのでお楽しみに!


さようなら!



この記事が気に入ったらサポートをしてみませんか?