rn.log

備忘録など

【Python】pipのget_supported()を実行する

pipの対応cpを確認しようとget_supported()を実行させようとたところ、いろいろと詰まったので備忘録もかねてメモ。

環境

Windows 10
Python 3.5.2
pip 10.0.1

pipのget_supported()の実行

Python上で以下を実行します。

>>> from pip._internal.pep425tags import get_supported
>>> get_supported()
[('cp35', 'cp35m', 'win_amd64'), ('cp35', 'none', 'win_amd64'), ('py3', 'none', 'win_amd64'), ('cp35', 'none', 'any'), ('cp3', 'none', 'any'), ('py35', 'none', 'any'), ('py3', 'none', 'any'), ('py34', 'none', 'any'), ('py33', 'none', 'any'), ('py32', 'none', 'any'), ('py31', 'none', 'any'), ('py30', 'none', 'any')]


get_supported()の実装を見る

pip --version を実行したときに表示されるメッセージを見るとpipがどこにあるかが分かります


例えば、以下のような表示の場合

pip 10.0.1 from c:\users\(ユーザー名○○)\appdata\local\programs\python\python35\lib\site-packages\pip (python 3.5)


pipの置き場所は以下になります。

c:\users\(ユーザー名○○)\appdata\local\programs\python\python35\lib\site-packages\pip


pip10.0.1の場合、_internalフォルダの中のpep425tags.pyファイルの221行目でget_supported()が定義されています。
f:id:r-ngtm:20180625121000p:plain

【VR】UnityのPrefabをSTYLYへアップロードしてみた

はじめに

Unityエディタ上でPrefabをSTYLYへアップロードする、というのをやってみました。

今回の記事では、Unityのインストール手順やUnityエディタからSTYLYへPrefabをアップロードするまでの手順を簡単にご紹介したいと思います。

STYLYについて

Webブラウザ上でVR空間を簡単に作れるWebサービスです。

VR対応していないPCでもVR空間を作ることができます。

wired.jp

styly.cc

手順1: Unityエディタの準備

UnityHubのDL

UnityエディタのインストールにはUnityHubがおすすめです。
unity3d.com

f:id:r-ngtm:20180531114250p:plain:w450

Unity2017.4をインストール

UnityHub上でUnity2017.4系をインストールします。

f:id:r-ngtm:20180531154536p:plain

STYLYへPrefabをアップロードするためには以下の5種類のプラットフォームをインストールしておく必要があります。
WebGL
iOS
Android
MacOSX
Windowsプレイヤー

必要なものすべてにチェックを入れてインストールしましょう。
f:id:r-ngtm:20180531121441p:plain:w300

プラットフォームが足りていない場合

「Unity2017.4はすでにインストールしてるけど、プラットフォームが足りていなかった」という場合は
UnityHub上でAddComponentを選ぶことでプラットフォームを追加でインストールできます

f:id:r-ngtm:20180531125515p:plain:w400

f:id:r-ngtm:20180531130108p:plain:w400

手順2: Unityプロジェクトの作成

UnityHubからプロジェクトを作成します。 Unity2017.4系を指定しておきます。

f:id:r-ngtm:20180531122320p:plain:w300


手順3: STYLYのUnity用プラグインの導入

STYLY公式サイトからUnity用プラグインをDLします。
styly.cc

ダウンロードしたSTYLY.unitypackageをダブルクリックして、UnityプロジェクトへSTYLYプラグインをインポートします。
f:id:r-ngtm:20180531155214p:plain

f:id:r-ngtm:20180531123812p:plain:w300

インポートが終わるとウィンドウが表示されます。

ウィンドウにメールアドレスとSTYLYのAPI Keyを入力すれば STYLYプラグインのセットアップは完了になります。

f:id:r-ngtm:20180531131655p:plain:w350

No ○○ module installed

Unityエディタにインストールされているプラットフォームが足りていない場合、
No ○○ module installed」という警告が表示されます。

f:id:r-ngtm:20180531124949p:plain:w300

UnityHub上のAdd Componentでプラットフォームを追加インストールしましょう

f:id:r-ngtm:20180531125515p:plain:w400

手順4: PrefabをSTYLYへアップロードする

今回は、以下のPrefab(Sphere.prefab)をアップロードしてみます。
f:id:r-ngtm:20180531160712p:plain:w350

アップロードしたいPrefabを右クリックし、「Upload prefab to STYLY」を選ぶとアップロードを開始します。
f:id:r-ngtm:20180531132440p:plain:w350

アップロードが完了すると以下のようなウィンドウが表示されます。
f:id:r-ngtm:20180531132910p:plain:w350

手順5: アップロードしたPrefabを確認してみる

STYLYを開き、Assets → 3DModel -> My Models を開くとアップロードしたPrefabを確認できます。

f:id:r-ngtm:20180531133602p:plain:w400

モデルの配置もできます。
f:id:r-ngtm:20180531134147p:plain:w300

OculusGoの空き容量をコマンドラインで確認する

OculusGoをWindowsのPCに接続した状態で、以下のコマンドを実行するとOculusGoのストレージの残量を確認できます。

adb shell df -h


Oculus Goの64GBモデルで試したところ、以下のような表示になりました。

f:id:r-ngtm:20180513133103p:plain

参考URL

ADBコマンド導入の方法 - Qiita

【Oculus Go開発】Oculus Goが操作不能になった時の最終手段 : コマンドラインでOculusGoを再起動する

激重なシーンを動かしてたらOculusGoが固まった

Unityで作った激重なシーンをOculusGoで動かしたらOculusGoが操作不能になってしまいました。 電源ボタン長押しでもダメ(半ばフリーズ状態)

最終手段 : コマンドラインで再起動

そんなときの最終手段、コマンドラインで再起動

以下のコマンドを実行するとOculus Goを再起動できます

adb reboot


コマンドの詳細を確認したい場合は以下を実行

adb --help

参考URL

ADBコマンド導入の方法 - Qiita

【プログラミング】bool変数の代入について考察

今回の記事はプログラミングについての話題になります。

言語はC#あたりを想定しています。

2つのコード

まずは以下の2つのコードを見てください。

// コードA
b = a; // bool値を直接代入している
// コードB
if (a) 
{ 
   b = true; // bool値の書き換え
} 
else 
{
   b = false; // bool値の書き換え
}


どちらのコードも、やっていることは

a が true なら b をtrue にする
a が false なら b もfalseにする

非常にシンプルなコードです。

使い分け

個人的な好みになりますが、

変数をコピーするだけ」という意図を強調したい場合はコードA
条件にひもづいた処理を追加する可能性が高い」ことを強調したい場合はコードB

という使い分けを意識しています。

良いコードはどちらか

さて、このコードAとコードBですが、実際のところどちらが良い書き方なのか、少し気になりました。

そこで、これら2つのメリット・デメリットを自分なりに列挙してみました。

コードA

b = a; // bool値を直接代入している

コードAのメリット : 低コスト

コードが1行と短いので、書くのに手間がかからない。

つまり、実装工数が小さいというメリットがあります。

コードAのデメリット : 可読性が低い

b = aというコードを見た時、これがintの代入なのか、boolの代入なのか、あるいはオブジェクトの代入なのか。

コードAから読み取れる情報だけでは変数型の判別が難しいです。
コメントが書かれていなければ変数型が判別不能

is○○ のように変数名を工夫すれば回避できそうなデメリットでもあります。


コードB

if (a) { 
   b = true; // bool値の書き換え
} else {
   b = false; // bool値の書き換え
}

コードBのメリット : 関心分離

・条件に関連する処理を追加しやすい ( = 追加実装のコストを減らせる)
a == trueの時にDebug.Logを追加したい、となった場合は一行追加するだけです。

if (a) { 
   b = true; // bool値の書き換え
   Debug.Log("a == trueです");
} else {
   b = false; // bool値の書き換え
}


・a という変数がbool型であるということがこのコードから読み取れる(=コードの可読性が高い)


「条件判定」と「値の書き換え」という2つの関心を分離しているというメリットがあるとも言えそうです。

コードBのデメリット : コード量が増える

・書くのに手間がかかる (=実装コストが増える)
・コードAよりもソースコード行数が多い

結局、どちらの書き方が良いんだろう?

コードAとコードBの両方にメリット・デメリットがあることが分かってきましたが、
結局のところ、どちらの書き方が良いのか分からなくなってきました。

悪いほうが良い(worse is better)

「悪いほうが良い」という言葉があります。

悪いほうが良い(worse is better) (http://www.kt.rim.or.jp/~hisashim/gabriel/WorseIsBetter.ja.html)

「最小限のものをまず作り、そして必要に応じて育てるほうがよい」とする考え方。

2つのコードにこの言葉を当てはめる

「悪いほうが良い」という視点で先ほどのコードAとコードBを見てみます。

コードA
「bool変数を代入する」という処理だけを行っている。
つまり、コードAは最低限の機能だけを持つ

コードB
「条件判定を行い、その後にbool変数を変更する」という複数の処理を行っている。
つまり、コードBは機能が多すぎる

コードBは悪?

最小限のものをまず作る」という視点から見た場合は

コードAは機能が最小限なのでGOOD
コードBは機能が多いのでBAD

まとめ

迷ったらコストが少ない選択肢をとるのが吉

【UE4】アニメーションブループリントをちょっと触ってみた。

アニメーションBP(アニメーションブループリント)を触ってみました。

UE4のバージョン : UE4.19

アニメーションBPとは

キャラクターのアニメーションを組む機能です。
UE4に標準で入っています。

とりあえずアニメーションBPを組んでみる

STEP1: 必要なデータを用意する

アニメーションBPを使用してアニメーションを組むためには3Dモデルとモーションデータが必要です。

そういえば最近、Paragonのアセット無料化のニュースを見かけました。

www.unrealengine.com

今回はありがたくこちらを使わせてもらうことにします。

マーケットプレイスでParagonアセットを見つける

EpicGamesランチャーのマーケットプレイスで"Paragon"と検索欄に入れます。

f:id:r-ngtm:20180409171843p:plain

Paragonのアセットがいろいろ出てくるので好きなものを選びます。

f:id:r-ngtm:20180409172412p:plain

Minionsというキャラがメカメカしくてなんかカッコイイ。 これにしよ。

ちなみに、Minionsのデータサイズは5GBあります。

DLしたアセットをプロジェクトへ追加する

DLしたデータはLibraryタブから追加できます。
f:id:r-ngtm:20180409173435p:plain

プロジェクト名一覧が表示されるので、データを追加したいプロジェクトを選びます。

以上でデータの用意は完了です。


今回の記事ではParagon: Minionsというアセットを使用していきます。
f:id:r-ngtm:20180409195357p:plain:w400

STEP2 : アニメーションBPの作成

モデルデータとモーションの用意が終わったらアニメーションBPの作成に移ります。

コンテンツブラウザ上で右クリックして、「アニメーション」 -> 「アニメーションBP」を選択します
f:id:r-ngtm:20180409174300p:plain


クラスとスケルトンを選択してOKを押します。
f:id:r-ngtm:20180409174500p:plain

"AnimBP_Buff_Black"という名前を付けることにしました。
f:id:r-ngtm:20180409174911p:plain

STEP3: ステートマシーンを追加する

上記で作成したアニメーションBP "AnimBP_Buff_Black"を開くと以下のような画面が出てきます。 f:id:r-ngtm:20180409175523p:plain

ここで画面を右クリックして"State Machine"と入力して、
"新規のステートマシーンを追加"を選んでStateMachineノードを作成します。
f:id:r-ngtm:20180409175704p:plain

ノードをつなぎます
f:id:r-ngtm:20180409182226g:plain


f:id:r-ngtm:20180409175402p:plain

STEP3: Idleモーションを再生してキャラクターを動かす

次にステートマシーンを編集していきます。

先ほど作成したステートマシーンのノードをダブルクリックして開きます。

f:id:r-ngtm:20180409182510p:plain


そして、アセットブラウザタブの中からIdleモーションをドラッグ&ドロップし、ノードを接続します。

f:id:r-ngtm:20180409183255g:plain

ノードが接続された状態でコンパイルボタンをクリックします。
うまくいけばIdleモーションが再生されます。

f:id:r-ngtm:20180409183708p:plain

f:id:r-ngtm:20180409183835g:plain:w240

アニメーションBPに関する説明はいったん終わりとします。

マップ上にキャラクターを出してアニメーションさせてみる

アニメーションするキャラクターをマップ上に出してみます

STEP1: キャラクターBPの作成

コンテンツブラウザの右クリックメニューからブループリントを選択
f:id:r-ngtm:20180409202212p:plain

キャラクターを選択
f:id:r-ngtm:20180409202337p:plain

ブループリントの名前は"BP_Buff_Black"としておきます。
f:id:r-ngtm:20180409202443p:plain

STEP2: キャラクターBPにアニメーションBPを設定する

Meshコンポーネントを選択し、
Anim Classへ "AnimBP_Buff_Black"を設定、Skeltal Meshへ "Buff_Black"を設定します。
f:id:r-ngtm:20180409202758p:plain

STEP3: マップにキャラクターを配置してゲームを再生

BP_Buff_Blackをマップへドラッグ&ドロップしてゲーム再生するとキャラクターが動きます。
f:id:r-ngtm:20180409203233g:plain

おまけ: 2つのモーションをブレンドしてみる

移動速度が速いときはRunモーションを、移動していないときはIdleモーションを再生。
中間の場合は速さに応じてRun/Idleモーションをブレンドする、というのをやってみました。

モーションを混ぜるにはブレンドスペース1Dを使う

2つのモーションをブレンドする(混ぜる)には ブレンドスペース1D を使用します。
BlendSpace1Dを利用することで、実数を使ってモーションを混ぜることができます。

例えば、「IdleモーションとRunモーションと1:2の割合で混ぜる」といった事ができます。


似た名前のもので "ブレンドスペース" (名前の末尾に1Dが付いていない) がありますが、今回はこれは使用しません。

STEP1: ブレンドスペース1Dの作成

コンテンツブラウザを右クリックして、「アニメーション」 -> 「ブレンドスペース1D」を選択します。

f:id:r-ngtm:20180409185639p:plain


今回は Buff_Blackのスケルトンから使用するブレンドスペース1Dを作りたいので、Buff_Black_Skeleton を選択します
f:id:r-ngtm:20180409185919p:plain


名前ですが、RunIdleを合成するブレンドスペース1Dを作りたいので、名前を「BlendSpace1D_RunIdle」としました。
f:id:r-ngtm:20180409190141p:plain

ブレンドスペース1Dの編集

次にブレンドスペース1Dを編集していきます。

ブレンドスペース1Dをダブルクリックして開くと以下のような画面が表示されます。
f:id:r-ngtm:20180409190949p:plain:w500


今回は赤い字で示した部分を触ります。
f:id:r-ngtm:20180409190911p:plain:w500

STEP2: モーションの登録

アセットブラウザからIdle(待機モーション)をドラッグ&ドロップし、Blend Space Editor(左隣にあるウィンドウ)の端に配置します。
f:id:r-ngtm:20180409191723g:plain

アセットブラウザからRun_FWD(前方へ歩くモーション)をドラッグ&ドロップし、Blend Space Editor(左隣にあるウィンドウ)の端に配置します。
f:id:r-ngtm:20180409192125g:plain

以上を済ませると左側にIdleモーション、右端にはRun_FWDモーションが登録された状態となります。
f:id:r-ngtm:20180409192513p:plain:w350 f:id:r-ngtm:20180409192519p:plain:w350

補足: 下にある数字の意味

ここで、ウィンドウの下を見てみると数字が書かれていることがわかります。
これは
「Axis(軸入力) = 0.0のときにはIdleモーションを再生し、Axis = 100.0の時にはRun_FWDモーションを再生する」
という意味を表しています。

f:id:r-ngtm:20180409193021p:plain

ここで補足ですが、アニメーションBPからブレンドスペース1Dに対してfloat値を引数として渡すことができます。
この入力のことをAxis(軸入力)と呼びます。
f:id:r-ngtm:20180409194617p:plain

STEP3: ステートの作成

まずは最初に作成したアニメーションBP "AnimBP_Buff_Black"を開きます。 f:id:r-ngtm:20180409193553p:plain

次に、右クリックメニューからステートを作成してEntryノードと接続します。
f:id:r-ngtm:20180409193741g:plain
今回はIdleとRunをブレンドしたいので、ノード名を"Idle/Run"としました。

STEP4: ブレンドスペース1Dを使う

Idle/Runノードをダブルクリックして開き、右クリックメニューから"BlendSpace1D_RunIdle"を追加して最終アニメーションポーズへ接続します。
f:id:r-ngtm:20180409200144g:plain

ブレンドスペース1Dの挙動を確認してみる

Axis = 0.0の場合
f:id:r-ngtm:20180409201313g:plain
Idleモーションが再生されます。


Axis = 100.0の場合
f:id:r-ngtm:20180409201450g:plain
Runモーションが再生されます.

Axis = 50.0の場合
f:id:r-ngtm:20180409201734g:plain
IdleモーションとRunモーションが1:1の割合でブレンドされて再生されます。

STEP5: キャラクターの移動スピードをアニメーションに反映する

実際にキャラクターを走らせるため、もうひと手間加えます。

AnimBP_Buff_Blackのイベントグラフタブを選択し、以下のようなノードを組みます。
f:id:r-ngtm:20180409204532p:plain

キャラクターの移動速度ベクトルの長さを計算して、変数Speedに代入しています。


次に、Idle/Runノードを開いてブレンドスペース1Dに変数Speedを接続します。
f:id:r-ngtm:20180409205108p:plain

補足: 挙動について軽く説明

キャラクターが速く移動しているときはSpeedが100に近づくので、BlendSpaceのAxis=100.0に登録されたRunモーションの比重が大きくなります。
移動が遅くなるとSpeedが0に近づくのでBlendSpaceのAxis=0.0に登録されたIdleモーションの比重が大きくなります。

遅く移動しているときはIdleモーションが再生され、速く移動しているときはRunモーションが再生されます。

STEP6: キャラクターを移動させてみる

実際にキャラクターを移動させてアニメーションを確認してみます。

まずは BP_Buff_Blackをダブルクリックして開き、「イベントグラフ」タブを開きます。
f:id:r-ngtm:20180409203551p:plain

Tickノードに"Add Movement Input"ノードを接続します。 WorldDirectionには(1,0,0)を入れておきます
f:id:r-ngtm:20180409203732p:plain

この状態で再生を押すと以下のようなRun_FWDモーションが大きくブレンドされた動きになります。
f:id:r-ngtm:20180409210006g:plain


移動速度を下げるとIdleモーションが大きくブレンドされた動きになります
f:id:r-ngtm:20180409210407p:plain
f:id:r-ngtm:20180409210240g:plain

【Unity 設計の話】初心者がやりがちな設計ミスにマサカリを投げてみる

はじめに

Unityでコードを書くとき、自分が普段気を付けていることを自戒の念を込めて記事としてまとめたいと思います。

かなり初歩的な話になるのでご容赦ください。

題材

今回は、1秒ごとに弾を発射する固定砲台を作ることを考えます。
シューティングゲームを作るときになどにおなじみかと思います。

f:id:r-ngtm:20180222124032g:plain:h280

これを実現するためにはいろんな方法が考えられますが、

今回はC#スクリプトを書いて弾を一定間隔で生成させることにします。

悪いコードの例

何も考えずにスクリプトを書くとこんなコードになるでしょうか。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BulletGenerator : MonoBehaviour
{
     // 弾のPrefabを指定
    [SerializeField] private GameObject bulletPrefab;

    // 弾の発射ロジック
    private IEnumerator Start()
    {
        while(true)
        {
            var bullet = Instantiate(this.bulletPrefab); // 弾を生成

            var rigidbody = bullet.GetComponent<Rigidbody>();
            rigidbody.velocity = new Vector3(0f, 10f, 0f); // 弾を上に飛ばす

            yield return new WaitForSeconds(1f); // 1秒待つ
        }
    }
}


昔の自分はこのようなコードをよく書いていましたが、これは悪いコードです。

なぜ悪いコードなのか。マサカリを投げつつ理由を書き連ねていきたいと思います。

マサカリその1 : PrefabのシリアライズにGameObjectを使うのはやめよう

フィールドのタイプにGameObjectを使えばPrefabを設定することができますが、これは色々な問題を招くのでやめましょう。

問題その1: Prefabの指定ミスが起きるリスク

PrefabのタイプをGameObjectにすると、あらゆるPrefabが指定できてしまいます。

本来は弾のPrefabが入る想定なのに間違えてエネミーのPrefabを指定しまう、といったヒューマンエラーを招きます。

問題その2: コンポーネントへのアクセスが汚い

弾のPrefabにRigidbodyがアタッチされていることを想定し、「弾のRigidbodyコンポーネントにアクセスしたい」という状況を考えます。

PrefabのタイプはGameObjectなので、RigidbodyにアクセスするにはGetComponentすることになります。

var rigidbody = bullet.GetComponent<Rigidbody>();

GetComponentは重いとされているので処理負荷的にこれは良くないコードです。

問題その3:依存関係が強い

このコードでは、弾の速度を設定する際にRigidbodyを直接参照しています。

つまり、「弾の速度を設定したい」という目的に対してコンポーネントのRigidbodyが密に結合しているという関係になっています。
これは設計上良くないです。

改善案: GameObjectの代わりにクラスを指定する。

上記を踏まえて修正したものがこちらです。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BulletGenerator : MonoBehaviour
{
    // 弾のPrefabを指定
    [SerializeField] private Bullet bulletPrefab;

    // Use this for initialization
    private IEnumerator Start()
    {
        while(true)
        {
            var bullet = Instantiate(this.bulletPrefab); // 弾を生成
            bullet.SetVelocity(new Vector3(0f, 10f, 0f)); // 上に飛ばす

            yield return new WaitForSeconds(1f); // 1秒待つ
        }
    }
}


GameObjectの代わりに自作クラスを指定しています。
そして、rigidbodyのvelocityに速度を代入する代わりにメソッドSetVelocityを実行して速度を指定しています。

※ 補足: Bulletクラス内部には速度を指定するメソッドpublic SetVelocity(Vector3 v) が定義されているものとします。

改善点: コードの見通しが良くなった。

速度を設定するメソッドを呼び出していることにより、「速度を設定しているんだな」ということがコードから読み取れるようになっています。

さらに、BulletGeneratorから見てBullet内部のRigidbodyの有無を意識しなくて済むようになりました。

BulletGeneratorでは「弾の速度の設定をしたい」という関心、Bulletでは「速度をどのように設定するか」という関心。
これら二つの関心をきれいに分離することができています。

これをプログラムの言葉で、「関心の分離」 と呼びます。

まとめ

・Prefabのタイプ指定にGameObjectを使うのはやめよう
・メソッドを利用して関心の分離を意識しよう

マサカリその2:数をメソッドの中に直接書くのはやめよう

コードの該当箇所を抜粋しました。

bullet.SetVelocity(new Vector3(0f, 10f, 0f)); // 上に飛ばす

yield return new WaitForSeconds(1f); // 1秒待つ


一般に、数をコードに直接指定するのは良くないとされています。(マジックナンバー)

問題その1: 数での使いまわしが難しい

例えば、以下のような状況に対処するのが難しくなってしまいます。
・弾の移動速度new Vector3(0f, 10f, 0f)を別の場所でも使いたい
・待機時間1fを別の場所でも使いたい

同じ目的の数を2か所以上に書いてしまうと、片方を変更したときにもう片方の変更を忘れてしまう、といったヒューマンエラーが発生します。

ある意味、数と目的が密に結合していると言えます。

問題その2:数の意味が分かりにくい

ソースコードを読んだ人は以下のような疑問が出てくるかと思います。
「new Vector3(0f, 10f, 0f)ってどういう値なの?」
「1fって何?」

数の直接指定は意味が分かりにくく、コードの明瞭性が落ちてしまいます。
これはヒューマンエラーを招きます。

改善案 : 定数を定義する

上記を踏まえて以下のようにコードを修正します。
読み取り専用な定数を定義し、ロジック内では定義した定数を利用しています。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BulletGenerator : MonoBehaviour
{
    // 弾の移動速度
    static readonly Vector3 BULLET_VELOCITY = new Vector3(0f, 10f, 0f);

    // 弾の発射の間隔
    const float BULLET_INTERVAL = 1f;

    // 弾のPrefabを指定
    [SerializeField] private Bullet bulletPrefab;

    // 弾の発射ロジック
    private IEnumerator Start()
    {
        while(true)
        {
            var bullet = Instantiate(this.bulletPrefab); // 弾を生成

            bullet.SetVelocity(BULLET_VELOCITY); // 速度設定

            yield return new WaitForSeconds(BULLET_INTERVAL); // 待つ
        }
    }
}

定数を利用することで、その数が何を意味するのかが見えやすくなりました。
また、定数の使いまわしもできるようになりました。

まとめ

・コード中に数を直接記述するのは避けて定数を定義しよう。

その他の懸念事項

ほかにも以下のようなことが考えられると思います。

・BulletGeneratorには弾の発射ロジックだけ持たせておいて、弾の発射の実行は別のクラスにさせるべきでは?(単一責任原則
・弾のPrefabはBulletGeneratorに持たせて良いのか? Prefabを管理するクラスを作ってそこで管理するべきなのでは?
・定数はBulletGeneratorに直接持たせて良いのか? 定数を管理するクラスを作ってそこで管理するべきなのでは?
・弾の一定間隔の発射はコルーチンを使わずにUniRxを使うべきなのでは?

ここらへんはプロジェクトによって正解が変わるかと思います。
銀の弾丸は無い

補足: 環境について

Windows 10
Unity2018.1.0b7