パックマンライクなゲーム制作過程の振り返り① マップの作成

マップの作成 ゲーム制作

パックマンライクなゲーム制作の振り返りになります。
ゲームは↓で遊べるので、よければ遊んでみてください。

前の投稿はこちらです。

今回のテーマ

最初のテーマは、マップの作成です。パックマンライクということで、シンプルに全体が真四角なフィールドと障害物だけで制作することにしました。縦21マス×横29マスで、基本的に1マスの通路上の道をキャラクターやモンスターが縦横に移動するだけのイメージです。

床に効果を作ったり、障害物の種類を増やしたり、色々応用出来る部分もありましたが、とりあえず、ここにこだわりはなかったので、時間はかけずシンプルに行くことにします。

ひとまず完成イメージとしては、以下の通りになります。

素材の準備

フリーの以下の素材を使用させていだきました。

参考:Unity Asset Storeからのダウンロード方法

一応、Unity Asset Storeからのダウンロード方法を記載しておきます。※上記のPixel Art Top DownはImport済なので、別の素材例になります。
web上からImportする場合の手順になります。

  • 「Add to My Assets」を押す
  • Acceptを押す
  • Open in Unityを押す(unityが開きます)
  • Package Managerが開かれているはずなので、Downloadを押す(しばらく待つ)
  • importを押す
  • importするファイルを選んで、importを押す(基本的にすべてのファイルにチェックが入っているので、そのままimportで問題ないはず)
  • Projectフォルダの中にファイルが追加されます。(他のフォルダは作成済のゲームのものです。)

Pixel Art Top Downをダウンロードすると、Cainosというフォルダが出来上がっていると思います。
その中から使用したのは、「Cainos > Pixel Art Top Down > Tile Pallete」にあるタイルマップと「Cainos > Pixel Art Top Down > Texture」のTX PlantにあったTX Bush T6だけです。アセット自体にはもっと多くのマップ素材や動かせるキャラクターもありますので、より複雑なマップも作れると思います。今回は最小限のパーツだけでマップを作ることにしました。

タイルマップで床部分を作成する

さて、ここから実際にマップ作成を行っていきます。unityでマップを作成する場合、タイルマップを使う方法がメジャーなようです。

unityのタイルマップとは何かというと、その名前の通り、画像をタイルのように分割し、分割されたパーツを選択して画面上に敷き詰めていくイメージでUIを作っていく方法になります。

Tilemap編集時のイメージ:右にあるTilePaletteでパーツを選んで、左のSceneに敷き詰めていく

触ったことのある方はRPGツクールのマップの作り方をイメージすると分かりやすいかもしれません。ただし、unityの場合、RPGツクールのようにジャンル特化はしていませんので、細かい設定は自分でやらないといけないです(例えば、今回は触れていないですが、そのままだとキャラクターが通れる通れないなどの区別がつきません)。以下、タイルマップによる床の作成手順になります。

画像を分割してタイルマップを用意する

前述したunityアセットの素材は、最初から分割されたタイルマップが用意されています。(後述のTilemapからすぐに使用可能です)

ですが、理解のために、タイルマップを自分で用意する手順もまとめておきます。

1.画像を用意する

タイルマップにしたい画像を用意します。(サンプルでマップ用ではない適当な画像でやっていきますが、大抵のゲーム素材の場合、32×32で分割しやすいファイルを用意されていると思います。)

(画像を使えばなんでもできるという確認のためにゲームと全く関係ない)ブログ用に取得していたフリー素材の画像を使っていきます。

2.画像をタイルに分割する

  • ファイルをプロジェクト内のフォルダに入れます。unityにドラッグ&ドロップでも、フォルダにコピーしてもどちらでも構いません。下記では25351_1の画像をドラッグ&ドロップで入れています。ゲーム作成後のフォルダなので、他にも入っていますが、気にしないでください。
  • 画像はタイルに分割する必要があります。そこで、Inspectorで画像を処理していく必要があります。使いたい画像を選択して、Inspectorの項目を画像のように入力・選択します。
    ただ、正直言うと、細かい仕様の部分は分かっておらず、参考サイトをそのまま使用しています。ちょっと調べてみようとも思ったのですが、一つ一つが結構細かそうなので、また、時間があったら別で調べてみます。今は、そういうものという前提で設定しています。
    デフォルトからの主な変更点は以下の通りです。
    • Sprite Mode:Multiple
    • Pixels Per Unit:32
    • Mesh Type:Full Rect
  • 「Apply」を押した後に「Sprite Editor」を押して編集画面を開きます。
  • 「Slice」ドロップダウンをクリックしてて(もしかすると隠れている場合もあるので、windowを広げると出てきます)、Typeを「Grid By Cell Size」に、Pixel Sizeを区切るサイズ(今回は32×32)に指定します。下の方で分割後のイメージが確認できます。問題なければ、「Slice」を押します。
  • 反応が分かりにくいですが、画像が分割されています(パーツごとに選択できるようになります)。Sprite Editorを閉じるときにSaveするかを確認されるので、Saveを選びます。これでタイルの分割は完了です。
  • 問題なく終わっていれば、フォルダに入れた画像がパーツごとに確認できます。

3.タイルマップでマップを描く

  • タイルマップをゲーム画面上に入れていきます。まずは、Hierarchyで「2DObject > Tilemap > Rectangular」を選択しオブジェクトを作成します。完全な余談ですが、Rectangularは日本語では矩形で「くけい」と読むことを始めて知りました。言葉は難しいですが、要は長方形のことのようです。
  • オブジェクト名は適当につけてください(ここでは、Testtilemapとしています)。Sceneビューにマス目のようなものが出来ており。左上の方に「Open Tile Palette」というウインドウも表示されます。「Open Tile Palette」を押します。
  • Tile Paletteのビューが開きます(作成済みのTile Paletteが表示されています)。Tile Paletteを選択できるドロップダウンから「Create New Palette」を押します。
  • Nameに適当な名前を入れて、Createを押します。Paletteを作成するフォルダを選びます(Palette用のフォルダを用意しておくとよいと思います)。
  • 先ほど作成したタイルマップの画像をTile Paletteの画面内にドラッグ&ドロップします。パレットの保存先が出てくるので、先ほど作成したフォルダにパレットの名前をつけて保存します。パレットが出来上がります。
  • 後は作成したタイルマップからパーツを選んで、Sceneビューに配置していくだけです。サンプルはマップ用の画像ではないので、コラージュ画像みたいになりますが、マップ用に作られた画像などであれば、マップを作成していくことが可能です。

以上がタイルマップでのマップの用意の仕方になります。最初に書いたように、今回のunityアセットは最初からタイルマップが用意されているので、途中はスキップして、最後のタイルマップからマップを作成するところだけで可能です。

実際のゲームのマップでは、床のタイルマップを使って、縦21マス、横29マスの空間を作ります(この範囲がキャラクターやモンスターの移動可能な範囲になります)。全部ただの緑でもいいんですが、ちょっと寂しいので、タイルマップのパーツを少し変えて装飾を入れています。この辺のデザイン的知識はないので、適当です。

ちょっと反省点としてですが、最初に意識できていなかった点として、マップの場所を適当に作ってしまったので、後々、プログラムでいろいろ制御するときにちょっと面倒なことになってしまいました。座標が分かりやすくなるように、x軸とy軸はプラスのところに作るとか、タイルの位置を小数点なしで指定できるように作る(今回は0.5単位で真ん中に来るようにしてしまった)など工夫をしておくと、後々の苦労が減ったのではと思っています。

障害物を用意する

障害物はアセットのTX Bush T6を、今描いたマップ上に配置していくだけです。だけなんですが、今回、障害物の数が膨大になってしまい大変なので、プログラムで障害物の場所を指定し、ゲーム開始時に障害物が自動で設置されるような設定をしました。

ソースコードを見てもらうと以下の通りです。

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

public class GameManager : MonoBehaviour
{
    public GameObject Obstract;
    
    // Start is called before the first frame update
    void Start()
    {
        int[][] map_generator = new int[][]
        {
            new int[] { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1 },
            new int[] { 1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 },
            new int[] { 1,0,1,0,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,0,0,1,0,1,0,1 },
            new int[] { 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1 },
            new int[] { 1,1,1,1,0,1,1,1,0,0,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,0,1,0,1 },
            new int[] { 1,1,1,1,0,1,1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,1 },
            new int[] { 1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,0,1,1,1,1,0,1,0,1,1,1,1 },
            new int[] { 1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1 },
            new int[] { 1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,0,1,0,1,1,1,1,0,1,1,1,1,1,1 },
            new int[] { 1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 },
            new int[] { 0,0,1,1,0,1,0,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,0,0 },
            new int[] { 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1 },
            new int[] { 1,0,1,1,1,1,0,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,0,1,1,1,1,0,1 },
            new int[] { 1,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 },
            new int[] { 1,0,0,0,1,1,1,1,1,0,1,1,1,1,0,1,1,1,1,1,1,1,0,1,1,1,1,0,1 },
            new int[] { 1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,1 },
            new int[] { 1,0,1,1,1,1,1,1,1,1,0,0,0,0,1,1,1,1,0,1,1,0,0,0,0,0,0,0,1 },
            new int[] { 1,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,1 },
            new int[] { 1,0,1,1,1,1,1,1,0,0,0,1,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1 },
            new int[] { 1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,0,0,1 },
            new int[] { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1 }
        };
        for (int i=0; i < map_generator.Length; i++)
        {
            for (int j=0; j < map_generator[i].Length; j++)
            {
                if (map_generator[i][j] == 1)
                {
                    float i_float = (float)-13.0+i;
                    float j_float = (float)-10.5+j;
                    Instantiate(Obstract, new Vector2(j_float, i_float), Quaternion.identity);
                }
            }
        }

        // 入口部分に木を置く
        Instantiate(Obstract, new Vector2(-11.5f, -3.0f), Quaternion.identity);
        Instantiate(Obstract, new Vector2(18.5f, -3.0f), Quaternion.identity);
        Instantiate(Obstract, new Vector2(3.5f, -14.0f), Quaternion.identity);
        Instantiate(Obstract, new Vector2(3.5f, 8.0f), Quaternion.identity);
    }
}

障害物を配置する場所を1、そうでない場所(歩ける場所)を0にして、縦横のfor文を回して障害物を配置します(書き方間違えて、縦が逆になってしまってますが)。このやり方だと、テキストで配置場所を変えるだけで、障害物の場所変更が出来るので、今回のように1つのマップを用意するだけであれば、楽でした。作成したマップの座標に合わせて、細かな座標は修正してください。

けっこうな力業になっているような気もしますが、まあ、多分直接座標をオブジェクトごとに書き直すよりは楽なはず。多分。

なお最後の4行に関しては、範囲外にプレイヤーや敵キャラクターが出ていかないように、入り口を塞ぐ木を配置しています。(マップの範囲外のため、for文とは別で作成しています。)

これをGameManagerとしてScriptを保存しておき、空のGameObjecにアタッチしておき、InspectorのObstractにTX Bush T6をアタッチすれば準備完了です(アタッチの方法など詳細は略)。以下の画像は完成したゲーム画面を持ってきたので、色々と作成済みのものが出てきています。

完成

ということで、マップの完成です。実行してみると以下のようにそれっぽいマップが表示されます。

この中をプレイヤーキャラクターを動かして、モンスターを避けつつ、アイテムを集めてスコアを伸ばす、というゲーム部分を作成していきます。

マップの作成については以上になります。次回はプレイヤーキャラクターやモンスターキャラクターの作成について、書いていく予定です。

コメント

タイトルとURLをコピーしました