首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 開發 WPF/Silverlight 動畫與遊戲系列教程(十九):完美精靈之八面玲瓏(僅限 WPF)③

     編輯:關於C#

前言:補充動畫幀切換邏輯

在上一節(十八)的 ChangeAction() 方法中,我們提到它會根據精靈當前的動作來設定切圖動畫的起始幀與結束幀。這兩個參數 CurrentStartFrame 與 CurrentEndFrame 至關重要,因為在精靈的生命線程中,我們透過以下核心代碼實現連續動畫:

// 精靈線程間隔事件
private void Timer_Tick(object sender, EventArgs e) {
    ...
    // 動態更改精靈圖片源以形成連續動作
    Body.Source = Source[(int)Direction, FrameCounter];
    FrameCounter = FrameCounter == CurrentEndFrame ? CurrentStartFrame : FrameCounter + 1;
}

以「跑步」動作為例:FrameCounter 從 5 開始計數,以 1 為單位遞增,目標為 12;當到達 12 時回到 5 重複。同理,「施法」動作從 20 計數至 25 後回到 20。其他動作以此類推。

本節核心目標:精確朝向與定位停止

第六節末尾曾略提相關實現方法,但未深入。本節將完整實現八方向精靈的正確朝向顯示與精確定位停止。實現步驟分析如下:

  1. 獲取主角當前座標(已於第十五節實現,定位於腳底 Spirit.X、Spirit.Y)
  2. 獲取目標座標(滑鼠左/右鍵點擊位置,前幾章已大量示範)
  3. 以兩座標為參數,透過正切值計算公式得出朝向代號(0-7 對應八個方向)

正切值計算朝向的核心方法

下圖右半部分註釋已詳細說明原理,以下為通用判斷朝向方法:

/// <summary>
/// 透過正切值獲取精靈朝向代號
/// </summary>
/// <param name="targetX">目標點 X</param>
/// <param name="targetY">目標點 Y</param>
/// <param name="currentX">當前點 X</param>
/// <param name="currentY">當前點 Y</param>
/// <returns>朝向代號(以北為 0,順時針 1-7)</returns>
public static double GetDirectionByTan(double targetX, double targetY, double currentX, double currentY) {
    double tan = (targetY - currentY) / (targetX - currentX);
    if (Math.Abs(tan) >= Math.Tan(Math.PI * 3 / 8) && targetY <= currentY) return 0;
    else if (Math.Abs(tan) > Math.Tan(Math.PI / 8) && Math.Abs(tan) < Math.Tan(Math.PI * 3 / 8) && targetX > currentX && targetY < currentY) return 1;
    else if (Math.Abs(tan) <= Math.Tan(Math.PI / 8) && targetX >= currentX) return 2;
    else if (Math.Abs(tan) > Math.Tan(Math.PI / 8) && Math.Abs(tan) < Math.Tan(Math.PI * 3 / 8) && targetX > currentX && targetY > currentY) return 3;
    else if (Math.Abs(tan) >= Math.Tan(Math.PI * 3 / 8) && targetY >= currentY) return 4;
    else if (Math.Abs(tan) > Math.Tan(Math.PI / 8) && Math.Abs(tan) < Math.Tan(Math.PI * 3 / 8) && targetX < currentX && targetY > currentY) return 5;
    else if (Math.Abs(tan) <= Math.Tan(Math.PI / 8) && targetX <= currentX) return 6;
    else if (Math.Abs(tan) > Math.Tan(Math.PI / 8) && Math.Abs(tan) < Math.Tan(Math.PI * 3 / 8) && targetX < currentX && targetY < currentY) return 7;
    else return 0;
}

註:Math.Tan() 參數為弧度單位,角度需透過公式轉換。演算法邏輯是否最優見仁見智,歡迎提供更優解法。

滑鼠點擊事件啟動尋路與朝向動畫

在滑鼠左鍵點擊事件中獲取目標點,切換主角為跑步狀態並啟動 A* 尋路:

private void Carrier_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
    Point p = e.GetPosition(Map); // 點擊處在 Map 中的座標
    Spirit.Action = Actions.Run;  // 切換為跑步狀態
    AStarMoveTo(p);               // 開始尋路
}

上兩節的 AStarMoveTo() 僅建立 X、Y 序列幀動畫。為實現角色時時朝向,需額外建立 Direction 序列幀動畫,改進後的方法如下:

private void AStarMoveTo(Point p) {
    ...
    // 建立 X 軸逐幀動畫
    DoubleAnimationUsingKeyFrames keyFramesAnimationX = new DoubleAnimationUsingKeyFrames();
    keyFramesAnimationX.Duration = new Duration(TimeSpan.FromMilliseconds(path.Count * cost));
    Storyboard.SetTarget(keyFramesAnimationX, Spirit);
    Storyboard.SetTargetProperty(keyFramesAnimationX, new PropertyPath("X"));

    // 建立 Y 軸逐幀動畫
    DoubleAnimationUsingKeyFrames keyFramesAnimationY = new DoubleAnimationUsingKeyFrames();
    keyFramesAnimationY.Duration = new Duration(TimeSpan.FromMilliseconds(path.Count * cost));
    Storyboard.SetTarget(keyFramesAnimationY, Spirit);
    Storyboard.SetTargetProperty(keyFramesAnimationY, new PropertyPath("Y"));

    // 建立朝向動畫(新增部分)
    DoubleAnimationUsingKeyFrames keyFramesAnimationDirection = new DoubleAnimationUsingKeyFrames();
    keyFramesAnimationDirection.Duration = new Duration(TimeSpan.FromMilliseconds(path.Count * cost));
    Storyboard.SetTarget(keyFramesAnimationDirection, Spirit);
    Storyboard.SetTargetProperty(keyFramesAnimationDirection, new PropertyPath("Direction"));

    for (int i = 0; i < framePosition.Count(); i++) {
        // X 軸關鍵幀
        LinearDoubleKeyFrame keyFrame = new LinearDoubleKeyFrame();
        keyFrame.Value = i == 0 ? Spirit.X : framePosition[i].X * GridSize;
        keyFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(cost * i));
        keyFramesAnimationX.KeyFrames.Add(keyFrame);

        // Y 軸關鍵幀
        keyFrame = new LinearDoubleKeyFrame();
        keyFrame.Value = i == 0 ? Spirit.Y : framePosition[i].Y * GridSize;
        keyFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(cost * i));
        keyFramesAnimationY.KeyFrames.Add(keyFrame);

        // 朝向關鍵幀(核心邏輯:後一點與前一點計算正切值決定方向)
        keyFrame = new LinearDoubleKeyFrame();
        keyFrame.Value = i == framePosition.GetUpperBound(0)
            ? Super.GetDirectionByTan(framePosition[i].X, framePosition[i].Y, framePosition[i - 1].X, framePosition[i - 1].Y)
            : Super.GetDirectionByTan(framePosition[i + 1].X, framePosition[i + 1].Y, framePosition[i].X, framePosition[i].Y);
        keyFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(cost * i));
        keyFramesAnimationDirection.KeyFrames.Add(keyFrame);
    }

    storyboard.Children.Add(keyFramesAnimationX);
    storyboard.Children.Add(keyFramesAnimationY);
    storyboard.Children.Add(keyFramesAnimationDirection);
    ...
}

方向動畫建立與 X、Y 動畫如出一轍,原理是將尋路點序列中後一點與前一點計算正切值確定方向,再將方向序列值作為關鍵幀加入 Storyboard。

抵達目標後自動切換待機狀態

測試發現角色抵達目的地後仍保持跑步狀態。新增判斷方法:

// 判斷是否移動到目的地
private bool ArriveTarget() {
    return (storyboard != null && storyboard.GetCurrentProgress() == 1) ? true : false;
}

在遊戲視窗刷新主線程中調用:

// 遊戲視窗刷新主線程間隔事件
private void Timer_Tick(object sender, EventArgs e) {
    // 判斷主角是否移動到目標,若是則切換為待機
    if (ArriveTarget()) {
        Spirit.Action = Actions.Stop;
    }
    ...
}

成果展示

歷經三節完成完美精靈系統,角色現可顯示正確方向並精確停止。下一節將對前 19 節內容進行補遺與拓展,作為教程第一大部分(1-20 節)的小結。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved