本章完成 QXSpirit 用戶控件的後台邏輯,核心包含三大支柱:
X、Y 支援 Storyboard 動畫、Binding、樣式觸發ImageAddress、Timer.Interval 實現動作速度動態調整// X 依賴屬性
public double X
{
get => (double)GetValue(XProperty);
set => SetValue(XProperty, value);
}
public static readonly DependencyProperty XProperty =
DependencyProperty.Register("X", typeof(double), typeof(QXSpirit),
new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.None));
// Y 依賴屬性(結構相同)
public double Y { get => (double)GetValue(YProperty); set => SetValue(YProperty, value); }
public static readonly DependencyProperty YProperty =
DependencyProperty.Register("Y", typeof(double), typeof(QXSpirit),
new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.None));
為何必須用依賴屬性? 普通 CLR 屬性(double _X; public double X { get; set; })無法作為 Storyboard 目標屬性、不支援資料繫結、不支援樣式/觸發器/動畫繼承。依賴屬性雖樣板碼較多,但在 WPF/Silverlight 視覺樹與動畫系統中不可或缺。
public QXSpirit()
{
InitializeComponent();
InitThread();
}
DispatcherTimer Timer = new DispatcherTimer();
private void InitThread()
{
Timer.Tick += Timer_Tick;
Timer.Start();
}
int count = 1;
private void Timer_Tick(object sender, EventArgs e)
{
Body.Source = new BitmapImage(new Uri(ImageAddress + count + ".png", UriKind.Relative));
count = count == 7 ? 0 : count + 1;
}
每個精靈實例擁有獨立 DispatcherTimer,優先級預設 Normal,間隔由外部設定(Spirit.Timer.Interval)。Timer_Tick 目前僅演示循環切換 0~7 幀圖片,後續將擴展為狀態機:待機、移動、攻擊、施法、受擊、死亡等狀態各自對應不同幀序列與幀率。
string _ImageAddress;
public string ImageAddress
{
get => _ImageAddress;
set => _ImageAddress = value;
}
圖片目錄路徑不需動畫/繫結,普通屬性即可。
using WPFGameCourse.Controls;
QXSpirit Spirit = new QXSpirit();
private void InitSpirit()
{
Spirit.X = 300;
Spirit.Y = 400;
Spirit.Timer.Interval = TimeSpan.FromMilliseconds(150); // 幀率 ~6.67 FPS
Spirit.ImageAddress = @"..\Player\";
Carrier.Children.Add(Spitit);
}
在主視窗的 CompositionTarget.Rendering 或 DispatcherTimer.Tick 中:
int scrollspeed = 3;
private void Timer_Tick(object sender, EventArgs e)
{
double mapLeft = Canvas.GetLeft(Map);
double mapTop = Canvas.GetTop(Map);
// 主角跟隨
Canvas.SetLeft(Spirit, Spirit.X + mapLeft);
Canvas.SetTop(Spirit, Spirit.Y + mapTop);
// 障礙物跟隨(示範用,實際建議用虛擬化面板)
foreach (UIElement uie in Carrier.Children)
{
if (uie is Rectangle rect)
{
Point p = GetPointFromTag(rect.Tag); // 解析 Tag 存儲的邏輯座標
Canvas.SetLeft(rect, mapLeft + p.X * GridSize);
Canvas.SetTop(rect, mapTop + p.Y * GridSize);
}
}
}
公式核心:視覺位置 = 邏輯位置 + 地圖偏移量。精靈因有 X/Y 依賴屬性直接存儲邏輯座標,無需解析 Tag,效能更佳。
private Point GetPointFromTag(object tag)
{
string[] s = tag.ToString().Split(',');
return new Point(double.Parse(s[0]), double.Parse(s[1]));
}
建構障礙物時將邏輯格座標存入 Tag:rect.Tag = $"{x},{y}";。
按 Ctrl+F5 執行,拖曳地圖時主角與障礙物同步移動,位置精確無誤差。但此時主角尚無「點擊地圖自主寻路移動」能力,下一章將整合第 16 章的 A* 尋路邏輯,完成牽引式地圖模式下的完整角色移動系統。