為區分滑鼠狀態,約定四種游標:空曠地圖區域顯示預設游標(0 號);滑鼠懸停精靈上方顯示發光游標(1 號);指向敵對精靈顯示攻擊游標(2 號);使用魔法快捷鍵顯示凝法狀態游標(3 號)。
新增 Cursors 資料夾存放 4 個 .ani 檔案,透過靜態陣列快取:
public static Cursor[] GameCursors = new Cursor[4];
public static Cursor getCursor(int sign) {
if (GameCursors[sign] == null) {
GameCursors[sign] = new Cursor(new FileStream(string.Format(@"Cursors\{0}.ani", sign), FileMode.Open, FileAccess.Read, FileShare.Read));
}
return GameCursors[sign];
}
在視窗 MouseMove 事件中:
private void Window_MouseMove(object sender, MouseEventArgs e) {
this.Cursor = e.Source is QXSpirit ? Super.getCursor(1) : Super.getCursor(0);
}
問題:精靈圖片為透明背景 PNG/GIF,整個 200×200 區域皆屬 Image 控件範圍,導致滑鼠在透明區域也觸發 1 號游標。
對當前拾取物件圖片源進行逐點顏色檢測:
public static Color getImagePointColor(BitmapSource bitmapsource, int x, int y) {
CroppedBitmap crop = new CroppedBitmap(bitmapsource as BitmapSource, new Int32Rect(x, y, 1, 1));
byte[] pixels = new byte[4];
try {
crop.CopyPixels(pixels, 4, 0);
crop = null;
} catch (Exception ee) { MessageBox.Show(ee.ToString()); }
return Color.FromArgb(pixels[3], pixels[2], pixels[1], pixels[0]);
}
優點:精確到像素;缺點:僅限 WPF、效能差、需包在 Try-Catch 中、圖片切換快易出錯。
定義 public double[] EfficaciousSection 參數:{左邊距, 右邊距, 上邊距, 下邊距},僅在滑鼠進入此矩形區域才判定捕獲。例如 200×200 怪物圖片,有效區域 {80, 125, 50, 145}。
改進滑鼠移動事件:
if (e.Source is QXSpirit) {
QXSpirit Spirit = e.Source as QXSpirit;
Point p = e.GetPosition(Spirit);
if (p.X >= Spirit.EfficaciousSection[0] && p.X <= Spirit.EfficaciousSection[1]
&& p.Y >= Spirit.EfficaciousSection[2] && p.Y <= Spirit.EfficaciousSection[3]) {
this.Cursor = Super.getCursor(1);
} else {
this.Cursor = Super.getCursor(0);
}
}
新問題:怪物圖片遮擋主角時,滑鼠懸停主角上方實捕獲到怪物,且滑鼠未進怪物有效區域,導致主角游標不變。
HitTest 能穿透所有層級捕獲視覺樹中任意深度的物件,效能極高。原理示意圖如下:
List<QXSpirit> SpiritList = new List<QXSpirit>();HitFilter,僅保留 QXSpirit 型別:public HitTestFilterBehavior HitFilter(DependencyObject dObject) {
if (dObject is QXSpirit) SpiritList.Add(dObject as QXSpirit);
return HitTestFilterBehavior.Continue;
}
HitResult,繼續向深層遞迴:public HitTestResultBehavior HitResult(HitTestResult result) {
return HitTestResultBehavior.Continue;
}
private void Window_MouseMove(object sender, MouseEventArgs e) {
SpiritList.Clear();
Point p = e.GetPosition(Carrier);
VisualTreeHelper.HitTest(Carrier, new HitTestFilterCallback(HitFilter), new HitTestResultCallback(HitResult), new PointHitTestParameters(p));
if (SpiritList.Count > 0) {
for (int i = 0; i < SpiritList.Count; i++) {
if (isEfficaciousSection(SpiritList[i].EfficaciousSection, e.GetPosition(SpiritList[i]))) {
this.Cursor = Super.getCursor(1);
label3.Content = SpiritList[i].Name; // 除錯用
break;
} else {
this.Cursor = Super.getCursor(0);
}
}
}
}
每次移動清空容器,對 Carrier 中滑鼠位置執行點擊測試,過濾後遍歷容器內精靈,僅當滑鼠在有效實體區域才判定捕獲成功。
地圖密集區加入 30 隻不同名稱怪物,滑鼠移動捕獲極其準確:
描邊方案 (OuterGlowBitmapEffect) 僅限 WPF 且效能極差。採用半透明方案,僅增改 6 行代碼:
private void Window_MouseMove(object sender, MouseEventArgs e) {
...
if (SpiritList.Count > 0) {
bool targetIsFound = false;
for (int i = 0; i < SpiritList.Count; i++) {
if (!targetIsFound && isEfficaciousSection(SpiritList[i].EfficaciousSection, e.GetPosition(SpiritList[i]))) {
this.Cursor = Super.getCursor(1);
SpiritList[i].Opacity = 0.6;
targetIsFound = true;
label3.Content = SpiritList[i].Name;
} else {
if (!targetIsFound) this.Cursor = Super.getCursor(0);
SpiritList[i].Opacity = 1;
}
}
}
}
效果圖:
HitTest 功能強大,是實現打怪與施法的前提。下節將講解精靈面板界面及生命、魔力、經驗值三大屬性表現形式。