Hey guys.
I'm making a paint by number game for my daughter, she really likes pokemon so i wanna do the original 151 in it. I spent several hours doing just bulbasaur. I've got it so it works perfect, however i feel theres a much better way it could be done, right now its using at least 60 objects for just Bulbasaur.
Theres nothing special or different about this, its just like any other coloring game on the app stores, pick a color, the blocks to be colored get highlighted. Once every color is done, then you get a congrats and it takes you back to the image selection
Basically i wanna simplfy the whole thing so its much more effiecent than having to work with so many objects.
Here's a few quick screenshots and a link to the project file.
Any help would be greatly appreciated.
Download Link
View attachment 29402View attachment 29403View attachment 29404View attachment 29405
Nice project.
Here are some tips:
1) You can group together sprites and objects that are very similar in sub folders.
In the resource tree (menu on the left side), right click on "Sprites" and click on "Create Group", you can create subfolders.
This helps keeping projects with lots of resources overviewable.
2) Learn about arrays.
You can store any amount of values simultaneously in a single variable by using arrays, which is super helpful for these kind of things.
Read up on them
here, as I'll use them quite a bit.
3) Objects in GameMaker have a built-in variable
image_blend.
With this variable, you can filter colors out of sprites.
If you have a white square, then the color that will actually be drawn, is exactly the color image_blend is set to.
You can use this to only need 1 obj_bulbasaurX object and only 1 spr_bulbasaurX sprite.
A reference for how you can specify colours in GML can be found
here.
For now, just keep this in mind, I'll come back to it later.
4) In obj_selectX, you use drawing functions in a mouse click event.
Generally, drawing functions only work in draw events, so you can get rid of:
GML:
draw_set_font(font0);
draw_set_colour(c_black);
draw_text(100, 100, "1 - " + string(global.bulbasaur1) + "/75")
in the mouse left pressed event of obj_select1 for example.
5) You only need 1 obj_select and you don't need the global.bulbasaurX variables.
You can derive the amount of squares that still need to be painted by counting the amount of obj_X instances in the room.
You can derive the value to which global.bulbasaurcolor needs to be set when clicking on an obj_select instance by looking at the y-value.
This means that you can remove all your obj_selectX objects and create a new object_select object with:
- spr_select as sprite
- as create event:
GML:
my_index = (y - 192) / 32;
- as step event:
GML:
var obj_X;
obj_X[0] = obj_1;
obj_X[1] = obj_2;
obj_X[2] = obj_3;
obj_X[3] = obj_4;
obj_X[4] = obj_5;
obj_X[5] = obj_6;
obj_X[6] = obj_7;
obj_X[7] = obj_8;
obj_X[8] = obj_9;
obj_X[9] = obj_10;
obj_X[10] = obj_11;
obj_X[11] = obj_12;
obj_X[12] = obj_13;
obj_X[13] = obj_14;
obj_X[14] = obj_15;
obj_X[15] = obj_16;
if instance_number(obj_X[my_index]) == 0 {
instance_change(obj_done, false);
}
- as mouse left pressed event:
GML:
global.bulbasaurcolor = my_index + 1;
6) Let's now tackle the bottleneck of your project: converting an image into objects per pixel.
Ideally, you should just have to add a sprite and have some code that automatically converts this sprite into the desired objects.
For this, we need a system to iterate through every pixel of the image however.
We can perform iterations using
for-loops.
We are able to extract colors from sprites using
surfaces in combination with
buffers.
It's a lot to take in at once if you're new to GameMaker, but it will make development for this project a lot easier.
I'll also be using the
with construction, the
other keyword,
list data structures, the
increment operator,
local scope variables and some common built-in functions.
Rename spr_bulbasaur2 to spr_image_pixel.
Remove spr_bulbasaur1 and spr_bulbasaur3 through spr_bulbasaur16.
Rename obj_bulbasaur2 to obj_image_pixel.
Remove obj_bulbasaur1 and obj_bulbasaur3 through obj_bulbasaur16.
Create a new object called obj_sprite_converter.
Put in its create event:
GML:
var obj_X;
obj_X[0] = obj_1;
obj_X[1] = obj_2;
obj_X[2] = obj_3;
obj_X[3] = obj_4;
obj_X[4] = obj_5;
obj_X[5] = obj_6;
obj_X[6] = obj_7;
obj_X[7] = obj_8;
obj_X[8] = obj_9;
obj_X[9] = obj_10;
obj_X[10] = obj_11;
obj_X[11] = obj_12;
obj_X[12] = obj_13;
obj_X[13] = obj_14;
obj_X[14] = obj_15;
obj_X[15] = obj_16;
var w = sprite_get_width(global.sprite_to_convert);
var h = sprite_get_height(global.sprite_to_convert);
var temp_surf = surface_create(w, h);
surface_set_target(temp_surf);
draw_clear_alpha(c_black, 0);
draw_sprite(global.sprite_to_convert, 0, 0, 0);
surface_reset_target();
var temp_buff = buffer_create(w * h * 4, buffer_grow, 1);
buffer_get_surface(temp_buff, temp_surf, 0, 0, 0);
for(var i = 0; i < w; i++) {
for(var j = 0; j < h; j++) {
var blue = buffer_read(temp_buff, buffer_u8);
var green = buffer_read(temp_buff, buffer_u8);
var red = buffer_read(temp_buff, buffer_u8);
var alpha = buffer_read(temp_buff, buffer_u8) / 255;
if alpha > 0 {
var color = make_colour_rgb(red, green, blue);
var color_index = ds_list_find_index(global.color_list, color);
var pixel_inst = instance_create(432 + i * 16, 48 + j * 16, obj_X[color_index]);
pixel_inst.my_color = color;
}
}
}
buffer_delete(temp_buff);
surface_free(temp_surf);
for(var k = 0; k < ds_list_size(global.color_list); k++) {
var ox = 32;
var oy = 192 + k * 32;
var select_background_inst = instance_create(ox, oy, obj_image_pixel);
select_background_inst.image_blend = ds_list_find_value(global.color_list, k);
select_background_inst.image_xscale = 2;
select_background_inst.image_yscale = 2;
instance_create(ox, oy, obj_select);
}
instance_destroy();
Now add a new sprite containing bulbasaur and call it spr_bulbasaur.
Then create a new room called rm_paint_bulbasaur and put it as first room.
Give this room a width of 1920 and a height of 1080.
Put an instance of object obj_game and object obj_brush in the room.
In the
room creation code, put:
GML:
global.sprite_to_convert = spr_img_test;
global.color_list = ds_list_create();
ds_list_add(global.color_list, make_colour_rgb(54, 59, 79));
ds_list_add(global.color_list, make_colour_rgb(255, 255, 255));
ds_list_add(global.color_list, make_colour_rgb(156, 27, 48));
ds_list_add(global.color_list, make_colour_rgb(76, 17, 26));
// Repeat this for all the remaining colors
instance_create(0, 0, obj_sprite_converter);
Finally, change every collision event of obj_brush with obj_X to:
GML:
// Replace X with the appropriate number
if global.bulbasaurcolor == X {
with other {
instance_change(obj_image_pixel, false);
image_blend = ds_list_find_value(global.color_list, X - 1);
}
global.bulbasaurtotal += 1;
}
And run the game to see the results.
Everything should still work the same, but it is all generated from a sprite now.
7) I can still further improve the design by e.g. getting rid of the multiple obj_X objects,
but I think this is already a lot to process and understand and that you will already be able to quickly expand your project now.
I hope this helps.
If you have any questions, feel free to ask them!