361 lines
16 KiB
C#
361 lines
16 KiB
C#
/*******************************************************************************************
|
|
*
|
|
* 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;
|
|
}
|
|
}
|