분류없음 2017.05.01 13:58

https://www.youtube.com/playlist?list=PLZlv_N0_O1gYwhBTjNLSFPRiBpwe5sTwc

신고
posted by andwhy
Fun Project 2016.02.12 10:40

예전에 만들어둔...탄막패턴 생성기?? 가 보이네요..

http://andwhy.cafe24.com/00_test/18_tanmak/strPatternApp.html

딴거 찾을려다 보여서 올려둡니다.

신고
posted by andwhy
Unity3D 2015.11.23 14:39

android 연동을 하면 간단하긴하지만..

따로 플러그인으로 배포하기 귀찮고, 그냥 유니티에서만 C#코드를 가지고 언어 셋팅값을 가져오게 만들어봤습니다.

AndroidJavaClass Locale = new AndroidJavaClass"java.util.Locale" );
AndroidJavaObject DefaultLang = 
Locale.CallStatic<AndroidJavaObject>("getDefault");
Debug.Log("lang " + 
DefaultLang.Call<string>("toString"));


정말 저거하나만 필요할때는 사용할만한데, 다른거랑 같이 쓸꺼면 플러그인 만들어서 쓰는게 깔끔하겠군요.

신고
posted by andwhy
잡담 2015.10.01 10:24

검색하면 많이 나오지만,

검색에서 못찾은 내용들도 있어서 하나씩 정리..

요청하신 항목은 구매할 수 없습니다

이 에러는 원인이 좀 많은것같다. 구글 계정이 원인인데,

1. 계정정보가 잘못되어있는경우.
    -테스트 권한이 없는계정으로 로그인되어있는경우 발생

2. 계정이 판매자 계정일경우.
    -판매자계정은 테스트 계정으로 사용불가. 새로 계정을 하나 만들어서 테스트로 등록하면 됨.

---------보통은 이 둘중 하나일경우가 많지만, 이번에 겪은문제는 여기에 해당이 안된다.--------

3. 테스트 계정으로 정상적으로 로그인되어있지만, 테스터 등록기간이 많이 지난경우.
    -테스트권한설정을 웹페이지를통해서 등록하게 되는데, 일정시간(아마 1달정도인듯)이 지나면 테스터권한이 자동으로 풀리는것같다.
    아래 스샷에 테스트 참여URL에 가서 다시한번 테스터로 등록하면 된다.


신고
posted by andwhy
Air 2015.09.29 22:57

내가 노가다하기 싫어서 만든 유틸.


FightingFactory_sprite_pivot_Util.air


1. 파이팅 팩토리를 이용해 스프라이트를 뽑아낸다.(팔렛트는 레드체널을 이용해 뽑는다.)

2. 제대로 뽑았으면 검은 바탕에 엄청 많은 여백이 있고, 중앙에 붉은색 실루엣의 케릭터가 보인다.

3. 뽑아진 sprite들이 있는 폴더를 util창에 드래그해넣는다.

4. 뽑아진 데이터에 같이 있는 axis.txt. 파일을 보면, pivot좌표가 나오는데 좌측상단에 global pivot에 넣어준다.(케릭터 중심점<아마 발끝정도> 쯤에 녹색 가이드라인이 보인다.)

5. save trim image 버튼을 눌러 저장할 경로를 선택한다.

6. 선택되면 이미지의 여백이 잘려저서 하나씩 저장되고, 마지막에 pivotdata.json으로 중심점 정보가 생성된다.

7. 참고로 alpha-8으로 인코딩할거라, red체널을 alpha체널로 변경시켜서 저장한다.

신고
posted by andwhy
분류없음 2015.03.25 15:25

csv 2 json

CSV 를 json으로 변경

이외에도 sql,xml등으로 변경 가능


http://www.convertcsv.com/csv-to-json.htm


jsonParser

간단하게 json확인

http://json.parser.online.fr/


logcat

로그켓 확인(생상구분되는건 좋은데,크게 좋지는 않음.)

http://forgottenprojects.com/tools/android/logcat/

신고
posted by andwhy
분류없음 2015.02.03 20:42

출처:http://stackoverflow.com/questions/5306009/facebook-android-generate-key-hash


In order to generate key hash you need to follow some easy steps.

1) Download Openssl from: here.

2) Make a openssl folder in C drive

3) Extract Zip files into this openssl folder created in C Drive.

4) Copy the File debug.keystore from .android folder in my case (C:\Users\SYSTEM.android) and paste into JDK bin Folder in my case (C:\Program Files\Java\jdk1.6.0_05\bin)

5) Open command prompt and give the path of JDK Bin folder in my case (C:\Program Files\Java\jdk1.6.0_05\bin).

6) Copy the following code and hit enter

keytool -exportcert -alias androiddebugkey -keystore debug.keystore > c:\openssl\bin\debug.txt

7) Now you need to enter password, Password = android.

8) If you see in openssl Bin folder, you will get a file with the name of debug.txt

9) Now either you can restart command prompt or work with existing command prompt

10) get back to C drive and give the path of openssl Bin folder

11) copy the following code and paste

openssl sha1 -binary debug.txt > debug_sha.txt

12) you will get debug_sha.txt in openssl bin folder

13) Again copy following code and paste

openssl base64 -in debug_sha.txt > debug_base64.txt

14) you will get debug_base64.txt in openssl bin folder

15) open debug_base64.txt file Here is your Key hash.

신고
posted by andwhy
Unity3D/TowerDefence 2014.10.12 23:13


드디어 타워디펜스 게임 만들기 강좌가 끝났습니다.

처음생각했던것과 달라진부분도 많고 만들지 못한것도 많았네요.

최대한 읽는분들이 이해하기 쉽게 하려고 했었는데, 그래도 많이 복잡하게 느껴졌을것 같습니다.

실제로 누군가를 앞에서 알려드리진 못했지만, 이렇게나마 누군가에게 설명을하려니까 어려운부분이 상당히 많네요.

개인적으로도 많은공부를 하게 된것 같습니다.

혼자 만들때는 그냥그냥 되는대로 뚝딱뚝딱 만들면 끝났지만 다시 하나씩 설명하려고 하니 정말 많이 준비하고, 정말 많이 생각해야하는군요.

다른 강좌나, 책쓰시는분들이 너무나도 대단해보입니다.

허접하게 나마 게임하나 강좌를 끝내보니 이제서야 어떻게 해야할지 조금 느껴지는것 같습니다.

앞으로 기회 되면 또다른 강좌를 좀더 많이 준비해서 만들어보도록 하겠습니다.


그동안 지루한 강좌 따라와 주셔서 감사하고, 고맙습니다.

특히 글올리면 바로바로 피드백 주신 "캣츠아이"님, "순순"님 정말 정말 감사드립니다.


마지막으로 지금까지 함께 만든 타워디펜스 게임 올리고 인사드리겠습니다.
(waveData와 벨런싱으좀 맞춰봤습니다...저는 11스테이지 이후론 못가봤네요;;)



감사합니다.







신고
posted by andwhy
Unity3D/TowerDefence 2014.10.11 18:36

마지막 시간이네요.

오늘 할건 지금까지 한거에 비하면 정말 간단한것입니다.

점수 보여주기와, GameOver, 그리고 Game 재시작.

크게 이 세가지입니다.


1. Score

타워 디펜스 게임에서는 점수가 크게 의미는 없을것 같습니다. 퍼즐게임이나 기록갱신 게임들에서는 얼마나 많은점수를 내느냐가 가장 중요한점이지만, 디펜스게임의 경우는 스테이지를 몇까지 클리어 하는지가 더 중요하니까요...

하지만, 점수가 없으면 뭔가 좀 허전해서...넣어보겠습니다.(타워디펜스도 기획에따라 점수가 정말 중요해질수도 있으니까요..)

우선 저희는 유닛을 죽이고 얻는 골드를 점수로 간주하도록 하겠습니다. 유닛마다 점수를 따로 설정하고, 타워를 짓거나 업그레이드 할때도 점수를 추가해줄수 있겠지만, 가장 간단한 방법으로 얻어지는 골드를 점수로 환산하도록 하겠습니다.


먼저 게임 점수를 저장할 gameScore변수를 만듭니다.

GameManager.cs

외부에서 게임점수를 추가할수 있도록 addScore()라는 함수도 만들어줍니다.

GameManager.cs

게임이 시작될때는 점수를 0점으로 초기화 시켜줍니다.

GameManager.cs

이제 화면에 점수를 표시해주기위해서, OnGUI() 안에 Gold를 표기한 다음줄에 표시하도록 코드를 수정해줍니다.

GameManager.cs

GameManager.cs 에서 해야할일은 끝났고, 이젠 유닛을 죽일때 GameManager addScore를 호출해주기만 하면됩니다.

처음에 말한것처럼, 유닛을 죽이고 골드를 획득할때(addGold) 획득한골드만큼 점수를 올려줄 것입니다. UnitBase에서 addGold해주는 부분을 찾아서 바로 밑에 addScore도 해주면 됩니다.

UnitBase.cs


이제 화면을 보면 아래처럼 Gold표시 아래에 Score도 표시됩니다.


2. GameOver

이번엔 GameOver를 만들어보겠습니다.
우리게임은 유닛이 목표점까지 못오도록 막는게 목적입니다. 하지만 유닛을 막지 못하고, 목표점까지 유닛이 침범한다면, 베이스캠프(?) 의 체력을 하나씩 줄어들고, 체력이 0이되면 게임은 끝납니다.

먼저 뭐부터 해야할까요?

베이스캠프(?) 의 최대 체력과 현재 체력 값을 만들어줍니다. 인스펙터에서 손쉽게 수정가능하도록 public 으로 선언하겠습니다.
그리고 현재 게임오버 상태인지 아닌지를 체크하는 isGameOver 값도 하나 추가 하겠습니다.

GameManager.cs

다음으론 유닛이 도착했을때, 현재 체력을 하나씩 깎고, 체력이 0인지 체크하는 reachUnit() 함수를 추가합니다.

GameManager.cs

기존에는 유닛이 목적지에 도착하면 자기 자신을 Destroy시키고 GameManagerremoveUnit() 를 호출해줬는데, 이제는 현재 체력을 하나씩 깍고, 체력이 0이 되면 게임 오버상태로 변경하기 위해서 reachUnit() 이란 함수를 새로 만들었습니다.

isGameOverisGameStart만 으로 게임진행상태를 판단하기 어려울 경우를 위해서 추가하였습니다.
GameOver 상태에선 새로운 타워를 짓거나, 타워를 제거하는일을 할수는 없습니다. 하지만, wave를 시작하기전에 미리 몇몇 타워를 짓고 싶다면 isGameStart 라는 변수 하나만으론 판단하기 힘듭니다.


다음으론 게임시작시, 베이스캠프의 체력을 초기화 시켜주고, isGameOverfalse로 변경시켜줍니다.

GameManager.cs


이제 게임이 시작되면, 게임상태는 게임은 스타트 되었고 게임오버는 되지 않은 상태가 됩니다.
그리고 게임점수는 0점 베이스캠프의 체력은 최대 채력(10) 이 되게 될겁니다.

이번엔 다시 UnitBase.cs 로 넘어가서, 유닛이 목적지 까지 도착할때 GameManagerreachUnit()함수를 호출하도록 하겠습니다.

UnitBase.cs

위의 그림처럼 Update함수안에 목적지에 도착했는지 판단하는 부분이 있는데, 기존의 removeUnit()을 호출하는것을 reachUnit()을 호출하도록 변경해줍니다.


이상태로 게임을 한번 진행해서, 정상적으로 게임오버가 되는지 돌려보겠습니다.
정상적으로 게임오버가 된다면, 10마리 이상의 유닛을 놓치면, GAMEOVER란 메시지가 표시되고 더이상 게임이 진행되면 안됩니다.




실제로 돌려보면.....
GAMEOVER 로그가 여러번 찍히고, Life는 0 이 되어도 계속 내려갑니다.

이문제는 게임이 끝났지만, 유닛과, 타워가 계속 동작하는게 문제네요.

UnitBase.csTowerBase.cs 파일에 GameOver인지를 검사하는 코드를 넣어보도록 하겠습니다.

UnitBase.cs

Update중에 GameManagerisGameOver  상태이면 스프라이트 애니메이션을 멈추고 더이상 아무것도 하지 않습니다.
만일 spr.Stop()구문을 빼버리면 유닛이 이동하진 않지만 제자리 걸음을 하고 있을껍니다.

TowerBase.cs


TowerBase 에서도, GameOver상태이면 바로 리턴시켜 버립니다. 이러면 유닛을 바라보거나, 공격하는 행동은 더이상 하지 않습니다.


그리고 한가지 놓친곳이 있는데, GameManager 에서 다음 Wave를 체크하는 부분과, 현재 WaveData들을 업데이트 해주는부분도, GameOver상태일때는 동작해선 안됩니다.

아래 그림처럼 각각 함수 윗부분에 게임중이 아닐경우엔 return 시켜 더이상 동작하지 않도록 합니다.

GameManager.cs


이제 게임을 테스트 해보면 게임오버까지 정상적으로 동작되는것을 볼수 있습니다.


3. 게임재시작.

게임이 끝난뒤 게임을 다시 시작할수 있게 만들어주면 됩니다.
게임의 점수나, 획득 골드를 처음상태로 바꿔주고, 게임화면에 나와있는 유닛들과, 타워들을 제거해준뒤 다시 게임을 시작하면됩니다.

화면의 유닛과, 타워를 모두 제거하는 함수를 만듭니다.

GameManager.cs


다음으론 게임초기화 함수를 만들겠습니다.

GameManager.cs

gameInit()은 방금 만든 clearTowers, clearUnits 함수와 현재 wave데이터를 초기화 하고, 기타 그외의 점수, 골드 등의 게임데이터들을 초기화 해줍니다.
그리고 기존의 startGame()isGameStart를 제외한 다른 초기화코드를 제거했는데,
이는 게임이 종료되었을때, "확인"버튼을 누르면 게임이 초기화 되고, 이상태에서 미리 타워들을 배치할수 있도록 하기위해서 gameInit()때만 초기화를 해줍니다.
(startGame()에서도 초기화 시키게되면 gold점수등이 잘못초기화 될수도 있습니다.)

마지막으로 게임오버시에 화면중앙에 "GameOver" 라는 버튼을 만들고, 버튼을 누르면 초기화 시키도록 OnGUI에 코드를 추가해줍니다.

GameManager.cs


이제 게임오버가 되면 다음과 같이 화면 중앙에 GameOver 버튼이 보이고, 버튼을 누르면 기존의 유닛,타워들이 사라지고, 초기화 되는것을 볼수 있습니다.



이번 강의는 여기까지입니다.

그동안 힘등 강의 따라와주셔서 정말 감사합니다.

마지막으로 게임코드 공유 하고 끝내도록 하겠습니다.


4. 게임코드


GameManager.cs


using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using common;

public class GameManager : MonoBehaviour {
	private static GameManager _instance = null;
	public static GameManager instance
	{
		get{
			if(_instance == null)Debug.LogError("GameManager is NULL");
			return _instance;
		}
	}

	void Awake()
	{
		_instance = this;
		init();
	}
	//=========================================
	public float cellSize = 40.0f;
	public int[,] wallMap = 
	{{1, 1, 1, 1, 1, 11, 11, 11, 1, 1, 1, 1, 1},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{1, 1, 1, 1, 1, 111, 111, 111, 1, 1, 1, 1, 1}};
	[HideInInspector]
	public List<UnitBase> unitList = new List<UnitBase>();
	[HideInInspector]
	public Dictionary<Point, TowerBase> towerDic = new Dictionary<Point, TowerBase>();
	public BgCellDisplayer bgGrid;
	public Transform unit_field;
	public UnitBase unit_marine;
	public TowerBase tower;
	public TowerUIMenu towerMenu;
	
	public int startedGold = 100;
	public int currentGold = 0;
	public int maxLife = 10;
	public int currentLife = 10;
	public bool isGameOver = false;

	public Camera mainCam;

	private int gameScore = 0;
	private bool isGameStart = false;
	public int waveIndex = 0;
	//-----------waveData
	public WaveData[] waveDataArray;
	private List<WaveData> currentWaveDataList = new List<WaveData>();
	private List<WaveData> removeWaveDataList = new List<WaveData>();
	private float nextWaveDelay = 0f;
	private float nextWaveDelayCount = 0;
	
	public void addWaveData(WaveData wd)
	{
		currentWaveDataList.Add(wd);
	}
	public void removeWaveData(WaveData wd)
	{
		removeWaveDataList.Add (wd);
	}


	private void init()
	{
		unitList.Clear();
		towerDic.Clear();
		initPathFinder();

		currentGold = startedGold;
	}

	public void addUnit(float hp, float speed, int gold)
	{
		UnitBase unit = Instantiate(unit_marine) as UnitBase;
		unitList.Add(unit);
		unit.setUnit(hp, speed, gold);
		unit.transform.parent = unit_field;
		Point startPoint = getStartPoint();
		unit.transform.localPosition = new Vector3(startPoint.x * cellSize + cellSize/2.0f, -startPoint.y * cellSize - cellSize/2.0f);
		unit.setStartPoint(startPoint);
		
	}

	void addTower(Point p)
	{
		
		TowerBase tw = Instantiate(tower) as TowerBase;
		tw.transform.parent = unit_field;
		tw.transform.localPosition = new Vector3(p.x * cellSize + cellSize/2.0f, -p.y * cellSize - cellSize);
		towerDic.Add(p, tw);
		
		useGold(tw.buildPrice);
	}
	public void addGold(int g)
	{
		currentGold += g;
	}
	public void addScore(int s)
	{
		gameScore += s;
	}
	public void useGold(int g)
	{
		currentGold -= g;
	}
	public bool checkGold(int g)
	{
		return (currentGold>=g);
	}
	public void reachUnit(UnitBase ub)
	{
		currentLife --;
		removeUnit(ub);
		if(currentLife<=0)
		{
			Debug.Log("GAMEOVER");
			isGameStart = false;
			isGameOver = true;
		}
	}
	public void removeUnit(UnitBase ub)
	{
		unitList.Remove(ub);
	}
	void researchPathUnits()
	{
		foreach(UnitBase ub in unitList)
		{
			if(ub!=null)
			{
				ub.getPath();
			}
		}
	}
	Point getStartPoint()
	{
		List<Point> startPointList = getStartPointList();
		if(startPointList.Count == 0)
		{
			Debug.LogError("Not Found Start Position");
			return null;
		}
		int ranIdx = Random.Range(0,startPointList.Count);
		return startPointList[ranIdx];
	}
	List<Point> getStartPointList()
	{
		//check startPoints
		List<Point> startPointList = new List<Point>();
		int _w = wallMap.GetLength(0);
		int _h = wallMap.GetLength(1);
		int x,y;
		for (x = 0; x < _w; x++)
		{
			for(y = 0; y < _h; y++)
			{
				if(wallMap[x,y] >= 10 && wallMap[x,y] <= 100)
				{
					startPointList.Add(new Point(x,y));
				}
			}
		}
		return startPointList;
	}
	public void initPathFinder()
	{
		PathFinder.instance.setMapData(wallMap);
	}
	void OnGUI()
	{
		if(isGameOver)
		{
			if(GUI.Button( new Rect( (Screen.width - 200)/2.0f, (Screen.height - 40)/2.0f, 100, 40), "GameOver"))
			{
				
				gameInit();
			}
		}
		else if(!isGameStart)
		{
			if(GUI.Button( new Rect( 10, 10, 100, 40), "Start Game"))
			{
				startGame();
			}
		}
		else 
		{
			if(GUI.Button( new Rect( 10, 10, 100, 40), "nextWave!!"))
			{
				nextWaveDelay = nextWaveDelayCount;
			}
		}
		GUI.Label(new Rect( (Screen.width - 100)/2.0f, 10, 100, 50), "GOLD : "+currentGold+" \n"+"Score : "+gameScore);
		if(waveIndex < waveDataArray.Length)
			GUI.Label(new Rect( (Screen.width - 200), 50, 200, 50), 
			          string.Format("NextWave [{0}] : {1:F2}",waveDataArray[waveIndex].name,(nextWaveDelay-nextWaveDelayCount)));
		GUI.Label(new Rect( (Screen.width - 100), 10, 100, 50), string.Format("Life {0} / {1}",currentLife, maxLife));
	}
	void Update()
	{
		checkNextWave();
		//updateWaveDatas
		updateWaveDataList();

		if(Input.GetMouseButtonDown(0) && !towerMenu.isShow) 
		{
			Vector2 pos = Input.mousePosition;
			Vector3 mouseP = mainCam.ScreenToWorldPoint(pos) - unit_field.TransformPoint(Vector3.zero);
			Point myPos = new Point((int)(mouseP.x/cellSize), -(int)(mouseP.y/cellSize));
			if(towerDic.ContainsKey(myPos))
			{
				showMenu(towerDic[myPos]);
			}
			else 
			{
				buildTower(myPos);
			}
		}
	}
	void gameInit()
	{
		gameScore = 0;
		waveIndex = 0;
		currentGold = startedGold;
		isGameOver = false;
		currentLife = maxLife;
		nextWaveDelay = 0f;
		nextWaveDelayCount = 0;

		clearTowers();
		clearUnits();
		currentWaveDataList.Clear();

	}
	void startGame()
	{
		isGameStart = true;
	}
	private void clearTowers()
	{
		foreach(Point keyP in towerDic.Keys)
		{
			if(keyP.x<0||keyP.y<0 || keyP.x >= wallMap.GetLength(0) || keyP.y >= wallMap.GetLength(1))return;
			wallMap[keyP.x, keyP.y] = 0;
			Destroy(towerDic[keyP].gameObject);
		}
		towerDic.Clear();
		bgGrid.refreshDisplay();
		PathFinder.instance.setPath();
		PathFinder.instance.setCheckMode(false);
	}
	private void clearUnits()
	{
		foreach(UnitBase unit in unitList)
		{
			Destroy( unit.gameObject);
		}
		unitList.Clear();
	}
	void checkNextWave()
	{
		if(!isGameStart||isGameOver)return;
		if(nextWaveDelay<0)return;
		nextWaveDelayCount += Time.deltaTime;
		if(nextWaveDelay<nextWaveDelayCount)
		{
			nextWaveDelayCount = 0;
			if(waveIndex >= waveDataArray.Length)
			{
				nextWaveDelay = -1;
			}
			else
			{
				addWaveData(waveDataArray[waveIndex].clone());
				nextWaveDelay = waveDataArray[waveIndex].nextWaveDelay;
				waveIndex++;
			}
		}
	}

	void updateWaveDataList()
	{
		if(!isGameStart||isGameOver)return;
		foreach( WaveData wd in currentWaveDataList)
		{
			wd.update();
		}
		foreach( WaveData wd in removeWaveDataList)
		{
			if(currentWaveDataList.Contains(wd))currentWaveDataList.Remove(wd);
		}
		removeWaveDataList.Clear();
	}

	bool checkReachAble()
	{
		PathFinder.instance.setCheckMode(true);
		foreach(Point sp in getStartPointList())
		{
			if(PathFinder.instance.getPath(sp,100+wallMap[sp.x,sp.y]) == null)
			{
				Debug.Log("StartPoint Path NULL");
				return false;
			}
		}
		foreach(UnitBase unit in unitList)
		{
			if(!unit.getPath())
			{
				Debug.Log("Unit Path NULL");
				return false;
			}
		}
		PathFinder.instance.setPath();
		return true;
	}
	void buildTower(Point p)
	{
		if(p.x<0||p.y<0 || p.x >= wallMap.GetLength(0) || p.y >= wallMap.GetLength(1))return;
		int prevIndex = wallMap[p.x, p.y];
		
		if(wallMap[p.x, p.y] == 0)wallMap[p.x, p.y] = 2;
		else return;
			
		if(checkGold(tower.buildPrice) && checkReachAble())
		{
			bgGrid.refreshDisplay();
			researchPathUnits();
			addTower(p);
		}
		else 
		{
			wallMap[p.x, p.y] = prevIndex;
		}
		PathFinder.instance.setCheckMode(false);
	}
	public void sellTower(TowerBase tw)
	{
		if(!towerDic.ContainsValue(tw))return;
		addGold(tw.totalPrice/2);
		removeTower(tw);
	}
	public void removeTower(TowerBase tw)
	{
		if(!towerDic.ContainsValue(tw))return;
		foreach(Point keyP in towerDic.Keys)
		{
			if(towerDic[keyP] == tw)
			{
				if(keyP.x<0||keyP.y<0 || keyP.x >= wallMap.GetLength(0) || keyP.y >= wallMap.GetLength(1))return;
				wallMap[keyP.x, keyP.y] = 0;
				towerDic.Remove(keyP);
				break;
			}
		}
		Destroy(tw.gameObject);
		bgGrid.refreshDisplay();
		checkReachAble();
		PathFinder.instance.setPath();
		PathFinder.instance.setCheckMode(false);
	}
	void showMenu(TowerBase tw)
	{
		towerMenu.showMenu(tw);
	}
}



UnitBase.cs


using UnityEngine;
using System.Collections;
using common;

public class UnitBase : MonoBehaviour {
	public float maxHp = 100;
	public float curHp = 100;
	public int gainGold = 5;

	public float moveSpeed = 1.0f;
	public tk2dSpriteAnimator spr;

	private enum CHAR_ANI {UP, DOWN, LEFT, RIGHT, DESTROY};
	private string[] charAniStr = new string[]{"walk_up","walk_down","walk_left","walk_right","destroy"};
	// Use this for initialization
	void Start () {
		curHp = maxHp;
	}
	private bool isMoveAble = false;
	// Update is called once per frame
	void Update () {
		if(!isMoveAble)return;
		if(GameManager.instance.isGameOver){spr.Stop();return;}
		float cellSize = GameManager.instance.cellSize;
		float _speed = cellSize * Time.deltaTime * moveSpeed;
		float rx = (nextPoint.x * cellSize + cellSize/2.0f) - this.transform.localPosition.x;
		float ry = (-nextPoint.y * cellSize - cellSize/2.0f) - this.transform.localPosition.y;
		float dx = _speed * makeNomal(rx);
		float dy = _speed * makeNomal(ry);
		bool isCloseX = false;
		bool isCloseY = false;
		if(Mathf.Abs(dx)>Mathf.Abs(rx)||dx==0){dx = rx;isCloseX = true;}
		if(Mathf.Abs(dy)>Mathf.Abs(ry)||dy==0){dy = ry;isCloseY = true;}
		this.transform.localPosition += new Vector3(dx , dy , 0);
		spr.Sprite.SortingOrder = -(int)this.transform.localPosition.y;
		if(isCloseX && isCloseY)
		{
			if(pathArr.Length <= pathIndex + 1)
			{
				isMoveAble = false;
				//GameManager.instance.removeUnit(this);
				GameManager.instance.reachUnit(this);
				Destroy(this.gameObject);
				return;
			}
			setNextPoint();
		}
	}

	public void setUnit(float hp, float speed, int gold) {
		curHp = maxHp = hp;
		moveSpeed = speed;
		gainGold = gold;
	}

	int makeNomal(float f)
	{
		float k = 0.1f;
		if(f>k)return 1;
		else if(f<-k)return -1;
		else return 0;
	}
	private Point[] pathArr;
	private Point startPoint;
	private Point nextPoint;
	private int pathIndex =0;
	public void setStartPoint(Point p)
	{
		startPoint = p;
		getPath();
		nextPoint = pathArr[pathIndex];
		showCharDir();
		isMoveAble = true;
	}
	public bool getPath()
	{
		float cellSize = GameManager.instance.cellSize;
		startPoint = new Point((int)(this.transform.localPosition.x/cellSize),-(int)(this.transform.localPosition.y/cellSize) );
		int wallMapIndex = GameManager.instance.wallMap[startPoint.x,startPoint.y];
		if(wallMapIndex > 0 && wallMapIndex < 10)return true;

		Point[] pArr = PathFinder.instance.getPath(startPoint, 111);
		if(pArr == null){Debug.Log("NULL path");return false;}
		pathArr = pArr;

		if(nextPoint != null && pathArr.Length > 1 && nextPoint.isEqual(pathArr[1]))pathIndex = 1;
		else pathIndex = 0;
		nextPoint = pathArr[pathIndex];
		showCharDir();
		return true;
	}
	public void attackMe(float dmg)
	{
		curHp -= dmg;
		if(curHp < 0)
		{
			isMoveAble = false;
			spr.Play ("destroy");
			GameManager.instance.addGold(gainGold);
			GameManager.instance.addScore(gainGold);
			GameManager.instance.removeUnit(this);
			spr.AnimationCompleted = unitDestoryAniComplete;
		}
	}
	private void unitDestoryAniComplete(tk2dSpriteAnimator sprite, tk2dSpriteAnimationClip clip)
	{
		Destroy(this.gameObject);
	}
	private void setNextPoint()
	{
		startPoint = nextPoint;
		pathIndex++;
		nextPoint = pathArr[pathIndex];
		showCharDir();
	}
	private void showCharDir()
	{
		
		float cellSize = GameManager.instance.cellSize;
		float nx = (nextPoint.x * cellSize + cellSize/2.0f);
		float ny = (-nextPoint.y * cellSize - cellSize/2.0f);
		if(this.transform.localPosition.x<nx)
			spr.Play (charAniStr[(int)CHAR_ANI.RIGHT]);
		else if(this.transform.localPosition.x>nx)
			spr.Play (charAniStr[(int)CHAR_ANI.LEFT]);
		else if(this.transform.localPosition.y<ny)
			spr.Play (charAniStr[(int)CHAR_ANI.UP]);
		else if(this.transform.localPosition.y>ny)
			spr.Play (charAniStr[(int)CHAR_ANI.DOWN]);
		spr.ClipFps *= moveSpeed;
	}
}


TowerBase.cs


using UnityEngine;
using System.Collections;

public class TowerBase : MonoBehaviour {
	public UnitBase target;
	public float attack = 10.0f;
	private string spriteAtkNameFormat = "tower_a_{0:d2}";
	private string spriteNormalNameFormat = "tower_n_{0:d2}";
	public float range = 100;
	public float shotDelay = 3.0f;
	private float reloadTime = 0;
	private tk2dSprite spr;

	public int totalPrice = 0;
	public int buildPrice = 50;
	private int atkLv = 1;
	private int spdLv = 1;
	private int rngLv = 1;

	public UpgradeInfo[] attackUpgradeInfoArr = new UpgradeInfo[]
	{
		new UpgradeInfo(1, 0, 20), new UpgradeInfo(2, 10, 25), new UpgradeInfo(3, 20, 30), new UpgradeInfo(4, 30, 35), new UpgradeInfo(5, 40, 40)
	};
	public UpgradeInfo[] speedUpgradeInfoArr = new UpgradeInfo[]
	{
		new UpgradeInfo(1, 0, 2.5f), new UpgradeInfo(2, 10, 2.1f), new UpgradeInfo(3, 20, 1.7f), new UpgradeInfo(4, 30, 1.3f), new UpgradeInfo(5, 40, .9f)
	};
	public UpgradeInfo[] rangeUpgradeInfoArr = new UpgradeInfo[]
	{
		new UpgradeInfo(1, 0, 100), new UpgradeInfo(2, 10, 130), new UpgradeInfo(3, 20, 160), new UpgradeInfo(4, 30, 190), new UpgradeInfo(5, 40, 220)
	};
	private UpgradeInfo getInfo(int lv, UpgradeInfo[] infoArr)
	{
		foreach(UpgradeInfo ui in infoArr)
		{
			if(ui.level == lv)return ui;
		}
		return null;
	}
	public int getLvupAtkPrice() { return getLvupAtkPrice(atkLv + 1); }
	public int getLvupAtkPrice(int lv)
	{
		UpgradeInfo info = getInfo(lv, attackUpgradeInfoArr);
		if(info== null)return -1;
		return info.price;
	}
	public int getLvupSpdPrice() { return getLvupSpdPrice(spdLv + 1); }
	public int getLvupSpdPrice(int lv)
	{
		UpgradeInfo info = getInfo(lv, speedUpgradeInfoArr);
		if(info== null)return -1;
		return info.price;
	}
	public int getLvupRngPrice() { return getLvupRngPrice(rngLv + 1); }
	public int getLvupRngPrice(int lv)
	{
		UpgradeInfo info = getInfo(lv, rangeUpgradeInfoArr);
		if(info== null)return -1;
		return info.price;
	}
	public void upgradeAtk(){upgradeAtk(atkLv+1);}
	public void upgradeAtk(int lv)
	{
		UpgradeInfo info = getInfo(lv, attackUpgradeInfoArr);
		if(info== null)return;
		if(!GameManager.instance.checkGold(info.price))return;
		GameManager.instance.useGold(info.price);
		totalPrice += info.price;
		atkLv = info.level;
		attack = info.value;
	}
	public void upgradeSpd(){upgradeSpd(spdLv+1);}
	public void upgradeSpd(int lv)
	{
		UpgradeInfo info = getInfo(lv, speedUpgradeInfoArr);
		if(info== null)return;
		if(!GameManager.instance.checkGold(info.price))return;
		GameManager.instance.useGold(info.price);
		totalPrice += info.price;
		spdLv = info.level;
		shotDelay = info.value;
	}
	public void upgradeRng(){upgradeRng(rngLv+1);}
	public void upgradeRng(int lv)
	{
		UpgradeInfo info = getInfo(lv, rangeUpgradeInfoArr);
		if(info== null)return;
		if(!GameManager.instance.checkGold(info.price))return;
		GameManager.instance.useGold(info.price);
		totalPrice += info.price;
		rngLv = info.level;
		range = info.value;
	}
	// Use this for initialization
	void Start () {
		spr = this.GetComponentInChildren<tk2dSprite>();
		init();
	}

	void init()
	{
		totalPrice = buildPrice;
		reloadTime = shotDelay;
		upgradeAtk(1);
		upgradeSpd(1);
		upgradeRng(1);
	}

	// Update is called once per frame
	void Update () {
		if(GameManager.instance.isGameOver)return;
		spr.SortingOrder = -(int)this.transform.localPosition.y;
		checkRangeTarget();
		lookAtTarget();
		autoShot();
		attackSprAnim();
		updateSprite();
	}

	void checkRangeTarget()
	{
		if(target != null)
		{
			if(range < Vector2.Distance((Vector2)target.transform.localPosition, (Vector2)this.transform.localPosition))target = null;
		}
		if(target == null)
		{
			foreach(UnitBase ub in GameManager.instance.unitList)
			{
				if(range > Vector2.Distance((Vector2)ub.transform.localPosition, (Vector2)this.transform.localPosition))
				{
					target = ub;
					return;
				}
			}
		}
		
	}

	void lookAtTarget()
	{
		if(target == null)return;
		float anglePI = Mathf.Atan2(this.transform.localPosition.y - target.transform.localPosition.y, target.transform.localPosition.x - this.transform.localPosition.x) + Mathf.PI/2.0f;
		int angle = (int)((anglePI/Mathf.PI * 18.0f) + 36)%36;
		spriteN = 18 -Mathf.Abs(angle - 18);
		int spriteDir = angle<18?1:-1;
		spr.scale = new Vector3(spriteDir, 1,1);
		
	}

	void autoShot()
	{
		reloadTime -= Time.deltaTime;
		if(target == null || reloadTime>0)return;
		attackTarget();
		
	}
	bool isAtkSpr = false;
	bool isAttacking = false;
	float attackTimeCount = 0f;
	float attackDuration = .1f;
	float attackDelay = .13f;
	int attackRepeat = 3;
	int attackedCount = 0;

	void attackTarget()
	{
		if(target == null || isAttacking)return;
		attackTimeCount = 0;
		attackedCount = 0;
		isAtkSpr = true;
		isAttacking = true;
		reloadTime = shotDelay;

		target.attackMe(attack);
	}

	void attackSprAnim()
	{
		if(!isAttacking)return;
		attackTimeCount += Time.deltaTime;
		if(isAtkSpr)
		{
			if(attackDuration<attackTimeCount)isAtkSpr = false;
		}
		else if(attackDelay<attackTimeCount)
		{
			attackedCount++;
			if(attackedCount<attackRepeat)
			{
				isAtkSpr = true;
				attackTimeCount -= attackDelay;
			}
			else isAttacking = false;
		}
	}

	private int spriteN = 0;
	void updateSprite()
	{
		string spriteName = string.Format(isAtkSpr?spriteAtkNameFormat:spriteNormalNameFormat, spriteN);
		spr.spriteId = spr.GetSpriteIdByName(spriteName);
	}

}
[System.Serializable]
public class UpgradeInfo
{
	public int level = 0;
	public int price = 0;
	public float value = 0;
	
	public UpgradeInfo(int _level, int _price, float _value)
	{
		level = _level;
		price = _price;
		value = _value;
	}
}






신고
posted by andwhy
Unity3D/TowerDefence 2014.10.05 23:21
앞으로 2회 남았네요...

처음 계획한것과 약간 달라져서.. 좀 더 세분화 된것도 있고, 약식으로 넘어가버린것들도 있네요.
앞으로 2회분의 내용은.

-오늘 WaveData 작성관 5Wave까지 구현..(나머지는 직접 만들어보시면 됩니다.)

-게임 종료와 재시작.

이렇게 2회로 끝내겠습니다.

UI도 넣어서 좀더 게임처럼 보이고 싶었는데, 적당한 UI를 구하질 못했네요.
혹시라도 이글을 보시는 디자이너분중에.. UI작업을 도와주실분있으시면 언제라도 연락 바랍니다.

요즘 업무가 많아지고, 이것저것 신경쓸일이 많아져서, 우선은 다음회까지로 종료하겠습니다.
종료후에 지금까지 했던걸 좀더 다듬어서 재판??으로 만들고, 시간되는데로 좀더 보강하도록 하겠습니다.

//===============================================================================

오늘 작업할 내용은 WaveData 입니다.

WaveData는 한웨이브(스테이지) 에 나올 적들에 대한 데이터 입니다.

지금까지는 수동으로 버튼을 눌러서 적 유닛을 추가해줬지만,

WaveData를 통해서 나오는 적들의 속도, 체력, 나오는 간격...등등을 정해줍니다.

유닛리소스 종류도 정해주면 좋겠지만..지금껏 우리가 작업한 리소스는 유닛이 1종밖엔 없으니 할수 없겠네요.


1. WaveData.cs

웨이브 데이터는 한 웨이브당 출현할 유닛들에 대한 정보를 담고 있습니다.

웨이브에 출현할 유닛에 필요한 정보는

유닛의 HP
유닛 스피드
유닛을 제거할때 얻는 골드량
한번생성될때 몇마리의 유닛이 생성될지
유닛이 생성되고 다음생성때까지의 딜레이
유닛을 총 몇번 생성할것인지,
다음 웨이브까지의 딜레이


정도입니다.

WaveData.cs 파일을 만들고,

다음과 같이 코딩합니다.
(완료된 cs파일들은 마지막에 한번에 모두 공유하도록 하겠습니다.)

WaveData.cs

모든 public 변수들은 위에 설명한 정보들을 저장한것이고,
name 이란 변수와, generateCount 란 변수만 위에서 설명이 없는 변수인데 잠시후에 설명하겠습니다.

그림에 표시된 부분을 보면,메타 테그가 붙어있는데, 지난 타워 업데이트 시간에 UpdateData 클래스에도 동일한 메타테그를 붙였습니다.
기억나실지 모르겠지만, 저 태가가 붙어있으면 인스팩터상에서 해당 클래스의 변수들을 볼수 있게 시리얼라이즈화 시켜줍니다.

다음으로..name 이란 string변수는 크게 쓸모는 없는 변수 이지만, 
인스팩터에 현재 클래스가 표시될때 name에 정해진 이름으로 표시됩니다.

(게임상 크게 필요없지만 팁정도로 생각해주세요.)
우리는 이 name레벨 1~5까지 이름을 붙여 놓겠습니다.


2. Wave 동작 구현

먼저 우리가 이 WaveData를 어떻게 활용할껀지에 대해서 설명을 좀 하는편이 좋을것 같네요.

1. 우선 게임이 시작되면, GameManager 에서 waveDataIndex 를 '0'으로 셋팅합니다.

2. waveDataIndex번째 WaveData를 가져와서 현재 WaveDataList에 추가하고, 다음 wave까지의 딜레이를 셋팅해줍니다.

2-1. GameManagerUpdate때마다 WaveDataList에 추가되어있는 WaveData의 정보를 업데이트 해주고, WaveData는 각각 정보를 업데이트 하다가 유닛을 생성해야할 시간이 되면 GameManager에게 유닛을 추가하라고 요청합니다.

2-2. WaveData는 자기자신을 업데이트 며 총 생성할 유닛을 모두 생성하면 자신을 GameManager에게 자신을 제외하도록 요청합니다.

3. GameManager에서 다음 웨이브 시작시간이 되면, waveDataIndex를 증가 시키고, 2번작업으로 돌아갑니다.


위에 설명한것처럼 GameManager.cs 에 추가 작업을 하겠습니다.

GameManager.cs



그림처럼 GameManager.cs파일에 waveData에 대한 코딩을 추가해줍니다.
isGameStart 는 "게임시작" 버튼을 눌렀을때 true로 바뀌고, false일동안에는 시간체크를 하지않아서 다음 웨이브로 넘어가지 않습니다.

waveIndex는 현재 스테이지 레벨입니다.
waveDataArray는 스테이지 웨이브 데이터로, 각 스테이지의 난이도를 배열로 가지고 있습니다. 위에서 시리얼라이즈 화 시켰기때문에 인스팩터에서 게임을 하면서 난이도를 조절할수 있습니다.

currentWaveDataList는 현재 적용되고 있는 waveData들로, list로 만들어진 이유는, 난이도에 따라 동시에 여러 WaveData가 존재할수 있기때문입니다.
nextWaveDelay와, nextWaveDelayCount는 다음 웨이브데이터 추가를 위한 카운트 값입니다.


그리고, 다음과같이 startGame(), checkNextWave(), updateWaveDataList() 함수를 만들어줍니다.

GameManager.cs

startGame()은 현재는 isStarttrue로 변경하고, 인덱스만 초기화 합니다.

checkNextWave()는 시간을 체크해서 다음 웨이브 시작시간이 되면 웨이브 리스트에 다음웨이브데이터를 복사해서 넣어주고, 다음웨이브 시간 설정, 웨이브 인덱스를 하나 증가시킵니다.
웨이브 데이터를 복사해서 넣는 이유는 복사해서 넣지 않았을경우 게임을 재시작하면 게임도중 변형된 데이터(이미 사용된 데이터들) 이 들어가기때문입니다.

그리고 밑줄친 부분을 보면, 마지막 웨이브가 될경우 다음 웨이브 시간을 음수로 만들어버려서 더이상 갱신되지 않도록 처리해두었습니다.

updateWaveDataList()는 현재 등록되어있는 WaveData들을 갱신시켜줍니다.

OnGUI()에서 addUnit 버튼의 라벨과 기능을 startGame으로 변경합니다.

GameManager.cs


이제 업데이트때 checkNextWave()updateWaveDataList()를 호출해줍니다.

GameManager.cs

여기까지 하면 위에서 설명한 1번, 2번, 2-1번(일부), 3번 내용을 모두 구현한겁니다.

waveDataIndex 를 0으로 초기화 하고,(1번), 다음웨이브데이터를 넣어주고, 다음웨이브 시간을 설정해주고,(2번), updateWaveDataList() 에선 추가되어있는 모든 웨이브데이터를 업데이트 해주고,(2-1번) 다시 checkNextWave() 에선 다음웨이브 시간이 되면 새로운 웨이브 데이터를 추가해줍니다.(3번)

이젠 WaveData에 자신의 정보를 업데이트 시켜줄 update()함수와, 자신을 복제할 clone()함수를 만들겠습니다.

WaveData.cs


clone() 함수는 상당히 단순합니다. 자신과 같은 데이터 형을 새로 생성해주고, public 으로 선언된 변수에 자신의 변수와 동일하게 셋팅해주면 됩니다.

update()함수는 현재 WaveDataMonoBehaviour 를 상속받은게 아니기 떄문에, 자동으로 호출되지는 않고, 다른 클레스(GameManager.cs)에서 호출을 해줘야합니다.
update()의 마지막에 표시된 부분을 보면, 바로 위에서 repeatTime 을 하나씩 감소 시키며 더이상 반복할일이 없을때, GameManagerremoveWaveData를 호출해서 자기 자신을 제외시켜 버립니다.(3번)
그리고 윗쪽 표시를 보면, 생성시간이 되었을때, onceGenerateUnitCount만큼 유닛을 추가하려고 하는데,(지금은 주석으로만 달려있습니다.)

현재는 유닛을 추가할때, 별도로 hp나, 속도를 설정할만한 함수가 없어서 주석으로 표시 해두었습니다.
이제부터 추가하도록 하겠습니다.

먼저 UnitBase.cs를 열고, setUnit() 이란 함수를 만듭니다.

UnitBase.cs

이 함수는 유닛의 hp, speed, 획득 골드를 설정해줍니다.

GameManager.csaddUnit()함수도 다음과 같이 public 으로 변경하고, 파라미터를 받아올수 있게 수정합니다.

GameManager.cs

유닛을 추가한뒤엔 hp와, speed, 획득 Gold를 유닛에게 셋팅해주면 됩니다.

이제 다시 WaveData.cs 로 가서 update함수에 주석으로 되어있던 addUnit을 구현해보겠습니다.

WaveData.cs

사진처럼 GameManageraddUnit을 각각 파라미터를 넣고 호출해주면 끝납니다.

마지막으로 GameManager 의 인스펙터에 WaveData를 5개 정도 만들고 간단히 설정해서 테스트 해보겠습니다.
인스펙터의 WaveDataArray 항목을 열어서 size에 5 를 입력하고 엔터를 처줍니다.

생성된 5개의 WaveData에 다음처럼 값을 넣어줍니다.(값은 제가 임의로 넣은것이기에, 테스트 할때는 마음껏 변경해보시기 바랍니다.)

이제 플레이버튼을 눌러서 "start Game" 버튼을 눌러보면 마린들이 적당한 간격을 두고 나오는것을 볼수 있습니다.

하지만 잠시뒤엔 다음과 같은 에러문구 볼수 있습니다.

이 에러가 생기는 원인은 바로 GameManager의 removeWaveData() updateWaveDataList() 함수가 원입니다.
updateWaveDataList() 에서 foreach 로 
currentWaveDataList를 검색하는 중에 , removeWaveData가 동작되어 currentWaveDataList의 인덱스가 엉키면서 생기는문제입니다.
당장 보기엔 별문제 없어보이지만, Error는 Error 이니까 해결하고 가도록 하겠습니다.

이를 해결하기 위해서 바로 currentWaveDataList에서 WaveData를 삭제 하지 않고 다른방법을 사용하겠습니다.
currentWaveDataList에서 바로 삭제하지 않고 , 삭제할 WaveData를 따로 저장했다가 
currentWaveDataList 업데이트가 끝나면 지울 리스트를 한번에 삭제하도록 합니다.

먼저 지울 리스트를 생성하고, removeWaveData 함수에서 currentWaveDataList에서 삭제 대신에 지울 리스트에 추가합니다.

GameManager.cs

그리고 updateWaveDataList() 에서 모든 업데이트가 끝난뒤 아래처럼 제거 대상 데이터들을 삭제해줍니다.

GameManager.cs

이제 다시 플레이버튼을 누르고 테스트 해보면 더이상 에러는 보이지 않습니다.

3. 추가 정보 및 스킵 버튼

지금 상태로 게임을 해보면 다음 다음웨이브까지 얼마나 남았는지 알수가 없고, 미리 유닛을 모두 제거한 상태라면, 빨리 다음웨이브를 진행하고 싶어집니다.

그래서 이번엔 간단하게 다음웨이브까지 남은 시간을 표시해주고, skip버튼을 만들어보겠습니다.

먼저 다음 웨이브까지 시간을 표시하겠습니다.

GameManager.cs

처음 if문을 보면, isGameStart false인경우(아직 게임이 시작되지 않음)는 이전처럼 StartGame버튼을 노출합니다.
isGameStart true 가 될경우(이미 게임이 시작된경우)에는 nextWave라는 skip버튼을 노출합니다.
skip버튼에는 nextWaveDelaycount값을 동일하게 만들어주면,  checkNextWave() 에서 자연스럽게 다음 웨이브를 시작하게 됩니다.

아랫쪽 표시한부분을 보면, 아주 간단하게 다음 웨이브의 이름과, 남은시간을 표시해주게 되어있습니다.
크게 복잡한 내용은 없고 string.Format에서 "F2"라는 옵션은 "고정 소수점" 을 표시하는 옵션으로 뒤에 2 가 붙었으니 항상 소수점 2째 자리까지 표시해줍니다.

게임을 시작해보면 다음과 같이 정보가 보입니다.


4. 소스 파일

오늘 작업한 소스파일들을 올려드립니다.
잘안되거나 이해안가는 부분이 있으면 참고 하시기 바랍니다.


WaveData.cs


using System;
using UnityEngine;

[System.Serializable]
public class WaveData
{
	public string name = "level_";
	
	public float unitHP = 0;
	public float unitSpeed = 0;
	public int unitGold = 0;
	public int onceGenerateUnitCount = 1;
	public float generateDelay = 1.0f;
	private float generateCount = 0;
	
	public int repeatTime = 10;
	public float nextWaveDelay = 30.0f;
	public WaveData clone()
	{
		WaveData retWd = new WaveData();
		retWd.unitHP = unitHP;
		retWd.unitSpeed = unitSpeed;
		retWd.unitGold = unitGold;
		retWd.onceGenerateUnitCount = onceGenerateUnitCount;
		retWd.generateDelay = generateDelay;
		retWd.generateCount = generateDelay;
		retWd.repeatTime = repeatTime;
		retWd.nextWaveDelay = nextWaveDelay;
		return retWd;
	}
	public void update()
	{
		generateCount += Time.deltaTime;
		if(generateDelay <= generateCount)
		{
			generateCount -= generateDelay;
			for(int i = 0; i < onceGenerateUnitCount; i++)
			{
				GameManager.instance.addUnit(unitHP, unitSpeed, unitGold);
			}
			repeatTime--;
			if(repeatTime<=0)GameManager.instance.removeWaveData(this);
		}
	}
}


GameManager.cs


using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using common;

public class GameManager : MonoBehaviour {
	private static GameManager _instance = null;
	public static GameManager instance
	{
		get{
			if(_instance == null)Debug.LogError("GameManager is NULL");
			return _instance;
		}
	}

	void Awake()
	{
		_instance = this;
		init();
	}
	//=========================================
	public float cellSize = 40.0f;
	public int[,] wallMap = 
	{{1, 1, 1, 1, 1, 11, 11, 11, 1, 1, 1, 1, 1},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{1, 1, 1, 1, 1, 111, 111, 111, 1, 1, 1, 1, 1}};
	[HideInInspector]
	public List<UnitBase> unitList = new List<UnitBase>();
	[HideInInspector]
	public Dictionary<Point, TowerBase> towerDic = new Dictionary<Point, TowerBase>();
	public BgCellDisplayer bgGrid;
	public Transform unit_field;
	public UnitBase unit_marine;
	public TowerBase tower;
	public TowerUIMenu towerMenu;
	
	public int startedGold = 100;
	public int currentGold = 0;

	public Camera mainCam;

	private bool isGameStart = false;
	public int waveIndex = 0;
	//-----------waveData
	public WaveData[] waveDataArray;
	private List<WaveData> currentWaveDataList = new List<WaveData>();
	private List<WaveData> removeWaveDataList = new List<WaveData>();
	private float nextWaveDelay = 0f;
	private float nextWaveDelayCount = 0;
	
	public void addWaveData(WaveData wd)
	{
		currentWaveDataList.Add(wd);
	}
	public void removeWaveData(WaveData wd)
	{
		removeWaveDataList.Add (wd);
	}


	private void init()
	{
		unitList.Clear();
		towerDic.Clear();
		initPathFinder();

		currentGold = startedGold;
	}

	public void addUnit(float hp, float speed, int gold)
	{
		UnitBase unit = Instantiate(unit_marine) as UnitBase;
		unitList.Add(unit);
		unit.setUnit(hp, speed, gold);
		unit.transform.parent = unit_field;
		Point startPoint = getStartPoint();
		unit.transform.localPosition = new Vector3(startPoint.x * cellSize + cellSize/2.0f, -startPoint.y * cellSize - cellSize/2.0f);
		unit.setStartPoint(startPoint);
		
	}

	void addTower(Point p)
	{
		
		TowerBase tw = Instantiate(tower) as TowerBase;
		tw.transform.parent = unit_field;
		tw.transform.localPosition = new Vector3(p.x * cellSize + cellSize/2.0f, -p.y * cellSize - cellSize);
		towerDic.Add(p, tw);
		
		useGold(tw.buildPrice);
	}
	public void addGold(int g)
	{
		currentGold += g;
	}
	public void useGold(int g)
	{
		currentGold -= g;
	}
	public bool checkGold(int g)
	{
		return (currentGold>=g);
	}
	public void removeUnit(UnitBase ub)
	{
		unitList.Remove(ub);
	}
	void researchPathUnits()
	{
		foreach(UnitBase ub in unitList)
		{
			if(ub!=null)
			{
				ub.getPath();
			}
		}
	}
	Point getStartPoint()
	{
		List<Point> startPointList = getStartPointList();
		if(startPointList.Count == 0)
		{
			Debug.LogError("Not Found Start Position");
			return null;
		}
		int ranIdx = Random.Range(0,startPointList.Count);
		return startPointList[ranIdx];
	}
	List<Point> getStartPointList()
	{
		//check startPoints
		List<Point> startPointList = new List<Point>();
		int _w = wallMap.GetLength(0);
		int _h = wallMap.GetLength(1);
		int x,y;
		for (x = 0; x < _w; x++)
		{
			for(y = 0; y < _h; y++)
			{
				if(wallMap[x,y] >= 10 && wallMap[x,y] <= 100)
				{
					startPointList.Add(new Point(x,y));
				}
			}
		}
		return startPointList;
	}
	public void initPathFinder()
	{
		PathFinder.instance.setMapData(wallMap);
	}
	void OnGUI()
	{
		if(!isGameStart)
		{
			if(GUI.Button( new Rect( 10, 10, 100, 40), "Start Game"))
			{
				startGame();
			}
		}
		else 
		{
			if(GUI.Button( new Rect( 10, 10, 100, 40), "nextWave!!"))
			{
				nextWaveDelay = nextWaveDelayCount;
			}
		}
		GUI.Label(new Rect( (Screen.width - 200)/2.0f, 10, 200, 50), "GOLD : "+currentGold);
		if(waveIndex < waveDataArray.Length)
			GUI.Label(new Rect( (Screen.width - 200), 10, 200, 50), 
			          string.Format("NextWave [{0}] : {1:F2}",waveDataArray[waveIndex].name,(nextWaveDelay-nextWaveDelayCount)));
	}
	void Update()
	{
		checkNextWave();
		//updateWaveDatas
		updateWaveDataList();

		if(Input.GetMouseButtonDown(0) && !towerMenu.isShow) 
		{
			Vector2 pos = Input.mousePosition;
			Vector3 mouseP = mainCam.ScreenToWorldPoint(pos) - unit_field.TransformPoint(Vector3.zero);
			Point myPos = new Point((int)(mouseP.x/cellSize), -(int)(mouseP.y/cellSize));
			if(towerDic.ContainsKey(myPos))
			{
				showMenu(towerDic[myPos]);
			}
			else 
			{
				buildTower(myPos);
			}
		}
	}
	void startGame()
	{
		waveIndex = 0;
		isGameStart = true;
	}

	void checkNextWave()
	{
		if(!isGameStart)return;
		if(nextWaveDelay<0)return;
		nextWaveDelayCount += Time.deltaTime;
		if(nextWaveDelay<nextWaveDelayCount)
		{
			nextWaveDelayCount = 0;
			if(waveIndex >= waveDataArray.Length)
			{
				nextWaveDelay = -1;
			}
			else
			{
				addWaveData(waveDataArray[waveIndex].clone());
				nextWaveDelay = waveDataArray[waveIndex].nextWaveDelay;
				waveIndex++;
			}
		}
	}

	void updateWaveDataList()
	{
		foreach( WaveData wd in currentWaveDataList)
		{
			wd.update();
		}
		foreach( WaveData wd in removeWaveDataList)
		{
			if(currentWaveDataList.Contains(wd))currentWaveDataList.Remove(wd);
		}
		removeWaveDataList.Clear();
	}

	bool checkReachAble()
	{
		PathFinder.instance.setCheckMode(true);
		foreach(Point sp in getStartPointList())
		{
			if(PathFinder.instance.getPath(sp,100+wallMap[sp.x,sp.y]) == null)
			{
				Debug.Log("StartPoint Path NULL");
				return false;
			}
		}
		foreach(UnitBase unit in unitList)
		{
			if(!unit.getPath())
			{
				Debug.Log("Unit Path NULL");
				return false;
			}
		}
		PathFinder.instance.setPath();
		return true;
	}
	void buildTower(Point p)
	{
		if(p.x<0||p.y<0 || p.x >= wallMap.GetLength(0) || p.y >= wallMap.GetLength(1))return;
		int prevIndex = wallMap[p.x, p.y];
		
		if(wallMap[p.x, p.y] == 0)wallMap[p.x, p.y] = 2;
		else return;
			
		if(checkGold(tower.buildPrice) && checkReachAble())
		{
			bgGrid.refreshDisplay();
			researchPathUnits();
			addTower(p);
		}
		else 
		{
			wallMap[p.x, p.y] = prevIndex;
		}
		PathFinder.instance.setCheckMode(false);
	}
	public void sellTower(TowerBase tw)
	{
		if(!towerDic.ContainsValue(tw))return;
		addGold(tw.totalPrice/2);
		removeTower(tw);
	}
	public void removeTower(TowerBase tw)
	{
		if(!towerDic.ContainsValue(tw))return;
		foreach(Point keyP in towerDic.Keys)
		{
			if(towerDic[keyP] == tw)
			{
				if(keyP.x<0||keyP.y<0 || keyP.x >= wallMap.GetLength(0) || keyP.y >= wallMap.GetLength(1))return;
				wallMap[keyP.x, keyP.y] = 0;
				towerDic.Remove(keyP);
				break;
			}
		}
		Destroy(tw.gameObject);
		bgGrid.refreshDisplay();
		checkReachAble();
		PathFinder.instance.setPath();
		PathFinder.instance.setCheckMode(false);
	}
	void showMenu(TowerBase tw)
	{
		towerMenu.showMenu(tw);
	}
}



UnitBase.cs


using UnityEngine;
using System.Collections;
using common;

public class UnitBase : MonoBehaviour {
	public float maxHp = 100;
	public float curHp = 100;
	public int gainGold = 5;

	public float moveSpeed = 1.0f;
	public tk2dSpriteAnimator spr;

	private enum CHAR_ANI {UP, DOWN, LEFT, RIGHT, DESTROY};
	private string[] charAniStr = new string[]{"walk_up","walk_down","walk_left","walk_right","destroy"};
	// Use this for initialization
	void Start () {
		curHp = maxHp;
	}
	private bool isMoveAble = false;
	// Update is called once per frame
	void Update () {
		if(!isMoveAble)return;
		float cellSize = GameManager.instance.cellSize;
		float _speed = cellSize * Time.deltaTime * moveSpeed;
		float rx = (nextPoint.x * cellSize + cellSize/2.0f) - this.transform.localPosition.x;
		float ry = (-nextPoint.y * cellSize - cellSize/2.0f) - this.transform.localPosition.y;
		float dx = _speed * makeNomal(rx);
		float dy = _speed * makeNomal(ry);
		bool isCloseX = false;
		bool isCloseY = false;
		if(Mathf.Abs(dx)>Mathf.Abs(rx)||dx==0){dx = rx;isCloseX = true;}
		if(Mathf.Abs(dy)>Mathf.Abs(ry)||dy==0){dy = ry;isCloseY = true;}
		this.transform.localPosition += new Vector3(dx , dy , 0);
		spr.Sprite.SortingOrder = -(int)this.transform.localPosition.y;
		if(isCloseX && isCloseY)
		{
			if(pathArr.Length <= pathIndex + 1)
			{
				isMoveAble = false;
				GameManager.instance.removeUnit(this);
				Destroy(this.gameObject);
				return;
			}
			setNextPoint();
		}
	}

	public void setUnit(float hp, float speed, int gold) {
		curHp = maxHp = hp;
		moveSpeed = speed;
		gainGold = gold;
	}

	int makeNomal(float f)
	{
		float k = 0.1f;
		if(f>k)return 1;
		else if(f<-k)return -1;
		else return 0;
	}
	private Point[] pathArr;
	private Point startPoint;
	private Point nextPoint;
	private int pathIndex =0;
	public void setStartPoint(Point p)
	{
		startPoint = p;
		getPath();
		nextPoint = pathArr[pathIndex];
		showCharDir();
		isMoveAble = true;
	}
	public bool getPath()
	{
		float cellSize = GameManager.instance.cellSize;
		startPoint = new Point((int)(this.transform.localPosition.x/cellSize),-(int)(this.transform.localPosition.y/cellSize) );
		int wallMapIndex = GameManager.instance.wallMap[startPoint.x,startPoint.y];
		if(wallMapIndex > 0 && wallMapIndex < 10)return true;

		Point[] pArr = PathFinder.instance.getPath(startPoint, 111);
		if(pArr == null){Debug.Log("NULL path");return false;}
		pathArr = pArr;

		if(nextPoint != null && pathArr.Length > 1 && nextPoint.isEqual(pathArr[1]))pathIndex = 1;
		else pathIndex = 0;
		nextPoint = pathArr[pathIndex];
		showCharDir();
		return true;
	}
	public void attackMe(float dmg)
	{
		curHp -= dmg;
		if(curHp < 0)
		{
			isMoveAble = false;
			spr.Play ("destroy");
			GameManager.instance.addGold(gainGold);
			GameManager.instance.removeUnit(this);
			spr.AnimationCompleted = unitDestoryAniComplete;
		}
	}
	private void unitDestoryAniComplete(tk2dSpriteAnimator sprite, tk2dSpriteAnimationClip clip)
	{
		Destroy(this.gameObject);
	}
	private void setNextPoint()
	{
		startPoint = nextPoint;
		pathIndex++;
		nextPoint = pathArr[pathIndex];
		showCharDir();
	}
	private void showCharDir()
	{
		
		float cellSize = GameManager.instance.cellSize;
		float nx = (nextPoint.x * cellSize + cellSize/2.0f);
		float ny = (-nextPoint.y * cellSize - cellSize/2.0f);
		if(this.transform.localPosition.x<nx)
			spr.Play (charAniStr[(int)CHAR_ANI.RIGHT]);
		else if(this.transform.localPosition.x>nx)
			spr.Play (charAniStr[(int)CHAR_ANI.LEFT]);
		else if(this.transform.localPosition.y<ny)
			spr.Play (charAniStr[(int)CHAR_ANI.UP]);
		else if(this.transform.localPosition.y>ny)
			spr.Play (charAniStr[(int)CHAR_ANI.DOWN]);
		spr.ClipFps *= moveSpeed;
	}
}






신고
posted by andwhy

티스토리 툴바