DynamicSprite + mask(both moving at different speed)

Started by Tarnos12, Thu 25/02/2021 10:21:46

Previous topic - Next topic

Tarnos12

Hello,

I am trying to achieve a "window" effect using a mask.

Anything "outside" a window should be transparent(sprite not visible, but room and other objects should still appear)
I am using DynamicSprite.Create attached to GUI as a drawing surface.

The window will move together with few other sprites(all of them have to be "masked" by the "window".

An example:
https://i.gyazo.com/7ea0268d3ca725087637efa1b0e38ce7.mp4

The horizontal black line is the "edge" of the viewport(so anything above it is not visible)
The red border box is a "mask"/window(I guess that a mask is everything around it, not sure how that works exactly hence why I am here) that moves(so half of the background is actually inside a room at the start viewport, just hidden beneath the mask, until the mask moves down as you can see on the video)
So, all parts move together at various speed.

I am using Tween Module for the x/y values, but everything is being drawn inside repeatedly_execute_always()

eri0o

You could just tween gui controls (like buttons) with the sprite and then use the GUI itself as the mask window, then there's no need for dynamic sprites.

Tarnos12

#2
Can you explain it in more detail?
The main issue is that I have unknown amount of sprites to draw.
Can I draw multiple of sprites on same button?(probably not)

Still it is fine if it works, I will just hard code 30 buttons and use those.

@EDIT:

I think I kinda get the idea.
GUI(parent object) is a mask
And the child object is a GUI Control like a button.
So I can move both GUI + control.
The problem is that, moving GUI will also move the control, even tho I want the control to move at different speed, so I will have to do some extra calculations to make sure it moves at the right speed.
Probably just subtract the speed at which GUI object is moving.

@EDIT2:
So I would probably have to "move" things in a other directions, while having them hidden.
That will be counter-intuitive, but if that's the way, then I will have to do that.

eri0o

Does it needs to be a GUI? If those could be in a room, you could use the Camera and Viewport config to slice a portion of the room and position where you want and then use a parallax module (I do have one, lol) with the camera control off, and slide the camera using Tween.

The advantage of using the GUI though is GUIs can spawn on top of others things.

If you want to go your first option with Dynamic Sprites, you can use Custom Tweens to tween the integers that describes the position of things.

Ah, yes, you could also just Tween one thing (say the GUI position) and use it as input to calculate the position of all others - instead of trying to do relative tweens on them.

Their position will be something like ctrl.y = ctrl_initial_y + FloatToInt(IntToFloat(gGUI.y - ggui_inital_y )* magic_number)

Tarnos12

Hey, so I am using GUI as a drawing surface, because I need it to be on top of everything.
Because I am using other GUI elements to do stuff(unless I move those elements into dynamic sprite, which I probably could do, but that would be a pain right now).

So, I am using DynamicSprites, but I am drawing them on a surface which is attached to a GUI.

The camera/viewport is actually what I was thinking before, but the problem with that I can't change the shape much right?
The mask would allow me to have different shapes which would be a bonus.

The example video in my first post, is using a rectangle/box "mask"/window, but I'd like the ability to use a sprite alpha channel as a mask.
That would be ideal.
But if that's not an easy thing to do, then rectangle/square shaped mask is good enough for now.

eri0o

What's the resolution of your game? If the size in pixels of this Dynamic Sprite is low enough you can hammer the Dynamic Sprite with CopyTransparencyMask every frame.

https://adventuregamestudio.github.io/ags-manual/DynamicSprite.html#dynamicspritecopytransparencymask

If the resolution of your game is big, then you need to have a static mask on top, with the window carved out. If you wish to have this with the room there instead of a color or something, just screenshot the room once, place the screenshot on a GUI on top of everything, and carve out your window with a single CopyTransparencyMask.

Tarnos12

#6
1920x1080
I did consider doing what you just said, but I don't know how to achieve that.
Because the mask has to be a part of a sprite.(background sprite)

I wouldn't mind trying to do that inside a repeatedly_execute_always.
It will stop redrawing a mask once it's done moving anyway, because it will have reached the end point and mask will no longer be needed.(but it should probably stay active).

I've been following that link and I am stuck at the fact that the mask has to be applied to the sprite, making it "connected" to the sprite, so it follows the sprite as it moves, which is not ideal.
I'd like the mask to be separate, it's own sprite.
I am trying to make a second copy of a background and applying a mask to it, I even tried to make background transparent when drawing, but nothing works.

@EDIT:

So I have those 2 images:
https://cdn.discordapp.com/attachments/798018705744986134/814471159701700638/07_01_bg_mask1.png
https://cdn.discordapp.com/attachments/798018705744986134/814471162326286366/07_01_bg_mask2.png

I am trying to get the mask effect from them, how would I use them properly?
The black rect in the middle is the supposed "background"
And the other one is the "window" transparency mask I copy.
Ignoring the fact that I get the pink border, it doesn't seem to work, it doesn't hide anything.

@EDIT2:
I am afraid that I don't understand enough about masks to use them, so I don't know what do you mean by carve out the window.
Also I need the mask to move, not be static otherwise it's not useful to me.
It has to be the same effect as the video in first post.

eri0o

Break the problem in parts.


  • How tween works and what is a custom tween
  • Relative movement of things (parallax)
  • 1 element, 1 background, no room
  • 2 elements, 1 background, no room
  • multiple elements, 1 background, no room
  • Windowing

The implementation doesn't matter as long you achieve the result at the end. Then as each thing works, post here.

The equation I gave on my other comment is how I would do, and the problem would be in finding the magic number for each thing.

AGS is a bit limited when working in bigger resolution, so the ideas have to be done more strategically. I would not use Dynamic Sprites that update every frame at that resolution since they will usually be slow. GUIs internally also use bitmap drawing, but I would test how it works before discarding - they aren't that slow for some reason... Cameras and Viewports will do clipping on the graphics card, and things that move on the room also use accelerated graphics, so you can usually have things faster there - so if you can switch rooms, they are usually good to move things around. Overlays are also accelerated, can do whatever with them with good results - but they are fairly limited in what one can do.

But, better to experiment and test the ideas.

Tarnos12

Quote from: eri0o on Thu 25/02/2021 12:35:10
Break the problem in parts.

  • How tween works and what is a custom tween
  • Relative movement of things (parallax)
  • 1 element, 1 background, no room
  • 2 elements, 1 background, no room
  • multiple elements, 1 background, no room
  • Windowing

1) I am using custom tween, it does it's job as intended.
2) Probably doing it already, all parts of a full scene are moving at different speed, because tween module is handling that(they start at different x/y coordinates, but all have same timing)
3) I don't understand, but the idea is to have 1 "image" which is created from multiple parts(background, foreground, foreground2 etc.) + mask, all moving at different speed due to starting at different position.
4) What do you mean by Windowing?

Crimson Wizard

#9
TBH looking at your video example, if I had to do something similar I definitely would do that using GUIs or GUI buttons, as you can easily adjust their heights and widths, thus limiting the picture. At least would try. IMO if working that would save a lot of time and effort.

You were saying that you dont want to use GUI as there's unknown number of sprites. But how many sprites approximately? You can create any amount of dummy gui and gui buttons, then make a system that reuses them. That may end up much easier than trying to do all this using masks and dynamic sprites (and may be perfoming faster at runtime).



PS. This is definitely not a topic for "Beginner Tech" forum, more like "Advanced Tech".

Tarnos12

Quote from: Crimson Wizard on Thu 25/02/2021 12:50:23
TBH looking at your video example, if I had to do something similar I definitely would do that using GUIs or GUI buttons, as you can easily adjust their heights, thus limiting the picture. At least would try. IMO if working that would save a lot of time and effort.

You were saying that you dont want to use GUI as there's unknown number of sprites. But how many sprites approximately? You can create any amount of dummy gui and gui buttons, then make a system that reuses them. That may end up much easier than trying to do all this using masks and dynamic sprites (and may be perfoming faster at runtime).



PS. This is definitely not a topic for "Beginner Tech" forum, more like "Advanced Tech".

I assumed that masks might be for beginners so I kinda went here, if you have the power, feel free to move it to advanced.
I will listen to the advice and use GUI, approx 30 sprites at any time(max, most of the time 4-12 I think)

The question now is, do I need 1 GUI per sprite, or can 1 GUI hold a "full image" and have multiple buttons?
I think that I kinda get the idea now, the GUI background will be transparent and GUI buttons will have all sprites(background + fg + other)
The GUI will act as a mask, and I have to calculate the position of the sprites correctly, since the image can come from any position on the screen, left/right/top/bottom.

Also, will this allow me to have different shaped "masks"?
Because GUI by default has a square/rect shape and that will be the shape of the mask right?

Crimson Wizard

#11
Quote from: Tarnos12 on Thu 25/02/2021 12:57:18
I will listen to the advice and use GUI, approx 30 sprites at any time(max, most of the time 4-12 I think)

You could make like 1.5-2 times more just in case, that won't hurt if unnecessary ones are hidden.

Quote from: Tarnos12 on Thu 25/02/2021 12:57:18
The question now is, do I need 1 GUI per sprite, or can 1 GUI hold a "full image" and have multiple buttons?
I think that I kinda get the idea now, the GUI background will be transparent and GUI buttons will have all sprites(background + fg + other)
The GUI will act as a mask, and I have to calculate the position of the sprites correctly, since the image can come from any position on the screen, left/right/top/bottom.

This is what I assumed at first, yes. Of course you may use separate GUIs too, if that's convenient, but if you are doing generic system for multiple animations, then there will be question of how to choose how many guis use...
Maybe letting to choose which "layer" (gui) to put new sprite on. But guess that's all solveable after main issues are solved.

Quote from: Tarnos12 on Thu 25/02/2021 12:57:18
Also, will this allow me to have different shaped "masks"?
Because GUI by default has a square/rect shape and that will be the shape of the mask right?

This is a good question. No, this won't be possible to do by just adjusting the gui or control size, so in that case you would have to actually cut sprites, unless there's another trick I don't see right now.

Could you draw an example of how the differently shaped mask would work, similar to the video you posted above? Even if it's just a static mockup. To make it easier to imagine the situation.

Tarnos12

Yeah definitely will do more than 30 just in case, but I remember there is an option to change ID of a GUI element, and it will change ID of all other GUIs to match.
Which might be enough, since this GUI is the only one that I would reference by ID so I can do ID + 1 etc(combined with array index I already use)

So I think that each GUI will have 10 buttons even(if I can add them at runtime... that would be perfect, but I assume that I can't) I will use however many I need and hide the rest.
This is really the best solution, it basically covers almost all my needs.

Regarding the mask, is it possible to have first layer, button image to be a mask?
Like a transparent shape, or something.
No idea how that works tbh, but I imagine multiple layers to allow for mask sprite to be attached.

For the video example, here you go:
https://i.gyazo.com/7f1814d797b1a9b3117591f00cad16e0.mp4

Crimson Wizard

#13
Quote from: Tarnos12 on Thu 25/02/2021 13:54:18
For the video example, here you go:
https://i.gyazo.com/7f1814d797b1a9b3117591f00cad16e0.mp4

Ugmmmm... yeah, dont think you can do this in AGS at the moment without actually editing sprites... that would be super cool to have though, but it's way above engine features right now.
No, placing mask on a separate gui or button won't work, as that won't interact with other sprites.
For a quick thought, you maybe will have to calculate mask position behind the scenes, and then apply it to each sprite on gui. In other words, you "imagine" that mask is there, knowing its position you compare this imaginary position of mask and position of sprites on screen, then apply mask correspondingly. I just don't know how exactly yet, but this must be doable with right math.



PS. btw, speaking of reusing guis and controls; I uploaded this module recently, it is a convenient way to keep track of used and unused objects from a pool. Of course it's rather easy to script this for specific purpose too.

Tarnos12

So regarding the mask and having it position on runtime for each sprite.
I have no clue how to do that, because mask can be done with DynamicSprite only afaik?
DynamicSprite has to be same size as mask as well.
Also mask has to be attached to the sprite and because they are same size, I am unable to offset the mask(otherwise I would have done that already).
And the mask has to interact with all sprites, so like you mentioned I could reuse same mask for each sprite, but move it at the same location.

I will take a look at the module as well.

Will post an update today/tomorrow if I have more questions regarding masks, otherwise I will mark one of the replies as an answer.

Thanks

eri0o

Quoteotherwise I will mark one of the replies as an answer.

This is not stackoverflow, feel free to post at anytime  :-D

Crimson Wizard

#16
From what I understand, for a simple proof of concept of irregularily shaped mask you need:

- your actual image, from resources, or else, that you want to display on screen (let's call it "real sprite").
- 1 dynamic sprite which serves as a final sprite (sprA);
- 1 "helper" dynamic sprite of size equal to real sprite (sprB); this will be used to create an individual cut-out mask for that sprite;
- the mask sprite itself; of its own size (sprMask).

The mask should be drawn as a simple 1-colour shape with transparent sections (they will define which parts not visible).
It does not have to be larger than necessary to accomodate the shape.

You need to track the imaginary position of that mask on screen. You could do this either with script variables, or even place the mask on a separate GUI, set Visible = false, and arrange it as necessary. Thus you will be able to set and get its size and position, and it may even be made optionally visible for testing purposes.

The process of applying mask on a given sprite is this:
1. Calculate relative positions between sprite and mask on screen.
2a. If sprite is fully outside of mask, then hide it (hide whatever gui or button it is on).
2b. If sprite is at least partially touching mask, then make it visible and proceed.
3. Take "helper" dynamic sprite (sprB) and clear it with transparent color.
4. Draw mask shape (sprMask) upon the "helper" sprite (sprB), at the proper position (use previously calculated relative position). Don't worry about mask being drawn partially outside, engine can handle this and it simply skips pixels that end up outside of the surface.
5. When this is done, now take the final sprite (sprA), and -
5a. Draw real sprite upon it.
5b. Use DynamicSprite.CopyTransparencyMask to copy transparent areas from "helper" sprite (sprB) to final sprite (sprA).
6. You got yourself a final sprite with the mask shape cut according to their relative position.
If this final sprite was assigned to a GUI or gui button, they will update automatically.

UPD: Pseudocode:
Code: ags

DrawingSurface *dsB = sprB.GetDrawingSurface();
dsB.Clear();
dsB.DrawImage( maskShapeSpriteNumber );
dsB.Release();

DrawingSurface *dsA = sprA.GetDrawingSurface();
dsA.DrawImage( realSpriteNumber );
dsA.Release();

sprA.CopyTransparencyMask(dsB.Graphic);



PS.
The final sprite (sprA) may be anything actually. It could be a single sprite, or a surface to merge group of sprites on (even whole scene). In any case the mask is applied using exactly same algorithm.
Depending on your case it may be more optimal to first draw several sprites to a bigger surface, and then apply mask to that surface, general idea remains same.
So, after making sure that above solution works, maybe it's worth doing some tests to see if masking separate sprites or merged sprites works faster.

Tarnos12

Quote from: Crimson Wizard on Fri 26/02/2021 15:58:23
From what I understand, for a simple proof of concept of irregularily shaped mask you need:

- your actual image, from resources, or else, that you want to display on screen (let's call it "real sprite").
- 1 dynamic sprite which serves as a final sprite (sprA);
- 1 "helper" dynamic sprite of size equal to real sprite (sprB); this will be used to create an individual cut-out mask for that sprite;
- the mask sprite itself; of its own size (sprMask).

The mask should be drawn as a simple 1-colour shape with transparent sections (they will define which parts not visible).
It does not have to be larger than necessary to accomodate the shape.

You need to track the imaginary position of that mask on screen. You could do this either with script variables, or even place the mask on a separate GUI, set Visible = false, and arrange it as necessary. Thus you will be able to set and get its size and position, and it may even be made optionally visible for testing purposes.

The process of applying mask on a given sprite is this:
1. Calculate relative positions between sprite and mask on screen.
2a. If sprite is fully outside of mask, then hide it (hide whatever gui or button it is on).
2b. If sprite is at least partially touching mask, then make it visible and proceed.
3. Take "helper" dynamic sprite (sprB) and clear it with transparent color.
4. Draw mask shape (sprMask) upon the "helper" sprite (sprB), at the proper position (use previously calculated relative position). Don't worry about mask being drawn partially outside, engine can handle this and it simply skips pixels that end up outside of the surface.
5. When this is done, now take the final sprite (sprA), and -
5a. Draw real sprite upon it.
5b. Use DynamicSprite.CopyTransparencyMask to copy transparent areas from "helper" sprite (sprB) to final sprite (sprA).
6. You got yourself a final sprite with the mask shape cut according to their relative position.
If this final sprite was assigned to a GUI or gui button, they will update automatically.

UPD: Pseudocode:
Code: ags

DrawingSurface *dsB = sprB.GetDrawingSurface();
dsB.Clear();
dsB.DrawImage( maskShapeSpriteNumber );
dsB.Release();

DrawingSurface *dsA = sprA.GetDrawingSurface();
dsA.DrawImage( realSpriteNumber );
dsA.Release();

sprA.CopyTransparencyMask(dsB.Graphic);



PS.
The final sprite (sprA) may be anything actually. It could be a single sprite, or a surface to merge group of sprites on (even whole scene). In any case the mask is applied using exactly same algorithm.
Depending on your case it may be more optimal to first draw several sprites to a bigger surface, and then apply mask to that surface, general idea remains same.
So, after making sure that above solution works, maybe it's worth doing some tests to see if masking separate sprites or merged sprites works faster.

Thanks, this solution works great.
I have tested it with 1 mask on top of multi sprite DynamicSprite, instead of attaching a mask to each sprite separately.
Once I figure out proper math, it will work great.


SMF spam blocked by CleanTalk