I'm making a program now where you have lots of "Pets". Each looks different according to it's "PetID" but I only have one "Pet" object for blitting. Therefore my code looks like this:
Code:
Private Function PetDraw(i As Integer)
'Call this function to draw Pets
'Assumes that the mask is right of the sprite in the image file
'Frames are square and side-by-side
Pet.Picture = LoadPicture(App.Path & "\Images\" & PetID(i) & ".gif")
If (PetLeft(i) = False) Then
BitBlt Me.hdc, PetX(i), PetY(i), (Pet.Width / 2), Pet.Height, Pet.hdc, (Pet.Width / 2), 0, vbSrcAnd
BitBlt Me.hdc, PetX(i), PetY(i), (Pet.Width / 2), Pet.Height, Pet.hdc, 0, 0, vbSrcPaint
Else 'Flip Pet (facing LEFT)
StretchBlt Me.hdc, PetX(i) + (Pet.Width / 2), PetY(i), -(Pet.Width / 2), Pet.Height, Pet.hdc, (Pet.Width / 2), 0, (Pet.Width / 2), Pet.Height, vbSrcAnd
StretchBlt Me.hdc, PetX(i) + (Pet.Width / 2), PetY(i), -(Pet.Width / 2), Pet.Height, Pet.hdc, (Pet.Width / 2), 0, Pet.Width, Pet.Height, vbSrcPaint
End If
End Function
I have two questions:
1) Is this at all efficient, as the line in red changes the Pet object's picture very frequently to draw all the different Pets?
2) Is there a better way of drawing transparent pictures without each image having to have a mask alongside it? I've drawn a lot of Pets and I don't particularly want to go through them adding masks.
TransparentBlt and AlphaBlend APIs can do it in a single call using an image that has transparency. LoadPictureGDI+ in my signature is well designed for loading sprites, such as GIFs or PNGs. The accompanying AlphaBlt can draw them.
You could make Pet an array, and load a picture into each array element instead of loading them every time.
I definitely agree with storing the pictures to an array... and if the same picture could be used for more than one pet at the same time, it might even be worth having one array for the pictures, and then for each pet store the array index of the picture array.
In addition to that, I notice that there are already several arrays for pets (PetX/PetY/PetLeft), which is a bit "untidy". I would recommend creating a user defined Type instead, and then creating an array of that. eg:
Code:
Type udt_Pet
X as Long
Y as Long
FacingLeft as Boolean
'more items here...
End Type
Private Pets() as udt_Pet
The usage of this array wouldn't be too different when using just a single item (eg: PetX(i) becomes Pets(i).X ), but by using a With block it makes a big difference to the size of code that uses multiple items for the same pet (and makes it slightly faster too).
For the code you showed, it would be changed like this:
Code:
With Pets(i)
If (.FacingLeft = False) Then
BitBlt Me.hdc, .X, .Y, (Pet.Width / 2), Pet.Height, Pet.hdc, (Pet.Width / 2), 0, vbSrcAnd
BitBlt Me.hdc, .X, .Y, (Pet.Width / 2), Pet.Height, Pet.hdc, 0, 0, vbSrcPaint
Else
...
End If
End With
Another issue regarding efficiency, doing division is rather slow, so your repeated use of (Pet.Width / 2) is not helping (but is not on the same scale as loading the pictures). It would be best to calculate it just once and store it to a variable.
I don't think you can get away with StretchBlt without using a mask, which would bring you back to where you were before.
What you could do is find a mask maker program (I'm pretty sure there is one in the CodeBank or UtilityBank forums), as that will make it much easier to create the masks.
How about not using any API call and not having a mask? Just set transparent color in your GIF palette and draw.
Project with a simple test picture attached, code only below:
Code:
Option Explicit
Dim m_Pic As StdPicture
Private Sub Form_Load()
Set m_Pic = LoadPicture(App.Path & "\Test.gif")
End Sub
Private Sub Form_Paint()
Dim Width As Long, Height As Long
' convert himetric to pixels
Width = Me.ScaleX(m_Pic.Width, vbHimetric, vbPixels)
Height = Me.ScaleY(m_Pic.Height, vbHimetric, vbPixels)
' render normal
m_Pic.Render Me.hDC, 0, 0, --Width, --Height, 0, m_Pic.Height, m_Pic.Width, -m_Pic.Height, ByVal 0&
' render mirrored
m_Pic.Render Me.hDC, --Width, 100, -Width, --Height, 0, m_Pic.Height, m_Pic.Width, -m_Pic.Height, ByVal 0&
End Sub
The -- trick fixes the mysterious Type Mismatch problem. It seems that you can't pass a variable as-is: constants & values received from objects seem to work OK.
It's not just that making the masks is so much hassle, I just don't like the idea of all my images becoming mask-ified in case I want to use them for something else. I'll look for the mask maker program nonethless though.
Thanks once again for the help guys, I'll let you know if I have any problems
EDIT: Problems :/
So I tried the Render method, and after a while figured it out (taking rather a long time to realise that AutoRedraw had to be set to False). However, I'm having a bit of trouble tweaking it to work with an array of Pets.
Am I correct in thinking that the Form_Paint event is what happens every time the LoadPicture command is used?
Here's my code.
Code:
For i = 1 to NumPets
'[Change X and Y co-ordinates a bit...]
Set PetPic(i) = LoadPicture(App.Path & "\Images\" & Pet(i).ID & ".gif")
Next i
(I'm using si's advice about a user-defined Type as well)
Code:
Private Sub Form_Paint()
Dim Width As Long, Height As Long
' Convert himetric to pixels
Width = Me.ScaleX(PetPic(CurrentPet).Width, vbHimetric, vbPixels)
Height = Me.ScaleY(PetPic(CurrentPet).Height, vbHimetric, vbPixels)
If (Pet(CurrentPet).FacingLeft = False) Then
' Render normal
PetPic(CurrentPet).Render Me.hdc, --Pet(CurrentPet).X, --Pet(CurrentPet).Y, --Width, --Height, 0, PetPic(CurrentPet).Height, PetPic(CurrentPet).Width, -PetPic(CurrentPet).Height, ByVal 0&
Else
' Render mirrored
PetPic(CurrentPet).Render Me.hdc, --Pet(CurrentPet).X, --Pet(CurrentPet).Y, -Width, --Height, 0, PetPic(CurrentPet).Height, PetPic(CurrentPet).Width, -PetPic(CurrentPet).Height, ByVal 0&
End If
End Sub
I've set "CurrentPet" to a variable, equal to "i" in the previous code, as it was the only way I knew of transferring the variable to Form_Paint.
The code works until I try and add a second Pet - at which point the first Pet disappears, leaving me with only the second one. If I add a third, the second disappears, and so on.
I've probably either missed something really obvious or totally misunderstood the Form_Paint event...
Ah, I see! So it was the Me.Refresh that was causing the Form_Paint event, meaning that it was being called outside of my loop, and therefore only being called once, for the last Pet that had been moved.
I've now put the loop inside the Form_Paint event and it works like a charm
(in fact better than I thought, since I thought I'd have to call LoadPicture every frame but really I only have to call it once per Pet, and it's stored to my "PetPic" StdPicture array)
Thanks a lot!
EDIT: It would appear that the .Render method has the same sort of effect as using transparent images - they occasionally "flicker" black for a split second... am I doing something wrong or is that something I'll just have to put up with?
The only downside with Form_Paint event & not using AutoRedraw is that if you don't create a background buffer using other means (such as API) then you are likely to get flickering. However, if you don't draw a lot and if the flickering isn't that bad then it isn't a problem
By the way, you could extend the Type so that it also includes the picture, rather than having a separate array for that.
If you do that and use a With block, the Form_Paint code above could be reduced to this:
Code:
Private Sub Form_Paint()
Dim Width As Long, Height As Long
With Pet(CurrentPet)
' Convert himetric to pixels
Width = Me.ScaleX(.Pic.Width, vbHimetric, vbPixels)
Height = Me.ScaleY(.Pic.Height, vbHimetric, vbPixels)
If (.FacingLeft = False) Then
' Render normal
.Pic.Render Me.hdc, --.X, --.Y, --Width, --Height, 0, .Pic.Height, .Pic.Width, -.Pic.Height, ByVal 0&
Else
' Render mirrored
.Pic.Render Me.hdc, --.X, --.Y, -Width, --Height, 0, .Pic.Height, .Pic.Width, -.Pic.Height, ByVal 0&
End If
End With
End Sub
It has 2 extra lines, but makes the existing ones shorter and easier to read, plus a bit faster too.
As always when it comes to With block, you should never, ever, use Exit Do, Exit Sub, or similar method that forces code execution outside the With block. This will cause internal problems and is a possible cause for very-hard-to-figure-out memory leaks.
Just a generic FYI, not specific to this case, just something to remember when using With blocks