1日1本クソゲーを作ってみた

 1日1本クソゲーを作りました。たまってきたのでまとめました。

 はじめに

 私はUnity(とC#)は全くの初心者でJavaPythonをかじってます。最近、強化学習に興味があり「強化学習+ゲーム」で何か面白いことができそうなので、まず手始めにゲーム環境であるUnityを勉強しようと思いました。

1日1本クソゲー

 「Game A Week」という毎週1本のゲームを作るというチャレンジが流行っている中、あえて1日1本のゲームを作るチャレンジをした方がより学習効率がいいのではないか という安直な考えで見切り発車しました。結果、めちゃんこ辛かったです。

1日目 カエルの卵が追いかけてくるゲーム

 カエルの卵状のボールが追いかけてくるゲームです。チュートリアルの応用みたいなものです。
 なぜかシーンを再読み込みするときに、なぜか全体が暗くなってしまう現象が発生。調べてみると、

qiita.com

すぐ解決。なぜこんな仕様になっているんだろうか・・・


2日目 缶あて

 2日目は缶あてゲームです。昔よくスマホでプレイしたゲームだったので作ってみたくて作りました。プレイしてみたらわかると思うんですけど、とても操作性が悪い。特に上段の缶がかなり当てにくいというクソゲー。操作性のいい世の中のゲームはすごいよ・・・


3日目 迷路

 3日目は迷路。Unityと言えば「Unityちゃん」。Unityちゃんが迷路を駆け巡ります。後で気づいたのですが、止まっているのにUnityちゃんが回りだす現象が起きます。まさにクソゲー。速度のみを指定して動かしているので曲がったとき(?)などで加速度が与えられてしまって、何も操作していないと回りだしてしまうようです。この頃は「動かない+寝不足」だったので、夜中のテンションでよくわからない絵を描いてしまいました。
 動かし方は以下のサイトを参考にしました。 【Unity講座】3D人型モデルの動かし方をユニティちゃんで学ぶ【Animator】 – Unity初心者向け講座<C#>


4日目 マ◯オ

 4日目は横スクロールアクションです。マ◯オです。以前、Androidアプリ(ネイティブ)でマ◯オ系のゲームをチャレンジした時は当たり判定が難しく、ブロックにめり込んだり、瞬間移動したりとで開発をあきらめてしまいましたが、Unityを使えば何もせずともブロックにめり込むこともなければ、瞬間移動することもないのでさすがだなと思いました。

 ただ、立った壁に触れた状態でジャンプすると昇り続けるというバグがあり、プレイした友達に「カタパルト」と称されるほどの出来のクソゲーです。


5日目 避けゲー

 宝石の国風の避けゲーです。月人(Unityちゃん)が放ってくる矢(に見立てた赤い棒)を宝石(Unityちゃん)が避けるゲームです(困惑)。
 宝石の国ってすごいですよね。けもふれは同じ3dcgアニメでしたが、CG感が強かったと思います(脚本は最高)が、宝石の国は3dcgがとても自然な感じがしませんか!? ストーリも謎が謎を呼ぶ展開で面白い。気になってマンが全部読んじゃいましたが・・・。金剛がまさか人g(殴
 矢の向きは宝石がいる方向を向くはずなのですが、月人の反対側だと矢が宝石の方向に向かないバグがあります。


6日目 強制横スクロールゲー

 強制横スクロールゲームです。落ちたり、赤い棒に当たるとゲームオーバー。
 本当は「チャ◯走」のようなゲームを作ろうといましたが時間が足りなく作り方もよくわからないので、結果的にこのゲームで落ち着きました。


7日目 ボタン早押しゲー

 ボタンを時間内に早く押すだけのゲームです。UnityのButtonを使ったことがなかったので練習ついでで作ってみました。
 日本語がTextで表示できなくて悩みました。Unityのエディターの方では普通に表示されていたのに出力したら何も書かれていない状態。
 日本語の表示は以下のサイトを参考にしました。 qiita.com フォントを変えるだけでOK。


時間が足りない

 帰宅後ゲームを作るのですが、帰宅時間が日によって異なるため、作業時間がまちまちになってしまいます。また、初心者なので何をするにもある程度時間がかかってしまい、基本毎日朝方近くまで作業を続けるという苦行になってしまいました。

まとめ

 毎日1本のゲーム完成させるのは学習にはいいかもしれませんが、とてもしんどいです。開発期間が1日しかないので複雑なゲームやバグの修正ができずにアップロードするので、ゲームの完成度が著しく下がります。「Game A Week」のように一週間程度がちょうどいいのかもしれませんね。
 ただ、自作ゲームをSNS上に公開することは「いいね」されただけでもモチベが上がりますし、続けていきたいなと思いました。

ポケモントレーナーのためにポケモン図鑑を作る②

f:id:hinata_game:20171029022209j:plain 二弾、ポケモン図鑑を作っていきます!

前回

hinata-game.hatenablog.com

ポケモン図鑑の音声を作る

 アニメ版では、ポケモン図鑑ポケモンに向けると、その端末自体が自動で喋ってそのポケモンの情報を教えてくれます。なので、ポケモンの判別が終わったら音声でデータをユーザーに伝えられるようにします。

使用ソフト

  • cevio体験版

音データ

 初代のポケモンのゲームの図鑑の内容をしゃべらせたいと思います。

ポケモン 台詞
ヒトカゲ トカゲポケモン 生まれたときから、尻尾に炎が灯っている。炎が消えたとき、その命は終わってしまう。
フシギダネ ポケモン 生まれた時から背中に植物の種があって、少しずつ大きく育つ。
ゼニガメ 亀の子ポケモン 長い首を甲羅の中に引っ込めるとき、勢いよく水鉄砲を発射する。
ピカチュウ ねずみポケモン ほっぺたの両側に小さい電気袋を持つ。ピンチの時には放電する。


 cevioを用いて、今回判別するポケモン(御三家+ピカチュウ)の図鑑データを音声ファイルにします。 cevio初めて使いましたけど本当にすごいわ。かなり滑らかに発音しますね。

f:id:hinata_game:20171026194918p:plain

図鑑プログラム

 ①で作った学習ファイルをKerasに読み込ませて判別します。判別できたら、cevioで作った音声ファイルをpyaudioを用いて再生します。

import keras
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras import backend as K
import numpy as np
import glob
import cv2
import random
import math
import time
import pyaudio
import wave
import threading
import time
import datetime
import sys


args = sys.argv

batch_size = 1
num_classes = 2
epochs = 1

img_rows, img_cols = 224, 224

path = args[1]

x_train = []
y_train = []
x_test = []
y_test = []

def sound(path):
    wf = wave.open(path, "r")
    p = pyaudio.PyAudio()
    stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
                    channels=wf.getnchannels(),
                    rate=wf.getframerate(),
                    output=True)
    chunk = 1024
    data = wf.readframes(chunk)
    while data != b"":
        stream.write(data)
        data = wf.readframes(chunk)
    stream.close()
    p.terminate()

def makeData(path):
    img_src = cv2.imread(path)
    w,h,c = img_src.shape
    if w < h:
        img_src = img_src[:,(h-w)//2:(h+w)//2,:]
    elif w > h:
        img_src = img_src[(w-h)//2:(w+h)//2,:,:]
        
    dst = img_src[:,:,0]
    dst = cv2.resize(dst,(img_rows, img_cols))
    dst2 = img_src[:,:,1]
    dst2 = cv2.resize(dst3,(img_rows, img_cols))
    dst3 = img_src[:,:,2]
    dst3 = cv2.resize(dst4,(img_rows, img_cols))
    x_train.append([ dst.tolist(), dst2.tolist(), dst3.tolist()] )

makeData(path)
x_train = np.array(x_train)
if K.image_data_format() == 'channels_first':
    x_train = x_train.reshape(x_train.shape[0], 3, img_rows, img_cols)
    input_shape = (3, img_rows, img_cols)
else:
    x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 3)
    input_shape = (img_rows, img_cols, 3)
x_train = x_train.astype('float32')
x_train /= 255
print('x_train shape:', x_train.shape)
print(x_train.shape[0], 'train samples')


from keras.models import model_from_json

model = model_from_json(open('cnn.json').read())

model.load_weights('cnn.h5')
data = model.predict_classes( x_train , batch_size=1, verbose=0)

if data[0] == 0:
    #ヒトカゲ
    print("ヒトカゲ")
    print("とかげポケモン")
    print("たかさ  0.6m")
    print("おもさ  8.5kg")
    print("うまれたときから しっぽに ほのおが")
    print("ともっている。ほのおが きえたとき")
    print("その いのちは おわって しまう。")
    sound("data/hitokage.wav")
elif data[0] == 1:
    #フシギダネ
    print("フシギダネ")
    print("たねポケモン")
    print("たかさ  0.7m")
    print("おもさ  6.9kg")
    print("うまれたときから せなかに")
    print("しょくぶつの タネが あって")
    print("すこしずつ おおきく そだつ。")
    sound("data/hushigi.wav")
elif data[0] == 2:
    #ピカチュウ
    print("ピカチュウ")
    print("ねずみポケモン")
    print("たかさ  0.4m")
    print("おもさ  6.0kg")
    print("ほっぺたの りょうがわに")
    print("ちいさい でんきぶくろを もつ。")
    print("ぴんちのときに ほうでんする")
    sound("data/pikatyu.wav")
elif data[0] == 3:
    #ゼニガメ
    print("ゼニガメ")
    print("かめのこポケモン")
    print("たかさ  0.5m")
    print("おもさ  9.0kg")
    print("ながい くびを こうらのなかに")
    print("ひっこめるとき いきおいよく")
    print("みずでっぽうを はっしゃする。")
    sound("data/zenigame.wav")

図鑑実行

 ピカチュウをイラストソフトで描いてみましたw
 なんだか生気の薄いピカチュウになってしまいました。このピカチュウを判別にかけたいと思います。 f:id:hinata_game:20171029012953j:plain

実行結果

 きちんと「ピカチュウ」と識別されました。動画が無いとわからないとおもいますが、図鑑の説明の音声も再生されました。

C:\Users\aaaaa\Documents\pokemon>python zukan.py C:\Users\aaaaa\Pictures\pikatyu.jpg
Using TensorFlow backend.
x_train shape: (1, 224, 224, 3)
1 train samples
ピカチュウ
ねずみポケモン
たかさ  0.4m
おもさ  6.0kg
ほっぺたの りょうがわに
ちいさい でんきぶくろを もつ。
ぴんちのときに ほうでんする

しかし、、

 イラストはピカチュウでも、背景がオレンジ色(ヒトカゲの色)だとヒトカゲに認識。
 ほかの色(青:ゼニガメ、緑:フシギダネ)の場合では、背景色に惑わされること無くピカチュウに判別してくれたのですが、オレンジ色だとうまく判別してくれませんでした。色の要素でもポケモンの種類を決めているような感じがする。 f:id:hinata_game:20171029014742j:plain

実行結果

 ヒトカゲと判別したため、ヒトカゲの図鑑情報が表示

C:\Users\aaaaa\Documents\pokemon>python zukan.py C:\Users\aaaaa\Pictures\b_pikatyu.jpg
Using TensorFlow backend.
x_train shape: (1, 224, 224, 3)
1 train samples
ヒトカゲ
とかげポケモン
たかさ  0.6m
おもさ  8.5kg
うまれたときから しっぽに ほのうが
ともっている。ほのうが きえたとき
その いのちは おわって しまう。

実行動画

 こんな感じに動作しました。PCのスペックが貧弱なのでtensorflowの読み込みがとにかく遅いです。

おわりに

 今回もコードの解説が無くてすいません。これで一応、ポケモン図鑑の情報を伝えられるようになりました。

 Deeplearning初心者なので、アドバイスをいただければ幸いです!

ポケモントレーナーのためにポケモン図鑑を作る①

f:id:hinata_game:20171029022209j:plain
 ポケモン図鑑をこれから作っていきたいと思います。まずはポケモンの判別プログラム(CNN)から作っていきます。

次回

hinata-game.hatenablog.com

とりあえず初代の御三家から

 いきなり数百種類のポケモンを判別するのは大変そうだし、何よりデータセットを作るのが大変。なので、御三家(ヒトカゲフシギダネゼニガメ)+ピカチュウに絞ります。
 ポケモントレーナーが、最初に渡されるポケモンを知らないかもしれませんしね。 f:id:hinata_game:20171026013406j:plain

使用した環境

 今回は以下の環境で学習しました。

 お気づきだろうか。
 友達に言ったら「へー、Core2quad使ってるんだ(笑)」と冷笑されるほどの糞スペック。
 「機械学習をやってみた系」のPC構成では見たことがないぐらいの前世代PC。でも、まだ戦える!!

データの準備

 調べてみると、Deeplearningは数千枚オーダーの画像がそれぞれ必要なようです。ですが、数千枚はさすがに面倒なので、Google画像検索でヒットした画像各200,300枚程度をかさ増し(約6倍)して正方形の画像に加工し使っています。
 

学習

 学習ライブラリはTensorflowベースのKerasを用いました。もちろんCPUモードでの学習になります。逆にこの構成のPCって最新のグラフィックボードを増設できるのだろうか。
 学習モデルはCNNで、 Keras : 画像分類 : AlexNet – PyTorchのAlexNetを元に使用させていただきました。
 AlexNetって本当に優秀ですね。自作のCNNモデルだと全然学習が進まないのに、AlexNetだとどんどん学習が進む。 コードが汚くてすいません。もっと短く書けそう。。

import keras
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import BatchNormalization
from keras import backend as K
import numpy as np
import glob
import cv2
import random
import math

batch_size = 100
num_classes = 4
epochs = 20

img_rows, img_cols = 224, 224

x_train = []
y_train = []
x_test = []
y_test = []

def makeData(path,ans):
    img_src = cv2.imread(path)
    dst = img_src[:,:,0]
    dst = cv2.resize(dst,(img_rows, img_cols))
    dst2 = img_src[:,:,1]
    dst2 = cv2.resize(dst2,(img_rows, img_cols))
    dst3 = img_src[:,:,2]
    dst3 = cv2.resize(dst3,(img_rows, img_cols))
    x_train.append([ dst.tolist(), dst2.tolist(), dst3.tolist()] )
    y_train.append(ans)

label = -1
for i in glob.glob('train/*'):
    if 'not_use' in i:
        continue
    if '.' not in i:
        label += 1
        print(i + "のラベル" + str(label))
        for j in glob.glob(i + '/*'):
            if '.jpg' in j:
                makeData( j, label)

for i in range(200):
    num = int(random.random() * len(x_train))
    x_test.append(x_train.pop(num))
    y_test.append(y_train.pop(num))

x_train = np.array(x_train)
y_train = np.array(y_train)

x_test = np.array(x_test)
y_test = np.array(y_test)

if K.image_data_format() == 'channels_first':
    x_train = x_train.reshape(x_train.shape[0],5,img_rows,img_cols)
    x_test = x_test.reshape(x_test.shape[0], 5, img_rows, img_cols)
    input_shape = (5, img_rows, img_cols)
else:
    x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 3)
    x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 3)
    input_shape = (img_rows, img_cols, 3)

x_train = x_train.astype('float32')
x_test = x_test.astype('float32')

x_train /= 255
x_test /= 255

y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

model = Sequential()

model.add(Conv2D(48, 11, strides=3,input_shape=input_shape, activation='relu', padding='same'))
model.add(MaxPooling2D(3, strides=2))
model.add(BatchNormalization())

model.add(Conv2D(128, 5, strides=3, activation='relu', padding='same'))
model.add(MaxPooling2D(3, strides=2))
model.add(BatchNormalization())

model.add(Conv2D(192, 3, strides=1, activation='relu', padding='same'))
model.add(Conv2D(192, 3, strides=1, activation='relu', padding='same'))
model.add(Conv2D(128, 3, strides=1, activation='relu', padding='same'))
model.add(MaxPooling2D(3, strides=2))
model.add(BatchNormalization())

model.add(Flatten())
model.add(Dense(2048, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(2048, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))

model.compile(loss=keras.losses.categorical_crossentropy,optimizer=keras.optimizers.Adadelta(),metrics=['accuracy'])

model.fit(x_train, y_train,batch_size=batch_size,epochs=epochs,verbose=1,validation_data=(x_test, y_test))

model_json_str = model.to_json()
open('cnn.json', 'w').write(model_json_str)
model.save_weights('cnn.h5');

score = model.evaluate(x_test, y_test, verbose=0)

print('Test loss:', score[0])
print('Test accuracy:', score[1])

結果

 実行結果は以下のようになりました。

Using TensorFlow backend.
train\hitokageのラベル0
train\hushigidaneのラベル1
train\pikatyuのラベル2
train\zenigameのラベル3
(224, 224, 3)
(6556, 224, 224, 3)
x_train shape: (6556, 224, 224, 3)
6556 train samples
200 test samples
Train on 6556 samples, validate on 200 samples
Epoch 1/25
6556/6556 [==============================] - 412s - loss: 1.0334 - acc: 0.5961 - val_loss: 1.4738 -
val_acc: 0.3400
Epoch 2/25
6556/6556 [==============================] - 408s - loss: 0.6979 - acc: 0.7088 - val_loss: 1.1469 -
val_acc: 0.4150
Epoch 3/25
6556/6556 [==============================] - 409s - loss: 0.6265 - acc: 0.7431 - val_loss: 0.9915 -
val_acc: 0.5400
Epoch 4/25
6556/6556 [==============================] - 407s - loss: 0.5694 - acc: 0.7677 - val_loss: 0.7536 -
val_acc: 0.6650
Epoch 5/25
6556/6556 [==============================] - 409s - loss: 0.5232 - acc: 0.7784 - val_loss: 0.7851 -
val_acc: 0.6850
Epoch 6/25
6556/6556 [==============================] - 405s - loss: 0.4937 - acc: 0.7979 - val_loss: 1.2362 -
val_acc: 0.5800
Epoch 7/25
6556/6556 [==============================] - 403s - loss: 0.4510 - acc: 0.8208 - val_loss: 0.6217 -
val_acc: 0.7450
Epoch 8/25
6556/6556 [==============================] - 403s - loss: 0.4028 - acc: 0.8388 - val_loss: 0.5831 -
val_acc: 0.7650
Epoch 9/25
6556/6556 [==============================] - 408s - loss: 0.3928 - acc: 0.8411 - val_loss: 0.4908 -
val_acc: 0.8300
Epoch 10/25
6556/6556 [==============================] - 409s - loss: 0.3528 - acc: 0.8565 - val_loss: 1.6967 -
val_acc: 0.5750
Epoch 11/25
6556/6556 [==============================] - 407s - loss: 0.3120 - acc: 0.8763 - val_loss: 0.5483 -
val_acc: 0.8050
Epoch 12/25
6556/6556 [==============================] - 409s - loss: 0.2787 - acc: 0.8868 - val_loss: 1.0185 -
val_acc: 0.7200
Epoch 13/25
6556/6556 [==============================] - 408s - loss: 0.2538 - acc: 0.9012 - val_loss: 2.0394 -
val_acc: 0.6150
Epoch 14/25
6556/6556 [==============================] - 406s - loss: 0.2131 - acc: 0.9137 - val_loss: 0.6719 -
val_acc: 0.7850
Epoch 15/25
6556/6556 [==============================] - 402s - loss: 0.2005 - acc: 0.9218 - val_loss: 1.2824 -
val_acc: 0.6750
Epoch 16/25
6556/6556 [==============================] - 403s - loss: 0.1903 - acc: 0.9253 - val_loss: 1.2046 -
val_acc: 0.7050
Epoch 17/25
6556/6556 [==============================] - 408s - loss: 0.1525 - acc: 0.9433 - val_loss: 0.7733 -
val_acc: 0.7750
Epoch 18/25
6556/6556 [==============================] - 408s - loss: 0.1384 - acc: 0.9475 - val_loss: 1.4794 -
val_acc: 0.6550
Epoch 19/25
6556/6556 [==============================] - 409s - loss: 0.1386 - acc: 0.9466 - val_loss: 0.8382 -
val_acc: 0.8100
Epoch 20/25
6556/6556 [==============================] - 408s - loss: 0.1092 - acc: 0.9565 - val_loss: 2.3111 -
val_acc: 0.5850
Epoch 21/25
6556/6556 [==============================] - 404s - loss: 0.0981 - acc: 0.9660 - val_loss: 0.9810 -
val_acc: 0.7800
Epoch 22/25
6556/6556 [==============================] - 402s - loss: 0.1022 - acc: 0.9620 - val_loss: 1.4912 -
val_acc: 0.7400
Epoch 23/25
6556/6556 [==============================] - 403s - loss: 0.0797 - acc: 0.9677 - val_loss: 1.0995 -
val_acc: 0.8000
Epoch 24/25
6556/6556 [==============================] - 407s - loss: 0.0895 - acc: 0.9657 - val_loss: 0.7260 -
val_acc: 0.8050
Epoch 25/25
6556/6556 [==============================] - 409s - loss: 0.0896 - acc: 0.9687 - val_loss: 1.0438 -
val_acc: 0.7750
Test loss: 1.04382741451
Test accuracy: 0.775

 少ないデータをかさ増ししたからなのか、精度は77.5%とあまりよくないですが、大体分別出来ているみたいです。よかった。
 

おわりに

 コードを貼り付けただけで特に解説等なくてすいません。以上である程度の精度でポケモンを判別できるようになりました。

 汚いコードですいません。Deeplearning初心者なので、アドバイスをいただければ幸いです!