kidoOooOoooOOom

ゲーム開発やってます

野蛮な進化心理学を読んで、理想的なものばかり見るのは避けようと思った

こちらの2つの進化心理学に関する記事を読んで(めちゃくちゃ面白い)、

yuchrszk.blogspot.com

note.com

マズローの欲求ピラミッドを置き換えるケンリックの欲求ピラミッドが気になったので、「野蛮な進化心理学」を買って読んでいる。

ケンリックの欲求ピラミッドに関しては7章で扱っていて、まだ3章までしか読んでないけれど、この時点でも面白いからメモを書いておく。

心の飴玉〜食べすぎに注意

本書の研究結果によると、

  • 男性であれば、グラビアアイドルなどの美女を見すぎると自分のパートナーや周りの女性に対する気持ちが揺らぐ
  • 女性であれば、社会的に成功しそうなやり手の男性ばかり見ると、自分のパートナーや周りの男性に対する献身の度合いが低くなる

ということが分かった。

これはコントラスト効果が働いていて、人間の脳は最近どんなものを体験していたかに大きく左右されている。

diveintomyself.funfairfanfare.com

この男女の理論が他分野にも適用されるのかどうか分からないが、もし適用されるのであれば

  • 魅力的な会社の情報ばかり集めていると、自社の魅力が薄れてくる
  • 優れたチームの話ばかり聞いていると、自分の所属するチームが劣って見えてくる
  • 最新の綺麗なグラフィック・快適な操作性のゲームばかりやっていると、少し前に発売されたゲームや資本力に劣るゲームを楽しめなくなる

などが起こり得るかもなぁと思った。

教訓としては、自分が大切にしているものへの影響度を減らすために、

  • 美男美女のコンテンツなどをなるべく避ける
  • 脂や砂糖が多い美味しいものばかり食べない
  • 最高峰のゲームばかり追いかけない

という自制が必要かもしれない。

殺人妄想

個人的な体験を思い返すと、誰かが暴力をしているシーンといえば「飲み会帰りの電車の中で見知らぬ男同士」が最初に思い浮かぶ。

これは本書の研究結果にも近くなっていて、男女ともに殺人妄想を抱いた相手は、

  • 男性の殺人妄想の85%は、男が相手
  • 女性の殺人妄想の65%は、男が相手

となっており、実際の殺人統計を見ても男性のほうがずっと殺人の被害者になりやすいので合致している。

また、殺人妄想の相手が知人かどうかというデータについては、

男性の59%はまったく知らない人物を殺すのを夢見たことがあり(女性は33%)、一番最初に抱いた殺人妄想が見知らぬ相手を殺すことだったと応えた男性も33%にのぼった(女性は10%)

とあり、つまり見知らぬ男性が殺人のターゲットになりやすい。

この突発的な動機が男性ほど多く見られるのは、

  • 男性のほうがテストステロンの量が多い
  • 性淘汰のために地位を確立することが重要
  • 「悪いやつら」は通常は男で、見知らぬ連中であることが多いという潜在意識

などが理由として考えられるが、理屈はどうあれ、

だが人は怒っているとき「オレはテストステロンとノルアドレナリンの上昇を体験しており、したがって繁殖の成功を確固たるものとするため、オレの地位に挑んでいるこの人物に怒鳴ったほうがいいな」などとは考えない。むしろ「このクソ野郎ときたら、まったく頭にくるぜ、何様のつもりだ!」と思っているのである。(野蛮な進化心理学 p62)

「カッとなってやった」の被害者にならないためにも、公共の場で人の地位を貶めることはしないように気をつけよう。

「レガシーコードからの脱却」を読了したが、読了の読了の読了はまだ

レガシーコードからの脱却」を読んだ。非常に良い本だと思ったので、印象に残った箇所をメモっておく。

ラクティス1: やり方より先に目的、理由、誰のためかを伝える

ここでは「顧客から要求を聞く」という前提になっているが、ゲーム開発では「企画者から要求を聞く」と置き換えて考える。基本は同じ。(まあゲーム開発ではチームの中の誰でも要求は出したりするけど)

ソフトウェア開発者ではない人がどうやって作るかというやり方まで伝えると、ソフトウェアの抽象化がうまくいかないことが起こりうる。

ソフトウェア開発はまだまだ確立されていない分野で、同じ機能を作っても開発者によって全然違う作り方になるし、その違いの中には保守性が全く無い作りになる恐れがある。

良い開発者であれば保守性も重要な関心事として捉えるので、専門家に任せて欲しいという意味でもこのプラクティスがある。

多くの場合開発者は、仕様書やユースケース記述を読みそっくりそのままコードに落とし込んでしまう。そしてそれは解決方法をあとで一般化することを困難にする。

プロダクトオーナーは開発者にやり方を教えるのでなく、終わらせてほしいことに焦点を合わせるように気をつけなければいけない。そうすることによって開発者に裁量が与えられ、より保守性の高い解決方法を思いつくことができる。

「レガシーコードからの脱却」p75より

先日ホットエントリーに入っていた、こちらのスライドでも同じテーマを感じた。

speakerdeck.com

Why?から始めることが人間をどう動かしていくかについて、こちらのTED動画でも述べている。

www.ted.com

完了と、完了の完了と、完了の完了の完了

インパクトがあった箇所。

著者は、完了には3つの定義あると言っていて、

  • 完了
    • 開発者が自分のマシンで結果を確認した状態。
  • 完了の完了
    • ビルドにも統合されている状態。
  • 完了の完了の完了
    • ビルドにも統合されていて、クリーンで保守可能になっている状態。

であり、著者が「完了」と言うときは、「完了の完了の完了」という意味で使っている。

対して自分が使う完了はここで言う「完了」か「完了の完了」くらいのことが多く、クリーンで保守可能な状態にするのは後回しにしたり辿り着かないままであったりする。

またこの記事のタイトルにも書いた通り、本を1回読み終わった時もここでいう「完了」ぐらいの状態で、再読やアウトプットによって理解を深めて「読了の読了の読了」の状態にしていくべきだと感じた。

下の記事でも再読の重要性を述べていて、新刊ばかりに飛びつかずに「読了の読了の読了」にしていかないとなぁ。。

readingmonkey.blog.fc2.com

CLEANコードを作る

CLEANコードは以下の頭文字を集めたコード品質を示す要素のこと。

  • Cohesive: 凝集性
  • Loosely Coupled: 疎結合
  • Encapsulated: カプセル化
  • Assertive: 断定的
  • Nonredundant: 非冗長

今自分がユニットテスト環境をちゃんと作れていないのは、CLEANコードを書けていないからだと自覚している。

これらの詳細については、また別途専門書を読みながら習得していく。

テストに感染する

正直、自分 & チームは、まだまだテストに感染した状態ではないなと思う。

テストファースト開発は設計方法論だ。テスト可能なコードを書くことを強制し、要件を具象化することで、開発者が高い品質のコードを書けるようにするものだ。

「レガシーコードからの脱却」p197より

より高い品質のコードが書けるチームになれるように、自分がテストの感染元となってパンデミック起こしていきたいなぁと思った。

junit.sourceforge.net

次はレガシーコード改善ガイド

先日書いた記事の通り、最近はUnity開発のテスト自動化に向けて色々試している。

kidooom.hatenadiary.jp

積読している「レガシーコード改善ガイド」にも、テストの無いレガシーコード環境をいかに改善していくかが書かれているので、ちゃんと読みながら今まで自分が産み出してきたレガシーコードを改善していかねば・・・。

抽象化がうまくなるように、いろいろな言葉とバイアスを学ぶ

9年振りに読んだ「プログラマが知るべき97のこと」から学んだことシリーズその3

その1と2はこっち

kidooom.hatenadiary.jp

kidooom.hatenadiary.jp

48 いろいろな言葉を学ぶ

いろいろな言葉を学ぶ | プログラマが知るべき97のこと

私の知る限り、優秀なプログラマプログラミング言語を巧みに操るだけでなく、自然言語も非常にうまく使うことができます。話す力が大事なのは、他人とのコミュニケーションが円滑にできるからだけではありません。自分の思考を明確にするためにも、話す能力は重要なのです。この能力は問題を抽象化する際には欠かせません。そして抽象化こそ、プログラミングの核心です。

プログラマーはコミュニケーション能力が低いオタクのような描写をされることが多いが、自分が現実世界で観測している優秀なプログラマはだいたいそんなことは無く、ブログや書籍執筆・カンファレンスでの登壇などで非常に高いコミュニケーション能力を発揮している。

抽象化がプログラミングの核心なんだなぁと日々感じることも多くなっていて、テスト容易性や変更容易性などを考慮した設計のためには、具体的なコードから作るのではなくインタフェース設計等の抽象化した階層で考える必要がある。

抽象化は、作る対象の「言葉」に精通していてこそできるのであって、先日読んだ「現場で役立つシステム設計の原則」でも紹介されているDDD(ドメイン駆動開発)のような考え方が重要だなと改めて理解した。

kidooom.hatenadiary.jp

54 見えないものを見えるように

見えないものを見えるように | プログラマが知るべき97のこと

ソフトウェアには、見えないことが良い性質をもたらすこともあれば、見えないことによる問題が起きやすい性質がある。

見えないことが良いことの例としては、「情報隠蔽」「カプセル化」「疎結合」などの依存性をなくしたり、情報を減らすことによる人間の認知コストを減らすものがある。

問題は、ソフトウェア(と人間の中身)は基本的に見えないものであるので、見えないものを見えるようにする努力をしないと以下のような問題が起きてしまう。

  • 順調そのものに見えたプロジェクトが突如、「実はこのままだと完了が半年遅れになる状況」だと判明する

  • 機能を盛りすぎた結果、スケジュールの後半に致命的なパフォーマンス問題が発生する

  • モチベーションがあると思っていた主要メンバーが、実はメンタルを病んでいて突然来なくなってしまう

「なんとなく品質良くなってきたな」とか「性能測ってないけど、まあ体感大丈夫だろ」というのはバイアスかかりまくった歪みの結果になってしまうので、ちゃんと計測できるようにしてバイアス取り除いていくことが大切。

また、よく陥りがちなバイアスも知っておきたいのも対策の一つかなと思う。

twitter.com

この中でも特に「ダチョウ効果」は、わざと見ないことにすることで問題なんて存在しなかったことにするバイアスで、人間は「見えないものを見えるように」とは逆のことも犯しうるから気をつけないといけないと思った。

カジュアル瞑想の習慣をつけていく

手軽にもっと瞑想をやっていこうと思い、「なまけ者の3分間瞑想法」を読んだ。

瞑想のことを胡散臭いと思っていた時期もあるけれど、最近は科学的検証でポジティブな実験結果が出ているので、プラセボ効果を超えた効果を期待してもいいのかもしれない。

gigazine.net

本書の中でも瞑想を「心のフィットネス」と呼んでいて、瞑想によって期待できる効果について説明している。

なまけ者の3分間瞑想法

難しい瞑想法は色々あるけれど、怠け者でもできる手軽な瞑想法として、「呼吸を数える」と「歩行と呼吸」を紹介している。

「呼吸を数える」では、単純に吸って吐いてをゆっくり数字を数えながら繰り返すだけ。

ここでポイントは、自分はいま瞑想していることを忘れないということ

集中が途切れたことに気づき次第、瞑想に戻す。

この途切れ -> 戻す のサイクルが筋トレのようなもので、自分の心を支配できるようにするための練習になっている。

(ほんとかよ と思うかもしれないが、まあそういうものだと信じておこう)

歩行瞑想の方は、ゆっくり歩きながら動かす足の裏に注意を集中する方法。

歩いているとどんどん頭の中に思考(邪念)が発生してくるので、それに気付き次第、また足の裏に集中を戻す。

自分はこの歩行瞑想を、会社の階段(1F ~ 15F)を登りながら毎日やっている。

kidooom.hatenadiary.jp

自分への思いやり、セルフコンパッション

前にもブログに書いた、セルフコンパッションの効果が瞑想にもある。

kidooom.hatenadiary.jp

自分を責めたり挫折を感じてしまった時など、ネガティブ感情が発生したことに気づき、前向きになれるように意識を集中する。

それが自然にできるようになるための練習が瞑想になる。

アインシュタインも以下のような名言を残している。

どうして自分を責めるんですか?

他人がちゃんと必要な時に責めてくれるんだから

いいじゃないですか。

Googleもやってる

Googleでも社員研修の中でマインドフルネス瞑想を実践している。

life-and-mind.com

CEDEC2019の心理的安全性に関する講演の中でも、このGoogleの研修を参考にしてマインドフルネス瞑想やセルフコンパッションを取り入れて、チームの雰囲気を改善していったと発表されていた。

kidooom.hatenadiary.jp

まとめ

「体を鍛えるように、心も鍛えておいた方がまあ良いよね」という信じておいたほうが得する理論が自分にもしっくり来たので、瞑想も今後毎日やっていこうと思う。

Coursera Machine Learningコースの2週目までの復習

Coursera Machine Learningコースの2週目プラグラミング課題を何とか終えて3週目に突入できたが、結構苦戦したので復習のメモをしておく。

kidooom.hatenadiary.jp

Octaveの基本操作振り返り

行列操作周り

3x2行列を定義する場合は、以下のようにスペースorカンマで同じ行内の異なる列の数値を定義し、セミコロン(;)で行を分ける。

octave:5> A = [1 2; 3 4; 5 6;]
A =
   1   2
   3   4
   5   6

転置行列は ' をつけるだけ

ans =

   1   3   5
   2   4   6

逆行列は pinv関数を使う

octave:7> pinv(A)
ans =

  -1.33333  -0.33333   0.66667
   1.08333   0.33333  -0.41667

A行列の特定の値にアクセスしたい場合は、()で行番号と列番号を指定する。

A =
   1   2
   3   4
   5   6

octave:9> A(3, 1) # Aの3行1列目の値を取得
ans =  5
octave:10> A(2, 2) # Aの2行2列目の値を取得
ans =  4

Octaveで行列のindexを示すときは 1 始まりなので注意(こういう配列系はよく0始まりの言語多いから)

octave:11> A(2, 4)
error: A(I,J): column index out of bounds; value 4 out of bound 2

ここで、コロンがよく使われる特別な意味で、全てのを表す(正規表現でいうところのアスタリスク 的な)

A =
   1   2
   3   4
   5   6

octave:11> A(2, :) # Aの2行目の全ての値
ans =

   3   4

octave:12> A(:, 2) # Aの2列目の全ての値
ans =

   2
   4
   6

行列の行数と列数は size関数を使って取得できる

octave:13> size(A, 1) # 行数取得
ans =  3
octave:14> size(A, 2) # 列数取得
ans =  2

ones, zeros 関数でそれぞれ全ての値が1, 0 の m * n 行列を生成してくれる。

octave:15> B = ones(3, 4)
B =

   1   1   1   1
   1   1   1   1
   1   1   1   1

octave:16> B = zeros(2, 1)
B =

   0
   0

eye関数で単位行列を生成してくれる。

octave:17> eye(4)
ans =

Diagonal Matrix

   1   0   0   0
   0   1   0   0
   0   0   1   0
   0   0   0   1

magic関数で、どの行、列、斜めを足し合わせても同じ数になる特別な行列を生成してくれる(サンプルデータ生成に便利)

ans =

   35    1    6   26   19   24
    3   32    7   21   23   25
   31    9    2   22   27   20
    8   28   33   17   10   15
   30    5   34   12   14   16
    4   36   29   13   18   11

ドット演算が重要で、普通に行列演算したい場合は * だけでいいが、各行列の同じ行・列の値で掛け算をしたい場合はドットをアスタリスクの前につける。

A =

   1   2
   3   4
   5   6

octave:31> B
B =  3
octave:32> B = [3;4;5]
B =

   3
   4
   5

octave:33> A * B
error: operator *: nonconformant arguments (op1 is 3x2, op2 is 3x1)
octave:33> A .* B
warning: product: automatic broadcasting operation applied
ans =

    3    6
   12   16
   25   30
octave:34> A
A =

   1   2
   3   4
   5   6

octave:35> B = [3 5]
B =

   3   5

octave:36> A * B
error: operator *: nonconformant arguments (op1 is 3x2, op2 is 1x2)
octave:36> A .* B
warning: product: automatic broadcasting operation applied
ans =

    3   10
    9   20
   15   30
octave:38> A
A =

   1   2
   3   4
   5   6

octave:39> B
B =

   10   20
   30   40
   50   60

octave:40> A * B
error: operator *: nonconformant arguments (op1 is 3x2, op2 is 3x2)
octave:40> A .* B
ans =

    10    40
    90   160
   250   360

制御構文

for文は普通に使える。 1:10 とすることで、1 から インクリメントされて 10 まで i が代入されて実行される。endでくくる。

octave:41> for i=1:10
> A = A .* i
> end

if文も使える。使い勝手は他の言語と同じ。

octave:48> for i=1:10
> if i == 5
> i
> end
> end

i =  5

Octaveで単回帰分析(linear regression)の目的関数(cost function)を実装

これはプログラミング課題で悩んだところで、そのまま書くと答えバラシになるので自分用のヒントになるものをメモ。

シグマ計算はsum関数を使う。

octave:57> A
A =

   1   2
   3   4
   5   6

octave:58> B
B =

   1
   2
   3

octave:59> A + B
warning: operator +: automatic broadcasting operation applied
ans =

   2   3
   5   6
   8   9

octave:60> sum(A + B)
warning: operator +: automatic broadcasting operation applied
ans =

   15   18

Octaveで単回帰分析(linear regression)の最急降下法(gradient descent)を実装

上と同じくプログラミング課題でめちゃくちゃ悩んだところで、そのまま書くと答えバラシになるので自分用のヒントになるものをメモ。

gradient descentでは、thetaをそれぞれ同時に(simultaneously)計算する必要があるので、一旦 theta 値を外の値に退避しておく。

    org_theta = theta;
    theta(1) = org_theta(1) - alpha / m * sum((X * org_theta - y) .* X(:,1));

ここは正直、ググらずにヒント無しでは解けなかった。

重回帰分析 (linear regression with multiple variables)の feature scaling をする方法

ここはテストで何度も間違えてお手上げ状態になったので、こちらのQiita記事を見て何とか正解することができた。

qiita.com

input の X が複数あって、それぞれの数値の範囲に大きな差がある場合(特徴1が 1 - 100 の範囲で、特徴2が0.001 - 0.002 の範囲など)は、feature scalingをして全てのXの値の範囲をおおよそ−1 ≤ x _ i ≤ 1にする必要がある。

その式が以下の通り。ここで mean(x) はベクトルxの平均値。


x _ i= \frac{x _ i−mean(x)}{max(x)−min(x)}

まとめ

プログラミング課題をやることで、自分がまだ全然理解できてないことを確かめられたので良かった。

最初の関門を通れた気がしてやる気も増したので、3週目以降も頑張ろう。

参考

qiita.com

qiita.com

qiita.com

qiita.com

7shi.hateblo.jp

「現場で役立つシステム設計の原則」と「レガシーコードからの脱却」を読んでUnity開発のテスト自動化への危機感UP

現場で役立つシステム設計の原則」を読み終わって、今は「レガシーコードからの脱却」を読んでいる途中。

先日のEOF2019であったt_wadaさんの「質とスピード」、ryuzeeさんの「レガシーコードからの脱却」の資料も合わせて読むと理解が深まってくる。

speakerdeck.com

slide.meguro.ryuzee.com

内部品質であるソフトウェアの保守性を高めること

f:id:gidooom:20191123095532p:plain
t_wadaさんの「質とスピード」スライドより

f:id:gidooom:20191123095556p:plain
t_wadaさんの「質とスピード」スライドより

最初から作るべきものが正しく予測できて、それを一発本番の開発フローで正しく作ることが「幻想であること」がもう分かってきたので、最近はアジャイル開発がキャズムを超えてきている。

当てになりにくい「予測」よりも「対応」(=変更容易性=適応性)を高めるプラクティスが重要になってきていて、テスト自動化やドメイン駆動、モブプロなどが効果的になっている。

現場で役立つシステム設計の原則」では、以下のような変更しやすいソフトウェアのための実践的なテクニックが分かりやすく説明されていて、基礎力向上に役に立つなぁと感じた(自分は科学系の本ばかり読んでてプログラミングの基礎力弱い)。

  • データとロジックを別クラスにするのではなく、三層+ドメインモデルを考える
  • 値オブジェクトやコレクションの型を言語が用意した汎用的なやつにしがちだが、ドメインに合わせた型にして不変にもなるようにする
  • 区分ごとのロジックを enumなどで実装する。C#の場合はenumに対してエクステンション使う
  • 手続き型プログラミングらしい書き方とオブジェクト指向らしい書き方とを判断できるようにしておく

テスト自動化

node.jsでサーバーサイド開発をやっているときはテスト自動化をできていたけれど、Unityでゲーム開発をし始めてからはまだ自分自身でテスト自動化を実現するのに至っていない。

区分ごとのロジックをenumで実装してそれの単体テストぐらいは書けたが、これでは全然足りないし、これのメンテ自体もまだちゃんとできてない。

kidooom.hatenadiary.jp

Unityの複雑なゲーム起動、データ読み込み、様々なinput処理、組み合わせを考慮できたテスト自動化ができる仕組みと設計を考えていかないと、「質とスピード」の悪い方のプロダクト開発に進んでしまいそうで危機感を持っている。

いきなり開発途中のでかいプロジェクトにテスト自動化を作ろうとしても超難しいので、Unityテスト自動化のプラクティスを見ながら小さいサンプルプロジェクトに何度も適用していって、徐々にどういう設計でどういう仕組みを作ればいいか勘所を探っていくしかないかなと思った。

blogs.unity3d.com

www.slideshare.net

ウェーバーフェヒナーの法則とRPGの経験値および人間の経験値

ゲーマーズブレインを読んで、ウェーバーフェヒナーの法則は気に留めておいたほうがいいなと思ったのでメモ。

ウェーバーフェヒナーの法則とは

ヴェーバー‐フェヒナーの法則 - Wikipedia

www.cradle.co.jp

ドイツの心理学者が1834年に『ウェーバーの法則』なるものを発見し、それを弟子のフェヒナーが拡張したもの。

たとえば、100の刺激が110になったときはじめて「増加した」と気付くならば、200の刺激が210に増加しても気付かず、気付かせるためには220にする必要がある。

また、100の刺激が倍に増加して200になるときの感覚量と、200の刺激が倍に増加して400になるときの感覚量の変化は等しい。

というのを実験によって発見したこと。

ゲームのパラメータ設計もこれを意識したほうがいい

ゲーマーズブレインでは、このウェーバーフェヒナーの法則をゲーム開発でも考慮すべきと述べている。

例としては、アナログスティックの入力の強さと出力の関係もウェーバーフェヒナーの法則を考慮して線形的にしないほうがいい(実際そうなってる気がする)。

また、RPG等におけるレベルアップの必要経験値の入手量も線形的ではなく対数で設計したほうが良いし、これもだいたいそうなっている。

序盤は経験値1とか2から始まって10, 15, 20 みたいにちょっとずつ敵の強さなどに応じて増えるけど、後半は1000 -> 1100 - > 1500 みたいに 増える量も大きくなっていく的な。

人間の成長もどんどん鈍感になっていく

ウェーバーフェヒナーの法則に関連して、下の記事の内容も近いなと思い出した。

readingmonkey.blog.fc2.com

人間も、新しいことを学び始めたときはどんどん成長できている感覚を味わえるが、ある程度(それが中級ぐらい?)まで来ると成長を感じにくくなる。

その成長を感じにくくなった時の状態を壁にぶつかったと捉えて、学ぶのを辞めてしまうケースは結構ありそうだけど、そんなもんなのだから諦めずに続けることが大事だなと改めて思った。