需求
(1)隨機(jī)生成游戲界面集漾;
(2)選擇兩個(gè)相同圖案的圖片,并以不超過(guò)兩個(gè)轉(zhuǎn)彎的連線將它們連接起來(lái)砸脊,便可以消除這對(duì)圖片具篇,每一局用戶需要消除所有的圖片;
(3)當(dāng)沒(méi)有可以消除的圖案時(shí)凌埂,可以使用重置功能驱显;
(4)選擇提示功能時(shí),游戲自動(dòng)消除一對(duì)相同的圖片瞳抓;
(5)實(shí)現(xiàn)聯(lián)網(wǎng)對(duì)戰(zhàn)
可能存在的問(wèn)題
生成界面存在的問(wèn)題
- 界面中棋子必須成對(duì)出現(xiàn)
- 初始界面必須保證有解
連接時(shí)存在的問(wèn)題
- 折線次數(shù)的判斷
- 配對(duì)圖片的判斷
- 連接路徑上保證沒(méi)有阻礙
聯(lián)網(wǎng)同步問(wèn)題
- 同步何種數(shù)據(jù)來(lái)體現(xiàn)對(duì)戰(zhàn)
構(gòu)思
- 成對(duì)問(wèn)題: 可以準(zhǔn)備兩個(gè)數(shù)組分別存放匹配對(duì)埃疫,每次各取一個(gè)賦給sprite renderer。
- 初始界面保證有解問(wèn)題: 可以先隨機(jī)生成一個(gè)棋盤(pán)挨下,再用自動(dòng)匹配算法進(jìn)行匹配,如果無(wú)解則重新生成棋盤(pán)脐湾。
- 折線次數(shù)問(wèn)題: 可以用
- 判斷一對(duì)棋子是否可以消除時(shí)臭笆,即最小折線次數(shù)小于等于2,可以用廣度優(yōu)先搜索;在自動(dòng)求解時(shí)愁铺,可以用深度優(yōu)先搜索鹰霍。
- 圖片選項(xiàng)配對(duì)問(wèn)題:可以通過(guò)判斷名稱是否匹配。
- 連線路徑上無(wú)障礙:確保路徑上的sprite renderer為空即可茵乱。
- 簡(jiǎn)單實(shí)現(xiàn)對(duì)戰(zhàn)茂洒,只要實(shí)現(xiàn)一個(gè)進(jìn)度條,同步對(duì)方的完成度即可瓶竭。
棋盤(pán)生成算法分析
匹配算法分析
顯然這個(gè)類型的游戲督勺,判斷折線次數(shù)內(nèi)匹配的算法時(shí)最核心的,其他都問(wèn)題不大斤贰。下面我們來(lái)仔細(xì)分解一下這個(gè)問(wèn)題智哀。
$$方法一:分類判斷法
首先我們用分類的思想類分析這個(gè)問(wèn)題,這樣更有助于我們深入理解∮校現(xiàn)在要求是折線數(shù)少于3次完成連接瓷叫,那么我們很容易劃分出三種情況:0折連,1折連送巡,2折連摹菠。
0折連情況(即直連)
這種情況兩個(gè)選中目標(biāo)的橫坐標(biāo)或者縱坐標(biāo)是一樣的。只要再判斷連線上沒(méi)有阻礙即可骗爆。
1折連情況
此情況的兩個(gè)目標(biāo)橫縱坐標(biāo)都不能相等次氨,相當(dāng)于矩形上的對(duì)角點(diǎn)。所以要判斷兩個(gè)點(diǎn)是否滿足1折連就只要確定另外兩個(gè)對(duì)角點(diǎn)上的任一點(diǎn)同時(shí)對(duì)A和B滿足0折連的情況即可淮腾。
2折連情況
向四周查找空格區(qū)域糟需,直到“撞墻”,然后記錄點(diǎn)C谷朝。再看點(diǎn)C是否滿足與B 1折連情況洲押。
$$方法二:廣度優(yōu)先算法
把所有滿足情況的點(diǎn)都放在一個(gè)集合中,然后只要判斷B在不在集合中就行了圆凰。具體是這樣的:
- 準(zhǔn)備兩個(gè)數(shù)組杈帐,組A是存放滿足條件的點(diǎn);組B是存放暫時(shí)滿足情況的點(diǎn)专钉。
- 先把A放到組A挑童。
- 然后把所有與組A 0折連的點(diǎn)放到組B,然后把組B拷貝到組A再清空跃须。折數(shù)+1
- 重復(fù)3站叼,直到B在組A中或者折數(shù)超過(guò)2。
$$方法三:A*尋路算法
雖然A*是解決靜態(tài)網(wǎng)路尋路問(wèn)題中求解最短路徑的算法菇民,但是其實(shí)在這里也可以有很好的應(yīng)用尽楔。(不過(guò)我還沒(méi)實(shí)現(xiàn)過(guò)投储,大家可以試試,可以在評(píng)論區(qū)甩代碼鏈接分享下)
代碼參考:
1.棋盤(pán)初始化(主題實(shí)現(xiàn))
//初始化布局
public void InitializeLayout()
{
float offset = sample.bounds.size.x;
layout = new GameObject[Height, Width];
//初始化空棋盤(pán)
for (int i = 0; i < Height; i++)
for (int j = 0; j < Width; j++)
{
GameObject tile = Instantiate(emptytile, new Vector3(parent.position.x + (offset * j), parent.position.y + (offset * i), 0), Quaternion.identity, parent);
tile.name = (i * 10 + j).ToString();
layout[i, j] = tile;
}
//矯正位置偏差
if (SceneManager.GetActiveScene().name.Equals("Single"))
{
if (currentLevel <= 5)
parent.position = parentPOS[currentLevel];
else
parent.position = parentPOS[5];
}
//給棋盤(pán)放上棋子
GiveSprites();
}
void GiveSprites()
{
//實(shí)現(xiàn)圖片的成對(duì)放置:遍歷棋盤(pán)阔馋,當(dāng)前tile沒(méi)有精靈則貼上精靈并且成對(duì)的在棋盤(pán)隨機(jī)位置放上對(duì)應(yīng)精靈
for (int i = 1; i < Height - 1; i++)
for (int j = 1; j < Width - 1; j++)
{
if (layout[i, j].GetComponent<SpriteRenderer>().sprite == null)
{
layout[i, j].GetComponent<SpriteRenderer>().sprite = GetRandomSprite();
GiveSpriteInRandomXY();
}
}
}
//棋子圖片隨機(jī)付給沒(méi)貼圖的棋子玛荞,防止擺放太整齊
void GiveSpriteInRandomXY()
{
int x = UnityEngine.Random.Range(100, (Height - 1) * 100);
x /= 100;
int y = UnityEngine.Random.Range(100, (Width - 1) * 100);
y /= 100;
//隨機(jī)定位一個(gè)位置
Sprite thisSprite = layout[x, y].GetComponent<SpriteRenderer>().sprite;
//檢查是否有空
if (thisSprite == null)
layout[x, y].GetComponent<SpriteRenderer>().sprite = GetRandomSprite();
else if (thisSprite != null)
GiveSpriteInRandomXY();
}
//隨機(jī)選取棋子圖片
Sprite GetRandomSprite()
{
Sprite s = null;
int index = -1;
//在s1中獲取一張精靈
if (currentName == "")
{
int maxrange = s1.Length * 100;
index = UnityEngine.Random.Range(0, maxrange);
index /= 100;
s = s1[index];
currentName = s.name;
}
//在s2中獲取一張精靈
else
{
string name1 = currentName;
for (int i = 0; i < s1.Length; i++)
{
if (s1[i].name.Equals(name1))
{
s = s1[i];
currentName = "";
break;
}
}
}
return s;
}
//重新開(kāi)始
public void Play()
{
if (CurrentLevel <= 5)
{ //根據(jù)當(dāng)前關(guān)卡顯示棋盤(pán)大小
Width = ReadLevelRowCol()[0];
Height = ReadLevelRowCol()[1];
}
if (CurrentLevel > 5)
{
Width = 7;
Height = 8;
}
GetComponent<GameManager>().PlayAgain();
}
//打亂棋盤(pán)
public void ConfuseLayout()
{
/*當(dāng)棋盤(pán)無(wú)解時(shí)打亂棋盤(pán)*/
remaining.Clear();
//1.先把剩余sprite收走,存入數(shù)組
for (int i = 1; i < Height - 1; i++)
for (int j = 1; j < Width - 1; j++)
if (layout[i, j].GetComponent<SpriteRenderer>().sprite != null)
{
remaining.Add(layout[i, j].GetComponent<SpriteRenderer>().sprite);
layout[i, j].GetComponent<SpriteRenderer>().sprite = null;
}
//2.然后把它分發(fā)到棋盤(pán)上
ReGive();
}
private void ReGive()
{
int x = UnityEngine.Random.Range(100, (Height - 1) * 100);
x /= 100;
int y = UnityEngine.Random.Range(100, (Width - 1) * 100);
y /= 100;
while (remaining.Count > 0)
{
if (layout[x, y].GetComponent<SpriteRenderer>().sprite == null)
{
layout[x, y].GetComponent<SpriteRenderer>().sprite = remaining[0];
remaining.RemoveAt(0);
}
else
ReGive();
}
}
2.判定(主體實(shí)現(xiàn))
private void Update()
{
SelectDetact();
}
void SelectDetact()
{
if (Input.GetMouseButtonDown(0))
{
position = Input.mousePosition;
Ray ray = Camera.main.ScreenPointToRay(position);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
SpriteRenderer hited = hit.transform.GetComponent<SpriteRenderer>();
//如果第一次點(diǎn)擊則記錄名字
if (firstSelect == null)
{
firstSelect = hited;
if (firstSelect.sprite == null)
firstSelect = null;
else //顯示年代
{
firstSelect.gameObject.GetComponent<Tile>().ShowFrame(true);
//記錄原先的圖呕寝,如果選擇失敗要回退
firstSprite = firstSelect.sprite;
firstSelect.sprite = layout.ShowDynasty(firstSelect.sprite.name);
}
}
else
{
nextSelect = hited;
//排除無(wú)效選擇
if (firstSelect.sprite == null || nextSelect.sprite == null || firstSelect.transform.position==nextSelect.transform.position )
{
//選擇置空
firstSelect.gameObject.GetComponent<Tile>().ShowFrame(false);
//圖片回退
firstSelect.sprite = firstSprite;
//移除選擇記錄
firstSelect = null;
nextSelect = null;
return;
}
//記錄
nextSprite = nextSelect.sprite;
nextSelect.sprite = layout.ShowDynasty(nextSelect.sprite.name);
nextSelect.gameObject.GetComponent<Tile>().ShowFrame(true);
//判斷選擇對(duì)是否匹配
string name1 = firstSelect.sprite.name;
string name2 = nextSelect.sprite.name;
if (name1.Equals(name2) && JudgeConnection(firstSelect, nextSelect))
//消除
StartCoroutine(MatchSuccess());
else
StartCoroutine(MatchFail());
}
}
}
}
bool JudgeConnection(SpriteRenderer a, SpriteRenderer b)
{
int count = 0;
lst1.Add(a);
while (!lst1.Contains(b) && count < 3)
{
foreach (SpriteRenderer s in lst1)
{
//0折連且路空則加入lst2
lst2.AddRange(DirectConnect(s));
}
//lst2 -> lst1 并清空
for (int i = 0; i < lst2.Count; i++)
if (!lst1.Contains(lst2[i]))
lst1.Add(lst2[i]);
lst2.Clear();
count++;
}
return lst1.Contains(b) ? true : false;
}
List<SpriteRenderer> DirectConnect(SpriteRenderer a)
{
List<SpriteRenderer> temp = new List<SpriteRenderer>();
temp.AddRange(FindDirect(Vector2.up, a));
temp.AddRange(FindDirect(Vector2.left, a));
temp.AddRange(FindDirect(Vector2.right, a));
temp.AddRange(FindDirect(Vector2.down, a));
return temp;
}
List<SpriteRenderer> FindDirect(Vector2 dirc, SpriteRenderer a)
{
SpriteRenderer start = a;
List<SpriteRenderer> cast = new List<SpriteRenderer>();
RaycastHit hit;
SpriteRenderer sr;
while (Physics.Raycast(start.transform.position, dirc, out hit))
{
sr = hit.transform.GetComponent<SpriteRenderer>();
if (hit.transform.GetComponent<SpriteRenderer>().Equals(nextSelect))
{
cast.Add(sr);
break;
}
else
{
if (JudgeRoad(hit.transform.GetComponent<SpriteRenderer>().sprite))
{
cast.Add(sr);
start = sr;
}
else
break;
}
}
////把所有在A,B形成矩形范圍內(nèi)的點(diǎn)都加入waypoints以便尋路
//foreach (SpriteRenderer s in cast)
//{
// if (InArea(s, firstSelect, nextSelect))
// waypoints.Add(s.transform.position);
//}
return cast;
}
bool JudgeRoad(Sprite s)
{
if (s == null)
return true;
else
return false;
}
/// <summary>
/// 匹配成功動(dòng)動(dòng)作
/// </summary>
IEnumerator MatchSuccess()
{
//匹配成功音效
SoundManager.instance.SetClip("success");
yield return new WaitForSeconds(0.2f);
if (remainingCount > 2)
{
remainingCount -= 2;
if (SceneManager.GetActiveScene().name.Equals("Online"))
owningSlider.GetComponent<Progress>().CmdMinuse();
}
//結(jié)算界面
else
{
if (SceneManager.GetActiveScene().name.Equals("Online"))
owningSlider.GetComponent<Progress>().CmdOver();
else
{
OverPanel("win");
if (SceneManager.GetActiveScene().name.Equals("Single"))
gameObject.GetComponent<LayoutInitailization>().CurrentLevel++;
}
}
firstSelect.GetComponent<ParticleSystem>().Play();
nextSelect.GetComponent<ParticleSystem>().Play();
firstSelect.sprite = null;
nextSelect.sprite = null;
lst1.Clear();
OverAction();
}
IEnumerator MatchFail()
{
//匹配失敗音效
SoundManager.instance.SetClip("fail");
yield return new WaitForSeconds(0.2f);
//還原圖片
firstSelect.sprite = firstSprite;
nextSelect.sprite = nextSprite;
lst1.Clear();
OverAction();
}
void OverAction()
{
//選擇置空
firstSelect.gameObject.GetComponent<Tile>().ShowFrame(false);
nextSelect.gameObject.GetComponent<Tile>().ShowFrame(false);
firstSelect = null;
nextSelect = null;
}
public void OverPanel(string v)
{
restartPanel.SetActive(true);
playingButton.SetActive(false);
image.sprite = v.Equals("fail") ? fail : win;
SoundManager.instance.SetClip(v.Equals("fail") ? "over" : "win");
if (changebtn[0] != null)
{
changebtn[0].SetActive(v.Equals("fail"));
changebtn[1].SetActive(v.Equals("win"));
timenow = 10000;
}
}
public void PlayAgain()
{
//更新行列值
countRecord = (GetComponent<LayoutInitailization>().Height - 2) * (GetComponent<LayoutInitailization>().Width - 2);
remainingCount = countRecord;
//更新UI
restartPanel.SetActive(false);
playingButton.SetActive(true);
timenow = 51;
time.text = "51";
//開(kāi)始倒計(jì)時(shí)
StopCoroutine("TimeCount");
StartCoroutine("TimeCount");
pa.DestroyChildren();
//布局
layout.InitializeLayout();
}
}
【https://github.com/jewis123/AntiqueCoin/blob/master/LinkUpScriptExample】