ユニティちゃんのRPGを作ってみよう10ーコマンド画面の作成とステータスの表示ー

記事内に広告が含まれています。

今回はユニティちゃんRPGでコマンド画面の作成と選択したキャラクターのステータスを表示するところを作っていきたいと思います。

前回はユニティちゃんRPGで使用するキャラクターステータスデータとアイテムデータの作成を行いました。

ユニティちゃんのRPGを作ってみよう9ーアイテムやステータス等のデータ作成ー
ユニティちゃんのRPGでアイテムやステータス等のデータの作成についてやっていきたいと思います。

ユニティちゃんのRPGを作ってみようの他の記事は

ユニティちゃんのRPGを作ってみよう
ユニティちゃんのRPGを徐々に作っていくカテゴリです。

から見ることが出来ます。

前回キャラクターのステータスをアセットファイルに設定したので、そのデータを取得して画面に表示するという事を行っていきますが、今回から処理が複雑になってきますので注意してください。

どうやって注意するんだ!!というのはナシでお願いします。(^_^;)

スポンサーリンク

コマンド画面の作成

キャラクターのステータスを表示する為にはコマンド画面を作成し、その中でステータスコマンドを選択できるようにしなければなりません。

そこで最初にコマンド画面UIの作成を行いたいと思います。

コマンドパネルの作成

Villageシーンのヒエラルキー上で右クリックからUI→Canvasを選択し、名前をCommandとします。

Commandを選択し、インスペクタのCanvas ScalerのUI Scale ModeをScale With Screen Sizeにします。

これはゲーム画面のサイズに合わせてUIのScaleを変化させる為です。

Referrence ResolutionはXに1024、Yに768を設定しこの画面サイズを元にUIのScaleを調整します。

Referrence Resolutionは適宜変更してください。

Screen Match ModeはExpandにし、UIが広がるようにします。

ユニティちゃんRPGのCommand

Commandを選択した状態で右クリックからUI→Panelを選択し、名前をCommandPanelとします。

CommandPanelのサイズを調整します。

シーンビューでCommandPanelの四隅の矢印をShiftキーを押しながらドラッグし左上に表示されるようにします。

今回は以下のような感じのサイズにしました。

ユニティちゃんRPGのCommandPanelのサイズ

CommandPanelの子要素にはコマンドの選択肢を表示しますが、その選択肢を整列させる為にVertical Layout Groupを追加します。

CommandPanelのインスペクタのAdd ComponentからLayout→Vertical Layout Groupを選択します。

Child AlignmentはMiddle Centerにし、中央に表示するようにし、Child Force ExpandのWidthとHeightにチェックを入れて子要素のコマンドボタンを強制的に広げるようにします。

次にAdd ComponentからLayout→Canvas Groupを取り付けます。

Canvas Groupはコマンドを選択していった時にパネル毎に選択出来たり出来なくしたりの切り替えで使用します。

Canvas Groupが設定されたUIはCanvas Groupのinteractableのチェックを外すとマウスで選択したりフォーカスの移動等が出来なくなります。

なのでスクリプトからCanvas Groupのinteractableの値を操作し、パネル毎に選択の可・不可を切り替えます。

ユニティちゃんRPGのCommandPanelにコンポーネントを追加する

CommandPanelを選択した状態で右クリックからUI→Buttonを選択し、名前をStatusButtonとします。

StatusButtonのインスペクタでWidthに150、Heightに50を設定します。

またImageのチェックを外すかコンポーネント自体を削除しボタンの背景は使わないことにします。

ButtonのTransitionをNoneにし、マウスが上に来た時やボタンの移動等をしてもボタン自体のアニメーションはしないことにします。

ユニティちゃんRPGのStatusButtonの設定

その代わりにボタンが選択された時はボタンの横にアイコンを表示しわかりやすくします。

StatusButtonを選択した状態で右クリックからUI→Imageを選択します。

Imageはボタンの横に表示されるように移動させます。

Imageにはボタンが選択された時に表示するSource Imageを設定します。

ユニティちゃんRPGのStatusButtonの子要素のImageのインスペクタ

StatusButtonの子要素のTextにはステータスという文字列を設定し、Font SizeやAlignmentを変更し、Colorでテキストの色を変更します。

ユニティちゃんRPGのStatusButtonの子要素のText

これで最初のCommandを表示する部分が出来ました。

ユニティちゃんRPGの最初のコマンド表示画面

ステータスを表示するキャラクター選択パネル

StatusButtonを押した時に次はどのキャラクターのステータスを表示するかを選ぶ必要があります。

Commandを選択した状態で右クリックからUI→Panelを選択し、名前をSelectCharacterPanelとします。

SelectCharacterPanelにもCanvas GroupとVertical Layout Groupコンポーネントを取り付けます。

SelectCharacterPanelはStatusButtonが押されるまでは有効にならないので、Canvas Groupのinteractableのチェックは外しておきます(SelectCharacterPanelの子要素のUIを操作させない)。

ユニティちゃんRPGのSelectCharacterPanelのインスペクタ

SelectCharacterPanelはCommandPanelと近接して作成しました。

ユニティちゃんRPGのSelectCharacterPanelのサイズ

パネルの表示位置やサイズ等は同じにする必要はありません。

キャラクターのステータスを表示するパネル

最後にキャラクターのステータスを表示するパネルを作成します。

Commandを選択した状態で右クリックからUI→Panelを選択し、StatusPanelという名前にします。

StatusPanelの子要素にPanelを作成し、名前をCharacterNamePanelとし、その子要素にTextを作成します。

ユニティちゃんRPGのStatusPanelのキャラクター名表示テキスト

StatusPanelの子要素にPanelを作成し、名前をStatusParamPanelとし、その子要素にTextを4つ作成します。

Textの名前はTitle、Param1、Division、Param2とします。

Font Sizeは25にします。

StatusPanelの階層は

ユニティちゃんRPGのStatusPanelの階層

上のようになります。

StatusPanelの領域は

ユニティちゃんRPGのStatusPanelのパネルの分け方

上のような感じで領域を分けました。

StatusParamPanelの4つのTextではAlignmentをそれぞれ変えます。

Titleは左寄せの上から表示

Param1は右寄せの上から表示

DivisionはTextに最初の行に空の文字、次の行に/を書いて改行し、/を書きます。

これは

HP / 最大HP

という表示にする為です。

Divisionは真ん中の上から表示

Param2は右寄せの上から表示とします。

ユニティちゃんRPGのStatusParamPanelの子要素のTextのAlignment

これでコマンドの選択パネルとキャラクターの選択パネル、ステータス表示パネルが出来ました。

コマンド処理スクリプトの作成

コマンドのUIが出来たので次はコマンドの画面を表示したりボタンが押された時の処理を行うスクリプトを作成していきます。

コマンド画面を開くスクリプトの作成

まずはAssets/RPG/Scriptsフォルダの中に新しくUnityChanCommandScriptスクリプトを作成します。

UnityChanCommandScriptスクリプトはMenuボタンを押した時にコマンド画面を開く処理とコマンド画面を閉じる処理を記述しています。

LoadSceneManager型のフィールドを用意しシーンの遷移処理をしているスクリプトを保持するようにします。

LoadSceneManagerは後で変更しますが、シーン遷移途中にコマンド画面が開いてしまうと困るのでシーン遷移途中ではコマンド画面を開けないようにします。

その為LoadSceneManagerスクリプトではシーン遷移中かどうかのフラグ(isTransition)を用意し、UnityChanCommandScriptからその値を調べてコマンド画面を開くかどうかを決めます。

シーン遷移中やユニティちゃんが会話の途中だった場合はreturnを実行し、その後の処理をしません。

これは会話途中にコマンド画面が開くと困るからです。

Menuボタンを押したらCommandがアクティブの時はUnityChanScriptスクリプトのSetStateメソッドを呼び出してユニティちゃんをCommand状態に変更します。

CommandがアクティブでなければExitCommandメソッドを呼んで選択しているボタンを解除し、ユニティちゃんの状態をNormal状態に変更します。

現時点ではエラーが起きているので修正を加えていきます。

LoadSceneManagerとUnityChanScriptスクリプトの修正

UnityChanCommandScriptからユニティちゃんの状態を変更したりシーン遷移中かどうかを調べる必要が出たのでその処理を追加します。

UnityChanScriptスクリプトに処理を追加

UnityChanScriptに新しくCommand状態を作成します。

次にUpdateメソッド内でCommandの時の処理を追加します。

UnityChanScript内では特に何もしないので空白のままです。

状態変更メソッド内を変更します。

Command状態にする時は移動速度を0にするのとアニメーションパラメータのSpeedを0にしているだけです。

他の状態とやっていることが同じなのでまとめて処理をしたいところですが、今後処理を分ける可能性もあるのでとりあえずそのままにしておきます。

LoadSceneManagerスクリプトに処理を追加

LoadSceneManagerに処理を追加します。

まずはシーン遷移中かどうかを表すisTransitionフィールドを用意します。

GoToNextSceneメソッドが呼び出されたらisTransitionにtrueを入れます。

新しくIsTransitionメソッドを用意しisTransitonの値を確認出来るようにします。

エラーが消えたと思いますので、UnityChanゲームオブジェクトにUnityChanCommandScriptを取り付けます。

UnityChanゲームオブジェクトのインスペクタでUnityChanCommandScriptのCommandUIにCommandゲームオブジェクトをドラッグ&ドロップして設定しておきます。

コマンド処理をするCommandScriptの作成

次はUnityChanCommandScriptでコマンド画面を開いた後のコマンド処理を行うCommandScriptスクリプトを作成します。

処理が分かり辛いので細かく作成していきます。

フィールド宣言部

まずはフィールドの宣言部分です。

CommandModeは今現在コマンドがどの状態なのかを表す列挙型です。

CommandPanelはコマンドパネルを開いた状態、StatusPanelSelectCharacterはどのキャラクターのステータスを見るかの選択パネルを開いた状態、StatusPanelはキャラクターのステータスを表示している状態です。

currentModeはその状態を入れておくフィールドです。

firstSelectButtonはコマンド画面を開いた時に最初に選択状態にするボタンを設定します。

commandPanel、statusPanel、selectCharacterPanelは先ほど作ったパネルを設定します。

コマンドパネルとキャラクター選択パネルのCanvas Groupの保持し、状況に応じてinteractableの値を切り替えます。

characterNameTextはCharacterNamePanelの子要素のText、statusTitleTextはStatusParamPanelのTitle、statusParam1TextはParam1、statusParam2TextはParam2を設定します。

PartyStatus型のpartyStatusフィールドはパーティーに関するステータスを保持するアセットファイルを設定します。

これは後で作成します。

characterPanelButtonPrefabはSelectCharacterPanelの子要素に表示するボタンのプレハブでパーティーメンバー数分のインスタンスを生成することになります。

これも後で作成します。

Stack型のselectedGameObjectStackは新しいパネルを開いてCancelボタンを押して前の画面のパネルに戻る時に前のパネルで選択していたボタンを選択する為に使用します。

StackはPushでデータを入れ、Popで取り出しますが、最後に入れたものからPopで取り出せるので最後に選択したゲームオブジェクトから取り出せます。

Stackについては

C#のStackを使用してみる
C#のStackをUnity付属のMonoDevelopを使用して勉強していきます。

を参照してください。

今回はステータスコマンドしかないですが例えばアイテムコマンドを作成した場合に

アイテムコマンドの選択→どのキャラクターのアイテムを表示するかのパネル→ユニティちゃんの持っているアイテム表示パネル

と開いてCancelボタンを押して「どのキャラクターのアイテムを表示するかのパネル」に戻った時にユニティちゃんのボタンを選択した状態にする為です。

Awakeメソッド

次にAwakeメソッドを追記します。

Awakeメソッドはスクリプトが設定されたゲームオブジェクトがアクティブになった初めの1回だけ実行されます。

unityChanCommandScriptはGameObject.FindWithTagを使ってPlayerタグを設定したUnityChanを探し、そこから取得しています。

その他は現在のコマンドの設定や自身(Command)のから階層を辿ってゲームオブジェクト、CanvasGroup、Text等を取得する処理を記述しています。

OnEnableメソッド

OnEnableメソッドはゲームオブジェクトがアクティブになった時に実行されます。

OnEnableが呼び出されるのはCommandゲームオブジェクトがアクティブになった時なので、コマンド画面を最初に開いた状態の時に実行されます。

なので初期化処理を実行しています。

SelectCharacterPanelでキャラクターを選択するボタンをプレハブからインスタンス化して表示しますが、Cancelボタンを押したり、コマンド画面自体を閉じた時にインスタンス化したボタンが残っていると困るので、SelectCharacterPanelの子要素がある場合は削除しています。

子要素の最初の要素から削除するとうまく処理が出来ません。

なぜなら0番目の要素を削除すると1番目の子要素が0番目になるからです。

なのでSelectCharacterPanelの最後の子要素から削除しています。

selectedGameObjectStackは次のパネルを開いた時に前のパネルで選択していたゲームオブジェクトを登録しているフィールドなので、新しくコマンド画面を開いた時はClearメソッドを使って中身を削除しています。

コマンド画面を開いた時はCommandPanelのCanvas GroupのinteractableをtrueにしCommand Panelだけを選択出来るようにします。

それ以外のCanvas Groupのinteractableはfalseにします。

EventSystem.current.SetSelectedGameObjectメソッドを使って最初に選択するゲームオブジェクトを設定しています。

Updateメソッド

Updateメソッドを追記します。

UpdateメソッドではCancelボタンを押した時の処理を記述しています。

キャンセルボタンを押した時に現在どのコマンドの状態なのか?を調べて処理を分岐させます。

CommandMode.CommandPanelの時はCommandPanelを開いたばかりの状態でCancelボタンを押したのでコマンド画面を閉じます。

コマンド画面を閉じる時はUnityChanCommandScriptのExitCommandメソッドを呼んでユニティちゃんの状態を変化させます。

CommandMode.StatusPanelSelectCharacterもしくはCommandMode.StatusPanelの時はCommandPanel状態に変化させる為の処理を記述しています。

行っていることは現在のパネルのCanvas Groupを無効にし、CommandPanelのCanvas Groupを有効にしたり、作成したキャラクター選択ボタンの削除をしています。

CommandPanelに戻る時はCommandPanelで最後に選択していたゲームオブジェクトがStackのselectedGameObjectStackに入っているのでPopしてそのゲームオブジェクトを選択状態にします。

SelectCommandメソッド

SelectCommandメソッドはStatusButtonを押した時に実行させるようにします(後で設定します)。

引数で受け取った文字列がStatusだった時に現在のコマンドを変更し、現在選択しているゲームオブジェクトをselectedGameObjectStackにPushします。

その後partyStatus.GetAllyStatusメソッドでパーティーメンバー数分のボタンを作成しSelectCharacterPanelの子要素に配置します。

作成したボタンの子要素のTextを取得しそこにキャラクター名を入れます。

作成したボタンのonClickにイベントリスナーを取り付けボタンが押されたらShowStatusメソッドにキャラクターステータスを渡して呼び出します。

ここら辺は少し分かり辛いですが用はcharacterPanelButtonPrefabというボタンのプレハブをインスタンス化してヒエラルキー上に配置し、そのボタンが押された時に実行するメソッドを指定しているのが

の部分です。

引数はラムダ式で記述していますので、ラムダ式ってなんだ?と思った方は

C#でデリゲートとラムダ式を使ってみる
C#のデリゲートとラムダ式をUnity付属のMonoDevelopで使用してみます。

を参照してみてください。

上の部分でSelectCharacterPanelを階層の一番最後に並び替えています。

こうすることでSelectCharacterPanelと他のパネルが重なって見えづらくなっていてもSelectCharacterPanelが一番手前に表示され見えやすくなります。

キャラクター選択ボタンを作成したら子要素の0番目(一番最初の子)を選択状態にします。

ShowStatusメソッド

先ほどのcharacterPanelButtonPrefabから作成したインスタンスのボタンがクリックされた時に呼び出していたのがShowStatusメソッドです。

引数ではキャラクターのステータスを受け取っています。

やっていることは単純で受け取ったAllyStatusのデータから該当するデータを取得し、それをStatusParamPanelの子要素のTitle、Param1、Param2に代入し表示しているだけです。

毒と痺れ等の状態異常は持っている状態異常を連結させて表示する為に条件分岐をしています。

装備している武器と装備している鎧はNullの可能性があるのでちょっと工夫をしています。

上の部分ではまずallyStatus.GetEquipWeapon()で装備している武器を取得し、GetAmout()でその武器の強さを取得しています。

?.GetAmount()はNull条件演算子でNullではなければallyStatus.GetEquipWeapon().GetAmount()の値を取得し、NullだったらNullを返します。

その後の??はNull合体演算子でallyStatus.GetEquipWeapon()?.GetAmount()がNullだったら??の後の0の値を取得し、NullじゃなければallyStatus.GetEquipWeapon().GetAmount()の値を取得します。

結果としてallyStatus.GetEquipWeapon().GetAmount()がNullじゃなければallyStatus.GetEquipWeapon().GetAmount()、Nullだったら0を入れます。

ここら辺はちょっとわかりずらいので普通のif文を使った処理で書いても問題ありません。

のような感じです。

スクリプトを作成した時点ではPartyStatusを作っていないのでエラーが発生します。

パーティーステータスの作成

パーティーステータスはScriptableObjectクラスを継承してアセットファイルとして作成します。

作り方は前回の記事でやった事と同じなので同じように作成してみてください。

Assets/RPG/Scripts/Statusフォルダ内に新しくPartyStatusスクリプトを作成します。

パーティー全体のステータスなので、持っているお金とパーティーメンバーのステータスを保持するListを持たせています。

SetAllyStatusメソッドではパーティーメンバーに登録されていなければメンバーにキャラクターのステータスを登録します。

Assets/RPG/Data/Statusフォルダ内で右クリックからCreate→CreatePartyStatusを選択します。

作成したPartyStatusには前回作成したUnityChanStatusとYujiStatusのアセットファイルを追加します。

ユニティちゃんRPGのPartyStatus

これでPartyStatusが出来ました。

CharacterButtonの作成

SelectCommandメソッド内でパーティーメンバー数分のボタンをプレハブからインスタンス化する処理を記述しましたが、そのプレハブをまだ作っていませんでした。

SelectCharacterPanelを選択した状態で右クリックからUI→Buttonを選択し、名前をCharacterButtonとします。

CharacterButtonのインスペクタでWidthを200、Heightを50にします。

Imageは使わないのでチェックを外すかコンポーネントを削除します。

Buttonのアニメーションは行わないのでTransitionはNoneにします。

CharacterButtonを選択した状態で右クリックからUI→Imageを選択します。

StatusButtonを作成した時と同じようにボタンが選択されたら表示するアイコンがImageになります。

ユニティちゃんRPGのキャラクター選択ボタンのプレハブのImage

CharacterButtonの子要素のTextのFont Sizeを25にし、Colorをしろいろ

Imageの位置を調整しボタンの左側に来るようにします。

ユニティちゃんRPGのキャラクター選択パネルの表示

上のような感じの位置にしました。

Assets/RPG/Scriptsフォルダ内に新しくCommandPanelButtonScriptスクリプトを作成し、CharacterButtonに取り付けます。

ISelectHandlerとIDeselectHandlerインターフェースを継承して作成します。

これらはOnSelectメソッドとOnDeselectメソッドを実装する必要があります。

OnSelectメソッドはボタンが選択された時に呼び出され、OnDeselectメソッドはボタンの選択が解除された時に呼び出されます。

このスクリプトが行っていることは単純で、CharacterButtonボタンの子要素のImageゲームオブジェクトのImageコンポーネントをselectedImageに保持し、ボタンが選択された時はそれを表示し、選択解除された時に非表示にしているだけです。

Awakeメソッドではコンポーネントの取得を行っています。

OnEnableメソッドではボタンが表示された時にEventSystemで現在選択されているのが自身だったらアイコンの表示、そうじゃない場合は非表示にしています。

Awakeメソッドで似たような処理を記述していましたが、なぜかWorldMapシーンだとうまくいかない為OnEnableメソッドで処理をするようにしました。

またCharacterButtonのインスペクタのAdd ComponentからAudio→Audio Sourceを取り付けます。

アセットストアで「Minimal UI Sounds」で検索しボタン選択時の効果音をインポートします。

CharacterButtonのAudio SourceのAudio ClipにAssets/Minimal UI SoundsのClack_Minimal UI Soundsを設定します。

Play On Awakeのチェックを外し自動で音声を再生しないようにします。

CharacterButtonは以下のようになりました。

ユニティちゃんRPGのCharacterButtonプレハブの最終的なインスペクタ

ButtonのOn Clickには何も設定していませんが、スクリプトからOn ClickにShowStatusメソッドを実行するように設定したので問題はありません。

ここまで出来たらAssets/RPG/Prefabs/UI/Commandフォルダ内にCharacterButtonゲームオブジェクトをドラッグ&ドロップしプレハブにします。

ヒエラルキー上のCharacterButtonはいらないので削除してください。

CommandのCommandScriptの設定

CommandゲームオブジェクトにCommandScriptを取り付けます。

CommandのCommandScriptのインスペクタでpartyStatusとcharacterPanelButtonPrefabを設定します。

CommandのCommandScriptのインスペクタの設定

partyStatusはAssets/RPG/Data/Status/PartyStatusを設定し、characterPanelButtonPrefabにはAssets/RPG/Prefabs/UI/Command/CharacterButtonを設定します。

StatusButtonを押した時に呼び出すメソッドの設定

StatusButtonを押した時に初めてキャラクターの選択パネルを開きます。

つまりStatusButtonを押したらCommandScriptスクリプトのSelectCommandメソッドに実行するコマンドの文字列を渡して実行する必要があります。

StatusButtonを選択し、インスペクタのButtonのOn ClickでCommandをドラッグ&ドロップして実行するメソッドにSelectCommandを指定し、文字列にStatusと入力します。

ユニティちゃんRPGのStatusButtonを押した時に実行するメソッドを設定

StatusButtonを選択した時や選択を解除した時もCharacterButtonと同じようにアイコンに表示や非表示を行いたいので、StatusButtonにもCommandPanelButtonScriptを取り付けておきます。

ボタンを選択した時に音を鳴らしたいのでCharacterButtonと同じようにAudio Sourceを取り付けAudio ClipにAssets/Minimal UI Sounds/Clack_Minimal UI Soundsを設定します。

Audio SourceのPlay On Awakeのチェックを外し最初から音が鳴らないようにします。

ユニティちゃんRPGのStatusButtonにAudio Sourceコンポーネントを取り付ける

CancelボタンとMenuボタンの設定変更

コマンドを表示するボタンにMenuを指定し、元のパネルに戻る時はCancelボタンを指定しました。

またボタンを押す時はSubmitボタンになるのでSubmitボタンも変更します。

改めてUnityメニューのEdit→Project Settings→Inputでボタンの設定を行います。

Submitボタンの設定にActionボタンと同じPS3の〇ボタンを押した時と同じ設定を追加します(使用するコントローラーによって変わります)。

Actionボタンはjoystick button 1を設定しているので、SubmitのAlt Positiveにも同じ設定をします。

Alt PositiveはPositiveの二つ目の設定です。

ユニティちゃんRPGのSubmitボタンにPS3の〇ボタンを追加する

次はCancelボタンです。

PS3の×ボタンを押した時の設定を追加します。×ボタンはjoystick button 2になります。

ユニティちゃんRPGのCancelにPS3の×ボタンを追加する

次はMenuボタンです。

Menuボタンは以前作成しましたが、キーボードとマウスで操作する場合のキーボードの設定をしていなかったので、設定を追加します。

Alt Positiveに1を入力し、キーボードの1キーが押されたらコマンド画面を開くようにします。

ユニティちゃんRPGのMenuボタンに1キーを追加する

コマンド画面の初期状態の設定

コマンド画面のUIが少し複雑になりましたが、ここでコマンド画面のアクティブと非アクティブの設定をしておきます。

Command、SelectCharacterPanel、StatusPanelはインスペクタで名前の横のチェックを外し非アクティブにしておきます。

CommandPanelはインスペクタでチェックし、アクティブな状態にしておきます。

コマンド画面の確認

コマンド画面とステータスの表示機能が出来たので確認してみましょう。

上のようになりました。

終わりに

今回はCommandをVillageシーンにしか配置していないのでシーンを遷移するとコマンド画面が開きません。

WorldMapシーンのUnityChanにUnityChanCommandScriptを取り付け、VillageシーンのCommandを複製してWorldMapシーンに移動させた後にUnityChanCommandScriptのcommandUIをWorldMapのCommandに変更します。

そうすればWorldMapシーンでも同じようにコマンド画面を開くことが出来ます。

ただ・・・この後アイテムコマンド等も追加していくのでそれが作り終わったらWorldMapシーンでもコマンド機能が使えるようにしていきます。

ユニティちゃんライセンス

この作品はユニティちゃんライセンス条項の元に提供されています

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