Category: Algorithm
Lindenmayer System 分形圖形學
Voronoi Diagram – A Case Study of Fortune’s Algorithm
Voronoi Diagram 是由俄國數學家 Georgy Voronoy 所定義的空間分割圖,在許多領域都有廣泛的應用。
Continue reading “Voronoi Diagram – A Case Study of Fortune’s Algorithm”
Classical Cryptography – 古典密碼學
古典密碼學是密碼學中的一個類型,其加密方式大部分都是使用替換式加密或移項式加密。
由於現代已經很少使用,故稱為古典密碼學。
加密有助於防止遊戲被破解的風險,但任何形式的加密都只會增加破解的難易度,並不能保證被破解的風險為零。
其中古典密碼學更是只要取得密鑰就能夠輕易破解的程度。
本篇文章會粗略的介紹密碼學中的基本概念,並分享古典密碼學中的幾種加密演算法。
關鍵字快搜:古典密碼學、凱撒密碼、仿射密碼、簡易替換密碼、乘法逆元
Maze Generator – 迷宮產生器
嘗試了在 Roguelike 遊戲中會看到的隨機迷宮生成
在每次玩家進入地城時會產生出不一樣的迷宮
使遊戲的變化性提升
A* Algorithm Line of Sight – 可視點優化
偶然和同好聊到 A* 時
得知了一種優化方法
可以得到更高效能的運算結果
先來看一張沒有用這種方法優化所算出來的路徑
可以看出雖然原來的方法所算出來的路徑的確正確
但卻又不能算是最佳路徑
原因在於一定要經過我們所定義出可行走的點
但如果將算出來的路徑再重製一次後會不會得到更好的結果呢?
可視點優化(Line of sight):
原理相當簡單,我們人在行走時如果遇到上面這種狀況
一定會以我們 “看” 到的最佳點前進
以上圖為例
我們在左下角的起點要向右上角的終點移動時
一定會直接以紅框為下一個目標點
因為我們可以 “看” 見紅框點
找到專案中的 AStar.cs
並新增 Function 如下
private static ArrayList LineOfSight( ArrayList path )
{
ArrayList list = new ArrayList();
list.Add( (Node)path[0] );
Node startNode = (Node)path[0];
Node nextNode;
int checkIndex = 1;
while( checkIndex < path.Count )
{
nextNode = ( (Node)path[checkIndex] );
if( !Physics.Linecast( startNode.position, nextNode.position ) )
{
checkIndex++;
}
else
{
if( checkIndex == path.Count - 1 )
{
list.Add( (Node)path[checkIndex - 1] );
list.Add( (Node)path[checkIndex] );
}
else
{
list.Add( (Node)path[checkIndex - 1] );
startNode = (Node)path[checkIndex - 1];
nextNode = (Node)path[checkIndex];
}
checkIndex++;
}
}
return list;
}
接著找到原來的 CalculatePath 方法
並修改成
private static ArrayList CalculatePath(Node node)
{
ArrayList list = new ArrayList();
while (node != null)
{
list.Add( node );
node = node.parent;
}
list.Reverse();
return LineOfSight( list );
}
這樣一來就可算出 Line of sight 優化過後的路徑
和沒有優化的路徑相比
是不是又更簡單明瞭
附上一張多障礙物模擬
A* Algorithm Eight Ways – 斜向方向優化
到目前為止
A* 的基本實作都已經結束了
但我們可以做一些簡單的優化來提高 A* 的運算結果
在上一篇中可以發現
雖然計算出來的路徑的確是最短路徑
但在移動方向上卻只限制於上下左右四個方向
考慮到優化演算的結果
我們在這邊加入斜向方向判定
首先一樣找到 NodeManager.cs 腳本
找到 GetNeighbours 方法
//Get the current node's neighbors
public void GetNeighbours( Node node, ArrayList neighbors )
{
Vector3 neighborPos = node.position;
int row = GetNodeRow( neighborPos );
int column = GetNodeColumn( neighborPos );
//Bottom
int leftNodeRow = row - 1;
int leftNodeColumn = column;
AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);
//Top
leftNodeRow = row + 1;
leftNodeColumn = column;
AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);
//Right
leftNodeRow = row;
leftNodeColumn = column + 1;
AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);
//Left
leftNodeRow = row;
leftNodeColumn = column - 1;
AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);
}
在原本的方法中
我們只有上下左右四個方向
所以在這邊將斜向方向也一併加入運算
//Get the current node's neighbors
public void GetNeighbours( Node node, ArrayList neighbors )
{
Vector3 neighborPos = node.position;
int row = GetNodeRow( neighborPos );
int column = GetNodeColumn( neighborPos );
//Bottom
int leftNodeRow = row - 1;
int leftNodeColumn = column;
AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);
//Top
leftNodeRow = row + 1;
leftNodeColumn = column;
AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);
//Right
leftNodeRow = row;
leftNodeColumn = column + 1;
AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);
//Left
leftNodeRow = row;
leftNodeColumn = column - 1;
AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);
//Bottom Right
leftNodeRow = row - 1;
leftNodeColumn = column + 1;
AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);
//Bottom Left
leftNodeRow = row - 1;
leftNodeColumn = column - 1;
AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);
//Top Right
leftNodeRow = row + 1;
leftNodeColumn = column + 1;
AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);
//Top Left
leftNodeRow = row + 1;
leftNodeColumn = column - 1;
AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);
}
再次執行 Unity
結果如下
加入斜向移動後果然移動距離又更短了
最後附上多個障礙物的模擬
A* Algorithm Obstacle Detection – 障礙物判定
在上一篇中我們已經完成了 A* 尋路的功能了
但是缺少了障礙物判定的方法
如果有認真看每一篇教學的讀者會發現
在一開始定義 Node 時,有一個 isObstacle 參數用來判斷該 Node 是不是障礙物
在這一篇我們就是要將它實現出來
找到 NodeManager.cs 腳本
找到其中的 CreateNode 方法
void CreateNodes()
{
nodes = new Node[numOfColumns, numOfRows];
for (int col = 0; col < numOfColumns; col++)
{
for (int row = 0; row < numOfRows; row++)
{
Vector3 cellPos = GetNodePosition(col, row);
Node node = new Node(cellPos);
nodes[col, row] = node;
}
}
}
將它改寫成
void CreateNodes()
{
nodes = new Node[numOfColumns, numOfRows];
for (int col = 0; col < numOfColumns; col++)
{
for (int row = 0; row < numOfRows; row++)
{
Vector3 cellPos = GetNodePosition(col, row);
Node node = new Node(cellPos);
nodes[col, row] = node;
//Obstacle Update
cellPos -= new Vector3( 0, 100, 0 );
RaycastHit hit;
Ray ray = new Ray( cellPos, new Vector3( 0, 1, 0 ) );
if( Physics.Raycast( ray , out hit, 1000 ) )
{
if( hit.transform.tag == "Obstacle" )
nodes[col, row].MarkAsObstacle();
}
}
}
}
接著在場景中加入任一包含 Collider 的物件
並將其 Tag 設定為 Obstacle
執行 Unity
得到以下成果
A* Algorithm Achievement – 演算法實作成果
終於到了最後一步
可以看到辛苦理解後的實現成果了
新增一個 C# 腳本命名為 TestAStar.cs
using UnityEngine;
using System.Collections;
public class TestAStar : MonoBehaviour
{
//The start and end gameobject
public GameObject startCube, endCube;
private Transform startTransform, endTransform;
private Node startNode;
private Node endNode;
public ArrayList pathArray;
private float elapsedTime = 0.0f;
//Interval time between pathfinding
public float intervalTime = 1.0f;
void Start ()
{
startTransform = startCube.transform;
endTransform = endCube.transform;
pathArray = new ArrayList();
FindPath();
}
void Update ()
{
elapsedTime += Time.deltaTime;
if (elapsedTime >= intervalTime)
{
elapsedTime = 0.0f;
FindPath();
}
}
void FindPath()
{
//Get start node with position
startNode = new Node( NodeManager.instance.GetNodeCenter( startTransform.position ) );
//Get end node with position
endNode = new Node( NodeManager.instance.GetNodeCenter( endTransform.position ) );
pathArray = AStar.FindPath(startNode, endNode);
}
//Display the a-star path finding line
void OnDrawGizmos()
{
if (pathArray == null)
return;
if (pathArray.Count > 0)
{
for( int cnt = 0; cnt < pathArray.Count; cnt++ )
{
if( cnt <= pathArray.Count - 2 )
Debug.DrawLine( ( (Node)pathArray[cnt] ).position, ( (Node)pathArray[cnt + 1] ).position, Color.green);
}
}
}
}
這個腳本主要是用來呼叫前面幾篇寫好的方法
直接來看怎麼使用 TestAStar.cs
在場景中新增一空物件取名為 AStar
並新增兩個 Cube 分別取名為 Start 以及 End 當作起始點與終點
並將兩個 Cube 分別拉近 AStar 欄位中
執行開始則會看到實作結果
到此 A* 演算法實作已經完成了
A* Algorithm Implement – 虛擬程式碼實例化
A* 虛擬程式碼實例化
看到這步的人可能還是會覺得一頭霧水
怎麼前面已經三篇了可是都還沒有看到 A* 的實作成果
但這篇看完後會突然發現概念都連接起來了 ( 應該吧…..
首先希望各位回想一下最一開始的 A* 演算法虛擬碼
這篇將會以那個虛擬碼來處理 A* 演算法運算
A* Algorithm Introduction – 演算法簡介
新增一個C#腳本命名為AStar.cs
using UnityEngine;
using System.Collections;
public class AStar
{
public static NodeSort closedList, openList;
private static float HeuristicEstimateCost(Node curNode, Node goalNode)
{
Vector3 vectorCost = goalNode.position - curNode.position;
return vectorCost.magnitude;
}
public static ArrayList FindPath( Node start, Node goal )
{
openList = new NodeSort();
openList.Push(start);
start.G_Cost = 0.0f;
start.H_Cost = HeuristicEstimateCost(start, goal);
closedList = new NodeSort();
Node node = null;
while (openList.Length != 0)
{
node = openList.First();
//Push the current node to the closed list
closedList.Push(node);
//and remove it from openList
openList.Remove(node);
//Check if the current node is the goal node
if ( node.position == goal.position )
{
return CalculatePath(node);
}
//Create an ArrayList to store the neighboring nodes
ArrayList neighbours = new ArrayList();
NodeManager.instance.GetNeighbours(node, neighbours);
Node neighbourNode;
for ( int i = 0; i < neighbours.Count; i++ )
{
neighbourNode = (Node)neighbours[i];
if ( !closedList.Contains( neighbourNode ) )
{
float cost;
float totalCost;
float neighbourNodeEstCost;
if ( !openList.Contains( neighbourNode ) )
{
//G
cost = HeuristicEstimateCost( node, neighbourNode );
totalCost = node.G_Cost + cost;
//H
neighbourNodeEstCost = HeuristicEstimateCost( neighbourNode, goal );
neighbourNode.G_Cost = totalCost;
neighbourNode.parent = node;
neighbourNode.H_Cost = neighbourNodeEstCost;
openList.Push(neighbourNode);
}
else
{
cost = HeuristicEstimateCost( node, neighbourNode );
totalCost = node.G_Cost + cost;
if( neighbourNode.G_Cost > totalCost )
{
neighbourNode.G_Cost = totalCost;
neighbourNode.parent = node;
}
}
}
}
}
if ( node.position != goal.position )
{
Debug.LogError("Goal Not Found");
return null;
}
return CalculatePath(node);
}
private static ArrayList CalculatePath(Node node)
{
ArrayList list = new ArrayList();
while (node != null)
{
list.Add( node );
node = node.parent;
}
list.Reverse();
return list;
}
}
6
一開始先分別定義 openList 以及 closeList
8~13 HeuristicEstimateCost
用來計算兩個節點間的移動距離
15~96 FindPath
這段是 A* 演算法實作中最複雜的一部分
可以將虛擬碼與實作互相對照
在演算初期先將 openList 及 closeList 初始化
並將 StartNode 的 G_Cost 及 H_Cost 也初始化
( 小提醒:G_Cost為實際移動距離、H_Cost為到終點的預估距離 )
接著將 StartNode 加入 openList 裡面
開始進行 A* 演算法中的核心計算
接下來都是將虛擬碼轉換為實際code的結果
需要注意的是取得 neighbors 用到了上一篇中的 GetNeighbours 方法
下一步則是判斷 neighbors 是否在 openList 裡
如果沒有再 openList 裡代表該節點沒有被運算過
所以需要將其 cost 算出來後加入 openList 裡
如果 neighbors 已經在 openList 裡
代表該節點已經運算過
但若這次運算的 cost 小於之前計算過的結果
則將它更新
如此一來,當程式運行到找到終點時
則會呼叫 CalculatePath 方法
98~110 CalculatePath
獲得終點 Node 後
即可逐一的從終點 Node 回推回起點 Node
最後將該陣列反轉後
就可以取得 A* 移動路徑