この記事は Google 翻訳による英語翻訳の結果であり、これに DrGraph がいくつかの修正を加えたものです。英語のオリジナルページ:
初めての 2D ゲーム — Godot Engine (安定版) ドキュメント (英語)
初めての 2Dゲーム¶
このステップバイステップのチュートリアル シリーズでは、Godot を使用して最初の完全な 2D ゲームを作成します。このシリーズが終わるまでに、下の画像に示すような、シンプルだが完全なゲームが完成するでしょう。
Godot エディタの仕組み、プロジェクトの構築、2D ゲームの構築方法を学びます。
注: このプロジェクトは Godot エンジンの入門です。すでにある程度のプログラミング経験があることを前提としています。プログラミングがまったく初めての場合は、ここから始める必要があります:スクリプト言語。
ゲームの名前は「Dodge the Creeps!」です。キャラクターはできるだけ長く移動して敵を避けなければなりません。
次のことを学びます:
-
Godot エディタを使用して完全な 2D ゲームを作成します。
-
簡単なゲームプロジェクトを構築します。
-
プレイヤーキャラクターを移動させ、スプライトを変更します。
-
ランダムな敵を生成します。
-
スコアを計算します。
もっと。
同様のゲームを 3D で作成する別のシリーズもあります。ただし、これから始めることをお勧めします。
なぜ 2D から始めるのでしょうか?
ゲーム開発が初めて、または Godot を初めて使用する場合は、2D ゲームから始めることをお勧めします。これにより、より複雑になることが多い 3D ゲームに取り組む前に、両方に慣れておくことができます。
このプロジェクトの完全版は次の場所にあります。
前提条件¶ _
このステップバイステップのチュートリアルは、完全な スタート ガイドに従った初心者向けです。
経験豊富なプログラマーの場合は、完全なデモのソース コードをここで見つけることができます: Godot Demo Project。
コードに直接ジャンプできるように、ダウンロードする必要があるゲーム アセットをいくつか用意しました。
以下のリンクをクリックしてダウンロードできます。
dodge_the_creeps_2d_assets.zip。
設定項目¶
この短い最初のパートでは、プロジェクトを設定して整理します。
Godot を起動し、新しいプロジェクトを作成します。
新しいプロジェクトを作成するときは、有効なプロジェクト パスを選択するだけです。他のデフォルト設定を保持することができます。
GDスクリプト
dodge_the_creeps_2d_assets.zipをダウンロードします。この zip ファイルには、ゲームの作成に使用する画像とサウンドが含まれています。アーカイブを抽出し、art/
ディレクトリfonts/
をプロジェクト ディレクトリに移動します。
プロジェクト フォルダーは次のようになります。
このゲームはポートレート モード用に設計されているため、ゲーム ウィンドウのサイズを変更する必要があります。「プロジェクト」→「プロジェクト設定」をクリックしてプロジェクト設定ウィンドウを開き、左側のサイドバーにある「表示」→「ウィンドウ」タブを開きます。そこで、ビューポートの幅を に480
、ビューポートの高さを に設定します720
。
また、[ストレッチ]オプションで、[モード] を[アスペクト比]に設定しますcanvas_items
。これにより、さまざまなサイズの画面にわたってゲームが一貫して拡大縮小されることが保証されます。keep
プロジェクトの整理¶
このプロジェクトでは、 、 の 3 つの別々のシーンを作成しPlayer
、 [最終的にMob
は HUD
] それらをゲームMain
シーンに結合します。
大規模なプロジェクトでは、さまざまなシーンとそのスクリプトを保持するフォルダーを作成すると便利ですが、この比較的小規模なゲームの場合は、 によってシーンとスクリプトをプロジェクトのルート フォルダーに保存できます。プロジェクト フォルダーを確認できますres://
。左下のファイル システム ドック内:
プロジェクトが完成したら、次のレッスンでプレーヤー シーンをデザインできます。
プレーヤーシーンの作成¶
プロジェクトのセットアップが完了したら、プレイヤーが制御するキャラクターの作業を開始できます。
最初のシーンではPlayer
オブジェクトを定義します。個別のプレーヤー シーンを作成する利点の 1 つは、ゲームの残りの部分を作成する前であっても、単独でテストできることです。
ノード構造¶
まず、プレーヤー オブジェクトのルート ノードを選択する必要があります。一般的なルールとして、シーンのルート ノードは、オブジェクトの目的の機能、つまりオブジェクトが何であるかを反映する必要があります。「その他のノード」ボタンをクリックして、Area2D ノードをシーンに追加します。
Godot は、シーン ツリーのノードの横に警告アイコンを表示します。今のところは無視して構いません。それについては後で説明します。
Area2D
プレイヤーに重なったり当たったりするオブジェクトを検出できます。ノード名を に変更しますPlayer
。シーンのルート ノードを設定したので、ノードを追加して機能をさらに高めることができます。
Player
ノードに子を追加する前に、クリックして誤って移動したりサイズ変更したりしないようにする必要があります。ノードを選択し、鍵の右側にあるアイコンをクリックします。そのツールチップは「選択したノードの子ノードを選択不可にする」です。
シーンを保存します。[シーン] -> [保存] をクリックするか、Windows/Linux では Ctrl + S を押すか、macOS では Cmd + S を押します。
注: このプロジェクトでは、Godot の命名規則に従います。
-
GDScript : クラス (ノード) には PascalCase を、変数と関数には Snake_case を、定数には ALL_CAPS を使用します (「 GDScript スタイル ガイド」を参照)。
-
C# : クラス、エクスポートされた変数、およびメソッドには PascalCase を使用し、プライベート フィールドには _camelCase を、ローカル変数とパラメーターには CamelCase を使用します (「C# スタイル ガイド」を参照)。信号を接続するときは、メソッド名を正確に入力するように注意してください。
スプライトアニメーション¶
Player
ノードをクリックし、子ノードAnimatedSprite2Dを追加 (Ctrl + A) します。【AnimatedSprite2D】はプレイヤーの外観とアニメーションを処理します。ノードの横に警告記号があることに注意してください。【AnimatedSprite2D
】表示できるアニメーションのリストであるSpriteFramesリソースが必要です。Animation
作成するには、インスペクターのタブでプロパティ [ ] を見つけSprite Frames
、[空] -> 新しい SpriteFrames をクリックします。もう一度クリックして [SpriteFrames] パネル [下部] を開きます。
左側にはアニメーションのリストがあります。「デフォルト」をクリックし、「walk」という名前に変更します。次に、「アニメーションの追加」ボタンをクリックして、「Up」という 2 番目のアニメーションを作成します。[ファイルシステム] タブでプレーヤー イメージを見つけます。これらは、前に解凍したart
フォルダーにあります。各アニメーションの 2 つのイメージ ( playerGrey_up[1/2]
とという名前playerGrey_walk[1/2]
) を、対応するアニメーション パネルの「アニメーション フレーム」側にドラッグします。
プレーヤーの画像はゲーム ウィンドウに対して少し大きすぎるため、縮小する必要があります。ノードをクリックしAnimatedSprite2D
、Scale
プロパティを に設定します(0.5, 0.5)
。Node2D
これは、インスペクターの [Scale プロパティは Node2D クラスで宣言および定義されています] という見出しの下にあります。
最後に、CollisionShape2DPlayer
を の子として追加します。これにより、プレーヤーの「コリジョン ボックス」、つまりコリジョン エリアの境界が決まります。このキャラクターの場合、ノードを使用して衝突領域を整形するのが最善であるためCapsuleShape2D
、インスペクターの Shape の横にある [empty] -> New CapsuleShape2D をクリックします。2 つのサイズ ハンドルを使用して、スプライトを覆うようにシェイプのサイズを変更します。
完了すると、Player
シーンは次のようになります。
これらの変更後は、必ずシーンを再度保存してください。
次のセクションでは、プレーヤー ノードにスクリプトを追加して、プレーヤー ノードを移動およびアニメーション化します。次に、プレイヤーが何かにぶつかりそうになったことを知るために衝突検出を設定します。
プレーヤーのエンコーディング¶
このレッスンでは、プレーヤーの動き、アニメーション コードを追加し、衝突検出を設定します。
これを行うには、組み込みノードからは利用できない機能を追加する必要があるため、スクリプトを追加します。ノードをクリックしPlayer
、「スクリプトをアタッチ」ボタンをクリックします。
スクリプト設定ウィンドウでは、デフォルト設定を維持できます。「作成」をクリックするだけです。
注: C# スクリプトまたは別の言語を作成している場合は、「作成」をクリックする前に「言語」ドロップダウン メニューから言語を選択します。
注: GDScript を初めて使用する場合は、 続行する前に「スクリプト言語」を読んでください。
まず、このオブジェクトに必要なメンバー変数を宣言します。
GDスクリプト
extends Area2D @export varspeed = 400 # プレーヤーが移動する速度 (ピクセル/秒)。var screen_size # ゲームウィンドウのサイズ。
最初の変数speed
にキーワードを使用すると、export,其作用是
後からインスペクタでその値を設定できます。これは、ノードの組み込みプロパティのように調整できるようにしたい値に便利です。そのPlayer
ノードをクリックすると、インスペクターの「スクリプト変数」セクションにプロパティが表示されることがわかります。ここで値を変更すると、スクリプトに記述された値が上書きされることに注意してください。
注: C# を使用している場合は、新しくエクスポートされた変数または信号を確認したい場合は常にプロジェクト アセンブリを (再) ビルドする必要があります。このビルドは、エディターの右上隅にある「ビルド」ボタンをクリックすることで手動でトリガーできます。
手動ビルドは、MSBuild パネルからトリガーすることもできます。エディター ウィンドウの下部にある「MSBuild」という単語をクリックして MSBuild パネルを表示し、[ビルド] ボタンをクリックします。
_ready()
この関数は、ノードがシーン ツリーに入ったときに呼び出されます。これは、ゲーム ウィンドウのサイズを見つける [取得] のに適したタイミングです。
GDスクリプト
func _ready(): screen_size = get_viewport_rect().size
この_process()
関数を使用して、プレーヤーが何を行うかを定義できるようになりました。これはフレームごとに呼び出されるため_process()
、頻繁に変更されることが予想されるゲームの要素を更新するために使用します。プレーヤーの場合は、次のことを行う必要があります。
-
入力を確認してください。
-
指定された方向に移動します。
-
適切なアニメーションを再生します。
まず、入力をチェックする必要があります - プレーヤーはキーを押しましたか? このゲームでは、4 つの方向入力をチェックします。入力アクションは、「入力マッピング」のプロジェクト設定で定義されます。ここでカスタム イベントを定義し、それらに別のキー、マウス イベント、またはその他の入力を割り当てることができます。このゲームでは、矢印キーを 4 方向にマッピングします。
[プロジェクト] -> [プロジェクト設定]をクリックして[プロジェクト設定] ウィンドウを開き、上部の [入力マップ] タブをクリックします。上部のバーに「move_right」と入力し、「追加」ボタンをクリックしてmove_right
アクション。
この操作にはキーを割り当てる必要があります。右側の「+」アイコンをクリックして、イベント マネージャー ウィンドウを開きます。
「入力をリッスン...」フィールドが自動的に選択されます。キーボードの「右」キーを押すと、メニューは次のように表示されます。
[OK]ボタンを選択します。「右」キーがmove_right
アクションに関連付けられました。
これらの手順を繰り返して、さらに 3 つのマップを追加します。
-
move_left
左矢印キーにマッピングされます。 -
move_up
上矢印キーにマッピングされます。 -
move_down
下矢印キーにマッピングされます。
入力マップ タブは次のようになります。
「閉じる」ボタンをクリックしてプロジェクトの設定を閉じます。
注: 各入力アクションには 1 つのキーのみをマップしますが、複数のキー、ジョイスティック ボタン、またはマウス ボタンを同じ入力アクションにマップすることもできます。
[DrGraph]: Godot はここで少し設計しすぎていますが、キーと値の定数をパラメーターとして直接呼び出した方が高速で、コードも読みやすいと思います。
Input.is_action_pressed()
キーが押された場合は検出、キーが押された場合は戻るtrue
、押されていない場合は戻るを使用できますfalse
。
GDスクリプト
func _process(delta): var Velocity = Vector2.ZERO # プレイヤーの移動ベクトル。 if Input.is_action_pressed("move_right"): 速度.x += 1 if Input.is_action_pressed("move_left"): 速度.x -= 1 if Input.is_action_pressed("move_down"): 速度.y += 1 if入力.is_action_pressed("move_up"): 速度.y -= 1速度.length() > 0の場合: 速度 = 速度.normalized() * 速度 $AnimatedSprite2D.play() else : $AnimatedSprite2D.stop()
まず、デフォルトでプレーヤーが移動してはならないことを示すvelocity
ように設定します。(0, 0),
次に、各入力をチェックし、velocity
そこから全体の方向を取得します。たとえば、right
と を同時に押した場合down
、結果のvelocity
ベクトルは になります(1, 1)
この場合、水平方向と垂直方向の動きを追加しているため、プレイヤーは水平方向に移動するだけよりも速く斜め方向に移動します。
velocity
速度 ( )を正規化する、つまり長さを に設定し、目的の速度で乗算すると、このような事態1
が発生するのを防ぐことができます。これは、斜め方向に高速で移動する必要がなくなることを意味します。
注:これまでにベクトル演算を使用したことがない場合、または再確認が必要な場合は、「 Vector math」で Godot でのベクトルの使用方法の説明を確認できます。[ただし、心配しないでください。ベクトル数学の知識は、このチュートリアルの残りの部分には必要ありません。
また、プレイヤーが動いているかどうかもチェックして、AnimatedSprite2Dplay()
または stop()
注:$
「はい」get_node()
の省略形。したがって、上記のコードでは $AnimatedSprite2D.play()
、 get_node("AnimatedSprite2D").play()【是等价的】
.
GDScript では、$
現在のノードへの相対パスにあるノード、null
またはノードが見つからない場合はノードを返します。AnimatedSprite2D は現在のノードの子であるため、 を使用できます $AnimatedSprite2D
。
移動方向が決まったので、プレーヤーの位置を更新できます。clamp()
画面外に消えるのを防ぐためにも使用できます。値をクランプするとは、値を特定の範囲に制限することを意味します。関数の最後に次の行を追加します_process
(else の下でインデントされていないことを確認してください)。
GDスクリプト:
位置 += 速度 * デルタ Position.x = クランプ(position.x, 0, screen_size.x) Position.y = クランプ(position.y, 0, screen_size.y)
注: _process() 関数のデルタ パラメータは、フレーム長、つまり前のフレームが完了するまでにかかった時間を指します。この値を使用すると、フレーム レートが変わってもモーションの一貫性が保たれます。
[シーンの再生] (macOS では F6、Cmd+R) をクリックし、プレーヤーを画面上の全方向に移動できることを確認します。
警告: [デバッガー] パネルにエラー メッセージが表示された場合は、AnimatedSprite2D ノードの名前のスペルを間違えている可能性があります。ノード名は大文字と小文字が区別され、シーン ツリーに表示されるものと一致する必要があります。Attempt to call function 'play' in base 'null instance' on a null instance]
$NodeName
アニメーションの選択¶
プレーヤーが移動できるようになったので、アニメーションの方向に応じて AnimatedSprite2D が再生しているアニメーションを変更する必要があります。プレイヤーが右に歩いていることを示す「歩行」アニメーションがあります。flip_h
このアニメーションは、左シフト プロパティを使用して水平方向に反転する必要があります。また、「上」アニメーションもあり、flip_v
下に移動するには垂直方向に反転する必要があります。このコードを関数の最後に追加しましょう_process()
。
GDスクリプト
if Velocity.x != 0: $AnimatedSprite2D.animation = "walk" $AnimatedSprite2D.flip_v = false # ブール代入については、以下の注を参照してください。 $AnimatedSprite2D.flip_h = 速度.x < 0 elif速度.y != 0: $AnimatedSprite2D.animation = "アップ" $AnimatedSprite2D.flip_v = 速度.y > 0
注: 上記のコード内のブール代入は、プログラマが一般的に使用する省略表現です。比較テスト (ブール値) を実行し、ブール値の代入も行っているため、両方を同時に行うことができます。このコードを上記の 1 行のブール代入と比較して考えてみましょう。
GDスクリプト
速度.x < 0の場合: $AnimatedSprite2D.flip_h = trueそれ以外の場合: $AnimatedSprite2D.flip_h = false
シーンを再度再生し、アニメーションが各方向で正しいことを確認します。
注: ここでよくある間違いは、間違ったアニメーション名を入力することです。SpriteFrames パネル内のアニメーションの名前は、コードに入力した名前と一致する必要があります。アニメーションに という名前を付ける場合は"Walk"
、コード内で大文字の「W」も使用する必要があります。
動作が確実に機能していることを確認したら、次の行を に追加して _ready()
、ゲーム開始時にプレーヤーが非表示になるようにします。
GDスクリプト
隠れる()
衝突の準備¶
Player
敵の攻撃を受けたことを検知したいのですが、まだ敵を作っていません。それは問題ありません。Godot のシグナル 機能を使用して動作させるつもりです。
スクリプトの先頭に以下を追加します。GDScript を使用している場合は に、extends Area2D之后
C# を使用している場合は に追加してください。public partial class Player : Area2D之后
GDスクリプト
信号ヒット
これは、プレイヤーが敵と衝突したときに発する(送信する)「ヒット」と呼ばれるカスタム信号を定義します。これを衝突の検出に使用しますArea2D
。ノードを選択しPlayer
、「インスペクター」タブの隣にある「ノード」タブをクリックすると、プレーヤーが発信できるシグナルのリストが表示されます。
カスタムの「ヒット」信号も存在することに注意してください。敵はRigidBody2D
ノードとなるため[各キャラクターの特性を体系的に考慮したプログラム設計]を行っておりますので、[ body_entered(body: Node2D)
Area2DとRigidBody2Dが衝突した際に送られる信号を処理する]必要があります。この信号は、身体がプレーヤーに触れたときに発せられます。「接続...」をクリックすると、「信号を接続」ウィンドウが表示されます。これらの設定を変更する必要はないので、もう一度 [接続] をクリックします。Godot はプレーヤー スクリプト内に関数を自動的に作成します。
緑色のアイコンは、信号がこの機能に接続されていることを示していることに注意してください。このコードを関数に追加します。
GD スクリプト C# C++
func _on_body_entered(body): hide() # Player disappears after being hit. hit.emit() # Must be deferred as we can't change physics properties on a physics callback. $CollisionShape2D.set_deferred("disabled", true)
每次敌人击中玩家时,都会发出信号。我们需要禁用玩家的碰撞,这样我们就不会hit
多次触发信号。
注:如果禁用区域的碰撞形状发生在引擎的碰撞处理过程中,可能会导致错误。使用set_deferred()
告诉 Godot 等待disable形状,直到这样做是安全的。
【DrGraph】:因为Godot Engine的物理引擎使用了多线程来提高效率,因此发生碰撞时(调用_on_body_entered函数),CollisionShape2D的disabled属性会被线程锁定,无法通过set_disabled来修改,这时需要使用set_deferred()
来延迟调用【相当于加到队列中,可以修改的时候再处理】
最后一部分是添加一个函数,我们可以在开始新游戏时调用该函数来重置玩家。
GDScript
func start(pos): position = pos show() $CollisionShape2D.disabled = false
随着玩家的工作,我们将在下一课中处理敌人。
制造敌人¶
现在是时候让我们的玩家必须躲避敌人了。它们的行为不会很复杂:生物会在屏幕边缘随机生成,选择随机方向,并沿直线移动。
我们将创建一个Mob
场景,然后我们可以实例化以在游戏中创建任意数量的独立生物。
节点设置¶
单击顶部菜单中的 Scene -> New Scene 并添加以下节点:
不要忘记设置子节点,使他们无法被选中,就像您在 Player 场景中所做的那样。
在RigidBody2D属性中,设置Gravity Scale
为0,这样生物就不会向下掉落。此外,在 CollisionObject2D部分下,取消选中属性Mask中的1。这将确保生物不会相互碰撞。
像为播放器所做的那样设置AnimatedSprite2D 。这次,我们有 3 个动画:fly
、swim
和walk
。art 文件夹中的每个动画都有两个图像。
必须为每个单独的动画设置Animation Speed
属性。将所有 3 个动画的属性值均调整为。3
您可以使用输入字段Animation Speed
右侧的“播放动画”按钮来预览动画。
我们将随机选择其中一种动画,这样生物就会有一些变化。
与玩家图像一样,这些生物图像也需要按比例缩小。将 AnimatedSprite2D
的Scale
属性设置为(0.75, 0.75)
类似于Player
场景,MOD场景也需要为碰撞添加一个CapsuleShape2D。
要将形状与图像对齐,您需要将Rotation
属性设置为Degrees
90
(在检查器的“变换(Transform)”下)。
保存场景。
敌人脚本¶
像这样添加一个脚本Mob
:
GDScript
extends RigidBody2D
现在让我们看看脚本的其余部分。在_ready()
我们播放动画并随机选择三种动画类型之一:
GD脚本C#C++
func _ready(): var mob_types = $AnimatedSprite2D.sprite_frames.get_animation_names() $AnimatedSprite2D.play(mob_types[randi() % mob_types.size()])
首先,我们从 AnimatedSprite2D 的属性中获取动画名称列表frames
。这将返回一个包含所有三个动画名称的数组:["walk", "swim", "fly"]
然后我们需要【在0~2之间】选择一个随机数,并从列表中选择这些名称之一(数组索引从0
开始)。randi() % n
选择一个介于0和n-1
之间的随机整数。
最后一块是让小怪在离开屏幕时删除自己。将VisibleOnScreenNotifier2D
节点的screen_exited()
信号连接到Mob
并添加以下代码:
GDScript
func _on_visible_on_screen_notifier_2d_screen_exited(): queue_free()
这样就完成了Mob场景。
准备好玩家和敌人后,在下一部分中,我们将把他们放在一个新场景中。我们会让敌人在游戏板周围随机生成并向前移动,将我们的项目变成一个可玩的游戏。
主要游戏场景¶
现在是时候将我们所做的一切整合到一个可玩的游戏场景中了。
创建一个新场景并添加一个名为Main
的节点【Node】。(我们使用 Node 而不是 Node2D 的原因是因为这个节点将成为处理游戏逻辑的容器。它本身不需要 2D 功能。)
单击实例按钮(由链接图标表示)并选择您保存的 player.tscn
.
现在,将以下节点添加为 的子节点Main
,并如图所示命名它们(值以秒为单位):
-
计时器(名称
MobTimer
)- 控制暴徒产生的频率 -
计时器(名称
ScoreTimer
) - 每秒递增分数 -
计时器(名称
StartTimer
)- 在开始前延迟 -
Marker2D (名称
StartPosition
) - 指示玩家的起始位置
设置每个Timer
节点的Wait Time
属性如下:
-
MobTimer
:0.5
-
ScoreTimer
:1
-
StartTimer
:2
此外,将StartTimer
的One Shot
属性设置为“On”并将StartPosition
节点的Position
设置为(240, 450)
生成生物¶
主节点将生成新的生物,我们希望它们出现在屏幕边缘的随机位置。添加一个Path2D节点,命名MobPath
为Main
的子节点。选择 时Path2D
,您会在编辑器顶部看到一些新按钮:
选择中间的一个(“添加点”)并通过单击以在显示的角处添加点来绘制路径。要使点捕捉到网格,请确保同时选择了“使用网格捕捉”和“使用智能捕捉”。这些选项可以在“锁定”按钮的左侧找到,分别显示为一些点和相交线旁边的磁铁。
重要提示:按顺时针顺序画出路径,否则你的生物生成时会指向外而不是指向内!
在图像中放置4点后,单击“关闭曲线”按钮,您的曲线将完成。
现在路径已定义,添加一个PathFollow2D 节点作为MobPath的
子节点并将其命名为MobSpawnLocation
。该节点在移动时会自动旋转并跟随路径,因此我们可以使用它来选择沿路径的随机位置和方向。
你的场景应该是这样的:
主脚本¶
添加一个脚本到Main
. 在脚本的顶部,我们使用@export var mob_scene: PackedScene
允许我们选择我们想要实例化的 Mob 场景。
GDScript
extends Node @export var mob_scene: PackedScene var score
单击Main节点,您将在“脚本变量”下的检查器中Main
看到该Mob Scene
属性。
您可以通过两种方式分配此属性的值:
-
从“文件系统”停靠栏拖放
mob.tscn
到Mob Scene 属性中。 -
单击“[空]”旁边的向下箭头并选择“加载”。选择
mob.tscn
。
接下来,在场景面板中选择Main节点下的Player
场景实例,并访问侧边栏上的 Node面板。确保在 Node面板中选择了 Signals 选项卡。
您应该看到Player
节点的信号列表。在列表中找到并双击hit
信号(或右键单击它并选择“连接...”)。这将打开信号连接对话框。我们想创建一个名为game_over
的新函数,它将处理游戏结束时需要发生的事情。在信号连接对话框底部的“Receiver Method”框中键入“game_over”,然后单击“Connect”。您的目标是让Player
中发出hit
信号并在Main
脚本中进行处理。将以下代码添加到新函数,以及一个new_game
函数,用于为新游戏设置所有内容:
GDScript
func game_over(): $ScoreTimer.stop() $MobTimer.stop() func new_game(): score = 0 $Player.start($StartPosition.position) $StartTimer.start()
现在将每个定时器节点(StartTimer
、 ScoreTimer
和MobTimer
)的timeout()
信号连接到主脚本。StartTimer
将启动其他两个计时器。ScoreTimer
将使分数增加 1。
GDScript
func _on_score_timer_timeout(): score += 1 func _on_start_timer_timeout(): $MobTimer.start() $ScoreTimer.start()
在 中_on_mob_timer_timeout()
,我们将创建一个生物实例,沿Path2D
随机选择一个起始位置,然后让生物开始运动。节点 PathFollow2D
会随着路径自动旋转,因此我们将使用它来选择生物的方向及其位置。当我们生成一个生物时,我们将在150.0
和250.0
之间选择一个随机值作为每个生物移动的速度(如果它们都以相同的速度移动会很无聊)。
请注意,必须使用add_child()
将新实例添加到场景中。
GDScript
func _on_mob_timer_timeout(): # Create a new instance of the Mob scene. var mob = mob_scene.instantiate() # Choose a random location on Path2D. var mob_spawn_location = get_node("MobPath/MobSpawnLocation") mob_spawn_location.progress_ratio = randf() # Set the mob's direction perpendicular to the path direction. var direction = mob_spawn_location.rotation + PI / 2 # Set the mob's position to a random location. mob.position = mob_spawn_location.position # Add some randomness to the direction. direction += randf_range(-PI / 4, PI / 4) mob.rotation = direction # Choose the velocity for the mob. var velocity = Vector2(randf_range(150.0, 250.0), 0.0) mob.linear_velocity = velocity.rotated(direction) # Spawn the mob by adding it to the Main scene. add_child(mob)
重要提示:为什么用PI
?在需要角度的函数中,Godot 使用弧度而不是度数。Pi 以弧度表示半圈,约 3.1415
(也有TAU
等于2 * PI
)。如果您更习惯使用度数,则需要使用deg_to_rad()
和rad_to_deg()
函数在两者之间进行转换。
测试场景¶
让我们测试场景以确保一切正常。将此new_game
调用添加到_ready()
:
GDScript
func _ready(): new_game()
让我们也指定Main
为我们的“主场景”——游戏启动时自动运行的场景。按“播放”按钮并main.tscn
在出现提示时选择。
注:如果您已经将另一个场景设置为“主场景”,则可以右键单击main.tscn
文件系统停靠栏并选择“设置为主场景”。
您应该能够移动玩家,看到怪物生成,并看到玩家在被怪物击中时消失。
当您确定一切正常时,在_ready()中
删除对new_game()
的调用。
我们的游戏缺少什么?一些用户界面。在下一课中,我们将添加一个标题屏幕并显示玩家的得分。
顶层屏幕¶
我们的游戏需要的最后一块是用户界面 (UI),用于显示得分、“游戏结束”消息和重启按钮等内容。
创建一个新场景,并添加一个名为HUD的
CanvasLayer节点 。“HUD”代表“顶层屏幕”,一种信息显示叠加层,显示在游戏视图顶部。
CanvasLayer节点让我们可以在游戏其余部分之上的层上绘制 UI 元素,这样它显示的信息就不会被任何游戏元素(例如玩家或生物)覆盖。
HUD需要显示以下信息:
-
得分,由
ScoreTimer
. -
消息,例如“游戏结束”或“准备好!”
-
一个“开始”按钮开始游戏。
UI 元素的基本节点是Control。要创建我们的 UI,我们将使用两种类型的Control节点:Label和Button。
创建以下节点作为HUD
节点的子节点:
单击ScoreLabel
并在检查器的Text
字段中键入一个数字。Control
节点的默认字体很小并且不能很好地缩放。游戏资源中包含一个名为“Xolonium-Regular.ttf”的字体文件。要使用此字体,请执行以下操作:
-
在“主题覆盖 > 字体”下,选择“加载”并选择“Xolonium-Regular.ttf”文件。
在ScoreLabel
上完成此操作后,您可以单击 Font 属性旁边的向下箭头并选择“复制”,然后将其“粘贴”到其他两个 Control 节点上的相同位置。设置ScoreLabel
“主题覆盖 > 字体大小”下的“字体大小”属性。设置为64的效果就很好。
注:锚点: Control
节点有位置和大小,但它们也有锚点。锚点定义原点——节点边缘的参考点。
如下图所示排列节点。您可以拖动节点以手动放置它们,或者为了更精确地放置它们,请使用具有以下设置的“锚点预设”:
Score标签¶
布局 :
-
锚预设:
Center Top
标签设置:
-
文本 :
0
-
水平对齐 :
Center
-
垂直对齐 :
Center
信息¶
布局 :
-
锚预设:
Center
标签设置:
-
文本 :
Dodge the Creeps!
-
水平对齐 :
Center
-
垂直对齐 :
Center
-
自动换行模式:
Word
开始按钮¶
布局 :
-
锚预设:
Center Bottom
按钮设置:
-
文本 :
Start
-
Y 位置:
580
(控制 - 布局/转换)
在MessageTimer
上,将Wait Time
设置为2并将One Shot
属性设置为“开”。
现在将此脚本添加到HUD
:
GDScript
extends CanvasLayer # Notifies `Main` node that the button has been pressed signal start_game
我们现在想临时显示一条消息,比如“Get Ready”,所以我们添加如下代码
GDScript
func show_message(text): $Message.text = text $Message.show() $MessageTimer.start()
我们还需要处理当玩家输了时发生的事情。下面的代码将显示“游戏结束”2 秒,然后返回到标题屏幕,并在短暂的暂停后显示“开始”按钮。
GDScript
func show_game_over(): show_message("Game Over") # Wait until the MessageTimer has counted down. await $MessageTimer.timeout $Message.text = "Dodge the\nCreeps!" $Message.show() # Make a one-shot timer and wait for it to finish. await get_tree().create_timer(1.0).timeout $StartButton.show()
当玩家输了时调用此函数。它将显示“Game Over”2 秒,然后返回到标题屏幕,并在短暂暂停后显示“Start”按钮。
注:当您需要短暂暂停时,使用 SceneTree 的create_timer()
函数是使用 Timer 节点的替代方法。这对于添加延迟非常有用,例如在上面的代码中,我们希望在显示“开始”按钮之前等待一段时间。
添加以下代码以HUD
更新分数
GDScript
func update_score(score): $ScoreLabel.text = str(score)
连接MessageTimer
的timeout()
信号和StartButton
的pressed()
信号,在新函数中添加如下代码:
GDScript
func _on_start_button_pressed(): $StartButton.hide() start_game.emit() func _on_message_timer_timeout(): $Message.hide()
将 HUD 连接到 Main¶
现在我们已经完成了HUD
场景的创建,回到Main
. 像你做Player
场景一样,在Main中
实例化HUD
场景。场景树应该是这样的,所以【你要检查一下】确保没有遗漏任何东西:
现在我们需要将HUD
功能连接到我们的Main
脚本。这需要在Main
场景中添加一些内容:
在“节点”选项卡中,通过单击“连接信号”窗口中的“选择”按钮并选择new_game()
方法或在窗口中的“接收器方法”下方键入“new_game”,将 HUD 的start_game
信号连接到主节点的【new_game()函数】
。确认【在脚本界面中】,绿色连接图标现在出现func new_game()
的旁边。
请记得从_ready()中
删除对new_game()
的调用。
在new_game()
中,更新分数显示并显示“准备好”消息:
GDScript
$HUD.update_score(score) $HUD.show_message("Get Ready")
在game_over()
我们需要调用相应的HUD
函数:
GDScript
$HUD.show_game_over()
提醒一下:我们不想自动开始新游戏,所以如果您还没有,请删除_ready()中
对new_game()
的调用。
最后,将此添加到_on_score_timer_timeout()
以更新显示与不断变化的分数同步:
GDScript
$HUD.update_score(score)
现在你可以玩了!单击“播放项目”按钮。你会被要求选择一个主场景,可选择main.tscn
。
移除旧的 creeps¶
如果您一直玩到“Game Over”,然后立即开始新游戏,则前一游戏的小兵可能仍会出现在屏幕上。如果他们在新游戏开始时全部消失会更好。我们只需要一种方法来告诉所有的暴徒自己离开。我们可以使用“组”功能来做到这一点。
在Mob
场景中,选择根节点并单击检查器旁边的“节点”选项卡(与您找到节点信号的位置相同)。在“信号”旁边,单击“组”,您可以输入新的组名称【mobs】并单击“添加”。
现在所有的生物都将在“mobs组中。然后我们可以将以下行添加到Main
的函数new_game()
中:
GDScript
get_tree().call_group("mobs", "queue_free")
该call_group()
函数在组中的每个节点上调用命名函数 - 在这种情况下,我们告诉每个mob【mobs组中的所有对象】删除自己。
至此游戏基本完成。在下一部分和最后一部分中,我们将通过添加背景、循环音乐和一些键盘快捷键来对其进行一些润色。
整理¶
我们现在已经完成了游戏的所有功能。以下是添加更多“果汁【小细节】”以改善游戏体验的一些剩余步骤。
随意用自己的想法扩展游戏玩法。
背景¶
默认的灰色背景不是很吸引人,所以让我们改变它的颜色。一种方法是使用ColorRect节点。使它成为下面的第一个节点,Main
这样它就会被绘制在其他节点的后面。 ColorRect
只有一个属性:Color
. 选择您喜欢的颜色,然后在视口顶部的工具栏或检查器中选择“布局”->“锚点预设”->“全矩形”,使其覆盖屏幕。
如果有的话,您也可以通过使用节点来添加背景图像 TextureRect
。
声音特效¶
声音和音乐可能是增加游戏体验吸引力的最有效方式。在您的游戏资产文件夹中,您有两个声音文件:背景音乐“House In a Forest Loop.ogg”和玩家失败时的“gameover.wav”。
添加两个AudioStreamPlayer节点作为Main
的子节点 。【分别命名为】Music
和DeathSound
。【分别】单击Stream
属性,选择“加载”,然后选择相应的音频文件。
所有音频的Loop属性缺省为禁用(disabled)。如果您希望音乐无缝循环,请单击“流文件”箭头,选择Make Unique
,然后单击“流文件”并选中Loop
复选框。
要播放音乐,在new_game()
函数中添加$Music.play(),game_over()函数中添加
$Music.stop()
。
最后,在game_over()函数中
添加$DeathSound.play()
。
键盘快捷键¶
由于游戏是用键盘控制的,如果我们也可以通过键盘上的一个键来启动游戏就方便了。我们可以使用Button
节点的“快捷方式”属性来做到这一点。
在上一课中,我们创建了四个输入动作来移动角色。我们将创建一个类似的输入操作来映射到开始按钮。
选择“项目”->“项目设置”,然后单击“输入【映射Input Map】”选项卡。以与创建移动输入操作相同的方式,创建一个名为start_game
的新输入操作并为该Enter 键添加一个键映射。
如果您有可用的控制器支持,现在是添加控制器支持的好时机。连接或配对您的控制器,然后在您希望为其添加控制器支持的每个输入操作下,单击“+”按钮并按下相应的按钮、方向键或您想要映射到相应输入操作的摇杆方向.
在HUD
场景中,选择StartButton
并在 Inspector 中找到其Shortcut属性。通过在框内单击创建一个新的快捷方式资源,打开事件数组并通过单击Array[InputEvent] (size 0)添加一个新的数组元素。
创建一个新的InputEventAction并将其命名为start_game
。
现在,当开始按钮出现时,您可以单击它或按下Enter 以开始游戏。
这样,您就完成了您在 Godot 中的第一个 2D 游戏。
你必须制作一个玩家控制的角色、在游戏板周围随机生成的敌人、计算分数、实现游戏结束和重播、用户界面、声音等等。恭喜!
还有很多东西要学,但你可以花点时间欣赏你所取得的成就。
準備ができたら、最初の 3D ゲームに進み、Godot で完全な 3D ゲームを最初から作成する方法を学ぶことができます。