반응형

1번 : 이미지 가장자리에 여백이 없고 Filter Mode를 Bilinear로 세팅.

2번 : 이미지 가장자리에 여백이 없고 Filter Mode를 Pointer로 세팅.

3번 : 이미지 가장자리에 4방으로 2px의 여백을 주고 Filter Mode를 Bilinear로 세팅.

4번 : 이미지 가장자리에 4방으로 2px의 여백을 주고 Filter Mode를 pointer로 세팅.

UI Canvas에서 설정 별 이미지 깨짐 현상 비교

보통 Sprite 이미지를 임포트 해서 Canvas 상에 올린 후 로테이션값을 조절해 사용하다보면 위 이미지에서 처럼 가장자리가 깨지거나 혹은 가장자리만 깨지는 현상을 경험할 수 있다. 결론부터 얘기해서 3번처럼 보이게 하기 위해서는 포토샵에서 아래처럼 이미지의 바깥쪽에 1~2px의 여백을 만들어 가져오면 해결된다. 가져온 이미지는 Filtermode를 Bilinear 또는 Trilinear로 선택해주고 Apply시켜준다.

포토샵에서 이미지 가장자리에 강제로 여백을 준다.
Filter Mode

 

 

참고 : https://gamedev.stackexchange.com/questions/139626/is-ui-anti-aliasing-broken-in-unity-5-6

 

Is UI anti-aliasing broken in Unity 5.6?

I have a project that was started in Unity 5.5. When I open it in 5.6, most 2D graphics look like they're not anti-aliased: left is 5.6, right is 5.5 What you see there are UI Images in a Canvas....

gamedev.stackexchange.com

 

반응형
반응형

아무 게임오브젝트에 해당 스크립트를 붙이면 Debug가 출력 될 때 화면에 똑같이 출력해준다.

에디터에서는 필요없겠지만 기기에서 테스트할 때 편리하다.

 

using System.Collections;
using UnityEngine;

public class DebugToScreen : MonoBehaviour {
 	string myLog;
 	Queue myLogQueue = new Queue ();

 	void OnEnable () {
 		Application.logMessageReceived += HandleLog;
 	}

 	void OnDisable () {
 		Application.logMessageReceived -= HandleLog;
 	}

 	void HandleLog (string logString, string stackTrace, LogType type) {
 		myLog = logString;
 		string newString = "\n [" + type + "] : " + myLog;
 		myLogQueue.Enqueue (newString);
 		if (type == LogType.Exception) {
 			newString = "\n" + stackTrace;
 			myLogQueue.Enqueue (newString);
 		}
 		myLog = string.Empty;
 		foreach (string mylog in myLogQueue) {
 			myLog += mylog;
 		}
 	}

 	void OnGUI () {
 		GUILayout.Label (myLog);
	}
}

 

반응형
반응형

핸드폰에서는 자이로센서를 사용하면 되겠지만...

고정되어있는 디바이스에서 화면에 공간적인 느낌을 낼 수 있는 방법중 하나가 카메라같은 센서를 사용하여 사람을 인식 한 후 위치를 가져와 화면을 움직을 수 있을 것이다. 대표적인 센서로는 키넥트나 인텔에서 나온 리얼센스카메라를 사용하면 되겠으나 그렇게 되면 배보다 배꼽이 더 커지는게 아닐까 싶다. 소프트웨어로 처리할 수 있는 기술도 있는데 대표적인게 Open CV라는 녀석인듯 하다. 근데 이놈도 유니티에서 사용하려니 95달러나... 다른 경로를 통해 검색해 보니 좀 낮은 버전을 얻을 수 있었다.

 

하지만 테스트 해본 결과 얼굴인식이 그다지 잘 되지는 않는것 같다. 간혹 얼굴을 측면으로 돌린다거나 멀어지면 인식이 되지 않는 경우가 많다. 그래서 고민하다 화면 전체의 이전 프레임과 현재프레임의 픽셀을 비교하고 달라지는 픽셀을 검출하는 방식으로 달라지는 픽셀들의 위치값의 평균을 가져오면 얼굴인식처럼 사람위치를 트래킹할 수 있겠다는 생각을 해보았다.

 

픽셀의 위치값을 가져오기 위해 해당 픽섹의 위치에 GameObject를 만들어야 하는데 해상도 만큼 만들게 되면 기기가 뻗을 수도 있다. 웹캠 해상도가 1280x720이면 921,600개를 만들어야 하고 매프레임마다 변화값을 체크해야하니 무리가 될 수 밖에 없겠다.

그래서 적당히 픽셀을 건너뛰고 오브젝트를 만들었다. 아래 코드는 20픽셀씩 스킵하였다. 기기가 조금 뜨거워 지는듯 하지만 적당히 잘 돌아간다.

 

아래코드는  웹캠을 통해 화면을 받아오고 픽셀을 검출해서 비교하기 위한 코드이다.

using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using UnityEngine;
using UnityEngine.UI;

public class WebCamTracker : MonoBehaviour {
	WebCamTexture webCamTexture;
	WebCamDevice webCamDevice;
	public RawImage rawImage;
	int width = 1280, height = 720;

	[SerializeField]
	bool isCamOn;
	[SerializeField]
	GameObject Pref;

	static List<Color> NowColor;
	List<PixelHandler> ListGameObj, ListOn;
	public RectTransform RT_Pos;
	public Transform T_Text, T_Head;
	public TextMesh[] ArrText;
	public Slider Slider;
	public float Value;
	public Text Sensitivity;

	int skip = 20;
	Vector2 LastPos;
	float CutOffMaxPixelCount = 1000;

	void Start () {
		if (Application.platform == RuntimePlatform.Android)
			webCamDevice = WebCamTexture.devices[1];
		else
			webCamDevice = WebCamTexture.devices[0];
		webCamTexture = new WebCamTexture (webCamDevice.name, width, height);
		rawImage.texture = webCamTexture;
		webCamTexture.Play ();
		rawImage.GetComponent<RectTransform> ().SetSizeWithCurrentAnchors (RectTransform.Axis.Horizontal, width);
		rawImage.GetComponent<RectTransform> ().SetSizeWithCurrentAnchors (RectTransform.Axis.Vertical, height);
		NowColor = new List<Color> ();
		makePixels ();
		Slider.onValueChanged.AddListener (ValueChange);
		ValueChange (Slider.value);
	}
	void ValueChange (float value) {
		Value = (value * CutOffMaxPixelCount);
		Value = Mathf.Round (Value);
		Sensitivity.text = "Cut Off Pixel : " + Value.ToString ();
	}
	void makePixels () {
		ListGameObj = new List<PixelHandler> ();
		ListOn = new List<PixelHandler> ();
		for (int y = 0; y < height / skip; y++) {
			for (int x = 0; x < width / skip; x++) {
				GameObject go = Instantiate (Pref);
				go.SetActive (true);
				go.transform.SetParent (Pref.transform.parent);
				go.transform.localScale = new Vector3 (1f, 1f, 1f);
				go.transform.localPosition = new Vector2 (x * skip - width / 2 + skip - 10, y * skip - height / 2 + 30);
				go.GetComponent<PixelHandler> ().CG.alpha = 0.5f;
				ListGameObj.Add (go.GetComponent<PixelHandler> ());
			}
		}
		Debug.Log ("make ListGameObj");
	}

	static float posX, posY;

	IEnumerator ComparePixel () {
		yield return new WaitForSeconds (0.1f);

		NowColor.Clear ();
		for (int y = 0; y < webCamTexture.height / skip; y++) {
			for (int x = 0; x < webCamTexture.width / skip; x++) {
				NowColor.Add (webCamTexture.GetPixel (x * skip, y * skip));
			}
		}

		ListOn.Clear ();
		for (int i = 0; i < ListGameObj.Count; i++) {
			ListGameObj[i].Img.color = NowColor[i];

			if (ListGameObj[i].isOn)
				ListOn.Add (ListGameObj[i]);

			if (ListGameObj[i].isOn) {
				ListGameObj[i].RT.DOScale (1f, 0.2f).SetEase (Ease.Linear);
				ListGameObj[i].RT.DOLocalRotate (new Vector3 (0, 0, 45), 0.2f).SetEase (Ease.Linear);
			} else {
				ListGameObj[i].RT.DOScale (0f, 0.2f).SetEase (Ease.Linear);
				ListGameObj[i].RT.DOLocalRotate (new Vector3 (0, 0, 0), 0.2f).SetEase (Ease.Linear);
			}
		}
		for (int i = 0; i < ArrText.Length; i++) {
			ArrText[i].text = "True Pixel Count : " + ListOn.Count;
		}
		if (ListOn.Count > Value) {
			for (int i = 0; i < ListOn.Count; i++) {
				posX = posX + ListOn[i].RT.anchoredPosition.x;
				posY = posY + ListOn[i].RT.anchoredPosition.y;
			}
			posX = Mathf.Clamp (posX / ListOn.Count, -width / 2, width / 2);
			posY = Mathf.Clamp (posY / ListOn.Count, -width / 2, width / 2);
			if (!float.IsNaN (posX) && !float.IsNaN (posY))
				try {
					RT_Pos.DOLocalMove (new Vector3 (-posX, posY * 0.4f, 24), 0.4f).SetEase (Ease.Linear);
				} catch { }
			LastPos = new Vector2 (-posX, posY);
		} else {
			RT_Pos.DOLocalMove (LastPos, 0.4f).SetEase (Ease.Linear);
		}
		posX = posY = 0;

		StartCoroutine ("ComparePixel");

	}
	void Update () {

		if (webCamTexture.width > 16) {
			if (!isCamOn) {
				width = webCamTexture.width;
				height = webCamTexture.height;
				isCamOn = true;
				StartCoroutine ("ComparePixel");
				// for (int i = 0; i < ArrText.Length; i++) {
				// 	ArrText[i].text = width + "X" + height;
				// }
			}
			T_Head.DOLocalRotate (new Vector3 (RT_Pos.anchoredPosition.x * 0.08f, 0, 30 + RT_Pos.anchoredPosition.y * 0.2f), 0.3f).SetEase (Ease.Linear);
			T_Text.DOLocalRotate (new Vector3 (7 + RT_Pos.anchoredPosition.y * 0.1f, -RT_Pos.anchoredPosition.x * 0.1f, 0), 0.3f).SetEase (Ease.Linear);
		}
	}
}

 

아래코드는 해당 픽셀에 붙는 코드이다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class PixelHandler : MonoBehaviour {
	public Image Img;
	public RectTransform RT;
	public CanvasGroup CG;
	public bool isOn;
	Color MyTempColor1;
	// public Color FistFrame;
	float min = 0.6f, max = 1.4f;

	void Start () {
		StartCoroutine (OnCheck ());
	}
	IEnumerator OnCheck () {

		MyTempColor1 = Img.color;
		yield return new WaitForSeconds (0.1f);
		if (
			MyTempColor1.r * max > Img.color.r && MyTempColor1.r * min < Img.color.r ||
			MyTempColor1.g * max > Img.color.g && MyTempColor1.r * min < Img.color.g ||
			MyTempColor1.b * max > Img.color.b && MyTempColor1.r * min < Img.color.b
		)
			isOn = false;
		else
			isOn = true;

		StartCoroutine (OnCheck ());
	}
}

 

 

반응형
반응형

유니티의 UI컴포넌트를 많이 사용하는데, ScrollRect도 당연히 많이 사용하게 된다.

ScrollRect로 리스트 구현할 때 간혹 리스트 안에 Button을 붙이는 경우가 생기는데 Button과 ScrollRect간에 이벤트가 공유되지 않아 원치 않은 상황이 생기게 된다. 예를 들면 Button위에서 Swipe을 하게 될 경우 이런경우 스크립으로 버튼오브젝트에 발생되는 터치 이벤트 정보를 스크롤렉트로 동일하게 잘 넘겨주면 되지만 ... 아무튼 귀찮다.

이번꺼는 버튼만 정리한 내용이다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class ButtonTouchHandler : MonoBehaviour, IDragHandler, IPointerDownHandler, IPointerClickHandler {
	bool isEnable;
	Button Btn_Target;

	void Start () {
		Btn_Target = this.gameObject.GetComponent<Button> ();
		Btn_Target.interactable = false;
	}
	public void OnPointerDown (PointerEventData e) {
		isEnable = true;
		Btn_Target.interactable = false;
	}
	public void OnDrag (PointerEventData e) {
		if (Mathf.Abs (e.delta.x) > 2 || Mathf.Abs (e.delta.y) > 2) {
			Btn_Target.interactable = false;
			isEnable = false;
		}
	}
	public void OnPointerClick (PointerEventData e) {
		if (isEnable) {
			Btn_Target.interactable = true;
			Btn_Target.OnPointerClick (e);
		}
	}
}

 

추가로 상위 ScrollRect로 이벤트를 공유하는 스크립은 이렇게...

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class MyEvent : UnityEvent<Vector2> { }

public class Synchronizer : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler {
	public ScrollRect ParentSR;
    
	public void OnBeginDrag (PointerEventData e) {
		ParentSR.OnBeginDrag (e);
	}
	public void OnDrag (PointerEventData e) {
		ParentSR.OnDrag (e);
		if (Mathf.Abs (e.delta.x) > 2 || Mathf.Abs (e.delta.y) > 2) {
			isDrag = true;
		}
	}
	public void OnEndDrag (PointerEventData e) {
		ParentSR.OnEndDrag (e);
	}
}
반응형
반응형
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using DG.Tweening;
using UnityEngine.SceneManagement;

public delegate void ProgressDelegate(float progress);

public class LoadSceneAsyncProgress : MonoBehaviour {
	public Image ProgressBar;
	public CanvasGroup CG_Loading;

	void Start() {
		CG_Loading.DOFade (0.5f, 0.5f).SetEase (Ease.Linear).SetLoops (-1, LoopType.Yoyo);
		StartCoroutine(LoadSceneAsyncByName("FamilyHomeVI_SR", OnLoadLevelProgressUpdate));

	}
	
	public static IEnumerator LoadSceneAsyncByName(string nextLevel, ProgressDelegate progressDelegate) {
		AsyncOperation async = SceneManager.LoadSceneAsync(nextLevel);
	
		while (!async.isDone) {
			progressDelegate (async.progress);
			async.allowSceneActivation = async.progress > 0.8;
			yield return null;
		}
	}

	private void OnLoadLevelProgressUpdate(float progress) {
		ProgressBar.fillAmount = progress;
		Debug.Log ("async.progress: " + progress);
	}
}

참조 : https://gist.github.com/crowjdh/26272392e425063cbd69586cd542a46d

 

Getting progress while loading scene in Unity using SceneManager.LoadSceneAsync.

Getting progress while loading scene in Unity using SceneManager.LoadSceneAsync. - UnityLoadSceneAsyncProgress.cs

gist.github.com

참조 : https://docs.unity3d.com/ScriptReference/AsyncOperation-progress.html

 

Unity - Scripting API: AsyncOperation.progress

Return an operation's progress. (Read Only) This returns how close the operation is to finishing. The operation is finished when the progress float reaches 1.0 and isDone is called. If you set allowSceneActivation to false, progress is halted at 0.9 until

docs.unity3d.com

 

반응형
반응형

유니티에서 드로우 구현 중 그려진 텍스쳐를 UI 쪽으로 옮겨오는 과정

1. 드로우되는 머트리얼의 RenderTexture를 화면 사이즈로 ReadPixels 하여 Texture2D로 가져온다.

2. ReadPixels로 가져와진 Texture2D에 각 픽섹들을 GetPixel 해서 상하 좌우 필요 없는 부분의 여백을 찾아낸다.

3. 여백많큼 다시 GetPixel 하여 크롭 된 컬러 데이터를 만들어 리사이즈된 Texture2D로 만든다.

4. Sprite.Create로 크롭된 데이터를 Sprite로 만들어준 후 UI Image에 넣어준다.

끝.

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;

public class ScreenShot : MonoBehaviour {
	public EP_DrawPadRT DrawPad;

	private int resWidth;
	private int resHeight;
	public GameObject Pref;
	static int startX, startY, endX, endY;
	static int _width, _height;
	Texture2D screenshot, resizeTexture;
	Image img;
	Rect rect;
	public GameObject GO_Dim;

	private Material DrawMat;

	//===================== Test =======================
	public Button Btn_Capture;
	public GameObject GO_Image;

	// Use this for initialization
	void Start () {
		startX = Screen.width;
		startY = 0;
		endX = 0;
		endY = Screen.height;
		resWidth = Screen.width;
		resHeight = Screen.height;
		if (Btn_Capture)
			Btn_Capture.onClick.AddListener (TakeScreenshot);

		DrawMat = DrawPad.GetComponent<Renderer> ().material;
	}
	bool takeScreenshotOnNextFrame;

	Texture2D toTexture2D (RenderTexture rTex) {
		Texture2D tex = new Texture2D (resWidth, resHeight, TextureFormat.RGBA32, false);

		RenderTexture.active = rTex;
		tex.ReadPixels (new Rect (0, 0, rTex.width, rTex.height), 0, 0);
		return tex;
	}

	Color[] destPix;
	public float warpFactor = 1.0f;

	public void TakeScreenshot () {
		startX = Screen.width;
		startY = 0;
		endX = 0;
		endY = Screen.height;
		resWidth = Screen.width;
		resHeight = Screen.height;

		screenshot = toTexture2D (DrawMat.mainTexture as RenderTexture);

		for (int x = 0; x < resWidth - 2; x++) {
			for (int y = 0; y < resHeight - 2; y++)
				if (screenshot.GetPixel (x, y).a > 0.3f) {
					if (startX > x)
						startX = x;
					if (endY > y)
						endY = y;
				}
		}
		for (int x = resWidth - 2; x > 0; x--) {
			for (int y = resHeight - 2; y > 0; y--)
				if (screenshot.GetPixel (x, y).a > 0.3f) {
					if (endX < x)
						endX = x;
					if (startY < y)
						startY = y;
				}
		}
		_width = endX - startX + 4;
		_height = startY - endY + 4;

		resizeTexture = new Texture2D (_width, _height, TextureFormat.RGBA32, false);

		#region Bilinear-----------------------
		// /*
		destPix = new Color[_width * _height];

		for (int x = 0; x < resWidth; x++) {
			for (int y = 0; y < resHeight; y++) {
				if (x >= startX - 2 && x < endX + 2) {
					if (y >= endY - 2 && y < startY + 2) {
						/*
						// Calculate the fraction of the way across the image
						// that this pixel positon corresponds to.
						float xFrac = x * 1.0f / (resWidth - 1);
						float yFrac = y * 1.0f / (resHeight - 1);

						// Take the fractions (0..1)and raise them to a power to apply
						// the distortion.
						float warpXFrac = Mathf.Pow (xFrac, warpFactor);
						float warpYFrac = Mathf.Pow (yFrac, warpFactor);

						destPix[(y - (endY - 2)) * _width + (x - (startX - 2))] = screenshot.GetPixelBilinear (warpXFrac, warpYFrac);

						// */
						destPix[(y - (endY - 2)) * _width + (x - (startX - 2))] = screenshot.GetPixel (x, y);
						/*
						if (screenshot.GetPixel (x, y).a == 1f) {

							destPix[(y - (endY - 2)) * _width + (x - (startX - 2))] = screenshot.GetPixel (x, y);

						} else if (screenshot.GetPixel (x, y).a > 0 && screenshot.GetPixel (x, y).a < 1f) {
							for (int i = -2; i < 3; i++) {
								for (int j = -2; j < 3; j++) {
									Color col = screenshot.GetPixel (x + i, y + j);
									try {
										if (col.a == 1) {
											if (col.r > 0f || col.g > 0f || col.b > 0f) {
												destPix[(y - (endY - 2)) * _width + (x - (startX - 2))] = new Vector4 (col.r, col.g, col.b, screenshot.GetPixel (x, y).a);
											}
											break;
										}
									} catch (Exception e) {
										Debug.LogError (e);
									}
								}
							}
						} else {
							destPix[(y - (endY - 2)) * _width + (x - (startX - 2))] = new Vector4 (0, 0, 0, 0);
						}
						// */
					}
				}
			}
		}
		resizeTexture.SetPixels (destPix);
		resizeTexture.Apply ();
		screenshot = null;
		Destroy (screenshot);
		// */
		#endregion //Bilinear-----------------------

		GameObject go = Instantiate (Pref);
		go.transform.SetParent (Pref.transform.parent);
		go.transform.localScale = Vector3.one;
		go.transform.localPosition = Vector3.zero;
		go.SetActive (true);

		go.tag = "Editable";
		if (GO_Dim)
			go.GetComponent<PinchMover> ().GO_Dim = GO_Dim;

		rect = new Rect (0, 0, _width, _height);
		img = go.GetComponent<Image> ();
		img.sprite = Sprite.Create (resizeTexture, rect, new Vector2 (0.5f, 0.5f), 100.0f, 0, SpriteMeshType.FullRect, Vector4.zero);
		// img.sprite.
		img.GetComponent<RectTransform> ().SetSizeWithCurrentAnchors (RectTransform.Axis.Horizontal, _width);
		img.GetComponent<RectTransform> ().SetSizeWithCurrentAnchors (RectTransform.Axis.Vertical, _height);
		img.GetComponent<RectTransform> ().anchoredPosition = new Vector2 (startX + _width / 2 - resWidth / 2 - 1, startY - _height / 2 - resHeight / 2 + 3);

		// 로컬에 파일로 저장
		// byte[] bytes = resizeTexture.EncodeToPNG ();
		// fileName = "drawing" + DateTime.Now.TimeOfDay + ".png";
		// System.IO.File.WriteAllBytes (Application.dataPath + "/Resources/" + fileName, bytes);

		if (DrawPad)
			DrawPad.OnClickClear ();

		if (GO_Image)
			GO_Image.SetActive (false);
	}
}

public static class TextureExtentions {
	public static Texture2D ToTexture2D (this Texture texture) {
		return Texture2D.CreateExternalTexture (
			texture.width,
			texture.height,
			TextureFormat.RGBA32,
			false, false,
			texture.GetNativeTexturePtr ());
	}
}
반응형
반응형

delegate 로 원하는 타입을 설정하고 생성해서 사용하면 끝
여기서 타입은 void, int, float, double 등을 얘기함.
어떤 함수를 호출할때 원하는 함수를 호출한다.
응용방법이 참 많을것 같아서 꼭 숙지해두는게 좋을 듯 하다.

 

반응형
반응형

GPS좌표간 거리계산
특정 매장근처에 가면 노티가 뜨는 시나리오를 구현하였다.
구글맵을 이용하면 경위도 좌표를 쉽게 구할 수 있고
Unity 안드로이드의 현재 위치값을 가져오는 Input.Location을 사용하였다.

출처 : http://fruitdev.tistory.com/189

반응형
반응형

 

1. 하이어라키에 빈게임오프젝트를 만들고 적당히 이름을 변경해준다.

 

2. 빈게임오브젝트에 SpriteRenderer를 추가해준다. (참고로 UI Image, RawImage도 된다)

 

3. 생성한 게임오브젝트를 선택하고 Animation창을 열어 Create 버튼을 눌러 애니메이션을 생성한다.

 

4. 준비해 둔 시퀀스파일을 모두 선택 후 생성한 Animation에 드래그해서 집어 넣는다.

 

 

 

 

5. 애니메이션의 프레임을 움직여보면 시퀀스가 적용된 것을 볼 수 있다.

반복재생, 속도 조절 등등....은 Animator사용법을 숙지하고 사용하면 끝.

 

 

 

 

 

반응형
반응형

아래 한줄 호출해주면 됨.

하이어라키에 존재하지 않는 리소스는 깨끗하게 날려주심.

 

단점이 있다면 한번 로딩했던 에셋을 다시 사용(Instantiate) 경우 다시 로딩시간이 필요하므로 상황에 맞춰서 사용해야 .

 

 

Resources.UnloadUnusedAssets(); 

 

반응형

+ Recent posts