基本操作系のチュートリアルは一通り終わったけど…
自信ないー!まだまだUnrealEngine怖いー!
私もです
EpicJapan社が開催する、UnrealEngineに関する問題を出題し、全て答えるとゲームが出来上がるという企画です
私自身、チュートリアルをこなすよりも、アンリアルクエストにチャレンジした方が、何倍速も技術が身につく感じがしました
実際のゲーム作りに移るのが怖い人は、アンリアルエンジンにチャレンジして、脳味噌に汗をかいてチャレンジしてみてください!
ただし、最後の「まとめ」にも書いた通りで、完全にプログラミング初心者にはかなり難しく感じるでしょう
そういう方に向けたアドバイスを後述しております
解説動画もあって安心
サンプルプロジェクト配布
verはUE4.26.2
今回私がチャレンジしたのは1です。2022年現在は4まで開催されています
こちらの課題はヒストリア社提供の夏休みの宿題のStep2-1番手として紹介されています
ギミック~初級編~
動く床
解説は上記Twitterを参照
当たると死ぬ棘
解説は上記Twitterを参照
ギミック~中級編~
ジップライン
かなり難しいです💦
・PC(ThirdPersonCharactor)にコンポーネント[Scene]追加
※ジップラインに接着するためのもの
・ジップラインとなるBP作成 親クラス:Actor
- 下記、作成するComponent
SplineComponent・BoxCollision(start用end用で2種)
- 作成したコンポーネントは、下記画像のようにビューポートに設置
- ジップラインの質感は下記で設定
コンポーネントSpline(Self)⇒[詳細]SplineMesh
※今回は配布されているプロジェクトデータから拝借しました
(他のプロジェクトデータから読み込む方法はやりかたはこちら)
- Collisionのガイドラインを(分かりやすくするために)太くする方法
Collision選択⇒[詳細]LineThicness
- イベントグラフ
- イベントBeginPlay
- onComponentBeginOverlap(start用Collisionから作成)
- onComponentEndOverlap(end用Collisionから作成)
- 関数
- コンストラクションスクリプト
- マクロ
- checkPlayer
- 変数
- 変数の型:Boolean 名前:IsDisableEndTrigger
- 変数の型:Boolean 名前:RunningZipline
- 変数の型:ThirdPersonCharactor 名前:PlayerRef
※変数の型はPCのBPデータ名 - 変数の型:vector 名前:PlayerZiplinePoint
- 変数の型:staticMesh 名前:SplineMesh
- イベントグラフ
- タイムラインノードの設定(ジップラインのスライドの動き部分)
イベントグラフ⇒ジップラインをすべる- f+(フロートトラック)を追加
- 長さ:1.0 値1.0
- トラック名:Rate
何をしているノードかはっきり分からなかったのだけど…
多分、ジップラインとの接着部分の設定
ごめんなさい・・・
これは完全に何をしているノードかわからないや
- ジップラインのBPをレベルエディタにD&D
- スプラインを編集して自由な形へ
- スプライン選択⇒[詳細]コンポーネントからSplineを選択
- スプラインの長さ:頂点選択⇒[詳細]SELECTS POINTS⇒トランスフォーム値調整
- スプラインの頂点を追加:辺選択⇒右クリック⇒[ここにスプラインポイントを追加]
- スプラインを曲げる:頂点選択⇒スプライトポイント型⇒カーブ選択
スプラインのトランスフォームを変えたい時
スプラインだけではなく、構成しているコンポーネントをすべて選択してトランスフォームを行う
一定時間乗ると落ちる床
・床のBP作成 親クラス:Actor
- StaticMesh(床となるメッシュ)
- BoxCollision(床にコリジョン判定を追加)
- イベントグラフ
- OnComponentBeginOverlap
※BoxCollison選択⇒[詳細]イベントより作成 - callfall(カスタムイベントノードで作成)
- set reshot floor(カスタムイベントノードで作成)
- イベントBeginPlay
- OnComponentBeginOverlap
- 変数
- 変数の型:Vector 名前:defaultLocation
- 変数の型:Rotator 名前:defaultRotation
- 落ちる床の初期位置
- 落ちる床の初期位置を戻すために、[BeginPlay]ノードから再設定
- 床が落ちる
- ~秒後落下を止める⇒コンポーネントの親子関係を再設定⇒床を初期位置に戻す
※床が落ちる~の続き(SetSimulatePhysicsから接続)- [コンポーネントの親子関係を再設定]の[SetSimulatePhysics]のsimulateをオフ
ギミック~上級編~
シーソー
・シーソーのBP作成 親クラス:Actor
- StaticMesh×2:シーソーを支える柱と座る板
- PhysicsConstraint:複数のStaticMeshを接続する物理機能
- コンポーネント内を親子付する
StaticMesh(柱)⇒PhysicsConstraint⇒StaticMesh(板)
- コンポーネントで追加したPhysicsConstraintのトランスフォーム制御([詳細]で設定)
下記のパラメータで意図しない方向に回転したり移動したりしないように制御する- LINEAR LIMITS
- ANGULAR LIMITS
Free/Limited/Lockesのいずれかで選択
ターザンロープ
・ターザンロープのBP作成 親クラス:Actor
- 今回のPCBPは元からある[ThirdPersonCharacter]を使用
- ロープを掴むポイントとなるコンポーネントを追加設定
[SceneComponent]GrabPoint
- Cylinder(ロープをぶら下げる柱)
- Box(コリジョン)
- Scene
- Cable(ロープ)
※Sceneの親子
- Cable(ロープ)の設定
[詳細]ケーブルより設定- Attachstart:接着させる
- EndLocation:長さ
- CableLength:揺れ幅
- PlayRef(変数の型:PCのBP名)
- Default_cable_length(変数の型:Float)
- additional_arounchValue(変数の型:Vector)
- 動作開始⇒スウィングしながら着地
- スウィングの動きを追加⇒PCをスイング中の方向に合わせる
ステージ~初級編~
ゴール
- ゴールの円柱となるBP作成 親クラス:Actor
- レベルクリアを表示するレベル[LevelClear]作成
- LevelClearと表記するWBPを作成
- WBP:コンテンツブラウザ空きスペース右クリック⇒ユーザーインターフェース⇒ウィジェットブループリント
- 下記WBPに乗せる要素の階層とビューポート
- 下記階層は[IsVariable]に✔
を入れる。この後ノードに組み込めるようにするため
Button_0/Button_nextStage
- 下記階層は[IsVariable]に✔
- ノードを組み込む
※右上のスイッチを[グラフ]に切り替える- 先述[IsVariable]に✔を入れたButton_0/Button_nextStageが変数で作られているので、[詳細]イベント⇒[On Clicked]+で、それぞれノードを作成する
- [On Clicked]⇒[Open Level]に繋げる
※Buttun_0:タイトルのレベルへ、Button_nextStage:次のステージのレベルへ、繋げるようにするところだが、今はゴール表示のみにスポットを当てて後述する
- 必要なコンポーネントは下記
- BoxCollision:ゴール判定
- Billboard Compornent:役割分からん。正直今回要らなかったかも?
- SM:ゴールの円柱
- BoxCollisionから作成した[OnComponentBeginOverlap]からスタートさせる
- [OpenLevel(by Name)]に作成したレベル[LevelClear]を入力
制限時間とスコアを入れる
スコア~初・中級編~の内容も含めています
この項目はUE4.26で行いました
UE5には解説動画で使用している時間を操る[DateTime]系のノードが無く、[WildCard]に変わったようですが、情報が少なく、UE4.26で作り直しました
- UI(WBP)の作成
- BP_GameModeの作成
- GameModeを作成するには?
親クラスを選択⇒すべてのクラスから探す
GameModeBaseではない
- GameModeを作成するには?
- スコアをゲットするBPの作成(今回はコイン 親クラス:Actor)
- 必要なコンポーネントは下記
- StaticMesh(コイン)
- Sphere(コリジョン)
- RotatingMovement(自動回転)
- 変数の型:Integer 名前:AddScoreValue
[詳細]デフォルト値⇒AddScoreValueで加算ポイント数入力
※デフォルト値はコンパイル後表示
- スコアの加算のノードは下記
- 下記WBPに乗せる要素の階層とビューポート
- 下記階層は[IsVariable]に✔
- Image_0(パレット:Image)
- HorizontalBox_94(パレット:HorizontalBox)
- TextBlock_Time(パレット:Text)
- TextBlock_Score(パレット:Text)
- グラフのノードは下記
- [UpdateTimeUI]ノード⇒[詳細]インプット⇒+で変数追加⇒変数の型:TimeSpan 名前:TimeValue
- [UpdateScoreUI]ノード⇒[詳細]インプット⇒+で変数追加⇒変数の型:Integer 名前:ScoreValue
- 変数の型:Integer 名前:totalScore
- 変数の型:DateTime 名前:startTime
- 変数の型:UIを作成したWBP名(今回はWBP in Game UQ) 名前:InGameUIRef
- 変数の型:Integer 名前:timeMinutes
- 制限時間を設定
[詳細]デフォルト値⇒TimeMinutes数値入力
※コンパイル後デフォルト値表示
- 制限時間を設定
- 変数の型:Integer 名前:timeSeconds
- レベルエディタに残り時間&スコアのUI表示
- [WBP in Game UQウィジェットを作成]ノード⇒Class[UIを作成したWBP(今回はWBP in Game UQ)]を選択
- 時間制限+経過時間を取得+制限時間&スコアカウントを表示
- [ExecuteConsoleCommand]ノード⇒Command[RestartLevel]入力
- スコア加算
ステージ~中級編~
レベルデザインをする
お好みで!
※私は速度重視したので省きました
ステージ~上級編~
次のステージへ
- UI(WBP)の作成
- タイトル
- ゲームオーバー
- レベルクリア(ステージ01用)
- コンプリート(ステージ02用)
- タイトル
- 空のレベル作成(UI表示用)
- タイトル
- ステージ01(今まで作ったステージを設定)
- ゲームオーバー
- レベルクリア(ステージ01用)
- ステージ02
- コンプリート(ステージ02用)
- タイトル
- タイトル
- 下記階層は[IsVariable]に✔
- Button_0(パレット:Button)
- グラフのノードは下記
- [OnClicked(Button_0)]ノード⇒[変数]Buttun_0選択⇒イベント[OnClicked]ビュー
- LevelName:[Play]を押したあと進むレベル入力(ここではステージ01に該当するレベル名)
- ゲームオーバー
- 下記階層は[IsVariable]に✔
- Button_0(パレット:Button)
- グラフのノードは下記
- [OnClicked(Button_0)]ノード⇒[変数]Buttun_0選択⇒イベント[OnClicked]ビュー
- LevelName:[Back ToTitle]を押したあと進むレベル入力(ここではタイトルに該当するレベル名)
- レベルクリア(ステージ01用)
- 下記階層は[IsVariable]に✔
- Button_0(パレット:Button)
- Button_nextStage(パレット:Button)
- グラフのノードは下記
- [OnClicked(Button_0)]ノード⇒[変数]Buttun_0・Button_nextStage選択⇒イベント[OnClicked]ビュー
- LevelName:[NextStage][Back ToTitle]を押したあと進むレベル入力([NextStage]⇒ステージ02のレベル [Back ToTitle]⇒タイトルのレベル)
- タイトル
- 下記階層は[IsVariable]に✔
- Button_0(パレット:Button)
- グラフのノードは下記
- [OnClicked(Button_0)]ノード⇒[変数]Buttun_0選択⇒イベント[OnClicked]ビュー
- LevelName:[Play]を押したあと進むレベル入力(ここではタイトルに該当するレベル名)
- 空のレベルを作成(新規レベル⇒空のレベル)
- [レベルブループリントを開く]で編集
- グラフのノードは下記
- ここではアンリアルクエスト01で作ってきたレベルでブループリント編集する
- グラフのノードは下記
※[OnActorBeginOverlap]が二つあるのはゴールのコリジョンが2つあるため- [OpenLevel(byName)]ノード⇒[LevelName]にレベルクリアのレベル名を入力
- タイトルと同じように空のレベルを作成
- グラフのノードは下記
- [GAMEOVER WBPウィジェットを作成]のClassにゲームオーバーのWBP名を入力
- タイトルと同じように空のレベルを作成
- グラフのノードは下記
- [LevelClear WBPウィジェットを作成]のClassにレベルクリアのWBP名を入力
- 適当に追加でステージを作成してレベルデザインをする
- グラフのノードは下記
※[OnActorBeginOverlap]が二つあるのはゴールのコリジョンが2つあるため- [OpenLevel(byName)]ノード⇒[LevelName]にコンプリートのレベル名を入力
- タイトルと同じように空のレベルを作成
- グラフのノードは下記
- [Compreate WBPウィジェットを作成]のClassにコンプリートのWBP名を入力
ゴール演出
ステージ~初級編~ゴールを作成から少し手を加える形で進めました
出来上がりのムービーは後述のランキングとまとめたので、そちらをご覧ください
訳あって、この項目はUE4.26で作成しました
- ゴール判定の時に、PCのアニメーションをスーパースローにする
- スーパースローが停止すると「ゴール!」の文字が表示される
- BP_GameMode
- 「ステージ~初級編~ゴールを作成」で作成したBP_Goal
- 「ゴール!」文字用のWBP
- 下記ノードを作成する
クリアすると、マウス入力が無効になるという内容
- ゴール!UI
- 下記IsVariable✔
Overlay
- 下記IsVariable✔
- ゴール!UIのグラフ
- [PrintString]ノードに続いて作成
- [PrintString]ノード⇒スーパースロー処理⇒スーパースロー後「ゴール!」呼び出し
エネミー~初級・中級・上級編~
当たったら死ぬ棘~死んだらリスタート~
初・中・上ひとまとめにします
当たったら死ぬ棘~死んだらリスタート~⇒追いかけてくる敵⇒攻撃してくる敵
- ここでは敵が矢印を発射し、矢印がPCに当たるとやられる、という流れを作成
- 矢印を発射する敵 親クラス:Actor
- 発射される矢印 親クラス:Charactor
- PCのBP
- 追加するコンポーネントは下記
- Sphere(赤い球の部分)
- CoxCollision(矢印がPCをめがけて発射開始する範囲)
- allow(ガイド ここでは、矢印発射方向 あってもなくても)
- SkeltalMesh
- StaticMesh(SkeltalMeshに親子付する形で追加 赤い胴部分)
- allow(ガイド あってもなくても)
- CharactorMovement
⇒[詳細]キャラクタームーブメント:歩行でスピード調節
下記設定しないと、PCを追いかけない
- BP親クラス[Charactor]にする
- capsuleComponentの中でStaticMesh全体を覆う事
親クラスを間違えても、[クラス設定]クラスオプションから変更できる
- だが推奨はしていない
- できるだけ初回で決める事
- 作成するのは下記変数
- 変数の型:float 名前:SightLength デフォルト値:1000
※不要かもしれない - 変数の型:float 名前:DamageValue デフォルト値:1000
- 変数の型:float 名前:HP デフォルト値:50
- 変数の型:boolean 名前:IsReload デフォルト値:1000
- 変数の型:float 名前:SightLength デフォルト値:1000
- イベントグラフでは敵の攻撃のノードは無く、敵がPCから攻撃受けて消えるまでのノードを作成します
- PCから攻撃を受ける
- 敵が倒れて消える
- 倒れる間、Collison反応不可
- 風に倒されるように倒れる
- 時間をおいて敵が消える
- 変数の型:Float コンテナ:Float デフォルト値:HP100
- ダメージを受ける
[On Component Hit]はコンポーネント[CapsuleComponent]([詳細]イベント)から生成する
- HPが減る
- おまおまけ:キャラクターをラグドール化して、死亡演出
- GameOver
スコア~上級編~
ランキング
- これまでに作った下記データを用意
- BPGameMode
- BP_Goal(ゴール)
- BP_Item(コイン)
- 下記WBPを新規作成
- ランキング全体のUI
- 項目のUI
- リザルト画面のUI
- ゴール通知
- 構造体
右クリック⇒ブループリントから選択 - BP_SaveData
右クリック⇒ブループリントクラス⇒親クラス⇒すべてのクラス⇒BP_SaveGame選択
- これまでに作ったBP_GameMode
- [新規変数]から下記構造体を作成
- 変数の型:Integer 名前:Score 編集可能✔
- 変数の型:TimeSpan名前:GoalTime 編集可能✔
- 設定は[マイブループリント]で変数を作成するのみ
変数の型:StructRankingData コンテナ:配列 名前:RankingData インスタンス編集可能✔
- 下記WBPに乗せる要素の階層とビューポート
- Canvas Panel
- Vertical Box
- Size Box
- Vertical Box
- Text(RANKING)
- Spacer
- Horizontal Box
- Spacer
- Size Box
- Text(ランク)
- Spacer
- Size Box
- Text(スコア)
- Spacer
- Size Box
- Text(タイム)
- Spacer
- ScaleBox(ランキング用)
- VerticalBox(ランキング表示)
※Is Variableに✔
- VerticalBox(ランキング表示)
- Size Box
- Vertical Box
- Text(YoureScore)
- Scale Box
- VerticalBox(今回のゲームスコアを表示)
※Is Variableに✔
- VerticalBox(今回のゲームスコアを表示)
- Scale Box
- Text(YoureScore)
- Vertical Box
- BackToStageButton
※Is Variableに✔- Text(ステージに戻る)
- Vertical Box
- Canvas Panel
- 作成する変数
- 変数の型:StructRanking 変数のタイプ:単一の変数 名前:CurrentRankingData
- 変数の型:StructRanking 変数のタイプ:配列 名前:Ranking Data All
- 変数の型:boolean 変数のタイプ:単一の変数 名前:IsHighScore
- 変数の型:boolean 変数のタイプ:単一の変数 名前:IsInsert
- 前提で先述の構造体[StructRanking]を作る必要有(変数が検索でヒットしない・作れない)
- ゲームリスタート
- [OnClicked]ノードは[BackToStageButton]から生成したもの(選択&[詳細]イベントより)
- 今回の自分のスコアデータを「YourScore」に反映
- 既存のランキングデータを上から順にチェックして行き、獲得スコアが既存スコアより高い箇所に今回の獲得スコアを挿入
- 最低スコアをランキングの最後に追加
- UIのランキング一覧にスコアを追加
- セーブデータ呼び出し
- 下記WBPに乗せる要素の階層とビューポート
- Overlay
- Horizontal Box
- Spacer
- Size Box
- Text(ランク)※IsVariable✔
- Spacer
- Size Box
- Text(スコア)※IsVariable✔
- Spacer
- Size Box
- Text(タイム)※IsVariable✔
- Text(ハイスコア)※IsVariable✔
- Horizontal Box
- Overlay
- 前提で先述の構造体[StructRanking]を作る必要有(変数が検索でヒットしない・作れない)
- ゲームリスタート
- [OnClicked]ノードは[BackToStageButton]から生成したもの(選択&[詳細]イベントより)
- 今回の自分のスコアデータを「YourScore」に反映
- 既存のランキングデータを上から順にチェックして行き、獲得スコアが既存スコアより高い箇所に今回の獲得スコアを挿入
- 最低スコアをランキングの最後に追加
- UIのランキング一覧にスコアを追加
- セーブデータ呼び出し
- 下記WBPに乗せる要素の階層とビューポート
- CanvasPanel
- Background Blur(IsVariable✔)
- image(IsVariable✔)
- Background Blur(IsVariable✔)
- ScaleBox(Goal)(IsVariable✔)
- Scale Box(Ranking)(IsVariable✔)
- CanvasPanel
- 変数の型:WBP Goal 変数名:GoalWidget
※[WBP_Gaol]を作成していれば選択可能 - 変数の型:StructRanking コンテナ:単一の変数 変数名:NewRankingData
- 変数の型:StructRanking コンテナ:配列 変数名:RankingDate
- ゴール通知
- WBP_Goalのアニメーション再生終了を通知
- ゴールアニメーション再生終了後にゴール用UIを外してランキング表示
- 下記WBPに乗せる要素の階層とビューポート
- Overlay(IsVariable✔)
- Image
- Text(ゴール!)
- Overlay(IsVariable✔)
- 変数の型:WidgetAnimation 変数名:GoalAnima
※[詳細]カテゴリ⇒Animation選択
- イベントディスパッチャー+
今回の変数名:FisnishedGoalAnim
- ゴール!表示&ゴール演出後にランキングを表示する
- 変数の型:Integer コンテナ:単一
- totalScore
- timeMinutes デフォルト値:3
※[制限時間とスコアを入れる]で作成済 - timeSeconds
※[制限時間とスコアを入れる]で作成済 - User Index
- 変数の型:DateTime コンテナ:単一
- startTime
- CurentTime
- 変数の型:WBPinGame_UQ コンテナ:デフォルト 名前:InGameUIRef
※[制限時間とスコアを入れる]で作成済 - 変数の型:TimeSpan構造体 コンテナ:単一 名前:ClearTime
- 変数の型:StructRanking コンテナ:配列 名前:RankingData
変数の型:StructRankingは先述作成した構造体 - 変数の型:BP_SaveData_UQ コンテナ:配列 名前:SaveGameRef_UQ
変数の型:BP_SaveData_UQ は先述作成したSaveGame
- 変数の型:String コンテナ:単一 名前:Slot Name デフォルト値:SlotName[RankingData]入力
- LoadData
- [ターゲットRankingData]の作成の前提
- Cast元[CastToBP_SaveModeData_UQ]内に変数[RankingData]が作られている事
- セット出力ピンから[Get 変数]で検索する事
- [ターゲットRankingData]の作成の前提
- SetupSaveData
- [CreateSaveGameObject]ノード⇒[SaveGameClass]先述作成したSaveGame名を選択
- GetTimeLeft
※[制限時間とスコアを入れる]で作成済
- Save Data
- 誤って[SaveData]⇒[CreateSaveGameObject]と接続し、偶数の段にしかスコア表示されない事象発生
今までのセーブデータが無視されてしまうとのこと - [ターゲット 変数名]の出し方(復習)
- 読み込み元[SBP_GameMode_UQ]に変数[RankingData]が作成されている事
- 出力ピンから[Get変数]でヒットされる
- 誤って[SaveData]⇒[CreateSaveGameObject]と接続し、偶数の段にしかスコア表示されない事象発生
- ゴール到着時のクリア
- リザルト画面にスコア表示をする
- [ゲームのクリアタイムを保存]の[RemoveAllWidgets]ノード⇒作成した関数[LoadData]へ接続
ハイスコア表示
実は…解決していません
詳細をUE日本語フォーラムへ投稿したので、どなたかご教示お願いいたします!
バトル
エネミー編で、矢印の攻撃を発射する敵を作りましたが、PCにも矢印を発射させるように設定し、撃ち合うバトルを作りました
追いかけてくるthirdPersonも作りました
- PC
- トゲ (PCと赤い敵から発する)
- 敵キャラ(thirdPerson&赤い敵)
- エネミー編(上中下)を追加編集。追加するのは下記
敵を攻撃するPC・PCから発射されるトゲ・PCの攻撃を受けて死ぬ赤い敵とthirdPerson
- 親クラス[Actor]
- 矢印の飛ぶスピード[ProjectileMovement]を追加
[詳細]プロジェクタイルでスピード調整
- 変数の型Float デフォルト値:1000
※敵にダメージを与える数値
※デフォルト値は一回コンパイルしないと表示されない
- 下記ノード
[Component Has Tag]ノードのTagは、敵キャラにTag設定しないと影響しない
※敵キャラのコリジョン選択⇒[詳細]タグ⇒[1配列エレメント列]にtag入力
- Allow
- マウス操作で攻撃を呼び出す
- 弾の発射処理
- [SpawnActor BP Enemy AllowPC]
- Class:敵BPを選択
- CollisionHandlingOverride:AlwaysSpawn,IgnoreCollosions選択
- [SpawnActor BP Enemy AllowPC]
- 変数の型:Float コンテナ:Float デフォルト値:HP50
- PCからダメージを受ける
- HPが減る
- おまけ:キャラクターをラグドール化して、死亡演出
まとめ:ブループリント完全初心者には難しいです!
決して「この夏、Unreal Engine 5を覚えよう!」に落ち度は無く、単純にプログラミングを一切やってこなかった人にとって、Step1をこなしただけではStep2-1のこの課題はとても難しいです💦
UE日本語フォーラムに結構頼る頻度が多くなったことも反省点です
とはいえ、いつまでもチュートリアルに頼ってはバッター打席に立てません
なので、本課題で一回溺れる・恥をかく経験が出来たのは、完全未経験の私にとっては必要な経験です
アンリアルクエスト1でBP組みが全く歯が立たなかった人は、次の課題「アンリアルクエスト3」の代わりに、初心者向けのブループリントの書籍をこなす事をお勧めします
おそらく理解度がまるで違うと思います
中にはゲームではなく、画作りメインで習得したい方もいますよね
そういう方はアンリアルクエスト01でストップして、「この夏」Step4で紹介しているマテリアルデザイン入門をこなして終了で十分に感じています
コメント