Game Map - Best Approach

Started by bulka_tarta, Sun 27/08/2017 17:12:34

Previous topic - Next topic

bulka_tarta

Hey,

I've been recently playing around with a map screen for my game.
The main issue is that the desired map is much bigger then original game size (Game Resolution: 384x216, Map: 975x618) and because of that I struggle to decide on the best approach to set the whole thing up.

Brief idea behind the map's functionality is that you should be able to pan it around. The character does not directly move around the map, but rather it has various locations on top, which you can click to access info about it. A label of some sort would be in place to show the location's name.


I looked around on forums, and it seems that there are three possible approaches, but all with some disadvantages. I wonder if I'm missing anything, or if I just need to stick to one of them and accept certain quirks.

Map as a GUI - seems like the ideal approach for my needs. I can add locations as buttons. I can add lists for location names. Clicking on button can trigger related the stuff. The issue? As you may already figured it out, a GUI cannot be bigger than the game's size. Using this approach gives me a warning when playing the game, but other than that everything works as intended. I guess just ignoring the warning is not acceptable?

Map as a separate Room - at glance, this could be a good alternative for the GUI map. I can pan around with SetViewport(), locations could be in a form of hotspots/objects - I'm not entirely sure about the labels though. The main issue here seems to be more of a visual one - having the map in a separate room means that I will need to have the fade in/out effect when accessing the map. The way all of the GUI in the game is meant to work is that it has this "flow". When you switch between various menus, they slide from one to another, and having this one menu to have to fade in/out is just very out of place.

DynamicSprite Approach - from what I found on the forums, it seems to be a good idea to create a DynamicSprite for the map. From what I can understand you create a DynamicSprite, then create a copy and crop it. I imagine that you can later pan it around achieving the desired effect. However, it seems to me that adding locations or UI info (e.g. label with name) to the map would be a huge hassle. I guess you could create another DynamicSprites for each location on top of the map one, but to declare x,y positions for each location seems like a nightmare, where I can't easily see how the location presents itself on the map.



As already mentioned above, GUI approach would be my favourite, where I can easily move the buildings (buttons) on the map to make sure they look just fine. I can easily add labels, and trigger anything I may need when the building is clicked. I was wondering if there would be some kind of way to perhaps crop the GUI so it does't give me the warning message? So basically the GUI is larger than screen in the editor, but when you start the game the GUI size is cropped down and you can pan around when dragging.

Are there any other ways to deal with the maps in AGS that I missed? Perhaps there's something I missed about the any of the above approaches?
Any suggestions or pointers in the right directions are welcome!

Khris

You can combine (1) and (3) by setting the DynamicSprite as GUI background. That way you can slide it in like your other GUIs but still pan around.

You can still use buttons for the locations, you just need to move or hide them, depending on where the current map offset is. They wouldn't even have to pop in and out as the player scrolls across the map; buttons can be resized dynamically, and you can also use cropped DynamicSprites for the button images.

As for (2), the fading can be disabled (once) using SetNextScreenTransition(eTransitionInstant); you'd simply call that right before switching to the map room, and again before switching back.

One approach I like is to map the screen to the entire map, as in, the mouse coordinates get converted into offset coordinates so that moving the mouse to the top left of the screen will move the map so its top left corner is visible; and moving the mouse to the bottom right of the screen will move the map so its bottom right is visible.

Scorpiorus

Also, with a map-GUI you don't lose the current room context, so to say.

For example, if the player interacts with something in the current room, you can just have your map-GUI reflecting it by simply enabling a corresponding GUI control element (like a button or a label) immediately. With a separate room for the map, you cannot update it instantly and you have to set up some global flags or something to apply them afterwards when the map room is actually loaded.

On the other hand, a room is a good choice if you want your player character to actually walk around the map, respecting walkable areas, interacting with other characters and objects there and so on. You cannot use AGS pathfinder/walkables with the GUI-as-map approach.

Another good thing with rooms -- for games with a higher screen resolution (say, 640x480 and greater), you can have a smooth map-room scrolling by using an invisible current player character moving in the direction, you want your map viewport to scroll in.

Slasher

QuoteAnother good thing with rooms -- for games with a higher screen resolution (say, 640x480 and greater), you can have a smooth map-room scrolling by using an invisible current player character moving in the direction, you want your map viewport to scroll in.
I used this method for "Splinter"...

Monsieur OUXX

the "map as separate room" technique is the one that requires the least coding and thinking. Locations can be hotspots (with a Region to trigger a small character walking on the map exiting the map if needed). And you can disable the fade In/fade out by changing this general setting in your code just before calling the map.


+1 for smooth scrolling by combining invisible character+smooth scrolling module.
 

Snarky

Quote from: Khris on Sun 27/08/2017 18:42:34
You can combine (1) and (3) by setting the DynamicSprite as GUI background. That way you can slide it in like your other GUIs but still pan around.

You can still use buttons for the locations, you just need to move or hide them, depending on where the current map offset is. They wouldn't even have to pop in and out as the player scrolls across the map; buttons can be resized dynamically, and you can also use cropped DynamicSprites for the button images.

Knowing a bit about your game, this would be my recommendation. And in AGS 3.4 you have unlimited GUI controls per GUI, so you can just have a fixed button for each location on the map (rather than having max 30 buttons that you have to constantly reassign to whichever locations are currently on-screen), which makes it a whole lot easier. The trickiest bit might be labels that are partially off-screen: I don't know off the top of my head how AGS deals with that. (I think having them spill over to the right or on the bottom is fine, but positioning them so that they're off on top or on the left might be harder.)

Using a dynamic sprite on a GUI, you could even zoom the map (e.g. with the scroll wheel).

bulka_tarta

Thank you all for the replies.

I was worried that DynamicSprite + GUI would be the best solution for what I need, because DynamicSprites are... terrifying.

It seems that even something as simple as panning is fairly tricky to do, as DynamicSprites don't have .X/.Y property (unless I'm wrong).
Originally I thought I could use this piece of code and modify it to work with DynamicSprites.

Closest I got to panning the map is this:
Code: ags

DynamicSprite* mapSprite;
DynamicSprite* sprite;
DrawingSurface* surface;

void SetMap()
{  
  mapSprite = DynamicSprite.Create(384, 216);
  
  gMapDyna.BackgroundGraphic = mapSprite.Graphic;

  sprite = DynamicSprite.CreateFromExistingSprite(80);
  sprite.Crop(0, 0, 384, 216);

  surface = mapSprite.GetDrawingSurface();
  surface.DrawImage(0, 0, sprite.Graphic);
  surface.Release();
}

bool map_is_dragged;
int xa, ya;

//Drag the map 
void on_event (EventType event, int data) {
  if (event == eEventGUIMouseDown && mouse.IsButtonDown(eMouseLeft)) {
      map_is_dragged = true;
    }
  }
 
void repeatedly_execute() {
  if (map_is_dragged) {
    if (!mouse.IsButtonDown(eMouseLeft)) map_is_dragged = false;
    else {
      xa = mouse.x;
      ya = mouse.y;
      
      sprite = DynamicSprite.CreateFromExistingSprite(80);
      sprite.Crop(xa, ya, 384, 216);
      
      surface = mapSprite.GetDrawingSurface();
      surface.DrawImage(0, 0, sprite.Graphic);
      surface.Release();
    }
  }
}

Which as you may guess, does not provide desired results.

What happens is that the map follows the mouse in a weird fashion. If I understand correctly, to "move" a DynamicSprite you need to delete it and draw again at a new position. The code above doesn't actually delete anything, because calling sprite.Delete(); does nothing and mapSprite.Delete(); simply gets rid of the map when clicked.

Currently I'm wondering if I need to somehow calculate the map's position using the DynamicSprite.Width/Height properties... But I can't see how that would help. Is there anyway to simply detect DynamicSprite's X and Y values? Surely there isn't anything about it in the manual.

Snarky

#7
Quote from: bulka_tarta on Tue 29/08/2017 21:49:35
It seems that even something as simple as panning is fairly tricky to do, as DynamicSprites don't have .X/.Y property (unless I'm wrong).

Sprites don't have x/y properties because you can draw them anywhere on screen. Or, to put it another way, their x/y properties will be the same as the GUI they're being rendered on.

There are two challenges here: 1. Figure out how to get mouse-dragging to work. 2. Render the right section of the map to the GUI background using dynamic sprites.

For the first part, I would do this:

-When user presses button:
-a. Store the map offset (the map coordinates of the top-left corner of the GUI)
-b. Store the mouse coordinates (simply mouse.x, mouse.y)

-While button is pressed, in repeatedly_execute()
-a. Calculate the difference between the current mouse coordinates and the stored mouse coordinates.
-b. If the difference is not zero:
--i. add/subtract that difference to/from the stored map offset â€" but with opposite sign (if the mouse moves down-right, the top-left corner of the GUI moves up-left on the map)
--ii. Update the stored mouse coordinates
--iii. Re-render the map

I'll leave the second part, the rendering, for now.

bulka_tarta

Quote from: Snarky on Tue 29/08/2017 22:18:36
Sprites don't have x/y properties because you can draw them anywhere on screen. Or, to put it another way, their x/y properties will be the same as the GUI they're being rendered on.

I guess that makes sense, but the reason I tried to figure out the sprite's position is that it seems the GUI's position is useless, since I don't want to drag the GUI, but rather the crop of the map. The GUI should always stay at 0,0 but the crop needs to change and if the GUI is at 0,0 how can you determine the cropped area?

Regarding the "Figure out how to get mouse-dragging to work":
I combined the code from dragging the GUI with Snarky's directions. You can now drag the map, but it's not ideal.

1) It leaves a trail of the map behind (basically the sprites duplicates so many times it creates a pattern)
2) The map jumps back to original position when you unclick and click again (not exactly original position, I guess it's based on the place where you click the second time).


Here's the code:
Code: ags

bool map_is_dragged;
GUI*dragged_map;
int ox, oy; // map offest x/y
int sx, sy; // stored mouse x/y
int dx, dy; // difference x/y
int currentX, currentY;

//When user presses button
void on_event (EventType event, int data) {
  if (event == eEventGUIMouseDown && mouse.IsButtonDown(eMouseLeft)) {
    GUIControl* gc = GUIControl.GetAtScreenXY(mouse.x, mouse.y);
    GUI* g = GUI.GetAtScreenXY(mouse.x, mouse.y);
    if (g == gMapDyna) {
      //Store the map offset
      ox = g.X;
      oy = g.Y;
      
      //Store the mouse coordinates
      sx = mouse.x;
      sy = mouse.y;
      
      map_is_dragged = true;
    }
  }
}
 
//While button is pressed
void repeatedly_execute() {
  if (map_is_dragged) {
    if (!mouse.IsButtonDown(eMouseLeft)) map_is_dragged = false;
    else {
      //Calculate difference between current and stored mouse coordinates
      dx = mouse.x - sx;
      dy = mouse.y - sy;
      
      //If difference is not zero
      if ((dx != 0)&&(dy != 0))
      {
        //Add/subtract difference from the stored map offset
        ox =  ox - dx;
        oy =  oy - dy;
        
        //Update the stored mouse coordinates
        sx = mouse.x;
        sy = mouse.y;
      }
      
      //Re-render the map
      sprite = DynamicSprite.CreateFromExistingSprite(80);
      sprite.Crop(ox, oy, 384, 216);
      
      surface = mapSprite.GetDrawingSurface();
      surface.DrawImage(0, 0, sprite.Graphic);
      surface.Release();
      sprite.Delete();
    }
  }
}



Btw, I know the names of the variables are not so great, I realise that's something I need to work on!

Scorpiorus

I think with dynamic sprites approach you have to redraw the whole composition from scratch each time the map is moved or animated.
You can probably prepare a huge map sprite and show a portion of it through a map viewport by repeatedly creating an instant cropped map-viewport-area-sized sprite from the map sprite, but it won't allow animating things on the map then.

However, wouldn't simply moving/translating all GUI controls (buttons) around give you a similar effect?

Basic code in the GlobalScript:
Code: AGS

int mouse_px = -1; // previous mouse x
int mouse_py = -1; // previous mouse y

void repeatedly_execute( )
{
    if (mouse.IsButtonDown(eMouseLeft) && (mouse_px!=-1) && (mouse_py!=-1))
    {
        int dx = mouse.x - mouse_px;
        int dy = mouse.y - mouse_py;
        for (int i=0; i < gMapDyna.ControlCount; i++)
        {
            gMapDyna.Controls[i].X += dx;
            gMapDyna.Controls[i].Y += dy;
        }
    }
    
    mouse_px = mouse.x;
    mouse_py = mouse.y;
}


The good thing with buttons is that they are already interact-able.
Also, try using a large button as your map background.

Snarky

Quote from: Scorpiorus on Thu 31/08/2017 22:14:59
However, wouldn't simply moving/translating all GUI controls (buttons) around give you a similar effect?

No? This will only move the buttons, not the background. Of course you have to do that too, but that's the simple part Once the rest is working. And what happens with buttons that are outside the GUI area? Particularly if the GUI doesn't cover the entire screen? I seem to remember that AGS is pretty sloppy about it, so that they may be drawn outside of the GUI bounds.

Quote from: Scorpiorus on Thu 31/08/2017 22:14:59
Also, try using a large button as your map background.

How does that help?



Crimson Wizard

#13
Quote from: Snarky on Fri 01/09/2017 06:41:36And what happens with buttons that are outside the GUI area? Particularly if the GUI doesn't cover the entire screen? I seem to remember that AGS is pretty sloppy about it, so that they may be drawn outside of the GUI bounds.

GUIs are rendered as a single ("flattened") bitmap, of the size of main gui, which technically prevents anything that is positioned outside of gui's bounds to be shown.

(This was probably easiest method at times of software-only renderer, because it is counter-productive in regards to hardware acceleration)

Snarky

Quote from: Scorpiorus on Fri 01/09/2017 07:03:45
https://files.fm/u/ja3s39yu

You're right, using a button as the background works. I didn't realize that buttons are allowed to be bigger than the GUI they're on, and that they get correctly cropped to the GUI dimensions (both graphically and in terms of interactivity).

Scorpiorus

#15
Yeah, surprisingly it works pretty well, even if implemented with basic scripting.
And it's a good starting point to add extra functionality like, for instance, a map scalling/zooming feature, as you suggest.

The GUI editing may not always be convenient, depending on the desktop resolution, since we don't have scrollbars in the GUI editor. But overall, it's quite manageable, I think.
And the "background" button can always be Locked, so that we don't accidentally move it, adjusting other buttons/labels, etc...

It may actually be a good candidate for a script-module/template/exported-GUI thing.
I see if I can pull out a script module or something out of that quick mock-up I made...


EDIT:
Some time ago I worked on a script equivalent to the snow/rain plugin to assist portability and it was one of the main problems in terms of how render snow flakes at different screen depth levels. Probably, GUI controls may work well as a complementary solution to other standard methods. It needs further testing, though...

bulka_tarta

#16
Setting up the map as a button seems to work pretty well!

I started playing around with it, and I have managed to make the map stop at the edges of the screen (the map in my game is fullscreen, rather than a section of it).

Code: ags

int mouse_px = -1; // previous mouse x
int mouse_py = -1; // previous mouse y
 
void repeatedly_execute( )
{
  if (mouse.IsButtonDown(eMouseLeft) && (mouse_px!=-1) && (mouse_py!=-1))   
  {
    int dx = mouse.x - mouse_px;
    int dy = mouse.y - mouse_py;
      
    for (int i=0; i < gMap.ControlCount; i++)
    {
      gMap.Controls[i].X += dx;
      gMap.Controls[i].Y += dy;
    }
      
    // Check X
    if (gMap.Controls[0].X > gMap.Width - System.ViewportWidth) gMap.Controls[0].X = System.ViewportWidth - gMap.Width; //gMap.Controls[0] is the button that has the map background
    if (gMap.Controls[0].X < (gMap.X - (gMap.Controls[0].Width - System.ViewportWidth))) gMap.Controls[0].X = gMap.X - (gMap.Controls[0].Width - System.ViewportWidth);
      
    // Check Y
    if (gMap.Controls[0].Y > gMap.Height - System.ViewportHeight) gMap.Controls[0].Y = System.ViewportHeight - gMap.Height;
    if (gMap.Controls[0].Y < (gMap.Y - (gMap.Controls[0].Height - System.ViewportHeight))) gMap.Controls[0].Y = gMap.Y - (gMap.Controls[0].Height - System.ViewportHeight);
  }
  
  mouse_px = mouse.x;
  mouse_py = mouse.y;
}


The problem I'm encountering now is that the other buttons (so buildings) keep sliding after the map background reaches any of the edge.
I tried setting up some kind of bool to stop it from scrolling when you meet the edge, or storing last positions for the buildings, but I can't get it to work properly.
Any idea how to stop the buttons from moving when the map reaches the edge?


EDIT: Wait, hang on, don't reply with solution! I think I got this!

EDIT2: Ok, it's getting somewhere. I now started storing the position of each button, which later is applied. The problem is that if you drag the map real fast, the buildings move by couple of pixel. If I understand correctly, the game doesn't call all the script fast enough, so I imagine I would need to speed up the game (never did this thought, so I'm not sure if that would help here at all?)

Code: ags

int mouse_px = -1; // previous mouse x
int mouse_py = -1; // previous mouse y
int btnX[10];
int btnY[10];
 
void repeatedly_execute( )
{
    if (mouse.IsButtonDown(eMouseLeft) && (mouse_px!=-1) && (mouse_py!=-1))
    {
      // Store button locations
      for (int i=1; i < gMap.ControlCount; i++)
      {
        btnX[i] = gMap.Controls[i].X;
        btnY[i] = gMap.Controls[i].Y;
      }
      
      int dx = mouse.x - mouse_px;
      int dy = mouse.y - mouse_py;
      
      for (int i=0; i < gMap.ControlCount; i++)
      {
        gMap.Controls[i].X += dx;
        gMap.Controls[i].Y += dy;
      }


      // Check X
      if (gMap.Controls[0].X > gMap.Width - System.ViewportWidth) 
      {
        gMap.Controls[0].X = System.ViewportWidth - gMap.Width;
        
        for (int i=1; i < gMap.ControlCount; i++)
        {
          gMap.Controls[i].X = btnX[i];
        }
      }
      if (gMap.Controls[0].X < (gMap.X - (gMap.Controls[0].Width - System.ViewportWidth))) 
      {
        gMap.Controls[0].X = gMap.X - (gMap.Controls[0].Width - System.ViewportWidth);
        
        for (int i=1; i < gMap.ControlCount; i++)
        {
          gMap.Controls[i].X = btnX[i];
        }
      }
      
      
      // Check Y
      if (gMap.Controls[0].Y > gMap.Height - System.ViewportHeight) 
      {
        gMap.Controls[0].Y = System.ViewportHeight - gMap.Height;
        for (int i=1; i < gMap.ControlCount; i++)
        {
          gMap.Controls[i].Y = btnY[i];
        }
      }
      
      if (gMap.Controls[0].Y < (gMap.Y - (gMap.Controls[0].Height - System.ViewportHeight))) 
      {
        gMap.Controls[0].Y = gMap.Y - (gMap.Controls[0].Height - System.ViewportHeight);
        for (int i=1; i < gMap.ControlCount; i++)
        {
          gMap.Controls[i].Y = btnY[i];
        }
      }
    }
 
    mouse_px = mouse.x;
    mouse_py = mouse.y;
}

Snarky

The answer is to check whether you're off the edge before moving any of the controls, and change dx and dy so that you stay inside.

Scorpiorus

#18
And here is an example of the script code to help you managing your map:

ScriptModule Header:
Code: AGS

#define Map_GUI              gMap     // Map GUI
#define Map_BKG_BUTTON_ID    0        // ID of the "background" button of the Map GUI

#define Map_MARGIN_TOP       (-10)    // top margin
#define Map_MARGIN_BOTTOM    (-10)    // bottom margin
#define Map_MARGIN_LEFT      (-10)    // left margin
#define Map_MARGIN_RIGHT     (-10)    // right margin

struct Map
{
    import static int GetWidth();
    import static int GetHeight();    
    import static int GetViewportX();
    import static int GetViewportY();
    import static int GetViewportWidth();
    import static int GetViewportHeight();
    import static void SetViewport(int newX, int newY);
};


ScriptModule Script:
Code: AGS

static int Map::GetWidth()
{
    return Map_GUI.Controls[Map_BKG_BUTTON_ID].Width;
}

static int Map::GetHeight()
{
    return Map_GUI.Controls[Map_BKG_BUTTON_ID].Height;
}

static int Map::GetViewportX()
{
    return -Map_GUI.Controls[Map_BKG_BUTTON_ID].X;
}

static int Map::GetViewportY()
{
    return -Map_GUI.Controls[Map_BKG_BUTTON_ID].Y;
}

static int Map::GetViewportWidth()
{
    return Map_GUI.Width;
}

static int Map::GetViewportHeight()
{
    return Map_GUI.Height;
}

static void Map::SetViewport(int newX, int newY)
{
    // just a shortcut for Map_BKG_BUTTON_ID button
    readonly GUIControl *BKG = Map_GUI.Controls[Map_BKG_BUTTON_ID];
    
    // remembering current viewport position
    int x0 = Map.GetViewportX();
    int y0 = Map.GetViewportY();
    
    // translating viewport / "background" button
    BKG.X = -newX;
    BKG.Y = -newY;
    
    // calculating viewport position constraints
    readonly int MIN_X = Map_MARGIN_LEFT;
    readonly int MIN_Y = Map_MARGIN_TOP;
    readonly int MAX_X = Map.GetWidth() - Map.GetViewportWidth() - Map_MARGIN_RIGHT;
    readonly int MAX_Y = Map.GetHeight() - Map.GetViewportHeight() - Map_MARGIN_BOTTOM;

    // applying viewport position constraints
    if (Map.GetViewportX() < MIN_X) // same as if (BKG.X > -MIN_X)
        BKG.X = -MIN_X;
    if (Map.GetViewportX() > MAX_X) // same as if (BKG.X < -MAX_X)
        BKG.X = -MAX_X;
    if (Map.GetViewportY() < MIN_Y) // same as if (BKG.Y > -MIN_Y)
        BKG.Y = -MIN_Y;
    if (Map.GetViewportY() > MAX_Y) // same as if (BKG.Y < -MAX_Y)
        BKG.Y = -MAX_Y;
    
    // calculating deltas for other GUI controls
    int dx = x0 - Map.GetViewportX();
    int dy = y0 - Map.GetViewportY();
    
    // translating other GUI controls (ie. all except the "background" button)
    for (int i=0; i < Map_GUI.ControlCount; i++)
    {
        if (i==Map_BKG_BUTTON_ID)
            continue;
        Map_GUI.Controls[i].X += dx;
        Map_GUI.Controls[i].Y += dy;
    }
}


GlobalScript:
Code: AGS

int mouse_px = -1; // previous mouse x
int mouse_py = -1; // previous mouse y

function repeatedly_execute( )
{
    if (mouse.IsButtonDown(eMouseLeft) && (mouse_px!=-1) && (mouse_py!=-1))
    {
        readonly float DRAG_SPEED_MULTIPLIER = 1.0;
        int dx = FloatToInt( IntToFloat(mouse.x - mouse_px) * DRAG_SPEED_MULTIPLIER );
        int dy = FloatToInt( IntToFloat(mouse.y - mouse_py) * DRAG_SPEED_MULTIPLIER );
        Map.SetViewport(Map.GetViewportX()-dx, Map.GetViewportY()-dy);
    }
    
    mouse_px = mouse.x;
    mouse_py = mouse.y;
}


You can use Map.XXX functions to control your map viewport position.

Setting the DRAG_SPEED_MULTIPLIER thing to "2.0" will make it twice faster to scroll/pan the map.

These "defines" specify corresponding margins. You may want to set them all to 0, if you like.
Code: ags

#define Map_MARGIN_TOP       (-10)     // top margin
#define Map_MARGIN_BOTTOM    (-10)     // bottom margin
#define Map_MARGIN_LEFT      (-10)     // left margin
#define Map_MARGIN_RIGHT     (-10)     // right margin


See if it works, and let us know how it goes.


(EDIT: cosmetic changes to SetViewport() to make it easier to read the script code, and made DRAG_SPEED_MULTIPLIER a float)

bulka_tarta

Wow, this is awesome! The map is working like a charm now!

I made a small change in the repeatedly execute so that the map doesn't scroll when it's not visible:

Code: ags
int mouse_px = -1; // previous mouse x
int mouse_py = -1; // previous mouse y
GUI* g; // Added GUI pointer
 
function repeatedly_execute( )
{
  g = GUI.GetAtScreenXY(mouse.x, mouse.y); // Check if the you are clicking on the map
    if (mouse.IsButtonDown(eMouseLeft) && (mouse_px!=-1) && (mouse_py!=-1) && (g == gMap)) // Pan around when clicking on the gMap
    {
        readonly int DRAG_SPEED_MULTIPLIER = 1;
        int dx = (mouse.x - mouse_px) * DRAG_SPEED_MULTIPLIER;
        int dy = (mouse.y - mouse_py) * DRAG_SPEED_MULTIPLIER;
        Map.SetViewport(Map.GetViewportX()-dx, Map.GetViewportY()-dy);
    }
    
    mouse_px = mouse.x;
    mouse_py = mouse.y;
}


Thank you so much for this! :-D

SMF spam blocked by CleanTalk