unity開発メモ帳

Unityゲーム開発です

ゲーム開発進捗-2週間でやったこと [5月後期]

初めに

お久しぶりです。

最近macbook proのタッチバーをスクショできることを知って、この機能いる?と思いました。

 

区切りの良いところまで進んだので今回も進捗を書いていこうかと思います。

 

進捗

星への入港

今まではフィールド上の星にはなんの機能もありませんでしたが、プレイヤーがEキーを押すと空港?に入港できるようになりました。

 

今後ははショップなどの設備をこの空港に設置していく方針です。

 

 

大型船の基地

大型船について、前回までは「生成後の挙動」を実装してきましたが、今回は「生成の挙動」を実装しました。

マップ上に大型船の基地を配置しました。基地は適切なタイミングで大型船を生成し、目的地など必要な項目を設定します。

 

下のGIFでは3つの船をスタート時に生成しています。

 

 

大型船の航路

大まかな大型船の挙動としては「いくつかの星を巡回する」というものです。

今回は大型船がどの航路を辿っているのかプレイヤーが見られるように、マップ上にそれぞれの航路を線として描画させました。

 

下のGIFでは、マップ(左側のボックス)上に大型船がオレンジで、その航路が緑色の線で示されているのがわかります。

また、星に船がたどり着くと、次の星へ航路を更新します。

 

 

終わりに

はい。いつも通りのスピードで進めております。

一応タスクの洗い出しを行ってはいるものの、明確に完成までの期間がまだまだ見えてきません、、、

 

これからは引き続き、進捗報告を書く他、もう少し技術的な話題も書いていこうかなとも思っております。

 

ありがとうございました。

 

 

ゲーム開発進捗-2週間でやったこと

お久しぶりです。

 

牛歩の歩みで進めているゲーム制作ですが、今回も切りの良いところまで進んだので進捗状況を書いていこうと思います。

 

 

細かな調整

  • 敵に見つかったときに表示される赤枠の位置調整
  • 敵の弾のグラフィク改善(あまり気に入っていないので変えるかも)

    敵の弾

 

 

「船内への侵入」の実装

船に接続した状態でスペースキーを押すと、相手船内に侵入することができるようになりました。

侵入後は船内を探索できます。

 

現時点では船内のグラフィックがヘボいので、これから調整していきます。

 

 

敵船のパトロール実装

敵船が、プレイヤーを発見していない際に指定の範囲内で巡回をするようになりました。

 

下のGIFでは敵船が四角い枠の範囲内でランダムに巡回しているのが分かると思います。

 

 

 

回復の実装

左下アイコンに表示されている数を「回復薬の量」として、Rボタンの長押しで回復ができるようになりました。

 

Rボタンの長押しで円いゲージが現れ、ゲージが最大になると左上の「プレイヤーのHP」が回復し、左下の「残りの回復薬の数」が一つ減ります。

 

 

終わりに

はい。大分ゆっくり進めております。

 

グラフィックに関してですが、現時点ではほとんどが仮グラフィックです。いつかは他の人に頼んだり、自分の根性でどうにかする予定です、、

なので、将来的にはもう少しかっこよくまとまったデザインになるかなと今のところは思っています。

 

次回もいつになるかわかりませんが、ある程度制作が進んだ段階で進捗報告をしようかなと思っています。

 

ありがとうございました。

 

ゲーム開発進捗-3ヶ月間でやったことと反省点

初めに

お久しぶりです。

長らく失踪していましたが、ゲーム制作は相変わらず続けていました。今回は新規ゲームの進捗状況について書いていこうかなと思います。

 

 

 

新規ゲーム制作の開始

とりあえずゲームを完成させることを目標に1月からゲームづくりを新規に始めました。

ゲームの仮概要としては

のようなものにしました。

 

プレイヤーの移動方法の初実装

ざっくり背景画像や宇宙船等々の画像を描き、

 

WASD移動ができるようコーディングを行いました。(また、右上にレーダーも表示させています。)

 

反省点1

上記のGIFからはわかりにくいですが、今回実装した移動方法は慣性を持つため、プレイヤーは移動を止める際に船体をいちいち減速させなければなりません。(GIFからは、船体の向きを180°変えてから減速しているのがわかる)

このゲームでは船を使ったテンポの早い戦闘を想定しているため、慣性のついた移動ではその想定を実現することができません。この仕様は改良が必要です。

改良後。プレイヤーが減速を行わなくても船の動きが止まるようになっています。

 

ワープ機能の実装

SFでおなじみのワープを実装しました。

現時点ではスペースキーを推している間に、ワープ先を示すカーソルが動いていきます。その後、スペースキーが離された数秒後に、カーソルの位置へ移動します。

右上に仮でワープ距離に応じて消費されるゲージも用意しました。

 

 

プレイヤーが襲う船(商船のようなもの)の実装

目的地を入力すると、慣性を考慮して自動で運転する大型船を作成しました。

目的地との距離が近くなると減速を開始しているのが分かると思います。分かりにくい。

 

また、この大型船の上でプレイヤーが指定のボタンを長押しすると、プレイヤー船が船体に接続されるようになっています。(この仕様は保留中、、)

 

反省点2

まず挙げるとするならば、

時間かけすぎ

今回は慣性を考慮した移動方法を実装しましたが、この仕様はゲームの進行において特に重要な部分でありません。また、制作の初期で大きい仕様を決定してしてしまうと、後の仕様変更に弱い実装となってしまいます。

 

(実装に時間をかけるときはそれに見合った段階で行うのが個人的にはベストだと思っています。実装に時間をかけると私のやる気パワーをどんどん削っていくので。)

 

敵の実装

マップ上を見回り、プレイヤーを見つけると攻撃してくる敵を想定しています。

現時点では「プレイヤーを見つけると攻撃する」という部分まで実装済みです。

 

 

敵はプレイヤーが至近距離にいればプレイヤーの速度を下げる弾を発射し、(↑)

 

 

 

 

プレイヤーが遠い位置にいる場合は船体にダメージを与える弾(仮グラフィック)を発射します。(↑)

左上のHPゲージが減っていっているのが分かると思います。

 

敵に見つかった際の赤い枠の位置、弾のグラフィック、弾のアニメーション、被弾時の挙動など、まだまだ調節しがいの有りそうなものばかりなので、次回までにやっておきます。(^^)

 

終わりに

はい。だいぶコンパクトにまとめました。

まだまだ半人前ですので改善点の多い内容となっていますが、暖かく見守っていただければと思います。

次回はいつになるかわかりませんが、ある程度制作が進んだ段階で進捗報告をしようかなと思っています。

 

ありがとうございました。

 

 

SOLID原則のざっくりまとめ:後編

 

前編↓

fatena.hatenablog.com

 

インターフェース分離の原則

インターフェース分離の原則とは、インターフェースを機能ごとに分けて、それぞれのクラスで使わない機能が無いようにすることです

 

例えば、酸素を吸う「呼吸メソッド」と、二酸化炭素を吸う「光合成メソッド」を複合したインターフェースを作ったとします。

 

 

そして、そのインターフェースを継承して「人間クラス」と「植物クラス」を作りました

これは好ましい状況とは言えません。

人間クラスは光合成をしませんし、植物クラスも呼吸をしません。

が、それを知らない人がクラスを改変すると、「人間が光合成をする」や「植物が呼吸をする」などのありえない状況が生まれてしまいます。

そういったバグを生み出す余地をなくすために、インターフェースは機能ごとに分ける必要があります

 

依存性逆転の原則

依存性逆転の原則とは、[下位モジュール --> 上位モジュール]のように、下位モジュールが上位モジュールに依存するような設計にすることです

 

例えば、自動車クラスを設計するとします。

おおざっぱにいえば、自動車クラスは「ハンドルをきった方向に曲がり、前進と後退とブレーキがかかる」ことができればよいわけです

問題は、それらの機能をどのモジュールで実現するかです。

基本的に上位モジュールは変更が少なく、下位モジュールは変更が多いです。

なので、主要な機能を変更の多い下位モジュールで実装してしまうと、後々の手間が大きくなってしまいます。

また、上位モジュールからの目線でも、「あの下位モジュールがないと動かない」という状況が生まれ、上位モジュールを再利用することが難しくなってしまいます。

 



終わりに

2回に渡って書いてきましたが、SOLIDは調べるほど様々な考えが出てきます。

この記事はSOLIDについて深く掘り下げたわけではなく、浅く、ざっくりまとめてみただけなので「もっと気になる」という方は記事の下にある参考元のページや、各原則について調べてみると良いと思います。

 

では

参考

インターフェース分離の原則

インターフェース分離の原則を考える

【オブジェクト指向】「インターフェース分離の原則」について | プログラミングマガジン

 

依存性逆転の原則

【オブジェクト指向】「依存関係逆転の原則」について | プログラミングマガジン

依存性の逆転のいちばんわかりやすい説明

SOLID原則のざっくりまとめ:前編

 

 

はじめに

SOLID原則は詳しくは知らなかったのですが、自身の知識不足が顕在化してきたのでまとめてみました。

※あくまでも「ざっくり」まとめているので、正確性に欠ける場合がございます。

 

SOLIDとは

  • Robert C. Martin氏(別名ボブおじさん、Uncle Bob)の論文で紹介された
  • 5つの原則の頭文字を取ってSOLID
  • ソフトウェア設計を柔軟なものにして保守しやすくすることを目的にする

 

具体的な内容

S-単一責任 (single-responsibility)

O-オープンクローズド (open/closed)

L-リスコフの置換 (Liskov substitution)

I-インターフェース分離 (Interface segregation)

D-依存性逆転 (dependency inversion)

 

単一責任原則

単一責任原則とは、ざっくり言えばクラス、メソッドはそれぞれ単一の仕事だけについて保証しなければならないという原則です

 

例えば、自動車クラスを作成したとします。

自動車クラスは動力を得るモーターメソッドから成っています。

そこへ、新幹線クラスが新たに作成されました。

そこで、自動車クラスに使われているモーターメソッドを共通化して実装しました。

モーターメソッドを2個作るのはメンドウなので、こうして1個で済ませられました!

 

しかし、新幹線クラスにとって自動車のモーターは遅すぎるようです。



これで一件落着!!

 

とはならず、自動車クラスは速すぎるモーターのせいで事故ってしまいました、、

この例では、モーターコンポーネントが単一責任原則に違反しています。コンポーネントが2つ以上の仕事の責任を一度に受けているためです。

 

この例のように、「それぞれのクラス、メソッド等が単一の仕事を保証すること」=単一責任原則 を満たすことができなければ、一度の変更が思いも寄らない事故を引き起こす可能性があります。

 

オープンクローズドの原則

オープンクローズドの原則とは、ざっくり言うと要素を追加する際、元のコードを「書き直さず」、元のコードを「拡張」して実装できるようにしなければならないという原則です。

 

例えば、貨物列車を作ったとします

そこへ、客車を追加したいという要望が出てきました。

もし、貨物列車の最後尾に客車を何かしらの金具でつなげることができれば、既存の貨物車両を改造するよりも効率的です。

このように、オープンクローズドの原則を用いれば、あとになって追加される要素に必要な労力が少なく済みます。

さらに、既存のコードを改造したことで発生するバグの数も抑えることができます(本当にざっくりですが)

 

 

リスコフの置換原則

リスコフの置換原則とは、ざっくり言うと継承によってできた派生型(サブクラス)は、その基底型(スーパークラス)と置き換え可能でなければならないという原則です

 

例えば、新しく大工クラスを作ったとします。大工クラスは建築に関わるメソッドを持っています

その大工クラスを継承した弟子クラスが作成されました。

しかし、弟子クラスは大工クラスを継承したにも関わらずパティシエとなってしまいました。

そんな事情を知らない人は彼に大工の仕事を頼み、パティシエの仕事は頼まないでしょう。

 

このようにリスコフの置換原則に違反すると、クライアント側でそのサブクラスが何をできるのかがわかりにくく、使われなくなってしまうようになります。

また、「大工の継承者だから大工の仕事を頼んだのに全然働いてくれないよ!!」というように、サブクラスで本来できることができないために、思わぬ事故が起きる可能性もあります。

 

続く

fatena.hatenablog.com

 

 

 

 

参考

ボブおじさんのブログ

https://blog.cleancoder.com/

 

wiki

https://ja.wikipedia.org/wiki/SOLID

 

単一責任原則について

単一責任原則で無責任な多目的クラスを爆殺する - Qiita

 

オープンクローズドについて

5分で理解するオープン・クローズドの原則 - Qiita

 

リスコフの置換原則について

【オブジェクト指向】「リスコフの置換原則」について | プログラミングマガジン

その7 参照オブジェクトの正体は気にしない原則 : LSP

 

【ゴリ押し】六角形のマップ(ヘックスマップ)をタイルごとにハイライト表示させる

はじめに

こんにちは。

ストラテジーゲームでよく見る、六角形のマップをunityで作った話です。

前もって記述しますが作者は数学的知識が皆無なので、もっと楽な実装方法が多分あります、、、

 

 

モデルを用意する

左:マップ用モデル 右:ハイライト用モデル

私はblenderの <add mesh extra objects> アドオンを利用して作成しました

そこが知りたい方は[add mesh extra objects ハニカム]で検索を。

 

 

 

マウスに合わせてハイライトを動かす

このままでは味気がないので、ハイライト表示を追加します

実装手順

  1. カメラからマウスのrayを飛ばす
  2. rayがマップに衝突した座標を取得する
  3. 取得した座標をマップに配置できるよう整える
  4. 整えた座標を使ってハイライト表示を移動させる

1.カメラからマウスのrayを飛ばす

これは簡単に実装できました。

Camera-ScreenPointToRay - Unity スクリプトリファレンス

ScreenPointToRayはスクリーン上の点から画面の奥へと向かうrayを作ってくれます。便利。

 

2.rayがマップに衝突した座標を取得する

まずは、マップのオブジェクトに8番のレイヤーを追加,設定しておきます(名前は何でもok)

  


次に、この行を追加します

 

 

これにより、rayがマップに当たったときの座標をハイライト表示の座標に使うことができます。(うまく動かない場合はコライダーがマップについているか確認してください)

この段階ではハイライトがタイルをまたいで移動してしまっています

 

3.取得した座標をマップに配置できるように整える

ここが難関でした。

考え方としては、マップの座標を「座標A」と「座標B」の2種類に分け、マウスが座標Aの範囲内であれば処理Aを、マウスが座標Bの範囲内であれば処理Bを行わせるというものです。

 

なぜ2種類の座標に分けるかと言うと、座標Bの境界は「斜め線」で、座標Aの境界は「縦線」なので、これらの座標を1種類の座標として認識してしまうと座標の正規化が難しくなるためです(説明難しい)

 

座標Aと座標Bの処理分け

(作者は数学的知識が皆無なので雑で面倒くさい方法をとっています、、、)

ここから出てくる座標の具体的な数値は私が測った数値ですので、各自適切な値を当てはめてみてください

 

 

下ののNormalizePos関数は、受け取った座標を確認し,それがどの座標グループに入るのかを判断しています 

gist359d8065925b9f4153c4642e97a45201

 

 

↓コードの補足画像.  ※拡大推奨

 

 

また、座標Aは更に

  • Zが0付近のA-1
  • それ以外のA-2

に分けられています

 

 

座標Aの処理

 

A-1の処理

//原点の行のXとZ
if (absZ < MinZ)
{

   //原点の行のときのZ
   repos.z = 0f;

   //Xの処理
   if (Mathf.Abs(pos.x) > addedX)
     //原点列以外
     repos.x = Mathf.Floor((pos.x + addedX) / sAddedX) * sAddedX;
   else
     //原点列
     repos.x = 0f;

}

>>A-1の範囲内のとき、

  Z座標 : 0

       X座標 : マウスのX座標を正規化

 

A-2の処理

 //ZがA-2の範囲であれば
else if (absZ % addedZ >= MaxZ || absZ % addedZ <= MinZ)
{
    
    //行数
    float ColumnsNum = Mathf.Round(Mathf.Abs(pos.z) / addedZ);
    
    //Zの処理
    repos.z = ColumnsNum * addedZ * Mathf.Sign(pos.z);


    //Xの処理
    //偶数行と奇数行の処理
    if (!Utilities.Isodd(ColumnsNum))
    {

        if (Mathf.Abs(pos.x) < addedX)
            //0列目
            repos.x = 0f;
        else
            //0列目以外
            repos.x = Mathf.Floor((pos.x + addedX) / sAddedX) * sAddedX;

    }
    else
        //奇数行は偶数行を半個分右にずらす
        repos.x = Mathf.Floor(pos.x / sAddedX) * sAddedX + addedX;

}

>>A-2の範囲内のとき

  Z座標 : [現在の行]と[1行あたりの距離]、そして[pos.zの符号]から計算

  X座標 : 0列目はx=0、それ以外の列の奇数行目は単純な座標の正規化を、

      偶数行目はその正規化した値を右にずらします

 

※Utilities.Isodd()は渡した値が奇数ならtrueを返す静的メソッドです。詳しい説明は割愛します。

 

 

座標Bの処理

ここが1番面倒でした。

 

考え方としては、マップ上に一次関数のグラフを描き、利用します

一次関数は大雑把に言うと直線のグラフです

 

一次関数の式はどう求めるかと言うと、

一次関数の式は y=ax+b の形で表されます。今回、bの値は引くグラフによって異なるので求めず、aの値を求めます。

 

端的に言うと、下の図で言う[MinZ]を[addedX]で割ってください。

私が測った値を入れて計算すると、

MinZ / addedX = 0.125 / 0.21644 = 0.577527.....

でaの値は約0.57753となり、一次関数の式は

y = 0.57753x + b

と表せます。

マウスのグラフの位置と基準グラフの位置関係は、この式のbの値(切片)を見ればわかります

 

 

ここまでをコードに書くと、

else
{

    //y=0.57753x+bの式のbの値を求めて利用

    //傾きa
    float a = 0.57753f;

    //0.57753Xに代入
    float x = pos.x * a;

    //xとyの値からbの値を求める
    float b = pos.z - x;

}

 

 

しかし、このままでは右斜め上へ伸びるグラフのみしか描けないので、マウスの位置によって下図のように正と負を切り替える必要があります

 

 

private bool IsPlusPos(float x,float y)
{

    float addedZ = 0.375f;
    float addedX = 0.21644f;

    float nmX = Mathf.Floor(x / addedX);

   float nmY = Mathf.Floor(y / addedZ);

    bool isPlus = false;

    if (Utilities.Isodd(nmX-nmY))
    {

        isPlus = true;

     }

     return isPlus;

} 

IsPlusPos関数は、渡したx,yの座標が正のグラフの範囲内であればtrueを返し、負のグラフであればfalseを返す関数です

 

この関数を利用すると、先程のコードは

else
{

    //y=0.57753x+bの式のbの値を求めて利用

    //傾きa
    float a = 0.57753f;
    
    //追加-----------------------------

    //+と-のグラフを切り替える
    if (!IsPlusPos(pos.x, pos.z))
        a = -a;

    //---------------------------------     

    //0.57753Xに代入
    float x = pos.x * a;

    //xとyの値からbの値を求める
    float b = pos.z - x;

}

となり、マウスの座標によって正負を切り替えられるようになりました。

 

あとはこのbの値を使って正規化後の座標を特定するのみです。

else
{

    //y=0.57753x+bの式のbの値を求めて利用

    //傾きa
    float a = 0.57753f;
    
    //+と-のグラフを切り替える
    if (!IsPlusPos(pos.x, pos.z))
        a = -a;

    //0.57753Xに代入
    float x = pos.x * a;

    //xとyの値からbの値を求める
    float b = pos.z - x;
    
    //追加___________________________________

    
    float sign = Mathf.Sign(pos.z);
    
    //bとyの符号が違うとバグるので対策。
    if (sign != Mathf.Sign(b))
        sign = -sign;
    
    //MinZ以上MaxZ未満であればok
    float sub = MaxZ-0.01f;

    //bを0.25で割ったあまりをもとに分岐 
    if (b % MaxZ < MinZ * sign)
        //MinZ未満ということは基準より少しはみ出しているということ
        repos = NormalizePos(new Vector3(pos.x, pos.y, pos.z + sub));
    else
    if (b % MaxZ >= MinZ * sign)
        //MinZ以上ということは基準線にぎりぎり届いていないということ
        repos = NormalizePos(new Vector3(pos.x, pos.y, pos.z - sub));

}

追加後の内容では、

bをMaxZで割ったあまりを「マウスのグラフの切片」

MinZを「基準グラフの切片」

として上下どちらのタイルに正規化すべきかを判断しています。

 

上下どちらのタイルに正規化すべきかが決まったあとは、調節した座標を用いて自身であるNormalizePosを再度呼んでいます

 

座標の正規化メソッドのまとめ

NormalizePos関数をまとめると次のようになります

giste36cbe067999d9f6d76083b17ab8ae28

 

 

4.整えた座標を使ってハイライト表示を移動させる

ここからは、ここまでの部品を組み合わせていきます

gist663f2f88f41aff410cdd1a6a135c9bad

 

実行すると,,,

すげえ!

 

 

感想

ぶっちゃけこの仕様いるかどうかといえばいらないです。

数学の知識が足りないので勉強したらもっと良いコードになると思います。(数学勉強しよ、、、

文章やコードで何かおかしな点があればコメントをお願いします。

 

ありがとうございました