kind: reference

raylib

Die wichtigsten Befehle von raylib zum Nachschlagen: Fenster und Spielschleife, Kamera und Formen in 3D, Farben, Tastatur und Maus, Zeit, Vektoren, Zusammenstöße, eigene Flächen, Texturen und Shader für Licht. Die Beispiele sind in C und passen zur raylib-Vorlage, die im Workspace zu einer Webseite übersetzt wird.

1. Grundlagen

Übersetzen

raylib ist im Workspace schon installiert. In einem Ordner mit der raylib-Vorlage übersetzt make die Datei main.c in die Webseite main.html, die du mit »Go Live« in der Vorschau öffnest:

make                  # main.c → main.html
make PROGRAM=test     # test.c → test.html
  • Findet make einen Fehler, nennt es Datei und Zeile, und die alte Webseite bleibt, wie sie war. Lies die erste Fehlermeldung zuerst.
  • Was dein Programm mit printf ausgibt, steht in der Konsole der Seite: Fahre mit der Maus über das Bild und klicke unten rechts auf das Terminal-Symbol.
  • Dateien, die das Programm lesen soll, wie die Shader light.vs und light.fs, bettet das Makefile mit --embed-file in die Seite ein. Eine weitere Datei schreibst du in die Zeile mit den anderen.

Ohne Vorlage geht es auch mit einem einzigen Befehl:

emcc main.c -o main.html -lraylib -sUSE_GLFW=3 -sASYNCIFY -sSINGLE_FILE

Fenster und Spielschleife

Ein raylib-Programm öffnet ein Fenster und zeichnet dann in einer Schleife ein Bild nach dem anderen:

#include <raylib.h>

int main(void)
{
    InitWindow(960, 540, "Mein Spiel");   // Breite, Höhe, Titel
    SetTargetFPS(60);                      // höchstens 60 Bilder pro Sekunde

    while (!WindowShouldClose())
    {
        // 1. Eingaben lesen, Spiel weiterrechnen
        BeginDrawing();
        ClearBackground(RAYWHITE);
        // 2. zeichnen
        EndDrawing();
    }

    CloseWindow();
    return 0;
}
Befehl Bedeutung
InitWindow(width, height, title) Öffnet das Fenster, im Browser die Zeichenfläche
SetTargetFPS(n) Bremst die Schleife auf höchstens n Bilder pro Sekunde
WindowShouldClose() Wahr, wenn das Programm enden soll (Fenster zu oder Esc)
SetExitKey(KEY_NULL) Esc beendet das Programm nicht mehr
BeginDrawing(), EndDrawing() Rahmen um alles, was in einem Bild gezeichnet wird
ClearBackground(color) Füllt das ganze Bild mit einer Farbe
GetScreenWidth(), GetScreenHeight() Größe des Bildes in Pixeln
CloseWindow() Schließt das Fenster am Ende

Zeichnen in 2D

In 2D ist (0, 0) die Ecke oben links, x wächst nach rechts und y nach unten, gemessen in Pixeln. Was nach EndMode3D() gezeichnet wird, liegt über dem 3D-Bild, etwa ein Fadenkreuz oder eine Anzeige.

Befehl Bedeutung
DrawText(text, x, y, fontSize, color) Text, fontSize ist die Höhe in Pixeln
TextFormat("%d Punkte", score) Text mit Werten darin, wie printf
MeasureText(text, fontSize) Breite eines Textes in Pixeln, zum Zentrieren
DrawRectangle(x, y, width, height, color) Gefülltes Rechteck
DrawRectangleLinesEx((Rectangle){ x, y, width, height }, thickness, color) Rahmen eines Rechtecks
DrawCircle(x, y, radius, color) Gefüllter Kreis
DrawLine(x1, y1, x2, y2, color) Linie
DrawFPS(x, y) Zeigt die Bilder pro Sekunde
const char *text = TextFormat("Zeit: %.1f s", GetTime());
DrawText(text, (GetScreenWidth() - MeasureText(text, 20)) / 2, 10, 20, WHITE);

2. 3D

Koordinaten

Ein Punkt im Raum hat drei Koordinaten: x nach rechts, y nach oben, z nach vorne, zum Betrachter hin, wenn man von vorne auf die Welt schaut. Positionen und Richtungen sind Werte vom Typ Vector3:

Vector3 p = { 1, 0.5f, -2 };
p.y += 1;                                  // eins höher
DrawCube((Vector3){ 0, 0.5f, 0 }, 1, 1, 1, RED);

Vector2 hat nur x und y, etwa für die Maus.

Kamera

Camera3D camera = { 0 };
camera.position = (Vector3){ 6, 5, 8 };    // wo die Kamera steht
camera.target = (Vector3){ 0, 0, 0 };      // welchen Punkt sie anschaut
camera.up = (Vector3){ 0, 1, 0 };          // welche Richtung oben ist
camera.fovy = 45;                          // Blickwinkel nach oben und unten, in Grad
camera.projection = CAMERA_PERSPECTIVE;

BeginMode3D(camera);
DrawGrid(10, 1);
EndMode3D();
  • Mit CAMERA_ORTHOGRAPHIC statt CAMERA_PERSPECTIVE werden entfernte Dinge nicht kleiner; fovy ist dann die Höhe des sichtbaren Bereichs in Einheiten.
  • Schaut die Kamera genau in Richtung up (senkrecht nach unten oder oben), weiß raylib nicht, wie das Bild gedreht sein soll. Ein kleiner Versatz hilft: position = (0, 12, 0.1f).

UpdateCamera(&camera, mode) bewegt die Kamera selbst, einmal pro Bild aufgerufen:

Modus Bedeutung
CAMERA_ORBITAL Kreist langsam um target
CAMERA_FREE Freier Flug mit Maus und Tasten
CAMERA_FIRST_PERSON Laufen in Augenhöhe mit WASD und Maus
CAMERA_THIRD_PERSON Wie oben, die Kamera schaut von hinten auf die Figur

Für ein eigenes Spiel setzt du position und target meist selbst, zum Beispiel aus den Augen der Figur und ihrer Blickrichtung (siehe Vektoren).

Formen

Alle Formen werden zwischen BeginMode3D und EndMode3D gezeichnet. Positionen sind die Mitte der Form.

Befehl Bedeutung
DrawCube(position, width, height, length, color) Quader
DrawCubeV(position, size, color) Quader, Größe als Vector3
DrawCubeWires(position, width, height, length, color) Nur die Kanten
DrawSphere(center, radius, color) Kugel
DrawSphereWires(center, radius, rings, slices, color) Kugel als Gitter
DrawCylinder(position, radiusTop, radiusBottom, height, slices, color) Zylinder, mit Radius 0 oben ein Kegel
DrawCapsule(startPos, endPos, radius, slices, rings, color) Kapsel zwischen zwei Punkten
DrawPlane(center, (Vector2){ width, length }, color) Flache Platte auf dem Boden
DrawLine3D(startPos, endPos, color) Linie
DrawGrid(slices, spacing) Gitter auf dem Boden um (0, 0, 0)

Die Formen haben kein Licht: Alle Seiten eines Würfels haben dieselbe Farbe. Für Licht brauchst du eigene Flächen und einen Shader.

Farben

Eine Farbe ist ein Color mit Rot, Grün, Blau und Deckkraft, jeweils von 0 bis 255:

Color grass = { 96, 168, 64, 255 };
DrawCube(p, 1, 1, 1, (Color){ 255, 200, 80, 255 });

raylib kennt viele Farben mit Namen, unter anderem WHITE, BLACK, GRAY, DARKGRAY, LIGHTGRAY, RED, MAROON, ORANGE, GOLD, YELLOW, GREEN, LIME, DARKGREEN, SKYBLUE, BLUE, DARKBLUE, PURPLE, VIOLET, PINK, BEIGE, BROWN, DARKBROWN und RAYWHITE.

Befehl Bedeutung
Fade(color, 0.5f) Dieselbe Farbe, halb durchsichtig
ColorBrightness(color, -0.2f) Dunkler (negativ) oder heller (positiv), von −1 bis 1
ColorLerp(a, b, t) Mischung: bei t = 0 ist es a, bei 1 ist es b
ColorFromHSV(hue, saturation, value) Farbe aus Farbton (0 bis 360) und zwei Werten von 0 bis 1

3. Eingabe und Zeit

Tastatur

Befehl Wahr, …
IsKeyDown(KEY_W) solange die Taste gedrückt ist; für Bewegung
IsKeyPressed(KEY_SPACE) nur in dem Bild, in dem sie heruntergedrückt wurde; für einmalige Aktionen
IsKeyReleased(KEY_E) nur in dem Bild, in dem sie losgelassen wurde

Tasten heißen KEY_A bis KEY_Z, KEY_ZERO bis KEY_NINE, KEY_SPACE, KEY_ENTER, KEY_ESCAPE, KEY_TAB, KEY_LEFT_SHIFT, KEY_LEFT_CONTROL, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT und KEY_F1 bis KEY_F12. Die Ziffern liegen in einer Reihe: KEY_ONE + 2 ist KEY_THREE.

Im Browser bekommt das Programm die Tasten erst, wenn du einmal ins Bild geklickt hast.

Maus

Befehl Bedeutung
IsMouseButtonPressed(MOUSE_BUTTON_LEFT) Wahr im Bild des Klicks; auch MOUSE_BUTTON_RIGHT, MOUSE_BUTTON_MIDDLE
IsMouseButtonDown(…) Wahr, solange die Taste gedrückt ist
GetMousePosition() Position als Vector2, in Pixeln
GetMouseDelta() Wie weit sich die Maus seit dem letzten Bild bewegt hat
GetMouseWheelMove() Drehung des Mausrads, positiv nach oben
DisableCursor() Fängt die Maus: Der Zeiger verschwindet, jede Bewegung zählt
EnableCursor() Gibt die Maus wieder frei
IsCursorHidden() Wahr, solange die Maus gefangen ist

Im Browser darf eine Seite die Maus nur nach einem Klick fangen, und Esc gibt sie immer frei. Ruf DisableCursor() deshalb beim Klick ins Bild auf:

if (!IsCursorHidden() && IsMouseButtonPressed(MOUSE_BUTTON_LEFT))
    DisableCursor();

Zeit

Befehl Bedeutung
GetFrameTime() Sekunden seit dem letzten Bild, meist etwa 1/60
GetTime() Sekunden seit dem Start des Programms
GetFPS() Bilder pro Sekunde

Wer sich mit 4,5 Einheiten pro Sekunde bewegt, kommt in einem Bild 4.5f * dt weit. So ist ein Spiel auf jedem Computer gleich schnell:

float dt = GetFrameTime();
if (dt > 0.05f) dt = 0.05f;   // nach einer Pause keinen Riesensprung machen
x += 4.5f * dt;

Zufall

GetRandomValue(1, 6) ist eine ganze Zahl von 1 bis 6, beide eingeschlossen. Nach SetRandomSeed(42) kommen bei jedem Start dieselben »Zufallszahlen«, zum Beispiel für eine Welt, die immer gleich aussehen soll.

4. Rechnen im Raum

Vektoren

Die Rechenbefehle stehen in raymath.h:

#include <raymath.h>
Befehl Ergebnis
Vector3Add(a, b), Vector3Subtract(a, b) Summe, Differenz
Vector3Scale(v, s) v mal eine Zahl
Vector3Length(v) Länge
Vector3Normalize(v) Dieselbe Richtung mit Länge 1
Vector3Distance(a, b) Abstand zweier Punkte
Vector3DotProduct(a, b) Skalarprodukt: für Länge 1 der Kosinus des Winkels dazwischen
Vector3CrossProduct(a, b) Vektor, der senkrecht auf beiden steht
Vector3Lerp(a, b, t) Punkt zwischen a und b
Clamp(x, min, max) x, aber nicht kleiner als min und nicht größer als max

Eine Blickrichtung aus zwei Winkeln im Bogenmaß, yaw nach links und rechts und pitch nach oben und unten:

Vector3 look = {
    sinf(yaw) * cosf(pitch),
    sinf(pitch),
    cosf(yaw) * cosf(pitch),
};
camera.position = eyes;
camera.target = Vector3Add(eyes, look);

sinf und cosf kommen aus math.h. Eine halbe Umdrehung ist PI.

Zusammenstöße

Eine BoundingBox ist ein Quader mit den Achsen, beschrieben durch die Ecke mit den kleinsten (min) und die mit den größten Koordinaten (max):

BoundingBox player = { { x - 0.3f, y, z - 0.3f }, { x + 0.3f, y + 1.7f, z + 0.3f } };
BoundingBox crate = { { 2, 0, 2 }, { 3, 1, 3 } };
if (CheckCollisionBoxes(player, crate)) { /* … */ }
Befehl Wahr, wenn …
CheckCollisionBoxes(a, b) sich zwei Quader überschneiden
CheckCollisionSpheres(center1, radius1, center2, radius2) sich zwei Kugeln berühren
CheckCollisionBoxSphere(box, center, radius) eine Kugel den Quader berührt

Ein Strahl (Ray) hat einen Anfang und eine Richtung der Länge 1. GetRayCollisionBox(ray, box) sagt, ob und wo er einen Quader trifft:

Ray ray = { eyes, look };
RayCollision collision = GetRayCollisionBox(ray, crate);
if (collision.hit && collision.distance < 6) { /* collision.point, collision.normal */ }

In einer Welt aus einem Gitter von Blöcken ist es oft einfacher, die Felder selbst zu prüfen: floorf(x) ist das Feld, in dem die Koordinate x liegt.

5. Licht

Eigene Flächen

Mit rlgl.h schickst du der Grafikkarte die Ecken selbst. Zu jeder Ecke gehören eine Farbe und eine Normale, ein Vektor der Länge 1, der senkrecht aus der Fläche zeigt. Die Normale braucht ein Shader für das Licht.

#include <rlgl.h>

// Die Oberseite eines Blocks bei (x, y, z): ein Viereck in der Höhe y + 1
rlBegin(RL_QUADS);
rlColor4ub(96, 168, 64, 255);
rlNormal3f(0, 1, 0);
rlVertex3f(x,     y + 1, z);
rlVertex3f(x,     y + 1, z + 1);
rlVertex3f(x + 1, y + 1, z + 1);
rlVertex3f(x + 1, y + 1, z);
rlEnd();
  • RL_QUADS macht aus je vier Ecken ein Viereck, RL_TRIANGLES aus je drei ein Dreieck, RL_LINES aus je zwei eine Linie.
  • Von außen gesehen müssen die Ecken gegen den Uhrzeigersinn laufen. Die Grafikkarte zeichnet nur Vorderseiten; in der falschen Reihenfolge ist die Fläche von außen unsichtbar.
  • Farbe und Normale gelten für alle folgenden Ecken, bis du sie änderst.

Texturen

Eine Textur ist ein Bild, das die Grafikkarte auf Flächen klebt. Im Browser muss das Bild in die Webseite eingebettet sein; die raylib-Vorlage bettet textures.png ein.

Texture2D texture = LoadTexture("textures.png");
SetTextureFilter(texture, TEXTURE_FILTER_POINT);   // scharfe Pixel statt weich
// …
UnloadTexture(texture);                            // am Ende

In 2D zeichnet DrawTexture(texture, x, y, WHITE) das ganze Bild, DrawTextureRec(texture, (Rectangle){ sx, sy, width, height }, (Vector2){ x, y }, WHITE) einen Ausschnitt. Auf eigenen Flächen bekommt jede Ecke Texturkoordinaten u und v von 0 bis 1: u = 0 ist der linke Rand des Bildes, u = 1 der rechte, v = 0 oben, v = 1 unten.

rlSetTexture(texture.id);
rlBegin(RL_QUADS);
rlColor4ub(255, 255, 255, 255);   // Weiß lässt die Farben der Textur, wie sie sind
rlNormal3f(0, 0, 1);
rlTexCoord2f(0, 1); rlVertex3f(0, 0, 1);   // links unten
rlTexCoord2f(1, 1); rlVertex3f(1, 0, 1);   // rechts unten
rlTexCoord2f(1, 0); rlVertex3f(1, 1, 1);   // rechts oben
rlTexCoord2f(0, 0); rlVertex3f(0, 1, 1);   // links oben
rlEnd();
rlSetTexture(0);

Liegen mehrere Bilder nebeneinander in einem Streifen (ein Atlas), reicht Bild Nummer k von acht von u = k / 8 bis u = (k + 1) / 8. Ein eigener Shader liest die Textur mit uniform sampler2D texture0; und texture2D(texture0, uv); die Koordinaten kommen im Vertex-Shader als attribute vec2 vertexTexCoord.

Shader

Ein Shader ist ein kleines Programm für die Grafikkarte, geschrieben in GLSL. Der Vertex-Shader läuft für jede Ecke, der Fragment-Shader für jeden Pixel einer Fläche. Im C-Programm:

Shader light = LoadShader("light.vs", "light.fs");
int loc_sun = GetShaderLocation(light, "sun");   // vor der Schleife

// in der Schleife
Vector3 sun = Vector3Normalize((Vector3){ 0.5f, 0.8f, 0.3f });
SetShaderValue(light, loc_sun, &sun, SHADER_UNIFORM_VEC3);

BeginMode3D(camera);
BeginShaderMode(light);
// … Flächen zeichnen …
EndShaderMode();
EndMode3D();

UnloadShader(light);   // am Ende
Typ Für
SHADER_UNIFORM_FLOAT float
SHADER_UNIFORM_INT int
SHADER_UNIFORM_VEC3 Vector3, im Shader vec3
SHADER_UNIFORM_VEC4 vier Zahlen, im Shader vec4

Eine Liste übergibt SetShaderValueV(shader, locIndex, values, uniformType, count). Ist ein Name falsch geschrieben, liefert GetShaderLocation −1, und der Wert kommt nie an, ohne Fehlermeldung. Fehler im Shader selbst meldet raylib in der Konsole der Seite.

Im Browser verstehen Shader die Version #version 100 von GLSL:

#version 100
precision mediump float;

varying vec3 normal;        // vom Vertex-Shader, für jeden Pixel gemischt
varying vec4 color;
uniform vec3 sun;          // vom C-Programm, für alle Pixel gleich

void main()
{
    float sunAmount = max(dot(normalize(normal), sun), 0.0);
    gl_FragColor = vec4(color.rgb * (0.4 + 0.6 * sunAmount), 1.0);
}
GLSL Bedeutung
float, int, vec3, vec4 Zahl, ganze Zahl, drei und vier Zahlen
dot(a, b) Skalarprodukt
normalize(v), length(v) Länge 1, Länge
max(a, b), min(a, b), clamp(x, a, b) Grenzen
mix(a, b, t) Mischung zwischen a und b
gl_FragColor Die Farbe, die der Pixel am Ende bekommt

Eine Schleife braucht in #version 100 eine feste Obergrenze: for (int i = 0; i < 16; i++) und darin if (i >= count) break;. Zahlen mit Komma schreibst du immer mit Punkt und Nachkommastelle, 1.0 statt 1.