From 470169016b3e241bd48e4d2db8b7250a6e4ce9c0 Mon Sep 17 00:00:00 2001 From: Roderic Bos Date: Sun, 18 Feb 2024 12:46:10 +0100 Subject: [PATCH] Added sprite animation example --- examples/textures_sprite_anim.c | 125 ++++++++++++++++++++++++++++++++ index.html | 2 +- raylib.js | 39 ++++++++-- resources/scarfy.png | Bin 0 -> 10394 bytes 4 files changed, 159 insertions(+), 7 deletions(-) create mode 100644 examples/textures_sprite_anim.c create mode 100644 resources/scarfy.png diff --git a/examples/textures_sprite_anim.c b/examples/textures_sprite_anim.c new file mode 100644 index 0000000..da51e29 --- /dev/null +++ b/examples/textures_sprite_anim.c @@ -0,0 +1,125 @@ +/******************************************************************************************* +* +* raylib [textures] example - Sprite animation +* +* Example originally created with raylib 1.3, last time updated with raylib 1.3 +* +* 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) 2014-2024 Ramon Santamaria (@raysan5) +* +********************************************************************************************/ + +#include "raylib.h" + +void raylib_js_set_entry(void (*entry)(void)); + +#define MAX_FRAME_SPEED 15 +#define MIN_FRAME_SPEED 1 +const int screenWidth = 800; +const int screenHeight = 450; + +// NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required) +Texture2D scarfy; + +Vector2 position; +Rectangle frameRec; +int currentFrame = 0; +int framesCounter = 0; +int framesSpeed = 8; // Number of spritesheet frames shown by second + +void GameFrame() +{ + // Update + //---------------------------------------------------------------------------------- + framesCounter++; + + if (framesCounter >= (60/framesSpeed)) + { + framesCounter = 0; + currentFrame++; + + if (currentFrame > 5) currentFrame = 0; + + frameRec.x = (float)currentFrame*(float)scarfy.width/6; + } + + // Control frames speed + if (IsKeyPressed(KEY_RIGHT)) framesSpeed++; + else if (IsKeyPressed(KEY_LEFT)) framesSpeed--; + + if (framesSpeed > MAX_FRAME_SPEED) framesSpeed = MAX_FRAME_SPEED; + else if (framesSpeed < MIN_FRAME_SPEED) framesSpeed = MIN_FRAME_SPEED; + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + DrawTexture(scarfy, 15, 40, WHITE); + DrawRectangleLines(15, 40, scarfy.width, scarfy.height, LIME); + DrawRectangleLines(15 + (int)frameRec.x, 40 + (int)frameRec.y, (int)frameRec.width, (int)frameRec.height, RED); + + DrawText("FRAME SPEED: ", 165, 210, 10, DARKGRAY); + DrawText(TextFormat("%02i FPS", framesSpeed), 575, 210, 10, DARKGRAY); + DrawText("PRESS RIGHT/LEFT KEYS to CHANGE SPEED!", 290, 240, 10, DARKGRAY); + + for (int i = 0; i < MAX_FRAME_SPEED; i++) + { + if (i < framesSpeed) DrawRectangle(250 + 21*i, 205, 20, 20, RED); + DrawRectangleLines(250 + 21*i, 205, 20, 20, MAROON); + } + + DrawTextureRec(scarfy, frameRec, position, WHITE); // Draw part of the texture + + DrawText("(c) Scarfy sprite by Eiden Marsal", screenWidth - 200, screenHeight - 20, 10, GRAY); + + EndDrawing(); + //---------------------------------------------------------------------------------- +} + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + InitWindow(screenWidth, screenHeight, "raylib [texture] example - sprite anim"); + + // NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required) + scarfy = LoadTexture("resources/scarfy.png"); // Texture loading + + position = (Vector2){ 350.0f, 280.0f }; + frameRec = (Rectangle){ 0.0f, 0.0f, (float)scarfy.width/6, (float)scarfy.height }; + currentFrame = 0; + + framesCounter = 0; + framesSpeed = 8; // Number of spritesheet frames shown by second + + 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 + //-------------------------------------------------------------------------------------- + UnloadTexture(scarfy); // Texture unloading + + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- +#endif + + return 0; +} \ No newline at end of file diff --git a/index.html b/index.html index 960a2cd..b39f5d2 100644 --- a/index.html +++ b/index.html @@ -71,7 +71,7 @@ "core": ["core_basic_window", "core_basic_screen_manager", "core_input_keys", "core_input_mouse_wheel",], "shapes": ["shapes_colors_palette"], "text": ["text_writing_anim"], - "textures": ["textures_logo_raylib"], + "textures": ["textures_logo_raylib","texture_sprite_anim"], } const defaultWasm = Object.values(wasmPaths)[0][0]; diff --git a/raylib.js b/raylib.js index a6a0154..6058099 100644 --- a/raylib.js +++ b/raylib.js @@ -248,7 +248,15 @@ class RaylibJs { this.ctx.fillStyle = color; this.ctx.fillRect(x, y, w, h); } - + DrawRectangleLines(x, y, w, h, color_ptr) { + const buffer = this.wasm.instance.exports.memory.buffer; + const color = getColorFromMemory(buffer, color_ptr); + const lineThick = 2; + this.ctx.strokeStyle = color; + this.ctx.lineWidth = lineThick; + this.ctx.strokeRect(x + lineThick/2, y + lineThick/2, w - lineThick, h - lineThick); + } + DrawRectangleLinesEx(rec_ptr, lineThick, color_ptr) { const buffer = this.wasm.instance.exports.memory.buffer; const [x, y, w, h] = new Float32Array(buffer, rec_ptr, 4); @@ -285,15 +293,22 @@ class RaylibJs { const buffer = this.wasm.instance.exports.memory.buffer; const filename = cstr_by_ptr(buffer, filename_ptr); - var result = new Uint32Array(buffer, result_ptr, 5) - var img = new Image(); + const result = new Uint32Array(buffer, result_ptr, 5) + const img = new Image(); img.src = filename; + + //wait for the image to load, busy loop no idea what the correct solution would be + let maxWait = 1000; + while(maxWait >= 0) + maxWait--; + + console.log("image loaded", filename, img.width, img.height); this.images.push(img); result[0] = this.images.indexOf(img); - // TODO: get the true width and height of the image - result[1] = 256; // width - result[2] = 256; // height + // TODO: get the true width and height of the image because it sometimes isnt loaded yet a refresh helps because it's cached + result[1] = img.width || 256; // width + result[2] = img.height || 256; // height result[3] = 1; // mipmaps result[4] = 7; // format PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 @@ -309,6 +324,18 @@ class RaylibJs { this.ctx.drawImage(this.images[id], posX, posY); } + //void DrawTextureRec(Texture2D texture, Rectangle source, Vector2 position, Color tint) + DrawTextureRec(texture_ptr, rec_ptr, position_ptr, color_ptr) { + const buffer = this.wasm.instance.exports.memory.buffer; + const [id, width, height, mipmaps, format] = new Uint32Array(buffer, texture_ptr, 5); + const [x, y, w, h] = new Float32Array(buffer, rec_ptr, 4); + const [px, py] = new Float32Array(buffer, position_ptr, 2); + // // TODO: implement tinting for DrawTexture + // const tint = getColorFromMemory(buffer, color_ptr); + + this.ctx.drawImage(this.images[id], x, y, w, h, px, py, w, h); + + } // TODO: codepoints are not implemented LoadFontEx(result_ptr, fileName_ptr/*, fontSize, codepoints, codepointCount*/) { diff --git a/resources/scarfy.png b/resources/scarfy.png new file mode 100644 index 0000000000000000000000000000000000000000..be3b83d0e7f523e2cf80da19e274a8479e33fe13 GIT binary patch literal 10394 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 E4?57ykpKVy literal 0 HcmV?d00001