2D 俯視角遊戲(RTS、SLG、ARPG)中,地圖遠大於螢幕。遊戲視窗如同固定支架上的攝影機鏡頭,只能看見鏡頭範圍內的地圖區域。要探索其他區域,必須移動地圖背景,而非移動攝影機。當滑鼠游標進入視窗邊緣 8 個區域(上、下、左、右、左上、右上、左下、右下)時,觸發地圖向相反方向平移,產生「視窗向該方向探索」的視覺錯覺。
在視窗邊緣建立 8 個透明 Rectangle(滾動介質),Opacity=0.3 供測試觀察,ZIndex=10001 置頂。UI 執行緒中輪詢 roller.IsMouseOver,若為真則移動地圖:
int scrollSpeed = 5;
private void Timer_Tick(object sender, EventArgs e)
{
if (roller.IsMouseOver)
Canvas.SetTop(Map, Canvas.GetTop(Map) - scrollSpeed); // 正下方介質 → 地圖向上移
}
優點:邏輯直觀、程式碼簡單。缺點:大量 UI 元素命中測試、難以處理對角線區域重疊、效能隨介質數量線性增長。
不建立額外 UI 元素,直接在 UI 執行緒中根據 Mouse.GetPosition(Carrier) 計算滑鼠所在 8 區域代號(0~7,對應時鐘 0~7 點),再結合地圖邊界檢查決定是否移動:
int distance = 80; // 觸發邊緣寬度
private int GetMouseArea()
{
Point mp = Mouse.GetPosition(Carrier);
if (mp.X < 0 || mp.Y < 0) return -1;
// 水平中央區:190~570,垂直中央區:125~375(視窗 800x600 為例)
if (mp.X >= 190 && mp.X <= 570)
{
if (mp.Y <= distance) return 0; // 上
if (mp.Y >= 500 - distance) return 4; // 下
}
else if (mp.Y >= 125 && mp.Y <= 375)
{
if (mp.X <= distance) return 6; // 左
if (mp.X >= 760 - distance) return 2; // 右
}
// 四個角落區域(邊界條件需仔細處理)
else if ((mp.X < 190 && mp.Y <= distance) || (mp.Y < 125 && mp.X <= distance)) return 7; // 左上
else if ((mp.X > 570 && mp.Y <= distance) || (mp.Y < 125 && mp.X >= 760 - distance)) return 1; // 右上
else if ((mp.X > 570 && mp.Y >= 500 - distance) || (mp.Y > 375 && mp.X >= 760 - distance)) return 3; // 右下
else if ((mp.X < 190 && mp.Y >= 500 - distance) || (mp.Y > 375 && mp.X <= distance)) return 5; // 左下
return -1;
}
private void Timer_Tick(object sender, EventArgs e)
{
double mapLeft = Canvas.GetLeft(Map);
double mapTop = Canvas.GetTop(Map);
int area = GetMouseArea();
switch (area)
{
case 0: // 上
if (mapTop < 0) Canvas.SetTop(Map, mapTop + scrollSpeed);
break;
case 1: // 右上
if (mapTop < 0) Canvas.SetTop(Map, mapTop + scrollSpeed);
if (Map.Width + mapLeft > ActualWidth) Canvas.SetLeft(Map, mapLeft - scrollSpeed);
break;
case 2: // 右
if (Map.Width + mapLeft > ActualWidth) Canvas.SetLeft(Map, mapLeft - scrollSpeed);
break;
case 3: // 右下
if (Map.Width + mapLeft > ActualWidth) Canvas.SetLeft(Map, mapLeft - scrollSpeed);
if (Map.Height + mapTop > ActualHeight) Canvas.SetTop(Map, mapTop - scrollSpeed);
break;
case 4: // 下
if (Map.Height + mapTop > ActualHeight) Canvas.SetTop(Map, mapTop - scrollSpeed);
break;
case 5: // 左下
if (Map.Height + mapTop > ActualHeight) Canvas.SetTop(Map, mapTop - scrollSpeed);
if (mapLeft < 0) Canvas.SetLeft(Map, mapLeft + scrollSpeed);
break;
case 6: // 左
if (mapLeft < 0) Canvas.SetLeft(Map, mapLeft + scrollSpeed);
break;
case 7: // 左上
if (mapTop < 0) Canvas.SetTop(Map, mapTop + scrollSpeed);
if (mapLeft < 0) Canvas.SetLeft(Map, mapLeft + scrollSpeed);
break;
}
}
邊界條件:mapLeft < 0 表示地圖未超出左邊界;Map.Width + mapLeft > ActualWidth 表示地圖右側未超出視窗右邊界。同理垂直方向。
地圖移動時,所有遊戲物件(主角、NPC、怪物、障礙物)必須同步更新視覺位置:
// 伪代碼:UI 執行緒每幀
foreach (var obj in GameObjects)
{
Canvas.SetLeft(obj.Visual, obj.LogicX + Canvas.GetLeft(Map));
Canvas.SetTop(obj.Visual, obj.LogicY + Canvas.GetTop(Map));
}
若物件數量極大(千個以上),逐幀遍歷會造成效能瓶頸。實戰優化方案:
Carrier.Children 移除,進入範圍者加入。VirtualizingPanel 或自訂面板按需實例化視覺元素。本教程為教學示範,採用全量更新,後續章節將引入精靈控件統一管理。
牽引式地圖移動的核心公式:視覺位置 = 邏輯位置 + 地圖偏移量。策略二(座標計算法)效能優於策略一,適合實戰。下一章將引入第 14 章建立的精靈控件,實現主角在地圖上的自主寻路移動,並解決移動中拖曳地圖的座標同步問題。