diff --git a/include/bindings/ruby.h b/include/bindings/ruby.h index f933eaf..08b91e4 100644 --- a/include/bindings/ruby.h +++ b/include/bindings/ruby.h @@ -116,6 +116,8 @@ DEF_SYM(pass) DEF_SYM(block) DEF_SYM(ignore) DEF_SYM(call) +DEF_SYM(padding) +DEF_SYM(offset) DEF_SYM(x) DEF_SYM(y) DEF_SYM(w) diff --git a/include/window/window.h b/include/window/window.h index 5052eb3..1a435e5 100644 --- a/include/window/window.h +++ b/include/window/window.h @@ -98,6 +98,7 @@ public: ~Window(); // Rendering functions + std::vector load_image_set(const char *path, Vec2 tile_size, Vec2 padding, Vec2 offset, bool pixel_art); uint64_t load_image(const char *path, bool pixel_art); uint64_t create_image(Vec2 size, bool pixel_art); Vec2 get_image_size(uint64_t image_id); diff --git a/samples/button.rb b/samples/button.rb index ba3bd8b..7996b7e 100644 --- a/samples/button.rb +++ b/samples/button.rb @@ -7,6 +7,11 @@ default_font = Font.new "assets/fonts/charybdis.ttf", 24, pixel_art: true bg_image1 = Image.load "assets/images/menu_bg.png", pixel_art: true bg_image2 = Image.load "assets/images/game_over_dead_bg.png", pixel_art: true +tile_set = Image.load_set "assets/images/walls.png", 20, 20, padding: {x: 0, y: 20}, offset: {x: 0, y: 0}, pixel_art: true +# tile_set is an array of images, so we can create an animation from it directly or use the images separately +pp tile_set, "Tile set size: #{tile_set.size}" +tile_animation = Animation.new tile_set, fps: 10 + bg_animation = Animation.new [bg_image1, bg_image2], fps: 5 bg_image1_a = Animation.from(bg_image1) @@ -18,7 +23,7 @@ text = ElementText.new :btn_text, default_font, 0xFFFF00, {name: "Me"}, position rect = ElementRect.new 100, 25, 0xFF0000, position: {x: 50, y: 50}, z: 0, rotation: 60, scale: { x: 1, y: 1 }, alpha: 0.8, click_mode: :block -image_element = ElementImage.new bg_animation, position: {x: 0, y: 0}, z: -1, click_mode: :block, alpha: 1 +image_element = ElementImage.new tile_animation, position: {x: 0, y: 0}, scale: { x: 20, y: 20 }, z: -1, click_mode: :block, alpha: 1 image1 = Image.new 50, 50, pixel_art: true @@ -68,6 +73,8 @@ App.update do |delta_time| green_pixel = [0xFF, 0, 255, 0] # ARGB c_p = ((c_c / 10) % 2) == 1 ? red_pixel : green_pixel raw_bytes = (c_p * (50 * 50)).pack('C*') + # this is not the most efficient way to create a raw byte array, but it works for demonstration purposes + # you can use ` commands to get image data from an external source or generate it procedurally bg_image1.update(0, 0, 50, 50, raw_bytes) image1.update(0, 0, 50, 50, raw_bytes) diff --git a/src/bindings/image.cc b/src/bindings/image.cc index e0b99c7..78513ab 100644 --- a/src/bindings/image.cc +++ b/src/bindings/image.cc @@ -46,6 +46,48 @@ mrb_value image_load(mrb_state *mrb, mrb_value) { return definitions::wrap(definitions::bindings.Image, id); } +mrb_value image_load_set(mrb_state *mrb, mrb_value) { + char *path; + mrb_float tile_width, tile_height; + mrb_value kwargs; + mrb_get_args(mrb, "zff|H", &path, &tile_width, &tile_height, &kwargs); + + bool pixel_art = false; + int padding_x = 0, padding_y = 0, offset_x = 0, offset_y = 0; + if (!mrb_nil_p(kwargs)) { + HASH_BOOL(kwargs, pixel_art, pixel_art); + + mrb_value padding_hash = HASH_GET(kwargs, padding); + if (!mrb_nil_p(padding_hash)) { + HASH_FLOAT(padding_hash, x, padding_x); + HASH_FLOAT(padding_hash, y, padding_y); + } + + mrb_value offset_hash = HASH_GET(kwargs, offset); + if (!mrb_nil_p(offset_hash)) { + HASH_FLOAT(offset_hash, x, offset_x); + HASH_FLOAT(offset_hash, y, offset_y); + } + } + + std::vector images = window.load_image_set( + path, + {(float)tile_width, (float)tile_height}, + {(float)padding_x, (float)padding_y}, + {(float)offset_x, (float)offset_y}, + pixel_art + ); + + mrb_value image_array = mrb_ary_new_capa(mrb, images.size()); + for (uint64_t image_id : images) { + image_pool.use(image_id); + mrb_value image_obj = definitions::wrap(definitions::bindings.Image, image_id); + mrb_ary_push(mrb, image_array, image_obj); + } + + return image_array; +} + mrb_value image_equal(mrb_state *mrb, mrb_value self) { mrb_value other; mrb_get_args(mrb, "o", &other); @@ -104,7 +146,8 @@ mrb_value image_update(mrb_state *mrb, mrb_value self) { namespace definitions { void register_image() { DEF_CLASS(Image, MRB_ARGS_REQ(2) | MRB_ARGS_OPT(1)); - ADD_CLASS_METHOD(Image, "load", image_load, MRB_ARGS_REQ(2) | MRB_ARGS_OPT(1)); + ADD_CLASS_METHOD(Image, "load", image_load, MRB_ARGS_REQ(1) | MRB_ARGS_OPT(1)); + ADD_CLASS_METHOD(Image, "load_set", image_load_set, MRB_ARGS_REQ(3) | MRB_ARGS_OPT(1)); ADD_METHOD(Image, "==", image_equal, MRB_ARGS_REQ(1)); ADD_METHOD(Image, "eql?", image_equal, MRB_ARGS_REQ(1)); ADD_METHOD(Image, "hash", image_hash, MRB_ARGS_NONE()); diff --git a/src/window/window.cc b/src/window/window.cc index 588b3c0..634922e 100644 --- a/src/window/window.cc +++ b/src/window/window.cc @@ -113,6 +113,69 @@ uint64_t Window::load_image(const char *path, bool pixel_art) { return id; } +std::vector Window::load_image_set(const char *path, Vec2 tile_size, Vec2 padding, Vec2 offset, bool pixel_art) { + SDL_Surface *surface = IMG_Load(path); + + if (!surface) { + fprintf(stderr, "Failed to load image set: %s\n", SDL_GetError()); + return {}; + } + + std::vector image_ids; + + int columns = (int)((surface->w - offset.x + padding.x) / (tile_size.x + padding.x)); + int rows = (int)((surface->h - offset.y + padding.y) / (tile_size.y + padding.y)); + + for (int y = 0; y < rows; y++) { + for (int x = 0; x < columns; x++) { + SDL_Rect src_rect = { + (int)(offset.x + x * (tile_size.x + padding.x)), + (int)(offset.y + y * (tile_size.y + padding.y)), + (int)tile_size.x, + (int)tile_size.y + }; + + SDL_Surface *tile_surface = SDL_CreateSurface(src_rect.w, src_rect.h, SDL_PIXELFORMAT_RGBA8888); + SDL_BlitSurface(surface, &src_rect, tile_surface, nullptr); + + SDL_Texture *texture = SDL_CreateTexture( + renderer, + SDL_PIXELFORMAT_RGBA8888, + SDL_TEXTUREACCESS_STREAMING, + tile_surface->w, + tile_surface->h + ); + + if (!texture) { + fprintf(stderr, "Failed to create texture for tile: %s\n", SDL_GetError()); + SDL_DestroySurface(tile_surface); + continue; + } + + SDL_UpdateTexture( + texture, + nullptr, + tile_surface->pixels, + tile_surface->pitch + ); + + if (pixel_art) { + SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); + } + + Image *img = new Image{texture, {(float)tile_surface->w, (float)tile_surface->h}}; + uint64_t id = image_pool.acquire(img); + image_pool.use(id); + image_ids.push_back(id); + + SDL_DestroySurface(tile_surface); + } + } + + SDL_DestroySurface(surface); + return image_ids; +} + uint64_t Window::create_image(Vec2 size, bool pixel_art) { SDL_Texture *texture = SDL_CreateTexture( renderer, diff --git a/writeup/analysis.md b/writeup/analysis.md index edb8bfd..e1e87c2 100644 --- a/writeup/analysis.md +++ b/writeup/analysis.md @@ -233,10 +233,6 @@ core: - ElementText - ElementRect - ElementImage - - ElementScript - uses function to return a pizel buffer each frame, can use frame caching (for stuff liek minimaps) - - should not reallocate every frame, can use dirty flag based system, uses a single string \ - from ruby and maybe keep string alive (add to gc exception) on c++ side too and use the same buffer to send to sdl. \ - so 2 copies 1 from ruby which can eb directly edited and then 1 a sdl texture object - ElementWorld - (It is a view through a camera) - The world has to have an ecs system buitl in it