Here's a kludged fix for it. There's probably a more accurate way to do this though. With this method, sometimes 2 pixels are represented as 1 due to the scaling. I'm not sure if there's a way around that though.
DeleteDir ".bmx",2 Graphics 1024,768,32 Local ProjMatrix:ProjectionMatrix = New ProjectionMatrix ProjMatrix.SetScale(800,600) ProjMatrix.SetLetterBox(800,600) SetOrigin ProjMatrix.CenterX()-ProjMatrix.Width()/2, ProjMatrix.CenterY()-ProjMatrix.Height()/2 Global VirtualMouseX:Int, VirtualMouseY:Int Local OriginX:Int = ProjMatrix.CenterX() - ProjMatrix.Width()/2 Local OriginY:Int = ProjMatrix.CenterY() - ProjMatrix.Height()/2 Local ProjX# = ProjMatrix.Width() / GraphicsWidth() Local ProjY# = ProjMatrix.Height() / GraphicsHeight() While KeyDown(KEY_ESCAPE)=False And AppTerminate()=False Cls VirtualMouseX = (MouseX() - OriginX) * ProjX VirtualMouseY = (MouseY() - OriginY) * ProjY DrawText VirtualMouseX,10,10 DrawText VirtualMouseY,10,25 ProjMatrix.DrawLetterBox() Flip 1 Wend EndGraphics End ' --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ' This type allows you to make games that can run at any resolution. ' --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Type ProjectionMatrix Global _Width% ' The size of the screen, in BlitzMax coordinates. Global _Height% ' Normally BlitzMax coordinates correspond 1:1 with pixels on the screen, but when you adjust the projection matrix, that relationship changes. Global _VirtualWidth% ' The size of the visible region in which gameplay takes place. The area inside the letterbox. Global _VirtualHeight% ' ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ' This function sets the scale of the projection matrix. ' ' If you simply wish your game to stretch vertically and horizontally to match the current resolution, and fill the screen, call this function with your desired "virtual" resolution. ' Ie, if you want to build your game around an 800x600 resolution, then set Width and Height to 800,600. If the game is then run at 1920x1200, it will be squashed vertically. ' ' If you want letterboxing however, call InitLetterbox() and then DrawLetterBox() every frame just before you flip. ' ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Function SetScale(Width%, Height%) _Width = Width _Height = Height _VirtualWidth = Width _VirtualHeight = Height ?Win32 Local D3D7Driver:TD3D7Max2DDriver = TD3D7Max2DDriver(_max2dDriver) If D3D7Driver Local Matrix#[] = [2.0/Width, 0.0, 0.0, 0.0,.. 0.0, -2.0/Height, 0.0, 0.0,.. 0.0, 0.0, 1.0, 0.0,.. -1-(1.0/Width), 1+(1.0/Height), 1.0, 1.0] D3D7Driver.device.SetTransform(D3DTS_PROJECTION, Matrix) Else ? ' If on platform other than Win32, or using OpenGL run this code. glMatrixMode(GL_PROJECTION) glLoadIdentity() glortho(0, Width, Height, 0, 0, 1) glMatrixMode(GL_MODELVIEW) glLoadIdentity() ?Win32 EndIf ? End Function ' ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ' This function initializes the letterbox. ' ' Call it with the virtual resolution you want to use for your game after you have set the graphics mode, and then DrawLetterBox() every frame just before you flip. ' ' These functions will automatically handle both letterboxing on screens which are too tall, and pillarboxing on screens which are too wide. So if you design your game for 800x600, ' then InitLetterbox() will squash things horizontally to maintain the proper aspect ratio, and DrawLetterbox() will add black bars to the sides of the display for you. ' ' ' ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Function SetLetterbox(Width%, Height%) Local NewWidth#, NewHeight# NewWidth# = Width NewHeight# = Float(GraphicsHeight()) / (Float(GraphicsWidth()) / Float(Width)) ' If screen is wider than the desired apsect ratio... If NewHeight# < Height ' Use pillarboxing instead of letterboxing. NewHeight# = Height NewWidth# = Float(GraphicsWidth()) / (Float(GraphicsHeight())/Float(Height)) EndIf ' Adjust the scale of the projection matrix to achieve the desired result. ProjectionMatrix.SetScale(NewWidth#, NewHeight#) ' Store the size of the visible game region. _VirtualWidth = Width _VirtualHeight = Height End Function ' ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ' This function draws black bars over the portions of the screen which are outside the gameplay area. ' ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Function DrawLetterbox() Local Size% RenderState.Push() If _VirtualHeight < _Height ' Draw Letterbox. Size = (_Height-_VirtualHeight) / 2 SetColor(0,0,0) DrawRect(0, 0, _Width, Size) DrawRect(0, _Height-Size, _Width, Size) Else ' Draw pillarbox. Size = (_Width-_VirtualWidth) / 2 SetColor(0,0,0) DrawRect( 0, 0, Size, _Height) DrawRect(_Width-Size, 0, Size, _Height) EndIf RenderState.Pop() ' Render push and pop simply resets the color and other display properties to whatever they were beforehand. These two functions are in the code archives. End Function ' ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ' These functions return the virtual width and height of the usuable region of the screen, inside any letterboxing. ' ' Basically, these functions are equivalent to GraphicsWidth() and GraphicsHeight() when using a projection matrix where coordinates don't match up 1:1 to pixels ' and/or letterboxing is being used. ' ' These functions will not return useful values until either ProjectionMatrix.SetScale() is called, or ProjectonMatrix.SetLetterbox() is called! ' ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Function Width#() Return _VirtualWidth End Function Function Height#() Return _VirtualHeight End Function ' ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ' These functions tell you where the center of the screen is when using letterboxing. ' ' Note that they do not calculate this using the width and height of the visible area of the screen, but rather, the size of the whole screen including the letterboxed regions. ' This is important, because if you draw objects using the size of the visible region only, they will be higher on the screen than they should be, or more to the left. ' ' In my games, what I do is create a pivot called Origin, and place it in the center of the screen, and attach all my sprites to that, so the only time I need to worry about where ' the center of the screen really is is when I position that pivot initially. ' ' Then when I've done that, the top of the screen is at -ProjectionMatrix.Height#()/2, half the height I passed to SetLetterbox(), and so on. ' ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Function CenterX#() Return _Width/2 End Function Function CenterY#() Return _Height/2 End Function End Type Type RenderState Global RenderStateList:TList = CreateList() Field Alpha# Field Blend Field ClsColor_R, ClsColor_G, ClsColor_B Field Color_R, Color_G, Color_B Field Handle_X#, Handle_Y# Field ImageFont:TImageFont Field LineWidth# Field MaskColor_R, MaskColor_G, MaskColor_B Field Origin_X#, Origin_Y# Field Rotation# Field Scale_X#, Scale_Y# Field Viewport_X, Viewport_Y, Viewport_Width, Viewport_Height ' ------------------------------------------------------------------------------------------------------------------------------------------------------- ' These methods allow you to save and restore the current render settings ' ' Each time you call the push method, the current state is placed on the stack. ' Each time you call the pop method, the last state placed on the stack is restored and removed from the stack. ' ------------------------------------------------------------------------------------------------------------------------------------------------------- Function Push() Local RS:RenderState = New RenderState RS.Alpha# = GetAlpha#() RS.Blend = GetBlend() GetClsColor(RS.ClsColor_R, RS.ClsColor_G, RS.ClsColor_B) GetColor(RS.Color_R, RS.Color_G, RS.Color_B) GetHandle(RS.Handle_X#, RS.Handle_Y#) RS.ImageFont = GetImageFont() RS.LineWidth# = GetLineWidth#() GetMaskColor(RS.MaskColor_R, RS.MaskColor_G, RS.MaskColor_B) GetOrigin(RS.Origin_X#, RS.Origin_Y#) ; SetOrigin 0,0 RS.Rotation# = GetRotation#() GetScale(RS.Scale_X#, RS.Scale_Y#) GetViewport(RS.Viewport_X, RS.Viewport_Y, RS.Viewport_Width, RS.Viewport_Height) RenderStateList.AddLast(RS) End Function Function Pop() Local RS:RenderState = RenderState(RenderStateList.RemoveLast()) SetAlpha(RS.Alpha#) SetBlend(RS.Blend) SetClsColor(RS.ClsColor_R, RS.ClsColor_G, RS.ClsColor_B) SetColor(RS.Color_R, RS.Color_G, RS.Color_B) SetHandle(RS.Handle_X#, RS.Handle_Y#) SetImageFont(RS.ImageFont) SetLineWidth(RS.LineWidth#) SetMaskColor(RS.MaskColor_R, RS.MaskColor_G, RS.MaskColor_B) SetOrigin(RS.Origin_X#, RS.Origin_Y#) SetRotation(RS.Rotation#) SetScale(RS.Scale_X#, RS.Scale_Y#) SetViewport(RS.Viewport_X, RS.Viewport_Y, RS.Viewport_Width, RS.Viewport_Height) End Function End Type