COC(Clash of Clans) is a freemium mobile MMO strategy video game developed and published by Supercell.The game was released for iOS platforms on 2 August, 2012,[1] and on Google Play for Android on 7 October, 2013 (from wiki)
The answer is 2D game,actually we should call 2.5D. 第一眼看到COC里面的所有动画人物给人的感觉都是3D的,但后来知道了Isometric Tileset Engine的概念。
Isometric Tileset Engine
What is Isometric Tileset Engine?
(斜视角游戏的地图渲染) (Isometric Tiles Introduction) 结合上述文章,我们可以知道,Isometric Tileset Engine主要是通过美术制作出Isometric Projection(we angle our camera along two axes (swing the camera 45 degrees to one side, then 30 degrees down))的2D图片来实现游戏的3D效果(2.5D)。
What does game with isometric projection look like?
典型的Isometric Projection游戏有: Age of Empires Diablo 2
UI的Render Mode主要分为三种 2.1 Screen Space – Overlay(Rendered on top of the secene) 2.2 Screen Space – Camera(有距离感的UI,受摄像机设置影响) 2.3 World Space(3D UI,有深度概念,会被3D物体遮挡)
voidStart() { mBBulletsList = new List<GameObject>(); mSBulletsList = new List<GameObject>();
for (int i = 0; i < mBBulletPoolAmount; i++) { GameObject bbulletobj = Instantiate(mBuildingBullet) as GameObject; bbulletobj.SetActive(false); mBBulletsList.Add(bbulletobj); }
for (int j = 0; j < mSBulletPoolAmount; j++) { GameObject sbulletobj = Instantiate(mSoldierBullet) as GameObject; sbulletobj.SetActive(false); mSBulletsList.Add(sbulletobj); } }
public GameObject GetBuildingBulletObject() { for (int i = 0; i < mBBulletsList.Count; i++) { if (!mBBulletsList[i].activeInHierarchy) { mBBulletsList[i].SetActive(true); return mBBulletsList[i]; } }
if (mWillGrow) { GameObject bbulletobj = Instantiate(mBuildingBullet) as GameObject; mBBulletsList.Add(bbulletobj); return bbulletobj; }
returnnull; }
public GameObject GetSoldierBulletObject() { for (int i = 0; i < mSBulletsList.Count; i++) { if (!mSBulletsList[i].activeInHierarchy) { mSBulletsList[i].SetActive(true); return mSBulletsList[i]; } }
if (mWillGrow) { GameObject sbulletobj = Instantiate(mSoldierBullet) as GameObject; mSBulletsList.Add(sbulletobj); return sbulletobj; }
[Serializable] publicclassSoldier : MonoBehaviour, GameObjectType { public SoldierState SCurrentState { set { if (mSCurrentState != null) { mSCurrentState.ExitState(); } mSCurrentState = value; mSCurrentState.EnterState(); } } [HideInInspector] private SoldierState mSCurrentState;
[HideInInspector] public SoldierAttackState mSAttackState;
[HideInInspector] public SoldierDeadState mSDeadState;
[HideInInspector] public SoldierMoveState mSMoveState;
......
publicvirtualvoidAwake() { mSAttackState = new SoldierAttackState(this);
mSMoveState = new SoldierMoveState(this);
mSDeadState = new SoldierDeadState(this);
...... }
publicvirtualvoidUpdate() { if (gameObject) { mSCurrentState.UpdateState(); } }
...... }
Decision Trees(决策树 – 用于简单的AI(Decision making)) Decision Trees主要用于AI体做决策,通过对已知数据的分析判断,根据Decision Tree抉择出最终的决定(即AI行为)。(项目里我主要使用FSM而非Decision Trees) 下图来源:《Artificial Intelligence for Game》 – Ian Millington
A Star(A Star是 Dijkstra(著名的最短路径算法)基础上通过一个启发因子来预估给定节点到目标节点的距离来使得路径节点搜索是向目标节点方向逼近不至于出现搜索大量无效节点的情况) (AI相关学习)
A Star
Searching
通过搜索,我发现网络上有现成的很完善的A Star的版本 A Star Pathfinding Project(Asset) 但通过使用后发现,里面所支持的Four,Six and Eight connections都不符合我的需求(每个点都和周围的八个点连通),所以最终放弃了A Star Pathfinding Project而决定自己实现自己的A Star Pathfinding
Create Myself A Star
A Star Preperation
结合Artificial-Inteligence-Study的学习,让我们了解下A Star里的一些基本概念和核心思想: A Star属于什么图? A Star里的图属于导航图(Navigation Graph),是基于开销的图搜索(cost-based graph searches)
Dijstra算法改进: A Star – 和Dijkstra算法的唯一区别是对搜索边界上的点的开销(GCost)的计算。因为Dijstra的搜索扩展方向是由GCost决定的,所以A Star算法通过给GCost添加一个启发因子(H)来确保搜索行进方向。 F的计算: F = G + H G是到达一个节点的累计开销, H是一个启发因子,它给出的是节点到目标节点的估计距离。
using UnityEngine; using System.Collections; using System.Collections.Generic; using System; using UnityEngine.Assertions;
publicclassSearchAStar { publicstruct PathInfo {
public List<int> PathToTarget { get { return mPathToTarget; } set { mPathToTarget = value; } } private List<int> mPathToTarget;
public List<Vector3> MovementPathToTarget { get { return mMovementPathToTarget; } set { mMovementPathToTarget = value; } } private List<Vector3> mMovementPathToTarget;
publicbool IsWallInPathToTarget { get { return mIsWallInPathToTarget; } set { mIsWallInPathToTarget = value; } } privatebool mIsWallInPathToTarget;
publicint WallInPathToTargetIndex { get { return mWallInPathToTargetIndex; } set { mWallInPathToTargetIndex = value; } } privateint mWallInPathToTargetIndex;
publicfloat CostToTarget { get { return mCostToTarget; } set { mCostToTarget = value; } } privatefloat mCostToTarget;
publicint ITarget { set { mITarget = value; } get { return mITarget; } } privateint mITarget;
publicint OriginalTarget { get { return mOriginalTarget; } set { mOriginalTarget = value; } } privateint mOriginalTarget;
publicint NodesSearched { get { return mNodesSearched; } set { mNodesSearched = value; } } privateint mNodesSearched;
publicint EdgesSearched { get { return mEdgesSearched; } set { mEdgesSearched = value; } } privateint mEdgesSearched;
//this list of edges is used to store any subtree returned from any of the graph algorithms /* public List<GraphEdge> SubTree { get { return mSubTree; } set { mSubTree = value; } } private List<GraphEdge> mSubTree; */ /* public PathInfo() { ResetPathInfo(); } */
public PathInfo DeepCopy() { PathInfo pi = (PathInfo)this.MemberwiseClone(); pi.PathToTarget = new List<int>(mPathToTarget); pi.MovementPathToTarget = new List<Vector3>(mMovementPathToTarget);
if (!mIsIgnoreWall) { //No matter the wall in path is jumpable or not, we should record it as useful information if (mGraph.Nodes[nd].IsWall /*&& !mGraph.Nodes[nd].IsJumpable*/) { mAStarPathInfo.IsWallInPathToTarget = true; mAStarPathInfo.WallInPathToTargetIndex = nd; } }
while (!mPQ.Empty()) { //Get lowest cost node from the queue int nextclosestnode = mPQ.Pop().Key;
mAStarPathInfo.NodesSearched++;
//move this node from the frontier to the spanning tree if (mSearchFrontier[nextclosestnode] != null && mSearchFrontier[nextclosestnode].IsValidEdge()) { mShortestPathTree[nextclosestnode] = mSearchFrontier[nextclosestnode]; } //If the target has been found exit if (nextclosestnode == mITarget) { return; }
//Now to test all the edges attached to this node List<GraphEdge> edgelist = mGraph.EdgesList[nextclosestnode]; GraphEdge edge; for (int i = 0; i < edgelist.Count; i++) { edge = edgelist[i]; //calculate the heuristic cost from this node to the target (H) float hcost = Heuristic_Euclid.Calculate(mGraph, mITarget, edge.To) * mHCostPercentage;
//calculate the 'real' cost to this node from the source (G) float gcost = mGCosts[nextclosestnode] + edge.Cost;
//if the node has not been added to the frontier, add it and update the G and F costs if (mSearchFrontier[edge.To] != null && !mSearchFrontier[edge.To].IsValidEdge()) { mFCosts[edge.To].Value = gcost + hcost; mGCosts[edge.To] = gcost;
mPQ.Push(mFCosts[edge.To]);
mSearchFrontier[edge.To] = edge;
mAStarPathInfo.EdgesSearched++;
if (mBDrawExplorePath) { Debug.DrawLine(mGraph.Nodes[edge.From].Position, mGraph.Nodes[edge.To].Position, Color.yellow, mExplorePathRemainTime); } }
//if this node is already on the frontier but the cost to get here //is cheaper than has been found previously, update the node //cost and frontier accordingly elseif (gcost < mGCosts[edge.To]) { mFCosts[edge.To].Value = gcost + hcost; mGCosts[edge.To] = gcost;
//Due to some node's f cost has been changed //we should reoder the priority queue to make sure we pop up the lowest fcost node first //compare the fcost will make sure we search the path in the right direction //h cost is the key to search in the right direction mPQ.ChangePriority(edge.To);
while (!mPQ.Empty()) { //Get lowest cost node from the queue int nextclosestnode = mPQ.Pop().Key;
mAStarPathInfo.NodesSearched++;
//move this node from the frontier to the spanning tree if (mSearchFrontier[nextclosestnode] != null && mSearchFrontier[nextclosestnode].IsValidEdge()) { mShortestPathTree[nextclosestnode] = mSearchFrontier[nextclosestnode]; }
//Now to test all the edges attached to this node List<GraphEdge> edgelist = mGraph.EdgesList[nextclosestnode]; GraphEdge edge; for (int i = 0; i < edgelist.Count; i++) { edge = edgelist[i]; //calculate the heuristic cost from this node to the target (H) float hcost = Heuristic_Euclid.Calculate(mGraph, mITarget, edge.To) * mHCostPercentage;
//calculate the 'real' cost to this node from the source (G) float gcost = mGCosts[nextclosestnode] + edge.Cost;
//if the node has not been added to the frontier, add it and update the G and F costs if (mSearchFrontier[edge.To] != null && !mSearchFrontier[edge.To].IsValidEdge()) { mFCosts[edge.To].Value = gcost + hcost; mGCosts[edge.To] = gcost;
mPQ.Push(mFCosts[edge.To]);
mSearchFrontier[edge.To] = edge;
mAStarPathInfo.EdgesSearched++;
if (mBDrawExplorePath) { Debug.DrawLine(mGraph.Nodes[edge.From].Position, mGraph.Nodes[edge.To].Position, Color.yellow, mExplorePathRemainTime); } }
//if this node is already on the frontier but the cost to get here //is cheaper than has been found previously, update the node //cost and frontier accordingly elseif (gcost < mGCosts[edge.To]) { mFCosts[edge.To].Value = gcost + hcost; mGCosts[edge.To] = gcost;
//Due to some node's f cost has been changed //we should reoder the priority queue to make sure we pop up the lowest fcost node first //compare the fcost will make sure we search the path in the right direction //h cost is the key to search in the right direction mPQ.ChangePriority(edge.To);
mSearchFrontier[edge.To] = edge;
mAStarPathInfo.EdgesSearched++; } } } }
//The A* search algorithm with strickdistance with wall consideration privatevoidSearch(float strickdistance, bool isignorewall) { float currentnodetotargetdistance = Mathf.Infinity;
mPQ.Clear();
mPQ.Push(mFCosts[mISource]);
//mSearchFrontier [mISource] = new GraphEdge (mISource, mISource, 0.0f); mSearchFrontier[mISource].From = mISource; mSearchFrontier[mISource].To = mISource; mSearchFrontier[mISource].Cost = 0.0f; GraphEdge edge = new GraphEdge(); int nextclosestnode = -1;
while (!mPQ.Empty()) { //Get lowest cost node from the queue nextclosestnode = mPQ.Pop().Key;
mAStarPathInfo.NodesSearched++;
//move this node from the frontier to the spanning tree if (mSearchFrontier[nextclosestnode] != null && mSearchFrontier[nextclosestnode].IsValidEdge()) { mShortestPathTree[nextclosestnode] = mSearchFrontier[nextclosestnode]; }
//Now to test all the edges attached to this node List<GraphEdge> edgelist = mGraph.EdgesList[nextclosestnode]; for (int i = 0; i < edgelist.Count; i++) { //Avoid pass refrence edge.Reset(); edge.From = edgelist[i].From; edge.To = edgelist[i].To; edge.Cost = edgelist[i].Cost; //calculate the heuristic cost from this node to the target (H) float hcost = Heuristic_Euclid.Calculate(mGraph, mITarget, edge.To) * mHCostPercentage;
//calculate the 'real' cost to this node from the source (G) float gcost = 0.0f; if (isignorewall) { gcost = mGCosts[nextclosestnode] + edge.Cost;
if (mGraph.Nodes[edge.From].IsWall) { gcost -= mGraph.Nodes[edge.From].Weight; } if (mGraph.Nodes[edge.To].IsWall) { gcost -= mGraph.Nodes[edge.To].Weight; } } else { gcost = mGCosts[nextclosestnode] + edge.Cost; if (mGraph.Nodes[edge.From].IsJumpable) { gcost -= mGraph.Nodes[edge.From].Weight; } if (mGraph.Nodes[edge.To].IsJumpable) { gcost -= mGraph.Nodes[edge.To].Weight; } }
//if the node has not been added to the frontier, add it and update the G and F costs if (mSearchFrontier[edge.To] != null && !mSearchFrontier[edge.To].IsValidEdge()) { mFCosts[edge.To].Value = gcost + hcost; mGCosts[edge.To] = gcost;
mPQ.Push(mFCosts[edge.To]);
mSearchFrontier[edge.To].ValueCopy(edge);
mAStarPathInfo.EdgesSearched++;
if (mBDrawExplorePath) { Debug.DrawLine(mGraph.Nodes[edge.From].Position, mGraph.Nodes[edge.To].Position, Color.yellow, mExplorePathRemainTime); } }
//if this node is already on the frontier but the cost to get here //is cheaper than has been found previously, update the node //cost and frontier accordingly elseif (gcost < mGCosts[edge.To]) { mFCosts[edge.To].Value = gcost + hcost; mGCosts[edge.To] = gcost;
//Due to some node's f cost has been changed //we should reoder the priority queue to make sure we pop up the lowest fcost node first //compare the fcost will make sure we search the path in the right direction //h cost is the key to search in the right direction mPQ.ChangePriority(edge.To);