Watch
2
0
Fork
You've already forked raylib-cs
0

chore: clean recommit

This commit is contained in:
tiger tiger tiger 2026-07-07 23:47:38 +02:00
commit 60ad2e7fb1
122 changed files with 23950 additions and 323 deletions

1
.gitignore vendored
View file

@ -63,6 +63,7 @@ project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json
dotnet-tools.json
*_i.c
*_p.c

View file

@ -0,0 +1,263 @@
/*******************************************************************************************
*
* raylib [audio] example - amp envelope
*
* Example complexity rating: [] 1/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by Arbinda Rizki Muhammad (@arbipink) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2026 Arbinda Rizki Muhammad (@arbipink)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Audio;
public unsafe partial class AmpEnvelope : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int BUFFER_SIZE = 4096;
private const int SAMPLE_RATE = 44100;
// Wave state
private enum ADSRState
{
Idle,
Attack,
Decay,
Sustain,
Release
}
// Grouping all ADSR parameters and state into a struct
private struct Envelope
{
public float AttackTime;
public float DecayTime;
public float SustainLevel;
public float ReleaseTime;
public float CurrentValue;
public ADSRState State;
}
public string Name => "Audio / Amp Envelope";
public string Title => "raylib [audio] example - amp envelope";
private float[] buffer;
private AudioStream stream;
private float audioTime;
private Envelope env;
public void Init()
{
InitAudioDevice();
// Set the number of samples the stream will keep in memory at a time to BUFFER_SIZE
SetAudioStreamBufferSizeDefault(BUFFER_SIZE);
buffer = new float[BUFFER_SIZE];
// Init raw audio stream (sample rate: 44100, sample size: 32bit-float, channels: 1-mono)
stream = LoadAudioStream(SAMPLE_RATE, 32, 1);
// Init Phase
audioTime = 0.0f;
// Initialize the struct
env = new Envelope
{
AttackTime = 1.0f,
DecayTime = 1.0f,
SustainLevel = 0.5f,
ReleaseTime = 1.0f,
CurrentValue = 0.0f,
State = ADSRState.Idle
};
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
if (IsKeyPressed(KeyboardKey.Space)) env.State = ADSRState.Attack;
if (IsKeyReleased(KeyboardKey.Space) && (env.State != ADSRState.Idle)) env.State = ADSRState.Release;
if (IsAudioStreamProcessed(stream))
{
if ((env.State != ADSRState.Idle) || (env.CurrentValue > 0.0f))
{
for (int i = 0; i < BUFFER_SIZE; i++)
{
UpdateEnvelope(ref env);
FillAudioBuffer(i, buffer, env.CurrentValue, ref audioTime);
}
}
else
{
// Clear buffer if silent to avoid looping noise
for (int i = 0; i < BUFFER_SIZE; i++) buffer[i] = 0;
audioTime = 0.0f;
}
fixed (float* bufferPtr = buffer)
{
UpdateAudioStream(stream, bufferPtr, BUFFER_SIZE);
}
}
if (!IsAudioStreamPlaying(stream)) PlayAudioStream(stream);
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
// NOTE: raygui is not bound in raylib-cs, so the sliders below are left as reference.
// The envelope keeps its default parameters (Attack/Decay/Release = 1.0s, Sustain = 0.5).
//GuiSliderBar(new Rectangle( 100, 60, 400, 30 ), "Attack (s)", TextFormat("%2.2fs", env.AttackTime), ref env.AttackTime, 0.1f, 3.0f);
//GuiSliderBar(new Rectangle( 100, 100, 400, 30 ), "Decay (s)", TextFormat("%2.2fs", env.DecayTime), ref env.DecayTime, 0.1f, 3.0f);
//GuiSliderBar(new Rectangle( 100, 140, 400, 30 ), "Sustain", TextFormat("%2.2f", env.SustainLevel), ref env.SustainLevel, 0.0f, 1.0f);
//GuiSliderBar(new Rectangle( 100, 180, 400, 30 ), "Release (s)", TextFormat("%2.2fs", env.ReleaseTime), ref env.ReleaseTime, 0.1f, 3.0f);
DrawADSRGraph(ref env, new Rectangle(100, 250, 400, 100));
DrawCircleV(new Vector2(520, 350 - (env.CurrentValue * 100)), 5, Color.Maroon);
DrawText($"Current Gain: {env.CurrentValue:F2}", 535, (int)(345 - (env.CurrentValue * 100)), 10, Color.Maroon);
DrawText("Press SPACE to PLAY the sound!", 200, 400, 20, Color.LightGray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadAudioStream(stream);
CloseAudioDevice();
}
//------------------------------------------------------------------------------------
// Module Functions Definition
//------------------------------------------------------------------------------------
private static void FillAudioBuffer(int i, float[] buffer, float envelopeValue, ref float audioTime)
{
int frequency = 440;
buffer[i] = envelopeValue * MathF.Sin(2.0f * MathF.PI * frequency * audioTime);
audioTime += (1.0f / SAMPLE_RATE);
}
private static void UpdateEnvelope(ref Envelope env)
{
// Calculate the time delta for ONE sample (1/44100)
float sampleTime = 1.0f / SAMPLE_RATE;
switch (env.State)
{
case ADSRState.Attack:
{
env.CurrentValue += (1.0f / env.AttackTime) * sampleTime;
if (env.CurrentValue >= 1.0f)
{
env.CurrentValue = 1.0f;
env.State = ADSRState.Decay;
}
}
break;
case ADSRState.Decay:
{
env.CurrentValue -= ((1.0f - env.SustainLevel) / env.DecayTime) * sampleTime;
if (env.CurrentValue <= env.SustainLevel)
{
env.CurrentValue = env.SustainLevel;
env.State = ADSRState.Sustain;
}
}
break;
case ADSRState.Sustain:
{
env.CurrentValue = env.SustainLevel;
}
break;
case ADSRState.Release:
{
env.CurrentValue -= (env.SustainLevel / env.ReleaseTime) * sampleTime;
if (env.CurrentValue <= 0.001f) // Use a small threshold to avoid infinite tail
{
env.CurrentValue = 0.0f;
env.State = ADSRState.Idle;
}
}
break;
default: break;
}
}
private static void DrawADSRGraph(ref Envelope env, Rectangle bounds)
{
DrawRectangleRec(bounds, Fade(Color.LightGray, 0.3f));
DrawRectangleLinesEx(bounds, 1, Color.Gray);
// Fixed visual width for sustain stage since it's an amplitude not a time value
float sustainWidth = 1.0f;
// Total time to visualize (sum of A, D, R + a padding for Sustain)
float totalTime = env.AttackTime + env.DecayTime + sustainWidth + env.ReleaseTime;
float scaleX = bounds.Width / totalTime;
float scaleY = bounds.Height;
Vector2 start = new Vector2(bounds.X, bounds.Y + bounds.Height);
Vector2 peak = new Vector2(start.X + (env.AttackTime * scaleX), bounds.Y);
Vector2 sustain = new Vector2(peak.X + (env.DecayTime * scaleX), bounds.Y + (1.0f - env.SustainLevel) * scaleY);
Vector2 rel = new Vector2(sustain.X + (sustainWidth * scaleX), sustain.Y);
Vector2 end = new Vector2(rel.X + (env.ReleaseTime * scaleX), bounds.Y + bounds.Height);
DrawLineV(start, peak, Color.SkyBlue);
DrawLineV(peak, sustain, Color.Blue);
DrawLineV(sustain, rel, Color.DarkBlue);
DrawLineV(rel, end, Color.Orange);
DrawText("ADSR Visualizer", (int)bounds.X, (int)bounds.Y - 20, 10, Color.DarkGray);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [audio] example - amp envelope");
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new AmpEnvelope();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,159 @@
/*******************************************************************************************
*
* raylib [audio] example - mixed processor
*
* Example complexity rating: [] 4/4
*
* Example originally created with raylib 4.2, last time updated with raylib 4.2
*
* Example contributed by hkc (@hatkidchan) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2023-2025 hkc (@hatkidchan)
*
********************************************************************************************/
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static Raylib_cs.Raylib;
namespace Examples.Audio;
[ExcludeFromBrowser("AttachAudioMixedProcessor callback is unreliable on the wasm audio backend")]
public unsafe partial class MixedProcessor : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Audio / Mixed Processor";
public string Title => "raylib [audio] example - mixed processor";
private static float exponent = 1.0f; // Audio exponentiation value
private static readonly float[] averageVolume = new float[400]; // Average volume history
private Music music;
private Sound sound;
//------------------------------------------------------------------------------------
// Audio processing function
//------------------------------------------------------------------------------------
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static void ProcessAudio(void* buffer, uint frames)
{
float* samples = (float*)buffer; // Samples internally stored as <float>s
float average = 0.0f; // Temporary average volume
for (uint frame = 0; frame < frames; frame++)
{
float* left = &samples[frame * 2 + 0];
float* right = &samples[frame * 2 + 1];
*left = MathF.Pow(MathF.Abs(*left), exponent) * ((*left < 0.0f) ? -1.0f : 1.0f);
*right = MathF.Pow(MathF.Abs(*right), exponent) * ((*right < 0.0f) ? -1.0f : 1.0f);
average += MathF.Abs(*left) / frames; // accumulating average volume
average += MathF.Abs(*right) / frames;
}
// Moving history to the left
for (int i = 0; i < 399; i++) averageVolume[i] = averageVolume[i + 1];
averageVolume[399] = average; // Adding last average value
}
public void Init()
{
InitAudioDevice(); // Initialize audio device
exponent = 1.0f;
Array.Clear(averageVolume, 0, averageVolume.Length);
AttachAudioMixedProcessor(&ProcessAudio);
music = LoadMusicStream("resources/audio/country.mp3");
sound = LoadSound("resources/audio/coin.wav");
PlayMusicStream(music);
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateMusicStream(music); // Update music buffer with new stream data
// Modify processing variables
//----------------------------------------------------------------------------------
if (IsKeyPressed(KeyboardKey.Left)) exponent -= 0.05f;
if (IsKeyPressed(KeyboardKey.Right)) exponent += 0.05f;
if (exponent <= 0.5f) exponent = 0.5f;
if (exponent >= 3.0f) exponent = 3.0f;
if (IsKeyPressed(KeyboardKey.Space)) PlaySound(sound);
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
DrawText("MUSIC SHOULD BE PLAYING!", 255, 150, 20, Color.LightGray);
DrawText($"EXPONENT = {exponent:F2}", 215, 180, 20, Color.LightGray);
DrawRectangle(199, 199, 402, 34, Color.LightGray);
for (int i = 0; i < 400; i++)
{
DrawLine(201 + i, 232 - (int)(averageVolume[i] * 32), 201 + i, 232, Color.Maroon);
}
DrawRectangleLines(199, 199, 402, 34, Color.Gray);
DrawText("PRESS SPACE TO PLAY OTHER SOUND", 200, 250, 20, Color.LightGray);
DrawText("USE LEFT AND RIGHT ARROWS TO ALTER DISTORTION", 140, 280, 20, Color.LightGray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadMusicStream(music); // Unload music stream buffers from RAM
DetachAudioMixedProcessor(&ProcessAudio); // Disconnect audio processor
CloseAudioDevice(); // Close audio device (music streaming is automatically stopped)
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [audio] example - mixed processor");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new MixedProcessor();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

182
Examples/Audio/RawStream.cs Normal file
View file

@ -0,0 +1,182 @@
/*******************************************************************************************
*
* raylib [audio] example - raw stream
*
* Example complexity rating: [] 3/4
*
* Example originally created with raylib 1.6, last time updated with raylib 6.0
*
* Example created by Ramon Santamaria (@raysan5) and reviewed by James Hofmann (@triplefox)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2015-2026 Ramon Santamaria (@raysan5) and James Hofmann (@triplefox)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Audio;
public unsafe partial class RawStream : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int BUFFER_SIZE = 4096;
private const int SAMPLE_RATE = 44100;
public string Name => "Audio / Raw Stream";
public string Title => "raylib [audio] example - raw stream";
public int TargetFps => 30;
private float[] buffer;
private AudioStream stream;
private float pan;
private int sineFrequency;
private int newSineFrequency;
private int sineIndex;
private double sineStartTime;
public void Init()
{
InitAudioDevice();
// Set the number of samples the stream will keep in memory at a time to BUFFER_SIZE
SetAudioStreamBufferSizeDefault(BUFFER_SIZE);
buffer = new float[BUFFER_SIZE];
// Init raw audio stream (sample rate: 44100, sample size: 32bit-float, channels: 1-mono)
stream = LoadAudioStream(SAMPLE_RATE, 32, 1);
pan = 0.0f;
SetAudioStreamPan(stream, pan);
PlayAudioStream(stream);
sineFrequency = 440;
newSineFrequency = 440;
sineIndex = 0;
sineStartTime = 0.0;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
if (IsKeyDown(KeyboardKey.Up))
{
newSineFrequency += 10;
if (newSineFrequency > 12500) newSineFrequency = 12500;
}
if (IsKeyDown(KeyboardKey.Down))
{
newSineFrequency -= 10;
if (newSineFrequency < 20) newSineFrequency = 20;
}
if (IsKeyDown(KeyboardKey.Left))
{
pan -= 0.01f;
if (pan < -1.0f) pan = -1.0f;
SetAudioStreamPan(stream, pan);
}
if (IsKeyDown(KeyboardKey.Right))
{
pan += 0.01f;
if (pan > 1.0f) pan = 1.0f;
SetAudioStreamPan(stream, pan);
}
if (IsAudioStreamProcessed(stream))
{
for (int i = 0; i < BUFFER_SIZE; i++)
{
int wl = SAMPLE_RATE / sineFrequency;
buffer[i] = MathF.Sin(2 * MathF.PI * sineIndex / wl);
sineIndex++;
if (sineIndex >= wl)
{
sineFrequency = newSineFrequency;
sineIndex = 0;
sineStartTime = GetTime();
}
}
fixed (float* bufferPtr = buffer)
{
UpdateAudioStream(stream, bufferPtr, BUFFER_SIZE);
}
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
DrawText($"sine frequency: {sineFrequency}", screenWidth - 220, 10, 20, Color.Red);
DrawText($"pan: {pan:F2}", screenWidth - 220, 30, 20, Color.Red);
DrawText("Up/down to change frequency", 10, 10, 20, Color.DarkGray);
DrawText("Left/right to pan", 10, 30, 20, Color.DarkGray);
int windowStart = (int)((GetTime() - sineStartTime) * SAMPLE_RATE);
int windowSize = (int)(0.1f * SAMPLE_RATE);
int wavelength = SAMPLE_RATE / sineFrequency;
// Draw a sine wave with the same frequency as the one being sent to the audio stream
for (int i = 0; i < screenWidth; i++)
{
int t0 = windowStart + i * windowSize / screenWidth;
int t1 = windowStart + (i + 1) * windowSize / screenWidth;
Vector2 startPos = new Vector2(i, 250 + 50 * MathF.Sin(2 * MathF.PI * t0 / wavelength));
Vector2 endPos = new Vector2(i + 1, 250 + 50 * MathF.Sin(2 * MathF.PI * t1 / wavelength));
DrawLineV(startPos, endPos, Color.Red);
}
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadAudioStream(stream); // Close raw audio stream and delete buffers from RAM
CloseAudioDevice(); // Close audio device (music streaming is automatically stopped)
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [audio] example - raw stream");
SetTargetFPS(30);
//--------------------------------------------------------------------------------------
var game = new RawStream();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,114 @@
/*******************************************************************************************
*
* raylib [audio] example - sound multi
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 5.0, last time updated with raylib 5.0
*
* Example contributed by Jeffery Myers (@JeffM2501) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2023-2025 Jeffery Myers (@JeffM2501)
*
********************************************************************************************/
using static Raylib_cs.Raylib;
namespace Examples.Audio;
public partial class SoundMulti : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int MAX_SOUNDS = 10;
public string Name => "Audio / Sound Multi";
public string Title => "raylib [audio] example - sound multi";
private Sound[] soundArray = new Sound[MAX_SOUNDS];
private int currentSound;
public void Init()
{
InitAudioDevice(); // Initialize audio device
// Load audio file into the first slot as the 'source' sound,
// this sound owns the sample data
soundArray[0] = LoadSound("resources/audio/sound.wav");
// Load an alias of the sound into slots 1-9. These do not own the sound data, but can be played
for (int i = 1; i < MAX_SOUNDS; i++) soundArray[i] = LoadSoundAlias(soundArray[0]);
currentSound = 0; // Set the sound list to the start
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
if (IsKeyPressed(KeyboardKey.Space))
{
PlaySound(soundArray[currentSound]); // Play the next open sound slot
currentSound++; // Increment the sound slot
// If the sound slot is out of bounds, go back to 0
if (currentSound >= MAX_SOUNDS) currentSound = 0;
// NOTE: Another approach would be to look at the list for the first sound
// that is not playing and use that slot
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
DrawText("Press SPACE to PLAY a WAV sound!", 200, 180, 20, Color.LightGray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
for (int i = 1; i < MAX_SOUNDS; i++) UnloadSoundAlias(soundArray[i]); // Unload sound aliases
UnloadSound(soundArray[0]); // Unload source sound data
CloseAudioDevice(); // Close audio device
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [audio] example - sound multi");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new SoundMulti();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,154 @@
/*******************************************************************************************
*
* raylib [audio] example - sound positioning
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 5.5, last time updated with raylib 5.5
*
* Example contributed by Le Juez Victor (@Bigfoot71) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Le Juez Victor (@Bigfoot71)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Audio;
public partial class SoundPositioning : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Audio / Sound Positioning";
public string Title => "raylib [audio] example - sound positioning";
public bool CursorDisabled => true;
private Sound sound;
private Camera3D camera;
public void Init()
{
InitAudioDevice();
sound = LoadSound("resources/audio/coin.wav");
camera = new Camera3D
{
Position = new Vector3(0, 5, 5),
Target = new Vector3(0, 0, 0),
Up = new Vector3(0, 1, 0),
FovY = 60,
Projection = CameraProjection.Perspective
};
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(ref camera, CameraMode.Free);
float th = (float)GetTime();
Vector3 spherePos = new Vector3(
5.0f * MathF.Cos(th),
0.0f,
5.0f * MathF.Sin(th)
);
SetSoundPosition(camera, sound, spherePos, 1.0f);
if (!IsSoundPlaying(sound)) PlaySound(sound);
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode3D(camera);
DrawGrid(10, 2);
DrawSphere(spherePos, 0.5f, Color.Red);
EndMode3D();
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadSound(sound);
CloseAudioDevice(); // Close audio device
}
//------------------------------------------------------------------------------------
// Module Functions Definition
//------------------------------------------------------------------------------------
// Set sound 3d position
private static void SetSoundPosition(Camera3D listener, Sound sound, Vector3 position, float maxDist)
{
// Calculate direction vector and distance between listener and sound source
Vector3 direction = Vector3.Subtract(position, listener.Position);
float distance = direction.Length();
// Apply logarithmic distance attenuation and clamp between 0-1
float attenuation = 1.0f / (1.0f + (distance / maxDist));
attenuation = Math.Clamp(attenuation, 0.0f, 1.0f);
// Calculate normalized vectors for spatial positioning
Vector3 normalizedDirection = Vector3.Normalize(direction);
Vector3 forward = Vector3.Normalize(Vector3.Subtract(listener.Target, listener.Position));
Vector3 right = Vector3.Normalize(Vector3.Cross(listener.Up, forward));
// Reduce volume for sounds behind the listener
float dotProduct = Vector3.Dot(forward, normalizedDirection);
if (dotProduct < 0.0f) attenuation *= (1.0f + dotProduct * 0.5f);
// Set stereo panning based on sound position relative to listener
float pan = 0.5f + 0.5f * Vector3.Dot(normalizedDirection, right);
// Apply final sound properties
SetSoundVolume(sound, attenuation);
SetSoundPan(sound, pan);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [audio] example - sound positioning");
DisableCursor();
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new SoundPositioning();
game.Init();
// Main game loop
while (!WindowShouldClose())
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,306 @@
/*******************************************************************************************
*
* raylib [audio] example - stream callback
*
* Example complexity rating: [] 3/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example created by Dan Hoang (@dan-hoang) and reviewed by Ramon Santamaria (@raysan5)
*
* NOTE: Example sends a wave to the audio device,
* user gets the choice of four waves: sine, square, triangle, and sawtooth
* A stream is set up to play to the audio device; stream is hooked to a callback that
* generates a wave, that is determined by user choice
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2026 Dan Hoang (@dan-hoang)
*
********************************************************************************************/
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static Raylib_cs.Raylib;
namespace Examples.Audio;
[ExcludeFromBrowser("SetAudioStreamCallback is unreliable on the wasm audio backend")]
public unsafe partial class StreamCallback : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int BUFFER_SIZE = 4096;
private const int SAMPLE_RATE = 44100;
// Wave type
private enum WaveType
{
Sine,
Square,
Triangle,
Sawtooth
}
public string Name => "Audio / Stream Callback";
public string Title => "raylib [audio] example - stream callback";
public int TargetFps => 30;
private static int waveFrequency = 440;
private static int newWaveFrequency = 440;
private static int waveIndex = 0;
// Buffer to keep the last second of uploaded audio,
// part of which will be drawn on the screen
private static readonly float[] buffer = new float[SAMPLE_RATE];
private static readonly string[] waveTypesAsString = { "sine", "square", "triangle", "sawtooth" };
private AudioStream stream;
private WaveType waveType;
public void Init()
{
InitAudioDevice();
waveFrequency = 440;
newWaveFrequency = 440;
waveIndex = 0;
Array.Clear(buffer, 0, buffer.Length);
// Set the number of samples the stream will keep in memory at a time to BUFFER_SIZE
SetAudioStreamBufferSizeDefault(BUFFER_SIZE);
// Init raw audio stream (sample rate: 44100, sample size: 32bit-float, channels: 1-mono)
stream = LoadAudioStream(SAMPLE_RATE, 32, 1);
PlayAudioStream(stream);
// Configure it so that the callback for waveType is called whenever stream is out of samples
waveType = WaveType.Sine;
SetWaveCallback();
}
// Attach the callback matching the current waveType to the stream
private void SetWaveCallback()
{
switch (waveType)
{
case WaveType.Sine: SetAudioStreamCallback(stream, &SineCallback); break;
case WaveType.Square: SetAudioStreamCallback(stream, &SquareCallback); break;
case WaveType.Triangle: SetAudioStreamCallback(stream, &TriangleCallback); break;
case WaveType.Sawtooth: SetAudioStreamCallback(stream, &SawtoothCallback); break;
}
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
if (IsKeyDown(KeyboardKey.Up))
{
newWaveFrequency += 10;
if (newWaveFrequency > 12500) newWaveFrequency = 12500;
}
if (IsKeyDown(KeyboardKey.Down))
{
newWaveFrequency -= 10;
if (newWaveFrequency < 20) newWaveFrequency = 20;
}
if (IsKeyPressed(KeyboardKey.Left))
{
if (waveType == WaveType.Sine) waveType = WaveType.Sawtooth;
else if (waveType == WaveType.Square) waveType = WaveType.Sine;
else if (waveType == WaveType.Triangle) waveType = WaveType.Square;
else waveType = WaveType.Triangle;
SetWaveCallback();
}
if (IsKeyPressed(KeyboardKey.Right))
{
if (waveType == WaveType.Sine) waveType = WaveType.Square;
else if (waveType == WaveType.Square) waveType = WaveType.Triangle;
else if (waveType == WaveType.Triangle) waveType = WaveType.Sawtooth;
else waveType = WaveType.Sine;
SetWaveCallback();
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
DrawText($"frequency: {newWaveFrequency}", screenWidth - 220, 10, 20, Color.Red);
DrawText($"wave type: {waveTypesAsString[(int)waveType]}", screenWidth - 220, 30, 20, Color.Red);
DrawText("Up/down to change frequency", 10, 10, 20, Color.DarkGray);
DrawText("Left/right to change wave type", 10, 30, 20, Color.DarkGray);
// Draw the last 10 ms of uploaded audio
for (int i = 0; i < screenWidth; i++)
{
Vector2 startPos = new Vector2(i, 250 - 50 * buffer[WaveSampleIndex(i)]);
Vector2 endPos = new Vector2(i + 1, 250 - 50 * buffer[WaveSampleIndex(i + 1)]);
DrawLineV(startPos, endPos, Color.Red);
}
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadAudioStream(stream); // Close raw audio stream and delete buffers from RAM
CloseAudioDevice(); // Close audio device (music streaming is automatically stopped)
}
// Maps a screen column to a sample index in the last 10 ms of the buffer. The final column
// maps one sample past the end of the buffer; upstream C reads out of bounds there, so we
// clamp to the last valid sample (visually identical, but safe under C# bounds checking).
private static int WaveSampleIndex(int column)
{
int index = SAMPLE_RATE - SAMPLE_RATE / 100 + column * SAMPLE_RATE / 100 / screenWidth;
return index < SAMPLE_RATE ? index : SAMPLE_RATE - 1;
}
//------------------------------------------------------------------------------------
// Module Functions Definition
//------------------------------------------------------------------------------------
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static void SineCallback(void* framesOut, uint frameCount)
{
int fc = (int)frameCount;
int wavelength = SAMPLE_RATE / waveFrequency;
float* frames = (float*)framesOut;
// Synthesize the sine wave
for (int i = 0; i < fc; i++)
{
frames[i] = MathF.Sin(2 * MathF.PI * waveIndex / wavelength);
waveIndex++;
if (waveIndex >= wavelength)
{
waveFrequency = newWaveFrequency;
waveIndex = 0;
}
}
// Save the synthesized samples for later drawing
for (int i = 0; i < SAMPLE_RATE - fc; i++) buffer[i] = buffer[i + fc];
for (int i = 0; i < fc; i++) buffer[SAMPLE_RATE - fc + i] = frames[i];
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static void SquareCallback(void* framesOut, uint frameCount)
{
int fc = (int)frameCount;
int wavelength = SAMPLE_RATE / waveFrequency;
float* frames = (float*)framesOut;
// Synthesize the square wave
for (int i = 0; i < fc; i++)
{
frames[i] = (waveIndex < wavelength / 2) ? 1 : -1;
waveIndex++;
if (waveIndex >= wavelength)
{
waveFrequency = newWaveFrequency;
waveIndex = 0;
}
}
// Save the synthesized samples for later drawing
for (int i = 0; i < SAMPLE_RATE - fc; i++) buffer[i] = buffer[i + fc];
for (int i = 0; i < fc; i++) buffer[SAMPLE_RATE - fc + i] = frames[i];
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static void TriangleCallback(void* framesOut, uint frameCount)
{
int fc = (int)frameCount;
int wavelength = SAMPLE_RATE / waveFrequency;
float* frames = (float*)framesOut;
// Synthesize the triangle wave
for (int i = 0; i < fc; i++)
{
frames[i] = (waveIndex < wavelength / 2) ? (-1 + 2.0f * waveIndex / (wavelength / 2)) : (1 - 2.0f * (waveIndex - wavelength / 2) / (wavelength / 2));
waveIndex++;
if (waveIndex >= wavelength)
{
waveFrequency = newWaveFrequency;
waveIndex = 0;
}
}
// Save the synthesized samples for later drawing
for (int i = 0; i < SAMPLE_RATE - fc; i++) buffer[i] = buffer[i + fc];
for (int i = 0; i < fc; i++) buffer[SAMPLE_RATE - fc + i] = frames[i];
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static void SawtoothCallback(void* framesOut, uint frameCount)
{
int fc = (int)frameCount;
int wavelength = SAMPLE_RATE / waveFrequency;
float* frames = (float*)framesOut;
// Synthesize the sawtooth wave
for (int i = 0; i < fc; i++)
{
frames[i] = -1 + 2.0f * waveIndex / wavelength;
waveIndex++;
if (waveIndex >= wavelength)
{
waveFrequency = newWaveFrequency;
waveIndex = 0;
}
}
// Save the synthesized samples for later drawing
for (int i = 0; i < SAMPLE_RATE - fc; i++) buffer[i] = buffer[i + fc];
for (int i = 0; i < fc; i++) buffer[SAMPLE_RATE - fc + i] = frames[i];
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [audio] example - stream callback");
SetTargetFPS(30);
//--------------------------------------------------------------------------------------
var game = new StreamCallback();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,222 @@
/*******************************************************************************************
*
* raylib [audio] example - stream effects
*
* Example complexity rating: [] 4/4
*
* Example originally created with raylib 4.2, last time updated with raylib 5.0
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2022-2025 Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static Raylib_cs.Raylib;
namespace Examples.Audio;
[ExcludeFromBrowser("AttachAudioStreamProcessor is unreliable on the wasm audio backend")]
public unsafe partial class StreamEffects : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Audio / Stream Effects";
public string Title => "raylib [audio] example - stream effects";
//----------------------------------------------------------------------------------
// Global Variables Definition
//----------------------------------------------------------------------------------
private static float[] delayBuffer = null;
private static uint delayBufferSize = 0;
private static uint delayReadIndex = 2;
private static uint delayWriteIndex = 0;
// Low-pass filter state (was a function-local static in the C example)
private static readonly float[] low = { 0.0f, 0.0f };
private Music music;
private float timePlayed;
private bool pause;
private bool enableEffectLPF;
private bool enableEffectDelay;
public void Init()
{
InitAudioDevice(); // Initialize audio device
music = LoadMusicStream("resources/audio/country.mp3");
// Allocate buffer for the delay effect
delayBufferSize = 48000 * 2; // 1 second delay (device sampleRate*channels)
delayBuffer = new float[delayBufferSize];
delayReadIndex = 2;
delayWriteIndex = 0;
low[0] = 0.0f;
low[1] = 0.0f;
PlayMusicStream(music);
timePlayed = 0.0f; // Time played normalized [0.0f..1.0f]
pause = false; // Music playing paused
enableEffectLPF = false; // Enable effect low-pass-filter
enableEffectDelay = false; // Enable effect delay (1 second)
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateMusicStream(music); // Update music buffer with new stream data
// Restart music playing (stop and play)
if (IsKeyPressed(KeyboardKey.Space))
{
StopMusicStream(music);
PlayMusicStream(music);
}
// Pause/Resume music playing
if (IsKeyPressed(KeyboardKey.P))
{
pause = !pause;
if (pause) PauseMusicStream(music);
else ResumeMusicStream(music);
}
// Add/Remove effect: lowpass filter
if (IsKeyPressed(KeyboardKey.F))
{
enableEffectLPF = !enableEffectLPF;
if (enableEffectLPF) AttachAudioStreamProcessor(music.Stream, &AudioProcessEffectLPF);
else DetachAudioStreamProcessor(music.Stream, &AudioProcessEffectLPF);
}
// Add/Remove effect: delay
if (IsKeyPressed(KeyboardKey.D))
{
enableEffectDelay = !enableEffectDelay;
if (enableEffectDelay) AttachAudioStreamProcessor(music.Stream, &AudioProcessEffectDelay);
else DetachAudioStreamProcessor(music.Stream, &AudioProcessEffectDelay);
}
// Get normalized time played for current music stream
timePlayed = GetMusicTimePlayed(music) / GetMusicTimeLength(music);
if (timePlayed > 1.0f) timePlayed = 1.0f; // Make sure time played is no longer than music
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
DrawText("MUSIC SHOULD BE PLAYING!", 245, 150, 20, Color.LightGray);
DrawRectangle(200, 180, 400, 12, Color.LightGray);
DrawRectangle(200, 180, (int)(timePlayed * 400.0f), 12, Color.Maroon);
DrawRectangleLines(200, 180, 400, 12, Color.Gray);
DrawText("PRESS SPACE TO RESTART MUSIC", 215, 230, 20, Color.LightGray);
DrawText("PRESS P TO PAUSE/RESUME MUSIC", 208, 260, 20, Color.LightGray);
DrawText($"PRESS F TO TOGGLE LPF EFFECT: {(enableEffectLPF ? "ON" : "OFF")}", 200, 320, 20, Color.Gray);
DrawText($"PRESS D TO TOGGLE DELAY EFFECT: {(enableEffectDelay ? "ON" : "OFF")}", 180, 350, 20, Color.Gray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadMusicStream(music); // Unload music stream buffers from RAM
CloseAudioDevice(); // Close audio device (music streaming is automatically stopped)
delayBuffer = null; // Free delay buffer
}
//------------------------------------------------------------------------------------
// Module Functions Definition
//------------------------------------------------------------------------------------
// Audio effect: lowpass filter
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static void AudioProcessEffectLPF(void* buffer, uint frames)
{
const float cutoff = 70.0f / 44100.0f; // 70 Hz lowpass filter
const float k = cutoff / (cutoff + 0.1591549431f); // RC filter formula
// Converts the buffer data before using it
float* bufferData = (float*)buffer;
for (uint i = 0; i < frames * 2; i += 2)
{
float l = bufferData[i];
float r = bufferData[i + 1];
low[0] += k * (l - low[0]);
low[1] += k * (r - low[1]);
bufferData[i] = low[0];
bufferData[i + 1] = low[1];
}
}
// Audio effect: delay
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static void AudioProcessEffectDelay(void* buffer, uint frames)
{
float* bufferData = (float*)buffer;
fixed (float* delay = delayBuffer)
{
for (uint i = 0; i < frames * 2; i += 2)
{
float leftDelay = delay[delayReadIndex++]; // ERROR: Reading buffer -> WHY??? Maybe thread related???
float rightDelay = delay[delayReadIndex++];
if (delayReadIndex == delayBufferSize) delayReadIndex = 0;
bufferData[i] = 0.5f * bufferData[i] + 0.5f * leftDelay;
bufferData[i + 1] = 0.5f * bufferData[i + 1] + 0.5f * rightDelay;
delay[delayWriteIndex++] = bufferData[i];
delay[delayWriteIndex++] = bufferData[i + 1];
if (delayWriteIndex == delayBufferSize) delayWriteIndex = 0;
}
}
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [audio] example - stream effects");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new StreamEffects();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,379 @@
/*******************************************************************************************
*
* raylib [core] example - automation events
*
* Example complexity rating: [] 3/4
*
* Example originally created with raylib 5.0, last time updated with raylib 5.0
*
* Example based on 2d_camera_platformer example by arvyy (@arvyy)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2023-2025 Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Core;
public partial class AutomationEvents : IExample
{
private const int GRAVITY = 400;
private const float PLAYER_JUMP_SPD = 350.0f;
private const float PLAYER_HOR_SPD = 200.0f;
private const int MAX_ENVIRONMENT_ELEMENTS = 5;
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Core / Automation Events";
public string Title => "raylib [core] example - automation events";
private struct Player
{
public Vector2 Position;
public float Speed;
public bool CanJump;
}
private struct EnvElement
{
public Rectangle Rect;
public int Blocking;
public Color Color;
}
private Player player;
private EnvElement[] envElements;
private Camera2D camera;
private AutomationEventList aelist;
private bool eventRecording;
private bool eventPlaying;
private uint frameCounter;
private uint playFrameCounter;
private uint currentPlayFrame;
public unsafe void Init()
{
// Define player
player = new Player();
player.Position = new Vector2(400, 280);
player.Speed = 0;
player.CanJump = false;
// Define environment elements (platforms)
envElements = new EnvElement[MAX_ENVIRONMENT_ELEMENTS]
{
new EnvElement { Rect = new Rectangle(0, 0, 1000, 400), Blocking = 0, Color = Color.LightGray },
new EnvElement { Rect = new Rectangle(0, 400, 1000, 200), Blocking = 1, Color = Color.Gray },
new EnvElement { Rect = new Rectangle(300, 200, 400, 10), Blocking = 1, Color = Color.Gray },
new EnvElement { Rect = new Rectangle(250, 300, 100, 10), Blocking = 1, Color = Color.Gray },
new EnvElement { Rect = new Rectangle(650, 300, 100, 10), Blocking = 1, Color = Color.Gray }
};
// Define camera
camera = new Camera2D();
camera.Target = player.Position;
camera.Offset = new Vector2(screenWidth / 2.0f, screenHeight / 2.0f);
camera.Rotation = 0.0f;
camera.Zoom = 1.0f;
// Automation events
aelist = LoadAutomationEventList((sbyte*)null); // Initialize list of automation events to record new events
SetAutomationEventList(ref aelist);
eventRecording = false;
eventPlaying = false;
frameCounter = 0;
playFrameCounter = 0;
currentPlayFrame = 0;
}
public unsafe void Update()
{
// Update
//----------------------------------------------------------------------------------
float deltaTime = 0.015f;//GetFrameTime();
// Dropped files logic
//----------------------------------------------------------------------------------
#if BROWSER
// NOTE: drag-and-drop event-list loading (.txt/.rae) is not supported in the browser host.
#else
if (IsFileDropped())
{
FilePathList droppedFiles = LoadDroppedFiles();
// Supports loading .rgs style files (text or binary) and .png style palette images
if (IsFileExtension(droppedFiles[0], ".txt;.rae"))
{
UnloadAutomationEventList(aelist);
aelist = LoadAutomationEventList(droppedFiles[0]);
eventRecording = false;
// Reset scene state to play
eventPlaying = true;
playFrameCounter = 0;
currentPlayFrame = 0;
player.Position = new Vector2(400, 280);
player.Speed = 0;
player.CanJump = false;
camera.Target = player.Position;
camera.Offset = new Vector2(screenWidth / 2.0f, screenHeight / 2.0f);
camera.Rotation = 0.0f;
camera.Zoom = 1.0f;
}
UnloadDroppedFiles(droppedFiles); // Unload filepaths from memory
}
#endif
//----------------------------------------------------------------------------------
// Update player
//----------------------------------------------------------------------------------
if (IsKeyDown(KeyboardKey.Left)) player.Position.X -= PLAYER_HOR_SPD * deltaTime;
if (IsKeyDown(KeyboardKey.Right)) player.Position.X += PLAYER_HOR_SPD * deltaTime;
if (IsKeyDown(KeyboardKey.Space) && player.CanJump)
{
player.Speed = -PLAYER_JUMP_SPD;
player.CanJump = false;
}
int hitObstacle = 0;
for (int i = 0; i < MAX_ENVIRONMENT_ELEMENTS; i++)
{
EnvElement element = envElements[i];
if (element.Blocking != 0 &&
element.Rect.X <= player.Position.X &&
element.Rect.X + element.Rect.Width >= player.Position.X &&
element.Rect.Y >= player.Position.Y &&
element.Rect.Y <= player.Position.Y + player.Speed * deltaTime)
{
hitObstacle = 1;
player.Speed = 0.0f;
player.Position.Y = element.Rect.Y;
}
}
if (hitObstacle == 0)
{
player.Position.Y += player.Speed * deltaTime;
player.Speed += GRAVITY * deltaTime;
player.CanJump = false;
}
else player.CanJump = true;
if (IsKeyPressed(KeyboardKey.R))
{
// Reset game state
player.Position = new Vector2(400, 280);
player.Speed = 0;
player.CanJump = false;
camera.Target = player.Position;
camera.Offset = new Vector2(screenWidth / 2.0f, screenHeight / 2.0f);
camera.Rotation = 0.0f;
camera.Zoom = 1.0f;
}
//----------------------------------------------------------------------------------
// Events playing
// NOTE: Logic must be before Camera update because it depends on mouse-wheel value,
// that can be set by the played event... but some other inputs could be affected
//----------------------------------------------------------------------------------
if (eventPlaying)
{
// NOTE: Multiple events could be executed in a single frame
while (playFrameCounter == aelist.Events[currentPlayFrame].Frame)
{
PlayAutomationEvent(aelist.Events[currentPlayFrame]);
currentPlayFrame++;
if (currentPlayFrame == aelist.Count)
{
eventPlaying = false;
currentPlayFrame = 0;
playFrameCounter = 0;
TraceLog(TraceLogLevel.Info, "FINISH PLAYING!");
break;
}
}
playFrameCounter++;
}
//----------------------------------------------------------------------------------
// Update camera
//----------------------------------------------------------------------------------
camera.Target = player.Position;
camera.Offset = new Vector2(screenWidth / 2.0f, screenHeight / 2.0f);
float minX = 1000, minY = 1000, maxX = -1000, maxY = -1000;
// WARNING: On event replay, mouse-wheel internal value is set
camera.Zoom += ((float)GetMouseWheelMove() * 0.05f);
if (camera.Zoom > 3.0f) camera.Zoom = 3.0f;
else if (camera.Zoom < 0.25f) camera.Zoom = 0.25f;
for (int i = 0; i < MAX_ENVIRONMENT_ELEMENTS; i++)
{
EnvElement element = envElements[i];
minX = MathF.Min(element.Rect.X, minX);
maxX = MathF.Max(element.Rect.X + element.Rect.Width, maxX);
minY = MathF.Min(element.Rect.Y, minY);
maxY = MathF.Max(element.Rect.Y + element.Rect.Height, maxY);
}
Vector2 max = GetWorldToScreen2D(new Vector2(maxX, maxY), camera);
Vector2 min = GetWorldToScreen2D(new Vector2(minX, minY), camera);
if (max.X < screenWidth) camera.Offset.X = screenWidth - (max.X - (float)screenWidth / 2);
if (max.Y < screenHeight) camera.Offset.Y = screenHeight - (max.Y - (float)screenHeight / 2);
if (min.X > 0) camera.Offset.X = (float)screenWidth / 2 - min.X;
if (min.Y > 0) camera.Offset.Y = (float)screenHeight / 2 - min.Y;
//----------------------------------------------------------------------------------
// Events management
if (IsKeyPressed(KeyboardKey.S)) // Toggle events recording
{
if (!eventPlaying)
{
if (eventRecording)
{
StopAutomationEventRecording();
eventRecording = false;
ExportAutomationEventList(aelist, "automation.rae");
TraceLog(TraceLogLevel.Info, $"RECORDED FRAMES: {aelist.Count}");
}
else
{
SetAutomationEventBaseFrame(180);
StartAutomationEventRecording();
eventRecording = true;
}
}
}
else if (IsKeyPressed(KeyboardKey.A)) // Toggle events playing (WARNING: Starts next frame)
{
if (!eventRecording && (aelist.Count > 0))
{
// Reset scene state to play
eventPlaying = true;
playFrameCounter = 0;
currentPlayFrame = 0;
player.Position = new Vector2(400, 280);
player.Speed = 0;
player.CanJump = false;
camera.Target = player.Position;
camera.Offset = new Vector2(screenWidth / 2.0f, screenHeight / 2.0f);
camera.Rotation = 0.0f;
camera.Zoom = 1.0f;
}
}
if (eventRecording || eventPlaying) frameCounter++;
else frameCounter = 0;
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.LightGray);
BeginMode2D(camera);
// Draw environment elements
for (int i = 0; i < MAX_ENVIRONMENT_ELEMENTS; i++)
{
DrawRectangleRec(envElements[i].Rect, envElements[i].Color);
}
// Draw player rectangle
DrawRectangleRec(new Rectangle(player.Position.X - 20, player.Position.Y - 40, 40, 40), Color.Red);
EndMode2D();
// Draw game controls
DrawRectangle(10, 10, 290, 145, Fade(Color.SkyBlue, 0.5f));
DrawRectangleLines(10, 10, 290, 145, Fade(Color.Blue, 0.8f));
DrawText("Controls:", 20, 20, 10, Color.Black);
DrawText("- RIGHT | LEFT: Player movement", 30, 40, 10, Color.DarkGray);
DrawText("- SPACE: Player jump", 30, 60, 10, Color.DarkGray);
DrawText("- R: Reset game state", 30, 80, 10, Color.DarkGray);
DrawText("- S: START/STOP RECORDING INPUT EVENTS", 30, 110, 10, Color.Black);
DrawText("- A: REPLAY LAST RECORDED INPUT EVENTS", 30, 130, 10, Color.Black);
// Draw automation events recording indicator
if (eventRecording)
{
DrawRectangle(10, 160, 290, 30, Fade(Color.Red, 0.3f));
DrawRectangleLines(10, 160, 290, 30, Fade(Color.Maroon, 0.8f));
DrawCircle(30, 175, 10, Color.Maroon);
if (((frameCounter / 15) % 2) == 1) DrawText($"RECORDING EVENTS... [{aelist.Count}]", 50, 170, 10, Color.Maroon);
}
else if (eventPlaying)
{
DrawRectangle(10, 160, 290, 30, Fade(Color.Lime, 0.3f));
DrawRectangleLines(10, 160, 290, 30, Fade(Color.DarkGreen, 0.8f));
DrawTriangle(new Vector2(20, 155 + 10), new Vector2(20, 155 + 30), new Vector2(40, 155 + 20), Color.DarkGreen);
if (((frameCounter / 15) % 2) == 1) DrawText($"PLAYING RECORDED EVENTS... [{currentPlayFrame}]", 50, 170, 10, Color.DarkGreen);
}
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadAutomationEventList(aelist);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [core] example - automation events");
var game = new AutomationEvents();
game.Init();
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
// Main game loop
while (!WindowShouldClose())
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,184 @@
/*******************************************************************************************
*
* raylib [core] example - 2d camera mouse zoom
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 4.2, last time updated with raylib 4.2
*
* Example contributed by Jeffery Myers (@JeffM2501) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2022-2025 Jeffery Myers (@JeffM2501)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Raymath;
namespace Examples.Core;
public partial class Camera2dMouseZoom : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Core / Camera 2D Mouse Zoom";
public string Title => "raylib [core] example - 2d camera mouse zoom";
private Camera2D camera;
private int zoomMode; // 0-Mouse Wheel, 1-Mouse Move
public void Init()
{
camera = new Camera2D();
camera.Zoom = 1.0f;
zoomMode = 0;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
if (IsKeyPressed(KeyboardKey.One))
{
zoomMode = 0;
}
else if (IsKeyPressed(KeyboardKey.Two))
{
zoomMode = 1;
}
// Translate based on mouse right click
if (IsMouseButtonDown(MouseButton.Left))
{
Vector2 delta = GetMouseDelta();
delta = Vector2Scale(delta, -1.0f / camera.Zoom);
camera.Target = Vector2Add(camera.Target, delta);
}
if (zoomMode == 0)
{
// Zoom based on mouse wheel
float wheel = GetMouseWheelMove();
if (wheel != 0)
{
// Get the world point that is under the mouse
Vector2 mouseWorldPos = GetScreenToWorld2D(GetMousePosition(), camera);
// Set the offset to where the mouse is
camera.Offset = GetMousePosition();
// Set the target to match, so that the camera maps the world space point
// under the cursor to the screen space point under the cursor at any zoom
camera.Target = mouseWorldPos;
// Zoom increment
// Uses log scaling to provide consistent zoom speed
float scale = 0.2f * wheel;
camera.Zoom = Clamp(MathF.Exp(MathF.Log(camera.Zoom) + scale), 0.125f, 64.0f);
}
}
else
{
// Zoom based on mouse right click
if (IsMouseButtonPressed(MouseButton.Right))
{
// Get the world point that is under the mouse
Vector2 mouseWorldPos = GetScreenToWorld2D(GetMousePosition(), camera);
// Set the offset to where the mouse is
camera.Offset = GetMousePosition();
// Set the target to match, so that the camera maps the world space point
// under the cursor to the screen space point under the cursor at any zoom
camera.Target = mouseWorldPos;
}
if (IsMouseButtonDown(MouseButton.Right))
{
// Zoom increment
// Uses log scaling to provide consistent zoom speed
float deltaX = GetMouseDelta().X;
float scale = 0.005f * deltaX;
camera.Zoom = Clamp(MathF.Exp(MathF.Log(camera.Zoom) + scale), 0.125f, 64.0f);
}
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode2D(camera);
// Draw the 3d grid, rotated 90 degrees and centered around 0,0
// just so we have something in the XY plane
Rlgl.PushMatrix();
Rlgl.Translatef(0, 25 * 50, 0);
Rlgl.Rotatef(90, 1, 0, 0);
DrawGrid(100, 50);
Rlgl.PopMatrix();
// Draw a reference circle
DrawCircle(GetScreenWidth() / 2, GetScreenHeight() / 2, 50, Color.Maroon);
EndMode2D();
// Draw mouse reference
//Vector2 mousePos = GetWorldToScreen2D(GetMousePosition(), camera)
DrawCircleV(GetMousePosition(), 4, Color.DarkGray);
DrawTextEx(GetFontDefault(), $"[{GetMouseX()}, {GetMouseY()}]",
Vector2Add(GetMousePosition(), new Vector2(-44, -24)), 20, 2, Color.Black);
DrawText("[1][2] Select mouse zoom mode (Wheel or Move)", 20, 20, 20, Color.DarkGray);
if (zoomMode == 0)
{
DrawText("Mouse left button drag to move, mouse wheel to zoom", 20, 50, 20, Color.DarkGray);
}
else
{
DrawText("Mouse left button drag to move, mouse press and move to zoom", 20, 50, 20, Color.DarkGray);
}
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [core] example - 2d camera mouse zoom");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new Camera2dMouseZoom();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,348 @@
/*******************************************************************************************
*
* raylib [core] example - 3d camera fps
*
* Example complexity rating: [] 3/4
*
* Example originally created with raylib 5.5, last time updated with raylib 5.5
*
* Example contributed by Agnis Aldiņš (@nezvers) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Agnis Aldiņš (@nezvers)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Raymath;
namespace Examples.Core;
public partial class Camera3dFps : IExample
{
// Movement constants
private const float GRAVITY = 32.0f;
private const float MAX_SPEED = 20.0f;
private const float CROUCH_SPEED = 5.0f;
private const float JUMP_FORCE = 12.0f;
private const float MAX_ACCEL = 150.0f;
// Grounded drag
private const float FRICTION = 0.86f;
// Increasing air drag, increases strafing speed
private const float AIR_DRAG = 0.98f;
// Responsiveness for turning movement direction to looked direction
private const float CONTROL = 15.0f;
private const float CROUCH_HEIGHT = 0.0f;
private const float STAND_HEIGHT = 1.0f;
private const float BOTTOM_HEIGHT = 0.5f;
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Core / 3D Camera FPS";
public string Title => "raylib [core] example - 3d camera fps";
public bool CursorDisabled => true;
// Body structure
private struct Body
{
public Vector3 Position;
public Vector3 Velocity;
public Vector3 Dir;
public bool IsGrounded;
}
// State that was global in the C example
private readonly Vector2 sensitivity = new Vector2(0.001f, 0.001f);
private Body player;
private Vector2 lookRotation;
private float headTimer;
private float walkLerp;
private float headLerp;
private Vector2 lean;
private Camera3D camera;
public void Init()
{
player = new Body();
lookRotation = new Vector2(0, 0);
headTimer = 0.0f;
walkLerp = 0.0f;
headLerp = STAND_HEIGHT;
lean = new Vector2(0, 0);
// Initialize camera variables
// NOTE: UpdateCameraFPS() takes care of the rest
camera = new Camera3D();
camera.FovY = 60.0f;
camera.Projection = CameraProjection.Perspective;
camera.Position = new Vector3(
player.Position.X,
player.Position.Y + (BOTTOM_HEIGHT + headLerp),
player.Position.Z);
UpdateCameraFPS(ref camera); // Update camera parameters
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
Vector2 mouseDelta = GetMouseDelta();
lookRotation.X -= mouseDelta.X * sensitivity.X;
lookRotation.Y += mouseDelta.Y * sensitivity.Y;
int sideway = (IsKeyDown(KeyboardKey.D) ? 1 : 0) - (IsKeyDown(KeyboardKey.A) ? 1 : 0);
int forward = (IsKeyDown(KeyboardKey.W) ? 1 : 0) - (IsKeyDown(KeyboardKey.S) ? 1 : 0);
bool crouching = IsKeyDown(KeyboardKey.LeftControl);
UpdateBody(ref player, lookRotation.X, sideway, forward, IsKeyPressed(KeyboardKey.Space), crouching);
float delta = GetFrameTime();
headLerp = Lerp(headLerp, (crouching ? CROUCH_HEIGHT : STAND_HEIGHT), 20.0f * delta);
camera.Position = new Vector3(
player.Position.X,
player.Position.Y + (BOTTOM_HEIGHT + headLerp),
player.Position.Z);
if (player.IsGrounded && ((forward != 0) || (sideway != 0)))
{
headTimer += delta * 3.0f;
walkLerp = Lerp(walkLerp, 1.0f, 10.0f * delta);
camera.FovY = Lerp(camera.FovY, 55.0f, 5.0f * delta);
}
else
{
walkLerp = Lerp(walkLerp, 0.0f, 10.0f * delta);
camera.FovY = Lerp(camera.FovY, 60.0f, 5.0f * delta);
}
lean.X = Lerp(lean.X, sideway * 0.02f, 10.0f * delta);
lean.Y = Lerp(lean.Y, forward * 0.015f, 10.0f * delta);
UpdateCameraFPS(ref camera);
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode3D(camera);
DrawLevel();
EndMode3D();
// Draw info box
DrawRectangle(5, 5, 330, 75, Fade(Color.SkyBlue, 0.5f));
DrawRectangleLines(5, 5, 330, 75, Color.Blue);
DrawText("Camera controls:", 15, 15, 10, Color.Black);
DrawText("- Move keys: W, A, S, D, Space, Left-Ctrl", 15, 30, 10, Color.Black);
DrawText("- Look around: arrow keys or mouse", 15, 45, 10, Color.Black);
float velLen = Vector2Length(new Vector2(player.Velocity.X, player.Velocity.Z));
DrawText($"- Velocity Len: ({velLen:00.000})", 15, 60, 10, Color.Black);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
// Update body considering current world state
private void UpdateBody(ref Body body, float rot, int side, int forward, bool jumpPressed, bool crouchHold)
{
Vector2 input = new Vector2((float)side, (float)-forward);
// Upstream guards this with `#if defined(NORMALIZE_INPUT)`, which is always true given the
// `#define NORMALIZE_INPUT 0` above it (defined() tests definedness, not the value), so the
// diagonal-movement normalization is active.
// Slow down diagonal movement
if ((side != 0) && (forward != 0)) input = Vector2Normalize(input);
float delta = GetFrameTime();
if (!body.IsGrounded) body.Velocity.Y -= GRAVITY * delta;
if (body.IsGrounded && jumpPressed)
{
body.Velocity.Y = JUMP_FORCE;
body.IsGrounded = false;
// Sound can be played at this moment
//SetSoundPitch(fxJump, 1.0f + (GetRandomValue(-100, 100)*0.001));
//PlaySound(fxJump);
}
Vector3 front = new Vector3(MathF.Sin(rot), 0.0f, MathF.Cos(rot));
Vector3 right = new Vector3(MathF.Cos(-rot), 0.0f, MathF.Sin(-rot));
Vector3 desiredDir = new Vector3(
input.X * right.X + input.Y * front.X,
0.0f,
input.X * right.Z + input.Y * front.Z);
body.Dir = Vector3Lerp(body.Dir, desiredDir, CONTROL * delta);
float decel = (body.IsGrounded ? FRICTION : AIR_DRAG);
Vector3 hvel = new Vector3(body.Velocity.X * decel, 0.0f, body.Velocity.Z * decel);
float hvelLength = Vector3Length(hvel); // Magnitude
if (hvelLength < (MAX_SPEED * 0.01f)) hvel = new Vector3(0, 0, 0);
// This is what creates strafing
float speed = Vector3DotProduct(hvel, body.Dir);
// Whenever the amount of acceleration to add is clamped by the maximum acceleration constant,
// a Player can make the speed faster by bringing the direction closer to horizontal velocity angle
// More info here: https://youtu.be/v3zT3Z5apaM?t=165
float maxSpeed = (crouchHold ? CROUCH_SPEED : MAX_SPEED);
float accel = Clamp(maxSpeed - speed, 0.0f, MAX_ACCEL * delta);
hvel.X += body.Dir.X * accel;
hvel.Z += body.Dir.Z * accel;
body.Velocity.X = hvel.X;
body.Velocity.Z = hvel.Z;
body.Position.X += body.Velocity.X * delta;
body.Position.Y += body.Velocity.Y * delta;
body.Position.Z += body.Velocity.Z * delta;
// Fancy collision system against the floor
if (body.Position.Y <= 0.0f)
{
body.Position.Y = 0.0f;
body.Velocity.Y = 0.0f;
body.IsGrounded = true; // Enable jumping
}
}
// Update camera for FPS behaviour
private void UpdateCameraFPS(ref Camera3D camera)
{
Vector3 up = new Vector3(0.0f, 1.0f, 0.0f);
Vector3 targetOffset = new Vector3(0.0f, 0.0f, -1.0f);
// Left and right
Vector3 yaw = Vector3RotateByAxisAngle(targetOffset, up, lookRotation.X);
// Clamp view up
float maxAngleUp = Vector3Angle(up, yaw);
maxAngleUp -= 0.001f; // Avoid numerical errors
if (-(lookRotation.Y) > maxAngleUp) { lookRotation.Y = -maxAngleUp; }
// Clamp view down
float maxAngleDown = Vector3Angle(Vector3Negate(up), yaw);
maxAngleDown *= -1.0f; // Downwards angle is negative
maxAngleDown += 0.001f; // Avoid numerical errors
if (-(lookRotation.Y) < maxAngleDown) { lookRotation.Y = -maxAngleDown; }
// Up and down
Vector3 right = Vector3Normalize(Vector3CrossProduct(yaw, up));
// Rotate view vector around right axis
float pitchAngle = -lookRotation.Y - lean.Y;
pitchAngle = Clamp(pitchAngle, -MathF.PI / 2 + 0.0001f, MathF.PI / 2 - 0.0001f); // Clamp angle so it doesn't go past straight up or straight down
Vector3 pitch = Vector3RotateByAxisAngle(yaw, right, pitchAngle);
// Head animation
// Rotate up direction around forward axis
float headSin = MathF.Sin(headTimer * MathF.PI);
float headCos = MathF.Cos(headTimer * MathF.PI);
const float stepRotation = 0.01f;
camera.Up = Vector3RotateByAxisAngle(up, pitch, headSin * stepRotation + lean.X);
// Camera BOB
const float bobSide = 0.1f;
const float bobUp = 0.15f;
Vector3 bobbing = Vector3Scale(right, headSin * bobSide);
bobbing.Y = MathF.Abs(headCos * bobUp);
camera.Position = Vector3Add(camera.Position, Vector3Scale(bobbing, walkLerp));
camera.Target = Vector3Add(camera.Position, pitch);
}
// Draw game level
private void DrawLevel()
{
const int floorExtent = 25;
const float tileSize = 5.0f;
Color tileColor1 = new Color(150, 200, 200, 255);
// Floor tiles
for (int y = -floorExtent; y < floorExtent; y++)
{
for (int x = -floorExtent; x < floorExtent; x++)
{
if ((y & 1) != 0 && (x & 1) != 0)
{
DrawPlane(new Vector3(x * tileSize, 0.0f, y * tileSize), new Vector2(tileSize, tileSize), tileColor1);
}
else if ((y & 1) == 0 && (x & 1) == 0)
{
DrawPlane(new Vector3(x * tileSize, 0.0f, y * tileSize), new Vector2(tileSize, tileSize), Color.LightGray);
}
}
}
Vector3 towerSize = new Vector3(16.0f, 32.0f, 16.0f);
Color towerColor = new Color(150, 200, 200, 255);
Vector3 towerPos = new Vector3(16.0f, 16.0f, 16.0f);
DrawCubeV(towerPos, towerSize, towerColor);
DrawCubeWiresV(towerPos, towerSize, Color.DarkBlue);
towerPos.X *= -1;
DrawCubeV(towerPos, towerSize, towerColor);
DrawCubeWiresV(towerPos, towerSize, Color.DarkBlue);
towerPos.Z *= -1;
DrawCubeV(towerPos, towerSize, towerColor);
DrawCubeWiresV(towerPos, towerSize, Color.DarkBlue);
towerPos.X *= -1;
DrawCubeV(towerPos, towerSize, towerColor);
DrawCubeWiresV(towerPos, towerSize, Color.DarkBlue);
// Red sun
DrawSphere(new Vector3(300.0f, 300.0f, 0.0f), 100.0f, new Color(255, 0, 0, 255));
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [core] example - 3d camera fps");
DisableCursor(); // Limit cursor to relative movement inside the window
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new Camera3dFps();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,286 @@
/*******************************************************************************************
*
* raylib [core] example - clipboard text
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by Ananth S (@Ananth1839) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Ananth S (@Ananth1839)
*
********************************************************************************************/
// NOTE: The upstream C example uses raygui (GuiTextBox/GuiButton/GuiLabel) for its UI.
// raygui is not bound in raylib-cs, so the widgets below are minimal re-implementations
// using plain raylib drawing/input. Clipboard behaviour (cut/copy/paste and CTRL+X/C/V
// shortcuts) matches the original.
using System;
using System.Numerics;
using System.Text;
using static Raylib_cs.Raylib;
namespace Examples.Core;
public partial class ClipboardText : IExample
{
private const int MaxTextSamples = 5;
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Core / Clipboard Text";
public string Title => "raylib [core] example - clipboard text";
private string[] sampleTexts;
private string clipboardText;
private StringBuilder inputBuffer;
// UI required variables
private bool textBoxEditMode;
private bool btnCutPressed;
private bool btnCopyPressed;
private bool btnPastePressed;
private bool btnClearPressed;
private bool btnRandomPressed;
private int framesCounter;
public void Init()
{
// Define some sample texts
sampleTexts = new string[]
{
"Hello from raylib!",
"The quick brown fox jumps over the lazy dog",
"Clipboard operations are useful!",
"raylib is a simple and easy-to-use library",
"Copy and paste me!"
};
clipboardText = null;
inputBuffer = new StringBuilder("Hello from raylib!"); // Random initial string
// UI required variables
textBoxEditMode = false;
btnCutPressed = false;
btnCopyPressed = false;
btnPastePressed = false;
btnClearPressed = false;
btnRandomPressed = false;
framesCounter = 0;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
framesCounter++;
// Handle button interactions
if (btnCutPressed)
{
SetClipboardText(inputBuffer.ToString());
clipboardText = GetClipboardText_();
inputBuffer.Clear(); // Quick solution to clear text
}
if (btnCopyPressed)
{
SetClipboardText(inputBuffer.ToString()); // Copy text to clipboard
clipboardText = GetClipboardText_(); // Get text from clipboard
}
if (btnPastePressed)
{
// Paste text from clipboard
clipboardText = GetClipboardText_();
if (clipboardText != null) SetInputBuffer(clipboardText);
}
if (btnClearPressed)
{
inputBuffer.Clear(); // Quick solution to clear text
}
if (btnRandomPressed)
{
// Get random text from sample list
SetInputBuffer(sampleTexts[GetRandomValue(0, MaxTextSamples - 1)]);
}
// Quick cut/copy/paste with keyboard shortcuts
if (IsKeyDown(KeyboardKey.LeftControl) || IsKeyDown(KeyboardKey.RightControl))
{
if (IsKeyPressed(KeyboardKey.X))
{
SetClipboardText(inputBuffer.ToString());
inputBuffer.Clear(); // Quick solution to clear text
}
if (IsKeyPressed(KeyboardKey.C)) SetClipboardText(inputBuffer.ToString());
if (IsKeyPressed(KeyboardKey.V))
{
clipboardText = GetClipboardText_();
if (clipboardText != null) SetInputBuffer(clipboardText);
}
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
// Draw instructions
GuiLabel(new Rectangle(50, 20, 700, 36), "Use the BUTTONS or KEY SHORTCUTS:");
DrawText("[CTRL+X] - CUT | [CTRL+C] COPY | [CTRL+V] | PASTE", 50, 60, 20, Color.Maroon);
// Draw text box
if (GuiTextBox(new Rectangle(50, 120, 652, 40), inputBuffer, 256, textBoxEditMode)) textBoxEditMode = !textBoxEditMode;
// Random text button
btnRandomPressed = GuiButton(new Rectangle(50 + 652 + 8, 120, 40, 40), "RND");
// Draw buttons
btnCutPressed = GuiButton(new Rectangle(50, 180, 158, 40), "CUT");
btnCopyPressed = GuiButton(new Rectangle(50 + 165, 180, 158, 40), "COPY");
btnPastePressed = GuiButton(new Rectangle(50 + 165 * 2, 180, 158, 40), "PASTE");
btnClearPressed = GuiButton(new Rectangle(50 + 165 * 3, 180, 158, 40), "CLEAR");
// Draw clipboard status
GuiLabel(new Rectangle(50, 260, 700, 40), "Clipboard current text data:");
GuiTextBoxReadOnly(new Rectangle(50, 300, 700, 40), clipboardText);
GuiLabel(new Rectangle(50, 360, 700, 40), "Try copying text from other applications and pasting here!");
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
// Replace input buffer contents (equivalent to raylib TextCopy into a fixed buffer)
private void SetInputBuffer(string text)
{
inputBuffer.Clear();
if (text != null)
{
if (text.Length > 255) text = text.Substring(0, 255);
inputBuffer.Append(text);
}
}
//----------------------------------------------------------------------------------
// Minimal raygui-like widgets (plain raylib re-implementation)
//----------------------------------------------------------------------------------
private static void GuiLabel(Rectangle bounds, string text)
{
DrawText(text, (int)bounds.X, (int)(bounds.Y + (bounds.Height - 20) / 2), 20, Color.DarkGray);
}
private static bool GuiButton(Rectangle bounds, string text)
{
bool pressed = false;
Vector2 mouse = GetMousePosition();
bool hover = CheckCollisionPointRec(mouse, bounds);
Color fill = hover ? (IsMouseButtonDown(MouseButton.Left) ? Color.SkyBlue : Color.LightGray) : Color.RayWhite;
DrawRectangleRec(bounds, fill);
DrawRectangleLinesEx(bounds, 1, hover ? Color.Blue : Color.Gray);
int textWidth = MeasureText(text, 20);
DrawText(text, (int)(bounds.X + (bounds.Width - textWidth) / 2), (int)(bounds.Y + (bounds.Height - 20) / 2), 20, Color.DarkGray);
if (hover && IsMouseButtonReleased(MouseButton.Left)) pressed = true;
return pressed;
}
private bool GuiTextBox(Rectangle bounds, StringBuilder text, int maxChars, bool editMode)
{
bool pressed = false;
Vector2 mouse = GetMousePosition();
bool hover = CheckCollisionPointRec(mouse, bounds);
if (editMode)
{
// Get char pressed (unicode character) on the queue
int key = GetCharPressed();
while (key > 0)
{
if ((key >= 32) && (key <= 125) && (text.Length < maxChars - 1))
{
text.Append((char)key);
}
key = GetCharPressed();
}
if (IsKeyPressed(KeyboardKey.Backspace) && (text.Length > 0)) text.Remove(text.Length - 1, 1);
}
DrawRectangleRec(bounds, Color.RayWhite);
DrawRectangleLinesEx(bounds, editMode ? 2 : 1, editMode ? Color.Red : (hover ? Color.Blue : Color.Gray));
string content = text.ToString();
DrawText(content, (int)bounds.X + 4, (int)(bounds.Y + (bounds.Height - 20) / 2), 20, Color.DarkGray);
// Draw blinking cursor while editing
if (editMode && ((framesCounter / 20) % 2 == 0))
{
DrawText("_", (int)bounds.X + 4 + MeasureText(content, 20), (int)(bounds.Y + (bounds.Height - 20) / 2), 20, Color.DarkGray);
}
if (hover && IsMouseButtonPressed(MouseButton.Left)) pressed = true;
return pressed;
}
private static void GuiTextBoxReadOnly(Rectangle bounds, string text)
{
DrawRectangleRec(bounds, Color.LightGray);
DrawRectangleLinesEx(bounds, 1, Color.Gray);
if (text != null) DrawText(text, (int)bounds.X + 4, (int)(bounds.Y + (bounds.Height - 20) / 2), 20, Color.Gray);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [core] example - clipboard text");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new ClipboardText();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,253 @@
/*******************************************************************************************
*
* raylib [core] example - compute hash
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Ramon Santamaria (@raysan5)
*
********************************************************************************************/
// NOTE: The upstream C example uses raygui (GuiTextBox/GuiButton/GuiLabel) for its UI.
// raygui is not bound in raylib-cs, so the widgets below are minimal re-implementations
// using plain raylib drawing/input. The hashing/Base64 logic is a faithful port.
using System;
using System.Numerics;
using System.Text;
using static Raylib_cs.Raylib;
namespace Examples.Core;
public unsafe partial class ComputeHash : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Core / Compute Hash";
public string Title => "raylib [core] example - compute hash";
// UI controls variables
private StringBuilder textInput;
private bool textBoxEditMode;
private bool btnComputeHashes;
// Data hash values
private uint hashCRC32;
private uint[] hashMD5;
private uint[] hashSHA1;
private uint[] hashSHA256;
// Base64 encoded data
private string base64Text;
private int framesCounter;
public void Init()
{
textInput = new StringBuilder("The quick brown fox jumps over the lazy dog.");
textBoxEditMode = false;
btnComputeHashes = false;
hashCRC32 = 0;
hashMD5 = null;
hashSHA1 = null;
hashSHA256 = null;
base64Text = null;
framesCounter = 0;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
framesCounter++;
if (btnComputeHashes)
{
byte[] bytes = Encoding.UTF8.GetBytes(textInput.ToString());
int textInputLen = bytes.Length;
fixed (byte* textInputPtr = bytes)
{
int base64TextSize;
// Encode data to Base64 string (includes NULL terminator), memory must be MemFree()
sbyte* base64 = EncodeDataBase64(textInputPtr, textInputLen, &base64TextSize);
base64Text = (base64 != null) ? new string(base64) : null;
MemFree(base64); // Free Base64 text data (kept managed above)
hashCRC32 = ComputeCRC32(textInputPtr, textInputLen); // Compute CRC32 hash code (4 bytes)
hashMD5 = CopyHash(ComputeMD5(textInputPtr, textInputLen), 4); // Compute MD5 hash code, returns static int[4] (16 bytes)
hashSHA1 = CopyHash(ComputeSHA1(textInputPtr, textInputLen), 5); // Compute SHA1 hash code, returns static int[5] (20 bytes)
hashSHA256 = CopyHash(ComputeSHA256(textInputPtr, textInputLen), 8); // Compute SHA256 hash code, returns static int[8] (32 bytes)
}
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
GuiLabel(new Rectangle(40, 26, 720, 32), "INPUT DATA (TEXT):", 20);
if (GuiTextBox(new Rectangle(40, 64, 720, 32), textInput, 95, textBoxEditMode, 10)) textBoxEditMode = !textBoxEditMode;
btnComputeHashes = GuiButton(new Rectangle(40, 64 + 40, 720, 32), "COMPUTE INPUT DATA HASHES", 10);
GuiLabel(new Rectangle(40, 160, 720, 32), "INPUT DATA HASH VALUES:", 20);
GuiLabel(new Rectangle(40, 200, 120, 32), "CRC32 [32 bit]:", 10);
GuiTextBoxReadOnly(new Rectangle(40 + 120, 200, 720 - 120, 32), GetDataAsHexText(new uint[] { hashCRC32 }, 1), 10);
GuiLabel(new Rectangle(40, 200 + 36, 120, 32), "MD5 [128 bit]:", 10);
GuiTextBoxReadOnly(new Rectangle(40 + 120, 200 + 36, 720 - 120, 32), GetDataAsHexText(hashMD5, 4), 10);
GuiLabel(new Rectangle(40, 200 + 36 * 2, 120, 32), "SHA1 [160 bit]:", 10);
GuiTextBoxReadOnly(new Rectangle(40 + 120, 200 + 36 * 2, 720 - 120, 32), GetDataAsHexText(hashSHA1, 5), 10);
GuiLabel(new Rectangle(40, 200 + 36 * 3, 120, 32), "SHA256 [256 bit]:", 10);
GuiTextBoxReadOnly(new Rectangle(40 + 120, 200 + 36 * 3, 720 - 120, 32), GetDataAsHexText(hashSHA256, 8), 10);
GuiLabel(new Rectangle(40, 200 + 36 * 5 - 30, 320, 32), "BONUS - BAS64 ENCODED STRING:", 10);
GuiLabel(new Rectangle(40, 200 + 36 * 5, 120, 32), "BASE64 ENCODING:", 10);
GuiTextBoxReadOnly(new Rectangle(40 + 120, 200 + 36 * 5, 720 - 120, 32), base64Text, 10);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
//----------------------------------------------------------------------------------
// Module Functions Definition
//----------------------------------------------------------------------------------
private static uint[] CopyHash(uint* data, int count)
{
if (data == null) return null;
uint[] result = new uint[count];
for (int i = 0; i < count; i++) result[i] = data[i];
return result;
}
private static string GetDataAsHexText(uint[] data, int dataSize)
{
if ((data != null) && (dataSize > 0) && (dataSize < ((128 / 8) - 1)))
{
StringBuilder text = new StringBuilder();
for (int i = 0; i < dataSize; i++) text.Append(data[i].ToString("X8"));
return text.ToString();
}
return "00000000";
}
//----------------------------------------------------------------------------------
// Minimal raygui-like widgets (plain raylib re-implementation)
//----------------------------------------------------------------------------------
private static void GuiLabel(Rectangle bounds, string text, int fontSize)
{
DrawText(text, (int)bounds.X, (int)(bounds.Y + (bounds.Height - fontSize) / 2), fontSize, Color.DarkGray);
}
private static bool GuiButton(Rectangle bounds, string text, int fontSize)
{
bool pressed = false;
Vector2 mouse = GetMousePosition();
bool hover = CheckCollisionPointRec(mouse, bounds);
Color fill = hover ? (IsMouseButtonDown(MouseButton.Left) ? Color.SkyBlue : Color.LightGray) : Color.RayWhite;
DrawRectangleRec(bounds, fill);
DrawRectangleLinesEx(bounds, 1, hover ? Color.Blue : Color.Gray);
int textWidth = MeasureText(text, fontSize);
DrawText(text, (int)(bounds.X + (bounds.Width - textWidth) / 2), (int)(bounds.Y + (bounds.Height - fontSize) / 2), fontSize, Color.DarkGray);
if (hover && IsMouseButtonReleased(MouseButton.Left)) pressed = true;
return pressed;
}
private bool GuiTextBox(Rectangle bounds, StringBuilder text, int maxChars, bool editMode, int fontSize)
{
bool pressed = false;
Vector2 mouse = GetMousePosition();
bool hover = CheckCollisionPointRec(mouse, bounds);
if (editMode)
{
int key = GetCharPressed();
while (key > 0)
{
if ((key >= 32) && (key <= 125) && (text.Length < maxChars - 1))
{
text.Append((char)key);
}
key = GetCharPressed();
}
if (IsKeyPressed(KeyboardKey.Backspace) && (text.Length > 0)) text.Remove(text.Length - 1, 1);
}
DrawRectangleRec(bounds, Color.RayWhite);
DrawRectangleLinesEx(bounds, editMode ? 2 : 1, editMode ? Color.Red : (hover ? Color.Blue : Color.Gray));
string content = text.ToString();
DrawText(content, (int)bounds.X + 4, (int)(bounds.Y + (bounds.Height - fontSize) / 2), fontSize, Color.DarkGray);
if (editMode && ((framesCounter / 20) % 2 == 0))
{
DrawText("_", (int)bounds.X + 4 + MeasureText(content, fontSize), (int)(bounds.Y + (bounds.Height - fontSize) / 2), fontSize, Color.DarkGray);
}
if (hover && IsMouseButtonPressed(MouseButton.Left)) pressed = true;
return pressed;
}
private static void GuiTextBoxReadOnly(Rectangle bounds, string text, int fontSize)
{
DrawRectangleRec(bounds, Color.LightGray);
DrawRectangleLinesEx(bounds, 1, Color.Gray);
if (text != null) DrawText(text, (int)bounds.X + 4, (int)(bounds.Y + (bounds.Height - fontSize) / 2), fontSize, Color.Gray);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [core] example - compute hash");
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new ComputeHash();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,188 @@
/*******************************************************************************************
*
* raylib [core] example - custom frame control
*
* Example complexity rating: [] 4/4
*
* NOTE: WARNING: This is an example for advanced users willing to have full control over
* the frame processes. By default, EndDrawing() calls the following processes:
* 1. Draw remaining batch data: rlDrawRenderBatchActive()
* 2. SwapScreenBuffer()
* 3. Frame time control: WaitTime()
* 4. PollInputEvents()
*
* To avoid steps 2, 3 and 4, flag SUPPORT_CUSTOM_FRAME_CONTROL can be enabled in
* config.h (it requires recompiling raylib). This way those steps are up to the user
*
* Note that enabling this flag invalidates some functions:
* - GetFrameTime()
* - SetTargetFPS()
* - GetFPS()
*
* Example originally created with raylib 4.0, last time updated with raylib 4.0
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2021-2025 Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using static Raylib_cs.Raylib;
namespace Examples.Core;
// NOTE: This example is intended to run against a raylib built with SUPPORT_CUSTOM_FRAME_CONTROL,
// where EndDrawing() does NOT swap buffers, wait or poll input, leaving those to the user code
// below. The stock raylib-cs native library is built WITHOUT that flag, so EndDrawing() still
// performs those steps and the manual SwapScreenBuffer()/WaitTime()/PollInputEvents() calls here
// run in addition to them. The port is kept faithful to upstream regardless.
[ExcludeFromBrowser("manual PollInputEvents/SwapScreenBuffer/WaitTime clashes with the emscripten main loop")]
public partial class CustomFrameControl : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Core / Custom Frame Control";
public string Title => "raylib [core] example - custom frame control";
// Custom timming variables
private double previousTime; // Previous time measure
private double currentTime; // Current time measure
private double updateDrawTime; // Update + Draw time
private double waitTime; // Wait time (if target fps required)
private float deltaTime; // Frame time (Update + Draw + Wait time)
private float timeCounter; // Accumulative time counter (seconds)
private float position; // Circle position
private bool pause; // Pause control flag
private int targetFPS; // Our initial target fps
public void Init()
{
previousTime = GetTime();
currentTime = 0.0;
updateDrawTime = 0.0;
waitTime = 0.0;
deltaTime = 0.0f;
timeCounter = 0.0f;
position = 0.0f;
pause = false;
targetFPS = 60;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
#if !BROWSER
// NOTE: On non web platforms the PollInputEvents just works before the inputs checks
PollInputEvents(); // Poll input events (SUPPORT_CUSTOM_FRAME_CONTROL)
#endif
if (IsKeyPressed(KeyboardKey.Space)) pause = !pause;
if (IsKeyPressed(KeyboardKey.Up)) targetFPS += 20;
else if (IsKeyPressed(KeyboardKey.Down)) targetFPS -= 20;
if (targetFPS < 0) targetFPS = 0;
if (!pause)
{
position += 200 * deltaTime; // We move at 200 pixels per second
if (position >= GetScreenWidth()) position = 0;
timeCounter += deltaTime; // We count time (seconds)
}
#if BROWSER
// NOTE: On web platform for some reason the PollInputEvents only works after the inputs
// check, so just call it after check all your inputs (on web)
PollInputEvents(); // Poll input events (SUPPORT_CUSTOM_FRAME_CONTROL)
#endif
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
for (int i = 0; i < GetScreenWidth() / 200; i++) DrawRectangle(200 * i, 0, 1, GetScreenHeight(), Color.SkyBlue);
DrawCircle((int)position, GetScreenHeight() / 2 - 25, 50, Color.Red);
DrawText($"{timeCounter * 1000.0f:000} ms", (int)position - 40, GetScreenHeight() / 2 - 100, 20, Color.Maroon);
DrawText($"PosX: {position:000}", (int)position - 50, GetScreenHeight() / 2 + 40, 20, Color.Black);
DrawText("Circle is moving at a constant 200 pixels/sec,\nindependently of the frame rate.", 10, 10, 20, Color.DarkGray);
DrawText("PRESS SPACE to PAUSE MOVEMENT", 10, GetScreenHeight() - 60, 20, Color.Gray);
DrawText("PRESS UP | DOWN to CHANGE TARGET FPS", 10, GetScreenHeight() - 30, 20, Color.Gray);
DrawText($"TARGET FPS: {targetFPS}", GetScreenWidth() - 220, 10, 20, Color.Lime);
if (deltaTime != 0)
{
DrawText($"CURRENT FPS: {(int)(1.0f / deltaTime)}", GetScreenWidth() - 220, 40, 20, Color.Green);
}
EndDrawing();
// NOTE: In case raylib is configured to SUPPORT_CUSTOM_FRAME_CONTROL,
// Events polling, screen buffer swap and frame time control must be managed by the user
SwapScreenBuffer(); // Flip the back buffer to screen (front buffer)
currentTime = GetTime();
updateDrawTime = currentTime - previousTime;
if (targetFPS > 0) // We want a fixed frame rate
{
waitTime = (1.0f / (float)targetFPS) - updateDrawTime;
if (waitTime > 0.0)
{
WaitTime((float)waitTime);
currentTime = GetTime();
deltaTime = (float)(currentTime - previousTime);
}
}
else deltaTime = (float)updateDrawTime; // Framerate could be variable
previousTime = currentTime;
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [core] example - custom frame control");
// NOTE: Not calling SetTargetFPS(): this example manages frame timing manually with
// WaitTime() (SetTargetFPS/GetFrameTime/GetFPS are invalidated by SUPPORT_CUSTOM_FRAME_CONTROL)
//--------------------------------------------------------------------------------------
var game = new CustomFrameControl();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,205 @@
/*******************************************************************************************
*
* raylib [core] example - directory files
*
* Example complexity rating: [] 1/4
*
* Example originally created with raylib 5.5, last time updated with raylib 5.6
*
* Example contributed by Hugo ARNAL (@hugoarnal) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Hugo ARNAL (@hugoarnal)
*
********************************************************************************************/
// NOTE: The original example relies on raygui (GuiButton, GuiLabel, GuiListViewEx) for its UI.
// raygui is not part of the raylib-cs bindings, so the back button, directory label and file
// list view are reimplemented here with plain raylib primitives. The directory navigation
// behaviour (enter directories, go back) is preserved.
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Core;
public partial class DirectoryFiles : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const string FileFilter = "DIRS*;.png;.c";
public string Name => "Core / Directory Files";
public string Title => "raylib [core] example - directory files";
private string directory;
private FilePathList files;
private bool btnBackPressed;
private int listScrollIndex;
private int listItemActive;
private int listItemFocused;
public void Init()
{
directory = GetWorkingDirectoryAsString();
// Load file-paths on current working directory
// NOTE: LoadDirectoryFiles() loads files and directories by default,
// use LoadDirectoryFilesEx() for custom filters and recursive directories loading
//files = LoadDirectoryFiles(directory);
files = LoadDirectoryFilesEx(directory, FileFilter, false);
btnBackPressed = false;
listScrollIndex = 0;
listItemActive = -1;
listItemFocused = -1;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
if (btnBackPressed)
{
directory = GetPrevDirectoryPath(directory);
UnloadDirectoryFiles(files);
files = LoadDirectoryFilesEx(directory, FileFilter, false);
listScrollIndex = 0;
listItemActive = -1;
listItemFocused = -1;
}
if ((listItemActive >= 0) && (listItemActive < (int)files.Count))
{
string selected = files[(uint)listItemActive];
bool isDirectory = DirectoryExists(selected);
if (isDirectory)
{
directory = selected;
UnloadDirectoryFiles(files);
files = LoadDirectoryFilesEx(directory, FileFilter, false);
listScrollIndex = 0;
listItemActive = -1;
listItemFocused = -1;
}
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
Vector2 mouse = GetMousePosition();
// Back button "<"
Rectangle backBounds = new Rectangle(40.0f, 10.0f, 48, 28);
bool backHover = CheckCollisionPointRec(mouse, backBounds);
DrawRectangleRec(backBounds, backHover ? Color.SkyBlue : Color.LightGray);
DrawRectangleLinesEx(backBounds, 1, Color.Gray);
int backTextWidth = MeasureText("<", 20);
DrawText("<", (int)(backBounds.X + (backBounds.Width - backTextWidth) / 2), (int)(backBounds.Y + 4), 20, Color.DarkGray);
btnBackPressed = backHover && IsMouseButtonReleased(MouseButton.Left);
// Current directory label
DrawText(directory, 40 + 48 + 10, 16, 20, Color.DarkGray);
// File list view
Rectangle listBounds = new Rectangle(0, 50, GetScreenWidth(), GetScreenHeight() - 50);
DrawRectangleRec(listBounds, Color.RayWhite);
DrawRectangleLinesEx(listBounds, 1, Color.Gray);
int count = (int)files.Count;
float rowHeight = 28;
int visibleRows = (int)(listBounds.Height / rowHeight);
bool mouseInList = CheckCollisionPointRec(mouse, listBounds);
if (mouseInList)
{
listScrollIndex -= (int)GetMouseWheelMove();
}
int maxScroll = Math.Max(0, count - visibleRows);
listScrollIndex = Math.Clamp(listScrollIndex, 0, maxScroll);
listItemFocused = -1;
for (int i = 0; i < visibleRows; i++)
{
int itemIndex = listScrollIndex + i;
if (itemIndex >= count)
{
break;
}
Rectangle rowRec = new Rectangle(listBounds.X + 1, listBounds.Y + 1 + i * rowHeight, listBounds.Width - 2, rowHeight);
bool rowHover = mouseInList && CheckCollisionPointRec(mouse, rowRec);
if (itemIndex == listItemActive)
{
DrawRectangleRec(rowRec, Fade(Color.SkyBlue, 0.7f));
}
else if (rowHover)
{
DrawRectangleRec(rowRec, Fade(Color.SkyBlue, 0.3f));
}
if (rowHover)
{
listItemFocused = itemIndex;
if (IsMouseButtonReleased(MouseButton.Left))
{
listItemActive = itemIndex;
}
}
DrawText(files[(uint)itemIndex], (int)rowRec.X + 40, (int)rowRec.Y + 8, 10, Color.DarkGray);
}
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
// De-Initialization
//--------------------------------------------------------------------------------------
UnloadDirectoryFiles(files);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [core] example - directory files");
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new DirectoryFiles();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -20,6 +20,7 @@ using static Raylib_cs.Raylib;
namespace Examples.Core;
[ExcludeFromBrowser]
public partial class DropFiles : IExample
{
private const int screenWidth = 800;

View file

@ -0,0 +1,173 @@
/*******************************************************************************************
*
* raylib [core] example - highdpi demo
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 5.0, last time updated with raylib 5.5
*
* Example contributed by Jonathan Marler (@marler8997) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Jonathan Marler (@marler8997)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Core;
[ExcludeFromBrowser("monitor/DPI APIs are meaningless on the fixed wasm canvas")]
public partial class HighDpiDemo : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Core / High DPI Demo";
public string Title => "raylib [core] example - highdpi demo";
public ConfigFlags ConfigFlags => ConfigFlags.HighDpiWindow | ConfigFlags.ResizableWindow;
private int logicalGridDescY;
private int logicalGridLabelY;
private int logicalGridTop;
private int logicalGridBottom;
private int pixelGridTop;
private int pixelGridBottom;
private int pixelGridLabelY;
private int pixelGridDescY;
private int cellSize;
private float cellSizePx;
public void Init()
{
SetWindowMinSize(450, 450);
logicalGridDescY = 120;
logicalGridLabelY = logicalGridDescY + 30;
logicalGridTop = logicalGridLabelY + 30;
logicalGridBottom = logicalGridTop + 80;
pixelGridTop = logicalGridBottom - 20;
pixelGridBottom = pixelGridTop + 80;
pixelGridLabelY = pixelGridBottom + 30;
pixelGridDescY = pixelGridLabelY + 30;
cellSize = 50;
cellSizePx = (float)cellSize;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
int monitorCount = GetMonitorCount();
if ((monitorCount > 1) && IsKeyPressed(KeyboardKey.N))
{
SetWindowMonitor((GetCurrentMonitor() + 1) % monitorCount);
}
int currentMonitor = GetCurrentMonitor();
Vector2 dpiScale = GetWindowScaleDPI();
cellSizePx = ((float)cellSize) / dpiScale.X;
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
int windowCenter = GetScreenWidth() / 2;
DrawTextCenter($"Dpi Scale: {dpiScale.X:F6}", windowCenter, 30, 40, Color.DarkGray);
DrawTextCenter($"Monitor: {currentMonitor + 1}/{monitorCount} ([N] next monitor)", windowCenter, 70, 20, Color.LightGray);
DrawTextCenter($"Window is {GetScreenWidth()} \"logical points\" wide", windowCenter, logicalGridDescY, 20, Color.Orange);
bool odd = true;
for (int i = cellSize; i < GetScreenWidth(); i += cellSize, odd = !odd)
{
if (odd)
{
DrawRectangle(i, logicalGridTop, cellSize, logicalGridBottom - logicalGridTop, Color.Orange);
}
DrawTextCenter($"{i}", i, logicalGridLabelY, 10, Color.LightGray);
DrawLine(i, logicalGridLabelY + 10, i, logicalGridBottom, Color.Gray);
}
odd = true;
const int minTextSpace = 30;
int lastTextX = -minTextSpace;
for (int i = cellSize; i < GetRenderWidth(); i += cellSize, odd = !odd)
{
int x = (int)(((float)i) / dpiScale.X);
if (odd)
{
DrawRectangle(x, pixelGridTop, (int)cellSizePx, pixelGridBottom - pixelGridTop, new Color(0, 121, 241, 100));
}
DrawLine(x, pixelGridTop, (int)(((float)i) / dpiScale.X), pixelGridLabelY - 10, Color.Gray);
if ((x - lastTextX) >= minTextSpace)
{
DrawTextCenter($"{i}", x, pixelGridLabelY, 10, Color.LightGray);
lastTextX = x;
}
}
DrawTextCenter($"Window is {GetRenderWidth()} \"physical pixels\" wide", windowCenter, pixelGridDescY, 20, Color.Blue);
string text = "Can you see this?";
Vector2 size = MeasureTextEx(GetFontDefault(), text, 20, 3);
Vector2 pos = new Vector2(GetScreenWidth() - size.X - 5, GetScreenHeight() - size.Y - 5);
DrawTextEx(GetFontDefault(), text, pos, 20, 3, Color.LightGray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
// Draw text centered on the given position
private static void DrawTextCenter(string text, int x, int y, int fontSize, Color color)
{
Vector2 size = MeasureTextEx(GetFontDefault(), text, (float)fontSize, 3);
Vector2 pos = new Vector2(x - size.X / 2, y - size.Y / 2);
DrawTextEx(GetFontDefault(), text, pos, (float)fontSize, 3, color);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
SetConfigFlags(ConfigFlags.HighDpiWindow | ConfigFlags.ResizableWindow);
InitWindow(screenWidth, screenHeight, "raylib [core] example - highdpi demo");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new HighDpiDemo();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,145 @@
/*******************************************************************************************
*
* raylib [core] example - highdpi testbed
*
* Example complexity rating: [] 1/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by Ramon Santamaria (@raysan5) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Core;
[ExcludeFromBrowser("fullscreen/borderless/monitor APIs are meaningless on the fixed wasm canvas")]
public partial class HighDpiTestbed : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Core / High DPI Testbed";
public string Title => "raylib [core] example - highdpi testbed";
public ConfigFlags ConfigFlags => ConfigFlags.ResizableWindow | ConfigFlags.HighDpiWindow;
private Vector2 scaleDpi;
private Vector2 mousePos;
private int currentMonitor;
private Vector2 windowPos;
private int gridSpacing; // Grid spacing in pixels
public void Init()
{
scaleDpi = GetWindowScaleDPI();
mousePos = GetMousePosition();
currentMonitor = GetCurrentMonitor();
windowPos = GetWindowPosition();
gridSpacing = 40;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
mousePos = GetMousePosition();
currentMonitor = GetCurrentMonitor();
scaleDpi = GetWindowScaleDPI();
windowPos = GetWindowPosition();
if (IsKeyPressed(KeyboardKey.Space))
{
ToggleBorderlessWindowed();
}
if (IsKeyPressed(KeyboardKey.F))
{
ToggleFullscreen();
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
// Draw grid
for (int h = 0; h < GetScreenHeight() / gridSpacing + 1; h++)
{
DrawText($"{h * gridSpacing:D2}", 4, h * gridSpacing - 4, 10, Color.Gray);
DrawLine(24, h * gridSpacing, GetScreenWidth(), h * gridSpacing, Color.LightGray);
}
for (int v = 0; v < GetScreenWidth() / gridSpacing + 1; v++)
{
DrawText($"{v * gridSpacing:D2}", v * gridSpacing - 10, 4, 10, Color.Gray);
DrawLine(v * gridSpacing, 20, v * gridSpacing, GetScreenHeight(), Color.LightGray);
}
// Draw UI info
DrawText($"CURRENT MONITOR: {currentMonitor + 1}/{GetMonitorCount()} ({GetMonitorWidth(currentMonitor)}x{GetMonitorHeight(currentMonitor)})", 50, 50, 20, Color.DarkGray);
DrawText($"WINDOW POSITION: {(int)windowPos.X}x{(int)windowPos.Y}", 50, 90, 20, Color.DarkGray);
DrawText($"SCREEN SIZE: {GetScreenWidth()}x{GetScreenHeight()}", 50, 130, 20, Color.DarkGray);
DrawText($"RENDER SIZE: {GetRenderWidth()}x{GetRenderHeight()}", 50, 170, 20, Color.DarkGray);
DrawText($"SCALE FACTOR: {scaleDpi.X:F2}x{scaleDpi.Y:F2}", 50, 210, 20, Color.Gray);
// Draw reference rectangles, top-left and bottom-right corners
DrawRectangle(0, 0, 30, 60, Color.Red);
DrawRectangle(GetScreenWidth() - 30, GetScreenHeight() - 60, 30, 60, Color.Blue);
// Draw mouse position
DrawCircleV(GetMousePosition(), 20, Color.Maroon);
DrawRectangleRec(new Rectangle(mousePos.X - 25, mousePos.Y, 50, 2), Color.Black);
DrawRectangleRec(new Rectangle(mousePos.X, mousePos.Y - 25, 2, 50), Color.Black);
DrawText($"[{GetMouseX()},{GetMouseY()}]", (int)mousePos.X - 44,
(mousePos.Y > GetScreenHeight() - 60) ? (int)mousePos.Y - 46 : (int)mousePos.Y + 30, 20, Color.Black);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
// TODO: Unload all loaded resources at this point
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
SetConfigFlags(ConfigFlags.ResizableWindow | ConfigFlags.HighDpiWindow);
InitWindow(screenWidth, screenHeight, "raylib [core] example - highdpi testbed");
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new HighDpiTestbed();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,224 @@
/*******************************************************************************************
*
* raylib [core] example - input actions
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 5.5, last time updated with raylib 5.6
*
* Example contributed by Jett (@JettMonstersGoBoom) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Jett (@JettMonstersGoBoom)
*
********************************************************************************************/
// Simple example for decoding input as actions, allowing remapping of input to different keys or gamepad buttons
// For example instead of using `IsKeyDown(KEY_LEFT)`, you can use `IsActionDown(ACTION_LEFT)`
// which can be reassigned to e.g. KEY_A and also assigned to a gamepad button. the action will trigger with either gamepad or keys
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Core;
public partial class InputActions : IExample
{
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
private enum ActionType
{
NoAction = 0,
ActionUp,
ActionDown,
ActionLeft,
ActionRight,
ActionFire,
MaxAction
}
// Key and button inputs
private struct ActionInput
{
public KeyboardKey Key;
public GamepadButton Button;
}
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Core / Input Actions";
public string Title => "raylib [core] example - input actions";
private int gamepadIndex; // Gamepad default index
private ActionInput[] actionInputs;
private int actionSet;
private bool releaseAction;
private Vector2 position;
private Vector2 size;
public void Init()
{
gamepadIndex = 0;
actionInputs = new ActionInput[(int)ActionType.MaxAction];
// Set default actions
actionSet = 0;
SetActionsDefault();
releaseAction = false;
position = new Vector2(400.0f, 200.0f);
size = new Vector2(40.0f, 40.0f);
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
gamepadIndex = 0; // Set gamepad being checked
if (IsActionDown(ActionType.ActionUp)) position.Y -= 2;
if (IsActionDown(ActionType.ActionDown)) position.Y += 2;
if (IsActionDown(ActionType.ActionLeft)) position.X -= 2;
if (IsActionDown(ActionType.ActionRight)) position.X += 2;
if (IsActionPressed(ActionType.ActionFire))
{
position.X = (screenWidth - size.X) / 2;
position.Y = (screenHeight - size.Y) / 2;
}
// Register release action for one frame
releaseAction = false;
if (IsActionReleased(ActionType.ActionFire)) releaseAction = true;
// Switch control scheme by pressing TAB
if (IsKeyPressed(KeyboardKey.Tab))
{
actionSet = (actionSet == 0) ? 1 : 0;
if (actionSet == 0) SetActionsDefault();
else SetActionsCursor();
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.Gray);
DrawRectangleV(position, size, releaseAction ? Color.Blue : Color.Red);
DrawText((actionSet == 0) ? "Current input set: WASD (default)" : "Current input set: Arrow keys", 10, 10, 20, Color.White);
DrawText("Use TAB key to toggles Actions keyset", 10, 50, 20, Color.Green);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
//----------------------------------------------------------------------------------
// Module Functions Definition
//----------------------------------------------------------------------------------
// Check action key/button pressed
// NOTE: Combines key pressed and gamepad button pressed in one action
private bool IsActionPressed(ActionType action)
{
bool result = false;
if (action < ActionType.MaxAction) result = (IsKeyPressed(actionInputs[(int)action].Key) || IsGamepadButtonPressed(gamepadIndex, actionInputs[(int)action].Button));
return result;
}
// Check action key/button released
// NOTE: Combines key released and gamepad button released in one action
private bool IsActionReleased(ActionType action)
{
bool result = false;
if (action < ActionType.MaxAction) result = (IsKeyReleased(actionInputs[(int)action].Key) || IsGamepadButtonReleased(gamepadIndex, actionInputs[(int)action].Button));
return result;
}
// Check action key/button down
// NOTE: Combines key down and gamepad button down in one action
private bool IsActionDown(ActionType action)
{
bool result = false;
if (action < ActionType.MaxAction) result = (IsKeyDown(actionInputs[(int)action].Key) || IsGamepadButtonDown(gamepadIndex, actionInputs[(int)action].Button));
return result;
}
// Set the "default" keyset
// NOTE: Here WASD and gamepad buttons on the left side for movement
private void SetActionsDefault()
{
actionInputs[(int)ActionType.ActionUp].Key = KeyboardKey.W;
actionInputs[(int)ActionType.ActionDown].Key = KeyboardKey.S;
actionInputs[(int)ActionType.ActionLeft].Key = KeyboardKey.A;
actionInputs[(int)ActionType.ActionRight].Key = KeyboardKey.D;
actionInputs[(int)ActionType.ActionFire].Key = KeyboardKey.Space;
actionInputs[(int)ActionType.ActionUp].Button = GamepadButton.LeftFaceUp;
actionInputs[(int)ActionType.ActionDown].Button = GamepadButton.LeftFaceDown;
actionInputs[(int)ActionType.ActionLeft].Button = GamepadButton.LeftFaceLeft;
actionInputs[(int)ActionType.ActionRight].Button = GamepadButton.LeftFaceRight;
actionInputs[(int)ActionType.ActionFire].Button = GamepadButton.RightFaceDown;
}
// Set the "alternate" keyset
// NOTE: Here cursor keys and gamepad buttons on the right side for movement
private void SetActionsCursor()
{
actionInputs[(int)ActionType.ActionUp].Key = KeyboardKey.Up;
actionInputs[(int)ActionType.ActionDown].Key = KeyboardKey.Down;
actionInputs[(int)ActionType.ActionLeft].Key = KeyboardKey.Left;
actionInputs[(int)ActionType.ActionRight].Key = KeyboardKey.Right;
actionInputs[(int)ActionType.ActionFire].Key = KeyboardKey.Space;
actionInputs[(int)ActionType.ActionUp].Button = GamepadButton.RightFaceUp;
actionInputs[(int)ActionType.ActionDown].Button = GamepadButton.RightFaceDown;
actionInputs[(int)ActionType.ActionLeft].Button = GamepadButton.RightFaceLeft;
actionInputs[(int)ActionType.ActionRight].Button = GamepadButton.RightFaceRight;
actionInputs[(int)ActionType.ActionFire].Button = GamepadButton.LeftFaceDown;
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [core] example - input actions");
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new InputActions();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,374 @@
/*******************************************************************************************
*
* raylib [core] example - keyboard testbed
*
* Example complexity rating: [] 2/4
*
* NOTE: raylib defined keys refer to ENG-US Keyboard layout,
* mapping to other layouts is up to the user
*
* Example originally created with raylib 5.6, last time updated with raylib 5.6
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2026 Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Core;
public partial class KeyboardTestbed : IExample
{
private const int KeyRecSpacing = 4; // Space in pixels between key rectangles
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Core / Keyboard Testbed";
public string Title => "raylib [core] example - keyboard testbed";
private int[] line01KeyWidths;
private int[] line01Keys;
private int[] line02KeyWidths;
private int[] line02Keys;
private int[] line03KeyWidths;
private int[] line03Keys;
private int[] line04KeyWidths;
private int[] line04Keys;
private int[] line05KeyWidths;
private int[] line05Keys;
private int[] line06KeyWidths;
private int[] line06Keys;
private Vector2 keyboardOffset;
public void Init()
{
SetExitKey(KeyboardKey.Null); // Avoid exit on KEY_ESCAPE
// Keyboard line 01
line01KeyWidths = new int[15];
for (int i = 0; i < 15; i++) line01KeyWidths[i] = 45;
line01KeyWidths[13] = 62; // PRINTSCREEN
line01Keys = new int[]
{
(int)KeyboardKey.Escape, (int)KeyboardKey.F1, (int)KeyboardKey.F2, (int)KeyboardKey.F3, (int)KeyboardKey.F4, (int)KeyboardKey.F5,
(int)KeyboardKey.F6, (int)KeyboardKey.F7, (int)KeyboardKey.F8, (int)KeyboardKey.F9, (int)KeyboardKey.F10, (int)KeyboardKey.F11,
(int)KeyboardKey.F12, (int)KeyboardKey.PrintScreen, (int)KeyboardKey.Pause
};
// Keyboard line 02
line02KeyWidths = new int[15];
for (int i = 0; i < 15; i++) line02KeyWidths[i] = 45;
line02KeyWidths[0] = 25; // GRAVE
line02KeyWidths[13] = 82; // BACKSPACE
line02Keys = new int[]
{
(int)KeyboardKey.Grave, (int)KeyboardKey.One, (int)KeyboardKey.Two, (int)KeyboardKey.Three, (int)KeyboardKey.Four,
(int)KeyboardKey.Five, (int)KeyboardKey.Six, (int)KeyboardKey.Seven, (int)KeyboardKey.Eight, (int)KeyboardKey.Nine,
(int)KeyboardKey.Zero, (int)KeyboardKey.Minus, (int)KeyboardKey.Equal, (int)KeyboardKey.Backspace, (int)KeyboardKey.Delete
};
// Keyboard line 03
line03KeyWidths = new int[15];
for (int i = 0; i < 15; i++) line03KeyWidths[i] = 45;
line03KeyWidths[0] = 50; // TAB
line03KeyWidths[13] = 57; // BACKSLASH
line03Keys = new int[]
{
(int)KeyboardKey.Tab, (int)KeyboardKey.Q, (int)KeyboardKey.W, (int)KeyboardKey.E, (int)KeyboardKey.R, (int)KeyboardKey.T, (int)KeyboardKey.Y,
(int)KeyboardKey.U, (int)KeyboardKey.I, (int)KeyboardKey.O, (int)KeyboardKey.P, (int)KeyboardKey.LeftBracket,
(int)KeyboardKey.RightBracket, (int)KeyboardKey.Backslash, (int)KeyboardKey.Insert
};
// Keyboard line 04
line04KeyWidths = new int[14];
for (int i = 0; i < 14; i++) line04KeyWidths[i] = 45;
line04KeyWidths[0] = 68; // CAPS
line04KeyWidths[12] = 88; // ENTER
line04Keys = new int[]
{
(int)KeyboardKey.CapsLock, (int)KeyboardKey.A, (int)KeyboardKey.S, (int)KeyboardKey.D, (int)KeyboardKey.F, (int)KeyboardKey.G,
(int)KeyboardKey.H, (int)KeyboardKey.J, (int)KeyboardKey.K, (int)KeyboardKey.L, (int)KeyboardKey.Semicolon,
(int)KeyboardKey.Apostrophe, (int)KeyboardKey.Enter, (int)KeyboardKey.PageUp
};
// Keyboard line 05
line05KeyWidths = new int[14];
for (int i = 0; i < 14; i++) line05KeyWidths[i] = 45;
line05KeyWidths[0] = 80; // LSHIFT
line05KeyWidths[11] = 76; // RSHIFT
line05Keys = new int[]
{
(int)KeyboardKey.LeftShift, (int)KeyboardKey.Z, (int)KeyboardKey.X, (int)KeyboardKey.C, (int)KeyboardKey.V, (int)KeyboardKey.B,
(int)KeyboardKey.N, (int)KeyboardKey.M, (int)KeyboardKey.Comma, (int)KeyboardKey.Period, /*KEY_MINUS*/
(int)KeyboardKey.Slash, (int)KeyboardKey.RightShift, (int)KeyboardKey.Up, (int)KeyboardKey.PageDown
};
// Keyboard line 06
line06KeyWidths = new int[11];
for (int i = 0; i < 11; i++) line06KeyWidths[i] = 45;
line06KeyWidths[0] = 80; // LCTRL
line06KeyWidths[3] = 208; // SPACE
line06KeyWidths[7] = 60; // RCTRL
line06Keys = new int[]
{
(int)KeyboardKey.LeftControl, (int)KeyboardKey.LeftSuper, (int)KeyboardKey.LeftAlt,
(int)KeyboardKey.Space, (int)KeyboardKey.RightAlt, 162, (int)KeyboardKey.Null,
(int)KeyboardKey.RightControl, (int)KeyboardKey.Left, (int)KeyboardKey.Down, (int)KeyboardKey.Right
};
keyboardOffset = new Vector2(26, 80);
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
int key = GetKeyPressed(); // Get pressed keycode
if (key > 0) TraceLog(TraceLogLevel.Info, $"KEYBOARD TESTBED: KEY PRESSED: {key}");
int ch = GetCharPressed(); // Get pressed char for text input, using OS mapping
if (ch > 0) TraceLog(TraceLogLevel.Info, $"KEYBOARD TESTBED: CHAR PRESSED: {(char)ch} ({ch})");
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
DrawText("KEYBOARD LAYOUT: ENG-US", 26, 38, 20, Color.LightGray);
// Keyboard line 01 - 15 keys
// ESC, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, IMP, CLOSE
for (int i = 0, recOffsetX = 0; i < 15; i++)
{
GuiKeyboardKey(new Rectangle(keyboardOffset.X + recOffsetX, keyboardOffset.Y, (float)line01KeyWidths[i], 30.0f), line01Keys[i]);
recOffsetX += line01KeyWidths[i] + KeyRecSpacing;
}
// Keyboard line 02 - 15 keys
// `, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, -, =, BACKSPACE, DEL
for (int i = 0, recOffsetX = 0; i < 15; i++)
{
GuiKeyboardKey(new Rectangle(keyboardOffset.X + recOffsetX, keyboardOffset.Y + 30 + KeyRecSpacing, (float)line02KeyWidths[i], 38.0f), line02Keys[i]);
recOffsetX += line02KeyWidths[i] + KeyRecSpacing;
}
// Keyboard line 03 - 15 keys
// TAB, Q, W, E, R, T, Y, U, I, O, P, [, ], \, INS
for (int i = 0, recOffsetX = 0; i < 15; i++)
{
GuiKeyboardKey(new Rectangle(keyboardOffset.X + recOffsetX, keyboardOffset.Y + 30 + 38 + KeyRecSpacing * 2, (float)line03KeyWidths[i], 38.0f), line03Keys[i]);
recOffsetX += line03KeyWidths[i] + KeyRecSpacing;
}
// Keyboard line 04 - 14 keys
// MAYUS, A, S, D, F, G, H, J, K, L, ;, ', ENTER, REPAG
for (int i = 0, recOffsetX = 0; i < 14; i++)
{
GuiKeyboardKey(new Rectangle(keyboardOffset.X + recOffsetX, keyboardOffset.Y + 30 + 38 * 2 + KeyRecSpacing * 3, (float)line04KeyWidths[i], 38.0f), line04Keys[i]);
recOffsetX += line04KeyWidths[i] + KeyRecSpacing;
}
// Keyboard line 05 - 14 keys
// LSHIFT, Z, X, C, V, B, N, M, ,, ., /, RSHIFT, UP, AVPAG
for (int i = 0, recOffsetX = 0; i < 14; i++)
{
GuiKeyboardKey(new Rectangle(keyboardOffset.X + recOffsetX, keyboardOffset.Y + 30 + 38 * 3 + KeyRecSpacing * 4, (float)line05KeyWidths[i], 38.0f), line05Keys[i]);
recOffsetX += line05KeyWidths[i] + KeyRecSpacing;
}
// Keyboard line 06 - 11 keys
// LCTRL, WIN, LALT, SPACE, ALTGR, \, FN, RCTRL, LEFT, DOWN, RIGHT
for (int i = 0, recOffsetX = 0; i < 11; i++)
{
GuiKeyboardKey(new Rectangle(keyboardOffset.X + recOffsetX, keyboardOffset.Y + 30 + 38 * 4 + KeyRecSpacing * 5, (float)line06KeyWidths[i], 38.0f), line06Keys[i]);
recOffsetX += line06KeyWidths[i] + KeyRecSpacing;
}
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
//------------------------------------------------------------------------------------
// Module Functions Definition
//------------------------------------------------------------------------------------
// Get keyboard keycode as text (US keyboard)
// NOTE: Mapping for other keyboard layouts can be done here
private static string GetKeyText(int key)
{
switch ((KeyboardKey)key)
{
case KeyboardKey.Apostrophe: return "'"; // Key: '
case KeyboardKey.Comma: return ","; // Key: ,
case KeyboardKey.Minus: return "-"; // Key: -
case KeyboardKey.Period: return "."; // Key: .
case KeyboardKey.Slash: return "/"; // Key: /
case KeyboardKey.Zero: return "0"; // Key: 0
case KeyboardKey.One: return "1"; // Key: 1
case KeyboardKey.Two: return "2"; // Key: 2
case KeyboardKey.Three: return "3"; // Key: 3
case KeyboardKey.Four: return "4"; // Key: 4
case KeyboardKey.Five: return "5"; // Key: 5
case KeyboardKey.Six: return "6"; // Key: 6
case KeyboardKey.Seven: return "7"; // Key: 7
case KeyboardKey.Eight: return "8"; // Key: 8
case KeyboardKey.Nine: return "9"; // Key: 9
case KeyboardKey.Semicolon: return ";"; // Key: ;
case KeyboardKey.Equal: return "="; // Key: =
case KeyboardKey.A: return "A"; // Key: A | a
case KeyboardKey.B: return "B"; // Key: B | b
case KeyboardKey.C: return "C"; // Key: C | c
case KeyboardKey.D: return "D"; // Key: D | d
case KeyboardKey.E: return "E"; // Key: E | e
case KeyboardKey.F: return "F"; // Key: F | f
case KeyboardKey.G: return "G"; // Key: G | g
case KeyboardKey.H: return "H"; // Key: H | h
case KeyboardKey.I: return "I"; // Key: I | i
case KeyboardKey.J: return "J"; // Key: J | j
case KeyboardKey.K: return "K"; // Key: K | k
case KeyboardKey.L: return "L"; // Key: L | l
case KeyboardKey.M: return "M"; // Key: M | m
case KeyboardKey.N: return "N"; // Key: N | n
case KeyboardKey.O: return "O"; // Key: O | o
case KeyboardKey.P: return "P"; // Key: P | p
case KeyboardKey.Q: return "Q"; // Key: Q | q
case KeyboardKey.R: return "R"; // Key: R | r
case KeyboardKey.S: return "S"; // Key: S | s
case KeyboardKey.T: return "T"; // Key: T | t
case KeyboardKey.U: return "U"; // Key: U | u
case KeyboardKey.V: return "V"; // Key: V | v
case KeyboardKey.W: return "W"; // Key: W | w
case KeyboardKey.X: return "X"; // Key: X | x
case KeyboardKey.Y: return "Y"; // Key: Y | y
case KeyboardKey.Z: return "Z"; // Key: Z | z
case KeyboardKey.LeftBracket: return "["; // Key: [
case KeyboardKey.Backslash: return "\\"; // Key: '\'
case KeyboardKey.RightBracket: return "]"; // Key: ]
case KeyboardKey.Grave: return "`"; // Key: `
case KeyboardKey.Space: return "SPACE"; // Key: Space
case KeyboardKey.Escape: return "ESC"; // Key: Esc
case KeyboardKey.Enter: return "ENTER"; // Key: Enter
case KeyboardKey.Tab: return "TAB"; // Key: Tab
case KeyboardKey.Backspace: return "BACK"; // Key: Backspace
case KeyboardKey.Insert: return "INS"; // Key: Ins
case KeyboardKey.Delete: return "DEL"; // Key: Del
case KeyboardKey.Right: return "RIGHT"; // Key: Cursor right
case KeyboardKey.Left: return "LEFT"; // Key: Cursor left
case KeyboardKey.Down: return "DOWN"; // Key: Cursor down
case KeyboardKey.Up: return "UP"; // Key: Cursor up
case KeyboardKey.PageUp: return "PGUP"; // Key: Page up
case KeyboardKey.PageDown: return "PGDOWN"; // Key: Page down
case KeyboardKey.Home: return "HOME"; // Key: Home
case KeyboardKey.End: return "END"; // Key: End
case KeyboardKey.CapsLock: return "CAPS"; // Key: Caps lock
case KeyboardKey.ScrollLock: return "LOCK"; // Key: Scroll down
case KeyboardKey.NumLock: return "NUMLOCK"; // Key: Num lock
case KeyboardKey.PrintScreen: return "PRINTSCR"; // Key: Print screen
case KeyboardKey.Pause: return "PAUSE"; // Key: Pause
case KeyboardKey.F1: return "F1"; // Key: F1
case KeyboardKey.F2: return "F2"; // Key: F2
case KeyboardKey.F3: return "F3"; // Key: F3
case KeyboardKey.F4: return "F4"; // Key: F4
case KeyboardKey.F5: return "F5"; // Key: F5
case KeyboardKey.F6: return "F6"; // Key: F6
case KeyboardKey.F7: return "F7"; // Key: F7
case KeyboardKey.F8: return "F8"; // Key: F8
case KeyboardKey.F9: return "F9"; // Key: F9
case KeyboardKey.F10: return "F10"; // Key: F10
case KeyboardKey.F11: return "F11"; // Key: F11
case KeyboardKey.F12: return "F12"; // Key: F12
case KeyboardKey.LeftShift: return "LSHIFT"; // Key: Shift left
case KeyboardKey.LeftControl: return "LCTRL"; // Key: Control left
case KeyboardKey.LeftAlt: return "LALT"; // Key: Alt left
case KeyboardKey.LeftSuper: return "WIN"; // Key: Super left
case KeyboardKey.RightShift: return "RSHIFT"; // Key: Shift right
case KeyboardKey.RightControl: return "RCTRL"; // Key: Control right
case KeyboardKey.RightAlt: return "ALTGR"; // Key: Alt right
case KeyboardKey.RightSuper: return "RSUPER"; // Key: Super right
case KeyboardKey.KeyboardMenu: return "KBMENU"; // Key: KB menu
case KeyboardKey.Kp0: return "KP0"; // Key: Keypad 0
case KeyboardKey.Kp1: return "KP1"; // Key: Keypad 1
case KeyboardKey.Kp2: return "KP2"; // Key: Keypad 2
case KeyboardKey.Kp3: return "KP3"; // Key: Keypad 3
case KeyboardKey.Kp4: return "KP4"; // Key: Keypad 4
case KeyboardKey.Kp5: return "KP5"; // Key: Keypad 5
case KeyboardKey.Kp6: return "KP6"; // Key: Keypad 6
case KeyboardKey.Kp7: return "KP7"; // Key: Keypad 7
case KeyboardKey.Kp8: return "KP8"; // Key: Keypad 8
case KeyboardKey.Kp9: return "KP9"; // Key: Keypad 9
case KeyboardKey.KpDecimal: return "KPDEC"; // Key: Keypad .
case KeyboardKey.KpDivide: return "KPDIV"; // Key: Keypad /
case KeyboardKey.KpMultiply: return "KPMUL"; // Key: Keypad *
case KeyboardKey.KpSubtract: return "KPSUB"; // Key: Keypad -
case KeyboardKey.KpAdd: return "KPADD"; // Key: Keypad +
case KeyboardKey.KpEnter: return "KPENTER"; // Key: Keypad Enter
case KeyboardKey.KpEqual: return "KPEQU"; // Key: Keypad =
default: return "";
}
}
// Draw keyboard key
private static void GuiKeyboardKey(Rectangle bounds, int key)
{
if (key == (int)KeyboardKey.Null) DrawRectangleLinesEx(bounds, 2.0f, Color.LightGray);
else
{
if (IsKeyDown((KeyboardKey)key))
{
DrawRectangleLinesEx(bounds, 2.0f, Color.Maroon);
DrawText(GetKeyText(key), (int)(bounds.X + 4), (int)(bounds.Y + 4), 10, Color.Maroon);
}
else
{
DrawRectangleLinesEx(bounds, 2.0f, Color.DarkGray);
DrawText(GetKeyText(key), (int)(bounds.X + 4), (int)(bounds.Y + 4), 10, Color.DarkGray);
}
}
if (CheckCollisionPointRec(GetMousePosition(), bounds))
{
DrawRectangleRec(bounds, Fade(Color.Red, 0.2f));
DrawRectangleLinesEx(bounds, 3.0f, Color.Red);
}
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [core] example - keyboard testbed");
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new KeyboardTestbed();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -18,6 +18,7 @@ using static Raylib_cs.Raylib;
namespace Examples.Core;
[ExcludeFromBrowser("System.Threading.Thread is unsupported on single-threaded wasm")]
public partial class LoadingThread : IExample
{
const int screenWidth = 800;

View file

@ -0,0 +1,209 @@
/*******************************************************************************************
*
* raylib [core] example - monitor detector
*
* Example complexity rating: [] 1/4
*
* Example originally created with raylib 5.5, last time updated with raylib 5.6
*
* Example contributed by Maicon Santana (@maiconpintoabreu) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Maicon Santana (@maiconpintoabreu)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Core;
[ExcludeFromBrowser("GetMonitorCount() is not implemented on the wasm target")]
public partial class MonitorDetector : IExample
{
private const int MaxMonitors = 10;
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Core / Monitor Detector";
public string Title => "raylib [core] example - monitor detector";
// Monitor info
private struct MonitorInfo
{
public Vector2 Position;
public string Name;
public int Width;
public int Height;
public int PhysicalWidth;
public int PhysicalHeight;
public int RefreshRate;
}
private MonitorInfo[] monitors;
private int currentMonitorIndex;
private int monitorCount;
public void Init()
{
monitors = new MonitorInfo[MaxMonitors];
currentMonitorIndex = GetCurrentMonitor();
monitorCount = 0;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
// Variables to find the max x and Y to calculate the scale
int maxWidth = 1;
int maxHeight = 1;
// Monitor offset is to fix when monitor position x is negative
int monitorOffsetX = 0;
// Rebuild monitors array every frame
monitorCount = GetMonitorCount();
for (int i = 0; i < monitorCount; i++)
{
monitors[i] = new MonitorInfo
{
Position = GetMonitorPosition(i),
Name = GetMonitorName_(i),
Width = GetMonitorWidth(i),
Height = GetMonitorHeight(i),
PhysicalWidth = GetMonitorPhysicalWidth(i),
PhysicalHeight = GetMonitorPhysicalHeight(i),
RefreshRate = GetMonitorRefreshRate(i)
};
if (monitors[i].Position.X < monitorOffsetX)
{
monitorOffsetX = -(int)monitors[i].Position.X;
}
int width = (int)monitors[i].Position.X + monitors[i].Width;
int height = (int)monitors[i].Position.Y + monitors[i].Height;
if (maxWidth < width)
{
maxWidth = width;
}
if (maxHeight < height)
{
maxHeight = height;
}
}
if (IsKeyPressed(KeyboardKey.Enter) && (monitorCount > 1))
{
currentMonitorIndex += 1;
// Set index to 0 if the last one
if (currentMonitorIndex == monitorCount)
{
currentMonitorIndex = 0;
}
SetWindowMonitor(currentMonitorIndex); // Move window to currentMonitorIndex
}
else
{
currentMonitorIndex = GetCurrentMonitor(); // Get currentMonitorIndex if manually moved
}
float monitorScale = 0.6f;
if (maxHeight > (maxWidth + monitorOffsetX))
{
monitorScale *= ((float)screenHeight / (float)maxHeight);
}
else
{
monitorScale *= ((float)screenWidth / (float)(maxWidth + monitorOffsetX));
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
DrawText("Press [Enter] to move window to next monitor available", 20, 20, 20, Color.DarkGray);
DrawRectangleLines(20, 60, screenWidth - 40, screenHeight - 100, Color.DarkGray);
// Draw Monitor Rectangles with information inside
for (int i = 0; i < monitorCount; i++)
{
// Calculate retangle position and size using monitorScale
Rectangle rec = new Rectangle(
(monitors[i].Position.X + monitorOffsetX) * monitorScale + 140,
monitors[i].Position.Y * monitorScale + 80,
monitors[i].Width * monitorScale,
monitors[i].Height * monitorScale
);
// Draw monitor name and information inside the rectangle
DrawText($"[{i}] {monitors[i].Name}", (int)rec.X + 10, (int)rec.Y + (int)(100 * monitorScale), (int)(120 * monitorScale), Color.Blue);
DrawText(
$"Resolution: [{monitors[i].Width}px x {monitors[i].Height}px]\nRefreshRate: [{monitors[i].RefreshRate}hz]\nPhysical Size: [{monitors[i].PhysicalWidth}mm x {monitors[i].PhysicalHeight}mm]\nPosition: {monitors[i].Position.X,3:F0} x {monitors[i].Position.Y,3:F0}",
(int)rec.X + 10, (int)rec.Y + (int)(200 * monitorScale), (int)(120 * monitorScale), Color.DarkGray);
// Highlight current monitor
if (i == currentMonitorIndex)
{
DrawRectangleLinesEx(rec, 5, Color.Red);
Vector2 windowPosition = new Vector2((GetWindowPosition().X + monitorOffsetX) * monitorScale + 140, GetWindowPosition().Y * monitorScale + 80);
// Draw window position based on monitors
DrawRectangleV(windowPosition, new Vector2(screenWidth * monitorScale, screenHeight * monitorScale), Fade(Color.Green, 0.5f));
}
else
{
DrawRectangleLinesEx(rec, 5, Color.Gray);
}
}
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [core] example - monitor detector");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new MonitorDetector();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -27,8 +27,6 @@ public partial class Picking3d : IExample
public string Title => "raylib [core] example - 3d picking";
public bool CursorDisabled => true;
private Camera3D camera;
private Vector3 cubePosition;
private Vector3 cubeSize;

View file

@ -0,0 +1,183 @@
/*******************************************************************************************
*
* raylib [core] example - random sequence
*
* Example complexity rating: [] 1/4
*
* Example originally created with raylib 5.0, last time updated with raylib 5.0
*
* Example contributed by Dalton Overmyer (@REDl3east) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2023-2025 Dalton Overmyer (@REDl3east)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Raymath;
namespace Examples.Core;
public partial class RandomSequence : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Core / Random Sequence";
public string Title => "raylib [core] example - random sequence";
private struct ColorRect
{
public Color Color;
public Rectangle Rect;
}
private int rectCount;
private float rectSize;
private ColorRect[] rectangles;
public void Init()
{
rectCount = 20;
rectSize = (float)screenWidth / rectCount;
rectangles = GenerateRandomColorRectSequence(rectCount, rectSize, screenWidth, 0.75f * screenHeight);
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
if (IsKeyPressed(KeyboardKey.Space))
{
ShuffleColorRectSequence(rectangles, rectCount);
}
if (IsKeyPressed(KeyboardKey.Up))
{
rectCount++;
rectSize = (float)screenWidth / rectCount;
// Re-generate random sequence with new count
rectangles = GenerateRandomColorRectSequence(rectCount, rectSize, screenWidth, 0.75f * screenHeight);
}
if (IsKeyPressed(KeyboardKey.Down))
{
if (rectCount >= 4)
{
rectCount--;
rectSize = (float)screenWidth / rectCount;
// Re-generate random sequence with new count
rectangles = GenerateRandomColorRectSequence(rectCount, rectSize, screenWidth, 0.75f * screenHeight);
}
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
for (int i = 0; i < rectCount; i++)
{
DrawRectangleRec(rectangles[i].Rect, rectangles[i].Color);
DrawText("Press SPACE to shuffle the current sequence", 10, screenHeight - 96, 20, Color.Black);
DrawText("Press UP to add a rectangle and generate a new sequence", 10, screenHeight - 64, 20, Color.Black);
DrawText("Press DOWN to remove a rectangle and generate a new sequence", 10, screenHeight - 32, 20, Color.Black);
}
DrawText($"Count: {rectCount} rectangles", 10, 10, 20, Color.Maroon);
DrawFPS(screenWidth - 80, 10);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
private static Color GenerateRandomColor()
{
return new Color(
GetRandomValue(0, 255),
GetRandomValue(0, 255),
GetRandomValue(0, 255),
255
);
}
private static ColorRect[] GenerateRandomColorRectSequence(float rectCount, float rectWidth, float screenWidth, float screenHeight)
{
ColorRect[] rectangles = new ColorRect[(int)rectCount];
int[] seq = GetRandomSequence((uint)rectCount, 0, (int)rectCount - 1);
float rectSeqWidth = rectCount * rectWidth;
float startX = (screenWidth - rectSeqWidth) * 0.5f;
for (int i = 0; i < rectCount; i++)
{
int rectHeight = (int)Remap(seq[i], 0, rectCount - 1, 0, screenHeight);
rectangles[i].Color = GenerateRandomColor();
rectangles[i].Rect = new Rectangle(startX + i * rectWidth, screenHeight - rectHeight, rectWidth, rectHeight);
}
return rectangles;
}
private static void ShuffleColorRectSequence(ColorRect[] rectangles, int rectCount)
{
int[] seq = GetRandomSequence((uint)rectCount, 0, rectCount - 1);
for (int i1 = 0; i1 < rectCount; i1++)
{
int i2 = seq[i1];
// Swap only the color and height
ColorRect tmp = rectangles[i1];
rectangles[i1].Color = rectangles[i2].Color;
rectangles[i1].Rect.Height = rectangles[i2].Rect.Height;
rectangles[i1].Rect.Y = rectangles[i2].Rect.Y;
rectangles[i2].Color = tmp.Color;
rectangles[i2].Rect.Height = tmp.Rect.Height;
rectangles[i2].Rect.Y = tmp.Rect.Y;
}
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [core] example - random sequence");
SetTargetFPS(60);
var game = new RandomSequence();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,139 @@
/*******************************************************************************************
*
* raylib [core] example - render texture
*
* Example complexity rating: [] 1/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Core;
public partial class RenderTextureDemo : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
// Define a render texture to render
private const int renderTextureWidth = 300;
private const int renderTextureHeight = 300;
public string Name => "Core / Render Texture";
public string Title => "raylib [core] example - render texture";
private RenderTexture2D target;
private Vector2 ballPosition;
private Vector2 ballSpeed;
private int ballRadius;
private float rotation;
public void Init()
{
target = LoadRenderTexture(renderTextureWidth, renderTextureHeight);
ballPosition = new Vector2(renderTextureWidth / 2.0f, renderTextureHeight / 2.0f);
ballSpeed = new Vector2(5.0f, 4.0f);
ballRadius = 20;
rotation = 0.0f;
}
public void Update()
{
// Update
//-----------------------------------------------------
// Ball movement logic
ballPosition.X += ballSpeed.X;
ballPosition.Y += ballSpeed.Y;
// Check walls collision for bouncing
if ((ballPosition.X >= (renderTextureWidth - ballRadius)) || (ballPosition.X <= ballRadius))
{
ballSpeed.X *= -1.0f;
}
if ((ballPosition.Y >= (renderTextureHeight - ballRadius)) || (ballPosition.Y <= ballRadius))
{
ballSpeed.Y *= -1.0f;
}
// Render texture rotation
rotation += 0.5f;
//-----------------------------------------------------
// Draw
//-----------------------------------------------------
// Draw our scene to the render texture
BeginTextureMode(target);
ClearBackground(Color.SkyBlue);
DrawRectangle(0, 0, 20, 20, Color.Red);
DrawCircleV(ballPosition, ballRadius, Color.Maroon);
EndTextureMode();
// Draw render texture to main framebuffer
BeginDrawing();
ClearBackground(Color.RayWhite);
// Draw our render texture with rotation applied
// NOTE 1: We set the origin of the texture to the center of the render texture
// NOTE 2: We flip vertically the texture setting negative source rectangle height
DrawTexturePro(target.Texture,
new Rectangle(0, 0, target.Texture.Width, -target.Texture.Height),
new Rectangle(screenWidth / 2.0f, screenHeight / 2.0f, target.Texture.Width, target.Texture.Height),
new Vector2(target.Texture.Width / 2.0f, target.Texture.Height / 2.0f), rotation, Color.White);
DrawText("DRAWING BOUNCING BALL INSIDE RENDER TEXTURE!", 10, screenHeight - 40, 20, Color.Black);
DrawFPS(10, 10);
EndDrawing();
//-----------------------------------------------------
}
public void Unload()
{
UnloadRenderTexture(target);
}
public static int Main()
{
// Initialization
//---------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [core] example - render texture");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//----------------------------------------------------------
var game = new RenderTextureDemo();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//---------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//----------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,382 @@
/*******************************************************************************************
*
* raylib [core] example - screen recording
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using System;
using System.Collections.Generic;
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Core;
// NOTE: The upstream C example records frames into an animated GIF using the bundled msf_gif.h
// single-header library. raylib-cs does not bind msf_gif, so this port replaces it with a small,
// self-contained GIF89a encoder (GifRecorder, below) that uses a fixed 3-3-2 RGB palette. The
// rest of the example (rendering, CTRL+R toggle, saving to <appdir>/screenrecording.gif) mirrors
// upstream. Frame capture via LoadImageFromScreen() is slow and can cause stuttering, as noted
// upstream.
[ExcludeFromBrowser("desktop screen capture + gif file export, no web equivalent")]
public partial class ScreenRecording : IExample
{
private const int GIF_RECORD_FRAMERATE = 5; // Record framerate, we get a frame every N frames
private const int MAX_SINEWAVE_POINTS = 256;
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Core / Screen Recording";
public string Title => "raylib [core] example - screen recording";
private bool gifRecording; // GIF recording state
private uint gifFrameCounter; // GIF frames counter
private GifRecorder gifState; // GIF context state
private Vector2 circlePosition;
private float timeCounter;
private Vector2[] sinePoints;
public void Init()
{
gifRecording = false;
gifFrameCounter = 0;
gifState = new GifRecorder();
circlePosition = new Vector2(0.0f, screenHeight / 2.0f);
timeCounter = 0.0f;
// Get sine wave points for line drawing
sinePoints = new Vector2[MAX_SINEWAVE_POINTS];
for (int i = 0; i < MAX_SINEWAVE_POINTS; i++)
{
sinePoints[i].X = i * GetScreenWidth() / 180.0f;
sinePoints[i].Y = screenHeight / 2.0f + 150 * MathF.Sin((2 * MathF.PI / 1.5f) * (1.0f / 60.0f) * (float)i); // Calculate for 60 fps
}
}
public unsafe void Update()
{
// Update
//----------------------------------------------------------------------------------
// Update circle sinusoidal movement
timeCounter += GetFrameTime();
circlePosition.X += GetScreenWidth() / 180.0f;
circlePosition.Y = screenHeight / 2.0f + 150 * MathF.Sin((2 * MathF.PI / 1.5f) * timeCounter);
if (circlePosition.X > screenWidth)
{
circlePosition.X = 0.0f;
circlePosition.Y = screenHeight / 2.0f;
timeCounter = 0.0f;
}
// Start-Stop GIF recording on CTRL+R
if (IsKeyDown(KeyboardKey.LeftControl) && IsKeyPressed(KeyboardKey.R))
{
if (gifRecording)
{
// Stop current recording and save file
gifRecording = false;
byte[] result = gifState.End();
SaveFileData(result, $"{GetApplicationDirectoryString()}/screenrecording.gif");
TraceLog(TraceLogLevel.Info, "Finish animated GIF recording");
}
else
{
// Start a new recording
gifRecording = true;
gifFrameCounter = 0;
gifState.Begin(GetRenderWidth(), GetRenderHeight());
TraceLog(TraceLogLevel.Info, "Start animated GIF recording");
}
}
if (gifRecording)
{
gifFrameCounter++;
// NOTE: We record one gif frame depending on the desired gif framerate
if (gifFrameCounter > GIF_RECORD_FRAMERATE)
{
// Get image data for the current frame (from backbuffer)
// WARNING: This process is quite slow, it can generate stuttering
Image imScreen = LoadImageFromScreen();
// Add the frame to the gif recording, providing and "estimated" time for display in centiseconds
int delayCs = (int)((1.0f / 60.0f) * GIF_RECORD_FRAMERATE) / 10;
gifState.AddFrame((byte*)imScreen.Data, imScreen.Width, imScreen.Height, imScreen.Width * 4, delayCs);
gifFrameCounter = 0;
UnloadImage(imScreen); // Free image data
}
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
for (int i = 0; i < (MAX_SINEWAVE_POINTS - 1); i++)
{
DrawLineV(sinePoints[i], sinePoints[i + 1], Color.Maroon);
DrawCircleV(sinePoints[i], 3, Color.Maroon);
}
DrawCircleV(circlePosition, 30, Color.Red);
DrawFPS(10, 10);
/*
// Draw record indicator
// WARNING: If drawn here, it will appear in the recorded image,
// use a render texture instead for the recording and LoadImageFromTexture(rt.texture)
if (gifRecording)
{
// Display the recording indicator every half-second
if ((int)(GetTime()/0.5)%2 == 1)
{
DrawCircle(30, GetScreenHeight() - 20, 10, Color.Maroon);
DrawText("GIF RECORDING", 50, GetScreenHeight() - 25, 10, Color.Red);
}
}
*/
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
// If still recording a GIF on close window, just finish
if (gifRecording)
{
gifState.End();
gifRecording = false;
}
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [core] example - screen recording");
var game = new ScreenRecording();
game.Init();
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
// Minimal self-contained animated GIF89a encoder (replacement for msf_gif.h)
// Uses a fixed 3-3-2 RGB global palette and standard GIF-variant LZW compression.
private class GifRecorder
{
private List<byte> output;
private int width;
private int height;
// LZW bit-packing state (per-frame)
private int bitBuffer;
private int bitCount;
private List<byte> subBlock;
public void Begin(int w, int h)
{
width = w;
height = h;
output = new List<byte>();
// Header
output.AddRange(new byte[] { (byte)'G', (byte)'I', (byte)'F', (byte)'8', (byte)'9', (byte)'a' });
// Logical Screen Descriptor
WriteU16(width);
WriteU16(height);
output.Add(0xF7); // Global color table present, 8-bit color res, 256-entry table
output.Add(0x00); // Background color index
output.Add(0x00); // Pixel aspect ratio
// Global Color Table: 256 entries, 3-3-2 RGB
for (int k = 0; k < 256; k++)
{
int r3 = (k >> 5) & 0x7;
int g3 = (k >> 2) & 0x7;
int b2 = k & 0x3;
output.Add((byte)((r3 << 5) | (r3 << 2) | (r3 >> 1)));
output.Add((byte)((g3 << 5) | (g3 << 2) | (g3 >> 1)));
output.Add((byte)((b2 << 6) | (b2 << 4) | (b2 << 2) | b2));
}
// NETSCAPE2.0 application extension (loop forever)
output.Add(0x21);
output.Add(0xFF);
output.Add(0x0B);
output.AddRange(new byte[] { (byte)'N', (byte)'E', (byte)'T', (byte)'S', (byte)'C', (byte)'A', (byte)'P', (byte)'E', (byte)'2', (byte)'.', (byte)'0' });
output.Add(0x03);
output.Add(0x01);
WriteU16(0); // Loop count (0 = forever)
output.Add(0x00);
}
public unsafe void AddFrame(byte* data, int w, int h, int stride, int delayCs)
{
if (output == null) return;
// Graphic Control Extension
output.Add(0x21);
output.Add(0xF9);
output.Add(0x04);
output.Add(0x00); // No transparency, disposal method 0
WriteU16(delayCs);
output.Add(0x00); // Transparent color index
output.Add(0x00); // Block terminator
// Image Descriptor
output.Add(0x2C);
WriteU16(0); // Left
WriteU16(0); // Top
WriteU16(w);
WriteU16(h);
output.Add(0x00); // No local color table, not interlaced
// Map pixels to palette indices (3-3-2)
byte[] indices = new byte[w * h];
for (int y = 0; y < h; y++)
{
int row = y * stride;
int dst = y * w;
for (int x = 0; x < w; x++)
{
byte r = data[row + x * 4 + 0];
byte g = data[row + x * 4 + 1];
byte b = data[row + x * 4 + 2];
indices[dst + x] = (byte)((r & 0xE0) | ((g & 0xE0) >> 3) | (b >> 6));
}
}
// LZW image data
const int minCodeSize = 8;
output.Add((byte)minCodeSize);
bitBuffer = 0;
bitCount = 0;
subBlock = new List<byte>();
int clearCode = 1 << minCodeSize; // 256
int stopCode = clearCode + 1; // 257
int keySize = minCodeSize + 1; // 9
int nkeys = clearCode + 2; // 258
var dict = new Dictionary<int, int>();
WriteBits(clearCode, keySize);
int key = indices[0];
for (int i = 1; i < indices.Length; i++)
{
int p = indices[i];
int combined = (key << 8) | p;
if (dict.TryGetValue(combined, out int existing))
{
key = existing;
}
else
{
WriteBits(key, keySize);
dict[combined] = nkeys;
nkeys++;
if (nkeys == (1 << keySize))
{
if (keySize < 12) keySize++;
}
if (nkeys == 0x1000)
{
WriteBits(clearCode, keySize);
dict.Clear();
keySize = minCodeSize + 1;
nkeys = clearCode + 2;
}
key = p;
}
}
WriteBits(key, keySize);
WriteBits(stopCode, keySize);
// Flush remaining bits
if (bitCount > 0)
{
subBlock.Add((byte)(bitBuffer & 0xFF));
bitBuffer = 0;
bitCount = 0;
}
if (subBlock.Count > 0) FlushSubBlock();
output.Add(0x00); // Image data block terminator
}
public byte[] End()
{
if (output == null) return Array.Empty<byte>();
output.Add(0x3B); // Trailer
byte[] result = output.ToArray();
output = null;
return result;
}
private void WriteBits(int code, int len)
{
bitBuffer |= code << bitCount;
bitCount += len;
while (bitCount >= 8)
{
subBlock.Add((byte)(bitBuffer & 0xFF));
bitBuffer >>= 8;
bitCount -= 8;
if (subBlock.Count == 255) FlushSubBlock();
}
}
private void FlushSubBlock()
{
output.Add((byte)subBlock.Count);
output.AddRange(subBlock);
subBlock.Clear();
}
private void WriteU16(int value)
{
output.Add((byte)(value & 0xFF));
output.Add((byte)((value >> 8) & 0xFF));
}
}
}

View file

@ -0,0 +1,204 @@
/*******************************************************************************************
*
* raylib [core] example - 2d camera split screen
*
* Example complexity rating: [] 4/4
*
* Addapted from the core_3d_camera_split_screen example:
* https://github.com/raysan5/raylib/blob/master/examples/core/core_3d_camera_split_screen.c
*
* Example originally created with raylib 4.5, last time updated with raylib 4.5
*
* Example contributed by Gabriel dos Santos Sanches (@gabrielssanches) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2023-2025 Gabriel dos Santos Sanches (@gabrielssanches)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Core;
public partial class SplitScreen2D : IExample
{
private const int PLAYER_SIZE = 40;
private const int screenWidth = 800;
private const int screenHeight = 440;
public string Name => "Core / 2D Camera Split Screen";
public string Title => "raylib [core] example - 2d camera split screen";
public int Width => screenWidth;
public int Height => screenHeight;
private Rectangle player1;
private Rectangle player2;
private Camera2D camera1;
private Camera2D camera2;
private RenderTexture2D screenCamera1;
private RenderTexture2D screenCamera2;
private Rectangle splitScreenRect;
public void Init()
{
player1 = new Rectangle(200, 200, PLAYER_SIZE, PLAYER_SIZE);
player2 = new Rectangle(250, 200, PLAYER_SIZE, PLAYER_SIZE);
camera1 = new Camera2D();
camera1.Target = new Vector2(player1.X, player1.Y);
camera1.Offset = new Vector2(200.0f, 200.0f);
camera1.Rotation = 0.0f;
camera1.Zoom = 1.0f;
camera2 = new Camera2D();
camera2.Target = new Vector2(player2.X, player2.Y);
camera2.Offset = new Vector2(200.0f, 200.0f);
camera2.Rotation = 0.0f;
camera2.Zoom = 1.0f;
screenCamera1 = LoadRenderTexture(screenWidth / 2, screenHeight);
screenCamera2 = LoadRenderTexture(screenWidth / 2, screenHeight);
// Build a flipped rectangle the size of the split view to use for drawing later
splitScreenRect = new Rectangle(0.0f, 0.0f, (float)screenCamera1.Texture.Width, (float)-screenCamera1.Texture.Height);
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
if (IsKeyDown(KeyboardKey.S)) player1.Y += 3.0f;
else if (IsKeyDown(KeyboardKey.W)) player1.Y -= 3.0f;
if (IsKeyDown(KeyboardKey.D)) player1.X += 3.0f;
else if (IsKeyDown(KeyboardKey.A)) player1.X -= 3.0f;
if (IsKeyDown(KeyboardKey.Up)) player2.Y -= 3.0f;
else if (IsKeyDown(KeyboardKey.Down)) player2.Y += 3.0f;
if (IsKeyDown(KeyboardKey.Right)) player2.X += 3.0f;
else if (IsKeyDown(KeyboardKey.Left)) player2.X -= 3.0f;
camera1.Target = new Vector2(player1.X, player1.Y);
camera2.Target = new Vector2(player2.X, player2.Y);
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginTextureMode(screenCamera1);
ClearBackground(Color.RayWhite);
BeginMode2D(camera1);
// Draw full scene with first camera
for (int i = 0; i < screenWidth / PLAYER_SIZE + 1; i++)
{
DrawLineV(new Vector2((float)PLAYER_SIZE * i, 0), new Vector2((float)PLAYER_SIZE * i, (float)screenHeight), Color.LightGray);
}
for (int i = 0; i < screenHeight / PLAYER_SIZE + 1; i++)
{
DrawLineV(new Vector2(0, (float)PLAYER_SIZE * i), new Vector2((float)screenWidth, (float)PLAYER_SIZE * i), Color.LightGray);
}
for (int i = 0; i < screenWidth / PLAYER_SIZE; i++)
{
for (int j = 0; j < screenHeight / PLAYER_SIZE; j++)
{
DrawText($"[{i},{j}]", 10 + PLAYER_SIZE * i, 15 + PLAYER_SIZE * j, 10, Color.LightGray);
}
}
DrawRectangleRec(player1, Color.Red);
DrawRectangleRec(player2, Color.Blue);
EndMode2D();
DrawRectangle(0, 0, GetScreenWidth() / 2, 30, Fade(Color.RayWhite, 0.6f));
DrawText("PLAYER1: W/S/A/D to move", 10, 10, 10, Color.Maroon);
EndTextureMode();
BeginTextureMode(screenCamera2);
ClearBackground(Color.RayWhite);
BeginMode2D(camera2);
// Draw full scene with second camera
for (int i = 0; i < screenWidth / PLAYER_SIZE + 1; i++)
{
DrawLineV(new Vector2((float)PLAYER_SIZE * i, 0), new Vector2((float)PLAYER_SIZE * i, (float)screenHeight), Color.LightGray);
}
for (int i = 0; i < screenHeight / PLAYER_SIZE + 1; i++)
{
DrawLineV(new Vector2(0, (float)PLAYER_SIZE * i), new Vector2((float)screenWidth, (float)PLAYER_SIZE * i), Color.LightGray);
}
for (int i = 0; i < screenWidth / PLAYER_SIZE; i++)
{
for (int j = 0; j < screenHeight / PLAYER_SIZE; j++)
{
DrawText($"[{i},{j}]", 10 + PLAYER_SIZE * i, 15 + PLAYER_SIZE * j, 10, Color.LightGray);
}
}
DrawRectangleRec(player1, Color.Red);
DrawRectangleRec(player2, Color.Blue);
EndMode2D();
DrawRectangle(0, 0, GetScreenWidth() / 2, 30, Fade(Color.RayWhite, 0.6f));
DrawText("PLAYER2: UP/DOWN/LEFT/RIGHT to move", 10, 10, 10, Color.DarkBlue);
EndTextureMode();
// Draw both views render textures to the screen side by side
BeginDrawing();
ClearBackground(Color.Black);
DrawTextureRec(screenCamera1.Texture, splitScreenRect, new Vector2(0, 0), Color.White);
DrawTextureRec(screenCamera2.Texture, splitScreenRect, new Vector2(screenWidth / 2.0f, 0), Color.White);
DrawRectangle(GetScreenWidth() / 2 - 2, 0, 4, GetScreenHeight(), Color.LightGray);
EndDrawing();
}
public void Unload()
{
UnloadRenderTexture(screenCamera1); // Unload render texture
UnloadRenderTexture(screenCamera2); // Unload render texture
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [core] example - 2d camera split screen");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new SplitScreen2D();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,209 @@
/*******************************************************************************************
*
* raylib [core] example - text file loading
*
* Example complexity rating: [] 1/4
*
* Example originally created with raylib 5.5, last time updated with raylib 5.6
*
* Example contributed by Aanjishnu Bhattacharyya (@NimComPoo-04) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 0 Aanjishnu Bhattacharyya (@NimComPoo-04)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Raymath;
namespace Examples.Core;
// NOTE: The upstream C code mutates the raw char buffers returned by LoadTextLines() in place,
// temporarily null-terminating to reuse MeasureText for word wrapping. This C# port keeps the
// same algorithm but works on managed char arrays: LoadFileText + split on '\n' reproduces
// raylib's LoadTextLines('\n') behaviour, and '\n' characters are inserted where lines wrap.
public partial class TextFileLoading : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Core / Text File Loading";
public string Title => "raylib [core] example - text file loading";
private Camera2D cam;
private string fileName;
private string[] lines;
private int lineCount;
private int fontSize;
private int textTop;
private int wrapWidth;
private int textHeight;
private Rectangle scrollBar;
public void Init()
{
// Setting up the camera
cam = new Camera2D();
cam.Offset = new Vector2(0, 0);
cam.Target = new Vector2(0, 0);
cam.Rotation = 0;
cam.Zoom = 1;
// Loading text file from resources/text_file.txt
fileName = "resources/text_file.txt";
string text = LoadFileText(fileName);
// Loading all the text lines (raylib's LoadTextLines splits on '\n')
lines = text.Split('\n');
lineCount = lines.Length;
// Stylistic choises
fontSize = 20;
textTop = 25 + fontSize; // Top of the screen from where the text is rendered
wrapWidth = screenWidth - 20;
// Wrap the lines as needed
for (int i = 0; i < lineCount; i++)
{
char[] chars = lines[i].ToCharArray();
int len = chars.Length;
int j = 0;
int lastSpace = 0; // Keeping track of last valid space to insert '\n'
int lastWrapStart = 0; // Keeping track of the start of this wrapped line.
while (j <= len)
{
char cur = (j < len) ? chars[j] : '\0';
if (cur == ' ' || cur == '\0')
{
// Making a C style string by "cutting" at the required location so that we can use MeasureText
string sub = new string(chars, lastWrapStart, j - lastWrapStart);
// Checking if the text has crossed the wrapWidth, then going back and inserting a newline
if (MeasureText(sub, fontSize) > wrapWidth)
{
chars[lastSpace] = '\n';
// Since we added a newline the place of wrap changed so we update our lastWrapStart
lastWrapStart = lastSpace + 1;
}
lastSpace = j; // Since we encountered a new space we update our last encountered space location
}
j++;
}
lines[i] = new string(chars);
}
// Calculating the total height so that we can show a scrollbar
textHeight = 0;
for (int i = 0; i < lineCount; i++)
{
Vector2 size = MeasureTextEx(GetFontDefault(), lines[i], (float)fontSize, 2);
textHeight += (int)size.Y + 10;
}
// A simple scrollbar on the side to show how far we have read into the file
scrollBar = new Rectangle(
(float)screenWidth - 5,
0,
5,
screenHeight * 100.0f / (textHeight - screenHeight)); // Scrollbar height is just a percentage
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
float scroll = GetMouseWheelMove();
cam.Target.Y -= scroll * fontSize * 1.5f; // Choosing an arbitrary speed for scroll
if (cam.Target.Y < 0) cam.Target.Y = 0; // Snapping to 0 if we go too far back
// Ensuring that the camera does not scroll past all text
if (cam.Target.Y > textHeight - screenHeight + textTop)
cam.Target.Y = (float)textHeight - screenHeight + textTop;
// Computing the position of the scrollBar depending on the percentage of text covered
scrollBar.Y = Lerp((float)textTop, (float)screenHeight - scrollBar.Height, (float)(cam.Target.Y - textTop) / (textHeight - screenHeight));
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode2D(cam);
// Going through all the read lines
for (int i = 0, t = textTop; i < lineCount; i++)
{
// Each time we go through and calculate the height of the text to move the cursor appropriately
Vector2 size;
if (lines[i] != "")
{
size = MeasureTextEx(GetFontDefault(), lines[i], (float)fontSize, 2);
}
else
{
// Fix for empty line in the text file
size = MeasureTextEx(GetFontDefault(), " ", (float)fontSize, 2);
}
DrawText(lines[i], 10, t, fontSize, Color.Red);
// Inserting extra space for real newlines,
// wrapped lines are rendered closer together
t += (int)size.Y + 10;
}
EndMode2D();
// Header displaying which file is being read currently
DrawRectangle(0, 0, screenWidth, textTop - 10, Color.Beige);
DrawText($"File: {fileName}", 10, 10, fontSize, Color.Maroon);
DrawRectangleRec(scrollBar, Color.Maroon);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [core] example - text file loading");
var game = new TextFileLoading();
game.Init();
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

333
Examples/Core/UndoRedo.cs Normal file
View file

@ -0,0 +1,333 @@
/*******************************************************************************************
*
* raylib [core] example - undo redo
*
* Example complexity rating: [] 3/4
*
* Example originally created with raylib 5.5, last time updated with raylib 5.6
*
* Example contributed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Core;
public partial class UndoRedo : IExample
{
private const int MAX_UNDO_STATES = 26; // Maximum undo states supported for the ring buffer
private const int GRID_CELL_SIZE = 24;
private const int MAX_GRID_CELLS_X = 30;
private const int MAX_GRID_CELLS_Y = 13;
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Core / Undo Redo";
public string Title => "raylib [core] example - undo redo";
// Point struct, like Vector2 but using int
private struct Point
{
public int X;
public int Y;
}
// Player state struct
// NOTE: Contains all player data that needs to be affected by undo/redo
private struct PlayerState
{
public Point Cell;
public Color Color;
}
// Undo/redo system variables
private int currentUndoIndex;
private int firstUndoIndex;
private int lastUndoIndex;
private int undoFrameCounter;
private Vector2 undoInfoPos;
private PlayerState player;
private PlayerState[] states;
// Grid variables
private Vector2 gridPosition;
// Compare two player states (replaces memcmp)
private static bool SameState(in PlayerState a, in PlayerState b)
{
return (a.Cell.X == b.Cell.X) && (a.Cell.Y == b.Cell.Y) &&
(a.Color.R == b.Color.R) && (a.Color.G == b.Color.G) &&
(a.Color.B == b.Color.B) && (a.Color.A == b.Color.A);
}
public void Init()
{
currentUndoIndex = 0;
firstUndoIndex = 0;
lastUndoIndex = 0;
undoFrameCounter = 0;
undoInfoPos = new Vector2(110, 400);
// Init current player state and undo/redo recorded states array
player = new PlayerState();
player.Cell = new Point { X = 10, Y = 10 };
player.Color = Color.Red;
// Init undo buffer to store MAX_UNDO_STATES states
states = new PlayerState[MAX_UNDO_STATES];
// Init all undo states to current state
for (int i = 0; i < MAX_UNDO_STATES; i++) states[i] = player;
// Grid variables
gridPosition = new Vector2(40, 60);
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
// Player movement logic
if (IsKeyPressed(KeyboardKey.Right)) player.Cell.X++;
else if (IsKeyPressed(KeyboardKey.Left)) player.Cell.X--;
else if (IsKeyPressed(KeyboardKey.Up)) player.Cell.Y--;
else if (IsKeyPressed(KeyboardKey.Down)) player.Cell.Y++;
// Make sure player does not go out of bounds
if (player.Cell.X < 0) player.Cell.X = 0;
else if (player.Cell.X >= MAX_GRID_CELLS_X) player.Cell.X = MAX_GRID_CELLS_X - 1;
if (player.Cell.Y < 0) player.Cell.Y = 0;
else if (player.Cell.Y >= MAX_GRID_CELLS_Y) player.Cell.Y = MAX_GRID_CELLS_Y - 1;
// Player color change logic
if (IsKeyPressed(KeyboardKey.Space))
{
player.Color.R = (byte)GetRandomValue(20, 255);
player.Color.G = (byte)GetRandomValue(20, 220);
player.Color.B = (byte)GetRandomValue(20, 240);
}
// Undo state change logic
undoFrameCounter++;
// Waiting a number of frames before checking if we should store a new state snapshot
if (undoFrameCounter >= 2) // Checking every 2 frames
{
if (!SameState(states[currentUndoIndex], player))
{
// Move cursor to next available position of the undo ring buffer to record state
currentUndoIndex++;
if (currentUndoIndex >= MAX_UNDO_STATES) currentUndoIndex = 0;
if (currentUndoIndex == firstUndoIndex) firstUndoIndex++;
if (firstUndoIndex >= MAX_UNDO_STATES) firstUndoIndex = 0;
states[currentUndoIndex] = player;
lastUndoIndex = currentUndoIndex;
}
undoFrameCounter = 0;
}
// Recover previous state from buffer: CTRL+Z
if (IsKeyDown(KeyboardKey.LeftControl) && IsKeyPressed(KeyboardKey.Z))
{
if (currentUndoIndex != firstUndoIndex)
{
currentUndoIndex--;
if (currentUndoIndex < 0) currentUndoIndex = MAX_UNDO_STATES - 1;
if (!SameState(states[currentUndoIndex], player))
{
player = states[currentUndoIndex];
}
}
}
// Recover next state from buffer: CTRL+Y
if (IsKeyDown(KeyboardKey.LeftControl) && IsKeyPressed(KeyboardKey.Y))
{
if (currentUndoIndex != lastUndoIndex)
{
int nextUndoIndex = currentUndoIndex + 1;
if (nextUndoIndex >= MAX_UNDO_STATES) nextUndoIndex = 0;
if (nextUndoIndex != firstUndoIndex)
{
currentUndoIndex = nextUndoIndex;
if (!SameState(states[currentUndoIndex], player))
{
player = states[currentUndoIndex];
}
}
}
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
// Draw controls info
DrawText("[ARROWS] MOVE PLAYER - [SPACE] CHANGE PLAYER COLOR", 40, 20, 20, Color.DarkGray);
// Draw player visited cells recorded by undo
// NOTE: Remember we are using a ring buffer approach so,
// some cells info could start at the end of the array and end at the beginning
if (lastUndoIndex > firstUndoIndex)
{
for (int i = firstUndoIndex; i < currentUndoIndex; i++)
DrawRectangleRec(new Rectangle(gridPosition.X + states[i].Cell.X * GRID_CELL_SIZE, gridPosition.Y + states[i].Cell.Y * GRID_CELL_SIZE,
GRID_CELL_SIZE, GRID_CELL_SIZE), Color.LightGray);
}
else if (firstUndoIndex > lastUndoIndex)
{
if ((currentUndoIndex < MAX_UNDO_STATES) && (currentUndoIndex > lastUndoIndex))
{
for (int i = firstUndoIndex; i < currentUndoIndex; i++)
DrawRectangleRec(new Rectangle(gridPosition.X + states[i].Cell.X * GRID_CELL_SIZE, gridPosition.Y + states[i].Cell.Y * GRID_CELL_SIZE,
GRID_CELL_SIZE, GRID_CELL_SIZE), Color.LightGray);
}
else
{
for (int i = firstUndoIndex; i < MAX_UNDO_STATES; i++)
DrawRectangle((int)gridPosition.X + states[i].Cell.X * GRID_CELL_SIZE, (int)gridPosition.Y + states[i].Cell.Y * GRID_CELL_SIZE,
GRID_CELL_SIZE, GRID_CELL_SIZE, Color.LightGray);
for (int i = 0; i < currentUndoIndex; i++)
DrawRectangle((int)gridPosition.X + states[i].Cell.X * GRID_CELL_SIZE, (int)gridPosition.Y + states[i].Cell.Y * GRID_CELL_SIZE,
GRID_CELL_SIZE, GRID_CELL_SIZE, Color.LightGray);
}
}
// Draw game grid
for (int y = 0; y <= MAX_GRID_CELLS_Y; y++)
DrawLine((int)gridPosition.X, (int)gridPosition.Y + y * GRID_CELL_SIZE,
(int)gridPosition.X + MAX_GRID_CELLS_X * GRID_CELL_SIZE, (int)gridPosition.Y + y * GRID_CELL_SIZE, Color.Gray);
for (int x = 0; x <= MAX_GRID_CELLS_X; x++)
DrawLine((int)gridPosition.X + x * GRID_CELL_SIZE, (int)gridPosition.Y,
(int)gridPosition.X + x * GRID_CELL_SIZE, (int)gridPosition.Y + MAX_GRID_CELLS_Y * GRID_CELL_SIZE, Color.Gray);
// Draw player
DrawRectangle((int)gridPosition.X + player.Cell.X * GRID_CELL_SIZE, (int)gridPosition.Y + player.Cell.Y * GRID_CELL_SIZE,
GRID_CELL_SIZE + 1, GRID_CELL_SIZE + 1, player.Color);
// Draw undo system buffer info
DrawText("UNDO STATES:", (int)undoInfoPos.X - 85, (int)undoInfoPos.Y + 9, 10, Color.DarkGray);
DrawUndoBuffer(undoInfoPos, firstUndoIndex, lastUndoIndex, currentUndoIndex, 24);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
// Draw undo system visualization logic
// NOTE: Visualizing the ring buffer array, every square can store a player state
private static void DrawUndoBuffer(Vector2 position, int firstUndoIndex, int lastUndoIndex, int currentUndoIndex, int slotSize)
{
// Draw index marks
DrawRectangle((int)position.X + 8 + slotSize * currentUndoIndex, (int)position.Y - 10, 8, 8, Color.Red);
DrawRectangleLines((int)position.X + 2 + slotSize * firstUndoIndex, (int)position.Y + 27, 8, 8, Color.Black);
DrawRectangle((int)position.X + 14 + slotSize * lastUndoIndex, (int)position.Y + 27, 8, 8, Color.Black);
// Draw background gray slots
for (int i = 0; i < MAX_UNDO_STATES; i++)
{
DrawRectangle((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.LightGray);
DrawRectangleLines((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.Gray);
}
// Draw occupied slots: firstUndoIndex --> lastUndoIndex
if (firstUndoIndex <= lastUndoIndex)
{
for (int i = firstUndoIndex; i < lastUndoIndex + 1; i++)
{
DrawRectangle((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.SkyBlue);
DrawRectangleLines((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.Blue);
}
}
else if (lastUndoIndex < firstUndoIndex)
{
for (int i = firstUndoIndex; i < MAX_UNDO_STATES; i++)
{
DrawRectangle((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.SkyBlue);
DrawRectangleLines((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.Blue);
}
for (int i = 0; i < lastUndoIndex + 1; i++)
{
DrawRectangle((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.SkyBlue);
DrawRectangleLines((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.Blue);
}
}
// Draw occupied slots: firstUndoIndex --> currentUndoIndex
if (firstUndoIndex < currentUndoIndex)
{
for (int i = firstUndoIndex; i < currentUndoIndex; i++)
{
DrawRectangle((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.Green);
DrawRectangleLines((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.Lime);
}
}
else if (currentUndoIndex < firstUndoIndex)
{
for (int i = firstUndoIndex; i < MAX_UNDO_STATES; i++)
{
DrawRectangle((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.Green);
DrawRectangleLines((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.Lime);
}
for (int i = 0; i < currentUndoIndex; i++)
{
DrawRectangle((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.Green);
DrawRectangleLines((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.Lime);
}
}
// Draw current selected UNDO slot
DrawRectangle((int)position.X + slotSize * currentUndoIndex, (int)position.Y, slotSize, slotSize, Color.Gold);
DrawRectangleLines((int)position.X + slotSize * currentUndoIndex, (int)position.Y, slotSize, slotSize, Color.Orange);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [core] example - undo redo");
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new UndoRedo();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,361 @@
/*******************************************************************************************
*
* raylib [core] example - viewport scaling
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 5.5, last time updated with raylib 5.5
*
* Example contributed by Agnis Aldiņš (@nezvers) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Agnis Aldiņš (@nezvers)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Core;
public partial class ViewportScaling : IExample
{
private const int ResolutionCount = 4; // For iteration purposes and teaching example
private enum ViewportType
{
// Only upscale, useful for pixel art
KeepAspectInteger,
KeepHeightInteger,
KeepWidthInteger,
// Can also downscale
KeepAspect,
KeepHeight,
KeepWidth,
// For itteration purposes and as a teaching example
ViewportTypeCount,
}
// For displaying on GUI
private static readonly string[] ViewportTypeNames = new string[]
{
"KEEP_ASPECT_INTEGER",
"KEEP_HEIGHT_INTEGER",
"KEEP_WIDTH_INTEGER",
"KEEP_ASPECT",
"KEEP_HEIGHT",
"KEEP_WIDTH",
};
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Core / Viewport Scaling";
public string Title => "raylib [core] example - viewport scaling";
public ConfigFlags ConfigFlags => ConfigFlags.ResizableWindow;
// Mutable window size (tracked from GetScreenWidth/GetScreenHeight)
private int curScreenWidth;
private int curScreenHeight;
private Vector2[] resolutionList;
private int resolutionIndex;
private int gameWidth;
private int gameHeight;
private RenderTexture2D target;
private Rectangle sourceRect;
private Rectangle destRect;
private ViewportType viewportType;
// Button rectangles
private Rectangle decreaseResolutionButton;
private Rectangle increaseResolutionButton;
private Rectangle decreaseTypeButton;
private Rectangle increaseTypeButton;
public void Init()
{
curScreenWidth = screenWidth;
curScreenHeight = screenHeight;
// Preset resolutions that could be created by subdividing screen resolution
resolutionList = new Vector2[]
{
new Vector2(64, 64),
new Vector2(256, 240),
new Vector2(320, 180),
// 4K doesn't work with integer scaling but included for example purposes with non-integer scaling
new Vector2(3840, 2160),
};
resolutionIndex = 0;
gameWidth = 64;
gameHeight = 64;
target = default;
sourceRect = default;
destRect = default;
viewportType = ViewportType.KeepAspectInteger;
ResizeRenderSize(viewportType, ref curScreenWidth, ref curScreenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect, ref target);
// Button rectangles
decreaseResolutionButton = new Rectangle(200, 30, 10, 10);
increaseResolutionButton = new Rectangle(215, 30, 10, 10);
decreaseTypeButton = new Rectangle(200, 45, 10, 10);
increaseTypeButton = new Rectangle(215, 45, 10, 10);
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
if (IsWindowResized()) ResizeRenderSize(viewportType, ref curScreenWidth, ref curScreenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect, ref target);
Vector2 mousePosition = GetMousePosition();
bool mousePressed = IsMouseButtonPressed(MouseButton.Left);
// Check buttons and rescale
if (CheckCollisionPointRec(mousePosition, decreaseResolutionButton) && mousePressed)
{
resolutionIndex = (resolutionIndex + ResolutionCount - 1) % ResolutionCount;
gameWidth = (int)resolutionList[resolutionIndex].X;
gameHeight = (int)resolutionList[resolutionIndex].Y;
ResizeRenderSize(viewportType, ref curScreenWidth, ref curScreenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect, ref target);
}
if (CheckCollisionPointRec(mousePosition, increaseResolutionButton) && mousePressed)
{
resolutionIndex = (resolutionIndex + 1) % ResolutionCount;
gameWidth = (int)resolutionList[resolutionIndex].X;
gameHeight = (int)resolutionList[resolutionIndex].Y;
ResizeRenderSize(viewportType, ref curScreenWidth, ref curScreenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect, ref target);
}
if (CheckCollisionPointRec(mousePosition, decreaseTypeButton) && mousePressed)
{
viewportType = (ViewportType)(((int)viewportType + (int)ViewportType.ViewportTypeCount - 1) % (int)ViewportType.ViewportTypeCount);
ResizeRenderSize(viewportType, ref curScreenWidth, ref curScreenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect, ref target);
}
if (CheckCollisionPointRec(mousePosition, increaseTypeButton) && mousePressed)
{
viewportType = (ViewportType)(((int)viewportType + 1) % (int)ViewportType.ViewportTypeCount);
ResizeRenderSize(viewportType, ref curScreenWidth, ref curScreenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect, ref target);
}
Vector2 textureMousePosition = Screen2RenderTexturePosition(mousePosition, sourceRect, destRect);
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
// Draw our scene to the render texture
BeginTextureMode(target);
ClearBackground(Color.White);
DrawCircleV(textureMousePosition, 20.0f, Color.Lime);
EndTextureMode();
// Draw render texture to main framebuffer
BeginDrawing();
ClearBackground(Color.Black);
// Draw our render texture with rotation applied
DrawTexturePro(target.Texture, sourceRect, destRect, new Vector2(0.0f, 0.0f), 0.0f, Color.White);
// Draw Native resolution (GUI or anything)
// Draw info box
Rectangle infoRect = new Rectangle(5, 5, 330, 105);
DrawRectangleRec(infoRect, Fade(Color.LightGray, 0.7f));
DrawRectangleLinesEx(infoRect, 1, Color.Blue);
DrawText($"Window Resolution: {curScreenWidth} x {curScreenHeight}", 15, 15, 10, Color.Black);
DrawText($"Game Resolution: {gameWidth} x {gameHeight}", 15, 30, 10, Color.Black);
DrawText($"Type: {ViewportTypeNames[(int)viewportType]}", 15, 45, 10, Color.Black);
Vector2 scaleRatio = new Vector2(destRect.Width / sourceRect.Width, -destRect.Height / sourceRect.Height);
if (scaleRatio.X < 0.001f || scaleRatio.Y < 0.001f) DrawText("Scale ratio: INVALID", 15, 60, 10, Color.Black);
else DrawText($"Scale ratio: {scaleRatio.X:F2} x {scaleRatio.Y:F2}", 15, 60, 10, Color.Black);
DrawText($"Source size: {sourceRect.Width:F2} x {-sourceRect.Height:F2}", 15, 75, 10, Color.Black);
DrawText($"Destination size: {destRect.Width:F2} x {destRect.Height:F2}", 15, 90, 10, Color.Black);
// Draw buttons
DrawRectangleRec(decreaseTypeButton, Color.SkyBlue);
DrawRectangleRec(increaseTypeButton, Color.SkyBlue);
DrawRectangleRec(decreaseResolutionButton, Color.SkyBlue);
DrawRectangleRec(increaseResolutionButton, Color.SkyBlue);
DrawText("<", (int)decreaseTypeButton.X + 3, (int)decreaseTypeButton.Y + 1, 10, Color.Black);
DrawText(">", (int)increaseTypeButton.X + 3, (int)increaseTypeButton.Y + 1, 10, Color.Black);
DrawText("<", (int)decreaseResolutionButton.X + 3, (int)decreaseResolutionButton.Y + 1, 10, Color.Black);
DrawText(">", (int)increaseResolutionButton.X + 3, (int)increaseResolutionButton.Y + 1, 10, Color.Black);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadRenderTexture(target);
}
//--------------------------------------------------------------------------------------
// Module Functions Definition
//--------------------------------------------------------------------------------------
private static void KeepAspectCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, ref Rectangle sourceRect, ref Rectangle destRect)
{
sourceRect.X = 0.0f;
sourceRect.Y = (float)gameHeight;
sourceRect.Width = (float)gameWidth;
sourceRect.Height = (float)-gameHeight;
int ratioX = (screenWidth / gameWidth);
int ratioY = (screenHeight / gameHeight);
float resizeRatio = (float)((ratioX < ratioY) ? ratioX : ratioY);
destRect.X = (float)(int)((screenWidth - (gameWidth * resizeRatio)) * 0.5f);
destRect.Y = (float)(int)((screenHeight - (gameHeight * resizeRatio)) * 0.5f);
destRect.Width = (float)(int)(gameWidth * resizeRatio);
destRect.Height = (float)(int)(gameHeight * resizeRatio);
}
private static void KeepHeightCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, ref Rectangle sourceRect, ref Rectangle destRect)
{
float resizeRatio = (float)screenHeight / gameHeight;
sourceRect.X = 0.0f;
sourceRect.Y = 0.0f;
sourceRect.Width = (float)(int)(screenWidth / resizeRatio);
sourceRect.Height = (float)-gameHeight;
destRect.X = (float)(int)((screenWidth - (sourceRect.Width * resizeRatio)) * 0.5f);
destRect.Y = (float)(int)((screenHeight - (gameHeight * resizeRatio)) * 0.5f);
destRect.Width = (float)(int)(sourceRect.Width * resizeRatio);
destRect.Height = (float)(int)(gameHeight * resizeRatio);
}
private static void KeepWidthCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, ref Rectangle sourceRect, ref Rectangle destRect)
{
float resizeRatio = (float)screenWidth / gameWidth;
sourceRect.X = 0.0f;
sourceRect.Y = 0.0f;
sourceRect.Width = (float)gameWidth;
sourceRect.Height = (float)(int)(screenHeight / resizeRatio);
destRect.X = (float)(int)((screenWidth - (gameWidth * resizeRatio)) * 0.5f);
destRect.Y = (float)(int)((screenHeight - (sourceRect.Height * resizeRatio)) * 0.5f);
destRect.Width = (float)(int)(gameWidth * resizeRatio);
destRect.Height = (float)(int)(sourceRect.Height * resizeRatio);
sourceRect.Height *= -1.0f;
}
private static void KeepAspectCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, ref Rectangle sourceRect, ref Rectangle destRect)
{
sourceRect.X = 0.0f;
sourceRect.Y = (float)gameHeight;
sourceRect.Width = (float)gameWidth;
sourceRect.Height = (float)-gameHeight;
float ratioX = ((float)screenWidth / (float)gameWidth);
float ratioY = ((float)screenHeight / (float)gameHeight);
float resizeRatio = (ratioX < ratioY ? ratioX : ratioY);
destRect.X = (float)(int)((screenWidth - (gameWidth * resizeRatio)) * 0.5f);
destRect.Y = (float)(int)((screenHeight - (gameHeight * resizeRatio)) * 0.5f);
destRect.Width = (float)(int)(gameWidth * resizeRatio);
destRect.Height = (float)(int)(gameHeight * resizeRatio);
}
private static void KeepHeightCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, ref Rectangle sourceRect, ref Rectangle destRect)
{
float resizeRatio = ((float)screenHeight / (float)gameHeight);
sourceRect.X = 0.0f;
sourceRect.Y = 0.0f;
sourceRect.Width = (float)(int)((float)screenWidth / resizeRatio);
sourceRect.Height = (float)-gameHeight;
destRect.X = (float)(int)((screenWidth - (sourceRect.Width * resizeRatio)) * 0.5f);
destRect.Y = (float)(int)((screenHeight - (gameHeight * resizeRatio)) * 0.5f);
destRect.Width = (float)(int)(sourceRect.Width * resizeRatio);
destRect.Height = (float)(int)(gameHeight * resizeRatio);
}
private static void KeepWidthCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, ref Rectangle sourceRect, ref Rectangle destRect)
{
float resizeRatio = ((float)screenWidth / (float)gameWidth);
sourceRect.X = 0.0f;
sourceRect.Y = 0.0f;
sourceRect.Width = (float)gameWidth;
sourceRect.Height = (float)(int)((float)screenHeight / resizeRatio);
destRect.X = (float)(int)((screenWidth - (gameWidth * resizeRatio)) * 0.5f);
destRect.Y = (float)(int)((screenHeight - (sourceRect.Height * resizeRatio)) * 0.5f);
destRect.Width = (float)(int)(gameWidth * resizeRatio);
destRect.Height = (float)(int)(sourceRect.Height * resizeRatio);
sourceRect.Height *= -1.0f;
}
private static void ResizeRenderSize(ViewportType viewportType, ref int screenWidth, ref int screenHeight, int gameWidth, int gameHeight, ref Rectangle sourceRect, ref Rectangle destRect, ref RenderTexture2D target)
{
screenWidth = GetScreenWidth();
screenHeight = GetScreenHeight();
switch (viewportType)
{
case ViewportType.KeepAspectInteger: KeepAspectCenteredInteger(screenWidth, screenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect); break;
case ViewportType.KeepHeightInteger: KeepHeightCenteredInteger(screenWidth, screenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect); break;
case ViewportType.KeepWidthInteger: KeepWidthCenteredInteger(screenWidth, screenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect); break;
case ViewportType.KeepAspect: KeepAspectCentered(screenWidth, screenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect); break;
case ViewportType.KeepHeight: KeepHeightCentered(screenWidth, screenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect); break;
case ViewportType.KeepWidth: KeepWidthCentered(screenWidth, screenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect); break;
default: break;
}
UnloadRenderTexture(target);
target = LoadRenderTexture((int)sourceRect.Width, -(int)sourceRect.Height);
}
// Example how to calculate position on RenderTexture
private static Vector2 Screen2RenderTexturePosition(Vector2 point, Rectangle textureRect, Rectangle scaledRect)
{
Vector2 relativePosition = new Vector2(point.X - scaledRect.X, point.Y - scaledRect.Y);
Vector2 ratio = new Vector2(textureRect.Width / scaledRect.Width, -textureRect.Height / scaledRect.Height);
return new Vector2(relativePosition.X * ratio.X, relativePosition.Y * ratio.X);
}
public static int Main()
{
// Initialization
//---------------------------------------------------------
SetConfigFlags(ConfigFlags.ResizableWindow);
InitWindow(screenWidth, screenHeight, "raylib [core] example - viewport scaling");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//----------------------------------------------------------
var game = new ViewportScaling();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//----------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//----------------------------------------------------------------------------------
return 0;
}
}

View file

@ -19,6 +19,7 @@ using static Raylib_cs.ConfigFlags;
namespace Examples.Core;
[ExcludeFromBrowser("runtime window-state flags don't apply to the emscripten canvas")]
public partial class WindowFlags : IExample
{
private const int screenWidth = 800;

View file

@ -0,0 +1,130 @@
/*******************************************************************************************
*
* raylib [core] example - window should close
*
* Example complexity rating: [] 1/4
*
* Example originally created with raylib 4.2, last time updated with raylib 4.2
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2013-2025 Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Core;
public partial class WindowShouldClose : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Core / Window Should Close";
public string Title => "raylib [core] example - window should close";
// The runner drives its loop off this instead of WindowShouldClose(), so the exit
// confirmation (polled in Update) can intercept both the X-button and KEY_ESCAPE.
public bool ShouldClose => exitWindow;
private bool exitWindowRequested; // Flag to request window to exit
private bool exitWindow; // Flag to set window to exit
public void Init()
{
SetExitKey(KeyboardKey.Null); // Disable KEY_ESCAPE to close window, X-button still works
exitWindowRequested = false;
exitWindow = false;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
// Detect if X-button or KEY_ESCAPE have been pressed to close window
bool closeRequested = IsKeyPressed(KeyboardKey.Escape);
#if !BROWSER
// WindowShouldClose() polls the OS window close button, but on the wasm target it calls
// emscripten_sleep (ASYNCIFY is not enabled) and the canvas has no window chrome anyway,
// so on web the confirmation is triggered by KEY_ESCAPE only.
closeRequested |= Raylib.WindowShouldClose();
#endif
if (closeRequested)
{
exitWindowRequested = true;
}
if (exitWindowRequested)
{
// A request for close window has been issued, we can save data before closing
// or just show a message asking for confirmation
if (IsKeyPressed(KeyboardKey.Y))
{
exitWindow = true;
}
else if (IsKeyPressed(KeyboardKey.N))
{
exitWindowRequested = false;
}
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
if (exitWindowRequested)
{
DrawRectangle(0, 100, screenWidth, 200, Color.Black);
DrawText("Are you sure you want to exit program? [Y/N]", 40, 180, 30, Color.White);
}
else
{
DrawText("Try to close the window to get confirmation message!", 120, 200, 20, Color.LightGray);
}
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [core] example - window should close");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new WindowShouldClose();
game.Init();
// Main game loop
while (!game.exitWindow)
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -1,15 +1,12 @@
using System.Diagnostics.CodeAnalysis;
using Examples.Core;
using Examples.Models;
using Examples.Shapes;
namespace Examples;
/// <summary>
/// Discovers every <see cref="IExample"/> implementation in this assembly via reflection and
/// derives the per-platform lists: <see cref="DesktopExamples"/> (Program.cs) and
/// <see cref="BrowserExamples"/> (Web/Host.cs). Ordering is by category (browser dropdown
/// grouping), then display name.
/// Discovers every <see cref="IExample"/> implementation in this assembly via reflection:
/// <see cref="DesktopExamples"/> (Program.cs) is all of them, <see cref="BrowserExamples"/>
/// (Web/Host.cs) is everything not marked <see cref="ExcludeFromBrowserAttribute"/>.
/// Ordering is by category (browser dropdown grouping), then display name.
/// </summary>
public static class ExampleRegistry
{
@ -25,29 +22,12 @@ public static class ExampleRegistry
"Shaders",
];
/// <summary>Desktop examples omitted from the browser host (platform limitations).</summary>
private static readonly Type[] DesktopExcludedFromBrowser =
[
typeof(DropFiles),
typeof(LoadingThread), // System.Threading.Thread is unsupported on single-threaded wasm
typeof(SkyboxDemo),
];
/// <summary>Browser-only shape examples not registered for desktop CLI runs.</summary>
private static readonly Type[] BrowserOnly =
[
typeof(DrawCircleSector),
typeof(DrawRectangleRounded),
typeof(DrawRing),
];
private static readonly IExample[] AllExamples = DiscoverAll();
public static readonly IExample[] DesktopExamples =
Array.FindAll(AllExamples, e => Array.IndexOf(BrowserOnly, e.GetType()) < 0);
public static readonly IExample[] DesktopExamples = AllExamples;
public static readonly IExample[] BrowserExamples =
Array.FindAll(AllExamples, e => Array.IndexOf(DesktopExcludedFromBrowser, e.GetType()) < 0);
Array.FindAll(AllExamples, e => !e.GetType().IsDefined(typeof(ExcludeFromBrowserAttribute), inherit: false));
[UnconditionalSuppressMessage("Trimming", "IL2026",
Justification = "The Examples assembly is rooted via TrimmerRootAssembly in Examples.csproj.")]

View file

@ -2,6 +2,9 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<!-- The wasm build targets net10 only: net8 + browser-wasm needs the retired
wasm-tools-net8 workload, whereas net10 uses the current wasm-tools workload. -->
<TargetFrameworks Condition="'$(RuntimeIdentifier)' == 'browser-wasm'">net10.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<StartupObject Condition="'$(RuntimeIdentifier)' != 'browser-wasm'">Examples.Program</StartupObject>
<StartupObject Condition="'$(RuntimeIdentifier)' == 'browser-wasm'">Examples.Web.Host</StartupObject>
@ -31,10 +34,6 @@
<WasmEmitSymbolMap Condition="'$(WasmEmitSymbolMap)' == ''">false</WasmEmitSymbolMap>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Text/Unicode.cs"/>
</ItemGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' != 'browser-wasm'">
<Compile Remove="Web/**/*.cs"/>
</ItemGroup>

View file

@ -0,0 +1,17 @@
namespace Examples;
/// <summary>
/// Marks an <see cref="IExample"/> that is omitted from the browser host (Web/Host.cs)
/// because of a wasm/WebGL platform limitation. Desktop runs are unaffected.
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class ExcludeFromBrowserAttribute : Attribute
{
/// <summary>Why the example cannot run in the browser.</summary>
public string Reason { get; }
public ExcludeFromBrowserAttribute(string reason = null)
{
Reason = reason;
}
}

View file

@ -1,18 +1,14 @@
namespace Examples;
/// <summary>
/// A runnable raylib example. Each example class implements this interface directly: loop-spanning
/// state from the original example's Main lives in instance fields, (re)initialized in
/// A runnable raylib example. Loop-spanning state lives in instance fields, (re)initialized in
/// <see cref="Init"/> so re-selecting an example resets it.
///
/// <para>
/// Desktop runs the example via its <c>static Main()</c>, a thin driver that owns the window
/// (InitWindow/CloseWindow, SetTargetFPS and friends) and drives <see cref="Init"/>,
/// <see cref="Update"/>, and <see cref="Unload"/> around a blocking
/// <c>while (!WindowShouldClose())</c> loop. In the browser, <c>Web/Host.cs</c> owns the single
/// window and calls <see cref="Update"/> one frame at a time from JavaScript, so examples never
/// block. Platform divergences (e.g. GLSL 100 vs 330 shaders) are guarded with <c>#if BROWSER</c>,
/// preferably around a single constant so both platforms share one code path.
/// Desktop drives examples via <c>Program.cs</c> (each also keeps a thin standalone
/// <c>static Main()</c>); in the browser, <c>Web/Host.cs</c> owns the single window and calls
/// <see cref="Update"/> one frame at a time from JavaScript. Platform divergences are guarded
/// with <c>#if BROWSER</c>, preferably around a single constant.
/// </para>
/// </summary>
public interface IExample
@ -23,19 +19,32 @@ public interface IExample
/// <summary>Window title, matching the example's standalone <c>Main()</c>.</summary>
string Title { get; }
/// <summary>Config flags the desktop runner applies before window creation.</summary>
/// <summary>Desktop window size, matching the standalone <c>Main()</c>. The browser canvas is fixed at 800x450.</summary>
int Width => 800;
/// <inheritdoc cref="Width"/>
int Height => 450;
/// <summary>Config flags the desktop runner applies before window creation. Ignored in the browser.</summary>
ConfigFlags ConfigFlags => 0;
/// <summary>Target FPS the desktop runner sets after window creation.</summary>
/// <summary>Target FPS. The desktop runner always applies it; the browser host paces its frame loop with it.</summary>
int TargetFps => 60;
/// <summary>Whether the desktop runner disables the cursor (relative mouse movement).</summary>
/// <summary>Whether the desktop runner disables the cursor. Ignored in the browser (the pointer stays visible).</summary>
bool CursorDisabled => false;
/// <summary>Whether the desktop runner hides the cursor.</summary>
/// <summary>Whether the desktop runner hides the cursor. Ignored in the browser.</summary>
bool CursorHidden => false;
/// <summary>One-time setup.</summary>
/// <summary>
/// Whether the desktop runner should exit its frame loop; defaults to
/// <see cref="Raylib.WindowShouldClose"/>. Examples that intercept the close request
/// override this and poll WindowShouldClose in <see cref="Update"/> themselves.
/// </summary>
bool ShouldClose => WindowShouldClose();
/// <summary>Set up the Example and preload necessary resources.</summary>
void Init();
/// <summary>Render one frame, including BeginDrawing/EndDrawing.</summary>

View file

@ -0,0 +1,390 @@
/*******************************************************************************************
*
* raylib [models] example - animation blend custom
*
* Example complexity rating: [] 4/4
*
* Example originally created with raylib 5.5, last time updated with raylib 6.0
*
* Example contributed by dmitrii-brand (@dmitrii-brand) and reviewed by Ramon Santamaria (@raysan5)
*
* DETAILS: Example demonstrates per-bone animation blending, allowing smooth transitions
* between two animations by interpolating bone transforms. This is useful for:
* - Blending movement animations (walk/run) with action animations (jump/attack)
* - Creating smooth animation transitions
* - Layering animations (e.g., upper body attack while lower body walks)
*
* WARNING: GPU skinning must be enabled in raylib with a compilation flag,
* if not enabled, CPU skinning will be used instead
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2026 dmitrii-brand (@dmitrii-brand)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Raymath;
namespace Examples.Models;
public partial class AnimationBlendCustom : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
#if BROWSER
private const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
#else
private const int GlslVersion = 330;
#endif
public string Name => "Models / Animation Blend Custom";
public string Title => "raylib [models] example - animation blend custom";
private Camera3D camera;
private Model model;
private Vector3 position;
private Shader skinningShader;
private unsafe ModelAnimation* anims;
private int animCount;
private int animIndex0;
private int animIndex1;
private int animCurrentFrame0;
private int animCurrentFrame1;
private bool upperBodyBlend;
public unsafe void Init()
{
// Define the camera to look into our 3d world
camera = new();
camera.Position = new Vector3(4.0f, 4.0f, 4.0f); // Camera position
camera.Target = new Vector3(0.0f, 1.0f, 0.0f); // Camera looking at point
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
camera.FovY = 45.0f; // Camera field-of-view Y
camera.Projection = CameraProjection.Perspective; // Camera projection type
// Load gltf model
model = LoadModel("resources/models/gltf/greenman.glb");
position = new Vector3(0.0f, 0.0f, 0.0f); // Set model position
// Load skinning shader
// WARNING: GPU skinning must be enabled in raylib with a compilation flag,
// if not enabled, CPU skinning will be used instead
skinningShader = LoadShader(
$"resources/shaders/glsl{GlslVersion}/skinning.vs",
$"resources/shaders/glsl{GlslVersion}/skinning.fs"
);
model.Materials[1].Shader = skinningShader;
// Load gltf model animations
animCount = 0;
anims = LoadModelAnimations("resources/models/gltf/greenman.glb", ref animCount);
// Use specific animation indices: 2-walk/move, 3-attack
animIndex0 = 2; // Walk/Move animation (index 2)
animIndex1 = 3; // Attack animation (index 3)
animCurrentFrame0 = 0;
animCurrentFrame1 = 0;
// Validate indices
if (animIndex0 >= animCount)
{
animIndex0 = 0;
}
if (animIndex1 >= animCount)
{
animIndex1 = (animCount > 1) ? 1 : 0;
}
upperBodyBlend = true; // Toggle: true = upper/lower body blending, false = uniform blending (50/50)
}
public unsafe void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(ref camera, CameraMode.Orbital);
// Toggle upper/lower body blending mode (SPACE key)
if (IsKeyPressed(KeyboardKey.Space))
{
upperBodyBlend = !upperBodyBlend;
}
// Update animation frames
var anim0 = anims[animIndex0];
var anim1 = anims[animIndex1];
animCurrentFrame0 = (animCurrentFrame0 + 1) % anim0.KeyFrameCount;
animCurrentFrame1 = (animCurrentFrame1 + 1) % anim1.KeyFrameCount;
// Blend the two animations
// When upperBodyBlend is ON: upper body = attack (1.0), lower body = walk (0.0)
// When upperBodyBlend is OFF: uniform blend at 0.5 (50% walk, 50% attack)
var blendFactor = upperBodyBlend ? 1.0f : 0.5f;
UpdateModelAnimationBones(anim0, animCurrentFrame0, anim1, animCurrentFrame1, blendFactor, upperBodyBlend);
// raylib provided animation blending function
//UpdateModelAnimationEx(model, anim0, (float)animCurrentFrame0,
// anim1, (float)animCurrentFrame1, blendFactor);
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode3D(camera);
DrawModel(model, position, 1.0f, Color.White);
DrawGrid(10, 1.0f);
EndMode3D();
// Draw UI
DrawText($"ANIM 0: {anim0.NameToString()}", 10, 10, 20, Color.Gray);
DrawText($"ANIM 1: {anim1.NameToString()}", 10, 40, 20, Color.Gray);
DrawText($"[SPACE] Toggle blending mode: {(upperBodyBlend ? "Upper/Lower Body Blending" : "Uniform Blending")}",
10, GetScreenHeight() - 30, 20, Color.DarkGray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public unsafe void Unload()
{
UnloadModelAnimations(anims, animCount); // Unload model animation
UnloadModel(model); // Unload model and meshes/material
UnloadShader(skinningShader); // Unload GPU skinning shader
}
// Check if a bone is part of upper body (for selective blending)
private static bool IsUpperBodyBone(string boneName)
{
// Common upper body bone names (adjust based on your model)
if (boneName is "spine" or "spine1" or "spine2" or
"chest" or "upperChest" or
"neck" or "head" or
"shoulder" or "shoulder_L" or "shoulder_R" or
"upperArm" or "upperArm_L" or "upperArm_R" or
"lowerArm" or "lowerArm_L" or "lowerArm_R" or
"hand" or "hand_L" or "hand_R" or
"clavicle" or "clavicle_L" or "clavicle_R")
{
return true;
}
// Check if bone name contains upper body keywords
if (boneName.Contains("spine") || boneName.Contains("chest") ||
boneName.Contains("neck") || boneName.Contains("head") ||
boneName.Contains("shoulder") || boneName.Contains("arm") ||
boneName.Contains("hand") || boneName.Contains("clavicle"))
{
return true;
}
return false;
}
// Blend two animations per-bone with selective upper/lower body blending
private unsafe void UpdateModelAnimationBones(ModelAnimation anim0, int frame0,
ModelAnimation anim1, int frame1, float blend, bool upperBodyBlend)
{
// Validate inputs
if ((anim0.BoneCount != 0) && (anim0.KeyframePoses != null) &&
(anim1.BoneCount != 0) && (anim1.KeyframePoses != null) &&
(model.Skeleton.BoneCount != 0) && (model.Skeleton.BindPose != null))
{
// Clamp blend factor to [0, 1]
blend = MathF.Min(1.0f, MathF.Max(0.0f, blend));
// Ensure frame indices are valid
if (frame0 >= anim0.KeyFrameCount)
{
frame0 = anim0.KeyFrameCount - 1;
}
if (frame1 >= anim1.KeyFrameCount)
{
frame1 = anim1.KeyFrameCount - 1;
}
if (frame0 < 0)
{
frame0 = 0;
}
if (frame1 < 0)
{
frame1 = 0;
}
// Get bone count (use minimum of all to be safe)
var boneCount = model.Skeleton.BoneCount;
if (anim0.BoneCount < boneCount)
{
boneCount = anim0.BoneCount;
}
if (anim1.BoneCount < boneCount)
{
boneCount = anim1.BoneCount;
}
// Blend each bone
for (var boneIndex = 0; boneIndex < boneCount; boneIndex++)
{
// Determine blend factor for this bone
var boneBlendFactor = blend;
// If upper body blending is enabled, use different blend factors for upper vs lower body
if (upperBodyBlend)
{
var boneName = model.Skeleton.Bones[boneIndex].NameToString();
var isUpperBody = IsUpperBodyBone(boneName);
// Upper body: use anim1 (attack), Lower body: use anim0 (walk)
// blend = 0.0 means full anim0 (walk), 1.0 means full anim1 (attack)
if (isUpperBody)
{
boneBlendFactor = blend; // Upper body: blend towards anim1 (attack)
}
else
{
boneBlendFactor = 1.0f - blend; // Lower body: blend towards anim0 (walk) - invert the blend
}
}
// Get transforms from both animations
var bindTransform = model.Skeleton.BindPose[boneIndex];
var animTransform0 = anim0.KeyframePoses[frame0][boneIndex];
var animTransform1 = anim1.KeyframePoses[frame1][boneIndex];
// Blend the transforms
Transform blended = new();
blended.Translation = Vector3Lerp(animTransform0.Translation, animTransform1.Translation, boneBlendFactor);
blended.Rotation = QuaternionSlerp(animTransform0.Rotation, animTransform1.Rotation, boneBlendFactor);
blended.Scale = Vector3Lerp(animTransform0.Scale, animTransform1.Scale, boneBlendFactor);
// Convert bind pose to matrix
var bindMatrix = MatrixMultiply(MatrixMultiply(
MatrixScale(bindTransform.Scale.X, bindTransform.Scale.Y, bindTransform.Scale.Z),
QuaternionToMatrix(bindTransform.Rotation)),
MatrixTranslate(bindTransform.Translation.X, bindTransform.Translation.Y, bindTransform.Translation.Z));
// Convert blended transform to matrix
var blendedMatrix = MatrixMultiply(MatrixMultiply(
MatrixScale(blended.Scale.X, blended.Scale.Y, blended.Scale.Z),
QuaternionToMatrix(blended.Rotation)),
MatrixTranslate(blended.Translation.X, blended.Translation.Y, blended.Translation.Z));
// Calculate final bone matrix (similar to UpdateModelAnimationBones)
model.BoneMatrices[boneIndex] = MatrixMultiply(MatrixInvert(bindMatrix), blendedMatrix);
}
// CPU skinning, updates CPU buffers and uploads them to GPU (if available)
// NOTE: Fallback in case GPU skinning is not supported or enabled
for (var m = 0; m < model.MeshCount; m++)
{
var mesh = model.Meshes[m];
Vector3 animVertex;
Vector3 animNormal;
var vertexValuesCount = mesh.VertexCount * 3;
var boneCounter = 0;
var bufferUpdateRequired = false; // Flag to check when anim vertex information is updated
// Skip if missing bone data or missing anim buffers initialization
if ((mesh.BoneWeights == null) || (mesh.BoneIndices == null) ||
(mesh.AnimVertices == null) || (mesh.AnimNormals == null))
{
continue;
}
for (var vCounter = 0; vCounter < vertexValuesCount; vCounter += 3)
{
mesh.AnimVertices[vCounter] = 0;
mesh.AnimVertices[vCounter + 1] = 0;
mesh.AnimVertices[vCounter + 2] = 0;
if (mesh.AnimNormals != null)
{
mesh.AnimNormals[vCounter] = 0;
mesh.AnimNormals[vCounter + 1] = 0;
mesh.AnimNormals[vCounter + 2] = 0;
}
// Iterates over 4 bones per vertex
for (var j = 0; j < 4; j++, boneCounter++)
{
var boneWeight = mesh.BoneWeights[boneCounter];
var boneIndex = mesh.BoneIndices[boneCounter];
// Early stop when no transformation will be applied
if (boneWeight == 0.0f)
{
continue;
}
animVertex = new Vector3(mesh.Vertices[vCounter], mesh.Vertices[vCounter + 1], mesh.Vertices[vCounter + 2]);
animVertex = Vector3Transform(animVertex, model.BoneMatrices[boneIndex]);
mesh.AnimVertices[vCounter] += animVertex.X * boneWeight;
mesh.AnimVertices[vCounter + 1] += animVertex.Y * boneWeight;
mesh.AnimVertices[vCounter + 2] += animVertex.Z * boneWeight;
bufferUpdateRequired = true;
// Normals processing
// NOTE: We use meshes.baseNormals (default normal) to calculate meshes.normals (animated normals)
if ((mesh.Normals != null) && (mesh.AnimNormals != null))
{
animNormal = new Vector3(mesh.Normals[vCounter], mesh.Normals[vCounter + 1], mesh.Normals[vCounter + 2]);
animNormal = Vector3Transform(animNormal, MatrixTranspose(MatrixInvert(model.BoneMatrices[boneIndex])));
mesh.AnimNormals[vCounter] += animNormal.X * boneWeight;
mesh.AnimNormals[vCounter + 1] += animNormal.Y * boneWeight;
mesh.AnimNormals[vCounter + 2] += animNormal.Z * boneWeight;
}
}
}
if (bufferUpdateRequired)
{
// Update GPU vertex buffers with updated data (position + normals)
Rlgl.UpdateVertexBuffer(mesh.VboId[(int)ShaderLocationIndex.VertexPosition], mesh.AnimVertices, mesh.VertexCount * 3 * sizeof(float), 0);
if (mesh.Normals != null)
{
Rlgl.UpdateVertexBuffer(mesh.VboId[(int)ShaderLocationIndex.VertexNormal], mesh.AnimNormals, mesh.VertexCount * 3 * sizeof(float), 0);
}
}
}
}
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [models] example - animation blend custom");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new AnimationBlendCustom();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,474 @@
/*******************************************************************************************
*
* raylib [models] example - animation blending
*
* Example complexity rating: [] 4/4
*
* Example originally created with raylib 5.5, last time updated with raylib 6.0
*
* Example contributed by Kirandeep (@Kirandeep-Singh-Khehra) and reviewed by Ramon Santamaria (@raysan5)
*
* WARNING: GPU skinning must be enabled in raylib with a compilation flag,
* if not enabled, CPU skinning will be used instead
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2024-2026 Kirandeep (@Kirandeep-Singh-Khehra) and Ramon Santamaria (@raysan5)
*
********************************************************************************************/
// NOTE: The upstream example uses raygui for its UI controls (dropdowns, sliders, progress bars).
// raygui is not part of raylib-cs, so the required controls are reimplemented here with
// basic raylib drawing primitives, preserving the original behaviour.
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Models;
public partial class AnimationBlending : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
#if BROWSER
private const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
#else
private const int GlslVersion = 330;
#endif
public string Name => "Models / Animation Blending";
public string Title => "raylib [models] example - animation blending";
private Camera3D camera;
private Model model;
private Vector3 position;
private Shader skinningShader;
private unsafe ModelAnimation* anims;
private int animCount;
private int currentAnimPlaying;
private int nextAnimToPlay;
private bool animTransition;
private int animIndex0;
private float animCurrentFrame0;
private float animFrameSpeed0;
private int animIndex1;
private float animCurrentFrame1;
private float animFrameSpeed1;
private float animBlendFactor;
private float animBlendTime;
private float animBlendTimeCounter;
private bool animPause;
private string[] animNames;
private bool dropdownEditMode0;
private bool dropdownEditMode1;
private float animFrameProgress0;
private float animFrameProgress1;
private float animBlendProgress;
public unsafe void Init()
{
// Define the camera to look into our 3d world
camera = new();
camera.Position = new Vector3(6.0f, 6.0f, 6.0f); // Camera position
camera.Target = new Vector3(0.0f, 2.0f, 0.0f); // Camera looking at point
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
camera.FovY = 45.0f; // Camera field-of-view Y
camera.Projection = CameraProjection.Perspective; // Camera projection type
// Load model
model = LoadModel("resources/models/gltf/robot.glb"); // Load character model
position = new Vector3(0.0f, 0.0f, 0.0f); // Set model world position
// Load skinning shader
// NOTE: It must be a valid shader, following raylib attribs/uniform conventions for GPU skinning
skinningShader = LoadShader(
$"resources/shaders/glsl{GlslVersion}/skinning.vs",
$"resources/shaders/glsl{GlslVersion}/skinning.fs"
);
// Skinning shader could be required to be assigned to all materials shaders, just to make
// sure required uniforms are being updated for the mesh using that material (and shader)
for (var i = 0; i < model.MaterialCount; i++)
{
model.Materials[i].Shader = skinningShader;
}
// Load model animations
animCount = 0;
anims = LoadModelAnimations("resources/models/gltf/robot.glb", ref animCount);
// Animation playing variables
// NOTE: Two animations are played with a smooth transition between them
currentAnimPlaying = 0; // Current animation playing (0 o 1)
nextAnimToPlay = 1; // Next animation to play (to transition)
animTransition = false; // Flag to register anim transition state
animIndex0 = 10; // Current animation playing (walking)
animCurrentFrame0 = 0.0f; // Current animation frame (supporting interpolated frames)
animFrameSpeed0 = 0.5f; // Current animation play speed
animIndex1 = 6; // Next animation to play (running)
animCurrentFrame1 = 0.0f; // Next animation frame (supporting interpolated frames)
animFrameSpeed1 = 0.5f; // Next animation play speed
animBlendFactor = 0.0f; // Blend factor from anim0[frame0] --> anim1[frame1], [0.0f..1.0f]
animBlendTime = 2.0f; // Time to blend from one playing animation to another (in seconds)
animBlendTimeCounter = 0.0f; // Time counter (delta time)
animPause = false; // Pause animation
// UI required variables
animNames = new string[animCount];
for (var i = 0; i < animCount; i++)
{
animNames[i] = anims[i].NameToString();
}
dropdownEditMode0 = false;
dropdownEditMode1 = false;
animFrameProgress0 = 0.0f;
animFrameProgress1 = 0.0f;
animBlendProgress = 0.0f;
}
public unsafe void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(ref camera, CameraMode.Orbital);
if (IsKeyPressed(KeyboardKey.P))
{
animPause = !animPause;
}
if (!animPause)
{
// Start transition from anim0[] to anim1[]
if (IsKeyPressed(KeyboardKey.Space) && !animTransition)
{
if (currentAnimPlaying == 0)
{
// Transition anim0 --> anim1
nextAnimToPlay = 1;
animCurrentFrame1 = 0.0f;
}
else
{
// Transition anim1 --> anim0
nextAnimToPlay = 0;
animCurrentFrame0 = 0.0f;
}
// Set animation transition
animTransition = true;
animBlendTimeCounter = 0.0f;
animBlendFactor = 0.0f;
}
if (animTransition)
{
// Playing anim0 and anim1 at the same time
animCurrentFrame0 += animFrameSpeed0;
if (animCurrentFrame0 >= anims[animIndex0].KeyFrameCount)
{
animCurrentFrame0 = 0.0f;
}
animCurrentFrame1 += animFrameSpeed1;
if (animCurrentFrame1 >= anims[animIndex1].KeyFrameCount)
{
animCurrentFrame1 = 0.0f;
}
// Increment blend factor over time to transition from anim0 --> anim1 over time
// NOTE: Time blending could be other than linear, using some easing
animBlendFactor = animBlendTimeCounter / animBlendTime;
animBlendTimeCounter += GetFrameTime();
animBlendProgress = animBlendFactor;
// Update model with animations blending
if (nextAnimToPlay == 1)
{
// Blend anim0 --> anim1
UpdateModelAnimationEx(model, anims[animIndex0], animCurrentFrame0,
anims[animIndex1], animCurrentFrame1, animBlendFactor);
}
else
{
// Blend anim1 --> anim0
UpdateModelAnimationEx(model, anims[animIndex1], animCurrentFrame1,
anims[animIndex0], animCurrentFrame0, animBlendFactor);
}
// Check if transition completed
if (animBlendFactor > 1.0f)
{
// Reset frame states
if (currentAnimPlaying == 0)
{
animCurrentFrame0 = 0.0f;
}
else if (currentAnimPlaying == 1)
{
animCurrentFrame1 = 0.0f;
}
currentAnimPlaying = nextAnimToPlay; // Update current animation playing
animBlendFactor = 0.0f; // Reset blend factor
animTransition = false; // Exit transition mode
animBlendTimeCounter = 0.0f;
}
}
else
{
// Play only one anim, the current one
if (currentAnimPlaying == 0)
{
// Playing anim0 at defined speed
animCurrentFrame0 += animFrameSpeed0;
if (animCurrentFrame0 >= anims[animIndex0].KeyFrameCount)
{
animCurrentFrame0 = 0.0f;
}
UpdateModelAnimation(model, anims[animIndex0], animCurrentFrame0);
}
else if (currentAnimPlaying == 1)
{
// Playing anim1 at defined speed
animCurrentFrame1 += animFrameSpeed1;
if (animCurrentFrame1 >= anims[animIndex1].KeyFrameCount)
{
animCurrentFrame1 = 0.0f;
}
UpdateModelAnimation(model, anims[animIndex1], animCurrentFrame1);
}
}
}
// Update progress bars values with current frame for each animation
animFrameProgress0 = animCurrentFrame0;
animFrameProgress1 = animCurrentFrame1;
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode3D(camera);
DrawModel(model, position, 1.0f, Color.White); // Draw animated model
DrawGrid(10, 1.0f);
EndMode3D();
if (animTransition)
{
DrawText("ANIM TRANSITION BLENDING!", 170, 50, 30, Color.Blue);
}
// Draw UI elements
//---------------------------------------------------------------------------------------------
GuiSlider(new Rectangle(10, 38, 160, 12), null, $"x{animFrameSpeed0:0.0}", ref animFrameSpeed0, 0.1f, 2.0f);
GuiSlider(new Rectangle(GetScreenWidth() - 170.0f, 38, 160, 12), $"{animFrameSpeed1:0.0}x", null, ref animFrameSpeed1, 0.1f, 2.0f);
// Blending process progress bar
GuiProgressBar(new Rectangle(180, 14, 440, 16), null, null, animBlendProgress, 0.0f, 1.0f);
// Centered "PRESS SPACE" label
const string spaceLabel = "PRESS SPACE to START BLENDING";
var spaceLabelWidth = MeasureText(spaceLabel, 20);
DrawText(spaceLabel, (GetScreenWidth() - spaceLabelWidth) / 2, (int)(GetScreenHeight() - 100.0f + 10), 20, Color.DarkGray);
// Draw playing timeline with keyframes for anim0[]
GuiProgressBar(new Rectangle(60, GetScreenHeight() - 60.0f, GetScreenWidth() - 180.0f, 20), "ANIM 0",
$"FRAME: {animFrameProgress0:0.00} / {anims[animIndex0].KeyFrameCount}",
animFrameProgress0, 0.0f, anims[animIndex0].KeyFrameCount);
for (var i = 0; i < anims[animIndex0].KeyFrameCount; i++)
{
DrawRectangle(60 + (int)(((float)(GetScreenWidth() - 180) / anims[animIndex0].KeyFrameCount) * i),
GetScreenHeight() - 60, 1, 20, Color.Blue);
}
// Draw playing timeline with keyframes for anim1[]
GuiProgressBar(new Rectangle(60, GetScreenHeight() - 30.0f, GetScreenWidth() - 180.0f, 20), "ANIM 1",
$"FRAME: {animFrameProgress1:0.00} / {anims[animIndex1].KeyFrameCount}",
animFrameProgress1, 0.0f, anims[animIndex1].KeyFrameCount);
for (var i = 0; i < anims[animIndex1].KeyFrameCount; i++)
{
DrawRectangle(60 + (int)(((float)(GetScreenWidth() - 180) / anims[animIndex1].KeyFrameCount) * i),
GetScreenHeight() - 30, 1, 20, Color.Blue);
}
// Draw animation selectors for blending transition (drawn last so open lists render on top)
// NOTE: Transition does not start until requested
if (GuiDropdownBox(new Rectangle(10, 10, 160, 24), animNames, ref animIndex0, dropdownEditMode0))
{
dropdownEditMode0 = !dropdownEditMode0;
}
if (GuiDropdownBox(new Rectangle(GetScreenWidth() - 170.0f, 10, 160, 24), animNames, ref animIndex1, dropdownEditMode1))
{
dropdownEditMode1 = !dropdownEditMode1;
}
//---------------------------------------------------------------------------------------------
EndDrawing();
//----------------------------------------------------------------------------------
}
public unsafe void Unload()
{
UnloadModelAnimations(anims, animCount); // Unload model animation
UnloadModel(model); // Unload model and meshes/material
UnloadShader(skinningShader); // Unload GPU skinning shader
}
// Minimal immediate-mode slider (raygui replacement)
private static bool GuiSlider(Rectangle bounds, string textLeft, string textRight, ref float value, float minValue, float maxValue)
{
var mouse = GetMousePosition();
var dragging = false;
if (CheckCollisionPointRec(mouse, bounds) && IsMouseButtonDown(MouseButton.Left))
{
value = minValue + ((mouse.X - bounds.X) / bounds.Width) * (maxValue - minValue);
if (value < minValue)
{
value = minValue;
}
if (value > maxValue)
{
value = maxValue;
}
dragging = true;
}
DrawRectangleRec(bounds, Color.LightGray);
var pct = (value - minValue) / (maxValue - minValue);
DrawRectangleRec(new Rectangle(bounds.X, bounds.Y, bounds.Width * pct, bounds.Height), Color.SkyBlue);
DrawRectangleLinesEx(bounds, 1, Color.Gray);
if (!string.IsNullOrEmpty(textLeft))
{
DrawText(textLeft, (int)(bounds.X - MeasureText(textLeft, 10) - 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
if (!string.IsNullOrEmpty(textRight))
{
DrawText(textRight, (int)(bounds.X + bounds.Width + 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
return dragging;
}
// Minimal immediate-mode progress bar (raygui replacement)
private static void GuiProgressBar(Rectangle bounds, string textLeft, string textRight, float value, float minValue, float maxValue)
{
DrawRectangleRec(bounds, Color.LightGray);
var pct = maxValue > minValue ? (value - minValue) / (maxValue - minValue) : 0.0f;
if (pct < 0)
{
pct = 0;
}
if (pct > 1)
{
pct = 1;
}
DrawRectangleRec(new Rectangle(bounds.X, bounds.Y, bounds.Width * pct, bounds.Height), Color.SkyBlue);
DrawRectangleLinesEx(bounds, 1, Color.Gray);
if (!string.IsNullOrEmpty(textLeft))
{
DrawText(textLeft, (int)(bounds.X - MeasureText(textLeft, 10) - 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
if (!string.IsNullOrEmpty(textRight))
{
DrawText(textRight, (int)(bounds.X + bounds.Width + 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
}
// Minimal immediate-mode dropdown box (raygui replacement)
private static bool GuiDropdownBox(Rectangle bounds, string[] items, ref int active, bool editMode)
{
var result = false;
var mouse = GetMousePosition();
// Draw main box
DrawRectangleRec(bounds, Color.LightGray);
DrawRectangleLinesEx(bounds, 1, Color.Gray);
if (active >= 0 && active < items.Length)
{
DrawText(items[active], (int)bounds.X + 5, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
DrawText(editMode ? "^" : "v", (int)(bounds.X + bounds.Width - 12), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
// Draw items when open
if (editMode)
{
for (var i = 0; i < items.Length; i++)
{
Rectangle item = new(bounds.X, bounds.Y + bounds.Height * (i + 1), bounds.Width, bounds.Height);
var hover = CheckCollisionPointRec(mouse, item);
DrawRectangleRec(item, hover ? Color.SkyBlue : Color.LightGray);
DrawRectangleLinesEx(item, 1, Color.Gray);
DrawText(items[i], (int)item.X + 5, (int)(item.Y + item.Height / 2 - 5), 10, Color.DarkGray);
}
}
if (IsMouseButtonPressed(MouseButton.Left))
{
if (editMode)
{
for (var i = 0; i < items.Length; i++)
{
Rectangle item = new(bounds.X, bounds.Y + bounds.Height * (i + 1), bounds.Width, bounds.Height);
if (CheckCollisionPointRec(mouse, item))
{
active = i;
break;
}
}
result = true; // any click closes the dropdown
}
else if (CheckCollisionPointRec(mouse, bounds))
{
result = true; // open the dropdown
}
}
return result;
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [models] example - animation blending");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new AnimationBlending();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,160 @@
/*******************************************************************************************
*
* raylib [models] example - animation gpu skinning
*
* Example complexity rating: [] 3/4
*
* Example originally created with raylib 4.5, last time updated with raylib 4.5
*
* Example contributed by Daniel Holden (@orangeduck) and reviewed by Ramon Santamaria (@raysan5)
*
* WARNING: GPU skinning must be enabled in raylib with a compilation flag,
* if not enabled, CPU skinning will be used instead
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2024-2025 Daniel Holden (@orangeduck)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Models;
public partial class AnimationGpuSkinning : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
#if BROWSER
private const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
#else
private const int GlslVersion = 330;
#endif
public string Name => "Models / Animation GPU Skinning";
public string Title => "raylib [models] example - animation gpu skinning";
private Camera3D camera;
private Model model;
private Vector3 position;
private Shader skinningShader;
private unsafe ModelAnimation* anims;
private int animCount;
private int animIndex;
private int animCurrentFrame;
public unsafe void Init()
{
// Define the camera to look into our 3d world
camera = new();
camera.Position = new Vector3(5.0f, 5.0f, 5.0f); // Camera position
camera.Target = new Vector3(0.0f, 1.0f, 0.0f); // Camera looking at point
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
camera.FovY = 45.0f; // Camera field-of-view Y
camera.Projection = CameraProjection.Perspective; // Camera projection type
// Load gltf model
model = LoadModel("resources/models/gltf/greenman.glb"); // Load character model
position = new Vector3(0.0f, 0.0f, 0.0f); // Set model position
// Load skinning shader
// NOTE: It must be a valid shader, following raylib attribs/uniform conventions for GPU skinning
skinningShader = LoadShader(
$"resources/shaders/glsl{GlslVersion}/skinning.vs",
$"resources/shaders/glsl{GlslVersion}/skinning.fs"
);
// Skinning shader could be required to be assigned to all materials shaders, just to make
// sure required uniforms are being updated for the mesh using that material (and shader)
model.Materials[1].Shader = skinningShader; // Just assigning to materials[1] for this model
// Load gltf model animations
animCount = 0;
anims = LoadModelAnimations("resources/models/gltf/greenman.glb", ref animCount);
// Animation playing variables
animIndex = 0; // Current animation playing
animCurrentFrame = 0; // Current animation frame
}
public unsafe void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(ref camera, CameraMode.Orbital);
// Select current animation
if (IsKeyPressed(KeyboardKey.Right))
{
animIndex = (animIndex + 1) % animCount;
}
else if (IsKeyPressed(KeyboardKey.Left))
{
animIndex = (animIndex + animCount - 1) % animCount;
}
// Update model animation
animCurrentFrame = (animCurrentFrame + 1) % anims[animIndex].KeyFrameCount;
UpdateModelAnimation(model, anims[animIndex], animCurrentFrame);
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode3D(camera);
DrawModel(model, position, 1.0f, Color.White);
DrawGrid(10, 1.0f);
EndMode3D();
DrawText($"Current animation: {anims[animIndex].NameToString()}", 10, 40, 20, Color.Maroon);
DrawText("Use the LEFT/RIGHT keys to switch animation", 10, 10, 20, Color.Gray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public unsafe void Unload()
{
UnloadModelAnimations(anims, animCount); // Unload model animation
UnloadModel(model); // Unload model and meshes/material
UnloadShader(skinningShader); // Unload GPU skinning shader
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [models] example - animation gpu skinning");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new AnimationGpuSkinning();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,292 @@
/*******************************************************************************************
*
* raylib [models] example - animation timing
*
* Example complexity rating: [] 3/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2026 Ramon Santamaria (@raysan5)
*
********************************************************************************************/
// NOTE: The upstream example uses raygui for its UI controls (dropdown, slider, progress bar).
// raygui is not part of raylib-cs, so the required controls are reimplemented here with
// basic raylib drawing primitives, preserving the original behaviour.
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Models;
public partial class AnimationTiming : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Models / Animation Timing";
public string Title => "raylib [models] example - animation timing";
private Camera3D camera;
private Model model;
private Vector3 position;
private unsafe ModelAnimation* anims;
private int animCount;
private int animIndex;
private float animCurrentFrame;
private float animFrameSpeed;
private bool animPause;
private string[] animNames;
private bool dropdownEditMode;
private float animFrameProgress;
public unsafe void Init()
{
// Define the camera to look into our 3d world
camera = new();
camera.Position = new Vector3(6.0f, 6.0f, 6.0f); // Camera position
camera.Target = new Vector3(0.0f, 2.0f, 0.0f); // Camera looking at point
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
camera.FovY = 45.0f; // Camera field-of-view Y
camera.Projection = CameraProjection.Perspective; // Camera projection type
// Load model
model = LoadModel("resources/models/gltf/robot.glb");
position = new Vector3(0.0f, 0.0f, 0.0f); // Set model world position
// Load model animations
animCount = 0;
anims = LoadModelAnimations("resources/models/gltf/robot.glb", ref animCount);
// Animation playing variables
animIndex = 10; // Current animation playing
animCurrentFrame = 0.0f; // Current animation frame (supporting interpolated frames)
animFrameSpeed = 0.5f; // Animation play speed
animPause = false; // Pause animation
// UI required variables
animNames = new string[animCount];
for (var i = 0; i < animCount; i++)
{
animNames[i] = anims[i].NameToString();
}
dropdownEditMode = false;
animFrameProgress = 0.0f;
}
public unsafe void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(ref camera, CameraMode.Orbital);
if (IsKeyPressed(KeyboardKey.P))
{
animPause = !animPause;
}
if (!animPause && (animIndex < animCount))
{
// Update model animation
animCurrentFrame += animFrameSpeed;
if (animCurrentFrame >= anims[animIndex].KeyFrameCount)
{
animCurrentFrame = 0.0f;
}
UpdateModelAnimation(model, anims[animIndex], animCurrentFrame);
}
// NOTE: Animation and playing speed selected through UI
// Update progressbar value with current frame
animFrameProgress = animCurrentFrame;
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode3D(camera);
DrawModel(model, position, 1.0f, Color.White);
DrawGrid(10, 1.0f);
EndMode3D();
// Draw UI, select anim and playing speed
GuiSlider(new Rectangle(260, 10, 500, 24), "FRAME SPEED: ", $"x{animFrameSpeed:0.0}", ref animFrameSpeed, 0.1f, 2.0f);
// Draw playing timeline with keyframes
DrawText($"CURRENT FRAME: {animFrameProgress:0.00} / {anims[animIndex].KeyFrameCount}",
10, (int)(GetScreenHeight() - 64.0f), 10, Color.DarkGray);
GuiProgressBar(new Rectangle(10, GetScreenHeight() - 40.0f, GetScreenWidth() - 20.0f, 24), null, null,
animFrameProgress, 0.0f, anims[animIndex].KeyFrameCount);
for (var i = 0; i < anims[animIndex].KeyFrameCount; i++)
{
DrawRectangle(10 + (int)(((float)(GetScreenWidth() - 20) / anims[animIndex].KeyFrameCount) * i),
GetScreenHeight() - 40, 1, 24, Color.Blue);
}
// NOTE: Dropdown drawn last so its open item list renders on top
if (GuiDropdownBox(new Rectangle(10, 10, 140, 24), animNames, ref animIndex, dropdownEditMode))
{
dropdownEditMode = !dropdownEditMode;
}
EndDrawing();
//----------------------------------------------------------------------------------
}
public unsafe void Unload()
{
UnloadModelAnimations(anims, animCount); // Unload model animation
UnloadModel(model); // Unload model and meshes/material
}
// Minimal immediate-mode slider (raygui replacement)
private static bool GuiSlider(Rectangle bounds, string textLeft, string textRight, ref float value, float minValue, float maxValue)
{
var mouse = GetMousePosition();
var dragging = false;
if (CheckCollisionPointRec(mouse, bounds) && IsMouseButtonDown(MouseButton.Left))
{
value = minValue + ((mouse.X - bounds.X) / bounds.Width) * (maxValue - minValue);
if (value < minValue)
{
value = minValue;
}
if (value > maxValue)
{
value = maxValue;
}
dragging = true;
}
DrawRectangleRec(bounds, Color.LightGray);
var pct = (value - minValue) / (maxValue - minValue);
DrawRectangleRec(new Rectangle(bounds.X, bounds.Y, bounds.Width * pct, bounds.Height), Color.SkyBlue);
DrawRectangleLinesEx(bounds, 1, Color.Gray);
if (!string.IsNullOrEmpty(textLeft))
{
DrawText(textLeft, (int)(bounds.X - MeasureText(textLeft, 10) - 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
if (!string.IsNullOrEmpty(textRight))
{
DrawText(textRight, (int)(bounds.X + bounds.Width + 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
return dragging;
}
// Minimal immediate-mode progress bar (raygui replacement)
private static void GuiProgressBar(Rectangle bounds, string textLeft, string textRight, float value, float minValue, float maxValue)
{
DrawRectangleRec(bounds, Color.LightGray);
var pct = maxValue > minValue ? (value - minValue) / (maxValue - minValue) : 0.0f;
if (pct < 0)
{
pct = 0;
}
if (pct > 1)
{
pct = 1;
}
DrawRectangleRec(new Rectangle(bounds.X, bounds.Y, bounds.Width * pct, bounds.Height), Color.SkyBlue);
DrawRectangleLinesEx(bounds, 1, Color.Gray);
if (!string.IsNullOrEmpty(textLeft))
{
DrawText(textLeft, (int)(bounds.X - MeasureText(textLeft, 10) - 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
if (!string.IsNullOrEmpty(textRight))
{
DrawText(textRight, (int)(bounds.X + bounds.Width + 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
}
// Minimal immediate-mode dropdown box (raygui replacement)
private static bool GuiDropdownBox(Rectangle bounds, string[] items, ref int active, bool editMode)
{
var result = false;
var mouse = GetMousePosition();
// Draw main box
DrawRectangleRec(bounds, Color.LightGray);
DrawRectangleLinesEx(bounds, 1, Color.Gray);
if (active >= 0 && active < items.Length)
{
DrawText(items[active], (int)bounds.X + 5, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
DrawText(editMode ? "^" : "v", (int)(bounds.X + bounds.Width - 12), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
// Draw items when open
if (editMode)
{
for (var i = 0; i < items.Length; i++)
{
Rectangle item = new(bounds.X, bounds.Y + bounds.Height * (i + 1), bounds.Width, bounds.Height);
var hover = CheckCollisionPointRec(mouse, item);
DrawRectangleRec(item, hover ? Color.SkyBlue : Color.LightGray);
DrawRectangleLinesEx(item, 1, Color.Gray);
DrawText(items[i], (int)item.X + 5, (int)(item.Y + item.Height / 2 - 5), 10, Color.DarkGray);
}
}
if (IsMouseButtonPressed(MouseButton.Left))
{
if (editMode)
{
for (var i = 0; i < items.Length; i++)
{
Rectangle item = new(bounds.X, bounds.Y + bounds.Height * (i + 1), bounds.Width, bounds.Height);
if (CheckCollisionPointRec(mouse, item))
{
active = i;
break;
}
}
result = true; // any click closes the dropdown
}
else if (CheckCollisionPointRec(mouse, bounds))
{
result = true; // open the dropdown
}
}
return result;
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [models] example - animation timing");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new AnimationTiming();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,202 @@
/*******************************************************************************************
*
* raylib [models] example - basic voxel
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 5.5, last time updated with raylib 5.5
*
* Example contributed by Tim Little (@timlittle) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Tim Little (@timlittle)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Models;
public partial class BasicVoxel : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int WorldSize = 8; // Size of our voxel world (8x8x8 cubes)
public string Name => "Models / Basic Voxel";
public string Title => "raylib [models] example - basic voxel";
public bool CursorDisabled => true;
private Camera3D camera;
private Model cubeModel;
private bool[,,] voxels;
public unsafe void Init()
{
// Define the camera to look into our 3d world (first person)
camera = new();
camera.Position = new Vector3(-2.0f, 0.0f, -2.0f); // Camera position at ground level
camera.Target = new Vector3(0.0f, 0.0f, 0.0f); // Camera looking at point
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector
camera.FovY = 45.0f; // Camera field-of-view Y
camera.Projection = CameraProjection.Perspective; // Camera projection type
// Create a cube model
var cubeMesh = GenMeshCube(1.0f, 1.0f, 1.0f); // Create a unit cube mesh
cubeModel = LoadModelFromMesh(cubeMesh); // Convert mesh to a model
cubeModel.Materials[0].Maps[(int)MaterialMapIndex.Diffuse].Color = Color.Beige;
// Initialize voxel world - fill with voxels
voxels = new bool[WorldSize, WorldSize, WorldSize];
for (var x = 0; x < WorldSize; x++)
{
for (var y = 0; y < WorldSize; y++)
{
for (var z = 0; z < WorldSize; z++)
{
voxels[x, y, z] = true;
}
}
}
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(ref camera, CameraMode.FirstPerson);
// Handle voxel removal with mouse click
// This method is quite inefficient. Ray marching through the voxel grid using DDA would be faster, but more complex.
if (IsMouseButtonPressed(MouseButton.Left))
{
// Cast a ray from the screen center (where crosshair would be)
Vector2 screenCenter = new(GetScreenWidth() / 2.0f, GetScreenHeight() / 2.0f);
var ray = GetScreenToWorldRay(screenCenter, camera);
// Check ray collision with all voxels
var closestDistance = 99999.0f;
Vector3 closestVoxelPosition = new(-1, -1, -1);
var voxelFound = false;
for (var x = 0; x < WorldSize; x++)
{
for (var y = 0; y < WorldSize; y++)
{
for (var z = 0; z < WorldSize; z++)
{
if (!voxels[x, y, z])
{
continue; // Skip empty voxels
}
// Build a bounding box for this voxel
Vector3 position = new(x, y, z);
BoundingBox box = new(
new Vector3(position.X - 0.5f, position.Y - 0.5f, position.Z - 0.5f),
new Vector3(position.X + 0.5f, position.Y + 0.5f, position.Z + 0.5f)
);
// Check ray-box collision
var collision = GetRayCollisionBox(ray, box);
if (collision.Hit && (collision.Distance < closestDistance))
{
closestDistance = collision.Distance;
closestVoxelPosition = new Vector3(x, y, z);
voxelFound = true;
}
}
}
}
// Remove the closest voxel if one was hit
if (voxelFound)
{
voxels[(int)closestVoxelPosition.X,
(int)closestVoxelPosition.Y,
(int)closestVoxelPosition.Z] = false;
}
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode3D(camera);
DrawGrid(10, 1.0f);
// Draw all voxels
for (var x = 0; x < WorldSize; x++)
{
for (var y = 0; y < WorldSize; y++)
{
for (var z = 0; z < WorldSize; z++)
{
if (!voxels[x, y, z])
{
continue;
}
Vector3 position = new(x, y, z);
DrawModel(cubeModel, position, 1.0f, Color.Beige);
DrawCubeWires(position, 1.0f, 1.0f, 1.0f, Color.Black);
}
}
}
EndMode3D();
// Draw reference point for raycasting to delete blocks
DrawCircle(GetScreenWidth() / 2, GetScreenHeight() / 2, 4, Color.Red);
DrawText("Left-click a voxel to remove it!", 10, 10, 20, Color.DarkGray);
DrawText("WASD to move, mouse to look around", 10, 35, 10, Color.Gray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadModel(cubeModel);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [models] example - basic voxel");
DisableCursor(); // Lock mouse to window center
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new BasicVoxel();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow();
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,246 @@
/*******************************************************************************************
*
* raylib [models] example - bone socket
*
* Example complexity rating: [] 4/4
*
* Example originally created with raylib 4.5, last time updated with raylib 4.5
*
* Example contributed by iP (@ipzaur) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2024-2025 iP (@ipzaur)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Raymath;
namespace Examples.Models;
public partial class BoneSocket : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int BoneSockets = 3;
private const int BoneSocketHat = 0;
private const int BoneSocketHandR = 1;
private const int BoneSocketHandL = 2;
public string Name => "Models / Bone Socket";
public string Title => "raylib [models] example - bone socket";
public bool CursorDisabled => true;
private Camera3D camera;
private Model characterModel;
private Model[] equipModel;
private bool[] showEquip;
private int animsCount;
private int animIndex;
private int animCurrentFrame;
private unsafe ModelAnimation* modelAnimations;
private int[] boneSocketIndex;
private Vector3 position;
private int angle;
public unsafe void Init()
{
// Define the camera to look into our 3d world
camera = new();
camera.Position = new Vector3(5.0f, 5.0f, 5.0f); // Camera position
camera.Target = new Vector3(0.0f, 2.0f, 0.0f); // Camera looking at point
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
camera.FovY = 45.0f; // Camera field-of-view Y
camera.Projection = CameraProjection.Perspective; // Camera projection type
// Load gltf model
characterModel = LoadModel("resources/models/gltf/greenman.glb"); // Load character model
equipModel = new Model[BoneSockets]
{
LoadModel("resources/models/gltf/greenman_hat.glb"), // Index for the hat model is the same as BONE_SOCKET_HAT
LoadModel("resources/models/gltf/greenman_sword.glb"), // Index for the sword model is the same as BONE_SOCKET_HAND_R
LoadModel("resources/models/gltf/greenman_shield.glb") // Index for the shield model is the same as BONE_SOCKET_HAND_L
};
showEquip = new bool[3] { true, true, true }; // Toggle on/off equip
// Load gltf model animations
animsCount = 0;
animIndex = 0;
animCurrentFrame = 0;
modelAnimations = LoadModelAnimations("resources/models/gltf/greenman.glb", ref animsCount);
// Indices of bones for sockets
boneSocketIndex = new int[BoneSockets] { -1, -1, -1 };
// Search bones for sockets
for (var i = 0; i < characterModel.Skeleton.BoneCount; i++)
{
var boneName = characterModel.Skeleton.Bones[i].NameToString();
if (boneName == "socket_hat")
{
boneSocketIndex[BoneSocketHat] = i;
continue;
}
if (boneName == "socket_hand_R")
{
boneSocketIndex[BoneSocketHandR] = i;
continue;
}
if (boneName == "socket_hand_L")
{
boneSocketIndex[BoneSocketHandL] = i;
continue;
}
}
position = new Vector3(0.0f, 0.0f, 0.0f); // Set model position
angle = 0; // Set angle for rotate character
}
public unsafe void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(ref camera, CameraMode.ThirdPerson);
// Rotate character
if (IsKeyDown(KeyboardKey.F))
{
angle = (angle + 1) % 360;
}
else if (IsKeyDown(KeyboardKey.H))
{
angle = (360 + angle - 1) % 360;
}
// Select current animation
if (IsKeyPressed(KeyboardKey.T))
{
animIndex = (animIndex + 1) % animsCount;
}
else if (IsKeyPressed(KeyboardKey.G))
{
animIndex = (animIndex + animsCount - 1) % animsCount;
}
// Toggle shown of equip
if (IsKeyPressed(KeyboardKey.One))
{
showEquip[BoneSocketHat] = !showEquip[BoneSocketHat];
}
if (IsKeyPressed(KeyboardKey.Two))
{
showEquip[BoneSocketHandR] = !showEquip[BoneSocketHandR];
}
if (IsKeyPressed(KeyboardKey.Three))
{
showEquip[BoneSocketHandL] = !showEquip[BoneSocketHandL];
}
// Update model animation
var anim = modelAnimations[animIndex];
animCurrentFrame = (animCurrentFrame + 1) % anim.KeyFrameCount;
UpdateModelAnimation(characterModel, anim, animCurrentFrame);
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode3D(camera);
// Draw character
var characterRotate = QuaternionFromAxisAngle(new Vector3(0.0f, 1.0f, 0.0f), angle * DEG2RAD);
characterModel.Transform = MatrixMultiply(QuaternionToMatrix(characterRotate), MatrixTranslate(position.X, position.Y, position.Z));
UpdateModelAnimation(characterModel, anim, animCurrentFrame);
DrawMesh(characterModel.Meshes[0], characterModel.Materials[1], characterModel.Transform);
// Draw equipments (hat, sword, shield)
for (var i = 0; i < BoneSockets; i++)
{
if (!showEquip[i])
{
continue;
}
var transform = &anim.KeyframePoses[animCurrentFrame][boneSocketIndex[i]];
var inRotation = characterModel.Skeleton.BindPose[boneSocketIndex[i]].Rotation;
var outRotation = transform->Rotation;
// Calculate socket rotation (angle between bone in initial pose and same bone in current animation frame)
var rotate = QuaternionMultiply(outRotation, QuaternionInvert(inRotation));
var matrixTransform = QuaternionToMatrix(rotate);
// Translate socket to its position in the current animation
matrixTransform = MatrixMultiply(matrixTransform, MatrixTranslate(transform->Translation.X, transform->Translation.Y, transform->Translation.Z));
// Transform the socket using the transform of the character (angle and translate)
matrixTransform = MatrixMultiply(matrixTransform, characterModel.Transform);
// Draw mesh at socket position with socket angle rotation
DrawMesh(equipModel[i].Meshes[0], equipModel[i].Materials[1], matrixTransform);
}
DrawGrid(10, 1.0f);
EndMode3D();
DrawText("Use the T/G to switch animation", 10, 10, 20, Color.Gray);
DrawText("Use the F/H to rotate character left/right", 10, 35, 20, Color.Gray);
DrawText("Use the 1,2,3 to toggle shown of hat, sword and shield", 10, 60, 20, Color.Gray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public unsafe void Unload()
{
UnloadModelAnimations(modelAnimations, animsCount);
UnloadModel(characterModel); // Unload character model and meshes/material
// Unload equipment model and meshes/material
for (var i = 0; i < BoneSockets; i++)
{
UnloadModel(equipModel[i]);
}
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [models] example - bone socket");
DisableCursor(); // Limit cursor to relative movement inside the window
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new BoneSocket();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

592
Examples/Models/Decals.cs Normal file
View file

@ -0,0 +1,592 @@
/*******************************************************************************************
*
* raylib [models] example - decals
*
* Example complexity rating: [] 4/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by JP Mortiboys (@themushroompirates) and reviewed by Ramon Santamaria (@raysan5)
* Based on previous work by @mrdoob
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 JP Mortiboys (@themushroompirates) and Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using System;
using System.Collections.Generic;
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Raymath;
namespace Examples.Models;
public partial class Decals : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int MaxDecals = 256;
public string Name => "Models / Decals";
public string Title => "raylib [models] example - decals";
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
private Camera3D camera;
private Model model;
private Texture2D modelTexture;
private BoundingBox modelBBox;
private float decalSize;
private float decalOffset;
private Model placementCube;
private Material decalMaterial;
private Texture2D decalTexture;
private bool showModel;
private readonly List<Model> decalModels = new();
public unsafe void Init()
{
// Define the camera to look into our 3d world
camera = new();
camera.Position = new Vector3(5.0f, 5.0f, 5.0f); // Camera position
camera.Target = new Vector3(0.0f, 1.0f, 0.0f); // Camera looking at point
camera.Up = new Vector3(0.0f, 1.6f, 0.0f); // Camera up vector (rotation towards target)
camera.FovY = 45.0f; // Camera field-of-view Y
camera.Projection = CameraProjection.Perspective; // Camera projection type
// Load character model
model = LoadModel("resources/models/obj/character.obj");
// Apply character skin
modelTexture = LoadTexture("resources/models/obj/character_diffuse.png");
SetTextureFilter(modelTexture, TextureFilter.Bilinear);
model.Materials[0].Maps[(int)MaterialMapIndex.Diffuse].Texture = modelTexture;
modelBBox = GetMeshBoundingBox(model.Meshes[0]); // Get mesh bounding box
camera.Target = Vector3Lerp(modelBBox.Min, modelBBox.Max, 0.5f);
camera.Position = modelBBox.Max * 1.0f;
camera.Position.X *= 0.1f;
var modelSize = MathF.Min(
MathF.Min(MathF.Abs(modelBBox.Max.X - modelBBox.Min.X), MathF.Abs(modelBBox.Max.Y - modelBBox.Min.Y)),
MathF.Abs(modelBBox.Max.Z - modelBBox.Min.Z));
camera.Position = new Vector3(0.0f, modelBBox.Max.Y * 1.2f, modelSize * 3.0f);
decalSize = modelSize * 0.25f;
decalOffset = 0.01f;
placementCube = LoadModelFromMesh(GenMeshCube(decalSize, decalSize, decalSize));
placementCube.Materials[0].Maps[0].Color = Color.Lime;
decalMaterial = LoadMaterialDefault();
decalMaterial.Maps[0].Color = Color.Yellow;
var decalImage = LoadImage("resources/raylib_logo.png");
ImageResizeNN(ref decalImage, decalImage.Width / 4, decalImage.Height / 4);
decalTexture = LoadTextureFromImage(decalImage);
UnloadImage(decalImage);
SetTextureFilter(decalTexture, TextureFilter.Bilinear);
decalMaterial.Maps[(int)MaterialMapIndex.Diffuse].Texture = decalTexture;
decalMaterial.Maps[(int)MaterialMapIndex.Diffuse].Color = Color.RayWhite;
showModel = true;
decalModels.Clear();
}
public unsafe void Update()
{
// Update
//----------------------------------------------------------------------------------
if (IsMouseButtonDown(MouseButton.Right))
{
UpdateCamera(ref camera, CameraMode.ThirdPerson);
}
// Display information about closest hit
RayCollision collision = new();
collision.Distance = float.MaxValue;
collision.Hit = false;
// Get mouse ray
var ray = GetScreenToWorldRay(GetMousePosition(), camera);
// Check ray collision against bounding box first, before trying the full ray-mesh test
var boxHitInfo = GetRayCollisionBox(ray, modelBBox);
RayCollision meshHitInfo = new();
if (boxHitInfo.Hit && (decalModels.Count < MaxDecals))
{
// Check ray collision against model meshes
for (var m = 0; m < model.MeshCount; m++)
{
// NOTE: We consider the model.transform for the collision check but
// it can be checked against any transform Matrix, used when checking against same
// model drawn multiple times with multiple transforms
meshHitInfo = GetRayCollisionMesh(ray, model.Meshes[m], model.Transform);
if (meshHitInfo.Hit)
{
// Save the closest hit mesh
if (!collision.Hit || (collision.Distance > meshHitInfo.Distance))
{
collision = meshHitInfo;
}
}
}
if (meshHitInfo.Hit)
{
collision = meshHitInfo;
}
}
// Add decal to mesh on hit point
if (collision.Hit && IsMouseButtonPressed(MouseButton.Left) && (decalModels.Count < MaxDecals))
{
// Create the transformation to project the decal
var origin = collision.Point + (collision.Normal * 1.0f);
var splat = MatrixLookAt(collision.Point, origin, new Vector3(0.0f, 1.0f, 0.0f));
// Spin the placement around a bit
splat = MatrixMultiply(splat, MatrixRotateZ(DEG2RAD * GetRandomValue(-180, 180)));
var decalMesh = GenMeshDecal(model, splat, decalSize, decalOffset);
if (decalMesh.VertexCount > 0)
{
var decalModel = LoadModelFromMesh(decalMesh);
decalModel.Materials[0].Maps[0] = decalMaterial.Maps[0];
decalModels.Add(decalModel);
}
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode3D(camera);
// Draw the model at the origin and default scale
if (showModel)
{
DrawModel(model, new Vector3(0.0f, 0.0f, 0.0f), 1.0f, Color.White);
}
// Draw the decal models
for (var i = 0; i < decalModels.Count; i++)
{
DrawModel(decalModels[i], Vector3.Zero, 1.0f, Color.White);
}
// If we hit the mesh, draw the box for the decal
if (collision.Hit)
{
var origin = collision.Point + (collision.Normal * 1.0f);
var splat = MatrixLookAt(collision.Point, origin, new Vector3(0, 1, 0));
placementCube.Transform = MatrixInvert(splat);
DrawModel(placementCube, Vector3.Zero, 1.0f, Fade(Color.White, 0.5f));
}
DrawGrid(10, 10.0f);
EndMode3D();
float yPos = 10;
var x0 = GetScreenWidth() - 300.0f;
var x1 = x0 + 100;
var x2 = x1 + 100;
DrawText("Vertices", (int)x1, (int)yPos, 10, Color.Lime);
DrawText("Triangles", (int)x2, (int)yPos, 10, Color.Lime);
yPos += 15;
var vertexCount = 0;
var triangleCount = 0;
for (var i = 0; i < model.MeshCount; i++)
{
vertexCount += model.Meshes[i].VertexCount;
triangleCount += model.Meshes[i].TriangleCount;
}
DrawText("Main model", (int)x0, (int)yPos, 10, Color.Lime);
DrawText($"{vertexCount}", (int)x1, (int)yPos, 10, Color.Lime);
DrawText($"{triangleCount}", (int)x2, (int)yPos, 10, Color.Lime);
yPos += 15;
for (var i = 0; i < decalModels.Count; i++)
{
if (i == 20)
{
DrawText("...", (int)x0, (int)yPos, 10, Color.Lime);
yPos += 15;
}
if (i < 20)
{
DrawText($"Decal #{i + 1}", (int)x0, (int)yPos, 10, Color.Lime);
DrawText($"{decalModels[i].Meshes[0].VertexCount}", (int)x1, (int)yPos, 10, Color.Lime);
DrawText($"{decalModels[i].Meshes[0].TriangleCount}", (int)x2, (int)yPos, 10, Color.Lime);
yPos += 15;
}
vertexCount += decalModels[i].Meshes[0].VertexCount;
triangleCount += decalModels[i].Meshes[0].TriangleCount;
}
DrawText("TOTAL", (int)x0, (int)yPos, 10, Color.Lime);
DrawText($"{vertexCount}", (int)x1, (int)yPos, 10, Color.Lime);
DrawText($"{triangleCount}", (int)x2, (int)yPos, 10, Color.Lime);
yPos += 15;
DrawText("Hold RMB to move camera", 10, 430, 10, Color.Gray);
DrawText("(c) Character model and texture from kenney.nl", screenWidth - 260, screenHeight - 20, 10, Color.Gray);
// UI elements
if (GuiButton(new Rectangle(10, screenHeight - 1000.0f, 100, 60), showModel ? "Hide Model" : "Show Model"))
{
showModel = !showModel;
}
if (GuiButton(new Rectangle(10 + 110, screenHeight - 100.0f, 100, 60), "Clear Decals"))
{
// Clear decals, unload all decal models
for (var i = 0; i < decalModels.Count; i++)
{
UnloadModel(decalModels[i]);
}
decalModels.Clear();
}
DrawFPS(10, 10);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadModel(model);
UnloadTexture(modelTexture);
// Unload decal models
for (var i = 0; i < decalModels.Count; i++)
{
UnloadModel(decalModels[i]);
}
decalModels.Clear();
UnloadTexture(decalTexture);
UnloadModel(placementCube);
}
// Clip segment
private static Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s)
{
var d0 = Vector3.Dot(v0, p) - s;
var d1 = Vector3.Dot(v1, p) - s;
var s0 = d0 / (d0 - d1);
return Vector3.Lerp(v0, v1, s0);
}
// Generate mesh decals for provided model
private static unsafe Mesh GenMeshDecal(Model target, Matrix4x4 projection, float decalSize, float decalOffset)
{
// We're going to use these to build up our decal meshes
var meshBuilders = new List<Vector3>[2] { new(), new() };
// We're going to need the inverse matrix
var invProj = MatrixInvert(projection);
// We'll be flip-flopping between the two mesh builders
// Reading from one and writing to the other, then swapping
var mbIndex = 0;
// First pass, just get any triangle inside the bounding box (for each mesh of the model)
for (var meshIndex = 0; meshIndex < target.MeshCount; meshIndex++)
{
var mesh = target.Meshes[meshIndex];
for (var tri = 0; tri < mesh.TriangleCount; tri++)
{
var vertices = new Vector3[3];
// The way we calculate the vertices of the mesh triangle
// depend on whether the mesh vertices are indexed or not
if (mesh.Indices == null)
{
for (var v = 0; v < 3; v++)
{
vertices[v] = new Vector3(
mesh.Vertices[3 * 3 * tri + 3 * v + 0],
mesh.Vertices[3 * 3 * tri + 3 * v + 1],
mesh.Vertices[3 * 3 * tri + 3 * v + 2]
);
}
}
else
{
for (var v = 0; v < 3; v++)
{
vertices[v] = new Vector3(
mesh.Vertices[3 * mesh.Indices[3 * tri + 0] + v],
mesh.Vertices[3 * mesh.Indices[3 * tri + 1] + v],
mesh.Vertices[3 * mesh.Indices[3 * tri + 2] + v]
);
}
}
// Transform all 3 vertices of the triangle
// and check if they are inside our decal box
var insideCount = 0;
for (var i = 0; i < 3; i++)
{
// To projection space
var v = Vector3Transform(vertices[i], projection);
if ((MathF.Abs(v.X) < decalSize) || (MathF.Abs(v.Y) <= decalSize) || (MathF.Abs(v.Z) <= decalSize))
{
insideCount++;
}
// We need to keep the transformed vertex
vertices[i] = v;
}
// If any of them are inside, we add the triangle - we'll clip it later
if (insideCount > 0)
{
meshBuilders[mbIndex].Add(vertices[0]);
meshBuilders[mbIndex].Add(vertices[1]);
meshBuilders[mbIndex].Add(vertices[2]);
}
}
}
// Clipping time! We need to clip against all 6 directions
Vector3[] planes =
{
new(1, 0, 0),
new(-1, 0, 0),
new(0, 1, 0),
new(0, -1, 0),
new(0, 0, 1),
new(0, 0, -1)
};
for (var face = 0; face < 6; face++)
{
// Swap current model builder (so we read from the one we just wrote to)
mbIndex = 1 - mbIndex;
var inMesh = meshBuilders[1 - mbIndex];
var outMesh = meshBuilders[mbIndex];
// Reset write builder
outMesh.Clear();
var s = 0.5f * decalSize;
for (var i = 0; i < inMesh.Count; i += 3)
{
Vector3 nV1, nV2, nV3, nV4;
var d1 = Vector3.Dot(inMesh[i + 0], planes[face]) - s;
var d2 = Vector3.Dot(inMesh[i + 1], planes[face]) - s;
var d3 = Vector3.Dot(inMesh[i + 2], planes[face]) - s;
var v1Out = d1 > 0;
var v2Out = d2 > 0;
var v3Out = d3 > 0;
// Calculate, how many vertices of the face lie outside of the clipping plane
var total = (v1Out ? 1 : 0) + (v2Out ? 1 : 0) + (v3Out ? 1 : 0);
switch (total)
{
case 0:
// The entire face lies inside of the plane, no clipping needed
outMesh.Add(inMesh[i]);
outMesh.Add(inMesh[i + 1]);
outMesh.Add(inMesh[i + 2]);
break;
case 1:
// One vertex lies outside of the plane, perform clipping
if (v2Out)
{
nV1 = inMesh[i];
nV2 = inMesh[i + 2];
nV3 = ClipSegment(inMesh[i + 1], nV1, planes[face], s);
nV4 = ClipSegment(inMesh[i + 1], nV2, planes[face], s);
outMesh.Add(nV3); outMesh.Add(nV2); outMesh.Add(nV1);
outMesh.Add(nV2); outMesh.Add(nV3); outMesh.Add(nV4);
}
else
{
if (v1Out)
{
nV1 = inMesh[i + 1];
nV2 = inMesh[i + 2];
nV3 = ClipSegment(inMesh[i], nV1, planes[face], s);
nV4 = ClipSegment(inMesh[i], nV2, planes[face], s);
}
else // v3Out
{
nV1 = inMesh[i];
nV2 = inMesh[i + 1];
nV3 = ClipSegment(inMesh[i + 2], nV1, planes[face], s);
nV4 = ClipSegment(inMesh[i + 2], nV2, planes[face], s);
}
outMesh.Add(nV1); outMesh.Add(nV2); outMesh.Add(nV3);
outMesh.Add(nV4); outMesh.Add(nV3); outMesh.Add(nV2);
}
break;
case 2:
// Two vertices lies outside of the plane, perform clipping
if (!v1Out)
{
nV1 = inMesh[i];
nV2 = ClipSegment(nV1, inMesh[i + 1], planes[face], s);
nV3 = ClipSegment(nV1, inMesh[i + 2], planes[face], s);
outMesh.Add(nV1); outMesh.Add(nV2); outMesh.Add(nV3);
}
if (!v2Out)
{
nV1 = inMesh[i + 1];
nV2 = ClipSegment(nV1, inMesh[i + 2], planes[face], s);
nV3 = ClipSegment(nV1, inMesh[i], planes[face], s);
outMesh.Add(nV1); outMesh.Add(nV2); outMesh.Add(nV3);
}
if (!v3Out)
{
nV1 = inMesh[i + 2];
nV2 = ClipSegment(nV1, inMesh[i], planes[face], s);
nV3 = ClipSegment(nV1, inMesh[i + 1], planes[face], s);
outMesh.Add(nV1); outMesh.Add(nV2); outMesh.Add(nV3);
}
break;
default: // The entire face lies outside of the plane, so let's discard the corresponding vertices
break;
}
}
}
// Now we just need to re-transform the vertices
var theMesh = meshBuilders[mbIndex];
// Allocate room for UVs
if (theMesh.Count > 0)
{
var uvs = new Vector2[theMesh.Count];
for (var i = 0; i < theMesh.Count; i++)
{
var vert = theMesh[i];
// Calculate the UVs based on the projected coords
// They are clipped to (-decalSize .. decalSize) and we want them (0..1)
uvs[i] = new Vector2(vert.X / decalSize + 0.5f, vert.Y / decalSize + 0.5f);
// Tiny nudge in the normal direction so it renders properly over the mesh
vert.Z -= decalOffset;
// From projection space to world space
theMesh[i] = Vector3Transform(vert, invProj);
}
// Decal model data ready, create the mesh and return it
return BuildMesh(theMesh, uvs);
}
// Return a blank mesh as there's nothing to add
return new Mesh();
}
// Build a Mesh from builder data
private static unsafe Mesh BuildMesh(List<Vector3> builderVertices, Vector2[] uvs)
{
Mesh outMesh = new(builderVertices.Count, builderVertices.Count / 3);
outMesh.AllocVertices();
outMesh.AllocTexCoords();
var vertices = outMesh.VerticesAs<Vector3>();
var texcoords = outMesh.TexCoordsAs<Vector2>();
for (var i = 0; i < builderVertices.Count; i++)
{
vertices[i] = builderVertices[i];
texcoords[i] = uvs[i];
}
UploadMesh(ref outMesh, false);
return outMesh;
}
// Button UI element
private static bool GuiButton(Rectangle rec, string label)
{
var bgColor = Color.Gray;
var pressed = false;
if (CheckCollisionPointRec(GetMousePosition(), rec))
{
bgColor = Color.LightGray;
if (IsMouseButtonPressed(MouseButton.Left))
{
pressed = true;
}
}
DrawRectangleRec(rec, bgColor);
DrawRectangleLinesEx(rec, 2.0f, Color.DarkGray);
var fontSize = 10;
var textWidth = MeasureText(label, fontSize);
DrawText(label, (int)(rec.X + rec.Width * 0.5f - textWidth * 0.5f), (int)(rec.Y + rec.Height * 0.5f - fontSize * 0.5f), fontSize, Color.DarkGray);
return pressed;
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
SetConfigFlags(ConfigFlags.Msaa4xHint);
InitWindow(screenWidth, screenHeight, "raylib [models] example - decals");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new Decals();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,148 @@
/*******************************************************************************************
*
* raylib [models] example - directional billboard
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by Robin (@RobinsAviary) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Robin (@RobinsAviary)
* Killbot art by patvanmackelberg https://opengameart.org/content/killbot-8-directional under CC0
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Raymath;
namespace Examples.Models;
public partial class DirectionalBillboard : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Models / Directional Billboard";
public string Title => "raylib [models] example - directional billboard";
private Camera3D camera;
private Texture2D skillbot;
private float animTimer;
private uint anim;
public void Init()
{
// Set up the camera
camera = new();
camera.Position = new Vector3(2.0f, 1.0f, 2.0f); // Starting position
camera.Target = new Vector3(0.0f, 0.5f, 0.0f); // Target position
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Up vector
camera.FovY = 45.0f; // FOV
camera.Projection = CameraProjection.Perspective; // Projection type (Standard 3D perspective)
// Load billboard texture
skillbot = LoadTexture("resources/skillbot.png");
// Timer to update animation
animTimer = 0.0f;
// Animation frame
anim = 0;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(ref camera, CameraMode.Orbital);
// Update timer with delta time
animTimer += GetFrameTime();
// Update frame index after a certain amount of time (half a second)
if (animTimer > 0.5f)
{
animTimer = 0.0f;
anim += 1;
}
// Reset frame index to zero on overflow
if (anim >= 4)
{
anim = 0;
}
// Find the current direction frame based on the camera position to the billboard object
var dir = (float)Math.Floor(((Vector2Angle(new Vector2(2.0f, 0.0f), new Vector2(camera.Position.X, camera.Position.Z)) / MathF.PI) * 4.0f) + 0.25f);
// Correct frame index if angle is negative
if (dir < 0.0f)
{
dir = 8.0f - Math.Abs((int)dir);
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode3D(camera);
DrawGrid(10, 1.0f);
// Draw billboard pointing straight up to the sky, rotated relative to the camera and offset from the bottom
DrawBillboardPro(camera, skillbot, new Rectangle(0.0f + (anim * 24.0f), 0.0f + (dir * 24.0f), 24.0f, 24.0f),
Vector3.Zero, new Vector3(0.0f, 1.0f, 0.0f), Vector2.One, new Vector2(0.5f, 0.0f), 0, Color.White);
EndMode3D();
// Render various variables for reference
DrawText($"animation: {anim}", 10, 10, 20, Color.DarkGray);
DrawText($"direction frame: {dir:0}", 10, 40, 20, Color.DarkGray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
// Unload billboard texture
UnloadTexture(skillbot);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [models] example - directional billboard");
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new DirectionalBillboard();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,173 @@
/*******************************************************************************************
*
* raylib [models] example - loading m3d
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 4.5, last time updated with raylib 4.5
*
* Example contributed by bzt (@bztsrc) and reviewed by Ramon Santamaria (@raysan5)
*
* NOTES:
* - Model3D (M3D) fileformat specs: https://gitlab.com/bztsrc/model3d
* - Bender M3D exported: https://gitlab.com/bztsrc/model3d/-/tree/master/blender
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2022-2025 bzt (@bztsrc)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Models;
public partial class LoadingM3d : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Models / Loading M3D";
public string Title => "raylib [models] example - loading m3d";
private Camera3D camera;
private Model model;
private Vector3 position;
private unsafe ModelAnimation* anims;
private int animCount;
private int animIndex;
private float animCurrentFrame;
public unsafe void Init()
{
// Define the camera to look into our 3d world
camera = new();
camera.Position = new Vector3(1.5f, 1.5f, 1.5f); // Camera position
camera.Target = new Vector3(0.0f, 0.4f, 0.0f); // Camera looking at point
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
camera.FovY = 45.0f; // Camera field-of-view Y
camera.Projection = CameraProjection.Perspective; // Camera projection type
// Load model
model = LoadModel("resources/models/m3d/cesium_man.m3d"); // Load the animated model mesh and basic data
position = new Vector3(0.0f, 0.0f, 0.0f); // Set model position
// Load animation data
animCount = 0;
anims = LoadModelAnimations("resources/models/m3d/cesium_man.m3d", ref animCount);
// Animation playing variables
animIndex = 0; // Current animation playing
animCurrentFrame = 0.0f; // Current animation frame (supporting interpolated frames)
}
public unsafe void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(ref camera, CameraMode.Orbital);
// Select current animation
if (IsKeyPressed(KeyboardKey.Right))
{
animIndex = (animIndex + 1) % animCount;
}
else if (IsKeyPressed(KeyboardKey.Left))
{
animIndex = (animIndex + animCount - 1) % animCount;
}
// Update model animation
animCurrentFrame += 1.0f;
if (animCurrentFrame >= anims[animIndex].KeyFrameCount)
{
animCurrentFrame = 0.0f;
}
UpdateModelAnimation(model, anims[animIndex], animCurrentFrame);
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode3D(camera);
// Draw 3d model with texture
if (!IsKeyDown(KeyboardKey.Space))
{
DrawModel(model, position, 1.0f, Color.White);
}
else
{
// Draw the animated skeleton
DrawModelSkeleton(model.Skeleton, anims[animIndex].KeyframePoses[(int)animCurrentFrame], 1.0f, Color.Red);
}
DrawGrid(10, 1.0f);
EndMode3D();
DrawText($"Current animation: {anims[animIndex].NameToString()}", 10, 10, 20, Color.LightGray);
DrawText("Press SPACE to draw skeleton", 10, 40, 20, Color.Maroon);
DrawText("(c) CesiumMan model by KhronosGroup", GetScreenWidth() - 210, GetScreenHeight() - 20, 10, Color.Gray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public unsafe void Unload()
{
UnloadModelAnimations(anims, animCount); // Unload model animations data
UnloadModel(model); // Unload model
}
// Draw model skeleton
private static unsafe void DrawModelSkeleton(ModelSkeleton skeleton, Transform* pose, float scale, Color color)
{
// Loop to (boneCount - 1) because the last one is a special "no bone" bone,
// needed to workaround buggy models without a -1, a cube is always drawn at the origin
for (var i = 0; i < skeleton.BoneCount - 1; i++)
{
// Display the frame-pose skeleton
DrawCube(pose[i].Translation, scale * 0.05f, scale * 0.05f, scale * 0.05f, color);
if (skeleton.Bones[i].Parent >= 0)
{
DrawLine3D(pose[i].Translation, pose[skeleton.Bones[i].Parent].Translation, color);
}
}
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [models] example - loading m3d");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new LoadingM3d();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,246 @@
/*******************************************************************************************
*
* raylib [models] example - loading vox
*
* Example complexity rating: [] 1/4
*
* Example originally created with raylib 4.0, last time updated with raylib 4.0
*
* Example contributed by Johann Nadalutti (@procfxgen) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2021-2025 Johann Nadalutti (@procfxgen) and Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Raymath;
using Examples.Shared;
namespace Examples.Models;
public partial class LoadingVox : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int MaxVoxFiles = 4;
private const int MaxLights = 4;
#if BROWSER
private const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
#else
private const int GlslVersion = 330;
#endif
public string Name => "Models / Loading VOX";
public string Title => "raylib [models] example - loading vox";
private static readonly string[] VoxFileNames =
{
"resources/models/vox/chr_knight.vox",
"resources/models/vox/chr_sword.vox",
"resources/models/vox/monu9.vox",
"resources/models/vox/fez.vox"
};
private Camera3D camera;
private Model[] models;
private int currentModel;
private Vector3 modelpos;
private Vector3 camerarot;
private Shader shader;
private Light[] lights;
public unsafe void Init()
{
// Define the camera to look into our 3d world
camera = new();
camera.Position = new Vector3(10.0f, 10.0f, 10.0f); // Camera position
camera.Target = new Vector3(0.0f, 0.0f, 0.0f); // Camera looking at point
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
camera.FovY = 45.0f; // Camera field-of-view Y
camera.Projection = CameraProjection.Perspective; // Camera projection type
// Load MagicaVoxel files
models = new Model[MaxVoxFiles];
for (var i = 0; i < MaxVoxFiles; i++)
{
// Load VOX file and measure time
var t0 = GetTime() * 1000.0;
models[i] = LoadModel(VoxFileNames[i]);
var t1 = GetTime() * 1000.0;
TraceLog(TraceLogLevel.Info, $"[{VoxFileNames[i]}] Model file loaded in {t1 - t0:0.000} ms");
// Compute model translation matrix to center model on draw position (0, 0 , 0)
var bb = GetModelBoundingBox(models[i]);
Vector3 center = new();
center.X = bb.Min.X + ((bb.Max.X - bb.Min.X) / 2);
center.Z = bb.Min.Z + ((bb.Max.Z - bb.Min.Z) / 2);
var matTranslate = MatrixTranslate(-center.X, 0, -center.Z);
models[i].Transform = matTranslate;
}
currentModel = 0;
modelpos = new Vector3(0, 0, 0);
camerarot = new Vector3(0, 0, 0);
// Load voxel shader
shader = LoadShader(
$"resources/shaders/glsl{GlslVersion}/voxel_lighting.vs",
$"resources/shaders/glsl{GlslVersion}/voxel_lighting.fs"
);
// Get some required shader locations
shader.Locs[(int)ShaderLocationIndex.VectorView] = GetShaderLocation(shader, "viewPos");
// NOTE: "matModel" location name is automatically assigned on shader loading,
// no need to get the location again if using that uniform name
//shader.Locs[(int)ShaderLocationIndex.MatrixModel] = GetShaderLocation(shader, "matModel");
// Ambient light level (some basic lighting)
var ambientLoc = GetShaderLocation(shader, "ambient");
Raylib.SetShaderValue(shader, ambientLoc, new[] { 0.1f, 0.1f, 0.1f, 1.0f }, ShaderUniformDataType.Vec4);
// Assign out lighting shader to model
for (var i = 0; i < MaxVoxFiles; i++)
{
for (var j = 0; j < models[i].MaterialCount; j++)
{
models[i].Materials[j].Shader = shader;
}
}
// Create lights
lights = new Light[MaxLights];
lights[0] = Rlights.CreateLight(0, LightType.Point, new Vector3(-20, 20, -20), Vector3.Zero, Color.Gray, shader);
lights[1] = Rlights.CreateLight(1, LightType.Point, new Vector3(20, -20, 20), Vector3.Zero, Color.Gray, shader);
lights[2] = Rlights.CreateLight(2, LightType.Point, new Vector3(-20, 20, 20), Vector3.Zero, Color.Gray, shader);
lights[3] = Rlights.CreateLight(3, LightType.Point, new Vector3(20, -20, -20), Vector3.Zero, Color.Gray, shader);
}
public unsafe void Update()
{
// Update
//----------------------------------------------------------------------------------
if (IsMouseButtonDown(MouseButton.Middle))
{
var mouseDelta = GetMouseDelta();
camerarot.X = mouseDelta.X * 0.05f;
camerarot.Y = mouseDelta.Y * 0.05f;
}
else
{
camerarot.X = 0;
camerarot.Y = 0;
}
// Update camere movement, custom controls
UpdateCameraPro(ref camera,
new Vector3(
(IsKeyDown(KeyboardKey.W) || IsKeyDown(KeyboardKey.Up) ? 0.1f : 0.0f) - (IsKeyDown(KeyboardKey.S) || IsKeyDown(KeyboardKey.Down) ? 0.1f : 0.0f), // Move forward-backward
(IsKeyDown(KeyboardKey.D) || IsKeyDown(KeyboardKey.Right) ? 0.1f : 0.0f) - (IsKeyDown(KeyboardKey.A) || IsKeyDown(KeyboardKey.Left) ? 0.1f : 0.0f), // Move right-left
0.0f), // Move up-down
camerarot, // Camera rotation
GetMouseWheelMove() * -2.0f); // Move to target (zoom)
// Cycle between models on mouse click
if (IsMouseButtonPressed(MouseButton.Left))
{
currentModel = (currentModel + 1) % MaxVoxFiles;
}
// Update the shader with the camera view vector (points towards { 0.0f, 0.0f, 0.0f })
Raylib.SetShaderValue(shader, shader.Locs[(int)ShaderLocationIndex.VectorView], camera.Position, ShaderUniformDataType.Vec3);
// Update light values (actually, only enable/disable them)
for (var i = 0; i < MaxLights; i++)
{
Rlights.UpdateLightValues(shader, lights[i]);
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
// Draw 3D model
BeginMode3D(camera);
DrawModel(models[currentModel], modelpos, 1.0f, Color.White);
DrawGrid(10, 1.0f);
// Draw spheres to show where the lights are
for (var i = 0; i < MaxLights; i++)
{
if (lights[i].Enabled)
{
DrawSphereEx(lights[i].Position, 0.2f, 8, 8, lights[i].Color);
}
else
{
DrawSphereWires(lights[i].Position, 0.2f, 8, 8, ColorAlpha(lights[i].Color, 0.3f));
}
}
EndMode3D();
// Display info
DrawRectangle(10, 40, 340, 70, Fade(Color.SkyBlue, 0.5f));
DrawRectangleLines(10, 40, 340, 70, Fade(Color.DarkBlue, 0.5f));
DrawText("- MOUSE LEFT BUTTON: CYCLE VOX MODELS", 20, 50, 10, Color.Blue);
DrawText("- MOUSE MIDDLE BUTTON: ZOOM OR ROTATE CAMERA", 20, 70, 10, Color.Blue);
DrawText("- UP-DOWN-LEFT-RIGHT KEYS: MOVE CAMERA", 20, 90, 10, Color.Blue);
DrawText($"VOX model file: {GetFileName(VoxFileNames[currentModel])}", 10, 10, 20, Color.Gray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
// Unload models data (GPU VRAM)
for (var i = 0; i < MaxVoxFiles; i++)
{
UnloadModel(models[i]);
}
UnloadShader(shader);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [models] example - loading vox");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new LoadingVox();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -30,8 +30,6 @@ public partial class MeshPicking : IExample
public string Title => "raylib [models] example - mesh picking";
public bool CursorDisabled => true;
private Camera3D camera;
private Ray ray;
private Model tower;

View file

@ -79,8 +79,7 @@ public partial class ModelLoading : IExample
UpdateCamera(ref camera, CameraMode.Orbital);
#if BROWSER
// NOTE: Drag & drop file loading (IsFileDropped) is not available in the browser
// host, so it is skipped here. The default model stays loaded.
// NOTE: drag-and-drop model loading is not supported in the browser host; default loaded model is kept.
#else
// Load new models/textures on drag&drop
if (IsFileDropped())

View file

@ -0,0 +1,235 @@
/*******************************************************************************************
*
* raylib [models] example - point rendering
*
* Example complexity rating: [] 3/4
*
* Example originally created with raylib 5.0, last time updated with raylib 5.0
*
* Example contributed by Reese Gallagher (@satchelfrost) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2024-2025 Reese Gallagher (@satchelfrost)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Models;
[ExcludeFromBrowser("rlEnablePointMode needs glPolygonMode, which OpenGL ES/WebGL lacks (renders as triangles)")]
public partial class PointRendering : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int MaxPoints = 10000000; // 10 million
private const int MinPoints = 1000; // 1 thousand
public string Name => "Models / Point Rendering";
public string Title => "raylib [models] example - point rendering";
private static readonly Random Rand = new();
private Camera3D camera;
private Vector3 position;
private bool useDrawModelPoints;
private bool numPointsChanged;
private int numPoints;
private Mesh mesh;
private Model model;
public void Init()
{
camera = new()
{
Position = new Vector3(3.0f, 3.0f, 3.0f),
Target = new Vector3(0.0f, 0.0f, 0.0f),
Up = new Vector3(0.0f, 1.0f, 0.0f),
FovY = 45.0f,
Projection = CameraProjection.Perspective
};
position = new Vector3(0.0f, 0.0f, 0.0f);
useDrawModelPoints = true;
numPointsChanged = false;
numPoints = 1000;
mesh = GenMeshPoints(numPoints);
model = LoadModelFromMesh(mesh);
}
public unsafe void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(ref camera, CameraMode.Orbital);
if (IsKeyPressed(KeyboardKey.Space))
{
useDrawModelPoints = !useDrawModelPoints;
}
if (IsKeyPressed(KeyboardKey.Up))
{
numPoints = (numPoints * 10 > MaxPoints) ? MaxPoints : numPoints * 10;
numPointsChanged = true;
}
if (IsKeyPressed(KeyboardKey.Down))
{
numPoints = (numPoints / 10 < MinPoints) ? MinPoints : numPoints / 10;
numPointsChanged = true;
}
// Upload a different point cloud size
if (numPointsChanged)
{
UnloadModel(model);
mesh = GenMeshPoints(numPoints);
model = LoadModelFromMesh(mesh);
numPointsChanged = false;
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.Black);
BeginMode3D(camera);
// The new method only uploads the points once to the GPU
if (useDrawModelPoints)
{
DrawModelPoints(model, position, 1.0f, Color.White);
}
else
{
// The old method must continually draw the "points" (lines)
for (var i = 0; i < numPoints; i++)
{
Vector3 pos = new(
mesh.Vertices[i * 3 + 0],
mesh.Vertices[i * 3 + 1],
mesh.Vertices[i * 3 + 2]
);
Color color = new(
mesh.Colors[i * 4 + 0],
mesh.Colors[i * 4 + 1],
mesh.Colors[i * 4 + 2],
mesh.Colors[i * 4 + 3]
);
DrawPoint3D(pos, color);
}
}
// Draw a unit sphere for reference
DrawSphereWires(position, 1.0f, 10, 10, Color.Yellow);
EndMode3D();
// Draw UI text
DrawText($"Point Count: {numPoints}", 10, screenHeight - 50, 40, Color.White);
DrawText("UP - Increase points", 10, 40, 20, Color.White);
DrawText("DOWN - Decrease points", 10, 70, 20, Color.White);
DrawText("SPACE - Drawing function", 10, 100, 20, Color.White);
if (useDrawModelPoints)
{
DrawText("Using: DrawModelPoints()", 10, 130, 20, Color.Green);
}
else
{
DrawText("Using: DrawPoint3D()", 10, 130, 20, Color.Red);
}
DrawFPS(10, 10);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadModel(model);
}
// Generate a spherical point cloud
private static unsafe Mesh GenMeshPoints(int numPoints)
{
Mesh mesh = new(numPoints, 1);
mesh.AllocVertices();
mesh.AllocColors();
// REF: https://en.wikipedia.org/wiki/Spherical_coordinate_system
for (var i = 0; i < numPoints; i++)
{
var theta = MathF.PI * (float)Rand.NextDouble();
var phi = 2.0f * MathF.PI * (float)Rand.NextDouble();
var r = 10.0f * (float)Rand.NextDouble();
mesh.Vertices[i * 3 + 0] = r * MathF.Sin(theta) * MathF.Cos(phi);
mesh.Vertices[i * 3 + 1] = r * MathF.Sin(theta) * MathF.Sin(phi);
mesh.Vertices[i * 3 + 2] = r * MathF.Cos(theta);
var color = ColorFromHSV(r * 360.0f, 1.0f, 1.0f);
mesh.Colors[i * 4 + 0] = color.R;
mesh.Colors[i * 4 + 1] = color.G;
mesh.Colors[i * 4 + 2] = color.B;
mesh.Colors[i * 4 + 3] = color.A;
}
// Upload mesh data from CPU (RAM) to GPU (VRAM) memory
UploadMesh(ref mesh, false);
return mesh;
}
// Draw a model points
// WARNING: OpenGL ES 2.0 does not support point mode drawing
private static void DrawModelPoints(Model model, Vector3 position, float scale, Color tint)
{
Rlgl.EnablePointMode();
Rlgl.DisableBackfaceCulling();
DrawModel(model, position, scale, tint);
Rlgl.EnableBackfaceCulling();
Rlgl.DisablePointMode();
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [models] example - point rendering");
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new PointRendering();
game.Init();
// Main game loop
while (!WindowShouldClose())
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow();
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,122 @@
/*******************************************************************************************
*
* raylib [models] example - rotating cube
*
* Example complexity rating: [] 1/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by Jopestpe (@jopestpe)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Jopestpe (@jopestpe)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Models;
public partial class RotatingCube : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Models / Rotating Cube";
public string Title => "raylib [models] example - rotating cube";
private Camera3D camera;
private Model model;
private Texture2D texture;
private float rotation;
public unsafe void Init()
{
// Define the camera to look into our 3d world
camera = new();
camera.Position = new Vector3(0.0f, 3.0f, 3.0f);
camera.Target = new Vector3(0.0f, 0.0f, 0.0f);
camera.Up = new Vector3(0.0f, 1.0f, 0.0f);
camera.FovY = 45.0f;
camera.Projection = CameraProjection.Perspective;
// Load image to create texture for the cube
model = LoadModelFromMesh(GenMeshCube(1.0f, 1.0f, 1.0f));
var img = LoadImage("resources/cubicmap_atlas.png");
var crop = ImageFromImage(img, new Rectangle(0, img.Height / 2.0f, img.Width / 2.0f, img.Height / 2.0f));
texture = LoadTextureFromImage(crop);
UnloadImage(img);
UnloadImage(crop);
model.Materials[0].Maps[(int)MaterialMapIndex.Diffuse].Texture = texture;
rotation = 0.0f;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
rotation += 1.0f;
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode3D(camera);
// Draw model defining: position, size, rotation-axis, rotation (degrees), size, and tint-color
DrawModelEx(model, new Vector3(0.0f, 0.0f, 0.0f), new Vector3(0.5f, 1.0f, 0.0f),
rotation, new Vector3(1.0f, 1.0f, 1.0f), Color.White);
DrawGrid(10, 1.0f);
EndMode3D();
DrawFPS(10, 10);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadTexture(texture); // Unload texture
UnloadModel(model); // Unload model
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [models] example - rotating cube");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new RotatingCube();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -19,13 +19,18 @@ using static Raylib_cs.Raylib;
namespace Examples.Models;
[ExcludeFromBrowser("cubemap generation is too memory-heavy on web (upstream note)")]
public partial class SkyboxDemo : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
// GLSL version used for shaders (330 desktop, 100 web/GLES)
#if BROWSER
public const int GlslVersion = 100;
#else
public const int GlslVersion = 330;
#endif
public string Name => "Models / Skybox Demo";

View file

@ -0,0 +1,163 @@
/*******************************************************************************************
*
* raylib [models] example - tesseract view
*
* NOTE: This example only works on platforms that support drag & drop (Windows, Linux, OSX, Html5?)
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by Timothy van der Valk (@arceryz) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2024-2025 Timothy van der Valk (@arceryz) and Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Raymath;
namespace Examples.Models;
public partial class TesseractView : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Models / Tesseract View";
public string Title => "raylib [models] example - tesseract view";
// Define the camera to look into our 3d world
private Camera3D camera;
// Find the coordinates by setting XYZW to +-1
private Vector4[] tesseract;
private float rotation;
private Vector3[] transformed;
private float[] wValues;
public void Init()
{
// Define the camera to look into our 3d world
camera = new Camera3D();
camera.Position = new Vector3(4.0f, 4.0f, 4.0f); // Camera position
camera.Target = new Vector3(0.0f, 0.0f, 0.0f); // Camera looking at point
camera.Up = new Vector3(0.0f, 0.0f, 1.0f); // Camera up vector (rotation towards target)
camera.FovY = 50.0f; // Camera field-of-view Y
camera.Projection = CameraProjection.Perspective; // Camera mode type
// Find the coordinates by setting XYZW to +-1
tesseract = new Vector4[16]
{
new( 1, 1, 1, 1 ), new( 1, 1, 1, -1 ),
new( 1, 1, -1, 1 ), new( 1, 1, -1, -1 ),
new( 1, -1, 1, 1 ), new( 1, -1, 1, -1 ),
new( 1, -1, -1, 1 ), new( 1, -1, -1, -1 ),
new(-1, 1, 1, 1 ), new(-1, 1, 1, -1 ),
new(-1, 1, -1, 1 ), new(-1, 1, -1, -1 ),
new(-1, -1, 1, 1 ), new(-1, -1, 1, -1 ),
new(-1, -1, -1, 1 ), new(-1, -1, -1, -1 ),
};
rotation = 0.0f;
transformed = new Vector3[16];
wValues = new float[16];
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
rotation = DEG2RAD * 45.0f * (float)GetTime();
for (int i = 0; i < 16; i++)
{
Vector4 p = tesseract[i];
// Rotate the XW part of the vector
Vector2 rotXW = Vector2Rotate(new Vector2(p.X, p.W), rotation);
p.X = rotXW.X;
p.W = rotXW.Y;
// Projection from XYZW to XYZ from perspective point (0, 0, 0, 3)
// NOTE: Trace a ray from (0, 0, 0, 3) > p and continue until W = 0
float c = 3.0f / (3.0f - p.W);
p.X = c * p.X;
p.Y = c * p.Y;
p.Z = c * p.Z;
// Split XYZ coordinate and W values later for drawing
transformed[i] = new Vector3(p.X, p.Y, p.Z);
wValues[i] = p.W;
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode3D(camera);
for (int i = 0; i < 16; i++)
{
// Draw spheres to indicate the W value
DrawSphere(transformed[i], MathF.Abs(wValues[i] * 0.1f), Color.Red);
for (int j = 0; j < 16; j++)
{
// Two lines are connected if they differ by 1 coordinate
// This way we dont have to keep an edge list
Vector4 v1 = tesseract[i];
Vector4 v2 = tesseract[j];
int diff = (v1.X == v2.X ? 1 : 0) + (v1.Y == v2.Y ? 1 : 0) + (v1.Z == v2.Z ? 1 : 0) + (v1.W == v2.W ? 1 : 0);
// Draw only differing by 1 coordinate and the lower index only (duplicate lines)
if (diff == 3 && i < j) DrawLine3D(transformed[i], transformed[j], Color.Maroon);
}
}
EndMode3D();
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [models] example - tesseract view");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new TesseractView();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -5,9 +5,6 @@ namespace Examples;
internal static class Program
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private static unsafe void Main(string[] args)
{
Raylib.SetTraceLogCallback(&Logging.LogConsole);
@ -50,7 +47,7 @@ internal static class Program
SetConfigFlags(example.ConfigFlags);
}
InitWindow(screenWidth, screenHeight, example.Title);
InitWindow(example.Width, example.Height, example.Title);
if (example.CursorDisabled)
{
@ -63,16 +60,26 @@ internal static class Program
SetTargetFPS(example.TargetFps);
example.Init();
while (!WindowShouldClose())
try
{
example.Update();
example.Init();
while (!example.ShouldClose)
{
example.Update();
}
}
finally
{
try
{
example.Unload();
}
finally
{
CloseWindow();
}
}
example.Unload();
CloseWindow();
}
private static void RunExampleProcess(

View file

@ -0,0 +1,166 @@
/*******************************************************************************************
*
* raylib [shaders] example - ascii rendering
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 5.5, last time updated with raylib 6.0
*
* Example contributed by Maicon Santana (@maiconpintoabreu) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Maicon Santana (@maiconpintoabreu)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Shaders;
public partial class AsciiRendering : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
#if BROWSER
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
#else
private const int GlslVersion = 330;
#endif
public string Name => "Shaders / Ascii Rendering";
public string Title => "raylib [shaders] example - ascii rendering";
private Texture2D fudesumi;
private Texture2D raysan;
private Shader shader;
private int resolutionLoc;
private int fontSizeLoc;
private float fontSize;
private Vector2 circlePos;
private float circleSpeed;
private RenderTexture2D target;
public void Init()
{
// Texture to test static drawing
fudesumi = LoadTexture("resources/fudesumi.png");
// Texture to test moving drawing
raysan = LoadTexture("resources/raysan.png");
// Load shader to be used on postprocessing
shader = LoadShader(null, $"resources/shaders/glsl{GlslVersion}/ascii.fs");
// These locations are used to send data to the GPU
resolutionLoc = GetShaderLocation(shader, "resolution");
fontSizeLoc = GetShaderLocation(shader, "fontSize");
// Set the character size for the ASCII effect
// Fontsize should be 9 or more
fontSize = 9.0f;
// Send the updated values to the shader
var resolution = new[] { (float)screenWidth, (float)screenHeight };
Raylib.SetShaderValue(shader, resolutionLoc, resolution, ShaderUniformDataType.Vec2);
circlePos = new Vector2(40.0f, screenHeight * 0.5f);
circleSpeed = 1.0f;
// RenderTexture to apply the postprocessing later
target = LoadRenderTexture(screenWidth, screenHeight);
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
circlePos.X += circleSpeed;
if ((circlePos.X > 200.0f) || (circlePos.X < 40.0f))
{
circleSpeed *= -1; // Revert speed
}
if (IsKeyPressed(KeyboardKey.Left) && (fontSize > 9.0))
{
fontSize -= 1; // Reduce fontSize
}
if (IsKeyPressed(KeyboardKey.Right) && (fontSize < 15.0))
{
fontSize += 1; // Increase fontSize
}
// Set fontsize for the shader
Raylib.SetShaderValue(shader, fontSizeLoc, fontSize, ShaderUniformDataType.Float);
// Draw
//----------------------------------------------------------------------------------
BeginTextureMode(target);
ClearBackground(Color.White);
// Draw scene in our render texture
DrawTexture(fudesumi, 500, -30, Color.White);
DrawTextureV(raysan, circlePos, Color.White);
EndTextureMode();
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginShaderMode(shader);
// Draw the scene texture (that we rendered earlier) to the screen
// The shader will process every pixel of this texture
DrawTextureRec(
target.Texture,
new Rectangle(0, 0, target.Texture.Width, -target.Texture.Height),
new Vector2(0, 0),
Color.White
);
EndShaderMode();
DrawRectangle(0, 0, screenWidth, 40, Color.Black);
DrawText($"Ascii effect - FontSize:{fontSize,2:F0} - [Left] -1 [Right] +1 ", 120, 10, 20, Color.LightGray);
DrawFPS(10, 10);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadRenderTexture(target); // Unload render texture
UnloadShader(shader); // Unload shader
UnloadTexture(fudesumi); // Unload texture
UnloadTexture(raysan); // Unload texture
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - ascii rendering");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new AsciiRendering();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,252 @@
/*******************************************************************************************
*
* raylib [shaders] example - cel shading
*
* Example complexity rating: [] 3/4
*
* NOTE: This example requires raylib OpenGL 3.3 or ES2 versions for shaders support,
* OpenGL 1.1 does not support shaders, recompile raylib to OpenGL 3.3 version
*
* NOTE: Shaders used in this example are #version 330 (OpenGL 3.3)
*
* Example contributed by Gleb A (@ggrizzly) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2026 Gleb A (@ggrizzly)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Raymath;
using static Raylib_cs.Rlgl;
using Examples.Shared;
namespace Examples.Shaders;
public partial class CelShading : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int MaxLights = 4;
// rlgl cull face modes (rlgl.h: RL_CULL_FACE_FRONT = 0, RL_CULL_FACE_BACK = 1)
private const int CullFaceFront = 0;
private const int CullFaceBack = 1;
#if BROWSER
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
#else
private const int GlslVersion = 330;
#endif
public string Name => "Shaders / Cel Shading";
public string Title => "raylib [shaders] example - cel shading";
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
private Camera3D camera;
private Model model;
private Shader celShader;
private Shader defaultShader;
private Shader outlineShader;
private float numBands;
private int numBandsLoc;
private int outlineThicknessLoc;
private Light[] lights;
private bool celEnabled;
private bool outlineEnabled;
public unsafe void Init()
{
camera = new();
camera.Position = new Vector3(9.0f, 6.0f, 9.0f);
camera.Target = new Vector3(0.0f, 1.0f, 0.0f);
camera.Up = new Vector3(0.0f, 1.0f, 0.0f);
camera.FovY = 45.0f;
camera.Projection = CameraProjection.Perspective;
// Load model
model = LoadModel("resources/models/old_car_new.glb");
// Load cel shader
celShader = LoadShader(
$"resources/shaders/glsl{GlslVersion}/cel.vs",
$"resources/shaders/glsl{GlslVersion}/cel.fs"
);
celShader.Locs[(int)ShaderLocationIndex.VectorView] = GetShaderLocation(celShader, "viewPos");
// Apply cel shader to model, keep copy of default shader
defaultShader = model.Materials[0].Shader;
model.Materials[0].Shader = celShader;
// numBands: controls toon quantization steps (2 = hard binary, 20 = near-smooth)
numBands = 10.0f;
numBandsLoc = GetShaderLocation(celShader, "numBands");
Raylib.SetShaderValue(celShader, numBandsLoc, numBands, ShaderUniformDataType.Float);
// Inverted-hull outline shader: draws back faces extruded along normals
outlineShader = LoadShader(
$"resources/shaders/glsl{GlslVersion}/outline_hull.vs",
$"resources/shaders/glsl{GlslVersion}/outline_hull.fs"
);
outlineThicknessLoc = GetShaderLocation(outlineShader, "outlineThickness");
// Single directional white light, angled so toon bands are visible on the model sides.
// Spins opposite to CAMERA_ORBITAL (0.5 rad/s) so lighting changes as you watch.
lights = new Light[MaxLights];
lights[0] = Rlights.CreateLight(
0,
LightType.Directorional,
new Vector3(50.0f, 50.0f, 50.0f),
Vector3.Zero,
Color.White,
celShader
);
celEnabled = true;
outlineEnabled = true;
}
public unsafe void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(ref camera, CameraMode.Orbital);
Raylib.SetShaderValue(
celShader,
celShader.Locs[(int)ShaderLocationIndex.VectorView],
camera.Position,
ShaderUniformDataType.Vec3
);
// [Z] Toggle cel shading on/off
if (IsKeyPressed(KeyboardKey.Z))
{
celEnabled = !celEnabled;
if (celEnabled)
{
model.Materials[0].Shader = celShader; // Apply cel shader to model
}
else
{
model.Materials[0].Shader = defaultShader; // Apply default shader to model
}
}
// [C] Toggle outline on/off
if (IsKeyPressed(KeyboardKey.C))
{
outlineEnabled = !outlineEnabled;
}
// [Q/E] Decrease/increase toon band count (press or hold to repeat)
if (IsKeyPressed(KeyboardKey.E) || IsKeyPressedRepeat(KeyboardKey.E))
{
numBands = Clamp(numBands + 1.0f, 2.0f, 20.0f);
}
if (IsKeyPressed(KeyboardKey.Q) || IsKeyPressedRepeat(KeyboardKey.Q))
{
numBands = Clamp(numBands - 1.0f, 2.0f, 20.0f);
}
Raylib.SetShaderValue(celShader, numBandsLoc, numBands, ShaderUniformDataType.Float);
// Spin light opposite to CAMERA_ORBITAL (0.5 rad/s), angled 45 degrees off vertical
float t = (float)GetTime();
lights[0].Position = new Vector3(MathF.Sin(-t * 0.3f) * 5.0f, 5.0f, MathF.Cos(-t * 0.3f) * 5.0f);
for (var i = 0; i < MaxLights; i++)
{
Rlights.UpdateLightValues(celShader, lights[i]);
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode3D(camera);
if (outlineEnabled)
{
// Outline pass: cull front faces, draw extruded back faces as silhouette
float thickness = 0.005f;
Raylib.SetShaderValue(outlineShader, outlineThicknessLoc, thickness, ShaderUniformDataType.Float);
SetCullFace(CullFaceFront);
model.Materials[0].Shader = outlineShader;
DrawModel(model, Vector3.Zero, 0.75f, Color.White);
if (celEnabled)
{
model.Materials[0].Shader = celShader; // Apply cel shader to model
}
else
{
model.Materials[0].Shader = defaultShader; // Apply default shader to model
}
SetCullFace(CullFaceBack);
}
DrawModel(model, Vector3.Zero, 0.75f, Color.White);
DrawSphereEx(lights[0].Position, 0.2f, 50, 50, Color.Yellow); // Light position indicator
DrawGrid(10, 10.0f);
EndMode3D();
DrawFPS(10, 10);
DrawText($"Cel: {(celEnabled ? "ON" : "OFF")} [Z]", 10, 65, 20, celEnabled ? Color.DarkGreen : Color.DarkGray);
DrawText($"Outline: {(outlineEnabled ? "ON" : "OFF")} [C]", 10, 90, 20, outlineEnabled ? Color.DarkGreen : Color.DarkGray);
DrawText($"Bands: {numBands:0} [Q/E]", 10, 115, 20, Color.DarkGray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadModel(model);
UnloadShader(celShader);
UnloadShader(outlineShader);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
SetConfigFlags(ConfigFlags.Msaa4xHint);
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - cel shading");
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new CelShading();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,184 @@
/*******************************************************************************************
*
* raylib [shaders] example - color correction
*
* Example complexity rating: [] 2/4
*
* NOTE: This example requires raylib OpenGL 3.3 or ES2 versions for shaders support,
* OpenGL 1.1 does not support shaders, recompile raylib to OpenGL 3.3 version
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by Jordi Santonja (@JordSant) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Jordi Santonja (@JordSant)
*
********************************************************************************************/
using static Raylib_cs.Raylib;
namespace Examples.Shaders;
public partial class ColorCorrection : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
#if BROWSER
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
#else
private const int GlslVersion = 330;
#endif
private const int MaxTextures = 4;
public string Name => "Shaders / Color Correction";
public string Title => "raylib [shaders] example - color correction";
private Texture2D[] texture;
private Shader shdrColorCorrection;
private int imageIndex;
private int resetButtonClicked;
private float contrast;
private float saturation;
private float brightness;
private int contrastLoc;
private int saturationLoc;
private int brightnessLoc;
public void Init()
{
texture = new[]
{
LoadTexture("resources/parrots.png"),
LoadTexture("resources/cat.png"),
LoadTexture("resources/mandrill.png"),
LoadTexture("resources/fudesumi.png")
};
shdrColorCorrection = LoadShader(null, $"resources/shaders/glsl{GlslVersion}/color_correction.fs");
imageIndex = 0;
resetButtonClicked = 0;
contrast = 0.0f;
saturation = 0.0f;
brightness = 0.0f;
// Get shader locations
contrastLoc = GetShaderLocation(shdrColorCorrection, "contrast");
saturationLoc = GetShaderLocation(shdrColorCorrection, "saturation");
brightnessLoc = GetShaderLocation(shdrColorCorrection, "brightness");
// Set shader values (they can be changed later)
Raylib.SetShaderValue(shdrColorCorrection, contrastLoc, contrast, ShaderUniformDataType.Float);
Raylib.SetShaderValue(shdrColorCorrection, saturationLoc, saturation, ShaderUniformDataType.Float);
Raylib.SetShaderValue(shdrColorCorrection, brightnessLoc, brightness, ShaderUniformDataType.Float);
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
// Select texture to draw
if (IsKeyPressed(KeyboardKey.One)) imageIndex = 0;
else if (IsKeyPressed(KeyboardKey.Two)) imageIndex = 1;
else if (IsKeyPressed(KeyboardKey.Three)) imageIndex = 2;
else if (IsKeyPressed(KeyboardKey.Four)) imageIndex = 3;
// Reset values to 0
if (IsKeyPressed(KeyboardKey.R) || resetButtonClicked != 0)
{
contrast = 0.0f;
saturation = 0.0f;
brightness = 0.0f;
}
// Send the values to the shader
Raylib.SetShaderValue(shdrColorCorrection, contrastLoc, contrast, ShaderUniformDataType.Float);
Raylib.SetShaderValue(shdrColorCorrection, saturationLoc, saturation, ShaderUniformDataType.Float);
Raylib.SetShaderValue(shdrColorCorrection, brightnessLoc, brightness, ShaderUniformDataType.Float);
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginShaderMode(shdrColorCorrection);
DrawTexture(texture[imageIndex], 580 / 2 - texture[imageIndex].Width / 2, GetScreenHeight() / 2 - texture[imageIndex].Height / 2, Color.White);
EndShaderMode();
DrawLine(580, 0, 580, GetScreenHeight(), new Color(218, 218, 218, 255));
DrawRectangle(580, 0, GetScreenWidth(), GetScreenHeight(), new Color(232, 232, 232, 255));
// Draw UI info text
DrawText("Color Correction", 585, 40, 20, Color.Gray);
DrawText("Picture", 602, 75, 10, Color.Gray);
DrawText("Press [1] - [4] to Change Picture", 600, 230, 8, Color.Gray);
DrawText("Press [R] to Reset Values", 600, 250, 8, Color.Gray);
// Draw GUI controls
//------------------------------------------------------------------------------
// NOTE: raygui is not bound in raylib-cs; controls are kept for reference. Use
// keyboard shortcuts ([1]-[4], [R]) to interact with the example.
/*GuiToggleGroup(new Rectangle( 645, 70, 20, 20 ), "1;2;3;4", ref imageIndex);
GuiSliderBar(new Rectangle( 645, 100, 120, 20 ), "Contrast", TextFormat("%.0f", contrast), ref contrast, -100.0f, 100.0f);
GuiSliderBar(new Rectangle( 645, 130, 120, 20 ), "Saturation", TextFormat("%.0f", saturation), ref saturation, -100.0f, 100.0f);
GuiSliderBar(new Rectangle( 645, 160, 120, 20 ), "Brightness", TextFormat("%.0f", brightness), ref brightness, -100.0f, 100.0f);
resetButtonClicked = GuiButton(new Rectangle( 645, 190, 40, 20 ), "Reset");*/
//------------------------------------------------------------------------------
DrawFPS(710, 10);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
for (var i = 0; i < MaxTextures; i++)
{
UnloadTexture(texture[i]);
}
UnloadShader(shdrColorCorrection);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - color correction");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new ColorCorrection();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,400 @@
/*******************************************************************************************
*
* raylib [shaders] example - deferred rendering
*
* Example complexity rating: [] 4/4
*
* NOTE: This example requires raylib OpenGL 3.3 or OpenGL ES 3.0
*
* Example originally created with raylib 4.5, last time updated with raylib 4.5
*
* Example contributed by Justin Andreas Lacoste (@27justin) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2023-2025 Justin Andreas Lacoste (@27justin)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
using Examples.Shared;
namespace Examples.Shaders;
[ExcludeFromBrowser("multiple-render-target G-buffer, unsupported on WebGL1/GLSL100")]
public partial class DeferredRendering : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
#if BROWSER
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
#else
private const int GlslVersion = 330;
#endif
private const int MaxCubes = 30;
private const int MaxLights = 4;
private const float CubeScale = 0.25f;
// GL_READ_FRAMEBUFFER / GL_DRAW_FRAMEBUFFER / GL_DEPTH_BUFFER_BIT
private const uint RlReadFramebuffer = 0x8CA8;
private const uint RlDrawFramebuffer = 0x8CA9;
private const int GlDepthBufferBit = 0x00000100;
public string Name => "Shaders / Deferred Rendering";
public string Title => "raylib [shaders] example - deferred rendering";
// GBuffer data
private struct GBuffer
{
public uint FramebufferId;
public uint PositionTextureId;
public uint NormalTextureId;
public uint AlbedoSpecTextureId;
public uint DepthRenderbufferId;
}
// Deferred mode passes
private enum DeferredMode
{
Position,
Normal,
Albedo,
Shading
}
private Camera3D camera;
private Model model;
private Model cube;
private Shader gbufferShader;
private Shader deferredShader;
private GBuffer gBuffer;
private Light[] lights;
private Vector3[] cubePositions;
private float[] cubeRotations;
private DeferredMode mode;
// Texture units our g-buffer textures are bound to
private const int TexUnitPosition = 0;
private const int TexUnitNormal = 1;
private const int TexUnitAlbedoSpec = 2;
public unsafe void Init()
{
camera = new();
camera.Position = new Vector3(5.0f, 4.0f, 5.0f); // Camera position
camera.Target = new Vector3(0.0f, 1.0f, 0.0f); // Camera looking at point
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
camera.FovY = 60.0f; // Camera field-of-view Y
camera.Projection = CameraProjection.Perspective; // Camera projection type
// Load plane model from a generated mesh
model = LoadModelFromMesh(GenMeshPlane(10.0f, 10.0f, 3, 3));
cube = LoadModelFromMesh(GenMeshCube(2.0f, 2.0f, 2.0f));
// Load geometry buffer (G-buffer) shader and deferred shader
gbufferShader = LoadShader(
$"resources/shaders/glsl{GlslVersion}/gbuffer.vs",
$"resources/shaders/glsl{GlslVersion}/gbuffer.fs"
);
deferredShader = LoadShader(
$"resources/shaders/glsl{GlslVersion}/deferred_shading.vs",
$"resources/shaders/glsl{GlslVersion}/deferred_shading.fs"
);
deferredShader.Locs[(int)ShaderLocationIndex.VectorView] = GetShaderLocation(deferredShader, "viewPosition");
// Initialize the G-buffer
gBuffer = new();
gBuffer.FramebufferId = Rlgl.LoadFramebuffer();
if (gBuffer.FramebufferId == 0)
{
TraceLog(TraceLogLevel.Warning, "Failed to create framebufferId");
}
Rlgl.EnableFramebuffer(gBuffer.FramebufferId);
// NOTE: Vertex positions are stored in a texture for simplicity. A better approach would use a depth texture
// (instead of a detph renderbuffer) to reconstruct world positions in the final render shader via clip-space position,
// depth, and the inverse view/projection matrices
// 16-bit precision ensures OpenGL ES 3 compatibility, though it may lack precision for real scenarios
gBuffer.PositionTextureId = Rlgl.LoadTexture(null, screenWidth, screenHeight, PixelFormat.UncompressedR16G16B16, 1);
// Similarly, 16-bit precision is used for normals ensures OpenGL ES 3 compatibility
gBuffer.NormalTextureId = Rlgl.LoadTexture(null, screenWidth, screenHeight, PixelFormat.UncompressedR16G16B16, 1);
// Albedo (diffuse color) and specular strength can be combined into one texture
// The color in RGB, and the specular strength in the alpha channel
gBuffer.AlbedoSpecTextureId = Rlgl.LoadTexture(null, screenWidth, screenHeight, PixelFormat.UncompressedR8G8B8A8, 1);
// Activate the draw buffers for our framebufferId
Rlgl.ActiveDrawBuffers(3);
// Now we attach our textures to the framebufferId
Rlgl.FramebufferAttach(gBuffer.FramebufferId, gBuffer.PositionTextureId, FramebufferAttachType.ColorChannel0, FramebufferAttachTextureType.Texture2D, 0);
Rlgl.FramebufferAttach(gBuffer.FramebufferId, gBuffer.NormalTextureId, FramebufferAttachType.ColorChannel1, FramebufferAttachTextureType.Texture2D, 0);
Rlgl.FramebufferAttach(gBuffer.FramebufferId, gBuffer.AlbedoSpecTextureId, FramebufferAttachType.ColorChannel2, FramebufferAttachTextureType.Texture2D, 0);
// Finally we attach the depth buffer
gBuffer.DepthRenderbufferId = Rlgl.LoadTextureDepth(screenWidth, screenHeight, true);
Rlgl.FramebufferAttach(gBuffer.FramebufferId, gBuffer.DepthRenderbufferId, FramebufferAttachType.Depth, FramebufferAttachTextureType.Renderbuffer, 0);
// Make sure our framebufferId is complete
// NOTE: rlFramebufferComplete() automatically unbinds the framebufferId, so we don't have to rlDisableFramebuffer() here
if (Rlgl.FramebufferComplete(gBuffer.FramebufferId) == 0)
{
TraceLog(TraceLogLevel.Warning, "Framebuffer is not complete");
}
// Now we initialize the sampler2D uniform's in the deferred shader
// We do this by setting the uniform's values to the texture units that
// we later bind our g-buffer textures to
Rlgl.EnableShader(deferredShader.Id);
int texUnitPosition = TexUnitPosition;
int texUnitNormal = TexUnitNormal;
int texUnitAlbedoSpec = TexUnitAlbedoSpec;
Raylib.SetShaderValue(deferredShader, GetShaderLocation(deferredShader, "gPosition"), texUnitPosition, ShaderUniformDataType.Sampler2D);
Raylib.SetShaderValue(deferredShader, GetShaderLocation(deferredShader, "gNormal"), texUnitNormal, ShaderUniformDataType.Sampler2D);
Raylib.SetShaderValue(deferredShader, GetShaderLocation(deferredShader, "gAlbedoSpec"), texUnitAlbedoSpec, ShaderUniformDataType.Sampler2D);
Rlgl.DisableShader();
// Assign out lighting shader to model
model.Materials[0].Shader = gbufferShader;
cube.Materials[0].Shader = gbufferShader;
// Create lights
lights = new Light[MaxLights];
lights[0] = Rlights.CreateLight(0, LightType.Point, new Vector3(-2, 1, -2), Vector3.Zero, Color.Yellow, deferredShader);
lights[1] = Rlights.CreateLight(1, LightType.Point, new Vector3(2, 1, 2), Vector3.Zero, Color.Red, deferredShader);
lights[2] = Rlights.CreateLight(2, LightType.Point, new Vector3(-2, 1, 2), Vector3.Zero, Color.Green, deferredShader);
lights[3] = Rlights.CreateLight(3, LightType.Point, new Vector3(2, 1, -2), Vector3.Zero, Color.Blue, deferredShader);
var rand = new Random();
cubePositions = new Vector3[MaxCubes];
cubeRotations = new float[MaxCubes];
for (var i = 0; i < MaxCubes; i++)
{
cubePositions[i] = new Vector3(
(float)(rand.Next() % 10) - 5,
(float)(rand.Next() % 5),
(float)(rand.Next() % 10) - 5
);
cubeRotations[i] = (float)(rand.Next() % 360);
}
mode = DeferredMode.Shading;
Rlgl.EnableDepthTest();
}
public unsafe void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(ref camera, CameraMode.Orbital);
// Update the shader with the camera view vector (points towards { 0.0f, 0.0f, 0.0f })
Raylib.SetShaderValue(
deferredShader,
deferredShader.Locs[(int)ShaderLocationIndex.VectorView],
camera.Position,
ShaderUniformDataType.Vec3
);
// Check key inputs to enable/disable lights
if (IsKeyPressed(KeyboardKey.Y)) { lights[0].Enabled = !lights[0].Enabled; }
if (IsKeyPressed(KeyboardKey.R)) { lights[1].Enabled = !lights[1].Enabled; }
if (IsKeyPressed(KeyboardKey.G)) { lights[2].Enabled = !lights[2].Enabled; }
if (IsKeyPressed(KeyboardKey.B)) { lights[3].Enabled = !lights[3].Enabled; }
// Check key inputs to switch between G-buffer textures
if (IsKeyPressed(KeyboardKey.One)) mode = DeferredMode.Position;
if (IsKeyPressed(KeyboardKey.Two)) mode = DeferredMode.Normal;
if (IsKeyPressed(KeyboardKey.Three)) mode = DeferredMode.Albedo;
if (IsKeyPressed(KeyboardKey.Four)) mode = DeferredMode.Shading;
// Update light values (actually, only enable/disable them)
for (var i = 0; i < MaxLights; i++) Rlights.UpdateLightValues(deferredShader, lights[i]);
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
// Draw to the geometry buffer by first activating it
Rlgl.EnableFramebuffer(gBuffer.FramebufferId);
Rlgl.ClearColor(0, 0, 0, 0);
Rlgl.ClearScreenBuffers(); // Clear color and depth buffer
Rlgl.DisableColorBlend();
BeginMode3D(camera);
// NOTE: We have to use rlEnableShader here. `BeginShaderMode` or thus `rlSetShader`
// will not work, as they won't immediately load the shader program
Rlgl.EnableShader(gbufferShader.Id);
// When drawing a model here, make sure that the material's shaders are set to the gbuffer shader!
DrawModel(model, Vector3.Zero, 1.0f, Color.White);
DrawModel(cube, new Vector3(0.0f, 1.0f, 0.0f), 1.0f, Color.White);
for (var i = 0; i < MaxCubes; i++)
{
var position = cubePositions[i];
DrawModelEx(cube, position, new Vector3(1, 1, 1), cubeRotations[i], new Vector3(CubeScale, CubeScale, CubeScale), Color.White);
}
Rlgl.DisableShader();
EndMode3D();
Rlgl.EnableColorBlend();
// Go back to the default framebufferId (0) and draw our deferred shading
Rlgl.DisableFramebuffer();
Rlgl.ClearScreenBuffers(); // Clear color & depth buffer
switch (mode)
{
case DeferredMode.Shading:
{
BeginMode3D(camera);
Rlgl.DisableColorBlend();
Rlgl.EnableShader(deferredShader.Id);
// Bind our g-buffer textures
// We are binding them to locations that we earlier set in sampler2D uniforms `gPosition`, `gNormal`,
// and `gAlbedoSpec`
Rlgl.ActiveTextureSlot(TexUnitPosition);
Rlgl.EnableTexture(gBuffer.PositionTextureId);
Rlgl.ActiveTextureSlot(TexUnitNormal);
Rlgl.EnableTexture(gBuffer.NormalTextureId);
Rlgl.ActiveTextureSlot(TexUnitAlbedoSpec);
Rlgl.EnableTexture(gBuffer.AlbedoSpecTextureId);
// Finally, we draw a fullscreen quad to our default framebufferId
// This will now be shaded using our deferred shader
Rlgl.LoadDrawQuad();
Rlgl.DisableShader();
Rlgl.EnableColorBlend();
EndMode3D();
// As a last step, we now copy over the depth buffer from our g-buffer to the default framebufferId
Rlgl.BindFramebuffer(RlReadFramebuffer, gBuffer.FramebufferId);
Rlgl.BindFramebuffer(RlDrawFramebuffer, 0);
Rlgl.BlitFramebuffer(0, 0, screenWidth, screenHeight, 0, 0, screenWidth, screenHeight, GlDepthBufferBit);
Rlgl.DisableFramebuffer();
// Since our shader is now done and disabled, we can draw spheres
// that represent light positions in default forward rendering
BeginMode3D(camera);
Rlgl.EnableShader(Rlgl.GetShaderIdDefault());
for (var i = 0; i < MaxLights; i++)
{
if (lights[i].Enabled) DrawSphereEx(lights[i].Position, 0.2f, 8, 8, lights[i].Color);
else DrawSphereWires(lights[i].Position, 0.2f, 8, 8, ColorAlpha(lights[i].Color, 0.3f));
}
Rlgl.DisableShader();
EndMode3D();
DrawText("FINAL RESULT", 10, screenHeight - 30, 20, Color.DarkGreen);
}
break;
case DeferredMode.Position:
{
DrawTextureRec(
new Texture2D { Id = gBuffer.PositionTextureId, Width = screenWidth, Height = screenHeight },
new Rectangle(0, 0, screenWidth, -screenHeight),
Vector2.Zero,
Color.RayWhite
);
DrawText("POSITION TEXTURE", 10, screenHeight - 30, 20, Color.DarkGreen);
}
break;
case DeferredMode.Normal:
{
DrawTextureRec(
new Texture2D { Id = gBuffer.NormalTextureId, Width = screenWidth, Height = screenHeight },
new Rectangle(0, 0, screenWidth, -screenHeight),
Vector2.Zero,
Color.RayWhite
);
DrawText("NORMAL TEXTURE", 10, screenHeight - 30, 20, Color.DarkGreen);
}
break;
case DeferredMode.Albedo:
{
DrawTextureRec(
new Texture2D { Id = gBuffer.AlbedoSpecTextureId, Width = screenWidth, Height = screenHeight },
new Rectangle(0, 0, screenWidth, -screenHeight),
Vector2.Zero,
Color.RayWhite
);
DrawText("ALBEDO TEXTURE", 10, screenHeight - 30, 20, Color.DarkGreen);
}
break;
default: break;
}
DrawText("Toggle lights keys: [Y][R][G][B]", 10, 40, 20, Color.DarkGray);
DrawText("Switch G-buffer textures: [1][2][3][4]", 10, 70, 20, Color.DarkGray);
DrawFPS(10, 10);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
// Unload the models
UnloadModel(model);
UnloadModel(cube);
// Unload shaders
UnloadShader(deferredShader);
UnloadShader(gbufferShader);
// Unload geometry buffer and all attached textures
Rlgl.UnloadFramebuffer(gBuffer.FramebufferId);
Rlgl.UnloadTexture(gBuffer.PositionTextureId);
Rlgl.UnloadTexture(gBuffer.NormalTextureId);
Rlgl.UnloadTexture(gBuffer.AlbedoSpecTextureId);
Rlgl.UnloadTexture(gBuffer.DepthRenderbufferId);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - deferred rendering");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new DeferredRendering();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,226 @@
/*******************************************************************************************
*
* raylib [shaders] example - depth rendering
*
* Example complexity rating: [] 3/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by Luís Almeida (@luis605) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Luís Almeida (@luis605)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Shaders;
public class DepthRendering : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
#if BROWSER
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
#else
private const int GlslVersion = 330;
#endif
public string Name => "Shaders / Depth Rendering";
public string Title => "raylib [shaders] example - depth rendering";
public bool CursorDisabled => true;
private Camera3D camera;
private RenderTexture2D target;
private Shader depthShader;
private int depthLoc;
private Model cube;
private Model floor;
public void Init()
{
// Define the camera to look into our 3d world
camera = new();
camera.Position = new Vector3(4.0f, 1.0f, 5.0f);
camera.Target = new Vector3(0.0f, 0.0f, 0.0f);
camera.Up = new Vector3(0.0f, 1.0f, 0.0f);
camera.FovY = 45.0f;
camera.Projection = CameraProjection.Perspective;
// Load render texture with a depth texture attached
target = LoadRenderTextureDepthTex(screenWidth, screenHeight);
// Load depth shader and get depth texture shader location
depthShader = LoadShader(null, $"resources/shaders/glsl{GlslVersion}/depth_render.fs");
depthLoc = GetShaderLocation(depthShader, "depthTexture");
var flipTextureLoc = GetShaderLocation(depthShader, "flipY");
Raylib.SetShaderValue(depthShader, flipTextureLoc, 1, ShaderUniformDataType.Int); // Flip Y texture
// Load scene models
cube = LoadModelFromMesh(GenMeshCube(1.0f, 1.0f, 1.0f));
floor = LoadModelFromMesh(GenMeshPlane(20.0f, 20.0f, 1, 1));
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(ref camera, CameraMode.Free);
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginTextureMode(target);
ClearBackground(Color.White);
BeginMode3D(camera);
DrawModel(cube, new Vector3(0.0f, 0.0f, 0.0f), 3.0f, Color.Yellow);
DrawModel(floor, new Vector3(10.0f, 0.0f, 2.0f), 2.0f, Color.Red);
EndMode3D();
EndTextureMode();
// Draw into screen (main framebuffer)
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginShaderMode(depthShader);
SetShaderValueTexture(depthShader, depthLoc, target.Depth);
DrawTexture(target.Depth, 0, 0, Color.White);
EndShaderMode();
DrawRectangle(10, 10, 320, 93, Fade(Color.SkyBlue, 0.5f));
DrawRectangleLines(10, 10, 320, 93, Color.Blue);
DrawText("Camera Controls:", 20, 20, 10, Color.Black);
DrawText("- WASD to move", 40, 40, 10, Color.DarkGray);
DrawText("- Mouse Wheel Pressed to Pan", 40, 60, 10, Color.DarkGray);
DrawText("- Z to zoom to (0, 0, 0)", 40, 80, 10, Color.DarkGray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadModel(cube); // Unload model
UnloadModel(floor); // Unload model
UnloadRenderTextureDepthTex(target);
UnloadShader(depthShader); // Unload shader
}
// Load custom render texture, create a writable depth texture buffer
private static unsafe RenderTexture2D LoadRenderTextureDepthTex(int width, int height)
{
RenderTexture2D target = new();
// Load an empty framebuffer
target.Id = Rlgl.LoadFramebuffer();
if (target.Id > 0)
{
Rlgl.EnableFramebuffer(target.Id);
// Create color texture (default to RGBA)
target.Texture.Id = Rlgl.LoadTexture(
null,
width,
height,
PixelFormat.UncompressedR8G8B8A8,
1
);
target.Texture.Width = width;
target.Texture.Height = height;
target.Texture.Format = PixelFormat.UncompressedR8G8B8A8;
target.Texture.Mipmaps = 1;
// Create depth texture buffer (instead of raylib default renderbuffer)
target.Depth.Id = Rlgl.LoadTextureDepth(width, height, false);
target.Depth.Width = width;
target.Depth.Height = height;
target.Depth.Format = PixelFormat.CompressedPvrtRgba; // DEPTH_COMPONENT_24BIT: Not defined in raylib
target.Depth.Mipmaps = 1;
// Attach color texture and depth texture to FBO
Rlgl.FramebufferAttach(
target.Id,
target.Texture.Id,
FramebufferAttachType.ColorChannel0,
FramebufferAttachTextureType.Texture2D,
0
);
Rlgl.FramebufferAttach(
target.Id,
target.Depth.Id,
FramebufferAttachType.Depth,
FramebufferAttachTextureType.Texture2D,
0
);
// Check if fbo is complete with attachments (valid)
if (Rlgl.FramebufferComplete(target.Id) != 0)
{
TraceLog(TraceLogLevel.Info, $"FBO: [ID {target.Id}] Framebuffer object created successfully");
}
Rlgl.DisableFramebuffer();
}
else
{
TraceLog(TraceLogLevel.Warning, "FBO: Framebuffer object can not be created");
}
return target;
}
// Unload render texture from GPU memory (VRAM)
private static void UnloadRenderTextureDepthTex(RenderTexture2D target)
{
if (target.Id > 0)
{
// Color texture attached to FBO is deleted
Rlgl.UnloadTexture(target.Texture.Id);
Rlgl.UnloadTexture(target.Depth.Id);
// NOTE: Depth texture is automatically
// queried and deleted before deleting framebuffer
Rlgl.UnloadFramebuffer(target.Id);
}
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - depth rendering");
DisableCursor(); // Limit cursor to relative movement inside the window
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new DepthRendering();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,433 @@
/*******************************************************************************************
*
* raylib [shaders] example - game of life
*
* Example complexity rating: [] 3/4
*
* NOTE: This example requires raylib OpenGL 3.3 or ES2 versions for shaders support,
* OpenGL 1.1 does not support shaders, recompile raylib to OpenGL 3.3 version
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by Jordi Santonja (@JordSant) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Jordi Santonja (@JordSant)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Shaders;
public partial class GameOfLife : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
#if BROWSER
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
#else
private const int GlslVersion = 330;
#endif
public string Name => "Shaders / Game of Life";
public string Title => "raylib [shaders] example - game of life";
// Interaction mode
private enum InteractionMode
{
Run = 0,
Pause,
Draw,
}
// Struct to store example preset patterns
private struct PresetPattern
{
public string Name;
public Vector2 Position;
public PresetPattern(string name, Vector2 position)
{
Name = name;
Position = position;
}
}
private const int menuWidth = 100;
private const int windowWidth = screenWidth - menuWidth;
private const int windowHeight = screenHeight;
private const int worldWidth = 2048;
private const int worldHeight = 2048;
private const int randomTiles = 8; // Random preset: divide the world to compute random points in each tile
private static readonly PresetPattern[] presetPatterns =
{
new("Glider", new Vector2(0.5f, 0.5f)), new("R-pentomino", new Vector2(0.5f, 0.5f)), new("Acorn", new Vector2(0.5f, 0.5f)),
new("Spaceships", new Vector2(0.1f, 0.5f)), new("Still lifes", new Vector2(0.5f, 0.5f)), new("Oscillators", new Vector2(0.5f, 0.5f)),
new("Puffer train", new Vector2(0.1f, 0.5f)), new("Glider Gun", new Vector2(0.2f, 0.2f)), new("Breeder", new Vector2(0.1f, 0.5f)),
new("Random", new Vector2(0.5f, 0.5f))
};
private static readonly int numberOfPresets = presetPatterns.Length;
private Rectangle worldRectSource;
private Rectangle worldRectDest;
private Rectangle textureOnScreen;
private int zoom;
private float offsetX;
private float offsetY;
private int framesPerStep;
private int frame;
private int preset;
private InteractionMode mode;
private bool buttonZoomIn;
private bool buttonZomOut;
private bool buttonFaster;
private bool buttonSlower;
private Shader shdrGameOfLife;
private int resolutionLoc;
private RenderTexture2D world1;
private RenderTexture2D world2;
private RenderTexture2D currentWorld;
private RenderTexture2D previousWorld;
// Image to be used in DRAW mode, to be changed with mouse input
private Image imageToDraw;
private bool imageToDrawValid;
// Static locals in the original loop, promoted to fields
private Vector2 previousMousePosition;
private int firstColor;
private void FreeImageToDraw()
{
if (imageToDrawValid)
{
UnloadImage(imageToDraw);
imageToDrawValid = false;
}
}
public unsafe void Init()
{
worldRectSource = new Rectangle(0, 0, worldWidth, -worldHeight);
worldRectDest = new Rectangle(0, 0, worldWidth, worldHeight);
textureOnScreen = new Rectangle(0, 0, windowWidth, windowHeight);
zoom = 1;
offsetX = (worldWidth - windowWidth) / 2.0f; // Centered on window
offsetY = (worldHeight - windowHeight) / 2.0f; // Centered on window
framesPerStep = 1;
frame = 0;
preset = -1; // No button pressed for preset
mode = InteractionMode.Run; // Starting mode: running
buttonZoomIn = false; // Button states: false not pressed
buttonZomOut = false;
buttonFaster = false;
buttonSlower = false;
previousMousePosition = new Vector2(0.0f, 0.0f);
firstColor = -1;
imageToDrawValid = false;
// Load shader
shdrGameOfLife = LoadShader(null, $"resources/shaders/glsl{GlslVersion}/game_of_life.fs");
// Set shader uniform size of the world
resolutionLoc = GetShaderLocation(shdrGameOfLife, "resolution");
var resolution = new[] { (float)worldWidth, (float)worldHeight };
Raylib.SetShaderValue(shdrGameOfLife, resolutionLoc, resolution, ShaderUniformDataType.Vec2);
// Define two textures: the current world and the previous world
world1 = LoadRenderTexture(worldWidth, worldHeight);
world2 = LoadRenderTexture(worldWidth, worldHeight);
BeginTextureMode(world2);
ClearBackground(Color.RayWhite);
EndTextureMode();
var startPattern = LoadImage("resources/game_of_life/r_pentomino.png");
UpdateTextureRec(
world2.Texture,
new Rectangle(worldWidth / 2.0f, worldHeight / 2.0f, startPattern.Width, startPattern.Height),
startPattern.Data
);
UnloadImage(startPattern);
// References to the two textures, to be swapped
currentWorld = world2;
previousWorld = world1;
}
public unsafe void Update()
{
// Update
//----------------------------------------------------------------------------------
frame++;
// Change zoom: both by buttons or by mouse wheel
var mouseWheelMove = GetMouseWheelMove();
if (buttonZoomIn || (buttonZomOut && (zoom > 1)) || (mouseWheelMove != 0.0f))
{
FreeImageToDraw(); // Zoom change: free the image to draw to be recreated again
var centerX = offsetX + (windowWidth / 2.0f) / zoom;
var centerY = offsetY + (windowHeight / 2.0f) / zoom;
if (buttonZoomIn || (mouseWheelMove > 0.0f)) zoom *= 2;
if ((buttonZomOut || (mouseWheelMove < 0.0f)) && (zoom > 1)) zoom /= 2;
offsetX = centerX - (windowWidth / 2.0f) / zoom;
offsetY = centerY - (windowHeight / 2.0f) / zoom;
}
// Change speed: number of frames per step
if (buttonFaster && framesPerStep > 1) framesPerStep--;
if (buttonSlower) framesPerStep++;
// Mouse management
if ((mode == InteractionMode.Run) || (mode == InteractionMode.Pause))
{
FreeImageToDraw(); // Free the image to draw: no longer needed in these modes
// Pan with mouse left button
var mousePosition = GetMousePosition();
if (IsMouseButtonDown(MouseButton.Left) && (mousePosition.X < windowWidth))
{
offsetX -= (mousePosition.X - previousMousePosition.X) / zoom;
offsetY -= (mousePosition.Y - previousMousePosition.Y) / zoom;
}
previousMousePosition = mousePosition;
}
else // MODE_DRAW
{
var offsetDecimalX = offsetX - MathF.Floor(offsetX);
var offsetDecimalY = offsetY - MathF.Floor(offsetY);
var sizeInWorldX = (int)(MathF.Ceiling((windowWidth + offsetDecimalX * zoom) / zoom));
var sizeInWorldY = (int)(MathF.Ceiling((windowHeight + offsetDecimalY * zoom) / zoom));
if (offsetX + sizeInWorldX >= worldWidth) sizeInWorldX = worldWidth - (int)MathF.Floor(offsetX);
if (offsetY + sizeInWorldY >= worldHeight) sizeInWorldY = worldHeight - (int)MathF.Floor(offsetY);
// Create image to draw if not created yet
if (!imageToDrawValid)
{
var worldOnScreen = LoadRenderTexture(sizeInWorldX, sizeInWorldY);
BeginTextureMode(worldOnScreen);
DrawTexturePro(
currentWorld.Texture,
new Rectangle(MathF.Floor(offsetX), MathF.Floor(offsetY), sizeInWorldX, -sizeInWorldY),
new Rectangle(0, 0, sizeInWorldX, sizeInWorldY),
new Vector2(0, 0), 0.0f, Color.White
);
EndTextureMode();
imageToDraw = LoadImageFromTexture(worldOnScreen.Texture);
imageToDrawValid = true;
UnloadRenderTexture(worldOnScreen);
}
var mousePosition = GetMousePosition();
if (IsMouseButtonDown(MouseButton.Left) && (mousePosition.X < windowWidth))
{
var mouseX = (int)(mousePosition.X + offsetDecimalX * zoom) / zoom;
var mouseY = (int)(mousePosition.Y + offsetDecimalY * zoom) / zoom;
if (mouseX >= sizeInWorldX) mouseX = sizeInWorldX - 1;
if (mouseY >= sizeInWorldY) mouseY = sizeInWorldY - 1;
if (firstColor == -1) firstColor = (GetImageColor(imageToDraw, mouseX, mouseY).R < 5) ? 0 : 1;
var prevColor = (GetImageColor(imageToDraw, mouseX, mouseY).R < 5) ? 0 : 1;
ImageDrawPixel(ref imageToDraw, mouseX, mouseY, (firstColor != 0) ? Color.Black : Color.RayWhite);
if (prevColor != firstColor)
{
UpdateTextureRec(
currentWorld.Texture,
new Rectangle(MathF.Floor(offsetX), MathF.Floor(offsetY), sizeInWorldX, sizeInWorldY),
imageToDraw.Data
);
}
}
else firstColor = -1;
}
// Load selected preset
if (preset >= 0)
{
Image pattern;
if (preset < numberOfPresets - 1) // Preset with pattern image to load
{
pattern = preset switch
{
0 => LoadImage("resources/game_of_life/glider.png"),
1 => LoadImage("resources/game_of_life/r_pentomino.png"),
2 => LoadImage("resources/game_of_life/acorn.png"),
3 => LoadImage("resources/game_of_life/spaceships.png"),
4 => LoadImage("resources/game_of_life/still_lifes.png"),
5 => LoadImage("resources/game_of_life/oscillators.png"),
6 => LoadImage("resources/game_of_life/puffer_train.png"),
7 => LoadImage("resources/game_of_life/glider_gun.png"),
8 => LoadImage("resources/game_of_life/breeder.png"),
_ => default,
};
BeginTextureMode(currentWorld);
ClearBackground(Color.RayWhite);
EndTextureMode();
UpdateTextureRec(
currentWorld.Texture,
new Rectangle(
worldWidth * presetPatterns[preset].Position.X - pattern.Width / 2.0f,
worldHeight * presetPatterns[preset].Position.Y - pattern.Height / 2.0f,
pattern.Width, pattern.Height
),
pattern.Data
);
}
else // Last preset: Random values
{
pattern = GenImageColor(worldWidth / randomTiles, worldHeight / randomTiles, Color.RayWhite);
for (var i = 0; i < randomTiles; i++)
{
for (var j = 0; j < randomTiles; j++)
{
ImageClearBackground(ref pattern, Color.RayWhite);
for (var x = 0; x < pattern.Width; x++)
{
for (var y = 0; y < pattern.Height; y++)
{
if (GetRandomValue(0, 100) < 15) ImageDrawPixel(ref pattern, x, y, Color.Black);
}
}
UpdateTextureRec(
currentWorld.Texture,
new Rectangle(pattern.Width * i, pattern.Height * j, pattern.Width, pattern.Height),
pattern.Data
);
}
}
}
UnloadImage(pattern);
mode = InteractionMode.Pause;
offsetX = worldWidth * presetPatterns[preset].Position.X - (float)windowWidth / zoom / 2.0f;
offsetY = worldHeight * presetPatterns[preset].Position.Y - (float)windowHeight / zoom / 2.0f;
}
// Check window draw inside world limits
if (offsetX < 0) offsetX = 0;
if (offsetY < 0) offsetY = 0;
if (offsetX > worldWidth - (float)windowWidth / zoom) offsetX = worldWidth - (float)windowWidth / zoom;
if (offsetY > worldHeight - (float)windowHeight / zoom) offsetY = worldHeight - (float)windowHeight / zoom;
// Rectangles for drawing texture portion to screen
var textureSourceToScreen = new Rectangle(offsetX, offsetY, (float)windowWidth / zoom, (float)windowHeight / zoom);
//----------------------------------------------------------------------------------
// Draw to texture
//----------------------------------------------------------------------------------
if ((mode == InteractionMode.Run) && ((frame % framesPerStep) == 0))
{
// Swap worlds
var tempWorld = currentWorld;
currentWorld = previousWorld;
previousWorld = tempWorld;
// Draw to texture
BeginTextureMode(currentWorld);
BeginShaderMode(shdrGameOfLife);
DrawTexturePro(previousWorld.Texture, worldRectSource, worldRectDest, new Vector2(0, 0), 0.0f, Color.RayWhite);
EndShaderMode();
EndTextureMode();
}
//----------------------------------------------------------------------------------
// Draw to screen
//----------------------------------------------------------------------------------
BeginDrawing();
DrawTexturePro(currentWorld.Texture, textureSourceToScreen, textureOnScreen, new Vector2(0, 0), 0.0f, Color.White);
DrawLine(windowWidth, 0, windowWidth, screenHeight, new Color(218, 218, 218, 255));
DrawRectangle(windowWidth, 0, screenWidth - windowWidth, screenHeight, new Color(232, 232, 232, 255));
DrawText("Conway's", 704, 4, 20, Color.DarkBlue);
DrawText(" game of", 704, 19, 20, Color.DarkBlue);
DrawText(" life", 708, 34, 20, Color.DarkBlue);
DrawText("in raylib", 757, 42, 6, Color.Black);
DrawText("Presets", 710, 58, 8, Color.Gray);
preset = -1;
// Draw GUI controls
//------------------------------------------------------------------------------
// NOTE: raygui is not bound in raylib-cs; controls are kept for reference. The
// simulation still runs automatically and mouse wheel zoom / left-drag pan work.
/*for (int i = 0; i < numberOfPresets; i++)
if (GuiButton(new Rectangle( 710.0f, 70.0f + 18*i, 80.0f, 16.0f ), presetPatterns[i].Name)) preset = i;
GuiToggleGroup(new Rectangle( 710, 258, 80, 16 ), "Run\nPause\nDraw", ref mode);*/
DrawText($"Zoom: {zoom}x", 710, 316, 8, Color.Gray);
/*buttonZoomIn = GuiButton(new Rectangle( 710, 328, 80, 16 ), "Zoom in");
buttonZomOut = GuiButton(new Rectangle( 710, 346, 80, 16 ), "Zoom out");*/
DrawText($"Speed: {framesPerStep} frame{((framesPerStep > 1) ? "s" : "")}", 710, 370, 8, Color.Gray);
/*buttonFaster = GuiButton(new Rectangle( 710, 382, 80, 16 ), "Faster");
buttonSlower = GuiButton(new Rectangle( 710, 400, 80, 16 ), "Slower");*/
//------------------------------------------------------------------------------
DrawFPS(712, 426);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadShader(shdrGameOfLife);
UnloadRenderTexture(world1);
UnloadRenderTexture(world2);
FreeImageToDraw();
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - game of life");
SetTargetFPS(60); // Set at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new GameOfLife();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,213 @@
/*******************************************************************************************
*
* raylib [shaders] example - lightmap rendering
*
* Example complexity rating: [] 3/4
*
* NOTE: This example requires raylib OpenGL 3.3 or ES2 versions for shaders support,
* OpenGL 1.1 does not support shaders, recompile raylib to OpenGL 3.3 version
*
* NOTE: Shaders used in this example are #version 330 (OpenGL 3.3)
*
* Example originally created with raylib 4.5, last time updated with raylib 4.5
*
* Example contributed by Jussi Viitala (@nullstare) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2019-2025 Jussi Viitala (@nullstare) and Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Rlgl;
namespace Examples.Shaders;
public partial class LightmapRendering : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int MapSize = 16;
#if BROWSER
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
#else
private const int GlslVersion = 330;
#endif
public string Name => "Shaders / Lightmap Rendering";
public string Title => "raylib [shaders] example - lightmap rendering";
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
private Camera3D camera;
private Mesh mesh;
private Shader shader;
private Texture2D texture;
private Texture2D light;
private RenderTexture2D lightmap;
private Material material;
public unsafe void Init()
{
// Define the camera to look into our 3d world
camera = new();
camera.Position = new Vector3(4.0f, 6.0f, 8.0f); // Camera position
camera.Target = new Vector3(0.0f, 0.0f, 0.0f); // Camera looking at point
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
camera.FovY = 45.0f; // Camera field-of-view Y
camera.Projection = CameraProjection.Perspective; // Camera projection type
mesh = GenMeshPlane((float)MapSize, (float)MapSize, 1, 1);
// GenMeshPlane doesn't generate texcoords2 so we will upload them separately
mesh.AllocTexCoords2();
// X // Y
mesh.TexCoords2[0] = 0.0f; mesh.TexCoords2[1] = 0.0f;
mesh.TexCoords2[2] = 1.0f; mesh.TexCoords2[3] = 0.0f;
mesh.TexCoords2[4] = 0.0f; mesh.TexCoords2[5] = 1.0f;
mesh.TexCoords2[6] = 1.0f; mesh.TexCoords2[7] = 1.0f;
// Load a new texcoords2 attributes buffer
mesh.VboId[(int)ShaderLocationIndex.VertexTexcoord02] =
LoadVertexBuffer(mesh.TexCoords2, mesh.VertexCount * 2 * sizeof(float), false);
EnableVertexArray(mesh.VaoId);
// Index 5 is for texcoords2
SetVertexAttribute(5, 2, Rlgl.FLOAT, false, 0, 0);
EnableVertexAttribute(5);
DisableVertexArray();
// Load lightmap shader
shader = LoadShader(
$"resources/shaders/glsl{GlslVersion}/lightmap.vs",
$"resources/shaders/glsl{GlslVersion}/lightmap.fs"
);
texture = LoadTexture("resources/cubicmap_atlas.png");
light = LoadTexture("resources/spark_flame.png");
GenTextureMipmaps(ref texture);
SetTextureFilter(texture, TextureFilter.Trilinear);
lightmap = LoadRenderTexture(MapSize, MapSize);
material = LoadMaterialDefault();
material.Shader = shader;
material.Maps[(int)MaterialMapIndex.Albedo].Texture = texture;
material.Maps[(int)MaterialMapIndex.Metalness].Texture = lightmap.Texture;
// Drawing to lightmap
BeginTextureMode(lightmap);
ClearBackground(Color.Black);
BeginBlendMode(BlendMode.Additive);
DrawTexturePro(
light,
new Rectangle(0, 0, (float)light.Width, (float)light.Height),
new Rectangle(0, 0, 2.0f * MapSize, 2.0f * MapSize),
new Vector2((float)MapSize, (float)MapSize),
0.0f,
Color.Red
);
DrawTexturePro(
light,
new Rectangle(0, 0, (float)light.Width, (float)light.Height),
new Rectangle((float)MapSize * 0.8f, (float)MapSize / 2.0f, 2.0f * MapSize, 2.0f * MapSize),
new Vector2((float)MapSize, (float)MapSize),
0.0f,
Color.Blue
);
DrawTexturePro(
light,
new Rectangle(0, 0, (float)light.Width, (float)light.Height),
new Rectangle((float)MapSize * 0.8f, (float)MapSize * 0.8f, (float)MapSize, (float)MapSize),
new Vector2((float)MapSize / 2.0f, (float)MapSize / 2.0f),
0.0f,
Color.Green
);
BeginBlendMode(BlendMode.Alpha);
EndTextureMode();
// NOTE: To enable trilinear filtering we need mipmaps available for texture
GenTextureMipmaps(ref lightmap.Texture);
SetTextureFilter(lightmap.Texture, TextureFilter.Trilinear);
}
public unsafe void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(ref camera, CameraMode.Orbital);
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode3D(camera);
DrawMesh(mesh, material, Matrix4x4.Identity);
EndMode3D();
DrawTexturePro(
lightmap.Texture,
new Rectangle(0, 0, -MapSize, -MapSize),
new Rectangle((float)GetRenderWidth() - MapSize * 8 - 10, 10, (float)MapSize * 8, (float)MapSize * 8),
new Vector2(0.0f, 0.0f),
0.0f,
Color.White
);
DrawText($"LIGHTMAP: {MapSize}x{MapSize} pixels", GetRenderWidth() - 130, 20 + MapSize * 8, 10, Color.Green);
DrawFPS(10, 10);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadMesh(mesh); // Unload the mesh
UnloadShader(shader); // Unload shader
UnloadTexture(texture); // Unload texture
UnloadTexture(light); // Unload texture
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
SetConfigFlags(ConfigFlags.Msaa4xHint); // Enable Multi Sampling Anti Aliasing 4x (if available)
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - lightmap rendering");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new LightmapRendering();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,265 @@
/*******************************************************************************************
*
* raylib [shaders] example - mandelbrot set
*
* Example complexity rating: [] 3/4
*
* NOTE: This example requires raylib OpenGL 3.3 or ES2 versions for shaders support,
* OpenGL 1.1 does not support shaders, recompile raylib to OpenGL 3.3 version
*
* NOTE: Shaders used in this example are #version 330 (OpenGL 3.3)
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by Jordi Santonja (@JordSant)
* Based on previous work by Josh Colclough (@joshcol9232)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Jordi Santonja (@JordSant)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Shaders;
public partial class MandelbrotSet : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
#if BROWSER
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
#else
private const int GlslVersion = 330;
#endif
public string Name => "Shaders / Mandelbrot Set";
public string Title => "raylib [shaders] example - mandelbrot set";
// A few good interesting places
private static readonly float[][] pointsOfInterest = new[]
{
new[] { -1.76826775f, -0.00422996283f, 28435.9238f },
new[] { 0.322004497f, -0.0357099883f, 56499.7266f },
new[] { -0.748880744f, -0.0562955774f, 9237.59082f },
new[] { -1.78385007f, -0.0156200649f, 14599.5283f },
new[] { -0.0985441282f, -0.924688697f, 26259.8535f },
new[] { 0.317785531f, -0.0322612226f, 29297.9258f },
};
private const float zoomSpeed = 1.01f;
private const float offsetSpeedMul = 2.0f;
private const float startingZoom = 0.6f;
private static readonly float[] startingOffset = { -0.5f, 0.0f };
private Shader shader;
private RenderTexture2D target;
private float[] offset;
private float zoom;
private int maxIterations;
private float maxIterationsMultiplier;
private int zoomLoc;
private int offsetLoc;
private int maxIterationsLoc;
private bool showControls;
public void Init()
{
// Load mandelbrot set shader
// NOTE: Defining null (NULL) for vertex shader forces usage of internal default vertex shader
shader = LoadShader(null, $"resources/shaders/glsl{GlslVersion}/mandelbrot_set.fs");
// Create a RenderTexture2D to be used for render to texture
target = LoadRenderTexture(GetScreenWidth(), GetScreenHeight());
// Offset and zoom to draw the mandelbrot set at. (centered on screen and default size)
offset = new[] { startingOffset[0], startingOffset[1] };
zoom = startingZoom;
// Depending on the zoom the maximum number of iterations must be adapted to get more detail as we zoom in
// The solution is not perfect, so a control has been added to increase/decrease the number of iterations with UP/DOWN keys
#if BROWSER
maxIterations = 43;
maxIterationsMultiplier = 22.0f;
#else
maxIterations = 333;
maxIterationsMultiplier = 166.5f;
#endif
// Get variable (uniform) locations on the shader to connect with the program
// NOTE: If uniform variable could not be found in the shader, function returns -1
zoomLoc = GetShaderLocation(shader, "zoom");
offsetLoc = GetShaderLocation(shader, "offset");
maxIterationsLoc = GetShaderLocation(shader, "maxIterations");
// Upload the shader uniform values!
Raylib.SetShaderValue(shader, zoomLoc, zoom, ShaderUniformDataType.Float);
Raylib.SetShaderValue(shader, offsetLoc, offset, ShaderUniformDataType.Vec2);
Raylib.SetShaderValue(shader, maxIterationsLoc, maxIterations, ShaderUniformDataType.Int);
showControls = true; // Show controls
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
var updateShader = false;
// Press [1 - 6] to reset c to a point of interest
if (IsKeyPressed(KeyboardKey.One) ||
IsKeyPressed(KeyboardKey.Two) ||
IsKeyPressed(KeyboardKey.Three) ||
IsKeyPressed(KeyboardKey.Four) ||
IsKeyPressed(KeyboardKey.Five) ||
IsKeyPressed(KeyboardKey.Six))
{
var interestIndex = 0;
if (IsKeyPressed(KeyboardKey.One)) interestIndex = 0;
else if (IsKeyPressed(KeyboardKey.Two)) interestIndex = 1;
else if (IsKeyPressed(KeyboardKey.Three)) interestIndex = 2;
else if (IsKeyPressed(KeyboardKey.Four)) interestIndex = 3;
else if (IsKeyPressed(KeyboardKey.Five)) interestIndex = 4;
else if (IsKeyPressed(KeyboardKey.Six)) interestIndex = 5;
offset[0] = pointsOfInterest[interestIndex][0];
offset[1] = pointsOfInterest[interestIndex][1];
zoom = pointsOfInterest[interestIndex][2];
updateShader = true;
}
// If "R" is pressed, reset zoom and offset
if (IsKeyPressed(KeyboardKey.R))
{
offset[0] = startingOffset[0];
offset[1] = startingOffset[1];
zoom = startingZoom;
updateShader = true;
}
if (IsKeyPressed(KeyboardKey.F1)) showControls = !showControls; // Toggle whether or not to show controls
// Change number of max iterations with UP and DOWN keys
// WARNING: Increasing the number of max iterations greatly impacts performance
if (IsKeyPressed(KeyboardKey.Up))
{
maxIterationsMultiplier *= 1.4f;
updateShader = true;
}
else if (IsKeyPressed(KeyboardKey.Down))
{
maxIterationsMultiplier /= 1.4f;
updateShader = true;
}
// If either left or right button is pressed, zoom in/out
if (IsMouseButtonDown(MouseButton.Left) || IsMouseButtonDown(MouseButton.Right))
{
// Change zoom. If Mouse left -> zoom in. Mouse right -> zoom out
zoom *= IsMouseButtonDown(MouseButton.Left) ? zoomSpeed : (1.0f / zoomSpeed);
var mousePos = GetMousePosition();
Vector2 offsetVelocity;
// Find the velocity at which to change the camera. Take the distance of the mouse
// From the center of the screen as the direction, and adjust magnitude based on the current zoom
offsetVelocity.X = (mousePos.X / (float)screenWidth - 0.5f) * offsetSpeedMul / zoom;
offsetVelocity.Y = (mousePos.Y / (float)screenHeight - 0.5f) * offsetSpeedMul / zoom;
// Apply move velocity to camera
offset[0] += GetFrameTime() * offsetVelocity.X;
offset[1] += GetFrameTime() * offsetVelocity.Y;
updateShader = true;
}
// In case a parameter has been changed, update the shader values
if (updateShader)
{
// As we zoom in, increase the number of max iterations to get more detail
// Aproximate formula, but it works-ish
maxIterations = (int)(MathF.Sqrt(2.0f * MathF.Sqrt(MathF.Abs(1.0f - MathF.Sqrt(37.5f * zoom)))) * maxIterationsMultiplier);
// Update the shader uniform values!
Raylib.SetShaderValue(shader, zoomLoc, zoom, ShaderUniformDataType.Float);
Raylib.SetShaderValue(shader, offsetLoc, offset, ShaderUniformDataType.Vec2);
Raylib.SetShaderValue(shader, maxIterationsLoc, maxIterations, ShaderUniformDataType.Int);
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
// Using a render texture to draw Mandelbrot set
BeginTextureMode(target); // Enable drawing to texture
ClearBackground(Color.Black); // Clear the render texture
// Draw a rectangle in shader mode to be used as shader canvas
// NOTE: Rectangle uses font white character texture coordinates,
// So shader can not be applied here directly because input vertexTexCoord
// Do not represent full screen coordinates (space where want to apply shader)
DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), Color.Black);
EndTextureMode();
BeginDrawing();
ClearBackground(Color.Black); // Clear screen background
// Draw the saved texture and rendered mandelbrot set with shader
// NOTE: We do not invert texture on Y, already considered inside shader
BeginShaderMode(shader);
// WARNING: If FLAG_WINDOW_HIGHDPI is enabled, HighDPI monitor scaling should be considered
// When rendering the RenderTexture2D to fit in the HighDPI scaled Window
DrawTextureEx(target.Texture, new Vector2(0.0f, 0.0f), 0.0f, 1.0f, Color.White);
EndShaderMode();
if (showControls)
{
DrawText("Press Mouse buttons right/left to zoom in/out and move", 10, 15, 10, Color.RayWhite);
DrawText("Press F1 to toggle these controls", 10, 30, 10, Color.RayWhite);
DrawText("Press [1 - 6] to change point of interest", 10, 45, 10, Color.RayWhite);
DrawText("Press UP | DOWN to change number of iterations", 10, 60, 10, Color.RayWhite);
DrawText("Press R to recenter the camera", 10, 75, 10, Color.RayWhite);
}
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadShader(shader); // Unload shader
UnloadRenderTexture(target); // Unload render texture
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - mandelbrot set");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new MandelbrotSet();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,233 @@
/*******************************************************************************************
*
* raylib [shaders] example - normalmap rendering
*
* Example complexity rating: [] 4/4
*
* NOTE: This example requires raylib OpenGL 3.3 or ES2 versions for shaders support,
* OpenGL 1.1 does not support shaders, recompile raylib to OpenGL 3.3 version
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by Jeremy Montgomery (@Sir_Irk) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Jeremy Montgomery (@Sir_Irk) and Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Raymath;
namespace Examples.Shaders;
public partial class NormalmapRendering : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
#if BROWSER
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
#else
private const int GlslVersion = 330;
#endif
public string Name => "Shaders / Normalmap Rendering";
public string Title => "raylib [shaders] example - normalmap rendering";
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
private Camera3D camera;
private Shader shader;
private Model plane;
private Vector3 lightPosition;
private int lightPosLoc;
private float specularExponent;
private int specularExponentLoc;
private int useNormalMap;
private int useNormalMapLoc;
public unsafe void Init()
{
camera = new();
camera.Position = new Vector3(0.0f, 2.0f, -4.0f);
camera.Target = new Vector3(0.0f, 0.0f, 0.0f);
camera.Up = new Vector3(0.0f, 1.0f, 0.0f);
camera.FovY = 45.0f;
camera.Projection = CameraProjection.Perspective;
// Load basic normal map lighting shader
shader = LoadShader(
$"resources/shaders/glsl{GlslVersion}/normalmap.vs",
$"resources/shaders/glsl{GlslVersion}/normalmap.fs"
);
// Get some required shader locations
shader.Locs[(int)ShaderLocationIndex.MapNormal] = GetShaderLocation(shader, "normalMap");
shader.Locs[(int)ShaderLocationIndex.VectorView] = GetShaderLocation(shader, "viewPos");
// NOTE: "matModel" location name is automatically assigned on shader loading,
// no need to get the location again if using that uniform name
// shader.Locs[(int)ShaderLocationIndex.MatrixModel] = GetShaderLocation(shader, "matModel");
// This example uses just 1 point light
lightPosition = new Vector3(0.0f, 1.0f, 0.0f);
lightPosLoc = GetShaderLocation(shader, "lightPos");
// Load a plane model that has proper normals and tangents
plane = LoadModel("resources/models/plane.glb");
// Set the plane model's shader and texture maps
plane.Materials[0].Shader = shader;
plane.Materials[0].Maps[(int)MaterialMapIndex.Diffuse].Texture = LoadTexture("resources/tiles_diffuse.png");
plane.Materials[0].Maps[(int)MaterialMapIndex.Normal].Texture = LoadTexture("resources/tiles_normal.png");
// Generate Mipmaps and use TRILINEAR filtering to help with texture aliasing
GenTextureMipmaps(ref plane.Materials[0].Maps[(int)MaterialMapIndex.Diffuse].Texture);
GenTextureMipmaps(ref plane.Materials[0].Maps[(int)MaterialMapIndex.Normal].Texture);
SetTextureFilter(plane.Materials[0].Maps[(int)MaterialMapIndex.Diffuse].Texture, TextureFilter.Trilinear);
SetTextureFilter(plane.Materials[0].Maps[(int)MaterialMapIndex.Normal].Texture, TextureFilter.Trilinear);
// Specular exponent AKA shininess of the material
specularExponent = 8.0f;
specularExponentLoc = GetShaderLocation(shader, "specularExponent");
// Allow toggling the normal map on and off for comparison purposes
useNormalMap = 1;
useNormalMapLoc = GetShaderLocation(shader, "useNormalMap");
}
public unsafe void Update()
{
// Update
//----------------------------------------------------------------------------------
// Move the light around on the X and Z axis using WASD keys
Vector3 direction = new(0.0f, 0.0f, 0.0f);
if (IsKeyDown(KeyboardKey.W))
{
direction = Vector3Add(direction, new Vector3(0.0f, 0.0f, 1.0f));
}
if (IsKeyDown(KeyboardKey.S))
{
direction = Vector3Add(direction, new Vector3(0.0f, 0.0f, -1.0f));
}
if (IsKeyDown(KeyboardKey.D))
{
direction = Vector3Add(direction, new Vector3(-1.0f, 0.0f, 0.0f));
}
if (IsKeyDown(KeyboardKey.A))
{
direction = Vector3Add(direction, new Vector3(1.0f, 0.0f, 0.0f));
}
direction = Vector3Normalize(direction);
lightPosition = Vector3Add(lightPosition, Vector3Scale(direction, GetFrameTime() * 3.0f));
// Increase/Decrease the specular exponent(shininess)
if (IsKeyDown(KeyboardKey.Up))
{
specularExponent = Clamp(specularExponent + 40.0f * GetFrameTime(), 2.0f, 128.0f);
}
if (IsKeyDown(KeyboardKey.Down))
{
specularExponent = Clamp(specularExponent - 40.0f * GetFrameTime(), 2.0f, 128.0f);
}
// Toggle normal map on and off
if (IsKeyPressed(KeyboardKey.N))
{
useNormalMap = (useNormalMap != 0) ? 0 : 1;
}
// Spin plane model at a constant rate
plane.Transform = MatrixRotateY((float)GetTime() * 0.5f);
// Update shader values
Raylib.SetShaderValue(shader, lightPosLoc, lightPosition, ShaderUniformDataType.Vec3);
Raylib.SetShaderValue(
shader,
shader.Locs[(int)ShaderLocationIndex.VectorView],
camera.Position,
ShaderUniformDataType.Vec3
);
Raylib.SetShaderValue(shader, specularExponentLoc, specularExponent, ShaderUniformDataType.Float);
Raylib.SetShaderValue(shader, useNormalMapLoc, useNormalMap, ShaderUniformDataType.Int);
//--------------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode3D(camera);
BeginShaderMode(shader);
DrawModel(plane, Vector3.Zero, 2.0f, Color.White);
EndShaderMode();
// Draw sphere to show light position
DrawSphereWires(lightPosition, 0.2f, 8, 8, Color.Orange);
EndMode3D();
Color textColor = (useNormalMap != 0) ? Color.DarkGreen : Color.Red;
string toggleStr = (useNormalMap != 0) ? "On" : "Off";
DrawText($"Use key [N] to toggle normal map: {toggleStr}", 10, 10, 10, textColor);
int yOffset = 24;
DrawText("Use keys [W][A][S][D] to move the light", 10, 10 + yOffset * 1, 10, Color.Black);
DrawText("Use keys [Up][Down] to change specular exponent", 10, 10 + yOffset * 2, 10, Color.Black);
DrawText($"Specular Exponent: {specularExponent:F2}", 10, 10 + yOffset * 3, 10, Color.Blue);
DrawFPS(screenWidth - 90, 10);
EndDrawing();
//--------------------------------------------------------------------------------------
}
public void Unload()
{
UnloadShader(shader);
UnloadModel(plane);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
SetConfigFlags(ConfigFlags.Msaa4xHint);
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - normalmap rendering");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new NormalmapRendering();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,242 @@
/*******************************************************************************************
*
* raylib [shaders] example - rlgl compute
*
* WARNING: This example requires raylib compiled with OpenGL 4.3 version for
* compute shaders support, shaders used in this example are #version 430
*
* Example complexity rating: [] 4/4
*
* Example originally created with raylib 4.0, last time updated with raylib 4.0
*
* Example contributed by Teddy Astie (@tsnake41) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2021-2025 Teddy Astie (@tsnake41)
*
********************************************************************************************/
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using static Raylib_cs.Raylib;
namespace Examples.Shaders;
[ExcludeFromBrowser("compute shaders are not available on WebGL")]
public partial class RlglCompute : IExample
{
// IMPORTANT: This must match gol*.glsl GOL_WIDTH constant
// This must be a multiple of 16 (check golLogic compute dispatch)
private const int GolWidth = 768;
// Maximum amount of queued draw commands (squares draw from mouse down events)
private const int MaxBufferedTransferts = 48;
private const int screenWidth = GolWidth;
private const int screenHeight = GolWidth;
public string Name => "Shaders / Rlgl Compute";
public string Title => "raylib [shaders] example - rlgl compute";
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
// Game Of Life Update Command
[StructLayout(LayoutKind.Sequential)]
private struct GolUpdateCmd
{
public uint X; // x coordinate of the gol command
public uint Y; // y coordinate of the gol command
public uint W; // width of the filled zone
public uint Enabled; // whether to enable or disable zone
}
// Inline fixed-size array of GolUpdateCmd (MAX_BUFFERED_TRANSFERTS entries)
[InlineArray(MaxBufferedTransferts)]
private struct GolUpdateCmdBuffer
{
private GolUpdateCmd _element0;
}
// Game Of Life Update Commands SSBO
[StructLayout(LayoutKind.Sequential)]
private struct GolUpdateSSBO
{
public uint Count;
public GolUpdateCmdBuffer Commands;
}
private Vector2 resolution;
private uint brushSize;
private uint golLogicShader;
private uint golLogicProgram;
private uint golTransfertShader;
private uint golTransfertProgram;
private Shader golRenderShader;
private int resUniformLoc;
private uint ssboA;
private uint ssboB;
private uint ssboTransfert;
private GolUpdateSSBO transfertBuffer;
private Texture2D whiteTex;
public unsafe void Init()
{
resolution = new Vector2(screenWidth, screenHeight);
brushSize = 8;
// Game of Life logic compute shader
var golLogicCode = LoadFileText("resources/shaders/glsl430/gol.glsl");
var golLogicBytes = Encoding.UTF8.GetBytes(golLogicCode + "\0");
fixed (byte* p = golLogicBytes)
{
golLogicShader = Rlgl.LoadShader((sbyte*)p, (int)ShaderType.Compute);
}
golLogicProgram = Rlgl.LoadShaderProgramCompute(golLogicShader);
// Game of Life logic render shader
golRenderShader = LoadShader(null, "resources/shaders/glsl430/gol_render.glsl");
resUniformLoc = GetShaderLocation(golRenderShader, "resolution");
// Game of Life transfert shader (CPU<->GPU download and upload)
var golTransfertCode = LoadFileText("resources/shaders/glsl430/gol_transfert.glsl");
var golTransfertBytes = Encoding.UTF8.GetBytes(golTransfertCode + "\0");
fixed (byte* p = golTransfertBytes)
{
golTransfertShader = Rlgl.LoadShader((sbyte*)p, (int)ShaderType.Compute);
}
golTransfertProgram = Rlgl.LoadShaderProgramCompute(golTransfertShader);
// Load shader storage buffer object (SSBO), id returned
ssboA = Rlgl.LoadShaderBuffer((uint)(GolWidth * GolWidth * sizeof(uint)), null, Rlgl.DYNAMIC_COPY);
ssboB = Rlgl.LoadShaderBuffer((uint)(GolWidth * GolWidth * sizeof(uint)), null, Rlgl.DYNAMIC_COPY);
ssboTransfert = Rlgl.LoadShaderBuffer((uint)sizeof(GolUpdateSSBO), null, Rlgl.DYNAMIC_COPY);
transfertBuffer = new();
// Create a white texture of the size of the window to update
// each pixel of the window using the fragment shader: golRenderShader
var whiteImage = GenImageColor(GolWidth, GolWidth, Color.White);
whiteTex = LoadTextureFromImage(whiteImage);
UnloadImage(whiteImage);
}
public unsafe void Update()
{
// Update
//----------------------------------------------------------------------------------
brushSize += (uint)(int)GetMouseWheelMove();
if ((IsMouseButtonDown(MouseButton.Left) || IsMouseButtonDown(MouseButton.Right))
&& (transfertBuffer.Count < MaxBufferedTransferts))
{
// Buffer a new command
transfertBuffer.Commands[(int)transfertBuffer.Count].X = (uint)GetMouseX() - brushSize / 2;
transfertBuffer.Commands[(int)transfertBuffer.Count].Y = (uint)GetMouseY() - brushSize / 2;
transfertBuffer.Commands[(int)transfertBuffer.Count].W = brushSize;
transfertBuffer.Commands[(int)transfertBuffer.Count].Enabled = IsMouseButtonDown(MouseButton.Left) ? 1u : 0u;
transfertBuffer.Count++;
}
else if (transfertBuffer.Count > 0) // Process transfert buffer
{
// Send SSBO buffer to GPU
fixed (GolUpdateSSBO* ptr = &transfertBuffer)
{
Rlgl.UpdateShaderBuffer(ssboTransfert, ptr, (uint)sizeof(GolUpdateSSBO), 0);
}
// Process SSBO commands on GPU
Rlgl.EnableShader(golTransfertProgram);
Rlgl.BindShaderBuffer(ssboA, 1);
Rlgl.BindShaderBuffer(ssboTransfert, 3);
Rlgl.ComputeShaderDispatch(transfertBuffer.Count, 1, 1); // Each GPU unit will process a command!
Rlgl.DisableShader();
transfertBuffer.Count = 0;
}
else
{
// Process game of life logic
Rlgl.EnableShader(golLogicProgram);
Rlgl.BindShaderBuffer(ssboA, 1);
Rlgl.BindShaderBuffer(ssboB, 2);
Rlgl.ComputeShaderDispatch(GolWidth / 16, GolWidth / 16, 1);
Rlgl.DisableShader();
// ssboA <-> ssboB
var temp = ssboA;
ssboA = ssboB;
ssboB = temp;
}
Rlgl.BindShaderBuffer(ssboA, 1);
Raylib.SetShaderValue(golRenderShader, resUniformLoc, resolution, ShaderUniformDataType.Vec2);
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.Blank);
BeginShaderMode(golRenderShader);
DrawTexture(whiteTex, 0, 0, Color.White);
EndShaderMode();
DrawRectangleLines(GetMouseX() - (int)(brushSize / 2), GetMouseY() - (int)(brushSize / 2), (int)brushSize, (int)brushSize, Color.Red);
DrawText("Use Mouse wheel to increase/decrease brush size", 10, 10, 20, Color.White);
DrawFPS(GetScreenWidth() - 100, 10);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
// Unload shader buffers objects
Rlgl.UnloadShaderBuffer(ssboA);
Rlgl.UnloadShaderBuffer(ssboB);
Rlgl.UnloadShaderBuffer(ssboTransfert);
// Unload compute shader
Rlgl.UnloadShader(golLogicShader);
Rlgl.UnloadShader(golTransfertShader);
Rlgl.UnloadShaderProgram(golTransfertProgram);
Rlgl.UnloadShaderProgram(golLogicProgram);
UnloadTexture(whiteTex); // Unload white texture
UnloadShader(golRenderShader); // Unload rendering fragment shader
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - rlgl compute");
//--------------------------------------------------------------------------------------
var game = new RlglCompute();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,243 @@
/*******************************************************************************************
*
* raylib [shaders] example - rounded rectangle
*
* Example complexity rating: [] 3/4
*
* Example originally created with raylib 5.5, last time updated with raylib 5.5
*
* Example contributed by Anstro Pleuton (@anstropleuton) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Anstro Pleuton (@anstropleuton)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Shaders;
public class RoundedRectangle : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
#if BROWSER
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
#else
private const int GlslVersion = 330;
#endif
public string Name => "Shaders / Rounded Rectangle";
public string Title => "raylib [shaders] example - rounded rectangle";
// Rounded rectangle data
private struct RoundedRect
{
public Vector4 CornerRadius; // Individual corner radius (top-left, top-right, bottom-left, bottom-right)
// Shadow variables
public float ShadowRadius;
public Vector2 ShadowOffset;
public float ShadowScale;
// Border variables
public float BorderThickness; // Inner-border thickness
// Shader locations
public int RectangleLoc;
public int RadiusLoc;
public int ColorLoc;
public int ShadowRadiusLoc;
public int ShadowOffsetLoc;
public int ShadowScaleLoc;
public int ShadowColorLoc;
public int BorderThicknessLoc;
public int BorderColorLoc;
}
private Shader shader;
private RoundedRect roundedRectangle;
private readonly Color rectangleColor = Color.Blue;
private readonly Color shadowColor = Color.DarkBlue;
private readonly Color borderColor = Color.SkyBlue;
public void Init()
{
// Load the shader
shader = LoadShader(
$"resources/shaders/glsl{GlslVersion}/base.vs",
$"resources/shaders/glsl{GlslVersion}/rounded_rectangle.fs"
);
// Create a rounded rectangle
roundedRectangle = CreateRoundedRectangle(
new Vector4(5.0f, 10.0f, 15.0f, 20.0f), // Corner radius
20.0f, // Shadow radius
new Vector2(0.0f, -5.0f), // Shadow offset
0.95f, // Shadow scale
5.0f, // Border thickness
shader // Shader
);
// Update shader uniforms
UpdateRoundedRectangle(roundedRectangle, shader);
}
public void Update()
{
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
// Draw rectangle box with rounded corners using shader
Rectangle rec = new(50, 70, 110, 60);
DrawRectangleLines((int)rec.X - 20, (int)rec.Y - 20, (int)rec.Width + 40, (int)rec.Height + 40, Color.DarkGray);
DrawText("Rounded rectangle", (int)rec.X - 20, (int)rec.Y - 35, 10, Color.DarkGray);
// Flip Y axis to match shader coordinate system
rec.Y = screenHeight - rec.Y - rec.Height;
Raylib.SetShaderValue(shader, roundedRectangle.RectangleLoc, new[] { rec.X, rec.Y, rec.Width, rec.Height }, ShaderUniformDataType.Vec4);
// Only rectangle color
Raylib.SetShaderValue(shader, roundedRectangle.ColorLoc, new[] { rectangleColor.R / 255.0f, rectangleColor.G / 255.0f, rectangleColor.B / 255.0f, rectangleColor.A / 255.0f }, ShaderUniformDataType.Vec4);
Raylib.SetShaderValue(shader, roundedRectangle.ShadowColorLoc, new[] { 0.0f, 0.0f, 0.0f, 0.0f }, ShaderUniformDataType.Vec4);
Raylib.SetShaderValue(shader, roundedRectangle.BorderColorLoc, new[] { 0.0f, 0.0f, 0.0f, 0.0f }, ShaderUniformDataType.Vec4);
BeginShaderMode(shader);
DrawRectangle(0, 0, screenWidth, screenHeight, Color.White);
EndShaderMode();
// Draw rectangle shadow using shader
rec = new Rectangle(50, 200, 110, 60);
DrawRectangleLines((int)rec.X - 20, (int)rec.Y - 20, (int)rec.Width + 40, (int)rec.Height + 40, Color.DarkGray);
DrawText("Rounded rectangle shadow", (int)rec.X - 20, (int)rec.Y - 35, 10, Color.DarkGray);
rec.Y = screenHeight - rec.Y - rec.Height;
Raylib.SetShaderValue(shader, roundedRectangle.RectangleLoc, new[] { rec.X, rec.Y, rec.Width, rec.Height }, ShaderUniformDataType.Vec4);
// Only shadow color
Raylib.SetShaderValue(shader, roundedRectangle.ColorLoc, new[] { 0.0f, 0.0f, 0.0f, 0.0f }, ShaderUniformDataType.Vec4);
Raylib.SetShaderValue(shader, roundedRectangle.ShadowColorLoc, new[] { shadowColor.R / 255.0f, shadowColor.G / 255.0f, shadowColor.B / 255.0f, shadowColor.A / 255.0f }, ShaderUniformDataType.Vec4);
Raylib.SetShaderValue(shader, roundedRectangle.BorderColorLoc, new[] { 0.0f, 0.0f, 0.0f, 0.0f }, ShaderUniformDataType.Vec4);
BeginShaderMode(shader);
DrawRectangle(0, 0, screenWidth, screenHeight, Color.White);
EndShaderMode();
// Draw rectangle's border using shader
rec = new Rectangle(50, 330, 110, 60);
DrawRectangleLines((int)rec.X - 20, (int)rec.Y - 20, (int)rec.Width + 40, (int)rec.Height + 40, Color.DarkGray);
DrawText("Rounded rectangle border", (int)rec.X - 20, (int)rec.Y - 35, 10, Color.DarkGray);
rec.Y = screenHeight - rec.Y - rec.Height;
Raylib.SetShaderValue(shader, roundedRectangle.RectangleLoc, new[] { rec.X, rec.Y, rec.Width, rec.Height }, ShaderUniformDataType.Vec4);
// Only border color
Raylib.SetShaderValue(shader, roundedRectangle.ColorLoc, new[] { 0.0f, 0.0f, 0.0f, 0.0f }, ShaderUniformDataType.Vec4);
Raylib.SetShaderValue(shader, roundedRectangle.ShadowColorLoc, new[] { 0.0f, 0.0f, 0.0f, 0.0f }, ShaderUniformDataType.Vec4);
Raylib.SetShaderValue(shader, roundedRectangle.BorderColorLoc, new[] { borderColor.R / 255.0f, borderColor.G / 255.0f, borderColor.B / 255.0f, borderColor.A / 255.0f }, ShaderUniformDataType.Vec4);
BeginShaderMode(shader);
DrawRectangle(0, 0, screenWidth, screenHeight, Color.White);
EndShaderMode();
// Draw one more rectangle with all three colors
rec = new Rectangle(240, 80, 500, 300);
DrawRectangleLines((int)rec.X - 30, (int)rec.Y - 30, (int)rec.Width + 60, (int)rec.Height + 60, Color.DarkGray);
DrawText("Rectangle with all three combined", (int)rec.X - 30, (int)rec.Y - 45, 10, Color.DarkGray);
rec.Y = screenHeight - rec.Y - rec.Height;
Raylib.SetShaderValue(shader, roundedRectangle.RectangleLoc, new[] { rec.X, rec.Y, rec.Width, rec.Height }, ShaderUniformDataType.Vec4);
// All three colors
Raylib.SetShaderValue(shader, roundedRectangle.ColorLoc, new[] { rectangleColor.R / 255.0f, rectangleColor.G / 255.0f, rectangleColor.B / 255.0f, rectangleColor.A / 255.0f }, ShaderUniformDataType.Vec4);
Raylib.SetShaderValue(shader, roundedRectangle.ShadowColorLoc, new[] { shadowColor.R / 255.0f, shadowColor.G / 255.0f, shadowColor.B / 255.0f, shadowColor.A / 255.0f }, ShaderUniformDataType.Vec4);
Raylib.SetShaderValue(shader, roundedRectangle.BorderColorLoc, new[] { borderColor.R / 255.0f, borderColor.G / 255.0f, borderColor.B / 255.0f, borderColor.A / 255.0f }, ShaderUniformDataType.Vec4);
BeginShaderMode(shader);
DrawRectangle(0, 0, screenWidth, screenHeight, Color.White);
EndShaderMode();
DrawText("(c) Rounded rectangle SDF by Iñigo Quilez. MIT License.", screenWidth - 300, screenHeight - 20, 10, Color.Black);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadShader(shader); // Unload shader
}
// Create a rounded rectangle and set uniform locations
private static RoundedRect CreateRoundedRectangle(Vector4 cornerRadius, float shadowRadius, Vector2 shadowOffset, float shadowScale, float borderThickness, Shader shader)
{
RoundedRect rec;
rec.CornerRadius = cornerRadius;
rec.ShadowRadius = shadowRadius;
rec.ShadowOffset = shadowOffset;
rec.ShadowScale = shadowScale;
rec.BorderThickness = borderThickness;
// Get shader uniform locations
rec.RectangleLoc = GetShaderLocation(shader, "rectangle");
rec.RadiusLoc = GetShaderLocation(shader, "radius");
rec.ColorLoc = GetShaderLocation(shader, "color");
rec.ShadowRadiusLoc = GetShaderLocation(shader, "shadowRadius");
rec.ShadowOffsetLoc = GetShaderLocation(shader, "shadowOffset");
rec.ShadowScaleLoc = GetShaderLocation(shader, "shadowScale");
rec.ShadowColorLoc = GetShaderLocation(shader, "shadowColor");
rec.BorderThicknessLoc = GetShaderLocation(shader, "borderThickness");
rec.BorderColorLoc = GetShaderLocation(shader, "borderColor");
UpdateRoundedRectangle(rec, shader);
return rec;
}
// Update rounded rectangle uniforms
private static void UpdateRoundedRectangle(RoundedRect rec, Shader shader)
{
Raylib.SetShaderValue(shader, rec.RadiusLoc, new[] { rec.CornerRadius.X, rec.CornerRadius.Y, rec.CornerRadius.Z, rec.CornerRadius.W }, ShaderUniformDataType.Vec4);
Raylib.SetShaderValue(shader, rec.ShadowRadiusLoc, rec.ShadowRadius, ShaderUniformDataType.Float);
Raylib.SetShaderValue(shader, rec.ShadowOffsetLoc, new[] { rec.ShadowOffset.X, rec.ShadowOffset.Y }, ShaderUniformDataType.Vec2);
Raylib.SetShaderValue(shader, rec.ShadowScaleLoc, rec.ShadowScale, ShaderUniformDataType.Float);
Raylib.SetShaderValue(shader, rec.BorderThicknessLoc, rec.BorderThickness, ShaderUniformDataType.Float);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - rounded rectangle");
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new RoundedRectangle();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,316 @@
/*******************************************************************************************
*
* raylib [shaders] example - shadowmap rendering
*
* Example complexity rating: [] 4/4
*
* Example originally created with raylib 5.0, last time updated with raylib 5.0
*
* Example contributed by TheManTheMythTheGameDev (@TheManTheMythTheGameDev) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2023-2025 TheManTheMythTheGameDev (@TheManTheMythTheGameDev)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Raymath;
using static Raylib_cs.Rlgl;
namespace Examples.Shaders;
public partial class ShadowmapRendering : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
#if BROWSER
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
#else
private const int GlslVersion = 330;
#endif
private const int ShadowmapResolution = 1024;
public string Name => "Shaders / Shadowmap Rendering";
public string Title => "raylib [shaders] example - shadowmap rendering";
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
private Camera3D camera;
private Shader shadowShader;
private Vector3 lightDir;
private int lightDirLoc;
private int lightVPLoc;
private int shadowMapLoc;
private Model cube;
private Model robot;
private unsafe ModelAnimation* anims;
private int animCount;
private RenderTexture2D shadowMap;
private Camera3D lightCamera;
private int frameCounter;
private int textureActiveSlot;
public unsafe void Init()
{
// Shadows are a HUGE topic, and this example shows an extremely simple implementation of the shadowmapping algorithm,
// which is the industry standard for shadows. This algorithm can be extended in a ridiculous number of ways to improve
// realism and also adapt it for different scenes. This is pretty much the simplest possible implementation
camera = new();
camera.Position = new Vector3(10.0f, 10.0f, 10.0f);
camera.Target = Vector3.Zero;
camera.Projection = CameraProjection.Perspective;
camera.Up = new Vector3(0.0f, 1.0f, 0.0f);
camera.FovY = 45.0f;
shadowShader = LoadShader(
$"resources/shaders/glsl{GlslVersion}/shadowmap.vs",
$"resources/shaders/glsl{GlslVersion}/shadowmap.fs"
);
shadowShader.Locs[(int)ShaderLocationIndex.VectorView] = GetShaderLocation(shadowShader, "viewPos");
lightDir = Vector3Normalize(new Vector3(0.35f, -1.0f, -0.35f));
var lightColor = Color.White;
var lightColorNormalized = ColorNormalize(lightColor);
lightDirLoc = GetShaderLocation(shadowShader, "lightDir");
var lightColLoc = GetShaderLocation(shadowShader, "lightColor");
Raylib.SetShaderValue(shadowShader, lightDirLoc, lightDir, ShaderUniformDataType.Vec3);
Raylib.SetShaderValue(shadowShader, lightColLoc, lightColorNormalized, ShaderUniformDataType.Vec4);
var ambientLoc = GetShaderLocation(shadowShader, "ambient");
var ambient = new[] { 0.1f, 0.1f, 0.1f, 1.0f };
Raylib.SetShaderValue(shadowShader, ambientLoc, ambient, ShaderUniformDataType.Vec4);
lightVPLoc = GetShaderLocation(shadowShader, "lightVP");
shadowMapLoc = GetShaderLocation(shadowShader, "shadowMap");
var shadowMapResolution = ShadowmapResolution;
Raylib.SetShaderValue(shadowShader, GetShaderLocation(shadowShader, "shadowMapResolution"), shadowMapResolution, ShaderUniformDataType.Int);
cube = LoadModelFromMesh(GenMeshCube(1.0f, 1.0f, 1.0f));
cube.Materials[0].Shader = shadowShader;
robot = LoadModel("resources/models/robot.glb");
for (var i = 0; i < robot.MaterialCount; i++)
{
robot.Materials[i].Shader = shadowShader;
}
animCount = 0;
anims = LoadModelAnimations("resources/models/robot.glb", ref animCount);
shadowMap = LoadShadowmapRenderTexture(ShadowmapResolution, ShadowmapResolution);
// For the shadowmapping algorithm, we will be rendering everything from the light's point of view
lightCamera = new();
lightCamera.Position = Vector3Scale(lightDir, -15.0f);
lightCamera.Target = Vector3.Zero;
lightCamera.Projection = CameraProjection.Orthographic; // Use an orthographic projection for directional lights
lightCamera.Up = new Vector3(0.0f, 1.0f, 0.0f);
lightCamera.FovY = 20.0f;
frameCounter = 0;
textureActiveSlot = 10; // Can be anything 0 to 15, but 0 will probably be taken up
}
public unsafe void Update()
{
// Update
//----------------------------------------------------------------------------------
var deltaTime = GetFrameTime();
var cameraPos = camera.Position;
Raylib.SetShaderValue(shadowShader, shadowShader.Locs[(int)ShaderLocationIndex.VectorView], cameraPos, ShaderUniformDataType.Vec3);
UpdateCamera(ref camera, CameraMode.Orbital);
frameCounter++;
frameCounter %= anims[0].KeyFrameCount;
UpdateModelAnimation(robot, anims[0], (float)frameCounter);
// Move light with arrow keys
const float cameraSpeed = 0.05f;
if (IsKeyDown(KeyboardKey.Left))
{
if (lightDir.X < 0.6f)
{
lightDir.X += cameraSpeed * 60.0f * deltaTime;
}
}
if (IsKeyDown(KeyboardKey.Right))
{
if (lightDir.X > -0.6f)
{
lightDir.X -= cameraSpeed * 60.0f * deltaTime;
}
}
if (IsKeyDown(KeyboardKey.Up))
{
if (lightDir.Z < 0.6f)
{
lightDir.Z += cameraSpeed * 60.0f * deltaTime;
}
}
if (IsKeyDown(KeyboardKey.Down))
{
if (lightDir.Z > -0.6f)
{
lightDir.Z -= cameraSpeed * 60.0f * deltaTime;
}
}
lightDir = Vector3Normalize(lightDir);
lightCamera.Position = Vector3Scale(lightDir, -15.0f);
Raylib.SetShaderValue(shadowShader, lightDirLoc, lightDir, ShaderUniformDataType.Vec3);
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
// PASS 01: Render all objects into the shadowmap render texture
// We record all the objects' depths (as rendered from the light source's point of view) in a buffer
// Anything that is "visible" to the light is in light, anything that isn't is in shadow
// We can later use the depth buffer when rendering everything from the player's point of view
// to determine whether a given point is "visible" to the light
Matrix4x4 lightView;
Matrix4x4 lightProj;
BeginTextureMode(shadowMap);
ClearBackground(Color.White);
BeginMode3D(lightCamera);
lightView = GetMatrixModelview();
lightProj = GetMatrixProjection();
DrawScene(cube, robot);
EndMode3D();
EndTextureMode();
var lightViewProj = MatrixMultiply(lightView, lightProj);
// PASS 02: Draw the scene into main framebuffer, using the generated shadowmap
BeginDrawing();
ClearBackground(Color.RayWhite);
SetShaderValueMatrix(shadowShader, lightVPLoc, lightViewProj);
EnableShader(shadowShader.Id);
ActiveTextureSlot(textureActiveSlot);
EnableTexture(shadowMap.Depth.Id);
var slot = textureActiveSlot;
SetUniform(shadowMapLoc, &slot, (int)ShaderUniformDataType.Int, 1);
BeginMode3D(camera);
DrawScene(cube, robot); // Draw the same exact things as we drew in the shadowmap!
EndMode3D();
DrawText("Use the arrow keys to rotate the light!", 10, 10, 30, Color.Red);
DrawText("Shadows in raylib using the shadowmapping algorithm!", screenWidth - 280, screenHeight - 20, 10, Color.Gray);
EndDrawing();
if (IsKeyPressed(KeyboardKey.F))
{
TakeScreenshot("shaders_shadowmap.png");
}
//----------------------------------------------------------------------------------
}
public unsafe void Unload()
{
UnloadShader(shadowShader);
UnloadModel(cube);
UnloadModel(robot);
UnloadModelAnimations(anims, animCount);
UnloadShadowmapRenderTexture(shadowMap);
}
// Load render texture for shadowmap projection
// NOTE: Load framebuffer with only a texture depth attachment,
// no color attachment required for shadowmap
private static unsafe RenderTexture2D LoadShadowmapRenderTexture(int width, int height)
{
RenderTexture2D target = new();
target.Id = LoadFramebuffer(); // Load an empty framebuffer
target.Texture.Width = width;
target.Texture.Height = height;
if (target.Id > 0)
{
EnableFramebuffer(target.Id);
// Create depth texture
// NOTE: No need a color texture attachment for the shadowmap
target.Depth.Id = LoadTextureDepth(width, height, false);
target.Depth.Width = width;
target.Depth.Height = height;
target.Depth.Format = (PixelFormat)19; // DEPTH_COMPONENT_24BIT?
target.Depth.Mipmaps = 1;
// Attach depth texture to FBO
FramebufferAttach(target.Id, target.Depth.Id, FramebufferAttachType.Depth, FramebufferAttachTextureType.Texture2D, 0);
// Check if fbo is complete with attachments (valid)
if (FramebufferComplete(target.Id) != 0)
{
TraceLog(TraceLogLevel.Info, $"FBO: [ID {target.Id}] Framebuffer object created successfully");
}
DisableFramebuffer();
}
else
{
TraceLog(TraceLogLevel.Warning, "FBO: Framebuffer object can not be created");
}
return target;
}
// Unload shadowmap render texture from GPU memory (VRAM)
private static void UnloadShadowmapRenderTexture(RenderTexture2D target)
{
if (target.Id > 0)
{
// NOTE: Depth texture/renderbuffer is automatically
// queried and deleted before deleting framebuffer
UnloadFramebuffer(target.Id);
}
}
// Draw full scene projecting shadows
// NOTE: Required to be called several time to generate shadowmap
private static void DrawScene(Model cube, Model robot)
{
DrawModelEx(cube, Vector3.Zero, new Vector3(0.0f, 1.0f, 0.0f), 0.0f, new Vector3(10.0f, 1.0f, 10.0f), Color.Blue);
DrawModelEx(cube, new Vector3(1.5f, 1.0f, -1.5f), new Vector3(0.0f, 1.0f, 0.0f), 0.0f, Vector3.One, Color.White);
DrawModelEx(robot, new Vector3(0.0f, 0.5f, 0.0f), new Vector3(0.0f, 1.0f, 0.0f), 0.0f, new Vector3(1.0f, 1.0f, 1.0f), Color.Red);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
SetConfigFlags(ConfigFlags.Msaa4xHint);
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - shadowmap rendering");
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new ShadowmapRendering();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,143 @@
/*******************************************************************************************
*
* raylib [shaders] example - texture tiling
*
* Example complexity rating: [] 2/4
*
* Example demonstrates how to tile a texture on a 3D model using raylib
*
* Example originally created with raylib 4.5, last time updated with raylib 4.5
*
* Example contributed by Luis Almeida (@luis605) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2023-2025 Luis Almeida (@luis605)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Shaders;
public class TextureTiling : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
#if BROWSER
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
#else
private const int GlslVersion = 330;
#endif
public string Name => "Shaders / Texture Tiling";
public string Title => "raylib [shaders] example - texture tiling";
public bool CursorDisabled => true;
private Camera3D camera;
private Model model;
private Texture2D texture;
private Shader shader;
public unsafe void Init()
{
// Define the camera to look into our 3d world
camera = new();
camera.Position = new Vector3(4.0f, 4.0f, 4.0f); // Camera position
camera.Target = new Vector3(0.0f, 0.5f, 0.0f); // Camera looking at point
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
camera.FovY = 45.0f; // Camera field-of-view Y
camera.Projection = CameraProjection.Perspective; // Camera projection type
// Load a cube model
var cube = GenMeshCube(1.0f, 1.0f, 1.0f);
model = LoadModelFromMesh(cube);
// Load a texture and assign to cube model
texture = LoadTexture("resources/cubicmap_atlas.png");
model.Materials[0].Maps[(int)MaterialMapIndex.Diffuse].Texture = texture;
// Set the texture tiling using a shader
var tiling = new[] { 3.0f, 3.0f };
shader = LoadShader(null, $"resources/shaders/glsl{GlslVersion}/tiling.fs");
SetTextureWrap(texture, TextureWrap.Repeat);
Raylib.SetShaderValue(shader, GetShaderLocation(shader, "tiling"), tiling, ShaderUniformDataType.Vec2);
model.Materials[0].Shader = shader;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(ref camera, CameraMode.Free);
if (IsKeyPressed(KeyboardKey.Z))
{
camera.Target = new Vector3(0.0f, 0.5f, 0.0f);
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode3D(camera);
BeginShaderMode(shader);
DrawModel(model, new Vector3(0.0f, 0.0f, 0.0f), 2.0f, Color.White);
EndShaderMode();
DrawGrid(10, 1.0f);
EndMode3D();
DrawText("Use mouse to rotate the camera", 10, 10, 20, Color.DarkGray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadModel(model); // Unload model
UnloadShader(shader); // Unload shader
UnloadTexture(texture); // Unload texture
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - texture tiling");
DisableCursor(); // Limit cursor to relative movement inside the window
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new TextureTiling();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,148 @@
/*******************************************************************************************
*
* raylib [shaders] example - vertex displacement
*
* Example complexity rating: [] 3/4
*
* Example originally created with raylib 5.0, last time updated with raylib 4.5
*
* Example contributed by Alex ZH (@ZzzhHe) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2023-2025 Alex ZH (@ZzzhHe)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Rlgl;
namespace Examples.Shaders;
public partial class VertexDisplacement : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
#if BROWSER
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
#else
private const int GlslVersion = 330;
#endif
public string Name => "Shaders / Vertex Displacement";
public string Title => "raylib [shaders] example - vertex displacement";
private Camera3D camera;
private Shader shader;
private Texture2D perlinNoiseMap;
private Model planeModel;
private float time;
public unsafe void Init()
{
// set up camera
camera = new();
camera.Position = new Vector3(20.0f, 5.0f, -20.0f);
camera.Target = new Vector3(0.0f, 0.0f, 0.0f);
camera.Up = new Vector3(0.0f, 1.0f, 0.0f);
camera.FovY = 60.0f;
camera.Projection = CameraProjection.Perspective;
// Load vertex and fragment shaders
shader = LoadShader(
$"resources/shaders/glsl{GlslVersion}/vertex_displacement.vs",
$"resources/shaders/glsl{GlslVersion}/vertex_displacement.fs"
);
// Load perlin noise texture
var perlinNoiseImage = GenImagePerlinNoise(512, 512, 0, 0, 1.0f);
perlinNoiseMap = LoadTextureFromImage(perlinNoiseImage);
UnloadImage(perlinNoiseImage);
// Set shader uniform location
var perlinNoiseMapLoc = GetShaderLocation(shader, "perlinNoiseMap");
EnableShader(shader.Id);
ActiveTextureSlot(1);
EnableTexture(perlinNoiseMap.Id);
SetUniformSampler(perlinNoiseMapLoc, 1);
// Create a plane mesh and model
var planeMesh = GenMeshPlane(50, 50, 50, 50);
planeModel = LoadModelFromMesh(planeMesh);
// Set plane model material
var materials = planeModel.Materials;
materials[0].Shader = shader;
time = 0.0f;
}
public unsafe void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(ref camera, CameraMode.Free); // Update camera
time += GetFrameTime(); // Update time variable
Raylib.SetShaderValue(shader, GetShaderLocation(shader, "time"), time, ShaderUniformDataType.Float); // Send time value to shader
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode3D(camera);
BeginShaderMode(shader);
// Draw plane model
DrawModel(planeModel, new Vector3(0.0f, 0.0f, 0.0f), 1.0f, new Color(255, 255, 255, 255));
EndShaderMode();
EndMode3D();
DrawText("Vertex displacement", 10, 10, 20, Color.DarkGray);
DrawFPS(10, 40);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadShader(shader);
UnloadModel(planeModel);
UnloadTexture(perlinNoiseMap);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - vertex displacement");
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new VertexDisplacement();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,275 @@
/*******************************************************************************************
*
* raylib [shapes] example - ball physics
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by David Buzatto (@davidbuzatto) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 David Buzatto (@davidbuzatto)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Raymath;
namespace Examples.Shapes;
public partial class BallPhysics : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int MAX_BALLS = 5000; // Maximum quantity of balls
public string Name => "Shapes / Ball Physics";
public string Title => "raylib [shapes] example - ball physics";
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
// Ball data type
private struct Ball
{
public Vector2 position;
public Vector2 speed;
public Vector2 prevPosition;
public float radius;
public float friction;
public float elasticity;
public Color color;
public bool grabbed;
}
private Ball[] balls;
private int ballCount;
private int grabbedBallIndex; // Index of the current ball that is grabbed (-1 if none)
private Vector2 pressOffset; // Mouse press offset relative to the ball that grabbedd
private float gravity; // World gravity
private Vector2 windowPosition;
public void Init()
{
balls = new Ball[MAX_BALLS];
// Init first ball in the array
balls[0] = new Ball
{
position = new Vector2(GetScreenWidth()/2.0f, GetScreenHeight()/2.0f),
speed = new Vector2(200, 200),
prevPosition = new Vector2(0, 0),
radius = 40,
friction = 0.99f,
elasticity = 0.9f,
color = Color.Blue,
grabbed = false
};
ballCount = 1;
grabbedBallIndex = -1; // A reference to the current ball that is grabbed
pressOffset = new Vector2(0, 0); // Mouse press offset relative to the ball that grabbedd
gravity = 100; // World gravity
windowPosition = GetWindowPosition();
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
float delta = GetFrameTime();
Vector2 mousePos = GetMousePosition();
// Checks if a ball was grabbed
if (IsMouseButtonPressed(MouseButton.Left))
{
for (int i = ballCount - 1; i >= 0; i--)
{
pressOffset.X = mousePos.X - balls[i].position.X;
pressOffset.Y = mousePos.Y - balls[i].position.Y;
// If the distance between the ball position and the mouse press position
// is less than or equal to the ball radius, the event occurred inside the ball
if (MathF.Sqrt(pressOffset.X*pressOffset.X + pressOffset.Y*pressOffset.Y) <= balls[i].radius)
{
balls[i].grabbed = true;
grabbedBallIndex = i;
break;
}
}
}
// Releases any ball the was grabbed
if (IsMouseButtonReleased(MouseButton.Left))
{
if (grabbedBallIndex != -1)
{
balls[grabbedBallIndex].grabbed = false;
grabbedBallIndex = -1;
}
}
// Creates a new ball
if (IsMouseButtonPressed(MouseButton.Right) || (IsKeyDown(KeyboardKey.LeftControl) && IsMouseButtonDown(MouseButton.Right)))
{
if (ballCount < MAX_BALLS)
{
balls[ballCount++] = new Ball
{
position = mousePos,
speed = new Vector2(GetRandomValue(-300, 300), GetRandomValue(-300, 300)),
prevPosition = new Vector2(0, 0),
radius = 20.0f + GetRandomValue(0, 30),
friction = 0.99f,
elasticity = 0.9f,
color = new Color(GetRandomValue(0, 255), GetRandomValue(0, 255), GetRandomValue(0, 255), 255),
grabbed = false
};
}
}
// Get window position change for shaking
Vector2 windowPositionDelta = Vector2Subtract(windowPosition, GetWindowPosition());
if (Vector2Length(windowPositionDelta) > 5.0f)
{
for (int i = 0; i < ballCount; i++)
{
if (!balls[i].grabbed) balls[i].speed = Vector2Add(balls[i].speed, Vector2Scale(windowPositionDelta, 10.0f));
}
}
// Shake balls
if (IsMouseButtonPressed(MouseButton.Middle))
{
for (int i = 0; i < ballCount; i++)
{
if (!balls[i].grabbed) balls[i].speed = new Vector2(GetRandomValue(-2000, 2000), GetRandomValue(-2000, 2000));
}
}
// Changes gravity
gravity += GetMouseWheelMove()*5;
// Updates each ball state
for (int i = 0; i < ballCount; i++)
{
// The ball is not grabbed
if (!balls[i].grabbed)
{
// Ball repositioning using the velocity
balls[i].position.X += balls[i].speed.X * delta;
balls[i].position.Y += balls[i].speed.Y * delta;
// Does the ball hit the screen right boundary?
if ((balls[i].position.X + balls[i].radius) >= screenWidth)
{
balls[i].position.X = screenWidth - balls[i].radius; // Ball repositioning
balls[i].speed.X = -balls[i].speed.X*balls[i].elasticity; // Elasticity makes the ball lose 10% of its velocity on hit
}
// Does the ball hit the screen left boundary?
else if ((balls[i].position.X - balls[i].radius) <= 0)
{
balls[i].position.X = balls[i].radius;
balls[i].speed.X = -balls[i].speed.X*balls[i].elasticity;
}
// The same for y axis
if ((balls[i].position.Y + balls[i].radius) >= screenHeight)
{
balls[i].position.Y = screenHeight - balls[i].radius;
balls[i].speed.Y = -balls[i].speed.Y*balls[i].elasticity;
}
else if ((balls[i].position.Y - balls[i].radius) <= 0)
{
balls[i].position.Y = balls[i].radius;
balls[i].speed.Y = -balls[i].speed.Y*balls[i].elasticity;
}
// Friction makes the ball lose 1% of its velocity each frame
balls[i].speed.X = balls[i].speed.X*balls[i].friction;
// Gravity affects only the y axis
balls[i].speed.Y = balls[i].speed.Y*balls[i].friction + gravity;
}
else
{
// Ball repositioning using the mouse position
balls[i].position.X = mousePos.X - pressOffset.X;
balls[i].position.Y = mousePos.Y - pressOffset.Y;
// While the ball is grabbed, recalculates its velocity
balls[i].speed.X = (balls[i].position.X - balls[i].prevPosition.X)/delta;
balls[i].speed.Y = (balls[i].position.Y - balls[i].prevPosition.Y)/delta;
balls[i].prevPosition = balls[i].position;
}
}
windowPosition = GetWindowPosition();
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
for (int i = 0; i < ballCount; i++)
{
DrawCircleV(balls[i].position, balls[i].radius, balls[i].color);
DrawCircleLinesV(balls[i].position, balls[i].radius, Color.Black);
}
DrawText("grab a ball by pressing with the mouse and throw it by releasing", 10, 10, 10, Color.DarkGray);
DrawText("right click to create new balls (keep left control pressed to create a lot)", 10, 30, 10, Color.DarkGray);
DrawText("use mouse wheel to change gravity", 10, 50, 10, Color.DarkGray);
DrawText("middle click to shake", 10, 70, 10, Color.DarkGray);
DrawText($"BALL COUNT: {ballCount}", 10, GetScreenHeight() - 70, 20, Color.Black);
DrawText($"GRAVITY: {gravity:F2}", 10, GetScreenHeight() - 40, 20, Color.Black);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - ball physics");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new BallPhysics();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,290 @@
/*******************************************************************************************
*
* raylib [shapes] example - bullet hell
*
* Example complexity rating: [] 1/4
*
* Example originally created with raylib 5.6, last time updated with raylib 5.6
*
* Example contributed by Zero (@zerohorsepower) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Zero (@zerohorsepower)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Shapes;
public partial class BulletHell : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int MAX_BULLETS = 500000; // Max bullets to be processed
public string Name => "Shapes / Bullet Hell";
public string Title => "raylib [shapes] example - bullet hell";
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
private struct Bullet
{
public Vector2 position; // Bullet position on screen
public Vector2 acceleration; // Amount of pixels to be incremented to position every frame
public bool disabled; // Skip processing and draw case out of screen
public Color color; // Bullet color
}
// Bullets definition
private Bullet[] bullets;
private int bulletCount;
private int bulletDisabledCount; // Used to calculate how many bullets are on screen
private int bulletRadius;
private float bulletSpeed;
private int bulletRows;
private Color[] bulletColor;
// Spawner variables
private float baseDirection;
private int angleIncrement; // After spawn all bullet rows, increment this value on the baseDirection for next the frame
private float spawnCooldown;
private float spawnCooldownTimer;
// Magic circle
private float magicCircleRotation;
// Used on performance drawing
private RenderTexture2D bulletTexture;
private bool drawInPerformanceMode; // Switch between DrawCircle() and DrawTexture()
public void Init()
{
// Bullets definition
bullets = new Bullet[MAX_BULLETS]; // Bullets array
bulletCount = 0;
bulletDisabledCount = 0;
bulletRadius = 10;
bulletSpeed = 3.0f;
bulletRows = 6;
bulletColor = new[] { Color.Red, Color.Blue };
// Spawner variables
baseDirection = 0;
angleIncrement = 5;
spawnCooldown = 2;
spawnCooldownTimer = spawnCooldown;
// Magic circle
magicCircleRotation = 0;
// Used on performance drawing
bulletTexture = LoadRenderTexture(24, 24);
// Draw circle to bullet texture, then draw bullet using DrawTexture()
// NOTE: This is done to improve the performance, since DrawCircle() is very slow
BeginTextureMode(bulletTexture);
DrawCircle(12, 12, (float)bulletRadius, Color.White);
DrawCircleLines(12, 12, (float)bulletRadius, Color.Black);
EndTextureMode();
drawInPerformanceMode = true;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
// Reset the bullet index
// New bullets will replace the old ones that are already disabled due to out-of-screen
if (bulletCount >= MAX_BULLETS)
{
bulletCount = 0;
bulletDisabledCount = 0;
}
spawnCooldownTimer--;
if (spawnCooldownTimer < 0)
{
spawnCooldownTimer = spawnCooldown;
// Spawn bullets
float degreesPerRow = 360.0f/bulletRows;
for (int row = 0; row < bulletRows; row++)
{
if (bulletCount < MAX_BULLETS)
{
bullets[bulletCount].position = new Vector2((float)screenWidth/2, (float)screenHeight/2);
bullets[bulletCount].disabled = false;
bullets[bulletCount].color = bulletColor[row%2];
float bulletDirection = baseDirection + (degreesPerRow*row);
// Bullet speed*bullet direction, this will determine how much pixels will be incremented/decremented
// from the bullet position every frame. Since the bullets doesn't change its direction and speed,
// only need to calculate it at the spawning time
// 0 degrees = right, 90 degrees = down, 180 degrees = left and 270 degrees = up, basically clockwise
// Case you want it to be anti-clockwise, add "* -1" at the y acceleration
bullets[bulletCount].acceleration = new Vector2(
bulletSpeed*MathF.Cos(bulletDirection*DEG2RAD),
bulletSpeed*MathF.Sin(bulletDirection*DEG2RAD)
);
bulletCount++;
}
}
baseDirection += angleIncrement;
}
// Update bullets position based on its acceleration
for (int i = 0; i < bulletCount; i++)
{
// Only update bullet if inside the screen
if (!bullets[i].disabled)
{
bullets[i].position.X += bullets[i].acceleration.X;
bullets[i].position.Y += bullets[i].acceleration.Y;
// Disable bullet if out of screen
if ((bullets[i].position.X < -bulletRadius*2) ||
(bullets[i].position.X > screenWidth + bulletRadius*2) ||
(bullets[i].position.Y < -bulletRadius*2) ||
(bullets[i].position.Y > screenHeight + bulletRadius*2))
{
bullets[i].disabled = true;
bulletDisabledCount++;
}
}
}
// Input logic
if ((IsKeyPressed(KeyboardKey.Right) || IsKeyPressed(KeyboardKey.D)) && (bulletRows < 359)) bulletRows++;
if ((IsKeyPressed(KeyboardKey.Left) || IsKeyPressed(KeyboardKey.A)) && (bulletRows > 1)) bulletRows--;
if (IsKeyPressed(KeyboardKey.Up) || IsKeyPressed(KeyboardKey.W)) bulletSpeed += 0.25f;
if ((IsKeyPressed(KeyboardKey.Down) || IsKeyPressed(KeyboardKey.S)) && (bulletSpeed > 0.50f)) bulletSpeed -= 0.25f;
if (IsKeyPressed(KeyboardKey.Z) && (spawnCooldown > 1)) spawnCooldown--;
if (IsKeyPressed(KeyboardKey.X)) spawnCooldown++;
if (IsKeyPressed(KeyboardKey.Enter)) drawInPerformanceMode = !drawInPerformanceMode;
if (IsKeyDown(KeyboardKey.Space))
{
angleIncrement += 1;
angleIncrement %= 360;
}
if (IsKeyPressed(KeyboardKey.C))
{
bulletCount = 0;
bulletDisabledCount = 0;
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
// Draw magic circle
magicCircleRotation++;
DrawRectanglePro(new Rectangle((float)screenWidth/2, (float)screenHeight/2, 120, 120),
new Vector2(60.0f, 60.0f), magicCircleRotation, Color.Purple);
DrawRectanglePro(new Rectangle((float)screenWidth/2, (float)screenHeight/2, 120, 120),
new Vector2(60.0f, 60.0f), magicCircleRotation + 45, Color.Purple);
DrawCircleLines(screenWidth/2, screenHeight/2, 70, Color.Black);
DrawCircleLines(screenWidth/2, screenHeight/2, 50, Color.Black);
DrawCircleLines(screenWidth/2, screenHeight/2, 30, Color.Black);
// Draw bullets
if (drawInPerformanceMode)
{
// Draw bullets using pre-rendered texture containing circle
for (int i = 0; i < bulletCount; i++)
{
// Do not draw disabled bullets (out of screen)
if (!bullets[i].disabled)
{
DrawTexture(bulletTexture.Texture,
(int)(bullets[i].position.X - bulletTexture.Texture.Width*0.5f),
(int)(bullets[i].position.Y - bulletTexture.Texture.Height*0.5f),
bullets[i].color);
}
}
}
else
{
// Draw bullets using DrawCircle(), less performant
for (int i = 0; i < bulletCount; i++)
{
// Do not draw disabled bullets (out of screen)
if (!bullets[i].disabled)
{
DrawCircleV(bullets[i].position, (float)bulletRadius, bullets[i].color);
DrawCircleLinesV(bullets[i].position, (float)bulletRadius, Color.Black);
}
}
}
// Draw UI
DrawRectangle(10, 10, 280, 150, new Color(0, 0, 0, 200));
DrawText("Controls:", 20, 20, 10, Color.LightGray);
DrawText("- Right/Left or A/D: Change rows number", 40, 40, 10, Color.LightGray);
DrawText("- Up/Down or W/S: Change bullet speed", 40, 60, 10, Color.LightGray);
DrawText("- Z or X: Change spawn cooldown", 40, 80, 10, Color.LightGray);
DrawText("- Space (Hold): Change the angle increment", 40, 100, 10, Color.LightGray);
DrawText("- Enter: Switch draw method (Performance)", 40, 120, 10, Color.LightGray);
DrawText("- C: Clear bullets", 40, 140, 10, Color.LightGray);
DrawRectangle(610, 10, 170, 30, new Color(0, 0, 0, 200));
if (drawInPerformanceMode) DrawText("Draw method: DrawTexture(*)", 620, 20, 10, Color.Green);
else DrawText("Draw method: DrawCircle(*)", 620, 20, 10, Color.Red);
DrawRectangle(135, 410, 530, 30, new Color(0, 0, 0, 200));
DrawText($"[ FPS: {GetFPS()}, Bullets: {bulletCount - bulletDisabledCount}, Rows: {bulletRows}, Bullet speed: {bulletSpeed:F2}, Angle increment per frame: {angleIncrement}, Cooldown: {spawnCooldown:F0} ]",
155, 420, 10, Color.Green);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadRenderTexture(bulletTexture); // Unload bullet texture
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - bullet hell");
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new BulletHell();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,240 @@
/*******************************************************************************************
*
* raylib [shapes] example - clock of clocks
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 5.5, last time updated with raylib 6.0
*
* Example contributed by JP Mortiboys (@themushroompirates) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 JP Mortiboys (@themushroompirates)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Raymath; // Required for: Lerp(), Clamp()
namespace Examples.Shapes;
public partial class ClockOfClocks : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Shapes / Clock of Clocks";
public string Title => "raylib [shapes] example - clock of clocks";
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
private Color bgColor;
private Color handsColor;
private const float clockFaceSize = 24;
private const float clockFaceSpacing = 8.0f;
private const float sectionSpacing = 16.0f;
private static readonly Vector2 TL = new(0.0f, 90.0f); // Top-left corner
private static readonly Vector2 TR = new(90.0f, 180.0f); // Top-right corner
private static readonly Vector2 BR = new(180.0f, 270.0f); // Bottom-right corner
private static readonly Vector2 BL = new(0.0f, 270.0f); // Bottom-left corner
private static readonly Vector2 HH = new(0.0f, 180.0f); // Horizontal line
private static readonly Vector2 VV = new(90.0f, 270.0f); // Vertical line
private static readonly Vector2 ZZ = new(135.0f, 135.0f); // Not relevant
private Vector2[,] digitAngles;
// Time for the hands to move to the new position (in seconds); this must be <1s
private const float handsMoveDuration = 0.5f;
private int prevSeconds;
private Vector2[,] currentAngles;
private Vector2[,] srcAngles;
private Vector2[,] dstAngles;
private float handsMoveTimer;
private int hourMode;
public void Init()
{
bgColor = ColorLerp(Color.DarkBlue, Color.Black, 0.75f);
handsColor = ColorLerp(Color.Yellow, Color.RayWhite, .25f);
digitAngles = new Vector2[10, 24]
{
/* 0 */ { TL, HH, HH, TR, /* */ VV, TL, TR, VV, /* */ VV, VV, VV, VV, /* */ VV, VV, VV, VV, /* */ VV, BL, BR, VV, /* */ BL, HH, HH, BR },
/* 1 */ { TL, HH, TR, ZZ, /* */ BL, TR, VV, ZZ, /* */ ZZ, VV, VV, ZZ, /* */ ZZ, VV, VV, ZZ, /* */ TL, BR, BL, TR, /* */ BL, HH, HH, BR },
/* 2 */ { TL, HH, HH, TR, /* */ BL, HH, TR, VV, /* */ TL, HH, BR, VV, /* */ VV, TL, HH, BR, /* */ VV, BL, HH, TR, /* */ BL, HH, HH, BR },
/* 3 */ { TL, HH, HH, TR, /* */ BL, HH, TR, VV, /* */ TL, HH, BR, VV, /* */ BL, HH, TR, VV, /* */ TL, HH, BR, VV, /* */ BL, HH, HH, BR },
/* 4 */ { TL, TR, TL, TR, /* */ VV, VV, VV, VV, /* */ VV, BL, BR, VV, /* */ BL, HH, TR, VV, /* */ ZZ, ZZ, VV, VV, /* */ ZZ, ZZ, BL, BR },
/* 5 */ { TL, HH, HH, TR, /* */ VV, TL, HH, BR, /* */ VV, BL, HH, TR, /* */ BL, HH, TR, VV, /* */ TL, HH, BR, VV, /* */ BL, HH, HH, BR },
/* 6 */ { TL, HH, HH, TR, /* */ VV, TL, HH, BR, /* */ VV, BL, HH, TR, /* */ VV, TL, TR, VV, /* */ VV, BL, BR, VV, /* */ BL, HH, HH, BR },
/* 7 */ { TL, HH, HH, TR, /* */ BL, HH, TR, VV, /* */ ZZ, ZZ, VV, VV, /* */ ZZ, ZZ, VV, VV, /* */ ZZ, ZZ, VV, VV, /* */ ZZ, ZZ, BL, BR },
/* 8 */ { TL, HH, HH, TR, /* */ VV, TL, TR, VV, /* */ VV, BL, BR, VV, /* */ VV, TL, TR, VV, /* */ VV, BL, BR, VV, /* */ BL, HH, HH, BR },
/* 9 */ { TL, HH, HH, TR, /* */ VV, TL, TR, VV, /* */ VV, BL, BR, VV, /* */ BL, HH, TR, VV, /* */ TL, HH, BR, VV, /* */ BL, HH, HH, BR },
};
prevSeconds = -1;
currentAngles = new Vector2[6, 24];
srcAngles = new Vector2[6, 24];
dstAngles = new Vector2[6, 24];
handsMoveTimer = 0.0f;
hourMode = 24;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
// Get the current time
DateTime timeinfo = DateTime.Now;
if (timeinfo.Second != prevSeconds)
{
// The time has changed, so we need to move the hands to the new positions
prevSeconds = timeinfo.Second;
// Format the current time so we can access the individual digits
string clockDigits = $"{timeinfo.Hour % hourMode:D2}{timeinfo.Minute:D2}{timeinfo.Second:D2}";
// Fetch where we want all the hands to be
for (int digit = 0; digit < 6; digit++)
{
for (int cell = 0; cell < 24; cell++)
{
srcAngles[digit, cell] = currentAngles[digit, cell];
dstAngles[digit, cell] = digitAngles[clockDigits[digit] - '0', cell];
// Quick exception for 12h mode
if ((digit == 0) && (hourMode == 12) && (clockDigits[0] == '0')) dstAngles[digit, cell] = ZZ;
if (srcAngles[digit, cell].X > dstAngles[digit, cell].X) srcAngles[digit, cell].X -= 360.0f;
if (srcAngles[digit, cell].Y > dstAngles[digit, cell].Y) srcAngles[digit, cell].Y -= 360.0f;
}
}
// Reset the timer
handsMoveTimer = -GetFrameTime();
}
// Now let's animate all the hands if we need to
if (handsMoveTimer < handsMoveDuration)
{
// Increase the timer but don't go above the maximum
handsMoveTimer = Clamp(handsMoveTimer + GetFrameTime(), 0, handsMoveDuration);
// Calculate the % completion of the animation
float t = handsMoveTimer / handsMoveDuration;
// A little cheeky smoothstep
t = t * t * (3.0f - 2.0f * t);
for (int digit = 0; digit < 6; digit++)
{
for (int cell = 0; cell < 24; cell++)
{
currentAngles[digit, cell].X = Lerp(srcAngles[digit, cell].X, dstAngles[digit, cell].X, t);
currentAngles[digit, cell].Y = Lerp(srcAngles[digit, cell].Y, dstAngles[digit, cell].Y, t);
}
}
}
// Handle input
if (IsKeyPressed(KeyboardKey.Space)) hourMode = 36 - hourMode; // Toggle between 12 and 24 hour mode with space
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(bgColor);
DrawText($"{hourMode}-h mode, space to change", 10, 30, 20, Color.RayWhite);
float xOffset = 4.0f;
for (int digit = 0; digit < 6; digit++)
{
for (int row = 0; row < 6; row++)
{
for (int col = 0; col < 4; col++)
{
Vector2 centre = new(
xOffset + col * (clockFaceSize + clockFaceSpacing) + clockFaceSize * 0.5f,
100 + row * (clockFaceSize + clockFaceSpacing) + clockFaceSize * 0.5f
);
DrawRing(centre, clockFaceSize * 0.5f - 2.0f, clockFaceSize * 0.5f, 0, 360, 24, Color.DarkGray);
// Big hand
DrawRectanglePro(
new Rectangle(centre.X, centre.Y, clockFaceSize * 0.5f + 4.0f, 4.0f),
new Vector2(2.0f, 2.0f),
currentAngles[digit, row * 4 + col].X,
handsColor
);
// Little hand
DrawRectanglePro(
new Rectangle(centre.X, centre.Y, clockFaceSize * 0.5f + 2.0f, 4.0f),
new Vector2(2.0f, 2.0f),
currentAngles[digit, row * 4 + col].Y,
handsColor
);
}
}
xOffset += (clockFaceSize + clockFaceSpacing) * 4;
if (digit % 2 == 1)
{
DrawRing(new Vector2(xOffset + 4.0f, 160.0f), 6.0f, 8.0f, 0.0f, 360.0f, 24, handsColor);
DrawRing(new Vector2(xOffset + 4.0f, 225.0f), 6.0f, 8.0f, 0.0f, 360.0f, 24, handsColor);
xOffset += sectionSpacing;
}
}
DrawFPS(10, 10);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
SetConfigFlags(ConfigFlags.Msaa4xHint);
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - clock of clocks");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new ClockOfClocks();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,130 @@
/*******************************************************************************************
*
* raylib [shapes] example - dashed line
*
* Example complexity rating: [] 1/4
*
* Example originally created with raylib 5.5, last time updated with raylib 5.5
*
* Example contributed by Luís Almeida (@luis605)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Luís Almeida (@luis605)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Shapes;
public partial class DashedLine : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Shapes / Dashed Line";
public string Title => "raylib [shapes] example - dashed line";
// Line Properties
private Vector2 lineStartPosition;
private Vector2 lineEndPosition;
private float dashLength;
private float blankLength;
// Color selection
private Color[] lineColors;
private int colorIndex;
public void Init()
{
// Line Properties
lineStartPosition = new Vector2(20.0f, 50.0f);
lineEndPosition = new Vector2(780.0f, 400.0f);
dashLength = 25.0f;
blankLength = 15.0f;
// Color selection
lineColors = new[] { Color.Red, Color.Orange, Color.Gold, Color.Green, Color.Blue, Color.Violet, Color.Pink, Color.Black };
colorIndex = 0;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
lineEndPosition = GetMousePosition(); // Line endpoint follows the mouse
// Change Dash Length (UP/DOWN arrows)
if (IsKeyDown(KeyboardKey.Up)) dashLength += 1.0f;
if (IsKeyDown(KeyboardKey.Down) && dashLength > 1.0f) dashLength -= 1.0f;
// Change Space Length (LEFT/RIGHT arrows)
if (IsKeyDown(KeyboardKey.Right)) blankLength += 1.0f;
if (IsKeyDown(KeyboardKey.Left) && blankLength > 1.0f) blankLength -= 1.0f;
// Cycle through colors ('C' key)
if (IsKeyPressed(KeyboardKey.C)) colorIndex = (colorIndex + 1)%lineColors.Length;
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
// Draw the dashed line with the current properties
DrawLineDashed(lineStartPosition, lineEndPosition, (int)dashLength, (int)blankLength, lineColors[colorIndex]);
// Draw UI and Instructions
DrawRectangle(5, 5, 265, 95, Fade(Color.SkyBlue, 0.5f));
DrawRectangleLines(5, 5, 265, 95, Color.Blue);
DrawText("CONTROLS:", 15, 15, 10, Color.Black);
DrawText("UP/DOWN: Change Dash Length", 15, 35, 10, Color.Black);
DrawText("LEFT/RIGHT: Change Space Length", 15, 55, 10, Color.Black);
DrawText("C: Cycle Color", 15, 75, 10, Color.Black);
DrawText($"Dash: {dashLength:F0} | Space: {blankLength:F0}", 15, 115, 10, Color.DarkGray);
DrawFPS(screenWidth - 80, 10);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - dashed line");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new DashedLine();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,327 @@
/*******************************************************************************************
*
* raylib [shapes] example - digital clock
*
* Example complexity rating: [] 4/4
*
* Example originally created with raylib 5.5, last time updated with raylib 5.6
*
* Example contributed by Hamza RAHAL (@hmz-rhl) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Hamza RAHAL (@hmz-rhl) and Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Shapes;
public partial class DigitalClock : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int CLOCK_ANALOG = 0;
private const int CLOCK_DIGITAL = 1;
public string Name => "Shapes / Digital Clock";
public string Title => "raylib [shapes] example - digital clock";
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
// Clock hand type
private struct ClockHand
{
public int value; // Time value
// Visual elements
public float angle; // Hand angle
public int length; // Hand length
public int thickness; // Hand thickness
public Color color; // Hand color
}
// Clock hands
private struct Clock
{
public ClockHand second; // Clock hand for seconds
public ClockHand minute; // Clock hand for minutes
public ClockHand hour; // Clock hand for hours
}
private int clockMode;
private Clock clock;
public void Init()
{
clockMode = CLOCK_DIGITAL;
// Initialize clock
// NOTE: Includes visual info for analog clock
clock = new Clock();
clock.second.angle = 45;
clock.second.length = 140;
clock.second.thickness = 3;
clock.second.color = Color.Maroon;
clock.minute.angle = 10;
clock.minute.length = 130;
clock.minute.thickness = 7;
clock.minute.color = Color.DarkGray;
clock.hour.angle = 0;
clock.hour.length = 100;
clock.hour.thickness = 7;
clock.hour.color = Color.Black;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
if (IsKeyPressed(KeyboardKey.Space))
{
// Toggle clock mode
if (clockMode == CLOCK_DIGITAL) clockMode = CLOCK_ANALOG;
else if (clockMode == CLOCK_ANALOG) clockMode = CLOCK_DIGITAL;
}
UpdateClock(); // Update clock required data: value and angle
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
// Draw clock in selected mode
if (clockMode == CLOCK_ANALOG) DrawClockAnalog(clock, new Vector2(400, 240));
else if (clockMode == CLOCK_DIGITAL)
{
DrawClockDigital(clock, new Vector2(30, 60));
// Draw clock using default raylib font
string clockTime = $"{clock.hour.value:D2}:{clock.minute.value:D2}:{clock.second.value:D2}";
DrawText(clockTime, GetScreenWidth() / 2 - MeasureText(clockTime, 150) / 2, 300, 150, Color.Black);
}
DrawText($"Press [SPACE] to switch clock mode: {((clockMode == CLOCK_DIGITAL) ? "DIGITAL CLOCK" : "ANALOGUE CLOCK")}",
10, 10, 20, Color.DarkGray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
//----------------------------------------------------------------------------------
// Module Functions Definition
//----------------------------------------------------------------------------------
// Update clock time
private void UpdateClock()
{
DateTime timeinfo = DateTime.Now;
// Updating time data
clock.second.value = timeinfo.Second;
clock.minute.value = timeinfo.Minute;
clock.hour.value = timeinfo.Hour;
clock.hour.angle = (timeinfo.Hour % 12) * 180.0f / 6.0f;
clock.hour.angle += (timeinfo.Minute % 60) * 30 / 60.0f;
clock.hour.angle -= 90;
clock.minute.angle = (timeinfo.Minute % 60) * 6.0f;
clock.minute.angle += (timeinfo.Second % 60) * 6 / 60.0f;
clock.minute.angle -= 90;
clock.second.angle = (timeinfo.Second % 60) * 6.0f;
clock.second.angle -= 90;
}
// Draw analog clock
// Parameter: position, refers to center position
private static void DrawClockAnalog(Clock clock, Vector2 position)
{
// Draw clock base
DrawCircleV(position, clock.second.length + 40.0f, Color.LightGray);
DrawCircleV(position, 12.0f, Color.Gray);
// Draw clock minutes/seconds lines
for (int i = 0; i < 60; i++)
{
DrawLineEx(new Vector2(position.X + (clock.second.length + ((i % 5) != 0 ? 10 : 6)) * MathF.Cos((6.0f * i - 90.0f) * DEG2RAD),
position.Y + (clock.second.length + ((i % 5) != 0 ? 10 : 6)) * MathF.Sin((6.0f * i - 90.0f) * DEG2RAD)),
new Vector2(position.X + (clock.second.length + 20) * MathF.Cos((6.0f * i - 90.0f) * DEG2RAD),
position.Y + (clock.second.length + 20) * MathF.Sin((6.0f * i - 90.0f) * DEG2RAD)), ((i % 5) != 0 ? 1.0f : 3.0f), Color.DarkGray);
}
// Draw hand seconds
DrawRectanglePro(new Rectangle(position.X, position.Y, (float)clock.second.length, (float)clock.second.thickness),
new Vector2(0.0f, clock.second.thickness / 2.0f), clock.second.angle, clock.second.color);
// Draw hand minutes
DrawRectanglePro(new Rectangle(position.X, position.Y, (float)clock.minute.length, (float)clock.minute.thickness),
new Vector2(0.0f, clock.minute.thickness / 2.0f), clock.minute.angle, clock.minute.color);
// Draw hand hours
DrawRectanglePro(new Rectangle(position.X, position.Y, (float)clock.hour.length, (float)clock.hour.thickness),
new Vector2(0.0f, clock.hour.thickness / 2.0f), clock.hour.angle, clock.hour.color);
}
// Draw digital clock
// PARAM: position, refers to top-left corner
private static void DrawClockDigital(Clock clock, Vector2 position)
{
// Draw clock using custom 7-segments display (made of shapes)
DrawDisplayValue(new Vector2(position.X, position.Y), clock.hour.value / 10, Color.Red, Fade(Color.LightGray, 0.3f));
DrawDisplayValue(new Vector2(position.X + 120, position.Y), clock.hour.value % 10, Color.Red, Fade(Color.LightGray, 0.3f));
DrawCircle((int)position.X + 240, (int)position.Y + 70, 12, (clock.second.value % 2) != 0 ? Color.Red : Fade(Color.LightGray, 0.3f));
DrawCircle((int)position.X + 240, (int)position.Y + 150, 12, (clock.second.value % 2) != 0 ? Color.Red : Fade(Color.LightGray, 0.3f));
DrawDisplayValue(new Vector2(position.X + 260, position.Y), clock.minute.value / 10, Color.Red, Fade(Color.LightGray, 0.3f));
DrawDisplayValue(new Vector2(position.X + 380, position.Y), clock.minute.value % 10, Color.Red, Fade(Color.LightGray, 0.3f));
DrawCircle((int)position.X + 500, (int)position.Y + 70, 12, (clock.second.value % 2) != 0 ? Color.Red : Fade(Color.LightGray, 0.3f));
DrawCircle((int)position.X + 500, (int)position.Y + 150, 12, (clock.second.value % 2) != 0 ? Color.Red : Fade(Color.LightGray, 0.3f));
DrawDisplayValue(new Vector2(position.X + 520, position.Y), clock.second.value / 10, Color.Red, Fade(Color.LightGray, 0.3f));
DrawDisplayValue(new Vector2(position.X + 640, position.Y), clock.second.value % 10, Color.Red, Fade(Color.LightGray, 0.3f));
}
// Draw 7-segment display with value
private static void DrawDisplayValue(Vector2 position, int value, Color colorOn, Color colorOff)
{
switch (value)
{
case 0: Draw7SDisplay(position, 0b00111111, colorOn, colorOff); break;
case 1: Draw7SDisplay(position, 0b00000110, colorOn, colorOff); break;
case 2: Draw7SDisplay(position, 0b01011011, colorOn, colorOff); break;
case 3: Draw7SDisplay(position, 0b01001111, colorOn, colorOff); break;
case 4: Draw7SDisplay(position, 0b01100110, colorOn, colorOff); break;
case 5: Draw7SDisplay(position, 0b01101101, colorOn, colorOff); break;
case 6: Draw7SDisplay(position, 0b01111101, colorOn, colorOff); break;
case 7: Draw7SDisplay(position, 0b00000111, colorOn, colorOff); break;
case 8: Draw7SDisplay(position, 0b01111111, colorOn, colorOff); break;
case 9: Draw7SDisplay(position, 0b01101111, colorOn, colorOff); break;
default: break;
}
}
// Draw seven segments display
// Parameter: position, refers to top-left corner of display
// Parameter: segments, defines in binary the segments to be activated
private static void Draw7SDisplay(Vector2 position, int segments, Color colorOn, Color colorOff)
{
int segmentLen = 60;
int segmentThick = 20;
float offsetYAdjust = segmentThick * 0.3f; // HACK: Adjust gap space between segment limits
// Segment A
DrawDisplaySegment(new Vector2(position.X + segmentThick + segmentLen / 2.0f, position.Y + segmentThick),
segmentLen, segmentThick, false, (segments & 0b00000001) != 0 ? colorOn : colorOff);
// Segment B
DrawDisplaySegment(new Vector2(position.X + segmentThick + segmentLen + segmentThick / 2.0f, position.Y + 2 * segmentThick + segmentLen / 2.0f - offsetYAdjust),
segmentLen, segmentThick, true, (segments & 0b00000010) != 0 ? colorOn : colorOff);
// Segment C
DrawDisplaySegment(new Vector2(position.X + segmentThick + segmentLen + segmentThick / 2.0f, position.Y + 4 * segmentThick + segmentLen + segmentLen / 2.0f - 3 * offsetYAdjust),
segmentLen, segmentThick, true, (segments & 0b00000100) != 0 ? colorOn : colorOff);
// Segment D
DrawDisplaySegment(new Vector2(position.X + segmentThick + segmentLen / 2.0f, position.Y + 5 * segmentThick + 2 * segmentLen - 4 * offsetYAdjust),
segmentLen, segmentThick, false, (segments & 0b00001000) != 0 ? colorOn : colorOff);
// Segment E
DrawDisplaySegment(new Vector2(position.X + segmentThick / 2.0f, position.Y + 4 * segmentThick + segmentLen + segmentLen / 2.0f - 3 * offsetYAdjust),
segmentLen, segmentThick, true, (segments & 0b00010000) != 0 ? colorOn : colorOff);
// Segment F
DrawDisplaySegment(new Vector2(position.X + segmentThick / 2.0f, position.Y + 2 * segmentThick + segmentLen / 2.0f - offsetYAdjust),
segmentLen, segmentThick, true, (segments & 0b00100000) != 0 ? colorOn : colorOff);
// Segment G
DrawDisplaySegment(new Vector2(position.X + segmentThick + segmentLen / 2.0f, position.Y + 3 * segmentThick + segmentLen - 2 * offsetYAdjust),
segmentLen, segmentThick, false, (segments & 0b01000000) != 0 ? colorOn : colorOff);
}
// Draw one 7-segment display segment, horizontal or vertical
private static void DrawDisplaySegment(Vector2 center, int length, int thick, bool vertical, Color color)
{
if (!vertical)
{
// Horizontal segment points
/*
3___________________________5
/ \
/1 x 6\
\ /
\2___________________________4/
*/
Vector2[] segmentPointsH = new Vector2[6]
{
new Vector2(center.X - length / 2.0f - thick / 2.0f, center.Y), // Point 1
new Vector2(center.X - length / 2.0f, center.Y + thick / 2.0f), // Point 2
new Vector2(center.X - length / 2.0f, center.Y - thick / 2.0f), // Point 3
new Vector2(center.X + length / 2.0f, center.Y + thick / 2.0f), // Point 4
new Vector2(center.X + length / 2.0f, center.Y - thick / 2.0f), // Point 5
new Vector2(center.X + length / 2.0f + thick / 2.0f, center.Y), // Point 6
};
DrawTriangleStrip(segmentPointsH, 6, color);
}
else
{
// Vertical segment points
Vector2[] segmentPointsV = new Vector2[6]
{
new Vector2(center.X, center.Y - length / 2.0f - thick / 2.0f), // Point 1
new Vector2(center.X - thick / 2.0f, center.Y - length / 2.0f), // Point 2
new Vector2(center.X + thick / 2.0f, center.Y - length / 2.0f), // Point 3
new Vector2(center.X - thick / 2.0f, center.Y + length / 2.0f), // Point 4
new Vector2(center.X + thick / 2.0f, center.Y + length / 2.0f), // Point 5
new Vector2(center.X, center.Y + (float)length / 2 + thick / 2.0f), // Point 6
};
DrawTriangleStrip(segmentPointsV, 6, color);
}
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
SetConfigFlags(ConfigFlags.Msaa4xHint);
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - digital clock");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new DigitalClock();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,205 @@
/*******************************************************************************************
*
* raylib [shapes] example - double pendulum
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 5.5, last time updated with raylib 5.5
*
* Example contributed by JoeCheong (@Joecheong2006) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 JoeCheong (@Joecheong2006)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Shapes;
public partial class DoublePendulum : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
// Constant for Simulation
private const int SIMULATION_STEPS = 30;
private const float G = 9.81f;
public string Name => "Shapes / Double Pendulum";
public string Title => "raylib [shapes] example - double pendulum";
public ConfigFlags ConfigFlags => ConfigFlags.HighDpiWindow;
// Simulation Parameters
private float l1, m1, theta1, w1;
private float l2, m2, theta2, w2;
private float lengthScaler;
private float totalM;
private Vector2 previousPosition;
// Scale length
private float L1;
private float L2;
// Draw parameters
private float lineThick, trailThick;
private float fateAlpha;
// Create framebuffer
private RenderTexture2D target;
// Calculate pendulum end point
private static Vector2 CalculatePendulumEndPoint(float l, float theta)
{
return new(10 * l * MathF.Sin(theta), 10 * l * MathF.Cos(theta));
}
// Calculate double pendulum end point
private static Vector2 CalculateDoublePendulumEndPoint(float l1, float theta1, float l2, float theta2)
{
Vector2 endpoint1 = CalculatePendulumEndPoint(l1, theta1);
Vector2 endpoint2 = CalculatePendulumEndPoint(l2, theta2);
return new(endpoint1.X + endpoint2.X, endpoint1.Y + endpoint2.Y);
}
public void Init()
{
// Simulation Parameters
l1 = 15.0f; m1 = 0.2f; theta1 = DEG2RAD * 170; w1 = 0;
l2 = 15.0f; m2 = 0.1f; theta2 = DEG2RAD * 0; w2 = 0;
lengthScaler = 0.1f;
totalM = m1 + m2;
previousPosition = CalculateDoublePendulumEndPoint(l1, theta1, l2, theta2);
previousPosition.X += ((float)screenWidth / 2);
previousPosition.Y += ((float)screenHeight / 2 - 100);
// Scale length
L1 = l1 * lengthScaler;
L2 = l2 * lengthScaler;
// Draw parameters
lineThick = 20; trailThick = 2;
fateAlpha = 0.01f;
// Create framebuffer
target = LoadRenderTexture(screenWidth, screenHeight);
SetTextureFilter(target.Texture, TextureFilter.Bilinear);
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
float dt = GetFrameTime();
float step = dt / SIMULATION_STEPS, step2 = step * step;
// Update Physics - larger steps = better approximation
for (int i = 0; i < SIMULATION_STEPS; i++)
{
float delta = theta1 - theta2;
float sinD = MathF.Sin(delta), cosD = MathF.Cos(delta), cos2D = MathF.Cos(2 * delta);
float ww1 = w1 * w1, ww2 = w2 * w2;
// Calculate a1
float a1 = (-G * (2 * m1 + m2) * MathF.Sin(theta1)
- m2 * G * MathF.Sin(theta1 - 2 * theta2)
- 2 * sinD * m2 * (ww2 * L2 + ww1 * L1 * cosD))
/ (L1 * (2 * m1 + m2 - m2 * cos2D));
// Calculate a2
float a2 = (2 * sinD * (ww1 * L1 * totalM
+ G * totalM * MathF.Cos(theta1)
+ ww2 * L2 * m2 * cosD))
/ (L2 * (2 * m1 + m2 - m2 * cos2D));
// Update thetas
theta1 += w1 * step + 0.5f * a1 * step2;
theta2 += w2 * step + 0.5f * a2 * step2;
// Update omegas
w1 += a1 * step;
w2 += a2 * step;
}
// Calculate position
Vector2 currentPosition = CalculateDoublePendulumEndPoint(l1, theta1, l2, theta2);
currentPosition.X += (float)screenWidth / 2;
currentPosition.Y += (float)screenHeight / 2 - 100;
// Draw to render texture
BeginTextureMode(target);
// Draw a transparent rectangle - smaller alpha = longer trails
DrawRectangle(0, 0, screenWidth, screenHeight, Fade(Color.Black, fateAlpha));
// Draw trail
DrawCircleV(previousPosition, trailThick, Color.Red);
DrawLineEx(previousPosition, currentPosition, trailThick * 2, Color.Red);
EndTextureMode();
// Update previous position
previousPosition = currentPosition;
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.Black);
// Draw trails texture
DrawTextureRec(target.Texture, new Rectangle(0, 0, (float)target.Texture.Width, (float)-target.Texture.Height), new Vector2(0, 0), Color.White);
// Draw double pendulum
DrawRectanglePro(new Rectangle(screenWidth / 2.0f, screenHeight / 2.0f - 100, 10 * l1, lineThick),
new Vector2(0, lineThick * 0.5f), 90 - RAD2DEG * theta1, Color.RayWhite);
Vector2 endpoint1 = CalculatePendulumEndPoint(l1, theta1);
DrawRectanglePro(new Rectangle(screenWidth / 2.0f + endpoint1.X, screenHeight / 2.0f - 100 + endpoint1.Y, 10 * l2, lineThick),
new Vector2(0, lineThick * 0.5f), 90 - RAD2DEG * theta2, Color.RayWhite);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadRenderTexture(target);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
SetConfigFlags(ConfigFlags.HighDpiWindow);
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - double pendulum");
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new DoublePendulum();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -75,11 +75,11 @@ public partial class DrawCircleSector : IExample
// Draw GUI controls
//------------------------------------------------------------------------------
/*GuiSliderBar(new Rectangle( 600, 40, 120, 20), "StartAngle", TextFormat("%.2f", startAngle), ref startAngle, 0, 720);
GuiSliderBar(new Rectangle( 600, 70, 120, 20), "EndAngle", TextFormat("%.2f", endAngle), ref endAngle, 0, 720);
GuiSliderBar(new Rectangle(600, 40, 120, 20), "StartAngle", $"{startAngle:F2}", ref startAngle, 0, 720);
GuiSliderBar(new Rectangle(600, 70, 120, 20), "EndAngle", $"{endAngle:F2}", ref endAngle, 0, 720);
GuiSliderBar(new Rectangle( 600, 140, 120, 20), "Radius", TextFormat("%.2f", outerRadius), ref outerRadius, 0, 200);
GuiSliderBar(new Rectangle( 600, 170, 120, 20), "Segments", TextFormat("%.2f", segments), ref segments, 0, 100);*/
GuiSliderBar(new Rectangle(600, 140, 120, 20), "Radius", $"{outerRadius:F2}", ref outerRadius, 0, 200);
GuiSliderBar(new Rectangle(600, 170, 120, 20), "Segments", $"{segments:F2}", ref segments, 0, 100);
//------------------------------------------------------------------------------
minSegments = MathF.Truncate(MathF.Ceiling((endAngle - startAngle) / 90));
@ -96,6 +96,28 @@ public partial class DrawCircleSector : IExample
{
}
//----------------------------------------------------------------------------------
// Minimal raygui-like widgets (plain raylib re-implementation)
//----------------------------------------------------------------------------------
private static void GuiSliderBar(Rectangle bounds, string textLeft, string textRight, ref float value, float minValue, float maxValue)
{
Vector2 mouse = GetMousePosition();
bool hover = CheckCollisionPointRec(mouse, bounds);
if (hover && IsMouseButtonDown(MouseButton.Left))
{
value = minValue + ((mouse.X - bounds.X) / bounds.Width) * (maxValue - minValue);
if (value < minValue) value = minValue;
if (value > maxValue) value = maxValue;
}
DrawRectangleRec(bounds, Color.LightGray);
float pct = (value - minValue) / (maxValue - minValue);
DrawRectangleRec(new Rectangle(bounds.X, bounds.Y, bounds.Width * pct, bounds.Height), Color.SkyBlue);
DrawRectangleLinesEx(bounds, 1, Color.Gray);
if (!string.IsNullOrEmpty(textLeft)) DrawText(textLeft, (int)bounds.X - MeasureText(textLeft, 10) - 5, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
if (!string.IsNullOrEmpty(textRight)) DrawText(textRight, (int)(bounds.X + bounds.Width + 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
public static int Main()
{
// Initialization

View file

@ -86,15 +86,15 @@ public partial class DrawRectangleRounded : IExample
// Draw GUI controls
//------------------------------------------------------------------------------
/*GuiSliderBar(new Rectangle( 640, 40, 105, 20 ), "Width", TextFormat("%.2f", width), ref width, 0, (float)GetScreenWidth() - 300);
GuiSliderBar(new Rectangle( 640, 70, 105, 20 ), "Height", TextFormat("%.2f", height), ref height, 0, (float)GetScreenHeight() - 50);
GuiSliderBar(new Rectangle( 640, 140, 105, 20 ), "Roundness", TextFormat("%.2f", roundness), ref roundness, 0.0f, 1.0f);
GuiSliderBar(new Rectangle( 640, 170, 105, 20 ), "Thickness", TextFormat("%.2f", lineThick), ref lineThick, 0, 20);
GuiSliderBar(new Rectangle( 640, 240, 105, 20), "Segments", TextFormat("%.2f", segments), ref segments, 0, 60);
GuiSliderBar(new Rectangle(640, 40, 105, 20), "Width", $"{width:F2}", ref width, 0, (float)GetScreenWidth() - 300);
GuiSliderBar(new Rectangle(640, 70, 105, 20), "Height", $"{height:F2}", ref height, 0, (float)GetScreenHeight() - 50);
GuiSliderBar(new Rectangle(640, 140, 105, 20), "Roundness", $"{roundness:F2}", ref roundness, 0.0f, 1.0f);
GuiSliderBar(new Rectangle(640, 170, 105, 20), "Thickness", $"{lineThick:F2}", ref lineThick, 0, 20);
GuiSliderBar(new Rectangle(640, 240, 105, 20), "Segments", $"{segments:F2}", ref segments, 0, 60);
GuiCheckBox(new Rectangle( 640, 320, 20, 20 ), "DrawRoundedRect", ref drawRoundedRect);
GuiCheckBox(new Rectangle( 640, 350, 20, 20 ), "DrawRoundedLines", ref drawRoundedLines);
GuiCheckBox(new Rectangle( 640, 380, 20, 20), "DrawRect", ref drawRect);*/
GuiCheckBox(new Rectangle(640, 320, 20, 20), "DrawRoundedRect", ref drawRoundedRect);
GuiCheckBox(new Rectangle(640, 350, 20, 20), "DrawRoundedLines", ref drawRoundedLines);
GuiCheckBox(new Rectangle(640, 380, 20, 20), "DrawRect", ref drawRect);
//------------------------------------------------------------------------------
var text = $"MODE: {((segments >= 4) ? "MANUAL" : "AUTO")}";
@ -109,6 +109,39 @@ public partial class DrawRectangleRounded : IExample
{
}
//----------------------------------------------------------------------------------
// Minimal raygui-like widgets (plain raylib re-implementation)
//----------------------------------------------------------------------------------
private static void GuiSliderBar(Rectangle bounds, string textLeft, string textRight, ref float value, float minValue, float maxValue)
{
Vector2 mouse = GetMousePosition();
bool hover = CheckCollisionPointRec(mouse, bounds);
if (hover && IsMouseButtonDown(MouseButton.Left))
{
value = minValue + ((mouse.X - bounds.X) / bounds.Width) * (maxValue - minValue);
if (value < minValue) value = minValue;
if (value > maxValue) value = maxValue;
}
DrawRectangleRec(bounds, Color.LightGray);
float pct = (value - minValue) / (maxValue - minValue);
DrawRectangleRec(new Rectangle(bounds.X, bounds.Y, bounds.Width * pct, bounds.Height), Color.SkyBlue);
DrawRectangleLinesEx(bounds, 1, Color.Gray);
if (!string.IsNullOrEmpty(textLeft)) DrawText(textLeft, (int)bounds.X - MeasureText(textLeft, 10) - 5, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
if (!string.IsNullOrEmpty(textRight)) DrawText(textRight, (int)(bounds.X + bounds.Width + 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
private static void GuiCheckBox(Rectangle bounds, string text, ref bool active)
{
Vector2 mouse = GetMousePosition();
bool hover = CheckCollisionPointRec(mouse, bounds);
if (hover && IsMouseButtonPressed(MouseButton.Left)) active = !active;
DrawRectangleLinesEx(bounds, 1, hover ? Color.DarkBlue : Color.Gray);
if (active) DrawRectangle((int)bounds.X + 4, (int)bounds.Y + 4, (int)bounds.Width - 8, (int)bounds.Height - 8, Color.DarkGray);
if (text != null) DrawText(text, (int)(bounds.X + bounds.Width + 8), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
public static int Main()
{
// Initialization

View file

@ -112,17 +112,17 @@ public partial class DrawRing : IExample
// Draw GUI controls
//------------------------------------------------------------------------------
/*GuiSliderBar(new Rectangle( 600, 40, 120, 20 ), "StartAngle", TextFormat("%.2f", startAngle), ref startAngle, -450, 450);
GuiSliderBar(new Rectangle( 600, 70, 120, 20 ), "EndAngle", TextFormat("%.2f", endAngle), ref endAngle, -450, 450);
GuiSliderBar(new Rectangle(600, 40, 120, 20), "StartAngle", $"{startAngle:F2}", ref startAngle, -450, 450);
GuiSliderBar(new Rectangle(600, 70, 120, 20), "EndAngle", $"{endAngle:F2}", ref endAngle, -450, 450);
GuiSliderBar(new Rectangle( 600, 140, 120, 20 ), "InnerRadius", TextFormat("%.2f", innerRadius), ref innerRadius, 0, 100);
GuiSliderBar(new Rectangle( 600, 170, 120, 20 ), "OuterRadius", TextFormat("%.2f", outerRadius), ref outerRadius, 0, 200);
GuiSliderBar(new Rectangle(600, 140, 120, 20), "InnerRadius", $"{innerRadius:F2}", ref innerRadius, 0, 100);
GuiSliderBar(new Rectangle(600, 170, 120, 20), "OuterRadius", $"{outerRadius:F2}", ref outerRadius, 0, 200);
GuiSliderBar(new Rectangle( 600, 240, 120, 20 ), "Segments", TextFormat("%.2f", segments), ref segments, 0, 100);
GuiSliderBar(new Rectangle(600, 240, 120, 20), "Segments", $"{segments:F2}", ref segments, 0, 100);
GuiCheckBox(new Rectangle( 600, 320, 20, 20 ), "Draw Ring", ref drawRing);
GuiCheckBox(new Rectangle( 600, 350, 20, 20 ), "Draw RingLines", ref drawRingLines);
GuiCheckBox(new Rectangle( 600, 380, 20, 20 ), "Draw CircleLines", ref drawCircleLines);*/
GuiCheckBox(new Rectangle(600, 320, 20, 20), "Draw Ring", ref drawRing);
GuiCheckBox(new Rectangle(600, 350, 20, 20), "Draw RingLines", ref drawRingLines);
GuiCheckBox(new Rectangle(600, 380, 20, 20), "Draw CircleLines", ref drawCircleLines);
//------------------------------------------------------------------------------
var minSegments = (int)MathF.Ceiling((endAngle - startAngle) / 90);
@ -139,6 +139,39 @@ public partial class DrawRing : IExample
{
}
//----------------------------------------------------------------------------------
// Minimal raygui-like widgets (plain raylib re-implementation)
//----------------------------------------------------------------------------------
private static void GuiSliderBar(Rectangle bounds, string textLeft, string textRight, ref float value, float minValue, float maxValue)
{
Vector2 mouse = GetMousePosition();
bool hover = CheckCollisionPointRec(mouse, bounds);
if (hover && IsMouseButtonDown(MouseButton.Left))
{
value = minValue + ((mouse.X - bounds.X) / bounds.Width) * (maxValue - minValue);
if (value < minValue) value = minValue;
if (value > maxValue) value = maxValue;
}
DrawRectangleRec(bounds, Color.LightGray);
float pct = (value - minValue) / (maxValue - minValue);
DrawRectangleRec(new Rectangle(bounds.X, bounds.Y, bounds.Width * pct, bounds.Height), Color.SkyBlue);
DrawRectangleLinesEx(bounds, 1, Color.Gray);
if (!string.IsNullOrEmpty(textLeft)) DrawText(textLeft, (int)bounds.X - MeasureText(textLeft, 10) - 5, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
if (!string.IsNullOrEmpty(textRight)) DrawText(textRight, (int)(bounds.X + bounds.Width + 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
private static void GuiCheckBox(Rectangle bounds, string text, ref bool active)
{
Vector2 mouse = GetMousePosition();
bool hover = CheckCollisionPointRec(mouse, bounds);
if (hover && IsMouseButtonPressed(MouseButton.Left)) active = !active;
DrawRectangleLinesEx(bounds, 1, hover ? Color.DarkBlue : Color.Gray);
if (active) DrawRectangle((int)bounds.X + 4, (int)bounds.Y + 4, (int)bounds.Width - 8, (int)bounds.Height - 8, Color.DarkGray);
if (text != null) DrawText(text, (int)(bounds.X + bounds.Width + 8), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
public static int Main()
{
// Initialization

View file

@ -0,0 +1,230 @@
/*******************************************************************************************
*
* raylib [shapes] example - easings testbed
*
* Example complexity rating: [] 3/4
*
* Example originally created with raylib 2.5, last time updated with raylib 2.5
*
* Example contributed by Juan Miguel López (@flashback-fx) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2019-2025 Juan Miguel López (@flashback-fx) and Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using System;
using System.Numerics;
using Examples.Shared;
using static Raylib_cs.Raylib;
namespace Examples.Shapes;
public partial class EasingsTestbed : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int FONT_SIZE = 20;
private const float D_STEP = 20.0f;
private const float D_STEP_FINE = 2.0f;
private const float D_MIN = 1.0f;
private const float D_MAX = 10000.0f;
public string Name => "Shapes / Easings Testbed";
public string Title => "raylib [shapes] example - easings testbed";
// Easing types
private const int EASE_LINEAR_NONE = 0;
private const int NUM_EASING_TYPES = 27;
private const int EASING_NONE = NUM_EASING_TYPES;
// NoEase function, used when "no easing" is selected for any axis
// It just ignores all parameters besides b
private static float NoEase(float t, float b, float c, float d)
{
// Hack to avoid compiler warning (about unused variables)
float burn = t + b + c + d;
d += burn;
return b;
}
// Easing functions reference data
private string[] easingNames;
private Func<float, float, float, float, float>[] easingFuncs;
private Vector2 ballPosition;
private float t; // Current time (in any unit measure, but same unit as duration)
private float d; // Total time it should take to complete (duration)
private bool paused;
private bool boundedT; // If true, t will stop when d >= td, otherwise t will keep adding td to its value every loop
private int easingX; // Easing selected for x axis
private int easingY; // Easing selected for y axis
public void Init()
{
easingNames = new string[]
{
"EaseLinearNone", "EaseLinearIn", "EaseLinearOut", "EaseLinearInOut",
"EaseSineIn", "EaseSineOut", "EaseSineInOut",
"EaseCircIn", "EaseCircOut", "EaseCircInOut",
"EaseCubicIn", "EaseCubicOut", "EaseCubicInOut",
"EaseQuadIn", "EaseQuadOut", "EaseQuadInOut",
"EaseExpoIn", "EaseExpoOut", "EaseExpoInOut",
"EaseBackIn", "EaseBackOut", "EaseBackInOut",
"EaseBounceOut", "EaseBounceIn", "EaseBounceInOut",
"EaseElasticIn", "EaseElasticOut", "EaseElasticInOut",
"None",
};
easingFuncs = new Func<float, float, float, float, float>[]
{
Easings.EaseLinearNone, Easings.EaseLinearIn, Easings.EaseLinearOut, Easings.EaseLinearInOut,
Easings.EaseSineIn, Easings.EaseSineOut, Easings.EaseSineInOut,
Easings.EaseCircIn, Easings.EaseCircOut, Easings.EaseCircInOut,
Easings.EaseCubicIn, Easings.EaseCubicOut, Easings.EaseCubicInOut,
Easings.EaseQuadIn, Easings.EaseQuadOut, Easings.EaseQuadInOut,
Easings.EaseExpoIn, Easings.EaseExpoOut, Easings.EaseExpoInOut,
Easings.EaseBackIn, Easings.EaseBackOut, Easings.EaseBackInOut,
Easings.EaseBounceOut, Easings.EaseBounceIn, Easings.EaseBounceInOut,
Easings.EaseElasticIn, Easings.EaseElasticOut, Easings.EaseElasticInOut,
NoEase,
};
ballPosition = new Vector2(100.0f, 100.0f);
t = 0.0f;
d = 300.0f;
paused = true;
boundedT = true;
easingX = EASING_NONE;
easingY = EASING_NONE;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
if (IsKeyPressed(KeyboardKey.T)) boundedT = !boundedT;
// Choose easing for the X axis
if (IsKeyPressed(KeyboardKey.Right))
{
easingX++;
if (easingX > EASING_NONE) easingX = 0;
}
else if (IsKeyPressed(KeyboardKey.Left))
{
if (easingX == 0) easingX = EASING_NONE;
else easingX--;
}
// Choose easing for the Y axis
if (IsKeyPressed(KeyboardKey.Down))
{
easingY++;
if (easingY > EASING_NONE) easingY = 0;
}
else if (IsKeyPressed(KeyboardKey.Up))
{
if (easingY == 0) easingY = EASING_NONE;
else easingY--;
}
// Change d (duration) value
if (IsKeyPressed(KeyboardKey.W) && (d < D_MAX - D_STEP)) d += D_STEP;
else if (IsKeyPressed(KeyboardKey.Q) && (d > D_MIN + D_STEP)) d -= D_STEP;
if (IsKeyDown(KeyboardKey.S) && (d < D_MAX - D_STEP_FINE)) d += D_STEP_FINE;
else if (IsKeyDown(KeyboardKey.A) && (d > D_MIN + D_STEP_FINE)) d -= D_STEP_FINE;
// Play, pause and restart controls
if (IsKeyPressed(KeyboardKey.Space) || IsKeyPressed(KeyboardKey.T) ||
IsKeyPressed(KeyboardKey.Right) || IsKeyPressed(KeyboardKey.Left) ||
IsKeyPressed(KeyboardKey.Down) || IsKeyPressed(KeyboardKey.Up) ||
IsKeyPressed(KeyboardKey.W) || IsKeyPressed(KeyboardKey.Q) ||
IsKeyDown(KeyboardKey.S) || IsKeyDown(KeyboardKey.A) ||
(IsKeyPressed(KeyboardKey.Enter) && (boundedT == true) && (t >= d)))
{
t = 0.0f;
ballPosition.X = 100.0f;
ballPosition.Y = 100.0f;
paused = true;
}
if (IsKeyPressed(KeyboardKey.Enter)) paused = !paused;
// Movement computation
if (!paused && ((boundedT && t < d) || !boundedT))
{
ballPosition.X = easingFuncs[easingX](t, 100.0f, 700.0f - 170.0f, d);
ballPosition.Y = easingFuncs[easingY](t, 100.0f, 400.0f - 170.0f, d);
t += 1.0f;
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
// Draw information text
DrawText($"Easing x: {easingNames[easingX]}", 20, FONT_SIZE, FONT_SIZE, Color.LightGray);
DrawText($"Easing y: {easingNames[easingY]}", 20, FONT_SIZE * 2, FONT_SIZE, Color.LightGray);
DrawText($"t ({(boundedT == true ? 'b' : 'u')}) = {t:F2} d = {d:F2}", 20, FONT_SIZE * 3, FONT_SIZE, Color.LightGray);
// Draw instructions text
DrawText("Use ENTER to play or pause movement, use SPACE to restart", 20, GetScreenHeight() - FONT_SIZE * 2, FONT_SIZE, Color.LightGray);
DrawText("Use Q and W or A and S keys to change duration", 20, GetScreenHeight() - FONT_SIZE * 3, FONT_SIZE, Color.LightGray);
DrawText("Use LEFT or RIGHT keys to choose easing for the x axis", 20, GetScreenHeight() - FONT_SIZE * 4, FONT_SIZE, Color.LightGray);
DrawText("Use UP or DOWN keys to choose easing for the y axis", 20, GetScreenHeight() - FONT_SIZE * 5, FONT_SIZE, Color.LightGray);
// Draw ball
DrawCircleV(ballPosition, 16.0f, Color.Maroon);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - easings testbed");
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new EasingsTestbed();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow();
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,195 @@
/*******************************************************************************************
*
* raylib [shapes] example - ellipse collision
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 5.5, last time updated with raylib 5.5
*
* Example contributed by Ziya (@Monjaris)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Ziya (@Monjaris)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Shapes;
public partial class EllipseCollision : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Shapes / Ellipse Collision";
public string Title => "raylib [shapes] example - collision ellipses";
private Vector2 ellipseACenter;
private float ellipseARx;
private float ellipseARy;
private Vector2 ellipseBCenter;
private float ellipseBRx;
private float ellipseBRy;
// 0 = controlling A, 1 = controlling B
private int controlled;
// Check if point is inside ellipse
private static bool CheckCollisionPointEllipse(Vector2 point, Vector2 center, float rx, float ry)
{
float dx = (point.X - center.X) / rx;
float dy = (point.Y - center.Y) / ry;
return (dx * dx + dy * dy) <= 1.0f;
}
// Check if two ellipses collide
// Uses radial boundary distance in the direction between centers — scales correctly with radii
private static bool CheckCollisionEllipses(Vector2 c1, float rx1, float ry1, Vector2 c2, float rx2, float ry2)
{
float dx = c2.X - c1.X;
float dy = c2.Y - c1.Y;
float dist = MathF.Sqrt(dx * dx + dy * dy);
// Ellipses are on top of each other
if (dist == 0.0f)
{
return true;
}
float theta = MathF.Atan2(dy, dx);
float cosT = MathF.Cos(theta);
float sinT = MathF.Sin(theta);
// Radial distance from center to ellipse boundary in direction theta
// r(theta) = (rx * ry) / sqrt((ry*cos)^2 + (rx*sin)^2)
float r1 = (rx1 * ry1) / MathF.Sqrt((ry1 * cosT) * (ry1 * cosT) + (rx1 * sinT) * (rx1 * sinT));
float r2 = (rx2 * ry2) / MathF.Sqrt((ry2 * cosT) * (ry2 * cosT) + (rx2 * sinT) * (rx2 * sinT));
return dist <= (r1 + r2);
}
public void Init()
{
ellipseACenter = new((float)screenWidth / 4, (float)screenHeight / 2);
ellipseARx = 120.0f;
ellipseARy = 70.0f;
ellipseBCenter = new((float)screenWidth * 3 / 4, (float)screenHeight / 2);
ellipseBRx = 90.0f;
ellipseBRy = 140.0f;
// 0 = controlling A, 1 = controlling B
controlled = 0;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
if (IsKeyPressed(KeyboardKey.A))
{
controlled = 0;
}
if (IsKeyPressed(KeyboardKey.B))
{
controlled = 1;
}
if (controlled == 0)
{
ellipseACenter = GetMousePosition();
}
else
{
ellipseBCenter = GetMousePosition();
}
bool ellipsesCollide = CheckCollisionEllipses(
ellipseACenter, ellipseARx, ellipseARy,
ellipseBCenter, ellipseBRx, ellipseBRy
);
bool mouseInA = CheckCollisionPointEllipse(GetMousePosition(), ellipseACenter, ellipseARx, ellipseARy);
bool mouseInB = CheckCollisionPointEllipse(GetMousePosition(), ellipseBCenter, ellipseBRx, ellipseBRy);
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
DrawEllipse((int)ellipseACenter.X, (int)ellipseACenter.Y, ellipseARx, ellipseARy, ellipsesCollide ? Color.Red : Color.Blue);
DrawEllipse((int)ellipseBCenter.X, (int)ellipseBCenter.Y, ellipseBRx, ellipseBRy, ellipsesCollide ? Color.Red : Color.Green);
DrawEllipseLines((int)ellipseACenter.X, (int)ellipseACenter.Y, ellipseARx, ellipseARy, Color.White);
DrawEllipseLines((int)ellipseBCenter.X, (int)ellipseBCenter.Y, ellipseBRx, ellipseBRy, Color.White);
DrawCircleV(ellipseACenter, 4, Color.White);
DrawCircleV(ellipseBCenter, 4, Color.White);
if (ellipsesCollide)
{
DrawText("ELLIPSES COLLIDE", screenWidth / 2 - 120, 40, 28, Color.Red);
}
else
{
DrawText("NO COLLISION", screenWidth / 2 - 80, 40, 28, Color.DarkGray);
}
DrawText(controlled == 0 ? "Controlling: A" : "Controlling: B", 20, screenHeight - 40, 20, Color.Yellow);
if (mouseInA && controlled != 0)
{
DrawText("Mouse inside ellipse A", 20, screenHeight - 70, 20, Color.Blue);
}
if (mouseInB && controlled != 1)
{
DrawText("Mouse inside ellipse B", 20, screenHeight - 70, 20, Color.Green);
}
DrawText("Press [A] or [B] to switch control", 20, 20, 20, Color.Gray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - collision ellipses");
SetTargetFPS(60);
var game = new EllipseCollision();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow();
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,270 @@
/*******************************************************************************************
*
* raylib [shapes] example - hilbert curve
*
* Example complexity rating: [] 3/4
*
* Example originally created with raylib 5.6, last time updated with raylib 5.6
*
* Example contributed by Hamza RAHAL (@hmz-rhl) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Hamza RAHAL (@hmz-rhl)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Shapes;
public partial class HilbertCurve : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Shapes / Hilbert Curve";
public string Title => "raylib [shapes] example - hilbert curve";
private int order;
private float size;
private int strokeCount;
private Vector2[] hilbertPath;
private int prevOrder;
private int prevSize; // NOTE: Size from slider is float but for comparison we use int
private int counter;
private float thick;
private bool animate;
public void Init()
{
order = 2;
size = (float)GetScreenHeight();
strokeCount = 0;
hilbertPath = LoadHilbertPath(order, size, out strokeCount);
prevOrder = order;
prevSize = (int)size;
counter = 0;
thick = 2.0f;
animate = true;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
// Check if order or size have changed to regenerate
// NOTE: Size from slider is float but for comparison we use int
if ((prevOrder != order) || (prevSize != (int)size))
{
hilbertPath = LoadHilbertPath(order, size, out strokeCount);
if (animate) counter = 0;
else counter = strokeCount;
prevOrder = order;
prevSize = (int)size;
}
//----------------------------------------------------------------------------------
// Draw
//--------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
if (counter < strokeCount)
{
// Draw Hilbert path animation, one stroke every frame
for (int i = 1; i <= counter; i++)
{
DrawLineEx(hilbertPath[i], hilbertPath[i - 1], thick, ColorFromHSV(((float)i / strokeCount) * 360.0f, 1.0f, 1.0f));
}
counter += 1;
}
else
{
// Draw full Hilbert path
for (int i = 1; i < strokeCount; i++)
{
DrawLineEx(hilbertPath[i], hilbertPath[i - 1], thick, ColorFromHSV(((float)i / strokeCount) * 360.0f, 1.0f, 1.0f));
}
}
// Draw UI (minimal raygui-like controls, raygui is not bound in raylib-cs)
GuiCheckBox(new Rectangle(450, 50, 20, 20), "ANIMATE GENERATION ON CHANGE", ref animate);
GuiSpinner(new Rectangle(585, 100, 180, 30), "HILBERT CURVE ORDER: ", ref order, 2, 8);
GuiSlider(new Rectangle(524, 150, 240, 24), "THICKNESS: ", null, ref thick, 1.0f, 10.0f);
GuiSlider(new Rectangle(524, 190, 240, 24), "TOTAL SIZE: ", null, ref size, 10.0f, GetScreenHeight() * 1.5f);
EndDrawing();
//--------------------------------------------------------------------------
}
public void Unload()
{
}
//------------------------------------------------------------------------------------
// Module Functions Definition
//------------------------------------------------------------------------------------
// Load the whole Hilbert Path (including each U and their link)
private static Vector2[] LoadHilbertPath(int order, float size, out int strokeCount)
{
int N = 1 << order;
float len = size / N;
strokeCount = N * N;
Vector2[] hilbertPath = new Vector2[strokeCount];
for (int i = 0; i < strokeCount; i++)
{
hilbertPath[i] = ComputeHilbertStep(order, i);
hilbertPath[i].X = hilbertPath[i].X * len + len / 2.0f;
hilbertPath[i].Y = hilbertPath[i].Y * len + len / 2.0f;
}
return hilbertPath;
}
// Compute Hilbert path U positions
private static Vector2 ComputeHilbertStep(int order, int index)
{
// Hilbert points base pattern
Vector2[] hilbertPoints = new Vector2[4]
{
new Vector2(0, 0),
new Vector2(0, 1),
new Vector2(1, 1),
new Vector2(1, 0),
};
int hilbertIndex = index & 3;
Vector2 vect = hilbertPoints[hilbertIndex];
float temp = 0.0f;
int len = 0;
for (int j = 1; j < order; j++)
{
index = index >> 2;
hilbertIndex = index & 3;
len = 1 << j;
switch (hilbertIndex)
{
case 0:
{
temp = vect.X;
vect.X = vect.Y;
vect.Y = temp;
} break;
case 2:
{
vect.X += len;
vect.Y += len;
} break;
case 1: vect.Y += len; break;
case 3:
{
temp = len - 1 - vect.X;
vect.X = 2 * len - 1 - vect.Y;
vect.Y = temp;
} break;
default: break;
}
}
return vect;
}
//----------------------------------------------------------------------------------
// Minimal raygui-like widgets (plain raylib re-implementation)
//----------------------------------------------------------------------------------
private static void GuiCheckBox(Rectangle bounds, string text, ref bool active)
{
Vector2 mouse = GetMousePosition();
bool hover = CheckCollisionPointRec(mouse, bounds);
if (hover && IsMouseButtonPressed(MouseButton.Left)) active = !active;
DrawRectangleLinesEx(bounds, 1, hover ? Color.DarkBlue : Color.Gray);
if (active) DrawRectangle((int)bounds.X + 4, (int)bounds.Y + 4, (int)bounds.Width - 8, (int)bounds.Height - 8, Color.DarkGray);
if (text != null) DrawText(text, (int)(bounds.X + bounds.Width + 8), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
private static void GuiSpinner(Rectangle bounds, string text, ref int value, int minValue, int maxValue)
{
Vector2 mouse = GetMousePosition();
Rectangle left = new Rectangle(bounds.X, bounds.Y, bounds.Height, bounds.Height);
Rectangle right = new Rectangle(bounds.X + bounds.Width - bounds.Height, bounds.Y, bounds.Height, bounds.Height);
Rectangle mid = new Rectangle(bounds.X + bounds.Height, bounds.Y, bounds.Width - 2 * bounds.Height, bounds.Height);
if (CheckCollisionPointRec(mouse, left) && IsMouseButtonPressed(MouseButton.Left) && value > minValue) value--;
if (CheckCollisionPointRec(mouse, right) && IsMouseButtonPressed(MouseButton.Left) && value < maxValue) value++;
DrawRectangleRec(mid, Color.RayWhite);
DrawRectangleLinesEx(mid, 1, Color.Gray);
DrawRectangleRec(left, Color.LightGray);
DrawRectangleLinesEx(left, 1, Color.Gray);
DrawRectangleRec(right, Color.LightGray);
DrawRectangleLinesEx(right, 1, Color.Gray);
DrawText("-", (int)(left.X + left.Width / 2 - 2), (int)(left.Y + left.Height / 2 - 5), 10, Color.DarkGray);
DrawText("+", (int)(right.X + right.Width / 2 - 3), (int)(right.Y + right.Height / 2 - 5), 10, Color.DarkGray);
string vs = value.ToString();
DrawText(vs, (int)(mid.X + mid.Width / 2 - MeasureText(vs, 10) / 2), (int)(mid.Y + mid.Height / 2 - 5), 10, Color.DarkGray);
if (text != null) DrawText(text, (int)bounds.X - MeasureText(text, 10) - 5, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
private static void GuiSlider(Rectangle bounds, string textLeft, string textRight, ref float value, float minValue, float maxValue)
{
Vector2 mouse = GetMousePosition();
bool hover = CheckCollisionPointRec(mouse, bounds);
if (hover && IsMouseButtonDown(MouseButton.Left))
{
value = minValue + ((mouse.X - bounds.X) / bounds.Width) * (maxValue - minValue);
if (value < minValue) value = minValue;
if (value > maxValue) value = maxValue;
}
DrawRectangleRec(bounds, Color.LightGray);
float pct = (value - minValue) / (maxValue - minValue);
float handleX = bounds.X + pct * bounds.Width;
DrawRectangle((int)(handleX - 5), (int)bounds.Y, 10, (int)bounds.Height, Color.DarkGray);
DrawRectangleLinesEx(bounds, 1, Color.Gray);
if (textLeft != null) DrawText(textLeft, (int)bounds.X - MeasureText(textLeft, 10) - 5, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
if (textRight != null) DrawText(textRight, (int)(bounds.X + bounds.Width + 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - hilbert curve");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new HilbertCurve();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,224 @@
/*******************************************************************************************
*
* raylib [shapes] example - kaleidoscope
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 5.5, last time updated with raylib 5.6
*
* Example contributed by Hugo ARNAL (@hugoarnal) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Hugo ARNAL (@hugoarnal) and Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Raymath;
namespace Examples.Shapes;
public partial class Kaleidoscope : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int MAX_DRAW_LINES = 8192;
// Line data type
private struct Line
{
public Vector2 Start;
public Vector2 End;
}
public string Name => "Shapes / Kaleidoscope";
public string Title => "raylib [shapes] example - kaleidoscope";
public int TargetFps => 20;
// Lines array as a global static variable to be stored
// in heap and avoid potential stack overflow (on Web platform)
private Line[] lines;
// Line drawing properties
private int symmetry;
private float angle;
private float thickness;
private Rectangle resetButtonRec;
private Rectangle backButtonRec;
private Rectangle nextButtonRec;
private Vector2 mousePos;
private Vector2 prevMousePos;
private Vector2 scaleVector;
private Vector2 offset;
private Camera2D camera;
private int currentLineCounter;
private int totalLineCounter;
private bool resetButtonClicked;
private bool backButtonClicked;
private bool nextButtonClicked;
public void Init()
{
lines = new Line[MAX_DRAW_LINES];
// Line drawing properties
symmetry = 6;
angle = 360.0f / (float)symmetry;
thickness = 3.0f;
resetButtonRec = new(screenWidth - 55.0f, 5.0f, 50, 25);
backButtonRec = new(screenWidth - 55.0f, screenHeight - 30.0f, 25, 25);
nextButtonRec = new(screenWidth - 30.0f, screenHeight - 30.0f, 25, 25);
mousePos = new(0, 0);
prevMousePos = new(0, 0);
scaleVector = new(1.0f, -1.0f);
offset = new((float)screenWidth / 2.0f, (float)screenHeight / 2.0f);
camera = new();
camera.Target = new(0, 0);
camera.Offset = offset;
camera.Rotation = 0.0f;
camera.Zoom = 1.0f;
currentLineCounter = 0;
totalLineCounter = 0;
resetButtonClicked = false;
backButtonClicked = false;
nextButtonClicked = false;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
prevMousePos = mousePos;
mousePos = GetMousePosition();
Vector2 lineStart = Vector2Subtract(mousePos, offset);
Vector2 lineEnd = Vector2Subtract(prevMousePos, offset);
if (
IsMouseButtonDown(MouseButton.Left)
&& !CheckCollisionPointRec(mousePos, resetButtonRec)
&& !CheckCollisionPointRec(mousePos, backButtonRec)
&& !CheckCollisionPointRec(mousePos, nextButtonRec)
)
{
for (int s = 0; (s < symmetry) && (totalLineCounter < (MAX_DRAW_LINES - 1)); s++)
{
lineStart = Vector2Rotate(lineStart, angle * DEG2RAD);
lineEnd = Vector2Rotate(lineEnd, angle * DEG2RAD);
// Store mouse line
lines[totalLineCounter].Start = lineStart;
lines[totalLineCounter].End = lineEnd;
// Store reflective line
lines[totalLineCounter + 1].Start = Vector2Multiply(lineStart, scaleVector);
lines[totalLineCounter + 1].End = Vector2Multiply(lineEnd, scaleVector);
totalLineCounter += 2;
currentLineCounter = totalLineCounter;
}
}
if (resetButtonClicked)
{
Array.Clear(lines, 0, MAX_DRAW_LINES);
currentLineCounter = 0;
totalLineCounter = 0;
}
if (backButtonClicked && (currentLineCounter > 0))
{
currentLineCounter -= 1;
}
if (nextButtonClicked && (currentLineCounter < MAX_DRAW_LINES) && ((currentLineCounter + 1) <= totalLineCounter))
{
currentLineCounter += 1;
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode2D(camera);
for (int s = 0; s < symmetry; s++)
{
for (int i = 0; i < currentLineCounter; i += 2)
{
DrawLineEx(lines[i].Start, lines[i].End, thickness, Color.Black);
DrawLineEx(lines[i + 1].Start, lines[i + 1].End, thickness, Color.Black);
}
}
EndMode2D();
// NOTE: raygui is not bound in raylib-cs, so the on-screen back/next/reset
// controls are unavailable; drawing with the mouse still works.
//------------------------------------------------------------------------------
/*
if ((currentLineCounter - 1) < 0) GuiDisable();
backButtonClicked = GuiButton(backButtonRec, "<");
GuiEnable();
if ((currentLineCounter + 1) > totalLineCounter) GuiDisable();
nextButtonClicked = GuiButton(nextButtonRec, ">");
GuiEnable();
resetButtonClicked = GuiButton(resetButtonRec, "Reset");
*/
//------------------------------------------------------------------------------
DrawText($"LINES: {currentLineCounter}/{MAX_DRAW_LINES}", 10, screenHeight - 30, 20, Color.Maroon);
DrawFPS(10, 10);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - kaleidoscope");
SetTargetFPS(20);
//--------------------------------------------------------------------------------------
var game = new Kaleidoscope();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,173 @@
/*******************************************************************************************
*
* raylib [shapes] example - lines drawing
*
* Example complexity rating: [] 1/4
*
* Example originally created with raylib 6.0, last time updated with raylib 5.6
*
* Example contributed by Robin (@RobinsAviary) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Robin (@RobinsAviary)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Raymath;
namespace Examples.Shapes;
public partial class LinesDrawing : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Shapes / Lines Drawing";
public string Title => "raylib [shapes] example - lines drawing";
// Hint text that shows before you click the screen
private bool startText;
// The mouse's position on the previous frame
private Vector2 mousePositionPrevious;
// The canvas to draw lines on
private RenderTexture2D canvas;
// The line's thickness
private float lineThickness;
// The lines hue (in HSV, from 0-360)
private float lineHue;
public void Init()
{
// Hint text that shows before you click the screen
startText = true;
// The mouse's position on the previous frame
mousePositionPrevious = GetMousePosition();
// The canvas to draw lines on
canvas = LoadRenderTexture(screenWidth, screenHeight);
// The line's thickness
lineThickness = 8.0f;
// The lines hue (in HSV, from 0-360)
lineHue = 0.0f;
// Clear the canvas to the background color
BeginTextureMode(canvas);
ClearBackground(Color.RayWhite);
EndTextureMode();
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
// Disable the hint text once the user clicks
if (IsMouseButtonPressed(MouseButton.Left) && startText) startText = false;
// Clear the canvas when the user middle-clicks
if (IsMouseButtonPressed(MouseButton.Middle))
{
BeginTextureMode(canvas);
ClearBackground(Color.RayWhite);
EndTextureMode();
}
// Store whether the left and right buttons are down
bool leftButtonDown = IsMouseButtonDown(MouseButton.Left);
bool rightButtonDown = IsMouseButtonDown(MouseButton.Right);
if (leftButtonDown || rightButtonDown)
{
// The color for the line
Color drawColor = Color.White;
if (leftButtonDown)
{
// Increase the hue value by the distance our cursor has moved since the last frame (divided by 3)
lineHue += Vector2Distance(mousePositionPrevious, GetMousePosition())/3.0f;
// While the hue is >=360, subtract it to bring it down into the range 0-360
// This is more visually accurate than resetting to zero
while (lineHue >= 360.0f) lineHue -= 360.0f;
// Create the final color
drawColor = ColorFromHSV(lineHue, 1.0f, 1.0f);
}
else if (rightButtonDown) drawColor = Color.RayWhite; // Use the background color as an "eraser"
// Draw the line onto the canvas
BeginTextureMode(canvas);
// Circles act as "caps", smoothing corners
DrawCircleV(mousePositionPrevious, lineThickness/2.0f, drawColor);
DrawCircleV(GetMousePosition(), lineThickness/2.0f, drawColor);
DrawLineEx(mousePositionPrevious, GetMousePosition(), lineThickness, drawColor);
EndTextureMode();
}
// Update line thickness based on mousewheel
lineThickness += GetMouseWheelMove();
lineThickness = Clamp(lineThickness, 1.0f, 500.0f);
// Update mouse's previous position
mousePositionPrevious = GetMousePosition();
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
// Draw the render texture to the screen, flipped vertically to make it appear top-side up
DrawTextureRec(canvas.Texture, new Rectangle(0.0f, 0.0f, (float)canvas.Texture.Width, (float)-canvas.Texture.Height), Vector2Zero(), Color.White);
// Draw the preview circle
if (!leftButtonDown) DrawCircleLinesV(GetMousePosition(), lineThickness/2.0f, new Color(127, 127, 127, 127));
// Draw the hint text
if (startText) DrawText("try clicking and dragging!", 275, 215, 20, Color.LightGray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadRenderTexture(canvas); // Unload the canvas render texture
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - lines drawing");
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new LinesDrawing();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,141 @@
/*******************************************************************************************
*
* raylib [shapes] example - math angle rotation
*
* Example complexity rating: [] 1/4
*
* Example originally created with raylib 6.0, last time updated with raylib 5.6
*
* Example contributed by Kris (@krispy-snacc) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Kris (@krispy-snacc)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Shapes;
public partial class MathAngleRotation : IExample
{
private const int screenWidth = 720;
private const int screenHeight = 400;
public string Name => "Shapes / Math Angle Rotation";
public string Title => "raylib [shapes] example - math angle rotation";
public int Width => screenWidth;
public int Height => screenHeight;
private Vector2 center;
private const float lineLength = 150.0f;
// Predefined angles for fixed lines
private int[] angles;
private int numAngles;
private float totalAngle; // Animated rotation angle
public void Init()
{
center = new Vector2(screenWidth/2.0f, screenHeight/2.0f);
// Predefined angles for fixed lines
angles = new[] { 0, 30, 60, 90 };
numAngles = angles.Length;
totalAngle = 0.0f; // Animated rotation angle
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
totalAngle += 1.0f; // degrees per frame
if (totalAngle >= 360.0f) totalAngle -= 360.0f;
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.White);
DrawText("Fixed angles + rotating line", 10, 10, 20, Color.LightGray);
// Draw fixed-angle lines with colorful gradient
for (int i = 0; i < numAngles; i++)
{
float rad = angles[i]*DEG2RAD;
Vector2 end = new Vector2(center.X + MathF.Cos(rad)*lineLength,
center.Y + MathF.Sin(rad)*lineLength);
// Gradient color from green → cyan → blue → magenta
Color col;
switch(i)
{
case 0: col = Color.Green; break;
case 1: col = Color.Orange; break;
case 2: col = Color.Blue; break;
case 3: col = Color.Magenta; break;
default: col = Color.White; break;
}
DrawLineEx(center, end, 5.0f, col);
// Draw angle label slightly offset along the line
Vector2 textPos = new Vector2(center.X + MathF.Cos(rad)*(lineLength + 20),
center.Y + MathF.Sin(rad)*(lineLength + 20));
DrawText($"{angles[i]}°", (int)textPos.X, (int)textPos.Y, 20, col);
}
// Draw animated rotating line with changing color
float animRad = totalAngle*DEG2RAD;
Vector2 animEnd = new Vector2(center.X + MathF.Cos(animRad)*lineLength,
center.Y + MathF.Sin(animRad)*lineLength);
// Cycle through HSV colors for animated line
Color animCol = ColorFromHSV(totalAngle % 360.0f, 0.8f, 0.9f);
DrawLineEx(center, animEnd, 5.0f, animCol);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - math angle rotation");
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new MathAngleRotation();
game.Init();
// Main game loop
while (!WindowShouldClose())
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow();
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,212 @@
/*******************************************************************************************
*
* raylib [shapes] example - math sine cosine
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by Jopestpe (@jopestpe) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Jopestpe (@jopestpe)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Raymath;
namespace Examples.Shapes;
public partial class MathSineCosine : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
// Wave points for sine/cosine visualization
private const int WAVE_POINTS = 36;
public string Name => "Shapes / Math Sine Cosine";
public string Title => "raylib [shapes] example - math sine cosine";
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
private Vector2[] sinePoints;
private Vector2[] cosPoints;
private Vector2 center;
private Rectangle start;
private float radius;
private float angle;
private bool pause;
public void Init()
{
sinePoints = new Vector2[WAVE_POINTS];
cosPoints = new Vector2[WAVE_POINTS];
center = new((screenWidth / 2.0f) - 30.0f, screenHeight / 2.0f);
start = new(20.0f, screenHeight - 120.0f, 200.0f, 100.0f);
radius = 130.0f;
angle = 0.0f;
pause = false;
for (int i = 0; i < WAVE_POINTS; i++)
{
float t = i / (float)(WAVE_POINTS - 1);
float currentAngle = t * 360.0f * DEG2RAD;
sinePoints[i] = new(start.X + t * start.Width, start.Y + start.Height / 2.0f - MathF.Sin(currentAngle) * (start.Height / 2.0f));
cosPoints[i] = new(start.X + t * start.Width, start.Y + start.Height / 2.0f - MathF.Cos(currentAngle) * (start.Height / 2.0f));
}
}
public unsafe void Update()
{
// Update
//----------------------------------------------------------------------------------
float angleRad = angle * DEG2RAD;
float cosRad = MathF.Cos(angleRad);
float sinRad = MathF.Sin(angleRad);
Vector2 point = new(center.X + cosRad * radius, center.Y - sinRad * radius);
Vector2 limitMin = new(center.X - radius, center.Y - radius);
Vector2 limitMax = new(center.X + radius, center.Y + radius);
float complementary = 90.0f - angle;
float supplementary = 180.0f - angle;
float explementary = 360.0f - angle;
float tangent = Clamp(MathF.Tan(angleRad), -10.0f, 10.0f);
float cotangent = (MathF.Abs(tangent) > 0.001f) ? Clamp(1.0f / tangent, -radius, radius) : 0.0f;
Vector2 tangentPoint = new(center.X + radius, center.Y - tangent * radius);
Vector2 cotangentPoint = new(center.X + cotangent * radius, center.Y - radius);
angle = Wrap(angle + (!pause ? 1.0f : 0.0f), 0.0f, 360.0f);
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
// Cotangent (orange)
DrawLineEx(new Vector2(center.X, limitMin.Y), new Vector2(cotangentPoint.X, limitMin.Y), 2.0f, Color.Orange);
DrawLineDashed(center, cotangentPoint, 10, 4, Color.Orange);
// Side background
DrawLine(580, 0, 580, GetScreenHeight(), new Color(218, 218, 218, 255));
DrawRectangle(580, 0, GetScreenWidth(), GetScreenHeight(), new Color(232, 232, 232, 255));
// Base circle and axes
DrawCircleLinesV(center, radius, Color.Gray);
DrawLineEx(new Vector2(center.X, limitMin.Y), new Vector2(center.X, limitMax.Y), 1.0f, Color.Gray);
DrawLineEx(new Vector2(limitMin.X, center.Y), new Vector2(limitMax.X, center.Y), 1.0f, Color.Gray);
// Wave graph axes
DrawLineEx(new Vector2(start.X, start.Y), new Vector2(start.X, start.Y + start.Height), 2.0f, Color.Gray);
DrawLineEx(new Vector2(start.X + start.Width, start.Y), new Vector2(start.X + start.Width, start.Y + start.Height), 2.0f, Color.Gray);
DrawLineEx(new Vector2(start.X, start.Y + start.Height / 2), new Vector2(start.X + start.Width, start.Y + start.Height / 2), 2.0f, Color.Gray);
// Wave graph axis labels
DrawText("1", (int)start.X - 8, (int)start.Y, 6, Color.Gray);
DrawText("0", (int)start.X - 8, (int)start.Y + (int)start.Height / 2 - 6, 6, Color.Gray);
DrawText("-1", (int)start.X - 12, (int)start.Y + (int)start.Height - 8, 6, Color.Gray);
DrawText("0", (int)start.X - 2, (int)start.Y + (int)start.Height + 4, 6, Color.Gray);
DrawText("360", (int)start.X + (int)start.Width - 8, (int)start.Y + (int)start.Height + 4, 6, Color.Gray);
// Sine (red - vertical)
DrawLineEx(new Vector2(center.X, center.Y), new Vector2(center.X, point.Y), 2.0f, Color.Red);
DrawLineDashed(new Vector2(point.X, center.Y), new Vector2(point.X, point.Y), 10, 4, Color.Red);
DrawText($"Sine {sinRad:0.00}", 640, 190, 6, Color.Red);
DrawCircleV(new Vector2(start.X + (angle / 360.0f) * start.Width, start.Y + ((-sinRad + 1) * start.Height / 2.0f)), 4.0f, Color.Red);
fixed (Vector2* p = sinePoints) DrawSplineLinear(p, WAVE_POINTS, 1.0f, Color.Red);
// Cosine (blue - horizontal)
DrawLineEx(new Vector2(center.X, center.Y), new Vector2(point.X, center.Y), 2.0f, Color.Blue);
DrawLineDashed(new Vector2(center.X, point.Y), new Vector2(point.X, point.Y), 10, 4, Color.Blue);
DrawText($"Cosine {cosRad:0.00}", 640, 210, 6, Color.Blue);
DrawCircleV(new Vector2(start.X + (angle / 360.0f) * start.Width, start.Y + ((-cosRad + 1) * start.Height / 2.0f)), 4.0f, Color.Blue);
fixed (Vector2* p = cosPoints) DrawSplineLinear(p, WAVE_POINTS, 1.0f, Color.Blue);
// Tangent (purple)
DrawLineEx(new Vector2(limitMax.X, center.Y), new Vector2(limitMax.X, tangentPoint.Y), 2.0f, Color.Purple);
DrawLineDashed(center, tangentPoint, 10, 4, Color.Purple);
DrawText($"Tangent {tangent:0.00}", 640, 230, 6, Color.Purple);
// Cotangent (orange)
DrawText($"Cotangent {cotangent:0.00}", 640, 250, 6, Color.Orange);
// Complementary angle (beige)
DrawCircleSectorLines(center, radius * 0.6f, -angle, -90.0f, 36, Color.Beige);
DrawText($"Complementary {complementary:0}°", 640, 150, 6, Color.Beige);
// Supplementary angle (darkblue)
DrawCircleSectorLines(center, radius * 0.5f, -angle, -180.0f, 36, Color.DarkBlue);
DrawText($"Supplementary {supplementary:0}°", 640, 130, 6, Color.DarkBlue);
// Explementary angle (pink)
DrawCircleSectorLines(center, radius * 0.4f, -angle, -360.0f, 36, Color.Pink);
DrawText($"Explementary {explementary:0}°", 640, 170, 6, Color.Pink);
// Current angle - arc (lime), radius (black), endpoint (black)
DrawCircleSectorLines(center, radius * 0.7f, -angle, 0.0f, 36, Color.Lime);
DrawLineEx(new Vector2(center.X, center.Y), point, 2.0f, Color.Black);
DrawCircleV(point, 4.0f, Color.Black);
// Draw GUI controls
// NOTE: raygui is not bound in raylib-cs, so the Pause toggle and Angle slider
// are unavailable; the angle animates continuously.
//------------------------------------------------------------------------------
/*
GuiSetStyle(LABEL, TEXT_COLOR_NORMAL, ColorToInt(GRAY));
GuiToggle((Rectangle){ 640, 70, 120, 20}, TextFormat("Pause"), &pause);
GuiSetStyle(LABEL, TEXT_COLOR_NORMAL, ColorToInt(LIME));
GuiSliderBar((Rectangle){ 640, 40, 120, 20}, "Angle", TextFormat("%.0f°", angle), &angle, 0.0f, 360.0f);
// Angle values panel
GuiGroupBox((Rectangle){ 620, 110, 140, 170}, "Angle Values");
*/
//------------------------------------------------------------------------------
DrawFPS(10, 10);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
SetConfigFlags(ConfigFlags.Msaa4xHint);
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - math sine cosine");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new MathSineCosine();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,127 @@
/*******************************************************************************************
*
* raylib [shapes] example - Draw a mouse trail (position history)
*
* Example complexity rating: [] 1/4
*
* Example originally created with raylib 5.6
*
* Example contributed by Balamurugan R (@Bala050814]) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Balamurugan R (@Bala050814)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Shapes;
public partial class MouseTrail : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
// Define the maximum number of positions to store in the trail
private const int MAX_TRAIL_LENGTH = 30;
public string Name => "Shapes / Mouse Trail";
public string Title => "raylib [shapes] example - mouse trail";
// Array to store the history of mouse positions (our fixed-size queue)
private Vector2[] trailPositions;
public void Init()
{
// Array to store the history of mouse positions (our fixed-size queue)
trailPositions = new Vector2[MAX_TRAIL_LENGTH];
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
Vector2 mousePosition = GetMousePosition();
// Shift all existing positions backward by one slot in the array
// The last element (the oldest position) is dropped
for (int i = MAX_TRAIL_LENGTH - 1; i > 0; i--)
{
trailPositions[i] = trailPositions[i - 1];
}
// Store the new, current mouse position at the start of the array (Index 0)
trailPositions[0] = mousePosition;
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.Black);
// Draw the trail by looping through the history array
for (int i = 0; i < MAX_TRAIL_LENGTH; i++)
{
// Ensure we skip drawing if the array hasn't been fully filled on startup
if ((trailPositions[i].X != 0.0f) || (trailPositions[i].Y != 0.0f))
{
// Calculate relative trail strength (ratio is near 1.0 for new, near 0.0 for old)
float ratio = (float)(MAX_TRAIL_LENGTH - i)/MAX_TRAIL_LENGTH;
// Fade effect: oldest positions are more transparent
// Fade (color, alpha) - alpha is 0.5 to 1.0 based on ratio
Color trailColor = Fade(Color.SkyBlue, ratio*0.5f + 0.5f);
// Size effect: oldest positions are smaller
float trailRadius = 15.0f*ratio;
DrawCircleV(trailPositions[i], trailRadius, trailColor);
}
}
// Draw a distinct white circle for the current mouse position (Index 0)
DrawCircleV(mousePosition, 15.0f, Color.White);
DrawText("Move the mouse to see the trail effect!", 10, screenHeight - 30, 20, Color.LightGray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - mouse trail");
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new MouseTrail();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,283 @@
/*******************************************************************************************
*
* raylib [shapes] example - penrose tile
*
* Example complexity rating: [] 4/4
*
* Example originally created with raylib 5.5, last time updated with raylib 6.0
* Based on: https://processing.org/examples/penrosetile.html
*
* Example contributed by David Buzatto (@davidbuzatto) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 David Buzatto (@davidbuzatto)
*
********************************************************************************************/
using System;
using System.Numerics;
using System.Collections.Generic;
using System.Text;
using static Raylib_cs.Raylib;
namespace Examples.Shapes;
public partial class PenroseTile : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Shapes / Penrose Tile";
public string Title => "raylib [shapes] example - penrose tile";
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
public int TargetFps => 120;
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
private struct TurtleState
{
public Vector2 origin;
public float angle;
}
private class PenroseLSystem
{
public int steps;
public StringBuilder production;
public string ruleW;
public string ruleX;
public string ruleY;
public string ruleZ;
public float drawLength;
public float theta;
}
//----------------------------------------------------------------------------------
// Global Variables Definition
//----------------------------------------------------------------------------------
private Stack<TurtleState> turtleStack;
private const float drawLength = 460.0f;
private int minGenerations;
private int maxGenerations;
private int generations;
private PenroseLSystem ls;
public void Init()
{
turtleStack = new Stack<TurtleState>();
minGenerations = 0;
maxGenerations = 4;
generations = 0;
// Initialize new penrose tile
ls = CreatePenroseLSystem(drawLength * (generations / (float)maxGenerations));
for (int i = 0; i < generations; i++) BuildProductionStep(ls);
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
bool rebuild = false;
if (IsKeyPressed(KeyboardKey.Up))
{
if (generations < maxGenerations)
{
generations++;
rebuild = true;
}
}
else if (IsKeyPressed(KeyboardKey.Down))
{
if (generations > minGenerations)
{
generations--;
if (generations > 0) rebuild = true;
}
}
if (rebuild)
{
ls = CreatePenroseLSystem(drawLength * (generations / (float)maxGenerations));
for (int i = 0; i < generations; i++) BuildProductionStep(ls);
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
if (generations > 0) DrawPenroseLSystem(ls);
DrawText("penrose l-system", 10, 10, 20, Color.DarkGray);
DrawText("press up or down to change generations", 10, 30, 20, Color.DarkGray);
DrawText($"generations: {generations}", 10, 50, 20, Color.DarkGray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
//----------------------------------------------------------------------------------
// Module Functions Definition
//----------------------------------------------------------------------------------
// Push turtle state for next step
private void PushTurtleState(TurtleState state)
{
turtleStack.Push(state);
}
// Pop turtle state step
private TurtleState PopTurtleState()
{
if (turtleStack.Count > 0) return turtleStack.Pop();
else TraceLog(TraceLogLevel.Warning, "TURTLE STACK UNDERFLOW!");
return new TurtleState();
}
// Create a new penrose tile structure
private static PenroseLSystem CreatePenroseLSystem(float drawLength)
{
PenroseLSystem ls = new PenroseLSystem
{
steps = 0,
ruleW = "YF++ZF4-XF[-YF4-WF]++",
ruleX = "+YF--ZF[3-WF--XF]+",
ruleY = "-WF++XF[+++YF++ZF]-",
ruleZ = "--YF++++WF[+ZF++++XF]--XF",
drawLength = drawLength,
theta = 36.0f // Degrees
};
ls.production = new StringBuilder("[X]++[X]++[X]++[X]++[X]");
return ls;
}
// Build next penrose step
private static void BuildProductionStep(PenroseLSystem ls)
{
StringBuilder newProduction = new StringBuilder();
string production = ls.production.ToString();
for (int i = 0; i < production.Length; i++)
{
char step = production[i];
switch (step)
{
case 'W': newProduction.Append(ls.ruleW); break;
case 'X': newProduction.Append(ls.ruleX); break;
case 'Y': newProduction.Append(ls.ruleY); break;
case 'Z': newProduction.Append(ls.ruleZ); break;
default:
{
if (step != 'F') newProduction.Append(step);
} break;
}
}
ls.drawLength *= 0.5f;
ls.production = newProduction;
}
// Draw penrose tile lines
private void DrawPenroseLSystem(PenroseLSystem ls)
{
Vector2 screenCenter = new Vector2(GetScreenWidth() / 2.0f, GetScreenHeight() / 2.0f);
TurtleState turtle = new TurtleState
{
origin = new Vector2(0, 0),
angle = -90.0f
};
int repeats = 1;
string production = ls.production.ToString();
int productionLength = production.Length;
ls.steps += 12;
if (ls.steps > productionLength) ls.steps = productionLength;
for (int i = 0; i < ls.steps; i++)
{
char step = production[i];
if (step == 'F')
{
for (int j = 0; j < repeats; j++)
{
Vector2 startPosWorld = turtle.origin;
float radAngle = DEG2RAD * turtle.angle;
turtle.origin.X += ls.drawLength * MathF.Cos(radAngle);
turtle.origin.Y += ls.drawLength * MathF.Sin(radAngle);
Vector2 startPosScreen = new Vector2(startPosWorld.X + screenCenter.X, startPosWorld.Y + screenCenter.Y);
Vector2 endPosScreen = new Vector2(turtle.origin.X + screenCenter.X, turtle.origin.Y + screenCenter.Y);
DrawLineEx(startPosScreen, endPosScreen, 2, Fade(Color.Black, 0.2f));
}
repeats = 1;
}
else if (step == '+')
{
for (int j = 0; j < repeats; j++) turtle.angle += ls.theta;
repeats = 1;
}
else if (step == '-')
{
for (int j = 0; j < repeats; j++) turtle.angle += -ls.theta;
repeats = 1;
}
else if (step == '[') PushTurtleState(turtle);
else if (step == ']') turtle = PopTurtleState();
else if ((step >= 48) && (step <= 57)) repeats = (int)step - 48;
}
turtleStack.Clear();
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
SetConfigFlags(ConfigFlags.Msaa4xHint);
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - penrose tile");
SetTargetFPS(120); // Set our game to run at 120 frames-per-second
//---------------------------------------------------------------------------------------
var game = new PenroseTile();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

384
Examples/Shapes/PieChart.cs Normal file
View file

@ -0,0 +1,384 @@
/*******************************************************************************************
*
* raylib [shapes] example - pie chart
*
* Example complexity rating: [] 3/4
*
* Example originally created with raylib 5.5, last time updated with raylib 5.6
*
* Example contributed by Gideon Serfontein (@GideonSerf) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Gideon Serfontein (@GideonSerf)
*
********************************************************************************************/
using System;
using System.Numerics;
using System.Text;
using static Raylib_cs.Raylib;
namespace Examples.Shapes;
public partial class PieChart : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int MAX_PIE_SLICES = 10; // Max pie slices
public string Name => "Shapes / Pie Chart";
public string Title => "raylib [shapes] example - pie chart";
private int sliceCount;
private float donutInnerRadius;
private float[] values;
private StringBuilder[] labels;
private bool[] editingLabel;
private bool showValues;
private bool showPercentages;
private bool showDonut;
private int hoveredSlice;
private Vector2 scrollContentOffset;
// UI layout parameters
private const int panelWidth = 270;
private const int panelMargin = 5;
private Vector2 panelPos;
private Rectangle panelRect;
private Rectangle canvas;
private Vector2 center;
private const float radius = 205.0f;
// Total value for percentage calculations
private float totalValue;
private int framesCounter;
// Minimal raygui-like global state
private static bool guiDisabled;
public void Init()
{
sliceCount = 7;
donutInnerRadius = 25.0f;
values = new float[MAX_PIE_SLICES] { 300.0f, 100.0f, 450.0f, 350.0f, 600.0f, 380.0f, 750.0f, 0.0f, 0.0f, 0.0f };
labels = new StringBuilder[MAX_PIE_SLICES];
editingLabel = new bool[MAX_PIE_SLICES];
for (int i = 0; i < MAX_PIE_SLICES; i++) labels[i] = new StringBuilder($"Slice {i + 1:D2}");
showValues = true;
showPercentages = false;
showDonut = false;
hoveredSlice = -1;
scrollContentOffset = new Vector2(0, 0);
// UI Panel top-left anchor
panelPos = new Vector2(
(float)screenWidth - panelMargin - panelWidth,
(float)panelMargin
);
// UI Panel rectangle
panelRect = new Rectangle(
panelPos.X, panelPos.Y,
(float)panelWidth,
(float)screenHeight - 2.0f * panelMargin
);
// Pie chart geometry
canvas = new Rectangle(0, 0, panelPos.X, (float)screenHeight);
center = new Vector2(canvas.Width / 2.0f, canvas.Height / 2.0f);
totalValue = 0.0f;
framesCounter = 0;
guiDisabled = false;
}
public void Update()
{
framesCounter++;
// Update
//----------------------------------------------------------------------------------
// Calculate total value for percentage calculations
totalValue = 0.0f;
for (int i = 0; i < sliceCount; i++) totalValue += values[i];
// Check for mouse hover over slices
hoveredSlice = -1; // Reset hovered slice
Vector2 mousePos = GetMousePosition();
if (CheckCollisionPointRec(mousePos, canvas)) // Only check if mouse is inside the canvas
{
float dx = mousePos.X - center.X;
float dy = mousePos.Y - center.Y;
float distance = MathF.Sqrt(dx * dx + dy * dy);
if (distance <= radius) // Inside the pie radius
{
float angle = MathF.Atan2(dy, dx) * RAD2DEG;
if (angle < 0) angle += 360;
float currentAngle = 0.0f;
for (int i = 0; i < sliceCount; i++)
{
float sweep = (totalValue > 0) ? (values[i] / totalValue) * 360.0f : 0.0f;
if ((angle >= currentAngle) && (angle < (currentAngle + sweep)))
{
hoveredSlice = i;
break;
}
currentAngle += sweep;
}
}
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
// Draw the pie chart on the canvas
float startAngle = 0.0f;
for (int i = 0; i < sliceCount; i++)
{
float sweepAngle = (totalValue > 0) ? (values[i] / totalValue) * 360.0f : 0.0f;
float midAngle = startAngle + sweepAngle / 2.0f; // Middle angle for label positioning
Color color = ColorFromHSV((float)i / sliceCount * 360.0f, 0.75f, 0.9f);
float currentRadius = radius;
// Make the hovered slice pop out by adding pixels to its radius
if (i == hoveredSlice) currentRadius += 20.0f;
// Draw the pie slice using raylib's DrawCircleSector function
DrawCircleSector(center, currentRadius, startAngle, startAngle + sweepAngle, 120, color);
// Draw the label for the current slice
if (values[i] > 0)
{
string labelText;
if (showValues && showPercentages) labelText = $"{values[i]:F1} ({(values[i] / totalValue) * 100.0f:F0}%)";
else if (showValues) labelText = $"{values[i]:F1}";
else if (showPercentages) labelText = $"{(values[i] / totalValue) * 100.0f:F0}%";
else labelText = "";
Vector2 textSize = MeasureTextEx(GetFontDefault(), labelText, 20, 1);
float labelRadius = radius * 0.7f;
Vector2 labelPos = new Vector2(center.X + MathF.Cos(midAngle * DEG2RAD) * labelRadius - textSize.X / 2.0f,
center.Y + MathF.Sin(midAngle * DEG2RAD) * labelRadius - textSize.Y / 2.0f);
DrawText(labelText, (int)labelPos.X, (int)labelPos.Y, 20, Color.White);
}
// Draw inner circle to create donut effect
// TODO: This is a hacky solution, better use DrawRing()
if (showDonut) DrawCircleV(center, donutInnerRadius, Color.RayWhite);
startAngle += sweepAngle;
}
// UI control panel (minimal raygui-like controls, raygui is not bound in raylib-cs)
DrawRectangleRec(panelRect, Fade(Color.LightGray, 0.5f));
DrawRectangleLinesEx(panelRect, 1.0f, Color.Gray);
GuiSpinner(new Rectangle(panelPos.X + 95, (float)panelPos.Y + 12, 125, 25), "Slices ", ref sliceCount, 1, MAX_PIE_SLICES);
GuiCheckBox(new Rectangle(panelPos.X + 20, (float)panelPos.Y + 12 + 40, 20, 20), "Show Values", ref showValues);
GuiCheckBox(new Rectangle(panelPos.X + 20, (float)panelPos.Y + 12 + 70, 20, 20), "Show Percentages", ref showPercentages);
GuiCheckBox(new Rectangle(panelPos.X + 20, (float)panelPos.Y + 12 + 100, 20, 20), "Make Donut", ref showDonut);
if (showDonut) GuiDisable();
GuiSliderBar(new Rectangle(panelPos.X + 80, (float)panelPos.Y + 12 + 130, panelRect.Width - 100, 30),
"Inner Radius", null, ref donutInnerRadius, 5.0f, radius - 10.0f);
GuiEnable();
GuiLine(new Rectangle(panelPos.X + 10, (float)panelPos.Y + 12 + 170, panelRect.Width - 20, 1));
// Scrollable area for slice editors
float scrollTop = (float)panelPos.Y + 12 + 190;
Rectangle scrollPanelBounds = new Rectangle(
panelPos.X + panelMargin,
scrollTop,
panelRect.Width - panelMargin * 2,
(panelRect.Y + panelRect.Height) - scrollTop - panelMargin);
int contentHeight = sliceCount * 35;
// Simple vertical scroll via mouse wheel while hovering the panel
if (CheckCollisionPointRec(GetMousePosition(), scrollPanelBounds))
{
scrollContentOffset.Y += GetMouseWheelMove() * 20.0f;
float minOffset = MathF.Min(0.0f, scrollPanelBounds.Height - contentHeight);
if (scrollContentOffset.Y < minOffset) scrollContentOffset.Y = minOffset;
if (scrollContentOffset.Y > 0.0f) scrollContentOffset.Y = 0.0f;
}
Rectangle view = scrollPanelBounds;
float contentX = view.X + scrollContentOffset.X; // Left of content
float contentY = view.Y + scrollContentOffset.Y; // Top of content
BeginScissorMode((int)view.X, (int)view.Y, (int)view.Width, (int)view.Height);
for (int i = 0; i < sliceCount; i++)
{
int rowY = (int)(contentY + 5 + i * 35);
// Color indicator
Color color = ColorFromHSV((float)i / sliceCount * 360.0f, 0.75f, 0.9f);
DrawRectangle((int)(contentX + 15), rowY + 5, 20, 20, color);
// Label textbox
if (GuiTextBox(new Rectangle(contentX + 45, (float)rowY, 75, 30), labels[i], 32, editingLabel[i])) editingLabel[i] = !editingLabel[i];
GuiSliderBar(new Rectangle(contentX + 130, (float)rowY, 110, 30), null, null, ref values[i], 0.0f, 1000.0f);
}
EndScissorMode();
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
//----------------------------------------------------------------------------------
// Minimal raygui-like widgets (plain raylib re-implementation)
//----------------------------------------------------------------------------------
private static void GuiDisable() => guiDisabled = true;
private static void GuiEnable() => guiDisabled = false;
private static void GuiLine(Rectangle bounds)
{
int y = (int)(bounds.Y + bounds.Height / 2);
DrawLine((int)bounds.X, y, (int)(bounds.X + bounds.Width), y, Color.Gray);
}
private static void GuiCheckBox(Rectangle bounds, string text, ref bool active)
{
Vector2 mouse = GetMousePosition();
bool hover = CheckCollisionPointRec(mouse, bounds);
if (!guiDisabled && hover && IsMouseButtonPressed(MouseButton.Left)) active = !active;
DrawRectangleLinesEx(bounds, 1, hover ? Color.DarkBlue : Color.Gray);
if (active) DrawRectangle((int)bounds.X + 4, (int)bounds.Y + 4, (int)bounds.Width - 8, (int)bounds.Height - 8, Color.DarkGray);
if (text != null) DrawText(text, (int)(bounds.X + bounds.Width + 8), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
private static void GuiSpinner(Rectangle bounds, string text, ref int value, int minValue, int maxValue)
{
Vector2 mouse = GetMousePosition();
Rectangle left = new Rectangle(bounds.X, bounds.Y, bounds.Height, bounds.Height);
Rectangle right = new Rectangle(bounds.X + bounds.Width - bounds.Height, bounds.Y, bounds.Height, bounds.Height);
Rectangle mid = new Rectangle(bounds.X + bounds.Height, bounds.Y, bounds.Width - 2 * bounds.Height, bounds.Height);
if (!guiDisabled && CheckCollisionPointRec(mouse, left) && IsMouseButtonPressed(MouseButton.Left) && value > minValue) value--;
if (!guiDisabled && CheckCollisionPointRec(mouse, right) && IsMouseButtonPressed(MouseButton.Left) && value < maxValue) value++;
DrawRectangleRec(mid, Color.RayWhite);
DrawRectangleLinesEx(mid, 1, Color.Gray);
DrawRectangleRec(left, Color.LightGray);
DrawRectangleLinesEx(left, 1, Color.Gray);
DrawRectangleRec(right, Color.LightGray);
DrawRectangleLinesEx(right, 1, Color.Gray);
DrawText("-", (int)(left.X + left.Width / 2 - 2), (int)(left.Y + left.Height / 2 - 5), 10, Color.DarkGray);
DrawText("+", (int)(right.X + right.Width / 2 - 3), (int)(right.Y + right.Height / 2 - 5), 10, Color.DarkGray);
string vs = value.ToString();
DrawText(vs, (int)(mid.X + mid.Width / 2 - MeasureText(vs, 10) / 2), (int)(mid.Y + mid.Height / 2 - 5), 10, Color.DarkGray);
if (text != null) DrawText(text, (int)bounds.X - MeasureText(text, 10) - 5, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
private static void GuiSliderBar(Rectangle bounds, string textLeft, string textRight, ref float value, float minValue, float maxValue)
{
Vector2 mouse = GetMousePosition();
bool hover = CheckCollisionPointRec(mouse, bounds);
if (!guiDisabled && hover && IsMouseButtonDown(MouseButton.Left))
{
value = minValue + ((mouse.X - bounds.X) / bounds.Width) * (maxValue - minValue);
if (value < minValue) value = minValue;
if (value > maxValue) value = maxValue;
}
DrawRectangleRec(bounds, guiDisabled ? Color.Gray : Color.LightGray);
float pct = (value - minValue) / (maxValue - minValue);
DrawRectangleRec(new Rectangle(bounds.X, bounds.Y, bounds.Width * pct, bounds.Height), guiDisabled ? Color.DarkGray : Color.SkyBlue);
DrawRectangleLinesEx(bounds, 1, Color.Gray);
if (!string.IsNullOrEmpty(textLeft)) DrawText(textLeft, (int)bounds.X - MeasureText(textLeft, 10) - 5, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
if (!string.IsNullOrEmpty(textRight)) DrawText(textRight, (int)(bounds.X + bounds.Width + 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
private bool GuiTextBox(Rectangle bounds, StringBuilder text, int maxChars, bool editMode)
{
bool pressed = false;
Vector2 mouse = GetMousePosition();
bool hover = CheckCollisionPointRec(mouse, bounds);
if (editMode)
{
int key = GetCharPressed();
while (key > 0)
{
if ((key >= 32) && (key <= 125) && (text.Length < maxChars - 1))
{
text.Append((char)key);
}
key = GetCharPressed();
}
if (IsKeyPressed(KeyboardKey.Backspace) && (text.Length > 0)) text.Remove(text.Length - 1, 1);
}
DrawRectangleRec(bounds, Color.RayWhite);
DrawRectangleLinesEx(bounds, editMode ? 2 : 1, editMode ? Color.Red : (hover ? Color.Blue : Color.Gray));
string content = text.ToString();
DrawText(content, (int)bounds.X + 4, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
if (editMode && ((framesCounter / 20) % 2 == 0))
{
DrawText("_", (int)bounds.X + 4 + MeasureText(content, 10), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
if (hover && IsMouseButtonPressed(MouseButton.Left)) pressed = true;
return pressed;
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - pie chart");
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new PieChart();
game.Init();
// Main game loop
while (!WindowShouldClose())
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,252 @@
/*******************************************************************************************
*
* raylib [shapes] example - rectangle advanced
*
* Example complexity rating: [] 4/4
*
* Example originally created with raylib 5.5, last time updated with raylib 5.5
*
* Example contributed by Everton Jr. (@evertonse) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2024-2025 Everton Jr. (@evertonse) and Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Rlgl;
namespace Examples.Shapes;
public partial class RectangleAdvanced : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Shapes / Rectangle Advanced";
public string Title => "raylib [shapes] example - rectangle advanced";
public void Init()
{
}
public void Update()
{
// Update rectangle bounds
//----------------------------------------------------------------------------------
float width = GetScreenWidth() / 2.0f, height = GetScreenHeight() / 6.0f;
Rectangle rec = new Rectangle(
GetScreenWidth() / 2.0f - width / 2,
GetScreenHeight() / 2.0f - 5 * (height / 2),
width, height
);
//--------------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
// Draw All Rectangles with different roundess for each side and different gradients
DrawRectangleRoundedGradientH(rec, 0.8f, 0.8f, 36, Color.Blue, Color.Red);
rec.Y += rec.Height + 1;
DrawRectangleRoundedGradientH(rec, 0.5f, 1.0f, 36, Color.Red, Color.Pink);
rec.Y += rec.Height + 1;
DrawRectangleRoundedGradientH(rec, 1.0f, 0.5f, 36, Color.Red, Color.Blue);
rec.Y += rec.Height + 1;
DrawRectangleRoundedGradientH(rec, 0.0f, 1.0f, 36, Color.Blue, Color.Black);
rec.Y += rec.Height + 1;
DrawRectangleRoundedGradientH(rec, 1.0f, 0.0f, 36, Color.Blue, Color.Pink);
EndDrawing();
//--------------------------------------------------------------------------------------
}
public void Unload()
{
}
//--------------------------------------------------------------------------------------
// Module Functions Definition
//--------------------------------------------------------------------------------------
// Draw rectangle with rounded edges and horizontal gradient, with options to choose side of roundness
// NOTE: Adapted from both 'DrawRectangleRounded()' and 'DrawRectangleGradientH()' raylib [rshapes] implementations
private static void DrawRectangleRoundedGradientH(Rectangle rec, float roundnessLeft, float roundnessRight, int segments, Color left, Color right)
{
// Neither side is rounded
if ((roundnessLeft <= 0.0f && roundnessRight <= 0.0f) || (rec.Width < 1) || (rec.Height < 1))
{
DrawRectangleGradientEx(rec, left, left, right, right);
return;
}
if (roundnessLeft >= 1.0f) roundnessLeft = 1.0f;
if (roundnessRight >= 1.0f) roundnessRight = 1.0f;
// Calculate corner radius both from right and left
float recSize = rec.Width > rec.Height ? rec.Height : rec.Width;
float radiusLeft = (recSize * roundnessLeft) / 2;
float radiusRight = (recSize * roundnessRight) / 2;
if (radiusLeft <= 0.0f) radiusLeft = 0.0f;
if (radiusRight <= 0.0f) radiusRight = 0.0f;
if (radiusRight <= 0.0f && radiusLeft <= 0.0f) return;
float stepLength = 90.0f / (float)segments;
/*
Diagram Copied here for reference, original at 'DrawRectangleRounded()' source code
P0____________________P1
/| |\
/1| 2 |3\
P7 /__|____________________|__\ P2
| |P8 P9| |
| 8 | 9 | 4 |
| __|____________________|__ |
P6 \ |P11 P10| / P3
\7| 6 |5/
\|____________________|/
P5 P4
*/
// Coordinates of the 12 points also adapted from `DrawRectangleRounded`
Vector2[] point = new Vector2[12]
{
// PO, P1, P2
new Vector2(rec.X + radiusLeft, rec.Y), new Vector2((rec.X + rec.Width) - radiusRight, rec.Y), new Vector2(rec.X + rec.Width, rec.Y + radiusRight),
// P3, P4
new Vector2(rec.X + rec.Width, (rec.Y + rec.Height) - radiusRight), new Vector2((rec.X + rec.Width) - radiusRight, rec.Y + rec.Height),
// P5, P6, P7
new Vector2(rec.X + radiusLeft, rec.Y + rec.Height), new Vector2(rec.X, (rec.Y + rec.Height) - radiusLeft), new Vector2(rec.X, rec.Y + radiusLeft),
// P8, P9
new Vector2(rec.X + radiusLeft, rec.Y + radiusLeft), new Vector2((rec.X + rec.Width) - radiusRight, rec.Y + radiusRight),
// P10, P11
new Vector2((rec.X + rec.Width) - radiusRight, (rec.Y + rec.Height) - radiusRight), new Vector2(rec.X + radiusLeft, (rec.Y + rec.Height) - radiusLeft)
};
Vector2[] centers = new Vector2[4] { point[8], point[9], point[10], point[11] };
float[] angles = new float[4] { 180.0f, 270.0f, 0.0f, 90.0f };
// Here we use the 'Diagram' to guide ourselves to which point receives what color
// By choosing the color correctly associated with a point the gradient effect
// will naturally come from OpenGL interpolation
// But this time instead of Quad, we think in triangles
Begin(DrawMode.Triangles);
// Draw all of the 4 corners: [1] Upper Left Corner, [3] Upper Right Corner, [5] Lower Right Corner, [7] Lower Left Corner
for (int k = 0; k < 4; ++k)
{
Color color = new Color(0, 0, 0, 0);
float radius = 0.0f;
if (k == 0) { color = left; radius = radiusLeft; } // [1] Upper Left Corner
if (k == 1) { color = right; radius = radiusRight; } // [3] Upper Right Corner
if (k == 2) { color = right; radius = radiusRight; } // [5] Lower Right Corner
if (k == 3) { color = left; radius = radiusLeft; } // [7] Lower Left Corner
float angle = angles[k];
Vector2 center = centers[k];
for (int i = 0; i < segments; i++)
{
Color4ub(color.R, color.G, color.B, color.A);
Vertex2f(center.X, center.Y);
Vertex2f(center.X + MathF.Cos(DEG2RAD * (angle + stepLength)) * radius, center.Y + MathF.Sin(DEG2RAD * (angle + stepLength)) * radius);
Vertex2f(center.X + MathF.Cos(DEG2RAD * angle) * radius, center.Y + MathF.Sin(DEG2RAD * angle) * radius);
angle += stepLength;
}
}
// [2] Upper Rectangle
Color4ub(left.R, left.G, left.B, left.A);
Vertex2f(point[0].X, point[0].Y);
Vertex2f(point[8].X, point[8].Y);
Color4ub(right.R, right.G, right.B, right.A);
Vertex2f(point[9].X, point[9].Y);
Vertex2f(point[1].X, point[1].Y);
Color4ub(left.R, left.G, left.B, left.A);
Vertex2f(point[0].X, point[0].Y);
Color4ub(right.R, right.G, right.B, right.A);
Vertex2f(point[9].X, point[9].Y);
// [4] Right Rectangle
Color4ub(right.R, right.G, right.B, right.A);
Vertex2f(point[9].X, point[9].Y);
Vertex2f(point[10].X, point[10].Y);
Vertex2f(point[3].X, point[3].Y);
Vertex2f(point[2].X, point[2].Y);
Vertex2f(point[9].X, point[9].Y);
Vertex2f(point[3].X, point[3].Y);
// [6] Bottom Rectangle
Color4ub(left.R, left.G, left.B, left.A);
Vertex2f(point[11].X, point[11].Y);
Vertex2f(point[5].X, point[5].Y);
Color4ub(right.R, right.G, right.B, right.A);
Vertex2f(point[4].X, point[4].Y);
Vertex2f(point[10].X, point[10].Y);
Color4ub(left.R, left.G, left.B, left.A);
Vertex2f(point[11].X, point[11].Y);
Color4ub(right.R, right.G, right.B, right.A);
Vertex2f(point[4].X, point[4].Y);
// [8] Left Rectangle
Color4ub(left.R, left.G, left.B, left.A);
Vertex2f(point[7].X, point[7].Y);
Vertex2f(point[6].X, point[6].Y);
Vertex2f(point[11].X, point[11].Y);
Vertex2f(point[8].X, point[8].Y);
Vertex2f(point[7].X, point[7].Y);
Vertex2f(point[11].X, point[11].Y);
// [9] Middle Rectangle
Color4ub(left.R, left.G, left.B, left.A);
Vertex2f(point[8].X, point[8].Y);
Vertex2f(point[11].X, point[11].Y);
Color4ub(right.R, right.G, right.B, right.A);
Vertex2f(point[10].X, point[10].Y);
Vertex2f(point[9].X, point[9].Y);
Color4ub(left.R, left.G, left.B, left.A);
Vertex2f(point[8].X, point[8].Y);
Color4ub(right.R, right.G, right.B, right.A);
Vertex2f(point[10].X, point[10].Y);
End();
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - rectangle advanced");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new RectangleAdvanced();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,199 @@
/*******************************************************************************************
*
* raylib [shapes] example - recursive tree
*
* Example complexity rating: [] 3/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by Jopestpe (@jopestpe)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Jopestpe (@jopestpe)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Shapes;
public partial class RecursiveTree : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Shapes / Recursive Tree";
public string Title => "raylib [shapes] example - recursive tree";
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
private struct Branch
{
public Vector2 start;
public Vector2 end;
public float angle;
public float length;
}
private Vector2 start;
private float angle;
private float thick;
private float treeDepth;
private float branchDecay;
private float length;
private bool bezier;
private Branch[] branches;
public void Init()
{
start = new Vector2((screenWidth / 2.0f) - 125.0f, (float)screenHeight);
angle = 40.0f;
thick = 1.0f;
treeDepth = 10.0f;
branchDecay = 0.66f;
length = 120.0f;
bezier = false;
branches = new Branch[1030];
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
float theta = angle * DEG2RAD;
int maxBranches = (int)(MathF.Pow(2, MathF.Floor(treeDepth)));
int count = 0;
Vector2 initialEnd = new Vector2(start.X + length * MathF.Sin(0.0f), start.Y - length * MathF.Cos(0.0f));
branches[count++] = new Branch { start = start, end = initialEnd, angle = 0.0f, length = length };
for (int i = 0; i < count; i++)
{
Branch branch = branches[i];
if (branch.length < 2) continue;
float nextLength = branch.length * branchDecay;
if (count < maxBranches && nextLength >= 2)
{
Vector2 branchStart = branch.end;
float angle1 = branch.angle + theta;
Vector2 branchEnd1 = new Vector2(branchStart.X + nextLength * MathF.Sin(angle1), branchStart.Y - nextLength * MathF.Cos(angle1));
branches[count++] = new Branch { start = branchStart, end = branchEnd1, angle = angle1, length = nextLength };
float angle2 = branch.angle - theta;
Vector2 branchEnd2 = new Vector2(branchStart.X + nextLength * MathF.Sin(angle2), branchStart.Y - nextLength * MathF.Cos(angle2));
branches[count++] = new Branch { start = branchStart, end = branchEnd2, angle = angle2, length = nextLength };
}
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
for (int i = 0; i < count; i++)
{
Branch branch = branches[i];
if (branch.length >= 2)
{
if (bezier) DrawLineBezier(branch.start, branch.end, thick, Color.Red);
else DrawLineEx(branch.start, branch.end, thick, Color.Red);
}
}
DrawLine(580, 0, 580, GetScreenHeight(), new Color(218, 218, 218, 255));
DrawRectangle(580, 0, GetScreenWidth(), GetScreenHeight(), new Color(232, 232, 232, 255));
// Draw GUI controls (minimal raygui-like controls, raygui is not bound in raylib-cs)
//------------------------------------------------------------------------------
GuiSliderBar(new Rectangle(640, 40, 120, 20), "Angle", $"{angle:F0}", ref angle, 0, 180);
GuiSliderBar(new Rectangle(640, 70, 120, 20), "Length", $"{length:F0}", ref length, 12.0f, 240.0f);
GuiSliderBar(new Rectangle(640, 100, 120, 20), "Decay", $"{branchDecay:F2}", ref branchDecay, 0.1f, 0.78f);
GuiSliderBar(new Rectangle(640, 130, 120, 20), "Depth", $"{treeDepth:F0}", ref treeDepth, 1.0f, 10.0f);
GuiSliderBar(new Rectangle(640, 160, 120, 20), "Thick", $"{thick:F0}", ref thick, 1, 8);
GuiCheckBox(new Rectangle(640, 190, 20, 20), "Bezier", ref bezier);
//------------------------------------------------------------------------------
DrawFPS(10, 10);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
//----------------------------------------------------------------------------------
// Minimal raygui-like widgets (plain raylib re-implementation)
//----------------------------------------------------------------------------------
private static void GuiSliderBar(Rectangle bounds, string textLeft, string textRight, ref float value, float minValue, float maxValue)
{
Vector2 mouse = GetMousePosition();
bool hover = CheckCollisionPointRec(mouse, bounds);
if (hover && IsMouseButtonDown(MouseButton.Left))
{
value = minValue + ((mouse.X - bounds.X) / bounds.Width) * (maxValue - minValue);
if (value < minValue) value = minValue;
if (value > maxValue) value = maxValue;
}
DrawRectangleRec(bounds, Color.LightGray);
float pct = (value - minValue) / (maxValue - minValue);
DrawRectangleRec(new Rectangle(bounds.X, bounds.Y, bounds.Width * pct, bounds.Height), Color.SkyBlue);
DrawRectangleLinesEx(bounds, 1, Color.Gray);
if (textLeft != null) DrawText(textLeft, (int)bounds.X - MeasureText(textLeft, 10) - 5, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
if (textRight != null) DrawText(textRight, (int)(bounds.X + bounds.Width + 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
private static void GuiCheckBox(Rectangle bounds, string text, ref bool active)
{
Vector2 mouse = GetMousePosition();
bool hover = CheckCollisionPointRec(mouse, bounds);
if (hover && IsMouseButtonPressed(MouseButton.Left)) active = !active;
DrawRectangleLinesEx(bounds, 1, hover ? Color.DarkBlue : Color.Gray);
if (active) DrawRectangle((int)bounds.X + 4, (int)bounds.Y + 4, (int)bounds.Width - 8, (int)bounds.Height - 8, Color.DarkGray);
if (text != null) DrawText(text, (int)(bounds.X + bounds.Width + 8), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - recursive tree");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new RecursiveTree();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,331 @@
/*******************************************************************************************
*
* raylib [shapes] example - rlgl color wheel
*
* Example complexity rating: [] 3/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by Robin (@RobinsAviary) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Robin (@RobinsAviary)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Raymath;
using static Raylib_cs.Rlgl;
namespace Examples.Shapes;
public partial class RlglColorWheel : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Shapes / RLGL Color Wheel";
public string Title => "raylib [shapes] example - rlgl color wheel";
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
// The minimum/maximum points the circle can have
private const int pointsMin = 3;
private const int pointsMax = 256;
// The current number of points and the radius of the circle
private int triangleCount;
private float pointScale;
// Slider value, literally maps to value in HSV
private float value;
// The center of the screen
private Vector2 center;
// The location of the color wheel
private Vector2 circlePosition;
// The currently selected color
private Color color;
// Indicates if the slider is being clicked
private bool sliderClicked;
// Indicates if the current color going to be updated, as well as the handle position
private bool settingColor;
// How the color wheel will be rendered
private DrawMode renderType;
public void Init()
{
triangleCount = 64;
pointScale = 150.0f;
value = 1.0f;
center = new Vector2((float)screenWidth / 2.0f, (float)screenHeight / 2.0f);
circlePosition = center;
color = new Color(255, 255, 255, 255);
sliderClicked = false;
settingColor = false;
renderType = DrawMode.Triangles;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
triangleCount += (int)GetMouseWheelMove();
triangleCount = (int)Clamp((float)triangleCount, (float)pointsMin, (float)pointsMax);
Rectangle sliderRectangle = new Rectangle(42.0f, 16.0f + 64.0f + 45.0f, 64.0f, 16.0f);
Vector2 mousePosition = GetMousePosition();
// Checks if the user is hovering over the value slider
bool sliderHover = (mousePosition.X >= sliderRectangle.X && mousePosition.Y >= sliderRectangle.Y && mousePosition.X < sliderRectangle.X + sliderRectangle.Width && mousePosition.Y < sliderRectangle.Y + sliderRectangle.Height);
// Copy color as hex
if (IsKeyDown(KeyboardKey.LeftControl) && IsKeyDown(KeyboardKey.C))
{
if (IsKeyPressed(KeyboardKey.C))
{
SetClipboardText($"#{color.R:X2}{color.G:X2}{color.B:X2}");
}
}
// Scale up the color wheel, adjusting the handle visually
if (IsKeyDown(KeyboardKey.Up))
{
pointScale *= 1.025f;
if (pointScale > (float)screenHeight / 2.0f)
{
pointScale = (float)screenHeight / 2.0f;
}
else
{
circlePosition = Vector2Add(Vector2Multiply(Vector2Subtract(circlePosition, center), new Vector2(1.025f, 1.025f)), center);
}
}
// Scale down the wheel, adjusting the handle visually
if (IsKeyDown(KeyboardKey.Down))
{
pointScale *= 0.975f;
if (pointScale < 32.0f)
{
pointScale = 32.0f;
}
else
{
circlePosition = Vector2Add(Vector2Multiply(Vector2Subtract(circlePosition, center), new Vector2(0.975f, 0.975f)), center);
}
float distanceDown = Vector2Distance(center, circlePosition) / pointScale;
float angleDown = ((Vector2Angle(new Vector2(0.0f, -pointScale), Vector2Subtract(center, circlePosition)) / MathF.PI + 1.0f) / 2.0f);
if (distanceDown > 1.0f)
{
circlePosition = Vector2Add(new Vector2(MathF.Sin(angleDown * (MathF.PI * 2.0f)) * pointScale, -MathF.Cos(angleDown * (MathF.PI * 2.0f)) * pointScale), center);
}
}
// Checks if the user clicked on the color wheel
if (IsMouseButtonPressed(MouseButton.Left) && Vector2Distance(GetMousePosition(), center) <= pointScale + 10.0f)
{
settingColor = true;
}
// Update flag when mouse button is released
if (IsMouseButtonReleased(MouseButton.Left)) settingColor = false;
// Check if the user clicked/released the slider for the color's value
if (sliderHover && IsMouseButtonPressed(MouseButton.Left)) sliderClicked = true;
if (sliderClicked && IsMouseButtonReleased(MouseButton.Left)) sliderClicked = false;
// Update render mode accordingly
if (IsKeyPressed(KeyboardKey.Space)) renderType = DrawMode.Lines;
if (IsKeyReleased(KeyboardKey.Space)) renderType = DrawMode.Triangles;
// If the slider or the wheel was clicked, update the current color
if (settingColor || sliderClicked)
{
if (settingColor) circlePosition = GetMousePosition();
float distance = Vector2Distance(center, circlePosition) / pointScale;
float angle = ((Vector2Angle(new Vector2(0.0f, -pointScale), Vector2Subtract(center, circlePosition)) / MathF.PI + 1.0f) / 2.0f);
if (settingColor && distance > 1.0f) circlePosition = Vector2Add(new Vector2(MathF.Sin(angle * (MathF.PI * 2.0f)) * pointScale, -MathF.Cos(angle * (MathF.PI * 2.0f)) * pointScale), center);
float angle360 = angle * 360.0f;
float valueActual = Clamp(distance, 0.0f, 1.0f);
color = ColorLerp(new Color((int)(value * 255.0f), (int)(value * 255.0f), (int)(value * 255.0f), 255), ColorFromHSV(angle360, Clamp(distance, 0.0f, 1.0f), 1.0f), valueActual);
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
// Begin rendering color wheel
Begin(renderType);
for (int i = 0; i < triangleCount; i++)
{
float angleOffset = ((MathF.PI * 2.0f) / (float)triangleCount);
float angle = angleOffset * (float)i;
float angleOffsetCalculated = ((float)i + 1) * angleOffset;
Vector2 scale = new Vector2(pointScale, pointScale);
Vector2 offset = Vector2Multiply(new Vector2(MathF.Sin(angle), -MathF.Cos(angle)), scale);
Vector2 offset2 = Vector2Multiply(new Vector2(MathF.Sin(angleOffsetCalculated), -MathF.Cos(angleOffsetCalculated)), scale);
Vector2 position = Vector2Add(center, offset);
Vector2 position2 = Vector2Add(center, offset2);
float angleNonRadian = (angle / (2.0f * MathF.PI)) * 360.0f;
float angleNonRadianOffset = (angleOffset / (2.0f * MathF.PI)) * 360.0f;
Color currentColor = ColorFromHSV(angleNonRadian, 1.0f, 1.0f);
Color offsetColor = ColorFromHSV(angleNonRadian + angleNonRadianOffset, 1.0f, 1.0f);
// Input vertices differently depending on mode
if (renderType == DrawMode.Triangles)
{
// RL_TRIANGLES expects three vertices per triangle
Color4ub(currentColor.R, currentColor.G, currentColor.B, currentColor.A);
Vertex2f(position.X, position.Y);
Color4f(value, value, value, 1.0f);
Vertex2f(center.X, center.Y);
Color4ub(offsetColor.R, offsetColor.G, offsetColor.B, offsetColor.A);
Vertex2f(position2.X, position2.Y);
}
else if (renderType == DrawMode.Lines)
{
// RL_LINES expects two vertices per line
Color4ub(currentColor.R, currentColor.G, currentColor.B, currentColor.A);
Vertex2f(position.X, position.Y);
Color4ub(Color.White.R, Color.White.G, Color.White.B, Color.White.A);
Vertex2f(center.X, center.Y);
Vertex2f(center.X, center.Y);
Color4ub(offsetColor.R, offsetColor.G, offsetColor.B, offsetColor.A);
Vertex2f(position2.X, position2.Y);
Vertex2f(position2.X, position2.Y);
Color4ub(currentColor.R, currentColor.G, currentColor.B, currentColor.A);
Vertex2f(position.X, position.Y);
}
}
End();
// Make the handle slightly more visible overtop darker colors
Color handleColor = Color.Black;
if (Vector2Distance(center, circlePosition) / pointScale <= 0.5f && value <= 0.5f)
{
handleColor = Color.DarkGray;
}
// Draw the color handle
DrawCircleLinesV(circlePosition, 4.0f, handleColor);
// Draw the color in a preview, with a darkened outline.
DrawRectangleV(new Vector2(8.0f, 8.0f), new Vector2(64.0f, 64.0f), color);
DrawRectangleLinesEx(new Rectangle(8.0f, 8.0f, 64.0f, 64.0f), 2.0f, ColorLerp(color, Color.Black, 0.5f));
// Draw current color as hex and decimal
DrawText($"#{color.R:X2}{color.G:X2}{color.B:X2}\n({color.R}, {color.G}, {color.B})", 8, 8 + 64 + 8, 20, Color.DarkGray);
// Update the visuals for the copying text
Color copyColor = Color.DarkGray;
int textOffset = 0;
if (IsKeyDown(KeyboardKey.LeftControl) && IsKeyDown(KeyboardKey.C))
{
copyColor = Color.DarkGreen;
textOffset = 4;
}
// Draw the copying text
DrawText("press ctrl+c to copy!", 8, 425 - textOffset, 20, copyColor);
// Display the number of rendered triangles
DrawText($"triangle count: {triangleCount}", 8, 395, 20, Color.DarkGray);
// Slider to change color's value (minimal raygui-like control, raygui is not bound in raylib-cs)
GuiSliderBar(sliderRectangle, "value: ", "", ref value, 0.0f, 1.0f);
// Draw FPS next to outlined color preview
DrawFPS(64 + 16, 8);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
//----------------------------------------------------------------------------------
// Minimal raygui-like widgets (plain raylib re-implementation)
//----------------------------------------------------------------------------------
private static void GuiSliderBar(Rectangle bounds, string textLeft, string textRight, ref float value, float minValue, float maxValue)
{
Vector2 mouse = GetMousePosition();
bool hover = CheckCollisionPointRec(mouse, bounds);
if (hover && IsMouseButtonDown(MouseButton.Left))
{
value = minValue + ((mouse.X - bounds.X) / bounds.Width) * (maxValue - minValue);
if (value < minValue) value = minValue;
if (value > maxValue) value = maxValue;
}
DrawRectangleRec(bounds, Color.LightGray);
float pct = (value - minValue) / (maxValue - minValue);
DrawRectangleRec(new Rectangle(bounds.X, bounds.Y, bounds.Width * pct, bounds.Height), Color.SkyBlue);
DrawRectangleLinesEx(bounds, 1, Color.Gray);
if (!string.IsNullOrEmpty(textLeft)) DrawText(textLeft, (int)bounds.X - MeasureText(textLeft, 10) - 5, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
if (!string.IsNullOrEmpty(textRight)) DrawText(textRight, (int)(bounds.X + bounds.Width + 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
SetConfigFlags(ConfigFlags.Msaa4xHint);
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - rlgl color wheel");
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new RlglColorWheel();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,201 @@
/*******************************************************************************************
*
* raylib [shapes] example - rlgl triangle
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by Robin (@RobinsAviary) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Robin (@RobinsAviary)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Rlgl;
namespace Examples.Shapes;
public partial class RlglTriangle : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Shapes / RLGL Triangle";
public string Title => "raylib [shapes] example - rlgl triangle";
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
// Starting postions and rendered triangle positions
private Vector2[] startingPositions;
private Vector2[] trianglePositions;
// Currently selected vertex, -1 means none
private int triangleIndex;
private bool linesMode;
private float handleRadius;
public void Init()
{
// Starting postions and rendered triangle positions
startingPositions = [new(400.0f, 150.0f), new(300.0f, 300.0f), new(500.0f, 300.0f)];
trianglePositions = [startingPositions[0], startingPositions[1], startingPositions[2]];
// Currently selected vertex, -1 means none
triangleIndex = -1;
linesMode = false;
handleRadius = 8.0f;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
if (IsKeyPressed(KeyboardKey.Space)) linesMode = !linesMode;
// Check selected vertex
for (int i = 0; i < 3; i++)
{
// If the mouse is within the handle circle
if (CheckCollisionPointCircle(GetMousePosition(), trianglePositions[i], handleRadius) &&
IsMouseButtonDown(MouseButton.Left))
{
triangleIndex = i;
break;
}
}
// If the user has selected a vertex, offset it by the mouse's delta this frame
if (triangleIndex != -1)
{
Vector2 mouseDelta = GetMouseDelta();
trianglePositions[triangleIndex].X += mouseDelta.X;
trianglePositions[triangleIndex].Y += mouseDelta.Y;
}
// Reset index on release
if (IsMouseButtonReleased(MouseButton.Left)) triangleIndex = -1;
// Enable/disable backface culling (2-sided triangles, slower to render)
if (IsKeyPressed(KeyboardKey.Left)) EnableBackfaceCulling();
if (IsKeyPressed(KeyboardKey.Right)) DisableBackfaceCulling();
// Reset triangle vertices to starting positions and reset backface culling
if (IsKeyPressed(KeyboardKey.R))
{
trianglePositions[0] = startingPositions[0];
trianglePositions[1] = startingPositions[1];
trianglePositions[2] = startingPositions[2];
EnableBackfaceCulling();
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
if (linesMode)
{
// Draw triangle with lines
Begin(DrawMode.Lines);
// Three lines, six points
// Define color for next vertex
Color4ub(255, 0, 0, 255);
// Define vertex
Vertex2f(trianglePositions[0].X, trianglePositions[0].Y);
Color4ub(0, 255, 0, 255);
Vertex2f(trianglePositions[1].X, trianglePositions[1].Y);
Color4ub(0, 255, 0, 255);
Vertex2f(trianglePositions[1].X, trianglePositions[1].Y);
Color4ub(0, 0, 255, 255);
Vertex2f(trianglePositions[2].X, trianglePositions[2].Y);
Color4ub(0, 0, 255, 255);
Vertex2f(trianglePositions[2].X, trianglePositions[2].Y);
Color4ub(255, 0, 0, 255);
Vertex2f(trianglePositions[0].X, trianglePositions[0].Y);
End();
}
else
{
// Draw triangle as a triangle
Begin(DrawMode.Triangles);
// One triangle, three points
// Define color for next vertex
Color4ub(255, 0, 0, 255);
// Define vertex
Vertex2f(trianglePositions[0].X, trianglePositions[0].Y);
Color4ub(0, 255, 0, 255);
Vertex2f(trianglePositions[1].X, trianglePositions[1].Y);
Color4ub(0, 0, 255, 255);
Vertex2f(trianglePositions[2].X, trianglePositions[2].Y);
End();
}
// Render the vertex handles, reacting to mouse movement/input
for (int i = 0; i < 3; i++)
{
// Draw handle fill focused by mouse
if (CheckCollisionPointCircle(GetMousePosition(), trianglePositions[i], handleRadius))
DrawCircleV(trianglePositions[i], handleRadius, ColorAlpha(Color.DarkGray, 0.5f));
// Draw handle fill selected
if (i == triangleIndex) DrawCircleV(trianglePositions[i], handleRadius, Color.DarkGray);
// Draw handle outline
DrawCircleLinesV(trianglePositions[i], handleRadius, Color.Black);
}
// Draw controls
DrawText("SPACE: Toggle lines mode", 10, 10, 20, Color.DarkGray);
DrawText("LEFT-RIGHT: Toggle backface culling", 10, 40, 20, Color.DarkGray);
DrawText("MOUSE: Click and drag vertex points", 10, 70, 20, Color.DarkGray);
DrawText("R: Reset triangle to start positions", 10, 100, 20, Color.DarkGray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
SetConfigFlags(ConfigFlags.Msaa4xHint);
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - rlgl triangle");
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
var game = new RlglTriangle();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,308 @@
/*******************************************************************************************
*
* raylib [shapes] example - simple particles
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 5.6, last time updated with raylib 5.6
*
* Example contributed by Jordi Santonja (@JordSant)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Jordi Santonja (@JordSant)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Shapes;
public partial class SimpleParticles : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int MAX_PARTICLES = 3000; // Max number of particles
public string Name => "Shapes / Simple Particles";
public string Title => "raylib [shapes] example - simple particles";
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
private enum ParticleType
{
Water = 0,
Smoke,
Fire
}
private static readonly string[] particleTypeNames = ["WATER", "SMOKE", "FIRE"];
private struct Particle
{
public ParticleType type; // Particle type (WATER, SMOKE, FIRE)
public Vector2 position; // Particle position on screen
public Vector2 velocity; // Particle current speed and direction
public float radius; // Particle radius
public Color color; // Particle color
public float lifeTime; // Particle life time
public bool alive; // Particle alive: inside screen and life time
}
// Circular buffer state
private int head; // Index for the next write
private int tail; // Index for the next read
private Particle[] buffer; // Particle buffer array
// Particle emitter parameters
private int emissionRate; // Negative: on average every -X frames. Positive: particles per frame
private ParticleType currentType;
private Vector2 emitterPosition;
private Random random;
public void Init()
{
// Definition of particles
buffer = new Particle[MAX_PARTICLES]; // Particle array
head = 0;
tail = 0;
// Particle emitter parameters
emissionRate = -2; // Negative: on average every -X frames. Positive: particles per frame
currentType = ParticleType.Water;
emitterPosition = new(screenWidth / 2.0f, screenHeight / 2.0f);
random = new Random();
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
// Emit new particles: when emissionRate is 1, emit every frame
if (emissionRate < 0)
{
if (random.Next(-emissionRate) == 0) EmitParticle(emitterPosition, currentType);
}
else
{
for (int i = 0; i <= emissionRate; i++) EmitParticle(emitterPosition, currentType);
}
// Update the parameters of each particle
UpdateParticles(screenWidth, screenHeight);
// Remove dead particles from the circular buffer
UpdateCircularBuffer();
// Change Particle Emission Rate (UP/DOWN arrows)
if (IsKeyPressed(KeyboardKey.Up)) emissionRate++;
if (IsKeyPressed(KeyboardKey.Down)) emissionRate--;
// Change Particle Type (LEFT/RIGHT arrows)
if (IsKeyPressed(KeyboardKey.Right)) currentType = (currentType == ParticleType.Fire) ? ParticleType.Water : (ParticleType)((int)currentType + 1);
if (IsKeyPressed(KeyboardKey.Left)) currentType = (currentType == ParticleType.Water) ? ParticleType.Fire : (ParticleType)((int)currentType - 1);
if (IsMouseButtonDown(MouseButton.Left)) emitterPosition = GetMousePosition();
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
// Call the function with a loop to draw all particles
DrawParticles();
// Draw UI and Instructions
DrawRectangle(5, 5, 315, 75, Fade(Color.SkyBlue, 0.5f));
DrawRectangleLines(5, 5, 315, 75, Color.Blue);
DrawText("CONTROLS:", 15, 15, 10, Color.Black);
DrawText("UP/DOWN: Change Particle Emission Rate", 15, 35, 10, Color.Black);
DrawText("LEFT/RIGHT: Change Particle Type (Water, Smoke, Fire)", 15, 55, 10, Color.Black);
if (emissionRate < 0) DrawText($"Particles every {-emissionRate} frames | Type: {particleTypeNames[(int)currentType]}", 15, 95, 10, Color.DarkGray);
else DrawText($"{emissionRate + 1} Particles per frame | Type: {particleTypeNames[(int)currentType]}", 15, 95, 10, Color.DarkGray);
DrawFPS(screenWidth - 80, 10);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
//----------------------------------------------------------------------------------
// Module Functions Definition
//----------------------------------------------------------------------------------
private void EmitParticle(Vector2 emitterPosition, ParticleType type)
{
int index = AddToCircularBuffer();
// If buffer is full, index is -1
if (index != -1)
{
ref Particle newParticle = ref buffer[index];
// Fill particle properties
newParticle.position = emitterPosition;
newParticle.alive = true;
newParticle.lifeTime = 0.0f;
newParticle.type = type;
float speed = (float)(random.Next(10)) / 5.0f;
switch (type)
{
case ParticleType.Water:
{
newParticle.radius = 5.0f;
newParticle.color = Color.Blue;
} break;
case ParticleType.Smoke:
{
newParticle.radius = 7.0f;
newParticle.color = Color.Gray;
} break;
case ParticleType.Fire:
{
newParticle.radius = 10.0f;
newParticle.color = Color.Yellow;
speed /= 10.0f;
} break;
default: break;
}
float direction = (float)(random.Next(360));
newParticle.velocity = new(speed * MathF.Cos(direction * DEG2RAD), speed * MathF.Sin(direction * DEG2RAD));
}
}
private int AddToCircularBuffer()
{
int index = -1;
// Check if buffer full
if (((head + 1) % MAX_PARTICLES) != tail)
{
// Add new particle to the head position and advance head
index = head;
head = (head + 1) % MAX_PARTICLES;
}
return index;
}
private void UpdateParticles(int screenWidth, int screenHeight)
{
for (int i = tail; i != head; i = (i + 1) % MAX_PARTICLES)
{
// Update particle life and positions
buffer[i].lifeTime += 1.0f / 60.0f; // 60 FPS -> 1/60 seconds per frame
switch (buffer[i].type)
{
case ParticleType.Water:
{
buffer[i].position.X += buffer[i].velocity.X;
buffer[i].velocity.Y += 0.2f; // Gravity
buffer[i].position.Y += buffer[i].velocity.Y;
} break;
case ParticleType.Smoke:
{
buffer[i].position.X += buffer[i].velocity.X;
buffer[i].velocity.Y -= 0.05f; // Upwards
buffer[i].position.Y += buffer[i].velocity.Y;
buffer[i].radius += 0.5f; // Increment radius: smoke expands
buffer[i].color.A -= 4; // Decrement alpha: smoke fades
// If alpha transparent, particle dies
if (buffer[i].color.A < 4) buffer[i].alive = false;
} break;
case ParticleType.Fire:
{
// Add a little horizontal oscillation to fire particles
buffer[i].position.X += buffer[i].velocity.X + MathF.Cos(buffer[i].lifeTime * 215.0f);
buffer[i].velocity.Y -= 0.05f; // Upwards
buffer[i].position.Y += buffer[i].velocity.Y;
buffer[i].radius -= 0.15f; // Decrement radius: fire shrinks
buffer[i].color.G -= 3; // Decrement green: fire turns reddish starting from yellow
// If radius too small, particle dies
if (buffer[i].radius <= 0.02f) buffer[i].alive = false;
} break;
default: break;
}
// Disable particle when out of screen
Vector2 center = buffer[i].position;
float radius = buffer[i].radius;
if ((center.X < -radius) || (center.X > (screenWidth + radius)) ||
(center.Y < -radius) || (center.Y > (screenHeight + radius)))
{
buffer[i].alive = false;
}
}
}
private void UpdateCircularBuffer()
{
// Update circular buffer: advance tail over dead particles
while ((tail != head) && !buffer[tail].alive)
{
tail = (tail + 1) % MAX_PARTICLES;
}
}
private void DrawParticles()
{
for (int i = tail; i != head; i = (i + 1) % MAX_PARTICLES)
{
if (buffer[i].alive)
{
DrawCircleV(buffer[i].position,
buffer[i].radius,
buffer[i].color);
}
}
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - simple particles");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new SimpleParticles();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,387 @@
/*******************************************************************************************
*
* raylib [shapes] example - splines drawing
*
* Example complexity rating: [] 3/4
*
* Example originally created with raylib 5.0, last time updated with raylib 5.0
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2023-2025 Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Shapes;
public partial class SplinesDrawing : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int MAX_SPLINE_POINTS = 32;
public string Name => "Shapes / Splines Drawing";
public string Title => "raylib [shapes] example - splines drawing";
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
// Cubic Bezier spline control points
// NOTE: Every segment has two control points
private struct ControlPoint
{
public Vector2 start;
public Vector2 end;
}
// Spline types
private const int SPLINE_LINEAR = 0; // Linear
private const int SPLINE_BASIS = 1; // B-Spline
private const int SPLINE_CATMULLROM = 2; // Catmull-Rom
private const int SPLINE_BEZIER = 3; // Cubic Bezier
private Vector2[] points;
private Vector2[] pointsInterleaved;
private int pointCount;
private int selectedPoint;
private int focusedPoint;
private int selectedControlIndex; // -1 when none
private bool selectedControlStart;
private int focusedControlIndex; // -1 when none
private bool focusedControlStart;
private ControlPoint[] control;
// Spline config variables
private float splineThickness;
private int splineTypeActive; // 0-Linear, 1-BSpline, 2-CatmullRom, 3-Bezier
private bool splineTypeEditMode;
private bool splineHelpersActive;
// Minimal raygui-like global lock
private static bool guiLocked;
public void Init()
{
points = new Vector2[MAX_SPLINE_POINTS];
points[0] = new Vector2(50.0f, 400.0f);
points[1] = new Vector2(160.0f, 220.0f);
points[2] = new Vector2(340.0f, 380.0f);
points[3] = new Vector2(520.0f, 60.0f);
points[4] = new Vector2(710.0f, 260.0f);
// Array required for spline bezier-cubic,
// including control points interleaved with start-end segment points
pointsInterleaved = new Vector2[3 * (MAX_SPLINE_POINTS - 1) + 1];
pointCount = 5;
selectedPoint = -1;
focusedPoint = -1;
selectedControlIndex = -1;
focusedControlIndex = -1;
// Cubic Bezier control points initialization
control = new ControlPoint[MAX_SPLINE_POINTS - 1];
for (int i = 0; i < pointCount - 1; i++)
{
control[i].start = new Vector2(points[i].X + 50, points[i].Y);
control[i].end = new Vector2(points[i + 1].X - 50, points[i + 1].Y);
}
splineThickness = 8.0f;
splineTypeActive = SPLINE_LINEAR;
splineTypeEditMode = false;
splineHelpersActive = true;
guiLocked = false;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
// Spline points creation logic (at the end of spline)
if (IsMouseButtonPressed(MouseButton.Right) && (pointCount < MAX_SPLINE_POINTS))
{
points[pointCount] = GetMousePosition();
int i = pointCount - 1;
control[i].start = new Vector2(points[i].X + 50, points[i].Y);
control[i].end = new Vector2(points[i + 1].X - 50, points[i + 1].Y);
pointCount++;
}
// Spline point focus and selection logic
if ((selectedPoint == -1) && ((splineTypeActive != SPLINE_BEZIER) || (selectedControlIndex == -1)))
{
focusedPoint = -1;
for (int i = 0; i < pointCount; i++)
{
if (CheckCollisionPointCircle(GetMousePosition(), points[i], 8.0f))
{
focusedPoint = i;
break;
}
}
if (IsMouseButtonPressed(MouseButton.Left)) selectedPoint = focusedPoint;
}
// Spline point movement logic
if (selectedPoint >= 0)
{
points[selectedPoint] = GetMousePosition();
if (IsMouseButtonReleased(MouseButton.Left)) selectedPoint = -1;
}
// Cubic Bezier spline control points logic
if ((splineTypeActive == SPLINE_BEZIER) && (focusedPoint == -1))
{
// Spline control point focus and selection logic
if (selectedControlIndex == -1)
{
focusedControlIndex = -1;
for (int i = 0; i < pointCount - 1; i++)
{
if (CheckCollisionPointCircle(GetMousePosition(), control[i].start, 6.0f))
{
focusedControlIndex = i;
focusedControlStart = true;
break;
}
else if (CheckCollisionPointCircle(GetMousePosition(), control[i].end, 6.0f))
{
focusedControlIndex = i;
focusedControlStart = false;
break;
}
}
if (IsMouseButtonPressed(MouseButton.Left))
{
selectedControlIndex = focusedControlIndex;
selectedControlStart = focusedControlStart;
}
}
// Spline control point movement logic
if (selectedControlIndex != -1)
{
if (selectedControlStart) control[selectedControlIndex].start = GetMousePosition();
else control[selectedControlIndex].end = GetMousePosition();
if (IsMouseButtonReleased(MouseButton.Left)) selectedControlIndex = -1;
}
}
// Spline selection logic
if (IsKeyPressed(KeyboardKey.One)) splineTypeActive = 0;
else if (IsKeyPressed(KeyboardKey.Two)) splineTypeActive = 1;
else if (IsKeyPressed(KeyboardKey.Three)) splineTypeActive = 2;
else if (IsKeyPressed(KeyboardKey.Four)) splineTypeActive = 3;
// Clear selection when changing to a spline without control points
if (IsKeyPressed(KeyboardKey.One) || IsKeyPressed(KeyboardKey.Two) || IsKeyPressed(KeyboardKey.Three)) selectedControlIndex = -1;
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
if (splineTypeActive == SPLINE_LINEAR)
{
// Draw spline: linear
DrawSplineLinear(points, pointCount, splineThickness, Color.Red);
}
else if (splineTypeActive == SPLINE_BASIS)
{
// Draw spline: basis
DrawSplineBasis(points, pointCount, splineThickness, Color.Red); // Provide connected points array
}
else if (splineTypeActive == SPLINE_CATMULLROM)
{
// Draw spline: catmull-rom
DrawSplineCatmullRom(points, pointCount, splineThickness, Color.Red); // Provide connected points array
}
else if (splineTypeActive == SPLINE_BEZIER)
{
// NOTE: Cubic-bezier spline requires the 2 control points of each segment to be
// provided interleaved with the start and end point of every segment
for (int i = 0; i < (pointCount - 1); i++)
{
pointsInterleaved[3 * i] = points[i];
pointsInterleaved[3 * i + 1] = control[i].start;
pointsInterleaved[3 * i + 2] = control[i].end;
}
pointsInterleaved[3 * (pointCount - 1)] = points[pointCount - 1];
// Draw spline: cubic-bezier (with control points)
DrawSplineBezierCubic(pointsInterleaved, 3 * (pointCount - 1) + 1, splineThickness, Color.Red);
// Draw spline control points
for (int i = 0; i < pointCount - 1; i++)
{
// Every cubic bezier point have two control points
DrawCircleV(control[i].start, 6, Color.Gold);
DrawCircleV(control[i].end, 6, Color.Gold);
if (focusedControlIndex == i && focusedControlStart) DrawCircleV(control[i].start, 8, Color.Green);
else if (focusedControlIndex == i && !focusedControlStart) DrawCircleV(control[i].end, 8, Color.Green);
DrawLineEx(points[i], control[i].start, 1.0f, Color.LightGray);
DrawLineEx(points[i + 1], control[i].end, 1.0f, Color.LightGray);
// Draw spline control lines
DrawLineV(points[i], control[i].start, Color.Gray);
DrawLineV(control[i].end, points[i + 1], Color.Gray);
}
}
if (splineHelpersActive)
{
// Draw spline point helpers
for (int i = 0; i < pointCount; i++)
{
DrawCircleLinesV(points[i], (focusedPoint == i) ? 12.0f : 8.0f, (focusedPoint == i) ? Color.Blue : Color.DarkBlue);
if ((splineTypeActive != SPLINE_LINEAR) &&
(splineTypeActive != SPLINE_BEZIER) &&
(i < pointCount - 1)) DrawLineV(points[i], points[i + 1], Color.Gray);
DrawText($"[{points[i].X:F0}, {points[i].Y:F0}]", (int)points[i].X, (int)points[i].Y + 10, 10, Color.Black);
}
}
// Check all possible UI states that require controls lock
if (splineTypeEditMode || (selectedPoint != -1) || (selectedControlIndex != -1)) GuiLock();
// Draw spline config
GuiLabel(new Rectangle(12, 62, 140, 24), $"Spline thickness: {(int)splineThickness}");
GuiSliderBar(new Rectangle(12, 60 + 24, 140, 16), null, null, ref splineThickness, 1.0f, 40.0f);
GuiCheckBox(new Rectangle(12, 110, 20, 20), "Show point helpers", ref splineHelpersActive);
if (splineTypeEditMode) GuiUnlock();
GuiLabel(new Rectangle(12, 10, 140, 24), "Spline type:");
if (GuiDropdownBox(new Rectangle(12, 8 + 24, 140, 28), "LINEAR;BSPLINE;CATMULLROM;BEZIER", ref splineTypeActive, splineTypeEditMode)) splineTypeEditMode = !splineTypeEditMode;
GuiUnlock();
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
//----------------------------------------------------------------------------------
// Minimal raygui-like widgets (plain raylib re-implementation)
//----------------------------------------------------------------------------------
private static void GuiLock() => guiLocked = true;
private static void GuiUnlock() => guiLocked = false;
private static void GuiLabel(Rectangle bounds, string text)
{
DrawText(text, (int)bounds.X, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
private static void GuiSliderBar(Rectangle bounds, string textLeft, string textRight, ref float value, float minValue, float maxValue)
{
Vector2 mouse = GetMousePosition();
bool hover = CheckCollisionPointRec(mouse, bounds);
if (!guiLocked && hover && IsMouseButtonDown(MouseButton.Left))
{
value = minValue + ((mouse.X - bounds.X) / bounds.Width) * (maxValue - minValue);
if (value < minValue) value = minValue;
if (value > maxValue) value = maxValue;
}
DrawRectangleRec(bounds, Color.LightGray);
float pct = (value - minValue) / (maxValue - minValue);
DrawRectangleRec(new Rectangle(bounds.X, bounds.Y, bounds.Width * pct, bounds.Height), Color.SkyBlue);
DrawRectangleLinesEx(bounds, 1, Color.Gray);
if (!string.IsNullOrEmpty(textLeft)) DrawText(textLeft, (int)bounds.X - MeasureText(textLeft, 10) - 5, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
if (!string.IsNullOrEmpty(textRight)) DrawText(textRight, (int)(bounds.X + bounds.Width + 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
private static void GuiCheckBox(Rectangle bounds, string text, ref bool active)
{
Vector2 mouse = GetMousePosition();
bool hover = CheckCollisionPointRec(mouse, bounds);
if (!guiLocked && hover && IsMouseButtonPressed(MouseButton.Left)) active = !active;
DrawRectangleLinesEx(bounds, 1, hover ? Color.DarkBlue : Color.Gray);
if (active) DrawRectangle((int)bounds.X + 4, (int)bounds.Y + 4, (int)bounds.Width - 8, (int)bounds.Height - 8, Color.DarkGray);
if (text != null) DrawText(text, (int)(bounds.X + bounds.Width + 8), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
}
private static bool GuiDropdownBox(Rectangle bounds, string text, ref int active, bool editMode)
{
string[] items = text.Split(';');
bool pressed = false;
Vector2 mouse = GetMousePosition();
bool clickable = !guiLocked;
bool hoverMain = CheckCollisionPointRec(mouse, bounds);
DrawRectangleRec(bounds, editMode ? Color.SkyBlue : (hoverMain ? Color.LightGray : Color.RayWhite));
DrawRectangleLinesEx(bounds, 1, editMode ? Color.Blue : Color.Gray);
DrawText(items[active], (int)bounds.X + 6, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
if (editMode)
{
for (int i = 0; i < items.Length; i++)
{
Rectangle itemRec = new Rectangle(bounds.X, bounds.Y + bounds.Height * (i + 1), bounds.Width, bounds.Height);
bool hoverItem = CheckCollisionPointRec(mouse, itemRec);
DrawRectangleRec(itemRec, hoverItem ? Color.SkyBlue : Color.RayWhite);
DrawRectangleLinesEx(itemRec, 1, Color.Gray);
DrawText(items[i], (int)itemRec.X + 6, (int)(itemRec.Y + itemRec.Height / 2 - 5), 10, Color.DarkGray);
if (clickable && hoverItem && IsMouseButtonPressed(MouseButton.Left))
{
active = i;
pressed = true;
}
}
}
if (clickable && hoverMain && IsMouseButtonPressed(MouseButton.Left)) pressed = true;
return pressed;
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
SetConfigFlags(ConfigFlags.Msaa4xHint);
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - splines drawing");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new SplinesDrawing();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,180 @@
/*******************************************************************************************
*
* raylib [shapes] example - starfield effect
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 5.5, last time updated with raylib 6.0
*
* Example contributed by JP Mortiboys (@themushroompirates) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 JP Mortiboys (@themushroompirates)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Raymath; // Required for: Lerp()
namespace Examples.Shapes;
public partial class StarfieldEffect : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int STAR_COUNT = 420;
public string Name => "Shapes / Starfield Effect";
public string Title => "raylib [shapes] example - starfield effect";
private Color bgColor;
// Speed at which we fly forward
private float speed;
// We're either drawing lines or circles
private bool drawLines;
private Vector3[] stars;
private Vector2[] starsScreenPos;
public void Init()
{
bgColor = ColorLerp(Color.DarkBlue, Color.Black, 0.69f);
// Speed at which we fly forward
speed = 10.0f / 9.0f;
// We're either drawing lines or circles
drawLines = true;
stars = new Vector3[STAR_COUNT];
starsScreenPos = new Vector2[STAR_COUNT];
// Setup the stars with a random position
for (int i = 0; i < STAR_COUNT; i++)
{
stars[i].X = (float)GetRandomValue(-screenWidth / 2, (int)screenWidth / 2);
stars[i].Y = (float)GetRandomValue(-screenHeight / 2, (int)screenHeight / 2);
stars[i].Z = 1.0f;
}
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
// Change speed based on mouse
float mouseMove = GetMouseWheelMove();
if ((int)mouseMove != 0) speed += 2.0f * mouseMove / 9.0f;
if (speed < 0.0f) speed = 0.1f;
else if (speed > 2.0f) speed = 2.0f;
// Toggle lines / points with space bar
if (IsKeyPressed(KeyboardKey.Space)) drawLines = !drawLines;
float dt = GetFrameTime();
for (int i = 0; i < STAR_COUNT; i++)
{
// Update star's timer
stars[i].Z -= dt * speed;
// Calculate the screen position
starsScreenPos[i] = new Vector2(
screenWidth * 0.5f + stars[i].X / stars[i].Z,
screenHeight * 0.5f + stars[i].Y / stars[i].Z
);
// If the star is too old, or offscreen, it dies and we make a new random one
if ((stars[i].Z < 0.0f) || (starsScreenPos[i].X < 0) || (starsScreenPos[i].Y < 0.0f) ||
(starsScreenPos[i].X > screenWidth) || (starsScreenPos[i].Y > screenHeight))
{
stars[i].X = (float)GetRandomValue(-screenWidth / 2, screenWidth / 2);
stars[i].Y = (float)GetRandomValue(-screenHeight / 2, screenHeight / 2);
stars[i].Z = 1.0f;
}
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(bgColor);
for (int i = 0; i < STAR_COUNT; i++)
{
if (drawLines)
{
// Get the time a little while ago for this star, but clamp it
float t = Clamp(stars[i].Z + 1.0f / 32.0f, 0.0f, 1.0f);
// If it's different enough from the current time, we proceed
if ((t - stars[i].Z) > 1e-3)
{
// Calculate the screen position of the old point
Vector2 startPos = new Vector2(
screenWidth * 0.5f + stars[i].X / t,
screenHeight * 0.5f + stars[i].Y / t
);
// Draw a line connecting the old point to the current point
DrawLineV(startPos, starsScreenPos[i], Color.RayWhite);
}
}
else
{
// Make the radius grow as the star ages
float radius = Lerp(stars[i].Z, 1.0f, 5.0f);
// Draw the circle
DrawCircleV(starsScreenPos[i], radius, Color.RayWhite);
}
}
DrawText($"[MOUSE WHEEL] Current Speed: {9.0f * speed / 2.0f:F0}", 10, 40, 20, Color.RayWhite);
DrawText($"[SPACE] Current draw mode: {(drawLines ? "Lines" : "Circles")}", 10, 70, 20, Color.RayWhite);
DrawFPS(10, 10);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - starfield effect");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new StarfieldEffect();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,406 @@
/*******************************************************************************************
*
* raylib [shapes] example - top down lights
*
* Example complexity rating: [] 4/4
*
* Example originally created with raylib 4.2, last time updated with raylib 4.2
*
* Example contributed by Jeffery Myers (@JeffM2501) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2022-2025 Jeffery Myers (@JeffM2501)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Raymath;
using static Raylib_cs.Rlgl;
namespace Examples.Shapes;
public partial class TopDownLights : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
// Custom Blend Modes
private const int RLGL_SRC_ALPHA = 0x0302;
private const int RLGL_MIN = 0x8007;
private const int RLGL_MAX = 0x8008;
private const int MAX_BOXES = 20;
private const int MAX_SHADOWS = MAX_BOXES * 3; // MAX_BOXES*3 - Each box can cast up to two shadow volumes for the edges it is away from, and one for the box itself
private const int MAX_LIGHTS = 16;
public string Name => "Shapes / Top Down Lights";
public string Title => "raylib [shapes] example - top down lights";
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
// Shadow geometry type
private class ShadowGeometry
{
public Vector2[] vertices = new Vector2[4];
}
// Light info type
private class LightInfo
{
public bool active; // Is this light slot active?
public bool dirty; // Does this light need to be updated?
public bool valid; // Is this light in a valid position?
public Vector2 position; // Light position
public RenderTexture2D mask; // Alpha mask for the light
public float outerRadius; // The distance the light touches
public Rectangle bounds; // A cached rectangle of the light bounds to help with culling
public ShadowGeometry[] shadows = new ShadowGeometry[MAX_SHADOWS];
public int shadowCount;
public LightInfo()
{
for (int i = 0; i < MAX_SHADOWS; i++) shadows[i] = new ShadowGeometry();
}
}
//------------------------------------------------------------------------------------
// Global Variables Definition
//------------------------------------------------------------------------------------
private LightInfo[] lights;
private int boxCount;
private Rectangle[] boxes;
private Texture2D backgroundTexture;
private RenderTexture2D lightMask;
private int nextLight;
private bool showLines;
public void Init()
{
lights = new LightInfo[MAX_LIGHTS];
for (int i = 0; i < MAX_LIGHTS; i++) lights[i] = new LightInfo();
// Initialize our 'world' of boxes
boxCount = 0;
boxes = new Rectangle[MAX_BOXES];
SetupBoxes();
// Create a checkerboard ground texture
Image img = GenImageChecked(64, 64, 32, 32, Color.DarkBrown, Color.DarkGray);
backgroundTexture = LoadTextureFromImage(img);
UnloadImage(img);
// Create a global light mask to hold all the blended lights
lightMask = LoadRenderTexture(GetScreenWidth(), GetScreenHeight());
// Setup initial light
SetupLight(0, 600, 400, 300);
nextLight = 1;
showLines = false;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
// Drag light 0
if (IsMouseButtonDown(MouseButton.Left)) MoveLight(0, GetMousePosition().X, GetMousePosition().Y);
// Make a new light
if (IsMouseButtonPressed(MouseButton.Right) && (nextLight < MAX_LIGHTS))
{
SetupLight(nextLight, GetMousePosition().X, GetMousePosition().Y, 200);
nextLight++;
}
// Toggle debug info
if (IsKeyPressed(KeyboardKey.F1)) showLines = !showLines;
// Update the lights and keep track if any were dirty so we know if we need to update the master light mask
bool dirtyLights = false;
for (int i = 0; i < MAX_LIGHTS; i++)
{
if (UpdateLight(i, boxes, boxCount)) dirtyLights = true;
}
// Update the light mask
if (dirtyLights)
{
// Build up the light mask
BeginTextureMode(lightMask);
ClearBackground(Color.Black);
// Force the blend mode to only set the alpha of the destination
SetBlendFactors(RLGL_SRC_ALPHA, RLGL_SRC_ALPHA, RLGL_MIN);
SetBlendMode(BlendMode.Custom);
// Merge in all the light masks
for (int i = 0; i < MAX_LIGHTS; i++)
{
if (lights[i].active) DrawTextureRec(lights[i].mask.Texture, new Rectangle(0, 0, (float)GetScreenWidth(), -(float)GetScreenHeight()), Vector2Zero(), Color.White);
}
DrawRenderBatchActive();
// Go back to normal blend
SetBlendMode(BlendMode.Alpha);
EndTextureMode();
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.Black);
// Draw the tile background
DrawTextureRec(backgroundTexture, new Rectangle(0, 0, (float)GetScreenWidth(), (float)GetScreenHeight()), Vector2Zero(), Color.White);
// Overlay the shadows from all the lights
DrawTextureRec(lightMask.Texture, new Rectangle(0, 0, (float)GetScreenWidth(), -(float)GetScreenHeight()), Vector2Zero(), ColorAlpha(Color.White, showLines ? 0.75f : 1.0f));
// Draw the lights
for (int i = 0; i < MAX_LIGHTS; i++)
{
if (lights[i].active) DrawCircle((int)lights[i].position.X, (int)lights[i].position.Y, 10, (i == 0) ? Color.Yellow : Color.White);
}
if (showLines)
{
for (int s = 0; s < lights[0].shadowCount; s++)
{
DrawTriangleFan(lights[0].shadows[s].vertices, 4, Color.DarkPurple);
}
for (int b = 0; b < boxCount; b++)
{
if (CheckCollisionRecs(boxes[b], lights[0].bounds)) DrawRectangleRec(boxes[b], Color.Purple);
DrawRectangleLines((int)boxes[b].X, (int)boxes[b].Y, (int)boxes[b].Width, (int)boxes[b].Height, Color.DarkBlue);
}
DrawText("(F1) Hide Shadow Volumes", 10, 50, 10, Color.Green);
}
else
{
DrawText("(F1) Show Shadow Volumes", 10, 50, 10, Color.Green);
}
DrawFPS(screenWidth - 80, 10);
DrawText("Drag to move light #1", 10, 10, 10, Color.DarkGreen);
DrawText("Right click to add new light", 10, 30, 10, Color.DarkGreen);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadTexture(backgroundTexture);
UnloadRenderTexture(lightMask);
for (int i = 0; i < MAX_LIGHTS; i++)
{
if (lights[i].active) UnloadRenderTexture(lights[i].mask);
}
}
//------------------------------------------------------------------------------------
// Module Functions Definition
//------------------------------------------------------------------------------------
// Move a light and mark it as dirty so that we update it's mask next frame
private void MoveLight(int slot, float x, float y)
{
lights[slot].dirty = true;
lights[slot].position.X = x;
lights[slot].position.Y = y;
// update the cached bounds
lights[slot].bounds.X = x - lights[slot].outerRadius;
lights[slot].bounds.Y = y - lights[slot].outerRadius;
}
// Compute a shadow volume for the edge
// It takes the edge and projects it back by the light radius and turns it into a quad
private void ComputeShadowVolumeForEdge(int slot, Vector2 sp, Vector2 ep)
{
if (lights[slot].shadowCount >= MAX_SHADOWS) return;
float extension = lights[slot].outerRadius * 2;
Vector2 spVector = Vector2Normalize(Vector2Subtract(sp, lights[slot].position));
Vector2 spProjection = Vector2Add(sp, Vector2Scale(spVector, extension));
Vector2 epVector = Vector2Normalize(Vector2Subtract(ep, lights[slot].position));
Vector2 epProjection = Vector2Add(ep, Vector2Scale(epVector, extension));
lights[slot].shadows[lights[slot].shadowCount].vertices[0] = sp;
lights[slot].shadows[lights[slot].shadowCount].vertices[1] = ep;
lights[slot].shadows[lights[slot].shadowCount].vertices[2] = epProjection;
lights[slot].shadows[lights[slot].shadowCount].vertices[3] = spProjection;
lights[slot].shadowCount++;
}
// Setup a light
private void SetupLight(int slot, float x, float y, float radius)
{
lights[slot].active = true;
lights[slot].valid = false; // The light must prove it is valid
lights[slot].mask = LoadRenderTexture(GetScreenWidth(), GetScreenHeight());
lights[slot].outerRadius = radius;
lights[slot].bounds.Width = radius * 2;
lights[slot].bounds.Height = radius * 2;
MoveLight(slot, x, y);
// Force the render texture to have something in it
DrawLightMask(slot);
}
// See if a light needs to update it's mask
private bool UpdateLight(int slot, Rectangle[] boxes, int count)
{
if (!lights[slot].active || !lights[slot].dirty) return false;
lights[slot].dirty = false;
lights[slot].shadowCount = 0;
lights[slot].valid = false;
for (int i = 0; i < count; i++)
{
// Are we in a box? if so we are not valid
if (CheckCollisionPointRec(lights[slot].position, boxes[i])) return false;
// If this box is outside our bounds, we can skip it
if (!CheckCollisionRecs(lights[slot].bounds, boxes[i])) continue;
// Check the edges that are on the same side we are, and cast shadow volumes out from them
// Top
Vector2 sp = new Vector2(boxes[i].X, boxes[i].Y);
Vector2 ep = new Vector2(boxes[i].X + boxes[i].Width, boxes[i].Y);
if (lights[slot].position.Y > ep.Y) ComputeShadowVolumeForEdge(slot, sp, ep);
// Right
sp = ep;
ep.Y += boxes[i].Height;
if (lights[slot].position.X < ep.X) ComputeShadowVolumeForEdge(slot, sp, ep);
// Bottom
sp = ep;
ep.X -= boxes[i].Width;
if (lights[slot].position.Y < ep.Y) ComputeShadowVolumeForEdge(slot, sp, ep);
// Left
sp = ep;
ep.Y -= boxes[i].Height;
if (lights[slot].position.X > ep.X) ComputeShadowVolumeForEdge(slot, sp, ep);
// The box itself
lights[slot].shadows[lights[slot].shadowCount].vertices[0] = new Vector2(boxes[i].X, boxes[i].Y);
lights[slot].shadows[lights[slot].shadowCount].vertices[1] = new Vector2(boxes[i].X, boxes[i].Y + boxes[i].Height);
lights[slot].shadows[lights[slot].shadowCount].vertices[2] = new Vector2(boxes[i].X + boxes[i].Width, boxes[i].Y + boxes[i].Height);
lights[slot].shadows[lights[slot].shadowCount].vertices[3] = new Vector2(boxes[i].X + boxes[i].Width, boxes[i].Y);
lights[slot].shadowCount++;
}
lights[slot].valid = true;
DrawLightMask(slot);
return true;
}
// Draw the light and shadows to the mask for a light
private void DrawLightMask(int slot)
{
// Use the light mask
BeginTextureMode(lights[slot].mask);
ClearBackground(Color.White);
// Force the blend mode to only set the alpha of the destination
SetBlendFactors(RLGL_SRC_ALPHA, RLGL_SRC_ALPHA, RLGL_MIN);
SetBlendMode(BlendMode.Custom);
// If we are valid, then draw the light radius to the alpha mask
if (lights[slot].valid) DrawCircleGradient(lights[slot].position, lights[slot].outerRadius, ColorAlpha(Color.White, 0), Color.White);
DrawRenderBatchActive();
// Cut out the shadows from the light radius by forcing the alpha to maximum
SetBlendMode(BlendMode.Alpha);
SetBlendFactors(RLGL_SRC_ALPHA, RLGL_SRC_ALPHA, RLGL_MAX);
SetBlendMode(BlendMode.Custom);
// Draw the shadows to the alpha mask
for (int i = 0; i < lights[slot].shadowCount; i++)
{
DrawTriangleFan(lights[slot].shadows[i].vertices, 4, Color.White);
}
DrawRenderBatchActive();
// Go back to normal blend mode
SetBlendMode(BlendMode.Alpha);
EndTextureMode();
}
// Set up some boxes
private void SetupBoxes()
{
boxes[0] = new Rectangle(150, 80, 40, 40);
boxes[1] = new Rectangle(1200, 700, 40, 40);
boxes[2] = new Rectangle(200, 600, 40, 40);
boxes[3] = new Rectangle(1000, 50, 40, 40);
boxes[4] = new Rectangle(500, 350, 40, 40);
for (int i = 5; i < MAX_BOXES; i++)
{
boxes[i] = new Rectangle((float)GetRandomValue(0, GetScreenWidth()), (float)GetRandomValue(0, GetScreenHeight()), (float)GetRandomValue(10, 100), (float)GetRandomValue(10, 100));
}
boxCount = MAX_BOXES;
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - top down lights");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new TopDownLights();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,141 @@
/*******************************************************************************************
*
* raylib [shapes] example - triangle strip
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by Jopestpe (@jopestpe)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Jopestpe (@jopestpe)
*
********************************************************************************************/
using System;
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Shapes;
public partial class TriangleStrip : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Shapes / Triangle Strip";
public string Title => "raylib [shapes] example - triangle strip";
private Vector2[] points;
private Vector2 center;
private float segments;
private float insideRadius;
private float outsideRadius;
private bool outline;
public void Init()
{
points = new Vector2[122];
center = new((screenWidth / 2.0f) - 125.0f, screenHeight / 2.0f);
segments = 6.0f;
insideRadius = 100.0f;
outsideRadius = 150.0f;
outline = true;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
int pointCount = (int)(segments);
float angleStep = (360.0f / pointCount) * DEG2RAD;
for (int i = 0, i2 = 0; i < pointCount; i++, i2 += 2)
{
float angle1 = i * angleStep;
points[i2] = new Vector2(center.X + MathF.Cos(angle1) * insideRadius, center.Y + MathF.Sin(angle1) * insideRadius);
float angle2 = angle1 + angleStep / 2.0f;
points[i2 + 1] = new Vector2(center.X + MathF.Cos(angle2) * outsideRadius, center.Y + MathF.Sin(angle2) * outsideRadius);
}
points[pointCount * 2] = points[0];
points[pointCount * 2 + 1] = points[1];
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
for (int i = 0; i < pointCount; i++)
{
Vector2 a = points[i * 2];
Vector2 b = points[i * 2 + 1];
Vector2 c = points[i * 2 + 2];
Vector2 d = points[i * 2 + 3];
float angle1 = i * angleStep;
DrawTriangle(c, b, a, ColorFromHSV(angle1 * RAD2DEG, 1.0f, 1.0f));
DrawTriangle(d, b, c, ColorFromHSV((angle1 + angleStep / 2) * RAD2DEG, 1.0f, 1.0f));
if (outline)
{
DrawTriangleLines(a, b, c, Color.Black);
DrawTriangleLines(c, b, d, Color.Black);
}
}
DrawLine(580, 0, 580, GetScreenHeight(), new Color(218, 218, 218, 255));
DrawRectangle(580, 0, GetScreenWidth(), GetScreenHeight(), new Color(232, 232, 232, 255));
// Draw GUI controls
//------------------------------------------------------------------------------
// NOTE: raygui is not bound in raylib-cs, so the interactive controls are omitted
// and 'segments'/'outline' keep their initial values.
//GuiSliderBar(new Rectangle(640, 40, 120, 20), "Segments", TextFormat("%.0f", segments), ref segments, 6.0f, 60.0f);
//GuiCheckBox(new Rectangle(640, 70, 20, 20), "Outline", ref outline);
//------------------------------------------------------------------------------
DrawFPS(10, 10);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - triangle strip");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new TriangleStrip();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,154 @@
/*******************************************************************************************
*
* raylib [shapes] example - vector angle
*
* Example complexity rating: [] 2/4
*
* Example originally created with raylib 1.0, last time updated with raylib 5.0
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2023-2025 Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using System.Numerics;
using static Raylib_cs.Raylib;
using static Raylib_cs.Raymath; // Required for: Vector2LineAngle()
namespace Examples.Shapes;
public partial class VectorAngle : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Shapes / Vector Angle";
public string Title => "raylib [shapes] example - vector angle";
private Vector2 v0;
private Vector2 v1;
private Vector2 v2; // Updated with mouse position
private float angle; // Angle in degrees
private int angleMode; // 0-Vector2Angle(), 1-Vector2LineAngle()
public void Init()
{
v0 = new(screenWidth / 2.0f, screenHeight / 2.0f);
v1 = Vector2Add(v0, new Vector2(100.0f, 80.0f));
v2 = new(0, 0); // Updated with mouse position
angle = 0.0f; // Angle in degrees
angleMode = 0; // 0-Vector2Angle(), 1-Vector2LineAngle()
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
float startangle = 0.0f;
if (angleMode == 0) startangle = -Vector2LineAngle(v0, v1) * RAD2DEG;
if (angleMode == 1) startangle = 0.0f;
v2 = GetMousePosition();
if (IsKeyPressed(KeyboardKey.Space)) angleMode = (angleMode == 0) ? 1 : 0;
if ((angleMode == 0) && IsMouseButtonDown(MouseButton.Right)) v1 = GetMousePosition();
if (angleMode == 0)
{
// Calculate angle between two vectors, considering a common origin (v0)
Vector2 v1Normal = Vector2Normalize(Vector2Subtract(v1, v0));
Vector2 v2Normal = Vector2Normalize(Vector2Subtract(v2, v0));
angle = Vector2Angle(v1Normal, v2Normal) * RAD2DEG;
}
else if (angleMode == 1)
{
// Calculate angle defined by a two vectors line, in reference to horizontal line
angle = Vector2LineAngle(v0, v2) * RAD2DEG;
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
if (angleMode == 0)
{
DrawText("MODE 0: Angle between V1 and V2", 10, 10, 20, Color.Black);
DrawText("Right Click to Move V2", 10, 30, 20, Color.DarkGray);
DrawLineEx(v0, v1, 2.0f, Color.Black);
DrawLineEx(v0, v2, 2.0f, Color.Red);
DrawCircleSector(v0, 40.0f, startangle, startangle + angle, 32, Fade(Color.Green, 0.6f));
}
else if (angleMode == 1)
{
DrawText("MODE 1: Angle formed by line V1 to V2", 10, 10, 20, Color.Black);
DrawLine(0, screenHeight / 2, screenWidth, screenHeight / 2, Color.LightGray);
DrawLineEx(v0, v2, 2.0f, Color.Red);
DrawCircleSector(v0, 40.0f, startangle, startangle - angle, 32, Fade(Color.Green, 0.6f));
}
DrawText("v0", (int)v0.X, (int)v0.Y, 10, Color.DarkGray);
// If the line from v0 to v1 would overlap the text, move it's position up 10
if (angleMode == 0 && Vector2Subtract(v0, v1).Y > 0.0f) DrawText("v1", (int)v1.X, (int)v1.Y - 10, 10, Color.DarkGray);
if (angleMode == 0 && Vector2Subtract(v0, v1).Y < 0.0f) DrawText("v1", (int)v1.X, (int)v1.Y, 10, Color.DarkGray);
// If angle mode 1, use v1 to emphasize the horizontal line
if (angleMode == 1) DrawText("v1", (int)v0.X + 40, (int)v0.Y, 10, Color.DarkGray);
// position adjusted by -10 so it isn't hidden by cursor
DrawText("v2", (int)v2.X - 10, (int)v2.Y - 10, 10, Color.DarkGray);
DrawText("Press SPACE to change MODE", 460, 10, 20, Color.DarkGray);
DrawText($"ANGLE: {angle:F2}", 10, 70, 20, Color.Lime);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - vector angle");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new VectorAngle();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,312 @@
/*******************************************************************************************
*
* raylib [text] example - inline styling
*
* Example complexity rating: [] 3/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by Wagner Barongello (@SultansOfCode) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 Wagner Barongello (@SultansOfCode) and Ramon Santamaria (@raysan5)
*
********************************************************************************************/
using System;
using System.Numerics;
using System.Text;
using Raylib_cs;
using static Raylib_cs.Raylib;
namespace Examples.Text;
public partial class InlineStyling : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
public string Name => "Text / Inline Styling";
public string Title => "raylib [text] example - inline styling";
private Vector2 textSize; // Measure text box for provided font and text
private Color colRandom; // Random color used on text
private int frameCounter; // Used to generate a new random color every certain frames
public void Init()
{
textSize = new(0, 0);
colRandom = Color.Red;
frameCounter = 0;
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
frameCounter++;
if ((frameCounter % 20) == 0)
{
colRandom.R = (byte)GetRandomValue(0, 255);
colRandom.G = (byte)GetRandomValue(0, 255);
colRandom.B = (byte)GetRandomValue(0, 255);
colRandom.A = 255;
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
// Text inline styling strategy used: [ ] delimiters for format
// - Define foreground color: [cRRGGBBAA]
// - Define background color: [bRRGGBBAA]
// - Reset formating: [r]
// Colors defined with [cRRGGBBAA] or [bRRGGBBAA] are multiplied by the base color alpha
// This allows global transparency control while keeping per-section styling (ex. text fade effects)
// Example: [bAA00AAFF][cFF0000FF]red text on gray background[r] normal text
DrawTextStyled(GetFontDefault(), "This changes the [cFF0000FF]foreground color[r] of provided text!!!",
new Vector2(100, 80), 20.0f, 2.0f, Color.Black);
DrawTextStyled(GetFontDefault(), "This changes the [bFF00FFFF]background color[r] of provided text!!!",
new Vector2(100, 120), 20.0f, 2.0f, Color.Black);
DrawTextStyled(GetFontDefault(), "This changes the [c00ff00ff][bff0000ff]foreground and background colors[r]!!!",
new Vector2(100, 160), 20.0f, 2.0f, Color.Black);
DrawTextStyled(GetFontDefault(), "This changes the [c00ff00ff]alpha[r] relative [cffffffff][b000000ff]from source[r] [cff000088]color[r]!!!",
new Vector2(100, 200), 20.0f, 2.0f, new Color(0, 0, 0, 100));
// Get pointer to formated text
string text = $"Let's be [c{colRandom.R:x2}{colRandom.G:x2}{colRandom.B:x2}FF]CREATIVE[r] !!!";
DrawTextStyled(GetFontDefault(), text, new Vector2(100, 240), 40.0f, 2.0f, Color.Black);
textSize = MeasureTextStyled(GetFontDefault(), text, 40.0f, 2.0f);
DrawRectangleLines(100, 240, (int)textSize.X, (int)textSize.Y, Color.Green);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
//----------------------------------------------------------------------------------
// Module Functions Definition
//----------------------------------------------------------------------------------
// Draw text using inline styling
// PARAM: color is the default text color, background color is BLANK by default
// NOTE: Using input color as the base alpha multiplied to inline styles
private static unsafe void DrawTextStyled(Font font, string text, Vector2 position, float fontSize, float spacing, Color color)
{
// Text inline styling strategy used: [ ] delimiters for format
// - Define foreground color: [cRRGGBBAA]
// - Define background color: [bRRGGBBAA]
// - Reset formating: [r]
// Example: [bAA00AAFF][cFF0000FF]red text on gray background[r] normal text
if (font.Texture.Id == 0)
{
font = GetFontDefault();
}
using var textNative = new Utf8Buffer(text);
sbyte* t = textNative.AsPointer();
int textLen = Encoding.UTF8.GetByteCount(text);
Color colFront = color;
Color colBack = Color.Blank;
int backRecPadding = 4; // Background rectangle padding
float textOffsetY = 0.0f;
float textOffsetX = 0.0f;
float textLineSpacing = 0.0f;
float scaleFactor = fontSize / font.BaseSize;
for (int i = 0; i < textLen;)
{
int codepointByteCount = 0;
int codepoint = GetCodepointNext(&t[i], &codepointByteCount);
if (codepoint == '\n')
{
textOffsetY += (fontSize + textLineSpacing);
textOffsetX = 0.0f;
}
else
{
if (codepoint == '[') // Process pipe styling
{
if (((i + 2) < textLen) && ((char)t[i + 1] == 'r') && ((char)t[i + 2] == ']')) // Reset styling
{
colFront = color;
colBack = Color.Blank;
i += 3; // Skip "[r]"
continue; // Do not draw characters
}
else if (((i + 1) < textLen) && (((char)t[i + 1] == 'c') || ((char)t[i + 1] == 'b')))
{
i += 2; // Skip "[c" or "[b" to start parsing color
// Parse following color
var colHexText = new StringBuilder();
int colHexCount = 0;
while ((i + colHexCount < textLen) && (t[i + colHexCount] != 0) && ((char)t[i + colHexCount] != ']'))
{
char ch = (char)t[i + colHexCount];
if (((ch >= '0') && (ch <= '9')) ||
((ch >= 'A') && (ch <= 'F')) ||
((ch >= 'a') && (ch <= 'f')))
{
colHexText.Append(ch);
colHexCount++;
}
else break; // Only affects while loop
}
// Convert hex color text into actual Color
uint colHexValue = colHexText.Length > 0 ? Convert.ToUInt32(colHexText.ToString(), 16) : 0;
if ((char)t[i - 1] == 'c')
{
colFront = GetColor(colHexValue);
}
else if ((char)t[i - 1] == 'b')
{
colBack = GetColor(colHexValue);
}
i += (colHexCount + 1); // Skip color value retrieved and ']'
continue; // Do not draw characters
}
}
int index = GetGlyphIndex(font, codepoint);
float increaseX = 0.0f;
if (font.Glyphs[index].AdvanceX == 0) increaseX = (font.Recs[index].Width * scaleFactor + spacing);
else increaseX += (font.Glyphs[index].AdvanceX * scaleFactor + spacing);
// Draw background rectangle color (if required)
if (colBack.A > 0) DrawRectangleRec(new Rectangle(position.X + textOffsetX, position.Y + textOffsetY - backRecPadding, increaseX, fontSize + 2 * backRecPadding), colBack);
if ((codepoint != ' ') && (codepoint != '\t'))
{
DrawTextCodepoint(font, codepoint, new Vector2(position.X + textOffsetX, position.Y + textOffsetY), fontSize, colFront);
}
textOffsetX += increaseX;
}
i += codepointByteCount;
}
}
// Measure inline styled text
// NOTE: Measuring styled text requires skipping styling data
// WARNING: Not considering line breaks
private static unsafe Vector2 MeasureTextStyled(Font font, string text, float fontSize, float spacing)
{
Vector2 textSize = new(0, 0);
if ((font.Texture.Id == 0) || (text == null) || (text.Length == 0)) return textSize; // Security check
using var textNative = new Utf8Buffer(text);
sbyte* t = textNative.AsPointer();
int textLen = Encoding.UTF8.GetByteCount(text); // Get size in bytes of text
float textWidth = 0.0f;
float textHeight = fontSize;
float scaleFactor = fontSize / (float)font.BaseSize;
int codepoint = 0; // Current character
int index = 0; // Index position in sprite font
int validCodepointCounter = 0;
for (int i = 0; i < textLen;)
{
int codepointByteCount = 0;
codepoint = GetCodepointNext(&t[i], &codepointByteCount);
if (codepoint == '[') // Ignore pipe inline styling
{
if (((i + 2) < textLen) && ((char)t[i + 1] == 'r') && ((char)t[i + 2] == ']')) // Reset styling
{
i += 3; // Skip "[r]"
continue; // Do not measure characters
}
else if (((i + 1) < textLen) && (((char)t[i + 1] == 'c') || ((char)t[i + 1] == 'b')))
{
i += 2; // Skip "[c" or "[b" to start parsing color
int colHexCount = 0;
while ((i + colHexCount < textLen) && (t[i + colHexCount] != 0) && ((char)t[i + colHexCount] != ']'))
{
char ch = (char)t[i + colHexCount];
if (((ch >= '0') && (ch <= '9')) ||
((ch >= 'A') && (ch <= 'F')) ||
((ch >= 'a') && (ch <= 'f')))
{
colHexCount++;
}
else break; // Only affects while loop
}
i += (colHexCount + 1); // Skip color value retrieved and ']'
continue; // Do not measure characters
}
}
else if (codepoint != '\n')
{
index = GetGlyphIndex(font, codepoint);
if (font.Glyphs[index].AdvanceX > 0) textWidth += font.Glyphs[index].AdvanceX;
else textWidth += (font.Recs[index].Width + font.Glyphs[index].OffsetX);
validCodepointCounter++;
i += codepointByteCount;
}
}
textSize.X = textWidth * scaleFactor + (validCodepointCounter - 1) * spacing;
textSize.Y = textHeight;
return textSize;
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [text] example - inline styling");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new InlineStyling();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -0,0 +1,530 @@
/*******************************************************************************************
*
* raylib [text] example - strings management
*
* Example complexity rating: [] 3/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example contributed by David Buzatto (@davidbuzatto) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2025 David Buzatto (@davidbuzatto)
*
********************************************************************************************/
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Text;
using Raylib_cs;
using static Raylib_cs.Raylib;
namespace Examples.Text;
public partial class StringsManagement : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
private const int MaxTextLength = 100;
private const int MaxTextParticles = 100;
private const int FontSize = 30;
public string Name => "Text / Strings Management";
public string Title => "raylib [text] example - strings management";
//----------------------------------------------------------------------------------
// Types and Structures Definition
//----------------------------------------------------------------------------------
private class TextParticle
{
public string Text;
public Rectangle Rect; // Boundary
public Vector2 Vel; // Velocity
public Vector2 Ppos; // Previous position
public float Padding;
public float BorderWidth;
public float Friction;
public float Elasticity;
public Color Color;
public bool Grabbed;
}
private List<TextParticle> textParticles;
private TextParticle grabbedTextParticle;
private Vector2 pressOffset;
public void Init()
{
textParticles = new();
grabbedTextParticle = null;
pressOffset = new(0, 0);
PrepareFirstTextParticle("raylib => fun videogames programming!");
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
float delta = GetFrameTime();
Vector2 mousePos = GetMousePosition();
// Checks if a text particle was grabbed
if (IsMouseButtonPressed(MouseButton.Left))
{
for (int i = textParticles.Count - 1; i >= 0; i--)
{
TextParticle tp = textParticles[i];
if (CheckCollisionPointRec(mousePos, tp.Rect))
{
pressOffset.X = mousePos.X - tp.Rect.X;
pressOffset.Y = mousePos.Y - tp.Rect.Y;
tp.Grabbed = true;
grabbedTextParticle = tp;
break;
}
}
}
// Releases any text particle the was grabbed
if (IsMouseButtonReleased(MouseButton.Left))
{
if (grabbedTextParticle != null)
{
grabbedTextParticle.Grabbed = false;
grabbedTextParticle = null;
}
}
// Slice os shatter a text particle
if (IsMouseButtonPressed(MouseButton.Right))
{
for (int i = textParticles.Count - 1; i >= 0; i--)
{
TextParticle tp = textParticles[i];
if (CheckCollisionPointRec(mousePos, tp.Rect))
{
if (IsKeyDown(KeyboardKey.LeftShift))
{
ShatterTextParticle(tp, i);
}
else
{
SliceTextParticle(tp, i, tp.Text.Length / 2);
}
break;
}
}
}
// Shake text particles
if (IsMouseButtonPressed(MouseButton.Middle))
{
for (int i = 0; i < textParticles.Count; i++)
{
if (!textParticles[i].Grabbed)
{
textParticles[i].Vel = new Vector2(GetRandomValue(-2000, 2000), GetRandomValue(-2000, 2000));
}
}
}
// Reset using TextTo* functions
if (IsKeyPressed(KeyboardKey.One)) PrepareFirstTextParticle("raylib => fun videogames programming!");
if (IsKeyPressed(KeyboardKey.Two)) PrepareFirstTextParticle(TextToUpper("raylib => fun videogames programming!"));
if (IsKeyPressed(KeyboardKey.Three)) PrepareFirstTextParticle(TextToLower("raylib => fun videogames programming!"));
if (IsKeyPressed(KeyboardKey.Four)) PrepareFirstTextParticle(TextToPascal("raylib_fun_videogames_programming"));
if (IsKeyPressed(KeyboardKey.Five)) PrepareFirstTextParticle(TextToSnake("RaylibFunVideogamesProgramming"));
if (IsKeyPressed(KeyboardKey.Six)) PrepareFirstTextParticle(TextToCamel("raylib_fun_videogames_programming"));
// Slice by char pressed only when we have one text particle
int charPressed = GetCharPressed();
if ((charPressed >= 'A') && (charPressed <= 'z') && (textParticles.Count == 1))
{
SliceTextParticleByChar(textParticles[0], (char)charPressed);
}
// Updates each text particle state
for (int i = 0; i < textParticles.Count; i++)
{
TextParticle tp = textParticles[i];
// The text particle is not grabbed
if (!tp.Grabbed)
{
// text particle repositioning using the velocity
tp.Rect.X += tp.Vel.X * delta;
tp.Rect.Y += tp.Vel.Y * delta;
// Does the text particle hit the screen right boundary?
if ((tp.Rect.X + tp.Rect.Width) >= screenWidth)
{
tp.Rect.X = screenWidth - tp.Rect.Width; // Text particle repositioning
tp.Vel.X = -tp.Vel.X * tp.Elasticity; // Elasticity makes the text particle lose 10% of its velocity on hit
}
// Does the text particle hit the screen left boundary?
else if (tp.Rect.X <= 0)
{
tp.Rect.X = 0.0f;
tp.Vel.X = -tp.Vel.X * tp.Elasticity;
}
// The same for y axis
if ((tp.Rect.Y + tp.Rect.Height) >= screenHeight)
{
tp.Rect.Y = screenHeight - tp.Rect.Height;
tp.Vel.Y = -tp.Vel.Y * tp.Elasticity;
}
else if (tp.Rect.Y <= 0)
{
tp.Rect.Y = 0.0f;
tp.Vel.Y = -tp.Vel.Y * tp.Elasticity;
}
// Friction makes the text particle lose 1% of its velocity each frame
tp.Vel.X = tp.Vel.X * tp.Friction;
tp.Vel.Y = tp.Vel.Y * tp.Friction;
}
else
{
// Text particle repositioning using the mouse position
tp.Rect.X = mousePos.X - pressOffset.X;
tp.Rect.Y = mousePos.Y - pressOffset.Y;
// While the text particle is grabbed, recalculates its velocity
tp.Vel.X = (tp.Rect.X - tp.Ppos.X) / delta;
tp.Vel.Y = (tp.Rect.Y - tp.Ppos.Y) / delta;
tp.Ppos.X = tp.Rect.X;
tp.Ppos.Y = tp.Rect.Y;
// Glue text particles when dragging and pressing left ctrl
if (IsKeyDown(KeyboardKey.LeftControl))
{
for (int j = 0; j < textParticles.Count; j++)
{
if (textParticles[j] != grabbedTextParticle && grabbedTextParticle.Grabbed)
{
if (CheckCollisionRecs(grabbedTextParticle.Rect, textParticles[j].Rect))
{
GlueTextParticles(grabbedTextParticle, textParticles[j]);
grabbedTextParticle = textParticles[textParticles.Count - 1];
}
}
}
}
}
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
for (int i = 0; i < textParticles.Count; i++)
{
TextParticle tp = textParticles[i];
DrawRectangleRec(new Rectangle(tp.Rect.X - tp.BorderWidth, tp.Rect.Y - tp.BorderWidth, tp.Rect.Width + tp.BorderWidth * 2, tp.Rect.Height + tp.BorderWidth * 2), Color.Black);
DrawRectangleRec(tp.Rect, tp.Color);
DrawText(tp.Text, (int)(tp.Rect.X + tp.Padding), (int)(tp.Rect.Y + tp.Padding), FontSize, Color.Black);
}
DrawText("grab a text particle by pressing with the mouse and throw it by releasing", 10, 10, 10, Color.DarkGray);
DrawText("slice a text particle by pressing it with the mouse right button", 10, 30, 10, Color.DarkGray);
DrawText("shatter a text particle keeping left shift pressed and pressing it with the mouse right button", 10, 50, 10, Color.DarkGray);
DrawText("glue text particles by grabbing than and keeping left control pressed", 10, 70, 10, Color.DarkGray);
DrawText("1 to 6 to reset", 10, 90, 10, Color.DarkGray);
DrawText("when you have only one text particle, you can slice it by pressing a char", 10, 110, 10, Color.DarkGray);
DrawText($"TEXT PARTICLE COUNT: {textParticles.Count}", 10, GetScreenHeight() - 30, 20, Color.Black);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
}
//----------------------------------------------------------------------------------
// Module Functions Definition
//----------------------------------------------------------------------------------
private void PrepareFirstTextParticle(string text)
{
TextParticle first = CreateTextParticle(
text,
GetScreenWidth() / 2.0f,
GetScreenHeight() / 2.0f,
Color.RayWhite
);
textParticles.Clear();
textParticles.Add(first);
}
private static TextParticle CreateTextParticle(string text, float x, float y, Color color)
{
TextParticle tp = new()
{
Text = "",
Rect = new Rectangle(x, y, 30, 30),
Vel = new Vector2(GetRandomValue(-200, 200), GetRandomValue(-200, 200)),
Ppos = new Vector2(0, 0),
Padding = 5.0f,
BorderWidth = 5.0f,
Friction = 0.99f,
Elasticity = 0.9f,
Color = color,
Grabbed = false
};
// Emulate C TextCopy() into a fixed size buffer
if (text.Length > MaxTextLength - 1)
{
text = text.Substring(0, MaxTextLength - 1);
}
tp.Text = text;
tp.Rect.Width = MeasureText(tp.Text, FontSize) + tp.Padding * 2;
tp.Rect.Height = FontSize + tp.Padding * 2;
return tp;
}
private void SliceTextParticle(TextParticle tp, int particlePos, int sliceLength)
{
int length = tp.Text.Length;
if ((length > 1) && ((textParticles.Count + length) < MaxTextParticles))
{
for (int i = 0; i < length; i += sliceLength)
{
string text = sliceLength == 1 ? tp.Text[i].ToString() : Subtext(tp.Text, i, sliceLength);
textParticles.Add(CreateTextParticle(
text,
tp.Rect.X + i * tp.Rect.Width / length,
tp.Rect.Y,
new Color(GetRandomValue(0, 255), GetRandomValue(0, 255), GetRandomValue(0, 255), 255)
));
}
RealocateTextParticles(particlePos);
}
}
private void SliceTextParticleByChar(TextParticle tp, char charToSlice)
{
string[] tokens = tp.Text.Split(charToSlice);
int tokenCount = tokens.Length;
if (tokenCount > 1)
{
int textLength = tp.Text.Length;
for (int i = 0; i < textLength; i++)
{
if (tp.Text[i] == charToSlice)
{
textParticles.Add(CreateTextParticle(
charToSlice.ToString(),
tp.Rect.X,
tp.Rect.Y,
new Color(GetRandomValue(0, 255), GetRandomValue(0, 255), GetRandomValue(0, 255), 255)
));
}
}
for (int i = 0; i < tokenCount; i++)
{
int tokenLength = tokens[i].Length;
textParticles.Add(CreateTextParticle(
tokens[i],
tp.Rect.X + i * tp.Rect.Width / tokenLength,
tp.Rect.Y,
new Color(GetRandomValue(0, 255), GetRandomValue(0, 255), GetRandomValue(0, 255), 255)
));
}
RealocateTextParticles(0);
}
}
private void ShatterTextParticle(TextParticle tp, int particlePos)
{
SliceTextParticle(tp, particlePos, 1);
}
private void GlueTextParticles(TextParticle grabbed, TextParticle target)
{
int p1 = textParticles.IndexOf(grabbed);
int p2 = textParticles.IndexOf(target);
if ((p1 != -1) && (p2 != -1))
{
TextParticle tp = CreateTextParticle(
grabbed.Text + target.Text,
grabbed.Rect.X,
grabbed.Rect.Y,
Color.RayWhite
);
tp.Grabbed = true;
textParticles.Add(tp);
grabbed.Grabbed = false;
if (p1 < p2)
{
RealocateTextParticles(p2);
RealocateTextParticles(p1);
}
else
{
RealocateTextParticles(p1);
RealocateTextParticles(p2);
}
}
}
private void RealocateTextParticles(int particlePos)
{
textParticles.RemoveAt(particlePos);
}
// Extract a substring, clamping length to the available characters (like raylib TextSubtext)
private static string Subtext(string text, int position, int length)
{
if (position >= text.Length)
{
return "";
}
int maxLength = text.Length - position;
if (length > maxLength)
{
length = maxLength;
}
return text.Substring(position, length);
}
// C# equivalents of raylib TextTo* helpers (behaviour kept identical)
private static string TextToUpper(string text)
{
var sb = new StringBuilder(text.Length);
foreach (char c in text)
{
sb.Append((c >= 'a' && c <= 'z') ? (char)(c - 32) : c);
}
return sb.ToString();
}
private static string TextToLower(string text)
{
var sb = new StringBuilder(text.Length);
foreach (char c in text)
{
sb.Append((c >= 'A' && c <= 'Z') ? (char)(c + 32) : c);
}
return sb.ToString();
}
private static string TextToPascal(string text)
{
var sb = new StringBuilder(text.Length);
if (text.Length > 0)
{
sb.Append(char.ToUpperInvariant(text[0]));
for (int i = 1; i < text.Length; i++)
{
if (text[i] == '_' && (i + 1) < text.Length)
{
sb.Append(char.ToUpperInvariant(text[i + 1]));
i++;
}
else
{
sb.Append(text[i]);
}
}
}
return sb.ToString();
}
private static string TextToSnake(string text)
{
var sb = new StringBuilder(text.Length);
for (int i = 0; i < text.Length; i++)
{
char c = text[i];
if (c >= 'A' && c <= 'Z')
{
if (i > 0)
{
sb.Append('_');
}
sb.Append((char)(c + 32));
}
else
{
sb.Append(c);
}
}
return sb.ToString();
}
private static string TextToCamel(string text)
{
var sb = new StringBuilder(text.Length);
if (text.Length > 0)
{
sb.Append(char.ToLowerInvariant(text[0]));
for (int i = 1; i < text.Length; i++)
{
if (text[i] == '_' && (i + 1) < text.Length)
{
sb.Append(char.ToUpperInvariant(text[i + 1]));
i++;
}
else
{
sb.Append(text[i]);
}
}
}
return sb.ToString();
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [text] example - strings management");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new StringsManagement();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

739
Examples/Text/Text3D.cs Normal file
View file

@ -0,0 +1,739 @@
/*******************************************************************************************
*
* raylib [text] example - 3d drawing
*
* Example complexity rating: [] 4/4
*
* NOTE: Draw a 2D text in 3D space, each letter is drawn in a quad (or 2 quads if backface is set)
* where the texture coodinates of each quad map to the texture coordinates of the glyphs
* inside the font texture
*
* A more efficient approach, i believe, would be to render the text in a render texture and
* map that texture to a plane and render that, or maybe a shader but my method allows more
* flexibility...for example to change position of each letter individually to make somethink
* like a wavy text effect
*
* Special thanks to:
* @Nighten for the DrawTextStyle() code https://github.com/NightenDushi/Raylib_DrawTextStyle
* Chris Camacho (codifies - http://bedroomcoders.co.uk/) for the alpha discard shader
*
* Example originally created with raylib 3.5, last time updated with raylib 4.0
*
* Example contributed by Vlad Adrian (@demizdor) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2021-2025 Vlad Adrian (@demizdor)
*
********************************************************************************************/
using System;
using System.Numerics;
using System.Text;
using Raylib_cs;
using static Raylib_cs.Raylib;
namespace Examples.Text;
public partial class Text3D : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
#if BROWSER
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
#else
private const int GlslVersion = 330;
#endif
//--------------------------------------------------------------------------------------
// Global variables
//--------------------------------------------------------------------------------------
private const float LetterBoundrySize = 0.25f;
private const int TextMaxLayers = 32;
private static readonly Color LetterBoundryColor = Color.Violet;
private bool showLetterBoundry;
private bool showTextBoundry;
//--------------------------------------------------------------------------------------
// Types and Structures Definition
//--------------------------------------------------------------------------------------
// Configuration structure for waving the text
private struct WaveTextConfig
{
public Vector3 WaveRange;
public Vector3 WaveSpeed;
public Vector3 WaveOffset;
}
public string Name => "Text / Text 3D";
public string Title => "raylib [text] example - 3d drawing";
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint | ConfigFlags.VSyncHint;
public bool CursorDisabled => true;
private bool spin; // Spin the camera?
private bool multicolor; // Multicolor mode
private Camera3D camera;
private CameraMode cameraMode;
private Vector3 cubePosition;
private Vector3 cubeSize;
private Font font;
private float fontSize;
private float fontSpacing;
private float lineSpacing;
private string text;
private Vector3 tbox;
private int layers;
private int quads;
private float layerDistance;
private WaveTextConfig wcfg;
private float time;
private Color light;
private Color dark;
private Shader alphaDiscard;
private Color[] multi;
public void Init()
{
spin = true; // Spin the camera?
multicolor = false; // Multicolor mode
showLetterBoundry = false;
showTextBoundry = false;
// Define the camera to look into our 3d world
camera = new();
camera.Position = new Vector3(-10.0f, 15.0f, -10.0f); // Camera position
camera.Target = new Vector3(0.0f, 0.0f, 0.0f); // Camera looking at point
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
camera.FovY = 45.0f; // Camera field-of-view Y
camera.Projection = CameraProjection.Perspective; // Camera projection type
cameraMode = CameraMode.Orbital;
cubePosition = new Vector3(0.0f, 1.0f, 0.0f);
cubeSize = new Vector3(2.0f, 2.0f, 2.0f);
// Use the default font
font = GetFontDefault();
fontSize = 0.8f;
fontSpacing = 0.05f;
lineSpacing = -0.1f;
// Set the text (using markdown!)
text = "Hello ~~World~~ in 3D!";
tbox = new Vector3(0, 0, 0);
layers = 1;
quads = 0;
layerDistance = 0.01f;
wcfg = new WaveTextConfig();
wcfg.WaveSpeed.X = wcfg.WaveSpeed.Y = 3.0f; wcfg.WaveSpeed.Z = 0.5f;
wcfg.WaveOffset.X = wcfg.WaveOffset.Y = wcfg.WaveOffset.Z = 0.35f;
wcfg.WaveRange.X = wcfg.WaveRange.Y = wcfg.WaveRange.Z = 0.45f;
time = 0.0f;
// Setup a light and dark color
light = Color.Maroon;
dark = Color.Red;
// Load the alpha discard shader
alphaDiscard = LoadShader(null, $"resources/shaders/glsl{GlslVersion}/alpha_discard.fs");
// Array filled with multiple random colors (when multicolor mode is set)
multi = new Color[TextMaxLayers];
}
public void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(ref camera, cameraMode);
// Handle font files dropped
if (IsFileDropped())
{
string[] droppedFiles = GetDroppedFiles();
// NOTE: We only support first ttf file dropped
if (IsFileExtension(droppedFiles[0], ".ttf"))
{
UnloadFont(font);
font = LoadFontEx(droppedFiles[0], (int)fontSize, null, 0);
}
else if (IsFileExtension(droppedFiles[0], ".fnt"))
{
UnloadFont(font);
font = LoadFont(droppedFiles[0]);
fontSize = (float)font.BaseSize;
}
}
// Handle Events
if (IsKeyPressed(KeyboardKey.F1)) showLetterBoundry = !showLetterBoundry;
if (IsKeyPressed(KeyboardKey.F2)) showTextBoundry = !showTextBoundry;
if (IsKeyPressed(KeyboardKey.F3))
{
// Handle camera change
spin = !spin;
// we need to reset the camera when changing modes
camera = new();
camera.Target = new Vector3(0.0f, 0.0f, 0.0f); // Camera looking at point
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
camera.FovY = 45.0f; // Camera field-of-view Y
camera.Projection = CameraProjection.Perspective; // Camera mode type
if (spin)
{
camera.Position = new Vector3(-10.0f, 15.0f, -10.0f); // Camera position
cameraMode = CameraMode.Orbital;
}
else
{
camera.Position = new Vector3(10.0f, 10.0f, -10.0f); // Camera position
cameraMode = CameraMode.Free;
}
}
// Handle clicking the cube
if (IsMouseButtonPressed(MouseButton.Left))
{
Ray ray = GetScreenToWorldRay(GetMousePosition(), camera);
// Check collision between ray and box
RayCollision collision = GetRayCollisionBox(ray,
new BoundingBox(
new Vector3(cubePosition.X - cubeSize.X / 2, cubePosition.Y - cubeSize.Y / 2, cubePosition.Z - cubeSize.Z / 2),
new Vector3(cubePosition.X + cubeSize.X / 2, cubePosition.Y + cubeSize.Y / 2, cubePosition.Z + cubeSize.Z / 2)));
if (collision.Hit)
{
// Generate new random colors
light = GenerateRandomColor(0.5f, 0.78f);
dark = GenerateRandomColor(0.4f, 0.58f);
}
}
// Handle text layers changes
if (IsKeyPressed(KeyboardKey.Home)) { if (layers > 1) --layers; }
else if (IsKeyPressed(KeyboardKey.End)) { if (layers < TextMaxLayers) ++layers; }
// Handle text changes
if (IsKeyPressed(KeyboardKey.Left)) fontSize -= 0.5f;
else if (IsKeyPressed(KeyboardKey.Right)) fontSize += 0.5f;
else if (IsKeyPressed(KeyboardKey.Up)) fontSpacing -= 0.1f;
else if (IsKeyPressed(KeyboardKey.Down)) fontSpacing += 0.1f;
else if (IsKeyPressed(KeyboardKey.PageUp)) lineSpacing -= 0.1f;
else if (IsKeyPressed(KeyboardKey.PageDown)) lineSpacing += 0.1f;
else if (IsKeyDown(KeyboardKey.Insert)) layerDistance -= 0.001f;
else if (IsKeyDown(KeyboardKey.Delete)) layerDistance += 0.001f;
else if (IsKeyPressed(KeyboardKey.Tab))
{
multicolor = !multicolor; // Enable /disable multicolor mode
if (multicolor)
{
// Fill color array with random colors
for (int i = 0; i < TextMaxLayers; i++)
{
multi[i] = GenerateRandomColor(0.5f, 0.8f);
multi[i].A = (byte)GetRandomValue(0, 255);
}
}
}
// Handle text input
int ch = GetCharPressed();
if (IsKeyPressed(KeyboardKey.Backspace))
{
// Remove last char
if (text.Length > 0) text = text.Substring(0, text.Length - 1);
}
else if (IsKeyPressed(KeyboardKey.Enter))
{
// handle newline
if (text.Length < 63) text += '\n';
}
else
{
// append only printable chars
if ((ch != 0) && (text.Length < 63)) text += char.ConvertFromUtf32(ch);
}
// Measure 3D text so we can center it
tbox = MeasureTextWave3D(font, text, fontSize, fontSpacing, lineSpacing);
quads = 0; // Reset quad counter
time += GetFrameTime(); // Update timer needed by `DrawTextWave3D()`
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
BeginMode3D(camera);
DrawCubeV(cubePosition, cubeSize, dark);
DrawCubeWires(cubePosition, 2.1f, 2.1f, 2.1f, light);
DrawGrid(10, 2.0f);
// Use a shader to handle the depth buffer issue with transparent textures
// NOTE: more info at https://bedroomcoders.co.uk/posts/198
BeginShaderMode(alphaDiscard);
// Draw the 3D text above the red cube
Rlgl.PushMatrix();
Rlgl.Rotatef(90.0f, 1.0f, 0.0f, 0.0f);
Rlgl.Rotatef(90.0f, 0.0f, 0.0f, -1.0f);
for (int i = 0; i < layers; i++)
{
Color clr = light;
if (multicolor) clr = multi[i];
DrawTextWave3D(font, text, new Vector3(-tbox.X / 2.0f, layerDistance * i, -4.5f), fontSize, fontSpacing, lineSpacing, true, wcfg, time, clr);
}
// Draw the text boundry if set
if (showTextBoundry) DrawCubeWiresV(new Vector3(0.0f, 0.0f, -4.5f + tbox.Z / 2), tbox, dark);
Rlgl.PopMatrix();
// Don't draw the letter boundries for the 3D text below
bool slb = showLetterBoundry;
showLetterBoundry = false;
// Draw 3D options (use default font)
//-------------------------------------------------------------------------
Rlgl.PushMatrix();
Rlgl.Rotatef(180.0f, 0.0f, 1.0f, 0.0f);
string opt = $"< SIZE: {fontSize:0.0} >";
quads += opt.Length;
Vector2 m = MeasureTextEx(GetFontDefault(), opt, 0.8f, 0.1f);
Vector3 pos = new(-m.X / 2.0f, 0.01f, 2.0f);
DrawText3D(GetFontDefault(), opt, pos, 0.8f, 0.1f, 0.0f, false, Color.Blue);
pos.Z += 0.5f + m.Y;
opt = $"< SPACING: {fontSpacing:0.0} >";
quads += opt.Length;
m = MeasureTextEx(GetFontDefault(), opt, 0.8f, 0.1f);
pos.X = -m.X / 2.0f;
DrawText3D(GetFontDefault(), opt, pos, 0.8f, 0.1f, 0.0f, false, Color.Blue);
pos.Z += 0.5f + m.Y;
opt = $"< LINE: {lineSpacing:0.0} >";
quads += opt.Length;
m = MeasureTextEx(GetFontDefault(), opt, 0.8f, 0.1f);
pos.X = -m.X / 2.0f;
DrawText3D(GetFontDefault(), opt, pos, 0.8f, 0.1f, 0.0f, false, Color.Blue);
pos.Z += 0.5f + m.Y;
opt = $"< LBOX: {(slb ? "ON" : "OFF"),3} >";
quads += opt.Length;
m = MeasureTextEx(GetFontDefault(), opt, 0.8f, 0.1f);
pos.X = -m.X / 2.0f;
DrawText3D(GetFontDefault(), opt, pos, 0.8f, 0.1f, 0.0f, false, Color.Red);
pos.Z += 0.5f + m.Y;
opt = $"< TBOX: {(showTextBoundry ? "ON" : "OFF"),3} >";
quads += opt.Length;
m = MeasureTextEx(GetFontDefault(), opt, 0.8f, 0.1f);
pos.X = -m.X / 2.0f;
DrawText3D(GetFontDefault(), opt, pos, 0.8f, 0.1f, 0.0f, false, Color.Red);
pos.Z += 0.5f + m.Y;
opt = $"< LAYER DISTANCE: {layerDistance:0.000} >";
quads += opt.Length;
m = MeasureTextEx(GetFontDefault(), opt, 0.8f, 0.1f);
pos.X = -m.X / 2.0f;
DrawText3D(GetFontDefault(), opt, pos, 0.8f, 0.1f, 0.0f, false, Color.DarkPurple);
Rlgl.PopMatrix();
//-------------------------------------------------------------------------
// Draw 3D info text (use default font)
//-------------------------------------------------------------------------
opt = "All the text displayed here is in 3D";
quads += 36;
m = MeasureTextEx(GetFontDefault(), opt, 1.0f, 0.05f);
pos = new Vector3(-m.X / 2.0f, 0.01f, 2.0f);
DrawText3D(GetFontDefault(), opt, pos, 1.0f, 0.05f, 0.0f, false, Color.DarkBlue);
pos.Z += 1.5f + m.Y;
opt = "press [Left]/[Right] to change the font size";
quads += 44;
m = MeasureTextEx(GetFontDefault(), opt, 0.6f, 0.05f);
pos.X = -m.X / 2.0f;
DrawText3D(GetFontDefault(), opt, pos, 0.6f, 0.05f, 0.0f, false, Color.DarkBlue);
pos.Z += 0.5f + m.Y;
opt = "press [Up]/[Down] to change the font spacing";
quads += 44;
m = MeasureTextEx(GetFontDefault(), opt, 0.6f, 0.05f);
pos.X = -m.X / 2.0f;
DrawText3D(GetFontDefault(), opt, pos, 0.6f, 0.05f, 0.0f, false, Color.DarkBlue);
pos.Z += 0.5f + m.Y;
opt = "press [PgUp]/[PgDown] to change the line spacing";
quads += 48;
m = MeasureTextEx(GetFontDefault(), opt, 0.6f, 0.05f);
pos.X = -m.X / 2.0f;
DrawText3D(GetFontDefault(), opt, pos, 0.6f, 0.05f, 0.0f, false, Color.DarkBlue);
pos.Z += 0.5f + m.Y;
opt = "press [F1] to toggle the letter boundry";
quads += 39;
m = MeasureTextEx(GetFontDefault(), opt, 0.6f, 0.05f);
pos.X = -m.X / 2.0f;
DrawText3D(GetFontDefault(), opt, pos, 0.6f, 0.05f, 0.0f, false, Color.DarkBlue);
pos.Z += 0.5f + m.Y;
opt = "press [F2] to toggle the text boundry";
quads += 37;
m = MeasureTextEx(GetFontDefault(), opt, 0.6f, 0.05f);
pos.X = -m.X / 2.0f;
DrawText3D(GetFontDefault(), opt, pos, 0.6f, 0.05f, 0.0f, false, Color.DarkBlue);
//-------------------------------------------------------------------------
showLetterBoundry = slb;
EndShaderMode();
EndMode3D();
// Draw 2D info text & stats
//-------------------------------------------------------------------------
DrawText("Drag & drop a font file to change the font!\nType something, see what happens!\n\n" +
"Press [F3] to toggle the camera", 10, 35, 10, Color.Black);
quads += TextLengthUtf8(text) * 2 * layers;
string tmp = $"{layers,2} layer(s) | {(spin ? "ORBITAL" : "FREE")} camera | {quads,4} quads ({quads * 4,4} verts)";
int width = MeasureText(tmp, 10);
DrawText(tmp, screenWidth - 20 - width, 10, 10, Color.DarkGreen);
tmp = "[Home]/[End] to add/remove 3D text layers";
width = MeasureText(tmp, 10);
DrawText(tmp, screenWidth - 20 - width, 25, 10, Color.DarkGray);
tmp = "[Insert]/[Delete] to increase/decrease distance between layers";
width = MeasureText(tmp, 10);
DrawText(tmp, screenWidth - 20 - width, 40, 10, Color.DarkGray);
tmp = "click the [CUBE] for a random color";
width = MeasureText(tmp, 10);
DrawText(tmp, screenWidth - 20 - width, 55, 10, Color.DarkGray);
tmp = "[Tab] to toggle multicolor mode";
width = MeasureText(tmp, 10);
DrawText(tmp, screenWidth - 20 - width, 70, 10, Color.DarkGray);
//-------------------------------------------------------------------------
DrawFPS(10, 10);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadFont(font);
}
//--------------------------------------------------------------------------------------
// Module Functions Definitions
//--------------------------------------------------------------------------------------
// Get the total length in bytes of a UTF-8 string (raylib TextLength equivalent)
private static int TextLengthUtf8(string text)
{
return Encoding.UTF8.GetByteCount(text);
}
// Draw codepoint at specified position in 3D space
private unsafe void DrawTextCodepoint3D(Font font, int codepoint, Vector3 position, float fontSize, bool backface, Color tint)
{
// Character index position in sprite font
// NOTE: In case a codepoint is not available in the font, index returned points to '?'
int index = GetGlyphIndex(font, codepoint);
float scale = fontSize / (float)font.BaseSize;
// Character destination rectangle on screen
// NOTE: We consider charsPadding on drawing
position.X += (font.Glyphs[index].OffsetX - font.GlyphPadding) * scale;
position.Z += (font.Glyphs[index].OffsetY - font.GlyphPadding) * scale;
// Character source rectangle from font texture atlas
// NOTE: We consider chars padding when drawing, it could be required for outline/glow shader effects
Rectangle srcRec = new(font.Recs[index].X - font.GlyphPadding, font.Recs[index].Y - font.GlyphPadding,
font.Recs[index].Width + 2.0f * font.GlyphPadding, font.Recs[index].Height + 2.0f * font.GlyphPadding);
float width = (font.Recs[index].Width + 2.0f * font.GlyphPadding) * scale;
float height = (font.Recs[index].Height + 2.0f * font.GlyphPadding) * scale;
if (font.Texture.Id > 0)
{
const float x = 0.0f;
const float y = 0.0f;
const float z = 0.0f;
// normalized texture coordinates of the glyph inside the font texture (0.0f -> 1.0f)
float tx = srcRec.X / font.Texture.Width;
float ty = srcRec.Y / font.Texture.Height;
float tw = (srcRec.X + srcRec.Width) / font.Texture.Width;
float th = (srcRec.Y + srcRec.Height) / font.Texture.Height;
if (showLetterBoundry) DrawCubeWiresV(new Vector3(position.X + width / 2, position.Y, position.Z + height / 2), new Vector3(width, LetterBoundrySize, height), LetterBoundryColor);
Rlgl.CheckRenderBatchLimit(4 + 4 * (backface ? 1 : 0));
Rlgl.SetTexture(font.Texture.Id);
Rlgl.PushMatrix();
Rlgl.Translatef(position.X, position.Y, position.Z);
Rlgl.Begin(DrawMode.Quads);
Rlgl.Color4ub(tint.R, tint.G, tint.B, tint.A);
// Front Face
Rlgl.Normal3f(0.0f, 1.0f, 0.0f); // Normal Pointing Up
Rlgl.TexCoord2f(tx, ty); Rlgl.Vertex3f(x, y, z); // Top Left Of The Texture and Quad
Rlgl.TexCoord2f(tx, th); Rlgl.Vertex3f(x, y, z + height); // Bottom Left Of The Texture and Quad
Rlgl.TexCoord2f(tw, th); Rlgl.Vertex3f(x + width, y, z + height); // Bottom Right Of The Texture and Quad
Rlgl.TexCoord2f(tw, ty); Rlgl.Vertex3f(x + width, y, z); // Top Right Of The Texture and Quad
if (backface)
{
// Back Face
Rlgl.Normal3f(0.0f, -1.0f, 0.0f); // Normal Pointing Down
Rlgl.TexCoord2f(tx, ty); Rlgl.Vertex3f(x, y, z); // Top Right Of The Texture and Quad
Rlgl.TexCoord2f(tw, ty); Rlgl.Vertex3f(x + width, y, z); // Top Left Of The Texture and Quad
Rlgl.TexCoord2f(tw, th); Rlgl.Vertex3f(x + width, y, z + height); // Bottom Left Of The Texture and Quad
Rlgl.TexCoord2f(tx, th); Rlgl.Vertex3f(x, y, z + height); // Bottom Right Of The Texture and Quad
}
Rlgl.End();
Rlgl.PopMatrix();
Rlgl.SetTexture(0);
}
}
// Draw a 2D text in 3D space
private unsafe void DrawText3D(Font font, string text, Vector3 position, float fontSize, float fontSpacing, float lineSpacing, bool backface, Color tint)
{
using var textNative = new Utf8Buffer(text);
sbyte* t = textNative.AsPointer();
int length = Encoding.UTF8.GetByteCount(text); // Total length in bytes of the text, scanned by codepoints in loop
float textOffsetY = 0.0f; // Offset between lines (on line break '\n')
float textOffsetX = 0.0f; // Offset X to next character to draw
float scale = fontSize / (float)font.BaseSize;
for (int i = 0; i < length;)
{
// Get next codepoint from byte string and glyph index in font
int codepointByteCount = 0;
int codepoint = GetCodepoint(&t[i], &codepointByteCount);
int index = GetGlyphIndex(font, codepoint);
// NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f)
// but we need to draw all of the bad bytes using the '?' symbol moving one byte
if (codepoint == 0x3f) codepointByteCount = 1;
if (codepoint == '\n')
{
// NOTE: Fixed line spacing of 1.5 line-height
// TODO: Support custom line spacing defined by user
textOffsetY += fontSize + lineSpacing;
textOffsetX = 0.0f;
}
else
{
if ((codepoint != ' ') && (codepoint != '\t'))
{
DrawTextCodepoint3D(font, codepoint, new Vector3(position.X + textOffsetX, position.Y, position.Z + textOffsetY), fontSize, backface, tint);
}
if (font.Glyphs[index].AdvanceX == 0) textOffsetX += font.Recs[index].Width * scale + fontSpacing;
else textOffsetX += font.Glyphs[index].AdvanceX * scale + fontSpacing;
}
i += codepointByteCount; // Move text bytes counter to next codepoint
}
}
// Draw a 2D text in 3D space and wave the parts that start with `~~` and end with `~~`
// This is a modified version of the original code by @Nighten found here https://github.com/NightenDushi/Raylib_DrawTextStyle
private unsafe void DrawTextWave3D(Font font, string text, Vector3 position, float fontSize, float fontSpacing, float lineSpacing, bool backface, WaveTextConfig config, float time, Color tint)
{
using var textNative = new Utf8Buffer(text);
sbyte* t = textNative.AsPointer();
int length = Encoding.UTF8.GetByteCount(text); // Total length in bytes of the text, scanned by codepoints in loop
float textOffsetY = 0.0f; // Offset between lines (on line break '\n')
float textOffsetX = 0.0f; // Offset X to next character to draw
float scale = fontSize / (float)font.BaseSize;
bool wave = false;
for (int i = 0, k = 0; i < length; ++k)
{
// Get next codepoint from byte string and glyph index in font
int codepointByteCount = 0;
int codepoint = GetCodepoint(&t[i], &codepointByteCount);
int index = GetGlyphIndex(font, codepoint);
// NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f)
// but we need to draw all of the bad bytes using the '?' symbol moving one byte
if (codepoint == 0x3f) codepointByteCount = 1;
if (codepoint == '\n')
{
// NOTE: Fixed line spacing of 1.5 line-height
// TODO: Support custom line spacing defined by user
textOffsetY += fontSize + lineSpacing;
textOffsetX = 0.0f;
k = 0;
}
else if (codepoint == '~')
{
if (GetCodepoint(&t[i + 1], &codepointByteCount) == '~')
{
codepointByteCount += 1;
wave = !wave;
}
}
else
{
if ((codepoint != ' ') && (codepoint != '\t'))
{
Vector3 pos = position;
if (wave) // Apply the wave effect
{
pos.X += MathF.Sin(time * config.WaveSpeed.X - k * config.WaveOffset.X) * config.WaveRange.X;
pos.Y += MathF.Sin(time * config.WaveSpeed.Y - k * config.WaveOffset.Y) * config.WaveRange.Y;
pos.Z += MathF.Sin(time * config.WaveSpeed.Z - k * config.WaveOffset.Z) * config.WaveRange.Z;
}
DrawTextCodepoint3D(font, codepoint, new Vector3(pos.X + textOffsetX, pos.Y, pos.Z + textOffsetY), fontSize, backface, tint);
}
if (font.Glyphs[index].AdvanceX == 0) textOffsetX += font.Recs[index].Width * scale + fontSpacing;
else textOffsetX += font.Glyphs[index].AdvanceX * scale + fontSpacing;
}
i += codepointByteCount; // Move text bytes counter to next codepoint
}
}
// Measure a text in 3D ignoring the `~~` chars
private unsafe Vector3 MeasureTextWave3D(Font font, string text, float fontSize, float fontSpacing, float lineSpacing)
{
using var textNative = new Utf8Buffer(text);
sbyte* t = textNative.AsPointer();
int len = Encoding.UTF8.GetByteCount(text);
int tempLen = 0; // Used to count longer text line num chars
int lenCounter = 0;
float tempTextWidth = 0.0f; // Used to count longer text line width
float scale = fontSize / (float)font.BaseSize;
float textHeight = scale;
float textWidth = 0.0f;
int letter = 0; // Current character
int index = 0; // Index position in sprite font
for (int i = 0; i < len; i++)
{
int next = 0;
letter = GetCodepoint(&t[i], &next);
index = GetGlyphIndex(font, letter);
// NOTE: normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f)
// but we need to draw all of the bad bytes using the '?' symbol so to not skip any we set next = 1
if (letter == 0x3f) next = 1;
i += next - 1;
if (letter != '\n')
{
if (letter == '~' && GetCodepoint(&t[i + 1], &next) == '~')
{
i++;
}
else
{
lenCounter++;
if (font.Glyphs[index].AdvanceX != 0) textWidth += font.Glyphs[index].AdvanceX * scale;
else textWidth += (font.Recs[index].Width + font.Glyphs[index].OffsetX) * scale;
}
}
else
{
if (tempTextWidth < textWidth) tempTextWidth = textWidth;
lenCounter = 0;
textWidth = 0.0f;
textHeight += fontSize + lineSpacing;
}
if (tempLen < lenCounter) tempLen = lenCounter;
}
if (tempTextWidth < textWidth) tempTextWidth = textWidth;
Vector3 vec = new(0, 0, 0);
vec.X = tempTextWidth + ((tempLen - 1) * fontSpacing); // Adds chars spacing to measure
vec.Y = 0.25f;
vec.Z = textHeight;
return vec;
}
// Generates a nice color with a random hue
private static Color GenerateRandomColor(float s, float v)
{
const float Phi = 0.618033988749895f; // Golden ratio conjugate
float h = (float)GetRandomValue(0, 360);
h = (h + h * Phi) % 360.0f;
return ColorFromHSV(h, s, v);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
SetConfigFlags(ConfigFlags.Msaa4xHint | ConfigFlags.VSyncHint);
InitWindow(screenWidth, screenHeight, "raylib [text] example - 3d drawing");
DisableCursor(); // Limit cursor to relative movement inside the window
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new Text3D();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

View file

@ -22,11 +22,20 @@ using static Raylib_cs.Raylib;
namespace Examples.Text;
public class Unicode
public partial class Unicode : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
const int EmojiPerWidth = 8;
const int EmojiPerHeight = 4;
public string Name => "Text / Unicode Emojis";
public string Title => "raylib [text] example - unicode emojis";
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint | ConfigFlags.VSyncHint;
// Arrays that holds the random emojis
struct EmojiInfo
{
@ -35,10 +44,10 @@ public class Unicode
public Color Color; // Emoji color
}
static EmojiInfo[] emoji = new EmojiInfo[EmojiPerWidth * EmojiPerHeight];
private EmojiInfo[] emoji;
static int hovered = -1;
static int selected = -1;
private int hovered;
private int selected;
struct Message
{
@ -130,191 +139,187 @@ public class Unicode
new Message("\xED\x95\x9C\xEA\xB5\xAD\xEB\xA7\x90\x20\xED\x95\x98\xEC\x8B\xA4\x20\xEC\xA4\x84\x20\xEC\x95\x84\xEC\x84\xB8\xEC\x9A\x94\x3F", "Korean"),
};
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
const int screenWidth = 800;
const int screenHeight = 450;
private Font fontDefault;
private Font fontAsian;
private Font fontEmoji;
SetConfigFlags(ConfigFlags.Msaa4xHint | ConfigFlags.VSyncHint);
InitWindow(screenWidth, screenHeight, "raylib [text] example - unicode emojis");
private Vector2 hoveredPos;
private Vector2 selectedPos;
public void Init()
{
emoji = new EmojiInfo[EmojiPerWidth * EmojiPerHeight];
hovered = -1;
selected = -1;
// Load the font resources
// NOTE: fontAsian is for asian languages,
// fontEmoji is the emojis and fontDefault is used for everything else
Font fontDefault = LoadFont("resources/fonts/dejavu.fnt"); // Requires "resources/fonts/dejavu.png"
Font fontAsian = LoadFont("resources/fonts/noto_cjk.fnt"); // Requires "resources/fonts/noto_cjk.png"
Font fontEmoji = LoadFont("resources/fonts/symbola.fnt"); // Requires "resources/fonts/symbola.png"
fontDefault = LoadFont("resources/fonts/dejavu.fnt"); // Requires "resources/fonts/dejavu.png"
fontAsian = LoadFont("resources/fonts/noto_cjk.fnt"); // Requires "resources/fonts/noto_cjk.png"
fontEmoji = LoadFont("resources/fonts/symbola.fnt"); // Requires "resources/fonts/symbola.png"
Vector2 hoveredPos = new(0.0f, 0.0f);
Vector2 selectedPos = new(0.0f, 0.0f);
hoveredPos = new(0.0f, 0.0f);
selectedPos = new(0.0f, 0.0f);
// Set a random set of emojis when starting up
RandomizeEmoji();
}
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
// Main loop
while (!WindowShouldClose()) // Detect window close button or ESC key
public void Update()
{
// Update
//----------------------------------------------------------------------------------
// Add a new set of emojis when SPACE is pressed
if (IsKeyPressed(KeyboardKey.Space))
{
// Update
//----------------------------------------------------------------------------------
// Add a new set of emojis when SPACE is pressed
if (IsKeyPressed(KeyboardKey.Space))
{
RandomizeEmoji();
}
// Set the selected emoji
if (IsMouseButtonPressed(MouseButton.Left) && (hovered != -1) && (hovered != selected))
{
selected = hovered;
selectedPos = hoveredPos;
}
Vector2 mouse = GetMousePosition();
Vector2 position = new(28.8f, 10.0f);
hovered = -1;
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
// Draw random emojis in the background
//------------------------------------------------------------------------------
for (int i = 0; i < emoji.Length; i++)
{
string txt = GetEmojiAt(emoji[i].Index);
Rectangle emojiRect = new(position.X, position.Y, fontEmoji.BaseSize, fontEmoji.BaseSize);
if (!CheckCollisionPointRec(mouse, emojiRect))
{
DrawTextEx(fontEmoji, txt, position, fontEmoji.BaseSize, 1.0f, selected == i ? emoji[i].Color : Fade(Color.LightGray, 0.4f));
}
else
{
DrawTextEx(fontEmoji, txt, position, fontEmoji.BaseSize, 1.0f, emoji[i].Color);
hovered = i;
hoveredPos = position;
}
if ((i != 0) && (i % EmojiPerWidth == 0))
{
position.Y += fontEmoji.BaseSize + 24.25f;
position.X = 28.8f;
}
else
{
position.X += fontEmoji.BaseSize + 28.8f;
}
}
//------------------------------------------------------------------------------
// Draw the message when a emoji is selected
//------------------------------------------------------------------------------
if (selected != -1)
{
int message = emoji[selected].Message;
const int horizontalPadding = 20;
const int verticalPadding = 30;
Font font = fontDefault;
// Set correct font for asian languages
if ((messages[message].Language == "Chinese") ||
(messages[message].Language == "Korean") ||
(messages[message].Language == "Japanese"))
{
font = fontAsian;
}
// Calculate size for the message box (approximate the height and width)
Vector2 sz = MeasureTextEx(font, messages[message].Text, font.BaseSize, 1.0f);
if (sz.X > 300)
{
sz.Y *= sz.X / 300;
sz.X = 300;
}
else if (sz.X < 160)
{
sz.X = 160;
}
Rectangle msgRect = new(selectedPos.X - 38.8f, selectedPos.Y, 2 * horizontalPadding + sz.X, 2 * verticalPadding + sz.Y);
msgRect.Y -= msgRect.Height;
// Coordinates for the chat bubble triangle
Vector2 a = new(selectedPos.X, msgRect.Y + msgRect.Height);
Vector2 b = new(a.X + 8, a.Y + 10);
Vector2 c = new(a.X + 10, a.Y);
// Don't go outside the screen
if (msgRect.X < 10)
{
msgRect.X += 28;
}
if (msgRect.Y < 10)
{
msgRect.Y = selectedPos.Y + 84;
a.Y = msgRect.Y;
c.Y = a.Y;
b.Y = a.Y - 10;
// Swap values so we can actually render the triangle :(
Vector2 tmp = a;
a = b;
b = tmp;
}
if (msgRect.X + msgRect.Width > screenWidth)
{
msgRect.X -= (msgRect.X + msgRect.Width) - screenWidth + 10;
}
// Draw chat bubble
DrawRectangleRec(msgRect, emoji[selected].Color);
DrawTriangle(a, b, c, emoji[selected].Color);
// Draw the main text message
Rectangle textRect = new(msgRect.X + (float)horizontalPadding / 2, msgRect.Y + (float)verticalPadding / 2, msgRect.Width - horizontalPadding, msgRect.Height);
DrawTextBoxed(font, messages[message].Text, textRect, font.BaseSize, 1.0f, true, Color.White);
// Draw the info text below the main message
int size = Encoding.UTF8.GetByteCount(messages[message].Text);
int length = GetCodepointCount(messages[message].Text);
string info = $"{messages[message].Language} {length} characters {size} bytes";
sz = MeasureTextEx(GetFontDefault(), info, 10, 1.0f);
DrawText(info, (int)(textRect.X + textRect.Width - sz.X), (int)(msgRect.Y + msgRect.Height - sz.Y - 2), 10, Color.RayWhite);
}
//------------------------------------------------------------------------------
// Draw the info text
DrawText("These emojis have something to tell you, click each to find out!", (screenWidth - 650) / 2, screenHeight - 40, 20, Color.Gray);
DrawText("Each emoji is a unicode character from a font, not a texture... Press [SPACEBAR] to refresh", (screenWidth - 484) / 2, screenHeight - 16, 10, Color.Gray);
EndDrawing();
//----------------------------------------------------------------------------------
RandomizeEmoji();
}
// De-Initialization
//--------------------------------------------------------------------------------------
// Set the selected emoji
if (IsMouseButtonPressed(MouseButton.Left) && (hovered != -1) && (hovered != selected))
{
selected = hovered;
selectedPos = hoveredPos;
}
Vector2 mouse = GetMousePosition();
Vector2 position = new(28.8f, 10.0f);
hovered = -1;
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RayWhite);
// Draw random emojis in the background
//------------------------------------------------------------------------------
for (int i = 0; i < emoji.Length; i++)
{
string txt = GetEmojiAt(emoji[i].Index);
Rectangle emojiRect = new(position.X, position.Y, fontEmoji.BaseSize, fontEmoji.BaseSize);
if (!CheckCollisionPointRec(mouse, emojiRect))
{
DrawTextEx(fontEmoji, txt, position, fontEmoji.BaseSize, 1.0f, selected == i ? emoji[i].Color : Fade(Color.LightGray, 0.4f));
}
else
{
DrawTextEx(fontEmoji, txt, position, fontEmoji.BaseSize, 1.0f, emoji[i].Color);
hovered = i;
hoveredPos = position;
}
if ((i != 0) && (i % EmojiPerWidth == 0))
{
position.Y += fontEmoji.BaseSize + 24.25f;
position.X = 28.8f;
}
else
{
position.X += fontEmoji.BaseSize + 28.8f;
}
}
//------------------------------------------------------------------------------
// Draw the message when a emoji is selected
//------------------------------------------------------------------------------
if (selected != -1)
{
int message = emoji[selected].Message;
const int horizontalPadding = 20;
const int verticalPadding = 30;
Font font = fontDefault;
// Set correct font for asian languages
if ((messages[message].Language == "Chinese") ||
(messages[message].Language == "Korean") ||
(messages[message].Language == "Japanese"))
{
font = fontAsian;
}
// Calculate size for the message box (approximate the height and width)
Vector2 sz = MeasureTextEx(font, messages[message].Text, font.BaseSize, 1.0f);
if (sz.X > 300)
{
sz.Y *= sz.X / 300;
sz.X = 300;
}
else if (sz.X < 160)
{
sz.X = 160;
}
Rectangle msgRect = new(selectedPos.X - 38.8f, selectedPos.Y, 2 * horizontalPadding + sz.X, 2 * verticalPadding + sz.Y);
msgRect.Y -= msgRect.Height;
// Coordinates for the chat bubble triangle
Vector2 a = new(selectedPos.X, msgRect.Y + msgRect.Height);
Vector2 b = new(a.X + 8, a.Y + 10);
Vector2 c = new(a.X + 10, a.Y);
// Don't go outside the screen
if (msgRect.X < 10)
{
msgRect.X += 28;
}
if (msgRect.Y < 10)
{
msgRect.Y = selectedPos.Y + 84;
a.Y = msgRect.Y;
c.Y = a.Y;
b.Y = a.Y - 10;
// Swap values so we can actually render the triangle :(
Vector2 tmp = a;
a = b;
b = tmp;
}
if (msgRect.X + msgRect.Width > screenWidth)
{
msgRect.X -= (msgRect.X + msgRect.Width) - screenWidth + 10;
}
// Draw chat bubble
DrawRectangleRec(msgRect, emoji[selected].Color);
DrawTriangle(a, b, c, emoji[selected].Color);
// Draw the main text message
Rectangle textRect = new(msgRect.X + (float)horizontalPadding / 2, msgRect.Y + (float)verticalPadding / 2, msgRect.Width - horizontalPadding, msgRect.Height);
DrawTextBoxed(font, messages[message].Text, textRect, font.BaseSize, 1.0f, true, Color.White);
// Draw the info text below the main message
int size = Encoding.UTF8.GetByteCount(messages[message].Text);
int length = GetCodepointCount(messages[message].Text);
string info = $"{messages[message].Language} {length} characters {size} bytes";
sz = MeasureTextEx(GetFontDefault(), info, 10, 1.0f);
DrawText(info, (int)(textRect.X + textRect.Width - sz.X), (int)(msgRect.Y + msgRect.Height - sz.Y - 2), 10, Color.RayWhite);
}
//------------------------------------------------------------------------------
// Draw the info text
DrawText("These emojis have something to tell you, click each to find out!", (screenWidth - 650) / 2, screenHeight - 40, 20, Color.Gray);
DrawText("Each emoji is a unicode character from a font, not a texture... Press [SPACEBAR] to refresh", (screenWidth - 484) / 2, screenHeight - 16, 10, Color.Gray);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
UnloadFont(fontDefault); // Unload font resource
UnloadFont(fontAsian); // Unload font resource
UnloadFont(fontEmoji); // Unload font resource
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
// Fills the emoji array with random emoji (only those emojis present in fontEmoji)
static void RandomizeEmoji()
void RandomizeEmoji()
{
hovered = selected = -1;
int start = GetRandomValue(45, 360);
@ -510,4 +515,33 @@ public class Unicode
textOffsetX += glyphWidth;
}
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
SetConfigFlags(ConfigFlags.Msaa4xHint | ConfigFlags.VSyncHint);
InitWindow(screenWidth, screenHeight, "raylib [text] example - unicode emojis");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new Unicode();
game.Init();
// Main loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}

Some files were not shown because too many files have changed in this diff Show more