Ensimmäinen pelisi

Yleiskatsaus

Tämä opas opastaa sinut ensimmäisen Godot-projektisi tekemisen läpi. Opit kuinka Godotin editori toimii, kuinka jäsentää projekti ja kuinka rakentaa 2D-peli.

Muista

Tämä projekti on johdanto Godotin pelimoottoriin. Siinä oletetaan, että sinulla on jo jotain ohjelmointikokemusta. Jos ohjelmointi on sinulle kokonaan uutta, sinun tulisi aloittaa tästä: Skriptaus.

Pelin nimi on ”Dodge the Creeps!”. Pelihahmosi täytyy liikkua ja vältellä vihollisia niin kauan kuin mahdollista. Tässä on ennakkokuva lopputuloksesta:

../../_images/dodge_preview.gif

Miksi 2D? 3D-pelit ovat paljon monimutkaisempia kuin 2D-pelit. Sinun tulisi pysytellä 2D:ssä, kunnes sinulla on hyvä ymmärtämys pelikehitysprosessista.

Projektin järjestely

Käynnistä Godot ja luo uusi projekti. Lataa sen jälkeen dodge_assets.zip - kuvat ja äänet, joita tulet käyttämään pelin tekemisessä. Pura nämä tiedostot projektisi kansioon.

Muista

Oletamme tämän oppaan ajan, että olet sinut editorin kanssa. Jos et ole lukenut Skenet ja solmut, tee niin nyt, jotta saat selostuksen kuinka projekti pystytetään ja kuinka editoria käytetään.

Tämä peli käyttää potrettitilaa, joten meidän täytyy säätää peli-ikkunan kokoa. Napsauta Projekti -> Projektin asetukset -> Display -> Window, ja aseta ”Width” arvoon 480 ja ”Height” arvoon 720.

Projektin järjestäminen

Teemme tähän projektiin kolme itsenäistä skeneä: Player, Mob, ja HUD, jotka yhdistämme pelin Main skeneen. Suuremmassa projektissa voisi olla hyödyllistä tehdä kansioita eri skeneille ja niiden skripteille, mutta tätä suhteellisen pientä peliä varten voit tallentaa skenesi ja skriptisi juurikansioon, johon viitataan res://. Näet projektisi kansiot Tiedostojärjestelmä-telakassa vasemmassa alakulmassa:

../../_images/filesystem_dock.png

Player-skene

Ensimmäinen skene, jonka teemme, määrittelee Player objektin. Yksi etu erillisen pelaajaskenen luomisesta on, että voimme testata sitä erillisesti, jo ennen kuin olemme luoneet pelin muut osat.

Solmurakenne

Aloittaaksesi, napsauta ”Luo uusi solmu” painiketta ja lisää Area2D solmu skeneen.

../../_images/add_node.png

Godot will display a warning icon next to the node in the scene tree. You can ignore it for now. We will address it later.

Area2D solmulla voimme tunnistaa objektit, jotka ovat pelaajan kanssa päällekäin tai törmäävät siihen. Muuta sen nimeksi Player napsauttamalla solmun nimeä. Tämä on skenen juurisolmu. Voimme lisätä lisää solmuja pelaajalle lisätäksemme toiminnallisuutta.

Ennen kuin lisäämme Player solmulle yhtään alisolmuja, haluamme pitää huolen ettemme vahingossa siirrä niitä tai muuta niiden kokoa napsauttamalla niitä. Valitse solmu ja napsauta lukon vasemmalla puolella olevaa kuvaketta; sen työkaluvihjeessä lukee ”Varmistaa, ettei objektin alisolmuja voi valita.”

../../_images/lock_children.png

Save the scene. Click Scene -> Save, or press Ctrl + S on Windows/Linux or Cmd + S on macOS.

Muista

Tässä projektissa noudatamme Godotin nimeämiskäytäntöjä.

  • GDScript: Luokat (solmut) käyttävät PascalCase tyyliä, muuttujat ja funktiot käyttävät snake_case tyyliä, ja vakiot käyttävät ALL_CAPS tyyliä (katso GDScript style guide).
  • C#: Luokat, vientimuuttujat ja metodit käyttävät PascalCase tyyliä, yksityiset kentät käyttävät _camelCase tyyliä, paikalliset muuttujat ja parametrit käyttävät camelCase tyyliä (katso C# style guide). Ole huolellinen ja kirjoita metodien nimet tarkasti kytkiessäsi signaaleja.

Spriten animaatio

Napsauta Player solmua ja lisää AnimatedSprite solmu sen alisolmuksi. AnimatedSprite hoitaa pelaajamme ulkoasun ja animaatiot. Huomaa, että solmun vieressä on varoitusmerkki. AnimatedSprite tarvitsee SpriteFrames resurssin, joka on lista animaatioista, joita se voi toistaa. Luodaksesi sellaisen, etsi Frames ominaisuus Tarkastelijasta ja napsauta ”[empty]” -> ”New SpriteFrames”. Tämän pitäisi automaattisesti avata SpriteFrames paneelin.

../../_images/spriteframes_panel.png

Vasemmalla on lista animaatioista. Napsauta ”default” nimistä ja anna sille uudeksi nimeksi ”right”. Napsauta sen jälkeen ”Lisää” painiketta luodaksesi toisen animaation, jonka nimeksi tulee ”up”. Vedä kullekin animaatiolle kaksi kuvaa, nimeltään playerGrey_up[1/2] ja playerGrey_walk[1/2], paneelin ”Animaatioruudut” puolelle:

../../_images/spriteframes_panel2.png

Pelaajakuvat on hieman liian suuria peli-ikkunalle, joten meidän täytyy pienentää niitä. Napsauta AnimatedSprite solmua ja aseta Scale ominaisuus arvoon (0.5, 0.5). Löydät sen Tarkastelijasta Node2D otsikon alta.

../../_images/player_scale.png

Lisää lopuksi CollisionShape2D Player solmun alle. Tämä määrittelee pelaajan ”osumalaatikon” tai törmäysalueen rajat. Tälle hahmolle sopii parhaiten CapsuleShape2D solmu, joten napsauta Tarkastelijassa ”Shape” ominaisuuden vierestä ”[empty]” -> ”New CapsuleShape2D”. Käyttäen kahta kahvaa, venytä muoto kattamaan koko sprite:

../../_images/player_coll_shape.png

Kun olet valmis, Player skenesi pitäisi näyttää tältä:

../../_images/player_scene_nodes.png

Pelaajan liikuttaminen

Nyt meidän täytyy lisätä hiukan toiminnallisuutta, jota emme voi saada valmiilta solmulta, joten lisäämme skriptin. Napsauta Player solmua ja ”Lisää skripti” painiketta:

../../_images/add_script_button.png

Skriptin asetukset -ikkunassa voit antaa oletusasetuksien olla sellaisenaan. Napsauta vain ”Luo”:

Muista

Jos olet luomassa C#-skriptiä tai muun kielistä skriptiä, valitse kieli kieli pudotusvalikosta ennen kuin huitaiset luontinappia.

../../_images/attach_node_window.png

Muista

Jos törmäät nyt ensi kertaa GDScriptiin, ole hyvä ja lue Skriptaus ennen kuin jatkat.

Aloita luomalla ne jäsenmuuttujat, jotka tämä objekti tulee tarvitsemaan:

extends Area2D

export var speed = 400  # How fast the player will move (pixels/sec).
var screen_size  # Size of the game window.
public class Player : Area2D
{
    [Export]
    public int Speed = 400; // How fast the player will move (pixels/sec).

    private Vector2 _screenSize; // Size of the game window.
}

Käyttäen export avainsanaa ensimmäiselle muuttujalle speed antaa meidän asettaa sen arvon Tarkastelijassa. Se voi olla kätevää arvoille, joita haluat säätää kuin ne olisivat solmun valmiita ominaisuuksia. Napsauta Player solmua ja aseta nopeusominaisuudeksi 400.

Varoitus

If you’re using C#, you need to (re)build the project assemblies whenever you want to see new export variables or signals. This build can be manually triggered by clicking the word ”Mono” at the bottom of the editor window to reveal the Mono Panel, then clicking the ”Build Project” button.

../../_images/export_variable.png

_ready() funktiota kutsutaan, kun solmu lisätään skenepuuhun, mikä on hyvä hetki hakea peli-ikkunan koko:

func _ready():
    screen_size = get_viewport_rect().size
public override void _Ready()
{
    _screenSize = GetViewport().Size;
}

Nyt voimme käyttää _process() funktiota määrittelemään mitä pelaaja tekee. _process() funktiota kutsutaan jokaisella ruudunpäivityksellä, joten käytämme sitä päivittämään pelimme niitä osasia joiden odotamme muuttuvan usein. Pelaajan osalta teemme siinä seuraavaa:

  • Tarkista syöte.
  • Liiku annettuun suuntaan.
  • Toista asiaankuuluva animaatio.

Ensin meidän täytyy tarkistaa syöte - painaako pelaaja jotain näppäintä? Tässä pelissä meillä on neljän suunnan syötteet tarkistettavana. Syötetoiminnot on määritelty Projektin asetuksissa kohdassa ”Input Map”. Voit määritellä omia tapahtumia ja asettaa niille eri näppäimiä, hiiritapahtumia ja muita syötteitä. Tässä esimerkissä käytämme oletustapahtumia, jotka on asetettu näppäimistön nuolinäppäimille.

Voit tunnistaa onko näppäintä painettu käyttämällä Input.is_action_pressed() funktiota, joka palauttaa true, jos sitä on painettu ja false jos ei ole.

func _process(delta):
    var velocity = Vector2()  # The player's movement vector.
    if Input.is_action_pressed("ui_right"):
        velocity.x += 1
    if Input.is_action_pressed("ui_left"):
        velocity.x -= 1
    if Input.is_action_pressed("ui_down"):
        velocity.y += 1
    if Input.is_action_pressed("ui_up"):
        velocity.y -= 1
    if velocity.length() > 0:
        velocity = velocity.normalized() * speed
        $AnimatedSprite.play()
    else:
        $AnimatedSprite.stop()
public override void _Process(float delta)
{
    var velocity = new Vector2(); // The player's movement vector.

    if (Input.IsActionPressed("ui_right"))
    {
        velocity.x += 1;
    }

    if (Input.IsActionPressed("ui_left"))
    {
        velocity.x -= 1;
    }

    if (Input.IsActionPressed("ui_down"))
    {
        velocity.y += 1;
    }

    if (Input.IsActionPressed("ui_up"))
    {
        velocity.y -= 1;
    }

    var animatedSprite = GetNode<AnimatedSprite>("AnimatedSprite");

    if (velocity.Length() > 0)
    {
        velocity = velocity.Normalized() * Speed;
        animatedSprite.Play();
    }
    else
    {
        animatedSprite.Stop();
    }
}

Aloitamme asettamalla velocity arvoksi (0, 0) – lähtökohtaisesti pelihahmon ei pitäisi olla liikkeessä. Tarkistamme sitten kunkin syötteen ja kasvatamme/vähennämme velocity arvoa saadaksemme lopullisen suunnan. Jos esimerkiksi painat oikealle ja alas samanaikasesti, lopputuloksena oleva velocity vektori on (1, 1). Tässä tapauksessa, koska kasvatamme sekä vaaka- että pystyliikettä, pelihahmo liikkuisi nopeammin kuin jos se liikkuisi vain vaakasuuntaisesti.

Voimme estää sen, jos normalisoimme nopeusvektorin, mikä tarkoittaa että asetamme sen pituudeksi 1 ja kerromme sen halutulla nopeudella. Tämän seurauksena vinottainen liike ei ole enää nopeampaa.

Vihje

Jos et ole koskaan käyttänyt vektorimatematiikkaa, tai tarvitset kertausta, voit katsoa selityksen Godotin vektorien käytöstä: Vector math. Se on hyvä osata, mutta ei ole välttämätön oppaan loppuosan kannalta.

Tarkistamme myös liikkuuko pelaaja, jotta voimme aloittaa tai lopettaa AnimatedSprite animaation.

Vihje

GDScriptissä, $ palauttaa solmun, joka löytyy suhteellisesta polusta nykyiseen solmuun nähden, tai palauttaa null jos solmua ei löydy. Koska AnimatedSprite on nykyisen solmun alisolmu, voimme käyttää $AnimatedSprite syntaksia.

$ on lyhenne get_node() syntaksille. Toisin sanoen, yllä olevassa koodissa $AnimatedSprite.play() on sama kuin get_node("AnimatedSprite").play().

Now that we have a movement direction, we can update the player’s position. We can also use clamp() to prevent it from leaving the screen. Clamping a value means restricting it to a given range. Add the following to the bottom of the _process function:

position += velocity * delta
position.x = clamp(position.x, 0, screen_size.x)
position.y = clamp(position.y, 0, screen_size.y)
Position += velocity * delta;
Position = new Vector2(
    x: Mathf.Clamp(Position.x, 0, _screenSize.x),
    y: Mathf.Clamp(Position.y, 0, _screenSize.y)
);

Vihje

The delta parameter in the _process() function refers to the frame length - the amount of time that the previous frame took to complete. Using this value ensures that your movement will remain consistent even if the frame rate changes.

Napsuta ”Pelaa skeneä” (F6) ja varmista, että voit liikuttaa pelihahmoa ympäri ruutua kaikkiin suuntiin. Konsolitulosteen, joka aukeaa skeneä pelattaessa, voi sulkea napsauttamalla Tuloste (jonka pitäisi olla sinisellä korostettu) alapaneelin vasemmassa alakulmassa.

Varoitus

Jos saat ”Debuggeri” paneeliin virheen, jossa mainitaan ”null instance”, se johtuu todennäköisesti siitä, että kirjoitit nimen väärin. Solmujen nimet ovat kirjainkoosta riippuvaisia ja $SolmunNimi tai get_node("SolmunNimi") täytyy vastata skenepuussa näkyvää nimeä.

Animaatioiden valinta

Nyt kun pelaaja voi liikkua, meidän täytyy vaihtaa AnimatedSprite solmun toistama animaatio suuntaan perustuen. Meillä on ”oikealle” animaatio, joka pitäisi vasenta animaatiota varten kääntää vaakasuunnassa toisinpäin käyttäen flip_h ominaisuutta, sekä ”ylös” animaatio, joka pitäisi alaspäin animaatiota varten kääntää pystysuunnassa toisinpäin flip_v ominaisuudella. Lisätään tämä koodinpätkä _process() funktiomme loppuun:

if velocity.x != 0:
    $AnimatedSprite.animation = "right"
    $AnimatedSprite.flip_v = false
    # See the note below about boolean assignment
    $AnimatedSprite.flip_h = velocity.x < 0
elif velocity.y != 0:
    $AnimatedSprite.animation = "up"
    $AnimatedSprite.flip_v = velocity.y > 0
if (velocity.x != 0)
{
    animatedSprite.Animation = "right";
    animatedSprite.FlipV = false;
    // See the note below about boolean assignment
    animatedSprite.FlipH = velocity.x < 0;
}
else if(velocity.y != 0)
{
    animatedSprite.Animation = "up";
    animatedSprite.FlipV = velocity.y > 0;
}

Muista

Boolean-sijoitukset ylläolevassa koodissa ovat yleisiä pikakirjoitustapoja ohjelmoijille. Tarkastele tätä koodia yllä oleviin lyhennettyihin boolean-sijoituksiin verrattuna:

if velocity.x < 0:
    $AnimatedSprite.flip_h = true
else:
    $AnimatedSprite.flip_h = false
if velocity.x < 0:
    animatedSprite.FlipH = true
else:
    animatedSprite.FlipH = false

Pelaa skene uudelleen ja tarkista, että animaatiot toimivat oikein kuhunkin suuntaan. Kun olet varma, että liikkuminen toimii oikein, lisää tämä rivi _ready() funktioon, jotta pelihahmo on piilotettu pelin alkaessa:

hide()
Hide();

Törmäyksiin valmistautuminen

Haluamme, että Player havaitsee milloin vihollinen on törmännyt siihen, mutta emme ole tehneet vielä vihollisia! Se on OK, koska aiomme laittaa sen toimimaan Godotin signal toiminnallisuudella.

Lisää seuraava skriptin alkuun, extends Area2d jälkeen:

signal hit
// Don't forget to rebuild the project so the editor knows about the new signal.

[Signal]
public delegate void Hit();

Tämä määrittelee mukautetun signaalin nimeltä ”hit”, jonka laitamme pelaajamme lähettämään, kun se törmää viholliseen. Käytämme Area2D solmua tunnistamaan törmäyksen. Valitse Player solmu ja napsauta ”Solmu” välilehteä Tarkastelijan vieressä nähdäksesi listan signaaleista, joita pelaaja voi lähettää:

../../_images/player_signals.png

Huomaa, että oma ”hit” signaalimme on myös siellä! Koska viholliset tulevat olemaan RigidBody2D solmuja, haluamme body_entered( Object body ) signaalin; se lähetetään kappaleen osuessa pelaajaan. Napsauta ”Yhdistä…” ja sen jälkeen ”Yhdistä” uudelleen ”Yhdistetään signaalia” ikkunassa. Meidän ei tarvitse muuttaa mitään näistä asetuksista – Godot luo automaattisesti funktion pelaajasi skriptiin. Tätä funktiota kutsutaan aina, kun signaali lähetetään – se käsittelee signaalin.

Vihje

Signaalia yhdistettäessä, sen sijaan että antaisit Godotin luoda sinulle funktion, voit myös nimetä olemassa olevan funktion, johon haluaisit signaalin kytkettävän.

Lisää tämä koodi funktioon:

func _on_Player_body_entered(body):
    hide()  # Player disappears after being hit.
    emit_signal("hit")
    $CollisionShape2D.set_deferred("disabled", true)
public void OnPlayerBodyEntered(PhysicsBody2D body)
{
    Hide(); // Player disappears after being hit.
    EmitSignal("Hit");
    GetNode<CollisionShape2D>("CollisionShape2D").SetDeferred("disabled", true);
}

Each time an enemy hits the player, the signal is going to be emitted. We need to disable the player’s collision so that we don’t trigger the hit signal more than once.

Muista

Disabling the area’s collision shape can cause an error if it happens in the middle of the engine’s collision processing. Using set_deferred() allows us to have Godot wait to disable the shape until it’s safe to do so.

Viimeinen palanen pelaajallemme on lisätä funktio, jota voimme kutsua alustamaan pelaajan pelin alkaessa.

func start(pos):
    position = pos
    show()
    $CollisionShape2D.disabled = false
public void Start(Vector2 pos)
{
    Position = pos;
    Show();
    GetNode<CollisionShape2D>("CollisionShape2D").Disabled = false;
}

Vihollisen skene

Nyt on aika luoda viholliset, joita pelaajamme täytyy väistellä. Niiden käyttäytyminen ei tule olemaan kovin monimutkaista: vihulaiset ilmestyvät satunnaisesti ruudun reunoille ja liikkuvat satunnaisiin suuntiin suoraa viivaa pitkin, kadoten sitten poistuttuaan ruudulta.

Rakennamme tämän Mob skeneen, josta voimme sitten luoda ilmentymiä luodaksemme minkä tahansa määrän itsenäisiä vihulaisia peliin.

Solmun järjestely

Napsauta Skene -> Uusi skene, ja luomme Mob skenen.

Mob skene käyttää seuraavia solmuja:

  • RigidBody2D (nimeltä Mob)
    • AnimatedSprite
    • CollisionShape2D
    • VisibilityNotifier2D (nimeltään Visibility)

Älä unohda asettaa alisolmuja niin, ettei niitä voi valita, kuten teit Player skenen kanssa.

RigidBody2D ominaisuuksissa, aseta Gravity Scale arvoon 0, jotta vihulainen ei putoa alaspäin. Lisäksi, PhysicsBody2D osiossa, napsauta Mask ominaisuutta ja poista merkintä ensimmäisestä laatikosta. Tämä varmistaa, etteivät viholliset törmäile toisiinsa.

../../_images/set_collision_mask.png

Kokoa AnimatedSprite kuten teit pelaajahahmon kanssa. Tällä kertaa meillä on kolme animaatiota: fly, swim ja walk. Aseta Playing ominaisuus Tarkastelijassa ”On” asentoon ja säädä ”Speed (FPS)” asetusta kuten esitetty alla. Valitsemme satunnaisesti yhden näistä animaatioista, jotta vihulaisissa on vähän vaihtelua.

../../_images/mob_animations.gif

fly pitäisi asettaa arvoon 3 FPS, ja swim sekä walk arvoon 4 FPS.

Kuten pelaajan kuvat, nämä vihollisten kuvatkin täytyy pienentää. Aseta AnimatedSprite solmun Scale ominaisuus arvoon (0.75, 0.75).

Kuten Player skenssä, lisää CapsuleShape2D törmäyksiä varten. Muodon kohdistamiseksi kuvaan sinun täytyy asettaa Rotation Degrees ominaisuudeksi 90 Node2D solmussa.

Vihollisen skripti

Lisää Mob solmulle skripti ja lisää seuraavat jäsenmuuttujat:

extends RigidBody2D

export var min_speed = 150  # Minimum speed range.
export var max_speed = 250  # Maximum speed range.
var mob_types = ["walk", "swim", "fly"]
public class Mob : RigidBody2D
{
    // Don't forget to rebuild the project so the editor knows about the new export variables.

    [Export]
    public int MinSpeed = 150; // Minimum speed range.

    [Export]
    public int MaxSpeed = 250; // Maximum speed range.

    private String[] _mobTypes = {"walk", "swim", "fly"};
}

Kun synnytämme vihulaisen, valitsemme sen liikkumisen nopeudeksi satunnaisen arvon min_speed ja max_speed väliltä (olisi tylsää, jos ne kaikki liikkuisivat samalla nopeudella). Meillä on myös kolmen animaation nimet sisältävä taulukko, jota käytämme valitsemaan yhden niistä satunnaisesti. Varmista, että olet kirjoittanut ne samalla tavalla skriptiin ja SpriteFrames resurssiin.

Katsokaamme nyt skriptin jäljellä olevaa osaa. _ready() funktiossa valitsemme yhden kolmesta animaatiotyypistä:

func _ready():
    $AnimatedSprite.animation = mob_types[randi() % mob_types.size()]
// C# doesn't implement GDScript's random methods, so we use 'System.Random' as an alternative.
static private Random _random = new Random();

public override void _Ready()
{
    GetNode<AnimatedSprite>("AnimatedSprite").Animation = _mobTypes[_random.Next(0, _mobTypes.Length)];
}

Muista

Sinun täytyy käyttää randomize() funktiota, jos haluat ”satunnaislukujen” jaksosi olevan erilainen joka kerran, kun suoritat skeneä. Aiomme käyttää randomize() funktiota Main skenessämme, joten emme tarvitse sitä tässä. randi() % n on tavallinen tapa tuottaa satunnainen kokonaisluku 0 ja n-1 väliltä.

Viimeinen pätkä on sitä varten, että vihulaiset tuhoavat itsensä poistuessaan ruudulta. Yhdistä Visibility solmun screen_exited() signaali ja lisää tämä koodi:

func _on_Visibility_screen_exited():
    queue_free()
public void OnVisibilityScreenExited()
{
    QueueFree();
}

Tämä päättää Mob skenen.

Pääskene

Nyt on aika saattaa kaikki yhteen. Luo uusi skene ja lisää siihen Node solmu nimeltä Main. Napsauta ”Ilmentymä” painiketta ja valitse tallentamasi Player.tscn.

../../_images/instance_scene.png

Muista

Katso Ilmentymien luonti oppiaksesi enemmän ilmentymistä.

Lisää nyt seuraavat solmut Main solmun alisolmuiksi ja nimeä ne kuten esitetty (arvot ovat sekunteina):

  • Timer (nimeltään MobTimer) - ohjaamaan kuinka usein vihulaiset ilmestyvät
  • Timer (nimeltään ScoreTimer) - kasvattamaan pistelaskuria joka sekunti
  • Timer (nimeltään StartTimer) - antamaan viive ennen aloittamista
  • Position2D (nimeltään StartPosition) - merkkaamaan pelaajan aloituspaikkaa

Aseta kunkin Timer solmun Wait Time ominaisuus seuraavasti:

  • MobTimer: 0.5
  • ScoreTimer: 1
  • StartTimer: 2

Lisäksi, aseta StartTimer solmun One Shot ominaisuus arvoon ”On”, ja aseta StartPosition solmun Position ominaisuudeksi (240, 450).

Vihollisten lisääminen

Main solmu tuottaa uusia vihollisia ja haluamme niiden ilmestyvän satunnaiseen paikkaan ruudun reunalla. Lisää Main solmun alisolmuksi Path2D solmu nimeltä MobPath. Kun valitset Path2D solmun, näet joitakin uusia painikkeita editorin ylälaidassa:

../../_images/path2d_buttons.png

Select the middle one (”Add Point”) and draw the path by clicking to add the points at the corners shown. To have the points snap to the grid, make sure ”Use Grid Snap” is selected. This option can be found to the left of the ”Lock” button, appearing as a magnet next to some intersecting lines.

../../_images/draw_path2d.gif

Tärkeä

Piirrä polku myötäpäiväisessä järjestyksessä, tai vihulaisesi lisääntyvät suunnaten ulospäin eikä sisäänpäin!

Kuvan pisteen 4 lisäyksen jälkeen, napsauta ”Sulje käyrä” painiketta ja käyrästäsi tulee täysi.

Nyt kun polku on määritetty, lisää PathFollow2D solmu MobPath solmun alle ja nimeä se MobSpawnLocation. Tämä solmu kiertyy ja seuraa polkua automaattisesti edetessään, joten voimme käyttää sitä valitsemaan satunnaisen sijainnin ja suunnan polulta.

Pääskripti

Lisää skripti Main solmulle. Skriptin alussa käytämme export (PackedScene) koodia, jotta voimme valita mistä Mob skenestä haluamme luoda ilmentymän.

extends Node

export (PackedScene) var Mob
var score

func _ready():
    randomize()
public class Main : Node
{
    // Don't forget to rebuild the project so the editor knows about the new export variable.

    [Export]
    public PackedScene Mob;

    private int _score;

    // We use 'System.Random' as an alternative to GDScript's random methods.
    private Random _random = new Random();

    public override void _Ready()
    {
    }

    // We'll use this later because C# doesn't support GDScript's randi().
    private float RandRange(float min, float max)
    {
        return (float)_random.NextDouble() * (max - min) + min;
    }
}

Vedä Mob.tscn ”Tiedostojärjestelmä” paneelista ja pudota se Mob ominaisuuteen Main solmun Skriptimuuttujien alle.

Next, click on the Player and connect the hit signal. We want to make a new function named game_over, which will handle what needs to happen when a game ends. Type ”game_over” in the ”Receiver Method” box at the bottom of the ”Connecting Signal” window. Add the following code, as well as a new_game function to set everything up for a new game:

func game_over():
    $ScoreTimer.stop()
    $MobTimer.stop()

func new_game():
    score = 0
    $Player.start($StartPosition.position)
    $StartTimer.start()
public void GameOver()
{
    GetNode<Timer>("MobTimer").Stop();
    GetNode<Timer>("ScoreTimer").Stop();
}

public void NewGame()
{
    _score = 0;

    var player = GetNode<Player>("Player");
    var startPosition = GetNode<Position2D>("StartPosition");
    player.Start(startPosition.Position);

    GetNode<Timer>("StartTimer").Start();
}

Yhdistä nyt kunkin Timer solmun (StartTimer, ScoreTimer ja MobTimer) timeout() signaali pääskriptiin. StartTimer käynnistää kaksi muuta ajastinta. ScoreTimer kasvattaa pistelaskuria yhdellä.

func _on_StartTimer_timeout():
    $MobTimer.start()
    $ScoreTimer.start()

func _on_ScoreTimer_timeout():
    score += 1
public void OnStartTimerTimeout()
{
    GetNode<Timer>("MobTimer").Start();
    GetNode<Timer>("ScoreTimer").Start();
}

public void OnScoreTimerTimeout()
{
    _score++;
}

_on_MobTimer_timeout() funktiossa luomme vihollisilmentymän, valitsemme satunnaisen aloituspaikan Path2D varrelta, ja sysäämme vihollisen liikkeelle. PathFollow2D solmu kiertyy automaattisesti seuratessaan polkua, joten hyödynnämme sitä valitsemaan sekä vihollisen suunnan että sijainnin.

Huomaa, että uusi ilmentymä täytyy lisätä skeneen käyttämällä add_child() funktiota.

func _on_MobTimer_timeout():
    # Choose a random location on Path2D.
    $MobPath/MobSpawnLocation.offset = randi()
    # Create a Mob instance and add it to the scene.
    var mob = Mob.instance()
    add_child(mob)
    # Set the mob's direction perpendicular to the path direction.
    var direction = $MobPath/MobSpawnLocation.rotation + PI / 2
    # Set the mob's position to a random location.
    mob.position = $MobPath/MobSpawnLocation.position
    # Add some randomness to the direction.
    direction += rand_range(-PI / 4, PI / 4)
    mob.rotation = direction
    # Set the velocity (speed & direction).
    mob.linear_velocity = Vector2(rand_range(mob.min_speed, mob.max_speed), 0)
    mob.linear_velocity = mob.linear_velocity.rotated(direction)
public void OnMobTimerTimeout()
{
    // Choose a random location on Path2D.
    var mobSpawnLocation = GetNode<PathFollow2D>("MobPath/MobSpawnLocation");
    mobSpawnLocation.SetOffset(_random.Next());

    // Create a Mob instance and add it to the scene.
    var mobInstance = (RigidBody2D)Mob.Instance();
    AddChild(mobInstance);

    // Set the mob's direction perpendicular to the path direction.
    float direction = mobSpawnLocation.Rotation + Mathf.Pi / 2;

    // Set the mob's position to a random location.
    mobInstance.Position = mobSpawnLocation.Position;

    // Add some randomness to the direction.
    direction += RandRange(-Mathf.Pi / 4, Mathf.Pi / 4);
    mobInstance.Rotation = direction;

    // Choose the velocity.
    mobInstance.SetLinearVelocity(new Vector2(RandRange(150f, 250f), 0).Rotated(direction));
}

Tärkeä

GDScript käyttää kulmia tarvitsevissa funktioissa radiaaneja, ei asteita. Jos työskentelet mieluummin asteilla, sinun täytyy käyttää deg2rad() ja rad2deg() funktioita tehdäksesi muunnos niiden välillä.

Heijastusnäyttö (HUD)

Pelimme tarvitsema viimeinen osa on käyttöliittymä: sillä esitetään sellaisia juttuja kuten pistemäärä, ”game over”-viesti ja ”aloita alusta”-painike. Luo uusi skene ja lisää CanvasLayer solmu nimeltä HUD. ”HUD” tulee sanoista ”heads-up display” (suom. heijastusnäyttö). Se on informaatiota sisältävä näyttö, joka piirretään osittaiseksi peitteeksi pelinäkymän päälle.

CanvasLayer solmu antaa meidän piirtää käyttöliittymäelementtimme muun pelin yläpuolella olevalle kerrokselle, niin että sen esittämä tieto ei jää pelielementtien, kuten pelaaja ja vihollinen, alle.

Heijastusnäyttö näyttää seuraavat tiedot:

  • Pistemäärä, joka muuttuu ScoreTimer solmun toimesta.
  • Viesti, kuten ”Game Over” tai ”Get Ready!”
  • ”Start”-painike pelin aloittamiseksi.

Pohjasolmu käyttöliittymäelementeille on Control. Luodaksemme käyttöliittymämme käytämme kahdentyyppisiä Control solmuja: Label ja Button.

Luo seuraavat HUD solmun alle:

  • Label nimeltä ScoreLabel.
  • Label nimeltä MessageLabel.
  • Button nimeltä StartButton.
  • Timer nimeltä MessageTimer.

Napsauta ScoreLabel solmua ja kirjoita Tarkastelijassa Text kenttään jokin numero. Control solmujen oletuskirjasin on pieni, eikä skaalaudu hyvin. Pelin asseteissa on mukana kirjasintiedosto nimeltään ”Xolonium-Regular.ttf”. Käyttääksesi tätä kirjasintyyppiä, tee kullekin kolmelle Control solmulle seuraava:

  1. ”Custom Fonts” alla, valitse ”New DynamicFont”
../../_images/custom_font1.png
  1. Napsauta lisäämääsi ”DynamicFont” kirjasinta, valitse ”Font Data” alta ”Load” ja valitse ”Xolonium-Regular.ttf” tiedosto. Sinun täytyy myös asettaa kirjasimen koko Size ominaisuudesta. Asetus 64 toimii hyvin.
../../_images/custom_font2.png

Muista

Ankkurit ja marginaalit: Control solmuilla täytyy olla sijainti ja koko, mutta niillä on myös ankkurit ja marginaalit. Ankkurit määrittävät alkupisteen – viitepisteen solmun reunoille. Marginaalit päivittyvät automaattisesti, kun liikutat Control-solmua tai muutat sen kokoa. Ne edustavat etäisyyttä Control-solmun reunoista sen ankkuriin. Katso Design interfaces with the Control nodes tarkempia tietoja varten.

Arrange the nodes as shown below. Click the ”Layout” button to set a Control node’s layout:

../../_images/ui_anchor.png

Voit kiskoa solmuja asettaaksesi ne manuaalisesti, tai tarkempaa sijoittelua varten, voit käyttää seuraavia asetuksia:

ScoreLabel

  • Text : 0
  • Layout : ”Top Wide”
  • Align : ”Center”

MessageLabel

  • Text : Dodge the Creeps!
  • Layout : ”HCenter Wide”
  • Align : ”Center”
  • Autowrap : ”On”

StartButton

  • Text : Start
  • Layout : ”Center Bottom”
  • Margin :
    • Top: -200
    • Bottom: -100

Lisää nyt tämä skripti HUD solmulle:

extends CanvasLayer

signal start_game
public class HUD : CanvasLayer
{
    // Don't forget to rebuild the project so the editor knows about the new signal.

    [Signal]
    public delegate void StartGame();
}

start_game signaali kertoo Main solmulle, että painiketta on painettu.

func show_message(text):
    $MessageLabel.text = text
    $MessageLabel.show()
    $MessageTimer.start()
public void ShowMessage(string text)
{
    var messageLabel = GetNode<Label>("MessageLabel");
    messageLabel.Text = text;
    messageLabel.Show();

    GetNode<Timer>("MessageTimer").Start();
}

Tätä funktiota kutsutaan, kun haluamme näyttää väliaikaisen viestin, kuten ”Get Ready”. Aseta MessageTimer solmussa Wait Time arvoksi 2 ja aseta One Shot ominaisuudeksi ”On”.

func show_game_over():
    show_message("Game Over")

    yield($MessageTimer, "timeout")

    $MessageLabel.text = "Dodge the\nCreeps!"
    $MessageLabel.show()

    yield(get_tree().create_timer(1), "timeout")

    $StartButton.show()
async public void ShowGameOver()
{
    ShowMessage("Game Over");

    var messageTimer = GetNode<Timer>("MessageTimer");
    await ToSignal(messageTimer, "timeout");

    var messageLabel = GetNode<Label>("MessageLabel");
    messageLabel.Text = "Dodge the\nCreeps!";
    messageLabel.Show();

    GetNode<Button>("StartButton").Show();
}

Tätä funktiota kutsutaan kun pelaaja häviää. Se näyttää ”Game Over” kahden sekunnin ajan, ja palaa sitten alkuruutuun ja näyttää ”Start” painikkeen lyhyen tauon jälkeen.

Muista

When you need to pause for a brief time, an alternative to using a Timer node is to use the SceneTree’s create_timer() function. This can be very useful to delay, such as in the above code, where we want to wait a little bit of time before showing the ”Start” button.

func update_score(score):
    $ScoreLabel.text = str(score)
public void UpdateScore(int score)
{
    GetNode<Label>("ScoreLabel").Text = score.ToString();
}

Tätä funktiota kutsutaan Main solmussa aina kun pistemäärä muuttuu.

Yhdistä MessageTimer solmun timeout() signaali ja StartButton solmun pressed() signaali.

func _on_StartButton_pressed():
    $StartButton.hide()
    emit_signal("start_game")

func _on_MessageTimer_timeout():
    $MessageLabel.hide()
public void OnStartButtonPressed()
{
    GetNode<Button>("StartButton").Hide();
    EmitSignal("StartGame");
}

public void OnMessageTimerTimeout()
{
    GetNode<Label>("MessageLabel").Hide();
}

HUD- ja Main-skenejen liittäminen

Nyt kun olemme saaneet HUD skenen valmiiksi, tallenna se ja mene takaisin Main skeneen. Luo HUD skenestä ilmentymä Main skeneen, samoin kuin teit Player skenen kanssa, ja sijoita se alimmaksi puussa. Koko puun pitäisi näyttää tältä, joten varmista ettei mitään jäänyt huomaamatta:

../../_images/completed_main_scene.png

Nyt meidän täytyy liittää HUD toiminnallisuus Main skriptiimme. Tämä edellyttää muutamia lisäyksiä Main skeneen:

In the Node tab, connect the HUD’s start_game signal to the new_game() function of the Main node.

new_game() funktiossa, päivitä pistenäyttö ja näytä ”Get Ready” viesti:

$HUD.update_score(score)
$HUD.show_message("Get Ready")
var hud = GetNode<HUD>("HUD");
hud.UpdateScore(_score);
hud.ShowMessage("Get Ready!");

game_over() funktiossa meidän täytyy kutsua vastaavaa HUD funktiota:

$HUD.show_game_over()
GetNode<HUD>("HUD").ShowGameOver();

Lopuksi, lisää tämä _on_ScoreTimer_timeout() funktioon pitääksesi näytön synkassa muuttuvan pistemäärän kanssa:

$HUD.update_score(score)
GetNode<HUD>("HUD").UpdateScore(_score);

Nyt olet valmis pelaamaan! Napsauta ”Pelaa” painiketta. Sinua pyydetään valitsemaan pääskene, joten valitse Main.tscn.

Removing old creeps

If you play until ”Game Over” and then start a new game the creeps from the previous game are still on screen. It would be better if they all disappeared at the start of a new game.

We’ll use the start_game signal that’s already being emitted by the HUD node to remove the remaining creeps. We can’t use the editor to connect the signal to the mobs in the way we need because there are no Mob nodes in the Main scene tree until we run the game. Instead we’ll use code.

Start by adding a new function to Mob.gd. queue_free() will delete the current node at the end of the current frame.

func _on_start_game():
    queue_free()
public void OnStartGame()
{
    QueueFree();
}

Then in Main.gd add a new line inside the _on_MobTimer_timeout() function, at the end.

$HUD.connect("start_game", mob, "_on_start_game")
GetNode("HUD").Connect("StartGame", mobInstance, "OnStartGame");

This line tells the new Mob node (referenced by the mob variable) to respond to any start_game signal emitted by the HUD node by running its _on_start_game() function.

Viimeistely

Olemme saaneet valmiiksi kaiken toiminnallisuuden pelissämme. Alla on joitakin jäljellä olevia toimenpiteitä pelikokemuksen mehevöittämiseksi. Voit vapaasti laajentaa pelattavuutta omilla ideoillasi.

Tausta

Oletuksena oleva harmaa tausta ei ole kovinkaan puoleensavetävä, joten vaihdetaan sen väriä. Yksi tapa tehdä se, on käyttää ColorRect solmua. Tee siitä ensimmäinen solmu Main solmun alle, jotta se piirretään muiden solmujen alle. ColorRect solmulla on vain yksi ominaisuus: Color. Valitse haluamasi väri ja suurenna ColorRect solmua niin, että se kattaa koko ruudun.

Voit lisätä myös taustakuvan, jos sinulla on sellainen, käyttämällä Sprite solmua.

Ääniefektit

Äänet ja musiikki voivat olla kaikkein tehokkain tapa lisätä pelikokemuksen miellekkyyttä. Pelisi asset-kansiossa on kaksi äänitiedostoa: ”House In a Forest Loop.ogg” taustamusiikille ja ”gameover.wav” sitä varten, kun pelaaja häviää.

Lisää kaksi AudioStreamPlayer solmua Main solmun alle. Anna ensimmäiselle nimeksi Music ja toiselle DeathSound. Napsauta kummallekin Stream ominaisuutta, valitse ”Lataa” ja valitse nimeä vastaava äänitiedosto.

Soittaaksesi musiikkia, lisää $Music.play() new_game() funktioon ja $Music.stop() game_over() funktioon.

Lopuksi, lisää $DeathSound.play() game_over() funktioon.

Keyboard Shortcut

Since the game is played with keyboard controls, it would be convenient if we could also start the game by pressing a key on the keyboard. One way to do this is using the ”Shortcut” property of the Button node.

In the HUD scene, select the StartButton and find its Shortcut property in the Inspector. Select ”New Shortcut” and click on the ”Shortcut” item. A second Shortcut property will appear. Select ”New InputEventAction” and click the new ”InputEvent”. Finally, in the Action property, type the name ui_select. This is the default input event associated with the spacebar.

../../_images/start_button_shortcut.png

Now when the start button appears, you can either click it or press Space to start the game.

Projektin tiedostot

Löydät valmiin version tästä projektista näistä osoitteista: