|
-
May 21st, 2014, 03:40 PM
#1
Thread Starter
Lively Member
[RESOLVED] Screen Resizing
I'm trying to program a chess based game and use mouse x+y to work out which square is clicked on. This is fine while the screen is at the size I programed for but I would like to give players the option to resize or full screen the game. At that point my maths won't work anymore. How can I recalculate all the x,y positions of the squares depending on screen size?
-
May 21st, 2014, 09:32 PM
#2
Re: Screen Resizing
How are you going to resize the game?
If you just scale the game by some factor, then you just scale the mouse coordinates by the same factor so your coordinates stay the same.
-
May 21st, 2014, 10:09 PM
#3
Thread Starter
Lively Member
Re: Screen Resizing
My thought was to allow the player to drag the side/bottom of the game window, or hit maximize for full screen. Obviously the length to height ratio will have to remain constant.
What I need is some way to know the screens size and then an equation to convert the clicked location back to the X/Y square numbers. I thinking something like: (orig screen size 1024*768, board square size 65*65)
M=ScreenSizeX / 1024
Xsq=mouseX-M*(Xoffset of squares from left edge of form)
Xsq=Xsq/(65*M)
Giving a value of Xsq between 1 and 15
Questions:
A) Does that sort of formula work?
B) How do I get the value of ScreenSizeX?
-
May 22nd, 2014, 12:22 AM
#4
Re: Screen Resizing
I'm sure you'll work it out. It seems like you're on the right track.
I've probably done this a few times answering this on other forums.
I think you may have your values backwards, but it depends on how you use them. You have to use a factor for drawing and the reverse for mouse coordinate conversions.
I think that the easy way is to always draw the game in a fixed size bitmap. You then draw the bitmap to the screen scaling it up or down to fit the screen.
So, as an example, assume the game was drawn in a bitmap name mapCache. The size of mapCache would never change.
Assume also that we draw mapCache into a picturebox that is resized to fit the screen, and the map fills that picturebox.
The code I would have in the Picturebox1_Resize event would look like this.
Code:
Private Sub PictureBox1_Resize(ByVal sender As Object, ByVal e As System.EventArgs) Handles PictureBox1.Resize
'Resize will normally cause a paint event only if the picturebox is increasing in size and
'a clipping area will be set so the repainting only affects the "new" portion of the picturebox being exposed.
'Since we are redrawing the whole area, we need to Invalidate the picturebox so all of it is redrawn, otherwise we get a mess
PictureBox1.Invalidate() 'Redraw the whole picturebox area
xsf = mapCache.Width / PictureBox1.Width 'Recalculate the scale factors for mouse inputs (also inverse used to scale the drawing)
ysf = mapCache.Height / PictureBox1.Height
End Sub
Then, in the Picturebox paint event when I redraw the game from the mapCache, it would look like this.
Code:
Private Sub PictureBox1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles PictureBox1.Paint
e.Graphics.ScaleTransform(1 / xsf, 1 / ysf) 'xsf,ysf scales the mouse inputs to the drawing, the inverse scales the drawing to the picturebox
e.Graphics.DrawImage(mapCache, 0, 0) 'draw the "lower" map background first
If leftBtn Then
e.Graphics.DrawImage(drawOverlay, 0, 0) 'draw the overlay bitmap "on top"
Else
If tb IsNot Nothing Then
e.Graphics.FillRectangle(tb, PictureBox1.ClientRectangle)
End If
End If
End Sub
The example above drew a couple layers. The second layer was either an interactive drawing using the mouse, or that overlay was copied to a tb, and the tb was used for the overeay and could quickly be moved around.
The mouse coordinates were scaled to match the drawing as follows.
Code:
Private Sub PictureBox1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseMove
Static lloc As System.Drawing.Point 'Save the last mouse position
Dim nlloc, nloc As System.Drawing.Point 'Need two points to scale our X,Y locations to match drawing size
nlloc.X = lloc.X * xsf 'Multiply X values by the X scaleFactor
nlloc.Y = lloc.Y * ysf 'Multiply Y values by the Y scaleFactor
nloc.X = e.Location.X * xsf ' nlloc is the "new" (scaled) last location
nloc.Y = e.Location.Y * ysf ' nloc is the "scaled mouse location
'....
The coordinates in nloc would always map to the same values for the same point on the original map bitmap regardless of scaling.
I think your pseudo code is probably doing the same thing, just the values are reversed so you divide, where I multiply when converting the mouse coordinates.
In my case, since I was converting in the MouseMove event, you would rather multiply than divide because mutiplies are much faster operations than divides are.
Since mousemove events can occur up to 128 times per second, you want that conversion as efficient as possible.
If you just have to convert click positions, the math time difference won't matter. At 128 times per second, it might not really matter either, its just old programming habits on older slower machines that have ingrained trying to avoid divides when possible as a general rule.
-
May 22nd, 2014, 10:53 AM
#5
Thread Starter
Lively Member
Re: Screen Resizing
Thank you for that, I only have to deal with mouse click events so that makes life a bit easier.
I actually use the game board as the background image of the form, would I have to invalidate the form then redraw the background image when it was resized?
As the pieces walk around I redraw the whole form combining the walking animation frames, and moving the position as the bit moves. I vaguely remember (this was about 3 years ago) that using a picture box for the moving piece caused a right mess being left. Is this because I wasn't invalidating the picture box before moving to the next animation image?
I would guess just redrawing a small picture box would be a lot quicker for slower machines than redrawing the entire form background 17 times for eveery step taken.
-
May 22nd, 2014, 12:00 PM
#6
Re: Screen Resizing
I probably wouldn't modify what you're doing at the moment. The code I pulled my examples from had a single picturebox that filled the form, so isn't moving pictureboxes around as "players".
If you have the Background Image size mode set to Zoom, then it will automatically increase the background image (proportionally) to fit the size of the form when displayed, so you don't have to redraw the background image when it is resized, it is handled automatically. But you will have to determine what the scaling is and redraw your other things to the correct scale.
The background being automatically scaled proportionally is what may give you some issues, since it will be as large in one direction as possible, and centered in the other direction to maintain proportion. Having to account for those gaps will complicate the mouse coordinate conversion. You will have to hopefully be able to adjust the values using the same logic the form is using to resize and offset the background image. That always seems a little dangerous to me.
If, on the other hand, you allow the board to distort to fill the area (set to Stretch, not Zoom), then the mouse coordinates become trivial as I've already posted.
Of course, if you didn't use the Background image of the form, and just maintained the drawing in a bitmap and proportionally sized picturebox on the form, you could know that the background image filled the picturebox, so your mouse coordinates and image would both start at 0,0 in the upper left corner and could be computed reliably.
If you were moving just one piece at a time, then you could use invalidate, and a small rectangle area around the piece to limit the amount of data transferred from the bitmap to the screen when updating the picturebox, which should speed up the frame rate possible.
-
May 22nd, 2014, 12:30 PM
#7
Thread Starter
Lively Member
Re: Screen Resizing
 Originally Posted by passel
If you were moving just one piece at a time, then you could use invalidate, and a small rectangle area around the piece to limit the amount of data transferred from the bitmap to the screen when updating the picturebox, which should speed up the frame rate possible.
Only one piece moves at a time, how do I do this small rectangle thing? Sorry to be a bit thick but I'm self taught on VB (from a background of Basic on an Amstrad Cpc 30 years ago), things have moved on a bit in the meantime.
On the board resize, is it possible to force the ratio of length to height to be fixed?
-
May 22nd, 2014, 02:49 PM
#8
Re: Screen Resizing
Yes. There are so many ways to accomplish the same thing, it kind of depends on what you have and what you want to accomplish.
For an example of invalidating a small area of the form to be updated, I threw together this small bit of code.
This will simulate having a background image for the board, and another image for a piece.
If will also demonstrate resizing those images to keep proportions with changes in the form size.
Went ahead and added a lot of comments to the code so won't have to say too much here.
DoubleBuffer is set, but to use the backbuffer you have to use the Graphics object passed to you in the Paint Event. Doing your form drawing (or any control drawing) in the Paint event is how you should do drawing on controls. You may find many examples on the Internet where people use Me.CreateGraphics, or Picturebox1.CreateGraphics to get a graphics object and use it to draw. That is wrong. That will draw directly to the screen, bypassing the default doublebuffering that Pictureboxes have, and bypassing the backbuffer of other controls if you've enabled or set them up for double buffering. You always want to paint to controls in their paint events. You could of course draw into bitmaps at any time, and then draw the bitmap to the control in the paint event to manage your own levels of backbuffering or layering of images.
As is, the code will redraw the whole form whenever the mouse is moved due to the Invalidate() at the end of the MouseMove event.
Probably even though it is redrawing the whole form, it will still be fairly responsive.
If you click on the form, the form will be filled with the wash color (light.green), but as soon as you move the mouse, it will all go away because the full form's client area is being redrawn.
Now, if you go into the MouseMove event and comment out the invalidate at the end, you will see pretty much nothing happen as you move the mouse around the screen because nothing is telling the form to update itself.
If you uncomment the Invalidate after mloc.X and Y are updated, then the new position of the piece will be invalidated and you will see it drawn. But you see a trail of pieces of the piece because where it was before it moved was not updated.
So, uncomment the Invalidate before the mloc.X and Y are updated. This will invalidate the area around the piece where it was before it moved, so it will be "erased" because it is not drawn in that location when the screen is updated.
Click on the form to wash it again with the light green, and you can see what portions of the screen are being updated as you move the mouse.
If you comment out the Invalidate in the click event, then you will see that the wash will only fill the small invalidated area at the next paint event.
Lastly, if you comment out the invalidate in the form resize event, then increase the size of the form, you will see that the resize event tries to make the form update efficient by only invalidating the new portion of the form exposed around the bottom and right edges. Of course, if your whole form is being modified because of the resize, this doesn't work out so well.
Code:
Public Class Form1
Dim BGImage As New Bitmap(1024, 768) 'Create a bitmap to act as our example gameboard
Dim sf As Single 'Scale Factor used to maintain proportions and scaling of bitmaps
Dim PieceImage As New Bitmap(128, 128) 'Create a bitmap to act as a game piece.
Dim mloc As Point 'Where the mouse if moving, we'll draw a "piece"
Dim sqrSize As Integer = 64 'Make the background a checkboard pattern of 64x64 pixels squares
Dim sqrSize2 As Integer = sqrSize * 2 'Put two squares diagonally in a 128x128 location to do the checkerboard
Dim Wash As Boolean 'Flag Washing the form with a color so we can observe the areas updated
Private Sub Form1_Click(sender As Object, e As System.EventArgs) Handles Me.Click
Invalidate() 'Invalidate the whole form (will cause the paint event)
Wash = True 'And flag that we to "wash" the board with a semi-transparent color when we paint
End Sub
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
DoubleBuffered = True 'Set the form to double buffer to minimize flashing
Dim gGraphics As Graphics = Graphics.FromImage(BGImage) 'Get an object so we can draw on BGImage
For x = 0 To 1024 - sqrSize2 Step sqrSize2 'Draw the checkerboard pattern in the 1024x768 bitmap
For y = 0 To 768 - sqrSize2 Step sqrSize2
gGraphics.FillRectangle(Brushes.Brown, x, y, sqrSize, sqrSize)
gGraphics.FillRectangle(Brushes.Brown, x + sqrSize, y + sqrSize, sqrSize, sqrSize)
Next
Next
gGraphics.Dispose() 'Done drawing, release resources used to draw on BGImage
gGraphics = Graphics.FromImage(PieceImage) 'Get an object so we can draw on PieceImage
For i As Integer = 8 To 32 Step 4
gGraphics.DrawEllipse(Pens.Black, 32 - i, 32 - i, i * 2, i * 2) 'Draw some concentric Circles as our "piece".
Next
gGraphics.Dispose() 'Done drawing, release resources used.
End Sub
Private Sub Form1_MouseMove(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove
'Invalidate(New Rectangle(CInt(mloc.X * sf), CInt(mloc.Y * sf), CInt((sqrSize + 2) * sf), CInt((sqrSize + 2) * sf)))
mloc.X = CInt(e.X / sf)
mloc.Y = CInt(e.Y / sf)
'Invalidate(New Rectangle(CInt(mloc.X * sf), CInt(mloc.Y * sf), CInt((sqrSize + 2) * sf), CInt((sqrSize + 2) * sf)))
Invalidate() 'Comment out this, and uncomment the other two above to do partial area updates
End Sub
Private Sub Form1_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
'I'm calculating sf here, but it could be done in the Resize event so wouldn't have to be calculated every Paint Event
Dim sfx As Single = ClientSize.Width / 1024.0! ' Get the ratio of the width change compared to our fixed size
Dim sfy As Single = ClientSize.Height / 768.0! ' Get the ratio of the height change compared to our fixed size
sf = Math.Min(sfx, sfy) ' Whichever is smaller is the one we'll use to maintain proportion and fill as much as possible
e.Graphics.ScaleTransform(sf, sf) ' Scale our drawing up or down based on that scale factor
e.Graphics.DrawImage(BGImage, 0, 0) ' Draw the background at 0,0 (so drawing and mouse coordinates start at 0,0)
e.Graphics.DrawImage(PieceImage, mloc) ' Draw the game piece at the current mouse location (follows the mouse around)
If Wash Then ' If the Wash flag is set
Wash = False ' Make it a one-shot
e.Graphics.ResetTransform() ' Reset the scaling so we fill the form using the client Rectangle
Using fb As New SolidBrush(Color.FromArgb(128, Color.LightGreen)) ' Create a semi-transparent brush ("Using" is an alternative to "Dispose")
e.Graphics.FillRectangle(fb, ClientRectangle) ' Fill the form with that color
End Using ' Instead of Using .. End Using, we could do fb.Dispose here
End If
End Sub
Private Sub Form1_Resize(sender As Object, e As System.EventArgs) Handles Me.Resize
'Resize won't cause a full form paint. It will invalidate only the new parts of the form shown (if increasing size)
'Since we're changing the size of the image drawn on the "background", we need to force a full paint
Invalidate()
End Sub
End Class
Last edited by passel; May 22nd, 2014 at 05:38 PM.
-
May 22nd, 2014, 05:19 PM
#9
Thread Starter
Lively Member
Re: Screen Resizing
Thank you very much, very informative and even I could understand it. I shall head off to start on the new flexible size version 
If you want to have a play on the old fixed size version it sits in a rar file here:
http://www.mediafire.com/?r58c56tqj06aj9k
Its playable human v human on the same computer although the odd bug does spring up occasionally. Once I finish the resizable version I'll go for learning how to link machines over the net. Idea of the game is to kill your opponents Dragonlord (the dude sitting on the Dragon). Once you've played a game pieces become trainable via the main menu, Manage Good/Evil Team button.
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|