diff --git a/examples/2d_camera_platformer.c b/examples/2d_camera_platformer.c new file mode 100644 index 0000000..651253f --- /dev/null +++ b/examples/2d_camera_platformer.c @@ -0,0 +1,332 @@ +/******************************************************************************************* +* +* raylib [core] example - 2D Camera platformer +* +* Example originally created with raylib 2.5, last time updated with raylib 3.0 +* +* Example contributed by arvyy (@arvyy) 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-2024 arvyy (@arvyy) +* +********************************************************************************************/ + +#include "raylib.h" +#define RAYMATH_IMPLEMENTATION +#include "raymath.h" + +void raylib_js_set_entry(void (*entry)(void)); + +#define G 400 +#define PLAYER_JUMP_SPD 350.0f +#define PLAYER_HOR_SPD 200.0f + +typedef struct Player { + Vector2 position; + float speed; + bool canJump; +} Player; + +typedef struct EnvItem { + Rectangle rect; + int blocking; + Color color; +} EnvItem; + +//---------------------------------------------------------------------------------- +// Module functions declaration +//---------------------------------------------------------------------------------- +void UpdatePlayer(Player *player, EnvItem *envItems, int envItemsLength, float delta); +void UpdateCameraCenter(Camera2D *camera, Player *player, EnvItem *envItems, int envItemsLength, float delta, int width, int height); +void UpdateCameraCenterInsideMap(Camera2D *camera, Player *player, EnvItem *envItems, int envItemsLength, float delta, int width, int height); +void UpdateCameraCenterSmoothFollow(Camera2D *camera, Player *player, EnvItem *envItems, int envItemsLength, float delta, int width, int height); +void UpdateCameraEvenOutOnLanding(Camera2D *camera, Player *player, EnvItem *envItems, int envItemsLength, float delta, int width, int height); +void UpdateCameraPlayerBoundsPush(Camera2D *camera, Player *player, EnvItem *envItems, int envItemsLength, float delta, int width, int height); + + +//moved to globals for now +// used to be at the start of main when gameframe was inlined + + const int screenWidth = 800; + const int screenHeight = 450; + Player player = { 0 }; + EnvItem envItems[] = { + {{ 0, 0, 1000, 400 }, 0, LIGHTGRAY }, + {{ 0, 400, 1000, 200 }, 1, GRAY }, + {{ 300, 200, 400, 10 }, 1, GRAY }, + {{ 250, 300, 100, 10 }, 1, GRAY }, + {{ 650, 300, 100, 10 }, 1, GRAY } + }; + + int envItemsLength = sizeof(envItems)/sizeof(envItems[0]); + Camera2D camera = { 0 }; + + // Store pointers to the multiple update camera functions + void (*cameraUpdaters[])(Camera2D*, Player*, EnvItem*, int, float, int, int) = { + UpdateCameraCenter, + UpdateCameraCenterInsideMap, + UpdateCameraCenterSmoothFollow, + UpdateCameraEvenOutOnLanding, + UpdateCameraPlayerBoundsPush + }; + + int cameraOption = 0; + int cameraUpdatersLength = sizeof(cameraUpdaters)/sizeof(cameraUpdaters[0]); + + char *cameraDescriptions[] = { + "Follow player center", + "Follow player center, but clamp to map edges", + "Follow player center; smoothed", + "Follow player center horizontally; update player center vertically after landing", + "Player push camera on getting too close to screen edge" + }; + +//custom MATH funcitons +float myfminf(float a, float b) { + if (b < a ) return b; + return a; +} +float myfmaxf(float a, float b) { + if (b > a ) return b; + return a; +} + +void GameFrame() { + + // Update + //---------------------------------------------------------------------------------- + float deltaTime = GetFrameTime(); + + UpdatePlayer(&player, envItems, envItemsLength, deltaTime); + + 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; + + if (IsKeyPressed(KEY_R)) + { + camera.zoom = 1.0f; + player.position = (Vector2){ 400, 280 }; + } + + if (IsKeyPressed(KEY_C)) cameraOption = (cameraOption + 1)%cameraUpdatersLength; + + // Call update camera function by its pointer + cameraUpdaters[cameraOption](&camera, &player, envItems, envItemsLength, deltaTime, screenWidth, screenHeight); + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(LIGHTGRAY); + + BeginMode2D(camera); + + for (int i = 0; i < envItemsLength; i++) DrawRectangleRec(envItems[i].rect, envItems[i].color); + + Rectangle playerRect = { player.position.x - 20, player.position.y - 40, 40, 40 }; + DrawRectangleRec(playerRect, RED); + + DrawCircle(player.position.x, player.position.y, 5, GOLD); + + EndMode2D(); + + DrawText("Controls:", 20, 20, 10, BLACK); + DrawText("- Right/Left to move", 40, 40, 10, DARKGRAY); + DrawText("- Space to jump", 40, 60, 10, DARKGRAY); + DrawText("- Mouse Wheel to Zoom in-out, R to reset zoom", 40, 80, 10, DARKGRAY); + DrawText("- C to change camera mode", 40, 100, 10, DARKGRAY); + DrawText("Current camera mode:", 20, 120, 10, BLACK); + DrawText(cameraDescriptions[cameraOption], 40, 140, 10, DARKGRAY); + + EndDrawing(); + //---------------------------------------------------------------------------------- +} + + + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + + + InitWindow(screenWidth, screenHeight, "raylib [core] example - 2d camera"); + + player.position = (Vector2){ 400, 280 }; + player.speed = 0; + player.canJump = false; + + camera.target = player.position; + camera.offset = (Vector2){ screenWidth/2.0f, screenHeight/2.0f }; + camera.rotation = 0.0f; + camera.zoom = 1.0f; + + + SetTargetFPS(60); + //-------------------------------------------------------------------------------------- + +#ifdef PLATFORM_WEB + raylib_js_set_entry(GameFrame); +#else + // Main game loop + while (!WindowShouldClose()) + { + GameFrame(); + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- +#endif + + return 0; +} + +void UpdatePlayer(Player *player, EnvItem *envItems, int envItemsLength, float delta) +{ + if (IsKeyDown(KEY_LEFT)) player->position.x -= PLAYER_HOR_SPD*delta; + if (IsKeyDown(KEY_RIGHT)) player->position.x += PLAYER_HOR_SPD*delta; + if (IsKeyDown(KEY_SPACE) && player->canJump) + { + player->speed = -PLAYER_JUMP_SPD; + player->canJump = false; + } + + bool hitObstacle = false; + for (int i = 0; i < envItemsLength; i++) + { + EnvItem *ei = envItems + i; + Vector2 *p = &(player->position); + if (ei->blocking && + ei->rect.x <= p->x && + ei->rect.x + ei->rect.width >= p->x && + ei->rect.y >= p->y && + ei->rect.y <= p->y + player->speed*delta) + { + hitObstacle = true; + player->speed = 0.0f; + p->y = ei->rect.y; + break; + } + } + + if (!hitObstacle) + { + player->position.y += player->speed*delta; + player->speed += G*delta; + player->canJump = false; + } + else player->canJump = true; +} + +void UpdateCameraCenter(Camera2D *camera, Player *player, EnvItem *envItems, int envItemsLength, float delta, int width, int height) +{ + camera->offset = (Vector2){ width/2.0f, height/2.0f }; + camera->target = player->position; +} + +void UpdateCameraCenterInsideMap(Camera2D *camera, Player *player, EnvItem *envItems, int envItemsLength, float delta, int width, int height) +{ + camera->target = player->position; + camera->offset = (Vector2){ width/2.0f, height/2.0f }; + float minX = 1000, minY = 1000, maxX = -1000, maxY = -1000; + + for (int i = 0; i < envItemsLength; i++) + { + EnvItem *ei = envItems + i; + minX = myfminf(ei->rect.x, minX); + maxX = myfmaxf(ei->rect.x + ei->rect.width, maxX); + minY = myfminf(ei->rect.y, minY); + maxY = myfmaxf(ei->rect.y + ei->rect.height, maxY); + } + + Vector2 max = GetWorldToScreen2D((Vector2){ maxX, maxY }, *camera); + Vector2 min = GetWorldToScreen2D((Vector2){ minX, minY }, *camera); + + if (max.x < width) camera->offset.x = width - (max.x - width/2); + if (max.y < height) camera->offset.y = height - (max.y - height/2); + if (min.x > 0) camera->offset.x = width/2 - min.x; + if (min.y > 0) camera->offset.y = height/2 - min.y; +} + +void UpdateCameraCenterSmoothFollow(Camera2D *camera, Player *player, EnvItem *envItems, int envItemsLength, float delta, int width, int height) +{ + static float minSpeed = 30; + static float minEffectLength = 10; + static float fractionSpeed = 0.8f; + + camera->offset = (Vector2){ width/2.0f, height/2.0f }; + Vector2 diff = Vector2Subtract(player->position, camera->target); + float length = Vector2Length(diff); + + if (length > minEffectLength) + { + float speed = myfmaxf(fractionSpeed*length, minSpeed); + camera->target = Vector2Add(camera->target, Vector2Scale(diff, speed*delta/length)); + } +} + +void UpdateCameraEvenOutOnLanding(Camera2D *camera, Player *player, EnvItem *envItems, int envItemsLength, float delta, int width, int height) +{ + static float evenOutSpeed = 700; + static int eveningOut = false; + static float evenOutTarget; + + camera->offset = (Vector2){ width/2.0f, height/2.0f }; + camera->target.x = player->position.x; + + if (eveningOut) + { + if (evenOutTarget > camera->target.y) + { + camera->target.y += evenOutSpeed*delta; + + if (camera->target.y > evenOutTarget) + { + camera->target.y = evenOutTarget; + eveningOut = 0; + } + } + else + { + camera->target.y -= evenOutSpeed*delta; + + if (camera->target.y < evenOutTarget) + { + camera->target.y = evenOutTarget; + eveningOut = 0; + } + } + } + else + { + if (player->canJump && (player->speed == 0) && (player->position.y != camera->target.y)) + { + eveningOut = 1; + evenOutTarget = player->position.y; + } + } +} + +void UpdateCameraPlayerBoundsPush(Camera2D *camera, Player *player, EnvItem *envItems, int envItemsLength, float delta, int width, int height) +{ + static Vector2 bbox = { 0.2f, 0.2f }; + + Vector2 bboxWorldMin = GetScreenToWorld2D((Vector2){ (1 - bbox.x)*0.5f*width, (1 - bbox.y)*0.5f*height }, *camera); + Vector2 bboxWorldMax = GetScreenToWorld2D((Vector2){ (1 + bbox.x)*0.5f*width, (1 + bbox.y)*0.5f*height }, *camera); + camera->offset = (Vector2){ (1 - bbox.x)*0.5f * width, (1 - bbox.y)*0.5f*height }; + + if (player->position.x < bboxWorldMin.x) camera->target.x = player->position.x; + if (player->position.y < bboxWorldMin.y) camera->target.y = player->position.y; + if (player->position.x > bboxWorldMax.x) camera->target.x = bboxWorldMin.x + (player->position.x - bboxWorldMax.x); + if (player->position.y > bboxWorldMax.y) camera->target.y = bboxWorldMin.y + (player->position.y - bboxWorldMax.y); +} \ No newline at end of file diff --git a/examples/core_2d_camera.c b/examples/core_2d_camera.c new file mode 100644 index 0000000..10dc956 --- /dev/null +++ b/examples/core_2d_camera.c @@ -0,0 +1,152 @@ +/******************************************************************************************* +* +* raylib [core] example - 2D Camera system +* +* Example originally created with raylib 1.5, last time updated with raylib 3.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) 2016-2024 Ramon Santamaria (@raysan5) +* +********************************************************************************************/ + +#include "raylib.h" + +void raylib_js_set_entry(void (*entry)(void)); + +#define MAX_BUILDINGS 100 + +const int screenWidth = 800; +const int screenHeight = 450; + +Rectangle player = { 400, 280, 40, 40 }; +Rectangle buildings[MAX_BUILDINGS] = { 0 }; +Color buildColors[MAX_BUILDINGS] = { 0 }; + +Camera2D camera = { 0 }; + + +void GameFrame() +{ + // Update + //---------------------------------------------------------------------------------- + // Player movement + if (IsKeyDown(KEY_RIGHT)) player.x += 2; + else if (IsKeyDown(KEY_LEFT)) player.x -= 2; + + // Camera target follows player + camera.target = (Vector2){ player.x + 20, player.y + 20 }; + + // Camera rotation controls + if (IsKeyDown(KEY_A)) camera.rotation--; + else if (IsKeyDown(KEY_S)) camera.rotation++; + + // Limit camera rotation to 80 degrees (-40 to 40) + if (camera.rotation > 40) camera.rotation = 40; + else if (camera.rotation < -40) camera.rotation = -40; + + // Camera zoom controls + camera.zoom += ((float)GetMouseWheelMove()*0.05f); + + if (camera.zoom > 3.0f) camera.zoom = 3.0f; + else if (camera.zoom < 0.1f) camera.zoom = 0.1f; + + // Camera reset (zoom and rotation) + if (IsKeyPressed(KEY_R)) + { + camera.zoom = 1.0f; + camera.rotation = 0.0f; + } + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + BeginMode2D(camera); + + DrawRectangle(-6000, 320, 13000, 8000, DARKGRAY); + + for (int i = 0; i < MAX_BUILDINGS; i++) DrawRectangleRec(buildings[i], buildColors[i]); + + DrawRectangleRec(player, RED); + + DrawLine((int)camera.target.x, -screenHeight*10, (int)camera.target.x, screenHeight*10, GREEN); + DrawLine(-screenWidth*10, (int)camera.target.y, screenWidth*10, (int)camera.target.y, GREEN); + + EndMode2D(); + + DrawText("SCREEN AREA", 640, 10, 20, RED); + + DrawRectangle(0, 0, screenWidth, 5, RED); + DrawRectangle(0, 5, 5, screenHeight - 10, RED); + DrawRectangle(screenWidth - 5, 5, 5, screenHeight - 10, RED); + DrawRectangle(0, screenHeight - 5, screenWidth, 5, RED); + + DrawRectangle( 10, 10, 250, 113, Fade(SKYBLUE, 0.5f)); + DrawRectangleLines( 10, 10, 250, 113, BLUE); + + DrawText("Free 2d camera controls:", 20, 20, 10, BLACK); + DrawText("- Right/Left to move Offset", 40, 40, 10, DARKGRAY); + DrawText("- Mouse Wheel to Zoom in-out", 40, 60, 10, DARKGRAY); + DrawText("- A / S to Rotate", 40, 80, 10, DARKGRAY); + DrawText("- R to reset Zoom and Rotation", 40, 100, 10, DARKGRAY); + + EndDrawing(); + //---------------------------------------------------------------------------------- +} + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + + + InitWindow(screenWidth, screenHeight, "raylib [core] example - 2d camera"); + + + int spacing = 0; + + for (int i = 0; i < MAX_BUILDINGS; i++) + { + buildings[i].width = (float)GetRandomValue(50, 200); + buildings[i].height = (float)GetRandomValue(100, 800); + buildings[i].y = screenHeight - 130.0f - buildings[i].height; + buildings[i].x = -6000.0f + spacing; + + spacing += (int)buildings[i].width; + + buildColors[i] = (Color){ GetRandomValue(200, 240), GetRandomValue(200, 240), GetRandomValue(200, 250), 255 }; + } + + camera.target = (Vector2){ player.x + 20.0f, player.y + 20.0f }; + camera.offset = (Vector2){ screenWidth/2.0f, screenHeight/2.0f }; + camera.rotation = 0.0f; + camera.zoom = 1.0f; + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + //-------------------------------------------------------------------------------------- + +#ifdef PLATFORM_WEB + raylib_js_set_entry(GameFrame); +#else + // Main game loop + while (!WindowShouldClose()) + { + GameFrame(); + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- +#endif + + return 0; +} diff --git a/index.html b/index.html index 960a2cd..21ac22f 100644 --- a/index.html +++ b/index.html @@ -68,10 +68,11 @@