C#開發WPF/Silverlight動畫及游戲系列教程(Game Course):(九) 2D游戲角色在地圖上的移動
本節將運用前兩節的知識到實際的2D游戲人物在地圖上移動中,同時也算是對前面八節的內容進行一次綜合運用吧。
那麼先從最底層的地圖講起。首先我將一張地圖添加進游戲窗口中,這裡我同樣使用Image控件:
Image Map = new Image();
private void InitMap() {
Map.Width = 800;
Map.Height = 600;
Map.Source = new BitmapImage((new Uri(@"Map\Map.jpg", UriKind.Relative)));
Carrier.Children.Add(Map);
Map.SetValue(Canvas.ZIndexProperty, -1);
}
我將一個800*600名叫Map.jpg的地圖圖片添加進項目Map文件夾中,然後將它的Canvas.Zindex屬性設置為-1,這樣它就相當於地圖背景的作用了。有了這張地圖以後,我們需要對它進行障礙物設置:
從上圖可以看到,理想的狀態下,障礙物為我用藍色填充的區域,這是理想狀態下障礙物的設置。但是實際運用中,就拿本教程來講,因為GridSize設置為20,那麼我們最終得到的障礙物將是這樣的:
從上圖可以看到,每個綠色格子代表一個20*20像素的障礙物,只能說勉強達到描繪障礙物的效果吧。從而又驗證了我們上一節所講到的GridSize越小,定位將越精確,難道不是至理名言嗎!
有了這個思路,接下來我用了3個循環算法實現了左部分的障礙物設定:
//構建障礙物
for (int y = 12; y <= 27; y++) {
for (int x = 0; x <= 7; x++) {
//障礙物在矩陣中用0表示
Matrix[x, y] = 0;
rect = new Rectangle();
rect.Fill = new SolidColorBrush(Colors.GreenYellow);
rect.Opacity = 0.3;
rect.Stroke = new SolidColorBrush(Colors.Gray);
rect.Width = GridSize;
rect.Height = GridSize;
Carrier.Children.Add(rect);
Canvas.SetLeft(rect, x * GridSize);
Canvas.SetTop(rect, y * GridSize);
}
}
int move = 0;
for (int x = 8; x <= 15; x++) {
for (int y = 12; y <= 18; y++) {
Matrix[x, y - move] = 0;
rect = new Rectangle();
rect.Fill = new SolidColorBrush(Colors.GreenYellow);
rect.Opacity = 0.3;
rect.Stroke = new SolidColorBrush(Colors.Gray);
rect.Width = GridSize;
rect.Height = GridSize;
Carrier.Children.Add(rect);
Canvas.SetLeft(rect, x * GridSize);
Canvas.SetTop(rect, (y - move) * GridSize);
}
move = x % 2 == 0 ? move + 1 : move;
}
int start_y = 4;
int end_y = 10;
for (int x = 16; x <= 23; x++) {
for (int y = start_y; y <= end_y; y++) {
Matrix[x, y + move] = 0;
rect = new Rectangle();
rect.Fill = new SolidColorBrush(Colors.GreenYellow);
rect.Opacity = 0.3;
rect.Stroke = new SolidColorBrush(Colors.Gray);
rect.Width = GridSize;
rect.Height = GridSize;
Carrier.Children.Add(rect);
Canvas.SetLeft(rect, x * GridSize);
Canvas.SetTop(rect, (y + move) * GridSize);
}
start_y = x % 3 == 0 ? start_y + 1 : start_y;
end_y = x % 3 == 0 ? end_y - 1 : end_y;
}
構建好障礙物後運行程序測試的效果如下圖:
障礙物終於繪制完畢了,那麼接下來就是動畫部分了。還記得我們第六章中實現2D人物移動動畫嗎?其中有提到人物的移動基於它的左上角坐標,這是不真實的,那麼我們需要為主角定義X,Y坐標,實現真實的定位到主角的腳底,所以我們這裡需要一個邏輯:
int count = 1;
Image Spirit = new Image(); //創建主角
int SpiritCenterX = 4; //主角腳底離主角圖片左邊的距離(游戲坐標系中)
int SpiritCenterY = 5; //主角腳底離主角頂部的距離(游戲坐標系中)
//游戲坐標系中Spirit坐標(縮小操作)
int _SpiritGameX;
int SpiritGameX {
get { return ((int)Canvas.GetLeft(Spirit) / GridSize) + SpiritCenterX; }
set { _SpiritGameX = value; }
}
int _SpiritGameY;
int SpiritGameY {
get { return ((int)Canvas.GetTop(Spirit) / GridSize) + SpiritCenterY; }
set { _SpiritGameY = value; }
}
//窗口坐標系中Spirit坐標(放大操作)
int SpiritWindowX {
get { return (SpiritGameX - SpiritCenterX) * GridSize; }
}
int SpiritWindowY {
get { return (SpiritGameY - SpiritCenterY) * GridSize; }
}
上一節有說到關於兩個不同坐標系同時存在的問題,上面的代碼就是對它們的定義並且實現它們之間相互轉換,設置好以後,就可以根據情況的需要來分別調用不同坐標系下主角的X,Y坐標了。
定義好地圖、障礙物和主角的坐標系以後,接著需要對主角和地圖初始化:
public Window9() {
InitializeComponent();
ResetMatrix(); //初始化二維矩陣
InitPlayer(); //初始化目標對象
InitMap(); //初始化地圖
DispatcherTimer dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Interval = TimeSpan.FromMilliseconds(150);
dispatcherTimer.Start();
}
可以看到後面4行代碼那麼的眼熟?其實就是第三節所講到的知識。最後就是本節的重頭戲,實現鼠標點擊事件:
private void Carrier_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
Point p = e.GetPosition(Carrier);
//進行坐標系縮小
int start_x = SpiritGameX;
int start_y = SpiritGameY;
Start = new System.Drawing.Point(start_x, start_y); //設置起點坐標
int end_x = (int)p.X / GridSize;
int end_y = (int)p.Y / GridSize;
End = new System.Drawing.Point(end_x, end_y); //設置終點坐標
……
if (path == null) {
MessageBox.Show("路徑不存在!");
} else {
……
for (int i = 0; i < framePosition.Count(); i++) {
//加入X軸方向的勻速關鍵幀
LinearDoubleKeyFrame keyFrame = new LinearDoubleKeyFrame();
//平滑銜接動畫
keyFrame.Value = i == 0 ? Canvas.GetLeft(Spirit) : (framePosition[i].X - SpiritCenterX * GridSize);
keyFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(cost * i));
keyFramesAnimationX.KeyFrames.Add(keyFrame);
//加入X軸方向的勻速關鍵幀
keyFrame = new LinearDoubleKeyFrame();
keyFrame.Value = i == 0 ? Canvas.GetTop(Spirit): (framePosition[i].Y - SpiritCenterY * GridSize);
keyFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(cost * i));
keyFramesAnimationY.KeyFrames.Add(keyFrame);
}
……
}
}
代碼和上一節裡的沒有多大的區別,改動為我用黃色背景色描繪的區域(…….號表示該段代碼與上一節不變)。主要就是針對如何進行主角真實腳底坐標在兩個坐標系中的換算問題進行了布局修改,大家可以與上一節裡的示例代碼進行比較,非常容易就可以進行分析理解,這裡我就不再累述了。大功告成啦,我將障礙物的表現去掉,然後國際慣例Ctrl+F5測試一下我們的成果吧:
至此,我們就實現了2D游戲人物在地圖中的移動。大家再回頭看看或許會發現:本節地圖中的障礙物均是由正方形塊組成,也就是說地圖是基於直角坐標系的。但是在實際的游戲制作中,特別是SLG走格子回合制等類型的游戲中,基本都采用斜度的地圖構造。那麼下一節我將就如何構造斜度坐標系地圖進行講解,敬請關注。