Benutzer-Werkzeuge

Webseiten-Werkzeuge


theorie:programmieren

Programmieren Überblick

Der Computer macht ganz genau und ausschließlich das, was ihm gesagt wird. Das erfordert eine sehr viel präzisere Sprache, als wir es mit gesundem Menschenverstand gewöhnt sind: die Programmiersprache.

Da Mathematik eine ähnlich präzise Sprache ist, eignen sich Programme sehr gut zum Rechnen. Ich muss nur herausfinden, wie ich in der Programmiersprache meines Vertrauens eine Zahl speichern kann, und kann ein Ergebnis ausrechnen lassen.

int MeineZahl = 2 + 3;

Das „int“ sagt Programmiersprachen wie C# und Java zum Beispiel, dass ich eine Ganzzahl (englisch integer) namens „MeineZahl“ speichern möchte. Innerhalb des Namens darf ich keine Leerzeichen verwenden.

Der gespeicherte Wert soll 2+3 sein, also 5. Das rechnet das Programm für mich aus. Mit solchen Ergebnissen lässt sich auch weiter rechnen, beispielsweise können wir nochmals 4 dazuzählen:

int MeineZahl = 2 + 3;
MeineZahl += 4;

Die Schreibweise ist gewöhnungsbedürftig, aber eben eindeutig genug dass der Computer damit klar kommt. In der ersten Zeile steht das „int“, um dem Computer zu sagen dass ich eine Ganzzahl namens „MeineZahl“ speichern möchte. Wieder lasse ich den Wert 5 ausrechnen und speichere ihn.

In der zweiten Zeile kennt der Computer den Namen „MeineZahl“ schon, ich muss ihn also ohne den Zusatz benutzen, damit der Computer auch weiß dass er mit der bekannten 5 weiterrechnen soll und nicht einen neuen Speicherplatz sucht und mit einer frischen 0 neu zu rechnen beginnt.

Das „+=“ bedeutet dann „dazuzählen“, also 5+4. Im Speicher „MeineZahl“ steht also am Ende die Ganzzahl 9. Genauso kann ich „+=“ auch länger schreiben:

int MeineZahl = 2 + 3;
MeineZahl = MeineZahl + 4;

Die zweite Zeile heißt also, dass der Speicher „MeineZahl“ einen neuen Wert bekommen soll, nämlich den alten Wert aus „MeineZahl“ plus 4. Der alte Wert war 2+3=5, also rechnet der Computer wieder 5+4 und speichert am Ende die Ganzzahl 9.

Wenn ich jetzt keine Ganzzahl ausrechnen will, sondern ein Verhältnis, dann schreibe ich das etwas anders.

float MeineZahl = 0.5f;
double EineAndereZahl = 0.3;

Das alles nennt sich Rechnen mit Variablen. Ich kann den gespeicherten Wert verändern, also ist er variabel, daher der Name. Nein, die Informatiker konnten Variablen nicht einfach „Zahlen“ nennen, weil auch Text und komplexere Datenklumpen solche Variablen sein können.

string MeinText = "Hallo Welt!";
int AnzahlFragezeichen = 17;
MeinText += " Ich habe " + AnzahlFragezeichen + " Fragezeichen über meinem Kopf";

Hier sollte im Speicher „MeinText“ am Ende „Hallo Welt! Ich habe 17 Fragezeichen über meinem Kopf“ stehen. Das „string“ bezeichnet eine Zeichenkette, also einen Text. Sowohl „int“ als auch „string“, „float“ und „double“ sind Datentypen. Sie sagen dem Computer, wie viele Bytes er im Speicher reservieren soll und wie er damit rechnen kann. Jede Programmiersprache hat eine Liste von Basis Datentypen, auf denen alle anderen Datenklumpen aufbauen.

Ich soll also dem Computer sagen, welchen Datentyp ich speichern will, bevor ich ihn damit rechnen lasse. Gut. Und nun?

Vorhandene Variablen benutzen

In einem Spiel möchte ich ja nicht nur schicke Bytes im Speicher stehen haben, sondern es soll sich etwas am Bildschirm bewegen.

Nehmen wir an ich habe ein Unity Projekt und eine schicke Szene zusammengeklickt. Dabei hat Unity für jedes Element ein GameObject angelegt, das ist so ein komplexer Datenklumpen den wir benutzen dürfen. Dazu klicken wir in Unity "Skript Komponente hinzufügen" und uns wird ein praktisches Code Gerüst angelegt. Das dürfen wir zum Spielen benutzen. Wir recherchieren, dass die Position eines GameObject in der Transform-Komponente gespeichert wird und wir dessen Position verändern können, um unser Element herumzubewegen. In den Unity Docs steht dieser Beispiel Code:

using UnityEngine;
 
public class Example : MonoBehaviour
{
    // Moves all transform children 10 units upwards!
    void Start()
    {
        foreach (Transform child in transform)
        {
            child.position += Vector3.up * 10.0f;
        }
    }
}

Wenn wir alles ignorieren, was gerade noch völlig unverständlich ist, dann sollte diese Zeile übrig bleiben:

            child.position += Vector3.up * 10.0f;

Ein Datenklumpen namens „child“ hat also die Eigenschaft „position“, zu der ich einen Vector3, also einen dreidimensionalen Richtungspfeil, dazuzählen will. Das klingt ja schon mal nach herum bewegen. Ein bisschen Recherche später finden wir heraus, dass Vector3.up bedeutet (0,1,0), also aufwärts. Wenn wir (0,1,0) mal 10 rechnen, dann kommt (0, 10, 0) heraus. Wir wollen die Position also um 10 Schritte nach oben bewegen.

Und genau so hangelt man sich in der Dokumentation vorwärts. Von der kompletten Ahnungslosigkeit zu „das hab ich doch schon mal irgendwo gehört…“. Es ist für Menschen nicht möglich zu erraten, was mit „Vector3“ oder „Transform“ gemeint ist. Das sind Datenklumpen, die Unity erfunden hat. Wir haben dem Computer gesagt er soll Unity benutzen, also müssen wir in der Unity Dokumentation nachschauen wie sie ihre Variablen genannt haben und was wir mit ihnen machen können.

Listen durchgehen

Oben sind wir über das Schlüsselwort „foreach“ gestolpert. Zu deutsch „für jedes“. Es bedeutet „für jeden Listeneintrag in dieser Liste, mache das folgende…“

        foreach (Transform child in transform)
        {
            child.position += Vector3.up * 10.0f;
        }

Hierzu muss man wissen, dass in Unity ein Element in "transform" nicht nur die eigene Position speichern kann, sondern auch abhängige Elemente angehängt sein können. Diese „Kinder“ sind ihrerseits eine Liste von Transform-Datenklumpen, die eigene Kinder haben können. Unity macht es uns leicht, diese Liste der Kinder durchzugehen: „transform“ ist selbst die Liste seiner Kinder.

Wir sagen dem Computer also „für jeden Transform-Datenklumpen in der Liste namens 'transform', nenne den Transform-Datenklumpen 'child' und mache das folgende innerhalb der geschweiften Klammern“.

Wenn ich das Unity Element selbst bewegen will und nicht seine angehängten Kinder, dann schreibe ich das so:

using UnityEngine;
 
public class Example : MonoBehaviour
{
    // In a fixed time interval, move the object upwards
    void FixedUpdate()
    {
        transform.position += Vector3.up * 0.1f;
    }
}

Diesmal steht hier nicht Start(), sondern FixedUpdate(). Das sind vorgefertigte Einstiegspunkte, die uns Unity gibt um unseren Code irgendwo hin schreiben zu können, wo er auch im Programm ausgeführt wird. Die MonoBehaviour aus der UnityEngine ist dafür verantwortlich. Sobald ich so ein Skript zu einem Element in meiner Szene als Komponente hinzufüge, sorgt Unity dafür dass der Code zum Tragen kommt. Wir brauchen nur zu wissen, dass „Start“ ein mal ganz am Anfang ausgeführt wird, „Update“ jeden Frame und „FixedUpdate“ immer im gleichen zeitlichen Abstand.

Und dadurch haben wir schon alles, was wir brauchen, um unsere Szenen Elemente aus der Kamera schweben zu lassen!
Stöbere in den Docs, probiere herum und schau was passiert!

Was wäre wenn...?

Nun bestehen Programme natürlich nicht nur aus stupiden Berechnungen. Schaden berechnen können gut und schön, aber es soll ja etwas passieren wenn die Lebenspunkte unter 0 fallen!

int Lebenspunkte = 100;
int Schaden = 130;
Lebenspunkte -= Schaden;
 
if (Lebenspunkte <= 0)
{
    //Game Over
}
else
{
    //Weiter spielen
}

Wir ziehen von den Lebenspunkten den Schaden ab. Wenn die Lebenspunkte dabei unter oder auf 0 fallen, dann soll etwas anderes passieren als sonst. Dabei habe ich mit den beiden Schrägstrichen einen Platzhalter benutzt, die dem Computer sagen „ignoriere alles, was danach in dieser Zeile steht“. Ich kann also von Menschen lesbaren Text dahinter schreiben, um meinem zukünftigen Ich zu erklären, was ich mir an dieser Stelle gedacht habe. Später kann ich hier zum Beispiel einen Game Over Screen anzeigen oder eben nur einen Aua-Sound abspielen, je nach dem wie viele Lebenspunkte übrig sind.

Die geschweiften Klammern braucht der Computer, um zu wissen, was alles dazugehört das er machen soll wenn die Lebenspunkte auf / unter 0 sind. Das „else“ bedeutet „sonst“, also in diesem Fall „falls die Lebenspunkte größer 0 sind“.

Und weil wir die Lebenspunkte in unserem Spieler so speichern wollen, dass sich der Computer auch beim nächsten Update noch daran erinnert, speichern wir die Lebenspunkte als Eigenschaft des Beispiel Spielers. Das geht zum Beispiel so:

using UnityEngine;
 
public class ExamplePlayer : MonoBehaviour
{
    private int lebenspunkte;
 
    void Start()
    {
        this.lebenspunkte = 100;
    }
 
    void FixedUpdate()
    {
        //Hier prüfe ich eigentlich, ob der Spieler gerade irgendwo steht wo er Schaden nimmt.
        //Um das Beispiel kurz zu halten habe ich eine giftige Atmosphäre die ständig Schaden macht.
        int schaden = 13;
        lebenspunkte -= schaden;
 
        //Wenn der Spieler keine Lebenspunkte mehr hat, zum Game Over Screen wechseln.
        if (lebenspunkte <= 0)
        {
            //Game Over
        }
        else
        {
            //Lebenspunkte Anzeige aktualisieren
        }
    }
}

Selbst ausprobieren

Noch lustiger wird es dann natürlich mit User Input. Wenn du die Unity Doc Beispiele dazu benutzt, dann stelle bitte von Update auf FixedUpdate um und wirf den Time.deltaTime Krempel raus. FixedUpdate ist für saubere Bewegungen zuständig und regelt die vergangene Zeit selbst. Update ist für saubere Anzeige zuständig, nicht für Mechanik Berechnungen.

In unser Beispiel Spieler Skript eingebaut schaut das dann so aus:

using UnityEngine;
 
public class ExamplePlayer : MonoBehaviour
{
    private int lebenspunkte;
 
    public float speed = 10.0f;
    public float rotationSpeed = 100.0f;
 
    void Start()
    {
        this.lebenspunkte = 100;
    }
 
    void FixedUpdate()
    {
        // Get the horizontal and vertical axis.
        // By default they are mapped to the arrow keys.
        // The value is in the range -1 to 1
        float translation = Input.GetAxis("Vertical") * speed;
        float rotation = Input.GetAxis("Horizontal") * rotationSpeed;
 
        // Move translation along the object's z-axis
        transform.Translate(0, 0, translation);
 
        // Rotate around our y-axis
        transform.Rotate(0, rotation, 0);
 
 
        //Hier prüfe ich eigentlich, ob der Spieler gerade irgendwo steht wo er Schaden nimmt.
        //Um das Beispiel kurz zu halten habe ich eine giftige Atmosphäre die ständig Schaden macht.
        int schaden = 13;
        lebenspunkte -= schaden;
 
        //Wenn der Spieler keine Lebenspunkte mehr hat, zum Game Over Screen wechseln.
        if (lebenspunkte <= 0)
        {
            //Game Over
        }
        else
        {
            //Lebenspunkte Anzeige aktualisieren
        }
    }
}

Und so basteln wir am Code weiter und weiter. Für die ersten Minispiele ist es völlig in Ordnung, wenn noch alles im Player Skript steht. Je größer ein Spiel wird, desto komplizierter müssen wir den Code strukturieren. Also halte deine ersten Versuche klein und freunde dich mit dem Programmieren im Zusammenspiel mit Unity an.

Viel Spaß!

theorie/programmieren.txt · Zuletzt geändert: 2023/03/10 10:36 von spielzimmer

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki