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.
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
make einen Fehler, nennt es Datei und Zeile, und die alte Webseite bleibt, wie sie war. Lies die erste Fehlermeldung zuerst.printf ausgibt, steht in der Konsole der Seite: Fahre mit der Maus über das Bild und klicke unten rechts auf das Terminal-Symbol.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
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 |
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);
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.
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();
CAMERA_ORTHOGRAPHIC statt CAMERA_PERSPECTIVE werden entfernte Dinge nicht kleiner; fovy ist dann die Höhe des sichtbaren Bereichs in Einheiten.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).
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.
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 |
| 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.
| 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();
| 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;
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.
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.
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.
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.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.
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.