GML Is it possible for my code to pick a small region of a larger sprite and turn that into a surface?

Is it possible for my code to pick a small region of a larger sprite and turn that into a surface? Bonus if it can do this for several overlapping sprites in the same region (just taking the part that is visible).

I'm building a system that encodes terrain data onto a surface, which then affects the movement of my character. After finding out that surfaces ought to have dimensions that are relatively small and a power of two, it occurred to me that I ought to sub-divide my maps into squares. This brings up two issues: 1 - there will be a lot of time spent cropping and individually saving dozens of sprites from my larger map images, and 2 - it will make edits to the maps much more cumbersome.

My idea is to have my code select a small region of the larger image and generate a surface from that. When the character gets to the end of that region, do the same operation on the next region. What would be even better would be if the code could capture the top-level image of several overlapping sprites instead of being restricted to just one. This would allow me to build my maps up from more modular pieces.
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
After finding out that surfaces ought to have dimensions that are relatively small and a power of two,
This is erroneous. Surfaces can actually be pretty large, and have no pow2 requirement. Yes, supposedly pow2 sizes are more optimised, but I've not tested it and never worried about it... and I've never had issues with multiple surfaces of different sizes in any of my projects. I mention this, because if you can do something easier using a large, non pow2 surface, then I say do it rather than over-complicate things. :)
 
[...] over-complicate things. :)
Actually, what about the second part of my question, whether it's possible to take all the sprites in a given area and make a single surface out of those? For example, several rocks on top of a grassy hill. Being able to do this would allow me to construct levels out of separate parts directly in the room editor, rather than having to create the finished map in GIMP.
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
Actually, what about the second part of my question, whether it's possible to take all the sprites in a given area and make a single surface out of those? For example, several rocks on top of a grassy hill. Being able to do this would allow me to construct levels out of separate parts directly in the room editor, rather than having to create the finished map in GIMP.
Yes, that's perfectly possible. You have two possibilities:

1) Make objects with the sprites, place them in the room, then on room-start loop through the objects, draw them to a surface, and destroy the objects only drawing the surface.
2) Make an Asset Layer in your room, add the sprites, then get the element IDs from the layer on rooms start, loop through them and add them to the surface, then delete the asset layer

However, I'm not sure any of these will suit your needs from what you say. The problem with both these approaches is that surfaces are VOLATILE, which means that if the app is put into the background or minimised, then the memory used for the surface could be wiped, as the OS will use it for other processes. This means that you have always check every step if the surface exists and then recreate it if it doesn't. So, instead of destroying objects you'd need to deactivate them and then reactivate them if the surface goes to redraw them to the surface again, or if you go for the asset layer approach, simply make the layer invisible instead of deleting it, and draw it to surface again...

Another way around this is to draw everything to a surface and then make a SPRITE out of the surface. Sprites made this way are not volatile and so you don't have to worry about that aspect. This has it's own issues though, as by using sprites you will be increasing your texture swaps, which - if you have a lot of them - can also affect performance.

If all you want is to organise visual assets in the most performant way in the room editor, I honestly think you'd be fine simply using an asset layer and adding the sprites to it directly and letting GM handle everything else. This way you can include the sprites in the base texture pages, you don't need to worry about surfaces disappearing, and you don't need t worry about additional texture swaps. Asset layer sprites are actually very optimised and a great way to add environmental details and other visual flair without affecting the performance by very much at all.
 
Probably the biggest problem is that while I've managed to code a lot of this myself, I had somebody else help me build the surface logic and I don't think I fully grasped how it works. (I did finally get a good explanation of what "surface_reset_target()" does this evening while watching a tutorial.)

Unfortunately I think I need to use surfaces for the terrain contour idea to work.

So the system I came up with is this: For every piece of terrain there is a level object with three associated sprites. 1 - the artwork that you see the character walking on (this is also the sprite you see when placing the object), 2 - any foreground bits that should obscure the character, and 3 - a very abstract, tie-dye-looking sprite that encodes the contour data.

The way the contour data sprite works is, I have each of the three color channels, RGB, represent different values of pitch, roll and yaw for the terrain. The game would read the color data for the pixel at the player's current position using "surface_getpixel()", "color_get_red()" etc, do some trigonometry, and then the player's movement would get offset by the terrain. I even made some sprite rotations and various climbing poses that react to the contour data and I have to say the effect is pretty convincing.

But I'm looking at the code and the first thing I notice is, I have no idea how it is that we made the contour data sprite invisible.
Maybe this? Within the surface object's step event:
"draw_clear_alpha(c_black,0)"

The second thing is, we set the specific sprite and dimensions right in the surface's create event, and I can't wrap my head around how to get the "target" sprite to change based on the player's position. (We only got one of several level objects to work.)

Finally, the system didn't allow for any modular level building. Like let's say I wanted to add a rock in the middle of a hill. I currently have no way to do that other than to open the image file and edit both the artwork sprite and the contour data sprite.

So that's why I was thinking the surface's dimensions might want to be based on the player's location, and that what actually draws to the surface would be like "the flattened contour sprites of all level objects within immediate reach of the player." So if I had several overlapping level objects near the player, the surface object's draw event would find their associated contour sprites, draw them with the same layering, and create the single surface that the player actually interacts with.

It seems like I would want to use the "draw_sprite_part()" function to just draw what's within reach of the player, but I wouldn't know how to account for the fact that the number of sprites to draw would be constantly changing.

I hope that makes sense. Again, going back to the first problem, I feel like I don't quite have the vocabulary to even be asking the right questions.

Edit: case in point, apparently it took me an hour to work out how to word all this.
 
Last edited:
I'm trying to follow what's going on, but it's a bit difficult 😖 Some pictures would help. I'm curious to see what your tie-dye sprites are looking like.

Edit: Okay I understand it more now after reading it a few times lol. You are mostly on the right track. It's just a matter of doing the work.

I think I can help with one bit, which is how you say you want to use draw_sprite_part on what's within reach of the player.
You could use a data structure, like a grid, and divide the world up into squares. Then each square could reference an array of the objects that need to be drawn to that surface.
So you get the square the player is in, and loop through the array and draw the sprites.

The thing I don't understand is how your objects are going to follow the same encoding that your terrain does, but I need to see how you're doing the terrain first.

Also, are your levels big enough that the screen is scrolling? Could the whole level fit on one surface? Because then you could implement what Nocturne suggested. Just bake everything into one surface at the start of the level.
 
Last edited:
[ 1 ] 😖 Some pictures would help. I'm curious to see what your tie-dye sprites are looking like.

[...]

[ 2 ] You could use a data structure, like a grid, and divide the world up into squares. Then each square could reference an array of the objects that need to be drawn to that surface.
So you get the square the player is in, and loop through the array and draw the sprites.

[ 3 ] The thing I don't understand is how your objects are going to follow the same encoding that your terrain does, but I need to see how you're doing the terrain first.

[ 4 ] Also, are your levels big enough that the screen is scrolling? Could the whole level fit on one surface? Because then you could implement what Nocturne suggested. Just bake everything into one surface at the start of the level.
Thanks for the reply! Here's what I've got:

1 - I'll try and upload some pictures this evening when I get home. In summary, what I do is create a layer for each axis (pitch roll yaw), mask them in red, green, and blue, and then edit the alpha of each. When you're looking at any one layer it's pretty intuitive (for example, pitch is very close to white at horizontal, 50% grey at vertical, and very dark for overhangs), it just looks abstract when you combine them all.

2 - This sounds like the ideal solution.

3 - Not sure what you're asking here. If you're asking "how do you get each object to pull the correct sprite," here's what I'm thinking:

A - have all the objects and sprites follow a strict naming convention: obj_lev_[name], spr_lev_[name]_art, spr_lev_[name]_contour, apr_lev_[name]_valance.

B - write a script that is then called for every level object. It goes something like "take your name, turn it into a string, replace 'obj' with 'spr', append '_contour', finally call the sprite with that name for use on the surface.

I've already set up a similar system to correctly call specific climbing/rotating animations and it works great, though there may be a more efficient system.

4 - The levels are potentially quite big. I'd like to do that thing where different chunks of map load seamlessly to give the illusion of one big map. For that I'm thinking of having "overlapping" rooms, so room 1 goes A-B-C, room 2 goes C-D-E, and you swap from room 1 to room 2 while you're view is restricted to section C.

I'd also like to set up events that determine what the next room will be, so like section B might have a binary choice, and then in section C the game loads either C-D(1)-E(1) or C-D(2)-E(2) depending on your choice.
 
Okay I understand how you are encoding the terrain. So my question was: are you encoding all of your objects with that data too? (The objects you are trying to draw to a surface) So you're basically drawing all those sprites twice. Wouldn't that mean you couldn't rotate them, since the data would be oriented wrong? Or maybe I'm just imagining it wrong. Whether it does or not doesn't really matter, I'm just curious about it.

Your method of pulling the right sprites could work, but I find it generally a little janky to use string manipulation to get assets. You could instead make a global table (a ds map), where you store keys (the object id's) with the value of an array which contains [art, contour].

So this:
global.spriteTable[? obj_big_rock]
could return the array:
[spr_big_rock, spr_big_rock_contour]

That's how I would do it anyway. Your method could definitely still work.

Using string can just be hard to debug if you have problems. If you accidently name something wrong and it's not showing up in the game, you might not realize what went wrong. If you've added that sprite a while ago without noticing problems because it wasn't used in your testing level, now you go to the level it's supposed to be on, and you might be pretty confused to see it missing. You do you though 😁
 
Last edited:
I don't think I would be drawing the contour sprites twice, just once when creating the surface. I could be wrong about that.

As for rotation, correct, there'd be no way to rotate anything without making the data a bit nonsensical. But if I wanted, say, a pillar and a fallen pillar, not a big deal to make each separately.

By the way, here's some images from the demo I got working. There's no roll for this particular chunk of the map; that's used more for hilly terrain.

Disclaimer: The artwork is from Judson Cowan's map of Lordran from Dark Souls, which I got his permission to use. I'm planning to build my own levels, but this matched the perspective I'm going for so it made a good test map.
 

Attachments

Ahh I get the sprites now. Thanks for uploading those! I thought they would be much smaller chunks of level. Those are pretty high res. Not what I had imagined. But it shouldn't be a problem.
I don't think I would be drawing the contour sprites twice, just once when creating the surface. I could be wrong about that.
Sorry I wasn't being very clear :p I just mean during your actual asset production. If you want to add a rock to the game, you have to draw the rock, and then draw all the rotation data. I thought maybe the game would somehow generate the contour. Hopefully you have a nice pipeline to make this process not so tedious.

To get back to the original topic:
It's entirely possible to just build your whole map in the room editor and have the game handle the rest. On room start, have each object figure out what square (or squares) of the big grid it's in, and add it's contour sprite to the array of that cell. If you really only need these contour surfaces where the player is, you should be able to get away with using 4 surfaces. You couldn't just use one, because you'd be rebuilding it every frame as the player moves, so it'd be pretty slow.
So you'd have a 2x2 grid of surfaces, each the size of the grid cells for the sprite arrays. As the player moves, they should snap to the nearest 4 cells. When they move to a new position, clear them, and draw the sprites that are in the array for that cell.
Hopefully that gets you where you need to be!

Edit: And you shouldn't have any need to "crop" sprites. Even if the sprites are way bigger than the surface, game maker will cull the extra work. It's not wasteful to draw a huge thing to a smaller surface. At least as far as I know.
 
Last edited:
If you really only need these contour surfaces where the player is, you should be able to get away with using 4 surfaces. You couldn't just use one, because you'd be rebuilding it every frame as the player moves, so it'd be pretty slow.
So you'd have a 2x2 grid of surfaces, each the size of the grid cells for the sprite arrays. As the player moves, they should snap to the nearest 4 cells. When they move to a new position, clear them, and draw the sprites that are in the array for that cell.
My friend who helped me with the surface logic came up with a similar idea, but I still don't see the "why" of it.

One thing I know is this: the player's position will have a tendency to "jump." For example, when walking on relatively flat terrain the position marker will be close to the player's (hitbox) feet, but upon approaching a wall that position will jump to the player's (hitbox) hands. So I know that my surfaces will always need to extend beyond the reach (or maximum jump distance) of the position marker. What I think that means is that the surfaces drawn always need to be the area that the position marker is currently in but also the one they are heading toward.
 
What I think that means is that the surfaces drawn always need to be the area that the position marker is currently in but also the one they are heading toward.
Yes, that seems correct.
Picture it this way: Imagine a zoomed out view of your level, with a grid overlayed on top of it. The 4 cells that are closest to the player are green, the rest are black. As the player moves around, you see some cells turn green, while the furthest green ones turn black. The green ones have surfaces bound to them. The surfaces snap to the grid position. When they notice they've moved to a new cell, they are cleared and they draw all the contour sprites that are in the array of that cell. That's a one-time operation. Until the cell moves again, it doesn't spend any more clock cycles drawing sprites.

The reason you'd have 4 surfaces is because you will always have as much data as you need. Once you get close enough to the edge of a surface and the player needs the next cell, then you can remove an old surface that's too far away and generate the new one. Using 4 surfaces, as long as they are bigger than the player, you can always guarantee there will be one cell that's far enough away to not be needed. You couldn't use any less than 4, because there's always the possibility the player could be right in-between the inside corner of a 2x2 grid.

And if you mean "why" as in "why are we using multiple surfaces instead of one giant one?"
You COULD use one giant surface for the whole level, but surfaces do have limits at some point. Seeing how high-res your assets are, I could see you potentially reaching that limit. I don't know how many pixels tall/wide you plan your levels to be, but using the surface grid method, you just don't have to even consider that being an issue. You could have no limit to the size of your levels with this method.

There's also the volatile surfaces issue. If someone minimizes the game, you have to detect this when the game is maximized again. The surface will be gone most likely, and you'd have to re-build the entire thing, which might mean the game seems like it's frozen for a bit when they try to play again. Using the surface grid method, you're only re-building 4 much smaller surfaces, which will be much faster.
 
Last edited:
Top