I decided to have a little go myself at making a map editor for a simple RPG game. The map will allow drawing tiles at 32x32 into the map, nothing too fancy, but to give an idea
Your approach is wrong. The delay you suffer is mainly because the timer.
Here are some guidances:
You should collect the mouse coordinates inside of the OnMouseMove event, otherwise you'll only get a new mouse position when the timer fires.
In addition to that, use GetMouseMovePointsEx
in order to get up to 64 mouse positions you missed.
Don't use a TTimer
to control your drawing. When the mouse moves around the PaintBox, set your flags as needed, and also keep track of the current mouse coordinates, and then call the PaintBox's Invalidate()
method to trigger a repaint when flow control returns back to the message queue. Whenever the PaintBox's OnPaint
event is triggered for any reason, draw your map and tiles as needed, and if a tile is being dragged around then draw it at the saved mouse coordinates.
Also, in your DrawTileOnMap()
method, you don't need to copy the image to a temp TBitmap
, you can copy from your source TImage
directly to your target TCanvas
.
Try something more like this:
const
FTileHeight = 32; // height of each tile
FTileWidth = 32; // width of each tile
FSnapX = 32; // size of the X Snap
FSnapY = 32; // size of the Y Snap
FMapHeight = 1280; // height of the map
FMapWidth = 1280; // width of the map
var
FTilesetPos: TPoint; // tile position in tileset
FMapTilePos: TPoint; // tile position in map
FMapTileColumn: Integer;
FMapTileRow: Integer;
FIsDrawing: Boolean; // flag to determine if drawing tile on map.
procedure DrawTileOnMap(const Tileset: TImage; TileX, TileY: Integer;
MapX, MapY: Integer; OutCanvas: TCanvas);
begin
OutCanvas.CopyRect(
Rect(MapX, MapY, MapX + FTileWidth, MapY + FTileHeight),
Tileset.Canvas,
Rect(TileX, TileY, TileX + FTileWidth, TileY + FTileHeight));
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
FTilesetPos := Point(-1, -1);
FMapTilePos := Point(-1, -1);
FMapTileColumn = -1;
FMapTileRow := -1;
FIsDrawing := False;
end;
procedure TMainForm.MapEditorMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
if Button = mbMiddle then Exit;
if Button = mbLeft then
FIsDrawing := True
end else
DeleteTileAtPosition(FMapTilePos.X, FMapTilePos.Y, lvwRecords);
MapEditor.Invalidate;
end;
procedure TMainForm.MapEditorMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
ConvertToSnapPosition(X, Y, FSnapX, FSnapY, FMapTilePos);
FMapTileColumn := MapTilePositionToColumn(FMapTilePos.X);
FMapTileRow := MapTilePositionToRow(FMapTilePos.Y);
if (Button = mbLeft) and FDrawing then
MapEditor.Invalidate;
end;
procedure TMainForm.MapEditorMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
if Button = mbLeft then
begin
FIsDrawing := False
MapEditor.Invalidate;
end;
end;
procedure TMainForm.MapEditorPaint(Sender: TObject);
var
I, J: Integer;
TileX, TileY: Integer;
MapX, MapY: Integer;
begin
// draw empty/water tiles << NEEDS OPTIMIZATION AS VERY SLOW >>
{for I := 0 to GetMapTilesColumnCount(FMapWidth) do
begin
for J := 0 to GetMapTilesRowCount(FMapHeight) do
begin
DrawTileOnMap(Image1, 0, 0, I * FTileWidth, J * FTileHeight, MapEditor.Canvas);
end;
end;}
// draw tiles
with lvwRecords do
begin
for I := 0 to Items.Count -1 do
begin
MapX := StrToInt(Items[I].Caption);
MapY := StrToInt(Items[I].SubItems[0]);
TileX := StrToInt(Items[I].SubItems[1]);
TileY := StrToInt(Items[I].SubItems[2]);
DrawTileOnMap(imgTileset, TileX, TileY, MapX, MapY, MapEditor.Canvas);
end;
end;
PaintGrid(MapEditor.Canvas, FMapWidth, FMapHeight, 32, 1, $00543B1B);
if (FMapTileColumn > -1) and (FMapTileRow > -1) and FDrawing then
DoDrawTile(FMapTilePos.X, FMapTilePos.Y);
end;