C#開發WPF/Silverlight動畫及游戲系列教程(Game Course):(十五) 精靈控件橫空出世!②
緊接著上一節,我們打開QXSpirit.xaml.cs文件。在游戲設計中,為了能夠輕易控制及管理精靈的各項屬性及功能等,我賦予每個精靈一個專屬線程,它在精靈的使用中起到關鍵作用:
public QXSpirit() {
InitializeComponent();
InitThread(); //初始化精靈線程
}
DispatcherTimer Timer = new DispatcherTimer();
private void InitThread() {
Timer.Tick += new EventHandler(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線程的創建在前面的章節中見得不要太多,這裡不再累述了。那為何要在精靈控件中配置一個線程呢?打這樣一個比方吧:我們可以把精靈比做一個人,它的生命就好比這個線程,每個人只有一條命,在精子與卵子結合之後(控件的初始化中創建),生命即開始鮮活(創建後即啟動)簡單說就是“命懸一線”啦。汗一個…。目前該線程與前面章節中的一樣,暫時只做精靈動作圖片切用(Timer_Tick()),至於其他功用,我將在後面的章節中進行講解。
賦予了精靈生命以後,接著需要培養它的性格,讓它有更多的能力、更多的屬性。當然,大家首先迫切想要實現的就是前兩節遺留下來關於精靈的X,Y屬性。那麼先來看代碼:
//精靈X坐標(依賴屬性)
public double X {
get { return (double)GetValue(XProperty); }
set { SetValue(XProperty, value); }
}
public static readonly DependencyProperty XProperty = DependencyProperty.Register(
"X", //屬性名
typeof(double), //屬性類型
typeof(QXSpirit), //屬性主人類型
new FrameworkPropertyMetadata(
(double)0, //初始值0
FrameworkPropertyMetadataOptions.None, //不特定界面修改
//不需要屬性改變回調
null,//new PropertyChangedCallback(QXSpiritInvalidated),
//不使用強制回調
null
)
);
//精靈Y坐標(依賴屬性)
public double Y {
get { return (double)GetValue(YProperty); }
set { SetValue(YProperty, value); }
}
public static readonly DependencyProperty YProperty = DependencyProperty.Register(
"Y",
typeof(double),
typeof(QXSpirit),
new FrameworkPropertyMetadata(
(double)0,
FrameworkPropertyMetadataOptions.None,
null,
null
)
);
以上代碼實現了QXSpirit控件的X,Y依賴屬性。大家不要被看似復雜的代碼所嚇著,其實很簡單的,讓我一一道來。首先將以上代碼分成兩部分:X坐標為第一部分,Y坐標為第二部分。它們的結構是一模一樣的,我們可以忽略Y坐標,只要理解了X依賴屬性的實現,將X換成Y即可。
關於依賴屬性的相關知識,網上不要太多,它不是本教程的重點所以就不多做解釋了。理解它的朋友都明白,上面代碼是它的標准創建形式,public double X是它的屬性訪問器,public static readonly DependencyProperty XProperty 則是定義它。就如上面代碼注釋中寫到的,分別定義它的屬性名、類型、所處類名等等。這樣,一個完整的X依賴屬性就完成了。有的朋友又困惑了,為什麼要那麼麻煩去創建依賴屬性?我直接這樣寫不就得了:
double _X;
public double X {
get { return _X; }
set { _X = value; }
}
即傳統又簡單。但是,我想告訴大家的是,在WPF/Silverlight中,只有依賴屬性才能被更好的使用及識別,例如在屬性的綁定,Storyboard目標屬性的設定等等中,都必須使用到依賴屬性來實現,後面的章節中會講到它的必要性。而像如上的屬性訪問器只能用於創建純描述性屬性,例如精靈圖片地址目錄等,就可以使用屬性訪問器:
//精靈圖片源目錄地址
string _ImageAddress;
public string ImageAddress {
get { return _ImageAddress; }
set { _ImageAddress = value; }
}
至此,我們完成了一個初具雛形的精靈控件,接下來就是如何將之加入到游戲中了。首先要做的當然是添加精靈控件的引用:
using WPFGameCourse.Controls;
接下來就是創建精靈控件實例並將之添加進窗口的Carrier控件中:
QXSpirit Spirit = new QXSpirit();
private void InitSpirit() {
Spirit.X = 300; //為精靈依賴屬性X賦值
Spirit.Y = 400; //為精靈依賴屬性Y賦值
Spirit.Timer.Interval = TimeSpan.FromMilliseconds(150); //精靈圖片切換頻率
Spirit.ImageAddress = @"..\Player\"; //精靈圖片源地址
Carrier.Children.Add(Spirit);
}
從代碼可以看出,我們已經可以自由的使用Spirit的X,Y屬性了,並且輕松的控制該精靈的圖片切換頻率(為什麼我們需要去控制它的切換頻率呢?因為在游戲中,角色施放魔法有施法速度;物理攻擊時有攻擊速度、甚至可能會被凍結(移動速度減)、麻痺(精靈不動)、加速移動攻擊BUFF等等,這些不光需要更改角色的相關屬性邏輯,更需要在游戲窗口表現時通過調整精靈圖片切換速率來實現之,因此意義是相當相當重大的),是不是有些成就感了?
至於我們在牽引地圖移動的同時,如何實現角色及障礙物的跟隨移動?有了X,Y屬性以後,這再簡單不過了,首先大家來看這張圖:
因為地圖圖片在隨鼠標牽引的情況下,它的Canvas.getLeft(Map)和Canvas.getTop(Map)屬性是時時更新的,在第十三節中有提到。那麼在地圖動的時候,當已知主角在地圖圖片中的坐標(Spirit.X,Spirit.Y)後,即可以按上圖根據公式計算出它在游戲窗口中的時時位置為:Spirit.X+ Canvas.getLeft(Map),Spirit.Y+ Canvas.getTop(Map)。有了它,我們只需要在游戲窗口的Timer_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) {
Rectangle r = uie as Rectangle;
Point p = getPointFromTag(r.Tag); //此方法在上一節有介紹
Canvas.SetLeft(r, mapleft + p.X * GridSize);
Canvas.SetTop(r, maptop + p.Y * GridSize);
}
}
}
上面黃色代碼部分即為通過公式來改變主角在游戲窗口中的顯示。由於此時的障礙物實體為Rectangle,因此可以通過foreach來改變窗口中所有障礙物顯示實體Rectangle對象的顯示位置來描繪障礙物同樣隨著地圖的移動而移動(實際中並不需要此段代碼,只為了演示)。
到此為止就完成了牽引式地圖移動模式中的所有對象跟隨地圖移動。最後大家按下CTRL+F5並任意移動移動地圖看看:
嘿嘿,都能跟隨移動了呢,但是仿佛還遺漏了什麼??對了,還沒實現此模式下主角通過鼠標點擊進行走路呢,而且在走路的同時如果我們牽引地圖移動,主角也能同樣的顯示在正確窗口位置上,這又涉及到多個坐標系中坐標的換算,就讓這頭疼的問題留給下節去處理吧,敬請關注。