스테이지 생성방식 2
[이전 글 확인] https://cheondi.github.io/mess/2021/12/14/mess6.html
4.단어 총 갯수 확인 및 test
단어의 갯수와 test를 통해 추가적으로 단어가 이상하게 겹쳐지는 부분이 없는지 확인한 이후 문제가 생길 경우 게임을 재생성하는 방식이다.
if(!test() || wordLists.Count() < index*2-10)
{
Debug.Log("retry");
SetWordPosition();
return;
}
public bool test()
{
for (int i = 0; i < index; i++) //array의 모든 칸을 채운 후, -1인 단어가 들어갈 수 없는 칸을 0으로 돌려놓는다.(편함)
{
for (int j = 0; j < index; j++)
{
if (array[i, j] == "-1")
array[i, j] = "0";
}
}
for (int i = 0; i < index; i++)
{
for (int j = 0; j < index; j++)
{
//array의 모든 칸을 돌면서 확인한다.(2음절이 되버린 단어를 찾는다) 0110의 가로방향 세로방향을 모두 찾는다.
if (i + 3 < index && array[i, j] == "0" && array[i + 1, j] == "1" && array[i + 2, j] == "1" && array[i + 3, j] == "0")
{
Debug.Log("retry");
return false;
}
if (j + 3 < index && array[i, j] == "0" && array[i, j + 1] == "1" && array[i, j + 2] == "1" && array[i, j + 3] == "0")
{
Debug.Log("retry");
return false;
}
if (i == 0 && array[i, j] == "1" && array[i + 1, j] == "1" && array[i + 2, j] == "0")
{
Debug.Log("retry");
return false;
}
if (j == 0 && array[i, j] == "1" && array[i, j + 1] == "1" && array[i, j + 2] == "0")
{
Debug.Log("retry");
return false;
}
if (i == index - 1 && array[i, j] == "1" && array[i - 1, j] == "1" && array[i - 2, j] == "0")
{
Debug.Log("retry");
return false;
}
if (j == index - 1 && array[i, j] == "1" && array[i, j - 1] == "1" && array[i, j - 2] == "0")
{
Debug.Log("retry");
return false;
}
}
}
//0110을 찾아서 나올 경우 false를 return하고 아닐 경우 true를 return한다.
Debug.Log("Test success");
return true;
}
예외 상황을 모두 처리 하였음에도 불구하고, 1000판 중 1판 정도씩 버그가 튀어나와 test함수를 만들어 실행한다.
5.단어 리스트 생성
단어는 뒤끝차트에 csv형식 파일을 업로드하여, 게임이 시작될 때 현재 다운로드되어 있는 파일과 비교하여 최신버전이 아닐 경우 새로이 다운로드한다. 뒤끝에 관한 사항은 뒤쪽 포스트에서 작성할 예정이고, 지금 포스트에서는 json파일로 저장되어 있는 단어 파일을 list를 옮기는 과정을 하려 한다.
public List<List<string>> word3 = new List<List<string>>();
public List<List<string>> word4 = new List<List<string>>();
public void SetJson()
{
JsonData chartJson = JsonMapper.ToObject( Backend.Chart.GetLocalChartData("word3") ); //서버에서 다운로드 받은 json파일
//3음절 단어 파일이다.
var rows = chartJson["rows"];
List<string> word3_0 = new List<string>();
List<string> word3_1 = new List<string>();
List<string> word3_2 = new List<string>();
List<string> word3_3 = new List<string>();
List<string> word3_4 = new List<string>();
//list 생성 방식은 3음절 단어 파일은 약 14000개의 단어로 이루어져 있다.
//단어를 찾을 때 만약 한번 확인할 때 14000개의 단어 파일을 한번에 루프문을 회전한다면 시간이 오래걸려 스테이지를 생성하는데 //오래 걸리기에 단어를 5개의 list를 통해 관리하게 된다.
for(int i =0;i<rows.Count;i++)
{
if(i%5==0)
word3_0.Add(rows[i]["name"]["S"].ToString());
else if(i%5==1)
word3_1.Add(rows[i]["name"]["S"].ToString());
else if(i%5==2)
word3_2.Add(rows[i]["name"]["S"].ToString());
else if(i%5==3)
word3_3.Add(rows[i]["name"]["S"].ToString());
else
word3_4.Add(rows[i]["name"]["S"].ToString());
}
JsonData chartJson2 = JsonMapper.ToObject( Backend.Chart.GetLocalChartData("word4") );
//위와 동일하다.
var rows2 = chartJson2["rows"];
List<string> word4_0 = new List<string>();
List<string> word4_1 = new List<string>();
List<string> word4_2 = new List<string>();
List<string> word4_3 = new List<string>();
List<string> word4_4 = new List<string>();
for(int i =0;i<rows2.Count;i++)
{
if(i%5==0)
word4_0.Add(rows2[i]["name"]["S"].ToString());
else if(i%5==1)
word4_1.Add(rows2[i]["name"]["S"].ToString());
else if(i%5==2)
word4_2.Add(rows2[i]["name"]["S"].ToString());
else if(i%5==3)
word4_3.Add(rows2[i]["name"]["S"].ToString());
else
word4_4.Add(rows2[i]["name"]["S"].ToString());
}
word3.Add(word3_0);
word3.Add(word3_1);
word3.Add(word3_2);
word3.Add(word3_3);
word3.Add(word3_4);
word4.Add(word4_0);
word4.Add(word4_1);
word4.Add(word4_2);
word4.Add(word4_3);
word4.Add(word4_4);
//List<list<string>> 형식인 list를 추가한다.
}
6.첫 단어 생성
public void SetFirstWord()
{
System.Random random =new System.Random();
if (wordLists[0].len == 3) // 첫 단어의 길이가 3일 경우
{
List<string> file = new List<string>();
file = word3[random.Next(0, 5)];
int index11 = random.Next(0, file.Count());
wordLists[0].word = file[index11].ToString();
}
else //첫 단어의 길이가 4일 경우
{
List<string> file = new List<string>();
file = word4[random.Next(0, 5)];
int index11 = random.Next(0, file.Count());
wordLists[0].word = file[index11].ToString();
}
Debug.Log(wordLists[0].word);
for (int i = 0; i < wordLists[0].len; i++)
{
if (wordLists[0].direction)
{
array[wordLists[0].x, wordLists[0].y + i] = wordLists[0].word[i].ToString();
//array에 단어를 표시한다.
}
else
{
array[wordLists[0].x + i, wordLists[0].y] = wordLists[0].word[i].ToString();
}
}
if (wordLists[1].len == 3) //다음 단어의 길이가 3일 경우
WordSet(1, false, random.Next(0, 5), 1);
else //다음 단어의 길이가 4일 경우
WordSet(1, false, random.Next(0, 5), 1); //다음 단어 생성 함수
}
첫 단어를 생성하는 함수는 위와 같다. 3음절인지 4음절인지를 판단하여 단어의 정보를 랜덤하게 찾아 입력한다.
7.다음 단어 생성
public int funcCnt; //findword 함수 회전 횟수(만약 함수가 200회를 넘어가게 된다면, 처음부터 다시 시작하게 된다)
//함수는 단어를 찾을 때까지 반복하기 때문에, 다른 단어가 파생할 수 없는 단어가 생성된 경우에 함수를 굉장히 많이 반복하게 된다.
//이 때 스테이지를 생성하는 시간이 많으면 10초 정도 걸릴 수 있으므로, 생성하였다.(스테이지 생성은 1~3초 정도 걸림)
public void WordSet(int index, bool flag, int filenum, int cnt)
//index는 wordlist에서의 단어의 순번,
//filenum은 word3 or word4의 list의 갯수이다.
//cnt 단어의 파일을 찾을 index이다.
{
if(funcCnt >200)
{
funcCnt = 0;
Debug.Log("break");
foreach(var row in wordLists)
{
row.word="";
row.words.Clear();
}
funcflag = false; //findword 함수가 200회 이상 작동하면 플래그가 올라가게 되어 스테이지를 다시 생성 시작한다.
return ;
}
System.Random random =new System.Random();
//초기 버전에서는 서버에서 차트를 받아오는 식이 아닌, 게임 리소스 안에 csv 파일을 저장하고 빌드를 하여 사용하였다.
//아무리 빌드를 해도, 안드로이드에서 테스트를 할 경우에 csv 파일을 찾을 수 없다고 하여, csvReader라고 구글에 떠도는 파일을
//사용한 것이 잘못된 것이라 판단하여, 특정 함수를 지우기 위해 UnityEngine.Random이 아닌 System.Random을 사용하게 되었다.
//이후 원인을 찾은 후에도 따로 작동하는데 문제가 없어 수정하진 않았다.
if (flag&&wordLists[index].words.Count() > 0)
//wordInfo에 words라는 list가 있는데, 이것은 단어에 적합한 단어들의 목록이다.
{
wordLists[index].word = wordLists[index].words[random.Next(0, wordLists[index].words.Count)];
wordLists[index].words.Remove(wordLists[index].word);
// Debug.Log(wordLists[index].word);
for (int i = 0; i < wordLists[index].len; i++)
//단어를 정했을 경우 array에 입력한다.
{
if (wordLists[index - 1].direction)
array[wordLists[index].x, wordLists[index].y + i] = wordLists[index].word[i].ToString();
else
array[wordLists[index].x + i, wordLists[index].y] = wordLists[index].word[i].ToString();
}
if (index == wordLists.Count - 1) //마지막 단어라면 메인 함수로 되돌아간다.
return;
else //아니라면 다음 단어를 실행한다.
{
WordSet(index + 1, false, random.Next(0, 5), 1);
return;
}
}
else
{
for (int i = 0; i < wordLists[index].len; i++)
//for문을 돌면서 이전 단어와 겹치는 부분을 찾는다.
{
if (wordLists[index].direction && array[wordLists[index].x, wordLists[index].y + i] != "1")
{
wordLists[index].words = FindWord(index, i, array[wordLists[index].x, wordLists[index].y + i], filenum, cnt);
}
else if (!wordLists[index].direction && array[wordLists[index].x + i, wordLists[index].y] != "1")
{
wordLists[index].words = FindWord(index, i, array[wordLists[index].x + i, wordLists[index].y], filenum, cnt);
//FindWord는 단어의 겹치는 정보와 길이를 입력하여 들어갈 수 있는 단어의 list를 반환한다.
//단어 list를 index의 해당하는 words에 넣는다.
}
}
if ((wordLists[index].len == 3 &&cnt==5) ||(wordLists[index].len ==4 &&cnt==5))
//마지막까지 돌았음에도 불구하고, 단어를 찾지 못할 경우
{
wordLists[index - 1].excepts.Add(wordLists[index - 1].word);
//이전 단어가 다시 나올 수도 있기에 제외 목록에 추가해 둔다.
wordLists[index - 1].word = "";
//이전 단어의 정보를 지운다.
for (int i = 0; i < wordLists[index - 1].len; i++)
{
if (wordLists[index - 1].direction)
array[wordLists[index - 1].x, wordLists[index - 1].y + i] = "1";
else
array[wordLists[index - 1].x + i, wordLists[index - 1].y] = "1";
//array에 정보도 지운다.
}
if (index == 1)
//만약 index가 1일 경우 처음 단어를 생성하는 함수로 이동(1일 경우 2번째 단어이다.)
{
SetFirstWord();
return;
}
else
//아닐 경우에는 이전 단어로 되돌아간다.
{
WordSet(index - 1, true, random.Next(0, 5), 1);
return;
}
}
if (wordLists[index].words.Count > 0)
//스테이지를 만들 때, 단어의 일부분이 중복되는 경우가 많다.
//예를 들면, "사회주의", "민주주의" 등 어근과 어근이 합쳐지는 합성어들이 주로 이런 경우가 많이 나타난다.
//이런 경우를 방지하기 위해서 예비 단어 list가 생성되면, wordlist를 확인하여 같은 어근을 갖고 있는 단어를
//list에서 삭제한다.
{
List<string> deleteWord = new List<string>();
foreach(var row in wordLists[index].words)
{
for (int i = 0; i < wordLists[index - 1].len - 1; i++)
{
if (row.Contains(wordLists[index - 1].word.Substring(i, 2)))
deleteWord.Add(row);
}
}
foreach(var row in deleteWord)
{
for (int i = 0; i < index; i++)
{
if (wordLists[index].words.Contains(row))
{
//Debug.Log("삭제" + row);
wordLists[index].words.Remove(row);
}
}
}
if(wordLists[index].words.Count > 0)
//어근이 겹치는 단어를 지우고도 단어가 남아있는 경우
{
wordLists[index].word = wordLists[index].words[random.Next(0, wordLists[index].words.Count)];
//Debug.Log(wordLists[index].word);
wordLists[index].words.Remove(wordLists[index].word);
wordLists[index].words = wordLists[index].words;
//단어 선정
for (int i = 0; i < wordLists[index].len; i++)
//array에 적용
{
if (wordLists[index].direction)
array[wordLists[index].x, wordLists[index].y + i] = wordLists[index].word[i].ToString();
else
array[wordLists[index].x + i, wordLists[index].y] = wordLists[index].word[i].ToString();
}
if (index == wordLists.Count - 1)
//마지막 단어일 경우
{
Debug.Log("끝");
return;
}
else
//마지막 단어가 아닐 경우 다음 함수로 이동
{
if (wordLists[index + 1].len == 3)
{
WordSet(index + 1, false, random.Next(0, 5), 1);
return;
}
else
{
WordSet(index + 1, false, random.Next(0, 5), 1);
return;
}
}
}
else
//만약 단어가 남아 있지 않을 경우 다음 단어 list를 열어 실행한다.
{
WordSet(index, false, filenum + 1, cnt + 1);
}
}
else
//단어를 찾지 못할 경우 다음 파일로 이동
{
WordSet(index, false, filenum + 1, cnt + 1);
}
}
}
제일 버그가 많이 터지는 함수이다. 조금만 변수를 잘못 건드려도, 이상한 단어가 튀어나오곤 한다.
List<string> FindWord(int index, int num, string syllable, int filenum, int cnt)
//원하는 단어를 찾는 함수이다. index는 찾는 단어의 index이고, num은 겹치는 단어의 index이고 syllable은 겹치는 단어의 음절
//filenum은 찾고자 하는 list의 index, cnt는 findword 함수를 몇 번 돌았나 확인하는 변수이다.
{
funcCnt++;
List<string> words = new List<string>();
List<string> file = new List<string>();
if (wordLists[index].len == 3)
{
if (cnt == 6)
{
words = null;
return words;
//만약 모든 list를 다 돌게 된다면, null을 return 한다.
}
file = word3[filenum % 5];
}
else
{
if (cnt == 6)
{
words = null;
return words;
}
file = word4[filenum % 5];
}
for (int i = 0; i < file.Count; i++)
{
if (file[i].ToString()[num].ToString() == syllable && !wordLists[index].excepts.Contains(file[i].ToString()))
//제외되는 단어에 포함되지 않고, 겹치는 부분이 같은 단어를 찾는다.
{
if (wordLists.Find(x => x.word == file[i].ToString()) == null)
//같은 단어가 아닐 경우
{
words.Add(file[i].ToString());
}
}
}
return words;
//단어 list를 return
}
8.스테이지 저장
스테이지 저장 방식은 여러가지가 있는데, 나는 사용하기 편한 PlayerPrefs를 이용하여 string 형태로 저장하였다. 앱이 비활성화되거나, 메인화면으로 나갈 경우 게임이 저장된다.
void OnApplicationPause(bool pauseStatus) //앱이 비활성화 될때(pauseStatus가 true가 됨)
{
if(pauseStatus &&done)//done은 bool 형식의 stage가 완성되면 true로 변한는 변수이다.
Save();
}
//저장 : 플레이시간, 메인패널, 서브패널, emptyTiles 갯수, 단어리스트,힌트 갯수
public void Save()
//저장 방식은 플레이시간#메인패널#서브패널#emptytiles의 갯수#단어리스트#남은 힌트 갯수
//형식으로 저장되어서 string의 split함수를 이용하여 나누어 정보를 load하게 된다.
{
if(wordLists.Count<6)
return; //만약 스테이지에 버그가 났을 때 저장이 안된다.
save = (((int)Time.time)-startTime).ToString()+"#";//플레이시간 저장한 후 '#' 추가
for(int i =0;i<index*index;i++)
//모든 메인 패널을 확인하여, 단어가 들어갈 빈 칸 타일, 완성된 단어 타일, 단어가 들어갈 수 없는 빈 칸 타일,
//단어를 입력했지만 아직 맞추지 못한 타일로 4가지 타일을 구분하여 저장한다.
{
var row =mainPanel.transform.GetChild(i);
if(row.tag=="Done")
{
if(row.transform.GetChild(0).GetComponent<Text>().text=="")
{
save = save + "33";
}
else
{
save = save+"2"+row.GetChild(0).GetComponent<Text>().text;
}
}
else if(row.tag == "Tile")
{
save = save + "11";
}
else if(row.tag == "Hit")
{
save = save + "1" + row.GetChild(0).GetComponent<Text>().text;
}
save = save +"*";
}
save = save + "#";//'#'을 추가한다.
for(int i =0;i<subPanel.transform.childCount;i++)
//반복문을 통해 서브타일의 목록을 저장한다.(한가지 타일로 되어있기 때문에 그대로 저장하면 된다.)
{
save = save+subPanel.transform.GetChild(i).GetChild(0).GetComponent<Text>().text;
}
save = save +"#";//'#' 추가
save = save + TouchManager.Inst.emptyTiles.transform.childCount;
//empty 타일은 플레이어가 보기에서 메인 함수로 타일을 옮기거나 커서를 통해 빈 칸을 채운다면, 채우고 난 후 원래
//메인 패널에 설정되어 있는 흰색 패널이다. 이 흰색 패널은 비활성화된 후, emptyTiles의 자식 오브젝트가 된다.
save = save +"#";
foreach(var row in wordLists)
{
save = save + row.word+"*";
}
//wordlist의 정보를 입력한다.(위 정보는 다시 split되기 때문에 '*'로 추가하였다.)
save = save + "#";
foreach(var row in wordLists)
{
if(row.direction)
save = save +"1"+"*";
else
save = save +"0"+"*";
}
//단어의 정보를 입력한다.(단어의 방향)
save = save + "#";
foreach(var row in wordLists)
{
save = save + row.x.ToString()+"*";
}
//단어의 정보를 입력한다.(단어의 시작 x좌표)
save = save + "#";
foreach(var row in wordLists)
{
save = save + row.y.ToString()+"*";
}
//단어의 정보를 입력한다.(단어의 시작 y좌표)
save = save + "#";
save = save + hint.ToString() + "#";
//남은 힌트의 갯수를 저장
PlayerPrefs.SetString("save",save);
//PlayerPrefs에 저장(위 데이터는 앱을 종료해도 남아있게 된다.)
Debug.Log(save);
}
public void LoadGame()
//save했던 것이 존재했을 때 동작(loadgame이 동작하거나, save가 없다면 새로 생성)
{
empty.RemoveRange(0,empty.Count);
wordLists.RemoveRange(0, wordLists.Count);
string load = PlayerPrefs.GetString("save");
string[] loads = load.Split('#');
string[] loadTiles = loads[1].Split('*');
startTime = ((int)Time.time) - int.Parse(loads[0]);
SetMainPanel();
for(int i=0;i<index; i++)
{
for(int j=0;j<index;j++)
{
if (loadTiles[index * i + j][0] == '1')
{
if (loadTiles[index * i + j][1] == '1')
{
tiles[i,j].transform.GetChild(0).GetComponent<Text>().text = "";
tiles[i,j].GetComponent<Image>().sprite = emptyTile[Random.Range(0, emptyTile.Count())];
}
else
{
tiles[i,j].transform.GetChild(0).GetComponent<Text>().text = loadTiles[index*i+j][1].ToString();
tiles[i,j].GetComponent<Image>().sprite = hitTile[Random.Range(0, hitTile.Count())];
tiles[i,j].GetComponent<BoxCollider2D>().enabled = true;
tiles[i,j].tag = "Hit";
}
}
else if (loadTiles[index * i + j][0] == '2')
{
tiles[i,j].transform.GetChild(0).GetComponent<Text>().text = loadTiles[index*i+j][1].ToString();
tiles[i,j].GetComponent<Image>().sprite = existTile[Random.Range(0, existTile.Count())];
tiles[i,j].GetComponent<BoxCollider2D>().enabled = false;
tiles[i,j].tag = "Done";
}
else if (loadTiles[index * i + j][0] == '3')
{
tiles[i,j].transform.GetChild(0).GetComponent<Text>().text = "";
tiles[i,j].GetComponent<Image>().sprite = nullTile[Random.Range(0, nullTile.Count())];
tiles[i,j].GetComponent<BoxCollider2D>().enabled = false;
tiles[i,j].tag = "Done";
}
tiles[i,j].transform.DOMoveZ(0.0f, 0f);
}
}
for (int i = 0; i < loads[2].Length; i++)
{
var tile = Instantiate(tilePrefab);
tile.transform.parent = subPanel.transform;
tile.transform.localScale = new Vector3(1.0f, 1.0f, 1.0f);
tile.transform.GetChild(0).GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, (900 / 8) - 8);
tile.transform.GetChild(0).GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, (900 / 8) - 8);
tile.transform.GetChild(0).GetComponent<Text>().text = loads[2][i].ToString();
tile.GetComponent<Image>().sprite = hitTile[Random.Range(0, hitTile.Count())];
tile.tag = "Hit";
subTiles.Add(tile);
tile.transform.DOMoveZ(0.0f,0f);
}
subPanel.GetComponent<GridLayoutGroup>().cellSize = new Vector2(105f, 105f);
for(int i =0;i<loads[3].Length;i++)
{
var tile = Instantiate(tilePrefab);
tile.transform.parent = TouchManager.Inst.emptyTiles.transform;
tile.transform.localScale = new Vector3(1.0f, 1.0f, 1.0f);
tile.GetComponent<Image>().sprite = emptyTile[Random.Range(0, emptyTile.Count())];
tile.SetActive(false);
}
string[] words = loads[4].Split('*');
string[] direction = loads[5].Split('*');
string[] xPos = loads[6].Split('*');
string[] yPos = loads[7].Split('*');
for(int i =0;i<words.Length-1;i++)
{
wordInfo wordinfo = new wordInfo();
wordinfo.len = words[i].Length;
wordinfo.word = words[i];
wordinfo.x=int.Parse(xPos[i]);
wordinfo.y=int.Parse(yPos[i]);
if(direction[i]=="1")
wordinfo.direction = true;
else
wordinfo.direction = false;
wordLists.Add(wordinfo);
}
hint = int.Parse(loads[8]);
hintText.text="힌트 : "+hint.ToString();
PlayerPrefs.DeleteKey("save");
done = true;
CheckClear();
}
9.플레이
플레이할 때는 서브패널에서 드래그를 통해 메인패널에 옮겨 넣는 방식과 커서를 옮겨가며 서브패널의 타일을 터치하여 옮겨 넣는 방식이 있다.
두 방식 모두 타일의 스크립트와 Touch 스크립트를 통해 동작한다.
1)MouseDown
public void MouseDown(GameObject gameObject)
//마우스로 클릭을 시작하였을 때 동작하는 함수이다.(tile에서 호출)
{
if (gameObject.transform.parent.gameObject == mainPanel.gameObject)
//메인패널의 tile을 터치하였을 때
{
int childIndex = gameObject.transform.GetSiblingIndex();
//몇번째 index였는지 확인
gameObject.transform.parent = tiles.transform;
//tiles는 움직이고 있는 tile의 부모 오브젝트이다.(터치하면 이 오브젝트의 자식이된다.)
var child = emptyTiles.transform.GetChild(0);
//emptyTiles는 tile을 메인타일로 옮겼을 때 옯긴 위치의 빈 타일이 이 오브젝트의 자식으로 옮겨진다.
child.gameObject.SetActive(true);
//메인타일의 tile을 옮긴 터치하면 글자가 있는 tile을 손가락 tiles의 자식이되고, emptyTiles의 자식 중 제일
//index가 작은 tile이 다시 터치한 위치에 오게 된다.
child.transform.parent = mainPanel.transform;
child.transform.SetSiblingIndex(childIndex);
}
else
//서브패얼의 tile을 터치하였을 때
{
gameObject.transform.parent = tiles.transform;
gameObject.transform.GetChild(0).GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, (900 / index) - 8);
gameObject.transform.GetChild(0).GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, (900 / index) - 8); //터치하였을 경우 iindex의 키기에 따라 자식 오브젝트인 text의 크기가 작아져야 tile
//에서 오버되지 않는다. 그러므로 크기에 맞춰 작게 만든다.
gameObject.GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, size);
gameObject.GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, size);
//터치한 오브젝트 또한 메인패널과 크기가 같아야 하므로 크기를 조정한다.
}
}
2)MouseDrag
public void MouseDrag(GameObject gameObject)
//드래그는 mousedown과 mouseup 사이에 동작하는 함수이다.(tile에서 호출)
{
Vector3 mousePosition = new Vector3(Input.mousePosition.x,Input.mousePosition.y, 10f);
Vector3 objPosition = Camera.main.ScreenToWorldPoint(mousePosition);
gameObject.transform.position = objPosition;
//마우스 포지션인 터치하고 있는 손가락을 따라다닌다
}
3)MouseUp1
public void MouseUp(GameObject gameObject)
//터치를 멈출 때 두가지 동작이 있는데, 일반적인 동작으로 메인패널 위에 놓았을 때 그 위치에 빈 칸이 있을 경우와 서브패널 위에서
//터치를 멈출 때로 나뉜다. 메인패널에서 터치를 짧게하면 서브패널로 오는 동작도 있다(MouseUp2)
{
gameObject.GetComponent<BoxCollider2D>().enabled = false;
//터치하고 있는 오브젝트에 손가락과 충돌이 인식되므로 함수가 동작할 동안은 enabled을 false 해둔다.
Vector2 touchPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Ray2D ray = new Ray2D(touchPosition,Vector2.zero);
RaycastHit2D hit = Physics2D.Raycast(ray.origin,ray.direction);
//레이캐스트2D를 통해 겹친 부분을 찾는다. (손가락과)
if(hit.collider !=null)
//터치에 반응하는 부분이 있다면
{
if (hit.collider.gameObject.tag == "Tile")
//만약 빈 타일이라면 인식된 위치로 손가락으로 드래그하고 있는 타일이 그 자리로 이동한다.
//Tag는 Tile, Done, Hit이 있다(tile들의 Tag)
//Tile만 Collider 2D를 활성화 시켜 충돌을 인식할 수 있다.
{
int childIndex = hit.collider.gameObject.transform.GetSiblingIndex();
hit.collider.gameObject.transform.parent = emptyTiles.transform;
gameObject.transform.parent =mainPanel.transform;
gameObject.transform.SetSiblingIndex(childIndex);
hit.collider.gameObject.SetActive(false);
audioManager.GetComponents<AudioSource>()[2].Play(); // SFX 사운드 동작
}
else
//만약 빈 타일이 아니라면 다시 서브 패널로 돌아간다.
{
gameObject.transform.parent = subPanel.transform;
gameObject.transform.GetChild(0).GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, (900 / 8) - 8);
gameObject.transform.GetChild(0).GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, (900 / 8) - 8);
}
}
else
//터치에 반응하는 부분이 없다면 다시 서브 패널로 돌아온다.
{
gameObject.transform.parent = subPanel.transform;
gameObject.transform.GetChild(0).GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, (900 / 8) - 8);
gameObject.transform.GetChild(0).GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, (900 / 8) - 8);
}
gameObject.GetComponent<BoxCollider2D>().enabled = true;
}
4)MouseUp2
public void MouseUp2(GameObject tile)
//메인타일에서 살짝 터치하였을 때 동작한다.
//터치한 tile은 서브패널로 되돌아온다.
//Tile Tag를 가지고 있는 오브젝트에서 동작한다.
{
audioManager.GetComponents<AudioSource>()[3].Play();//SFX 사운드 동작
if(subPanel.transform.childCount==0)
//만약 서브패널이 빈 칸이라면
{
Vector3 tilePosition = new Vector3(-1.1f,-0.9f,0);
//서브패널 첫번째 index의 tile 포지션
tile.transform.GetChild(0).GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, (900 / 8) - 8);
tile.transform.GetChild(0).GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, (900 / 8) - 8);
tile.GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 105f);
tile.GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 105f);
//서브타일 크기에 맞게 크기 줄이기
Sequence sequence = DOTween.Sequence()
.Append(tile.transform.DOMove(tilePosition, 0.1f)).SetEase(Ease.Unset)
.OnComplete(() => tile.transform.parent = subPanel.transform);
//Dotween을 통해 애니메이션을 추가하여 이동
}
else
//서브패널이 빈 칸이 아니라면
{
Vector3 tilePosition = subPanel.transform.GetChild(subPanel.transform.childCount - 1).transform.position;
//서브패널의 마지막 tile의 포지션
if (tilePosition.x >= 0.9f) //마지막 tile의 옆으로 이동
tilePosition = new Vector3(-1.1f, tilePosition.y - 0.3f, 0f);
else //마지막 tile의 위치가 y축에서 마지막 위치라면 밑 줄 제일 첫 칸으로 이동
tilePosition = new Vector3(tilePosition.x + 0.3f, tilePosition.y, 0f);
tile.transform.GetChild(0).GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, (900 / 8) - 8);
tile.transform.GetChild(0).GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, (900 / 8) - 8);
tile.GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 105f);
tile.GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 105f);
Sequence sequence = DOTween.Sequence()
.Append(tile.transform.DOMove(tilePosition, 0.1f)).SetEase(Ease.Unset)
.OnComplete(() => tile.transform.parent = subPanel.transform);
//위와 동일
}
}
5)SetCurser
public void SetCurser()
//커서를 제일 첫 단어의 빈 칸에 생성
{
GameObject[] tiles = new GameObject[index*index];
for(int i =0;i<index *index;i++)
{
tiles[i] = mainPanel.transform.GetChild(i).gameObject;
}
int first = GameManager.Inst.GetFirst();
//첫 단어부터 확인하여 제일 첫번째 빈 칸의 index
//찾지 못할 경우 -1을 반환
if (first != -1)
{
curser.SetActive(true);
curser.GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, size + 3);
curser.GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, size + 3);
//메인패널의 tile 보다는 조금 크게 위치하게 한다.
curser.transform.position = tiles[first].transform.position;
curserX = first / index;
curserY = first % index;
//커서의 현재 위치를 저장
}
else
//찾지 못할 경우 제일 앞 index의 빈 칸에 위치
{
for (int i = 0; i < index * index; i++)
{
if (tiles[i].tag == "Tile")
{
curser.SetActive(true);
curser.GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, size + 3);
curser.GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, size + 3);
curser.transform.position = tiles[i].transform.position;
curserX = i / index;
curserY = i % index;
break;
}
}
}
}
6)MoveCurser
public void MoveCurser(GameObject gameObject,int index1)
//터치한 곳으로 커서를 이동시킨다.
{
GameObject[] tiles = new GameObject[index * index];
int moveindex = 0;
for (int i = 0; i < index * index; i++)
{
tiles[i] = mainPanel.transform.GetChild(i).gameObject;
if(gameObject == tiles[i])
moveindex = i;
}
if (index1 == -1)
{
Sequence sequence = DOTween.Sequence()
.Append(curser.transform.DOMove(tiles[moveindex].transform.position, 0.2f)).SetEase(Ease.Unset)
.Append(curser.transform.DOLocalMoveZ(0, 0));
curserX = moveindex / index;
curserY = moveindex % index;
}
else
{
Sequence sequence = DOTween.Sequence()
.Append(curser.transform.DOMove(tiles[index1].transform.position, 0.2f)).SetEase(Ease.Unset)
.Append(curser.transform.DOLocalMoveZ(0, 0));
curserX = index1 / index;
curserY = index1 % index;
}
}
7)MoveTile
public void MoveTile(GameObject gameObject)
//서브패널에서 tile을 터치할 경우 커서의 위치로 tile을 이동시킨다.
{
GameObject[] Tiles = new GameObject[index * index];
for (int i = 0; i < index * index; i++)
Tiles[i] = mainPanel.transform.GetChild(i).gameObject;
if(Tiles[curserX*index+curserY].tag == "Tile")
//만약 커서의 위치의 tile이 빈 칸이라면
{
var emp = Tiles[curserX*index+curserY];
Sequence sequence = DOTween.Sequence()
.Append(gameObject.transform.DOMove(emp.transform.position, 0.1f)).SetEase(Ease.Unset)
.OnComplete(()=>emp.transform.parent =emptyTiles.transform );
emp.SetActive(false);
gameObject.transform.parent = mainPanel.transform;
gameObject.transform.SetSiblingIndex(curserX*index+curserY);
//위의 mouseup과 동일한 방식으로 이동
}
else if(Tiles[curserX*index+curserY].tag == "Hit")
//만약 커서 위치에 글자 tile이 있다면
{
var swap = Tiles[curserX*index+curserY];
var swapos = swap.transform.position;
swap.transform.parent = tiles.transform;
MouseUp2(swap);//글자 tile은 서브패널로 되돌아온다.
var emp = emptyTiles.transform.GetChild(0).gameObject;
emp.transform.parent = mainPanel.transform;
emp.transform.SetSiblingIndex(curserX*index+curserY);
emp.SetActive(true);
Sequence sequence = DOTween.Sequence()
.Append(gameObject.transform.DOMove(swapos, 0.1f)).SetEase(Ease.Unset)
.OnComplete(()=>emp.transform.parent =emptyTiles.transform);
emp.SetActive(false);
gameObject.transform.parent = mainPanel.transform;
gameObject.transform.SetSiblingIndex(curserX*index+curserY);
}
else
{
MouseUp2(gameObject);
//만약 버그로 인해 tag가 위의 두개가 아닐 경우 서브패널로 tile을 되돌린다.
}
StartCoroutine(AutoMoveCurser()); //자동으로 커서의 위치를 옮긴다.
}
8)AutoMoveCurser
public IEnumerator AutoMoveCurser()
//커서를 통해 tile이 이동할 경우 커서는 자동적으로 커서 위치의 단어의 다른 빈칸으로 가거나
//다음 단어의 첫 빈 칸으로 이동한다.
{
yield return new WaitForSeconds(0.25f); // 0.25초 뒤에 반응
int pos = GameManager.Inst.GetPosition(curserX,curserY);
//움직일 위치의 index를 반환
Debug.Log(pos);
GameObject[] Tiles = new GameObject[index * index];
for (int i = 0; i < index * index; i++)
Tiles[i] = mainPanel.transform.GetChild(i).gameObject;
if(pos!=-1)
MoveCurser(Tiles[pos],pos); //커서를 이동
}
9)CheckClear
public IEnumerator CheckClear()
//타일이 이동이 있을 때마다 스테이지가 클리어 되었는지 확인
{
yield return new WaitForSeconds(0.1f);
Debug.Log("Check");
GameManager.Inst.CheckClear();
}
10)StageClear
IEnumerator StageClear()
//스테이지를 클리어했는지 확인한다.
{
TouchManager.Inst.curser.SetActive(false);
//커서를 비활성화
PlayerPrefs.DeleteKey("save");
//스테이지가 저장되어있던 것을 삭제
int time = ((int)Time.time)-startTime;//시간 반환(초)
Debug.Log(time+"초 걸림!");
TouchManager.Inst.audioManager.GetComponents<AudioSource>()[4].Play();//클리어 사운드
yield return null;
UIManager.Inst.DoMove(clearPanel);
clearPanel.SetActive(false);
yield return new WaitForSeconds(0.1f);
clearPanel.transform.GetChild(0).GetComponent<Text>().text = stageNum.ToString() +"단계 클리어!!";
clearPanel.transform.GetChild(1).GetComponent<Text>().text = "<color=#ff0000>" + (((int)Time.time)-startTime).ToString() + "</color>" +"초 걸림!!!";//빨간색으로
clearPanel.SetActive(true);
stageText.text = "";
AddWordsMyAccount();//서버와 관련됨
var bro = Backend.GameData.Get("Info", new Where());
if (bro.IsSuccess())
{
var MyInfo = Backend.GameData.GetMyData("Info", new Where(), 10);
int wordcnt = int.Parse(MyInfo.Rows()[0]["wordcnt"][0].ToString());
Param param = new Param();
param.Add("stage", stageNum + 1);
param.Add("wordcnt", wordcnt + wordLists.Count);
Backend.GameData.Update("Info", new Where(), param);
Backend.URank.User.UpdateUserScore("0abc3e30-5be5-11ec-9ddb-b3b31a0d7eae", "Info", bro.GetInDate().ToString(), param);
if(CheckAchieve(time))
{
StartCoroutine(AlarmAchieve());
}
}
else
{
UIManager.Inst.DoMove(UIManager.Inst.failPanel);
}
}
11)Tile.cs
public class Tile : MonoBehaviour
{
string par = "";
int index =-1;
public Vector3 mousePos;
void OnMouseDown()
//오브젝트 터치를 시작하였을 때
{
if(this.gameObject.tag == "Hit")
{
mousePos = Input.mousePosition;
par = this.gameObject.transform.parent.name;
if(par=="MainPanel")
{
index =this.transform.GetSiblingIndex();
}
TouchManager.Inst.MouseDown(this.gameObject);
}
}
void OnMouseDrag()
//드래그 중
{
if(this.gameObject.tag == "Hit")
{
TouchManager.Inst.MouseDrag(this.gameObject);
}
}
void OnMouseUp()
//터치를 끝냈을 때
{
Vector3 test = mousePos - Input.mousePosition;
double testm = Math.Pow(test.x,2) + Math.Pow(test.y,2);
//짧게 터치와 길게 터치를 구분할 방법을 찾다가 이동 거리를 확인하여
//이동거리가 특정한 상황이 되면 짧게 터치로 간주한다.
if(this.gameObject.tag == "Hit")
{
if(testm<1000)
{
if(par=="MainPanel")
{
TouchManager.Inst.MouseUp2(this.gameObject);
TouchManager.Inst.MoveCurser(this.gameObject,index);
index = -1;
}
else if(par=="SubPanel")
TouchManager.Inst.MoveTile(this.gameObject);
}
else
{
TouchManager.Inst.MouseUp(this.gameObject);
}
}
else if(this.gameObject.tag == "Tile")
TouchManager.Inst.MoveCurser(this.gameObject,index);
StartCoroutine(TouchManager.Inst.CheckClear());
}
}