Get it on Google Play


Wm뮤 :: 자주 쓰는 유니티 코드 모음

블로그 이미지
가끔 그림그리거나 3D모델링하거나
취미로 로봇만드는
전자과 게임프로그래머 (퇴사함)
2022.3.22f1 주로 사용
모카쨩
@Ahzkwid

Recent Comment

Archive


2020. 7. 9. 15:27 Unity/C#

 

 

뭔가 유용한데 정리하기 애매한것들

나중에 정리하자

 

 

 

 

 

 

씬 어딘가에 있는 컴포넌트를 불러옴. 매니저컴포넌트같은걸 다룰때 좋음. 할당 안해도 되니까

부하가 크니 사용시 주의

자주 호출할땐 아래의 '싱글톤에서 최상위 부모를 불러올때' 항목 참고

FindObjectOfType<클래스명>();

 

 

 

FindObjectsOfType을 대체하는 함수

2021.3.18에 추가됨

차이점은 FindObjectsByType은 정렬을 할지 안할지 정할수 있어서 정렬을 안 하면 속도가 더 빠르다는정도

var targets = FindObjectsByType<Renderer>(FindObjectsSortMode.None);

 

 

 

 

 

모든 자식들중에 해당 게임오브젝트가 존재하는지 검색함

단 Transform이 달려있어야함

System.Array.Find(transform.GetComponentsInChildren<Transform>(), x => x.name == "게임오브젝트명")

 

태그검색

GameObject.FindGameObjectsWithTag("태그명");

 

문자열로 레이어 체크

gameObject.layer == LayerMask.NameToLayer("레이어명")

 

카메라 레이어 토글

var layerIndex=LayerMask.NameToLayer("레이어명");
camera.cullingMask ^= 1 << layerIndex;

 

레이어마스크 추가 삭제

//추가
layerMask |= 1 << layer;

//삭제
layerMask &= ~(1 << layer);

 

 

 

컴포넌트 달린 오브젝트 생성

var gameObject = new GameObject(nameof(SampleClass), typeof(SampleClass));

 

 

 

 

컴포넌트를 활성화 하거나 비활성화할때

GetComponent<Image>().enabled = false;

 

 

 

유니티 에셋 파일 변경시 호출

#if UNITY_EDITOR
    클래스명()
    {
        EditorApplication.projectChanged += GetFileData;
    }
    void GetFileData()
    {
    	//실행문
    }
#endif

 

 

 

 

 

프리팹 저장

var target_ClassName = target as ClassName;
if (PrefabUtility.GetPrefabAssetType(target_ClassName.gameObject) != PrefabAssetType.NotAPrefab)
{
    PrefabUtility.SavePrefabAsset(target_ClassName.gameObject);
}

 

 

 

텍스트 일부분만 컬러주기, 리치텍스트

Rich Text가 적용되어 있어야 하는데 일반적으론 적용되어있으니 상관없음

텍스트컴포넌트.richText = true;
텍스트컴포넌트.text=
"<color=#ff0000>빨간색</color>
<color=green>초록색</color> 
<color=#0000FFc0>반투명파란색</color>
<b>굵게</b>
<i>기울임</i>
<size=50>사이즈50</size>
<material=2>매터리얼2번</material>
"
;

 

버튼을 누르면 URL실행

            if (GUILayout.Button("URL실행"))
            {
                Application.OpenURL("https://wmmu.tistory.com/");
            }

 

 

인스펙터의 값을 글로벌로 저장할때 ScriptableObjects를 사용

스크립트테이블오브젝트

using UnityEngine;

[CreateAssetMenu(fileName = "Data", menuName = "ScriptableObjects/SpawnManagerScriptableObject", order = 1)]
public class SpawnManagerScriptableObject : ScriptableObject
{
    public string prefabName;

    public int numberOfPrefabsToCreate;
    public Vector3[] spawnPoints;
}

 

 

 

 

string.IsNullOrEmpty("문자열")

 

 

폴더 이름 반환

var folderPath=AssetDatabase.GetAssetPath(object형폴더);
string[] fileNames = AssetDatabase.GetSubFolders(folderPath);

 

폴더 아래의 Sprite이름들 반환

{
  var folderPath=AssetDatabase.GetAssetPath(object형폴더);
  string[] guidFilePathSprites = AssetDatabase.FindAssets($"t:{typeof(Sprite).Name}", new string[] { folderPath });
  Sprite[] sprites = new Sprite[guidFilePathSprites.Length];
  for (int i = 0; i < sprites.Length; i++)
  {
    var path=AssetDatabase.GUIDToAssetPath(sprites[i]));//Assets/FileName.Sprite 같은식으로 반환
    sprites[i]=AssetDatabase.LoadAssetAtPath<Sprite>(path);
  }
}

 

 

정적 업데이트

[InitializeOnLoad]
public class MyWindow : EditorWindow
{
    static MyWindow()
    {
        EditorApplication.update -= StaticUpdate;
        EditorApplication.update += StaticUpdate;
    }
    static long lastTime = 0;
    public static void StaticUpdate()
    {
        if ((System.DateTime.Now.Ticks-lastTime)/10000000 >= 3)//3초마다 동작
        { 
            //실행문
            lastTime = System.DateTime.Now.Ticks;
        }
    }
}

 

 

 

 

Time.time 타이머 예제

가끔 지속체크 해야해서 코루틴 쓰기 곤란한거 쓸때 쓰면 좋음

public Text respawnTimeText;
public Image respawnProgress;
 float lastDeathTime = float.MinValue;

/// <summary>
/// update에서 지속 호출
/// </summary>
public void RespawnCheck()
{
    var nowTime = Time.time;
    var delay = 10;

    if (nowTime < lastDeathTime + delay)
    {
        //부활대기중
        var respawnTime = (lastDeathTime + delay) - nowTime;
        respawnTimeText.text = respawnTime.ToString("N0"); //10부터 시작
        respawnProgress.fillAmount = 1f - respawnTime / delay; //0부터 시작

        respawnTimeText.gameObject.SetActive(true);
        respawnProgress.gameObject.SetActive(true);
    }
    else
    {
        if (lastDeathTime > 0)
        {
            //리스폰
            Respawn();
            lastDeathTime = float.MinValue;
        }
        //생존중
        respawnTimeText.gameObject.SetActive(false);
        respawnProgress.gameObject.SetActive(false);
    }

}

public void Respawn()
{
    (var poses, var rot) = GameSystem.GetInstance().GetSpawnPoints(GetComponent<PhotonCharacter>().team);
    transform.position = poses[0];
    transform.rotation = rot;
    GetComponent<PhotonCharacter>().hp = 100;
    GetAnimator().GetComponent<DynamicRagdoll>().Deactive();
}

public void Death()
{
    lastDeathTime = Time.time;
}

 

 

 

 

 

 

 

 

모든 씬에서 단 한번만 자동생성되는 오브젝트

public class GameManager : MonoBehaviour
{
	static GameObject gameManager;
    static void CreateInstance()
    {
        if (gameManager == null)
        {
        	DontDestroyOnLoad(new GameObject(nameof(GameManager), typeof(GameManager)));
            //nameof가 오브젝트명이고 typeof가 addcomponant
        }
    }
    static GameManager()
    {
        if(Application.isEditor==false)
        {
            CreateInstance();
        }
        #if UNITY_EDITOR
        if (UnityEditor.EditorApplication.isPlaying)
        {
            CreateInstance();
        }
        #endif
    }

 

 

 

열닫 가능한 코드

#region 주석


#endregion

 

 

 

 

웹컬러 변환

출력은 #이 빠져나오고 입력은 #이 붙어야 하는 미쿠친구 함수니까 조심하자

string webColor = $"#{ColorUtility.ToHtmlStringRGB(Color.red)}";
//webColor = #FF0000 

string webColorRGBA = $"#{ColorUtility.ToHtmlStringRGBA(Color.red)}";
//webColor = #FF0000FF 

Color color;
ColorUtility.TryParseHtmlString(webColor, out color);
//color = RGBA(1.000, 0.000, 0.000, 1.000)

 

에디터 모드에서도 MonoBehaviour를 동작하게 함

update나 start이런 함수도 작동

[ExecuteInEditMode]

 

 

데이터 타입 이름 구한거

Debug.Log($"bool: {typeof(bool).Name}"); //Boolean
Debug.Log($"char: {typeof(char).Name}"); //Char
Debug.Log($"string: {typeof(string).Name}");  //String
Debug.Log($"short: {typeof(short).Name}"); //Int16
Debug.Log($"int: {typeof(int).Name}");  //Int32
Debug.Log($"long: {typeof(long).Name}");  //Int64
Debug.Log($"float: {typeof(float).Name}");  //Single
Debug.Log($"double: {typeof(double).Name}");  //Double

 

 

 

배열을 콤마가 붙은 문자열로 반환

string strData = $"({string.Join(",", 배열데이터)})";

string형이 아닌 배열일때

string strData = $"({string.Join(",", Array.ConvertAll(배열데이터, x => x.ToString()))})";

 

 

 

 

 

 

 

 

하이어라키의 해당 오브젝트의 자식계층에서 최하위로 설정함, 부모는 변하지 않음

transform.SetAsLastSibling();

 

 

 

리플렉션으로 애니메이션 윈도우 접근

var animationWindowType = System.Type.GetType("UnityEditor.AnimationWindow,UnityEditor");
var animationWindow = Resources.FindObjectsOfTypeAll(animationWindowType)[0];

 

마우스에 대고있는 윈도우 이름

var mouseOverWindow = EditorWindow.mouseOverWindow;
if (mouseOverWindow != null)
{
    Debug.Log("mouseOverWindow: " + mouseOverWindow.GetType().ToString());
}

 

폰이 스스로 종료되지 않도록

Screen.sleepTimeout = SleepTimeout.NeverSleep; //네버슬립모드

백그라운드에서도 동작함

Application.runInBackground=true;

 

 

 

 

TextUI의 적절한 가로 사이즈, 캐릭터(문자)의 위치를 구할때 가아아아끔씩 쓴다

GetComponent<Text>().preferredWidth

 

씬에디터상에 박스를 표시

//에디터상에서 박스위치 표시할때 사용
private void OnDrawGizmos()
{
    Gizmos.DrawWireCube(borderCenter, borderSize);
}
public Vector3 borderCenter = Vector3.zero;
public Vector3 borderSize = new Vector3(200,100,200);

 

 

 

 

배속설정, 디버깅할때 유용하더라

Time.timeScale=2;

 

 

싱글톤에서 최상위 부모를 불러올때

v1

더보기
static MainSystem mainSystemPrivate;
public static MainSystem mainSystem
{
    get
    {
        if (mainSystemPrivate == null)
        {
            mainSystemPrivate = FindObjectOfType<MainSystem>();
        }
        return mainSystemPrivate;
    }
}

v2

네이밍만 바뀜

static GameSystem gameSystem = null;
public static GameSystem GetInstance()
{
    if (gameSystem == null)
    {
        gameSystem = FindObjectOfType<GameSystem>();
    }
    return gameSystem;
}

 

 

 

 

 

 

 

팝업창을 띄움, 에디터 전용

#if UNITY_EDITOR
[UnityEditor.InitializeOnLoad]
public class Class : MonoBehaviour {

	static Class()
	{
		UnityEditor.EditorUtility.DisplayDialog("제목", "내용", "네", "아니요");
	}
	// Use this for initialization
	void Start () {

	}
	
	// Update is called once per frame
	void Update () {
		
	}
}

#endif

 

 

 

인스턴트 SE

    public void InstantiateSE(AudioClip clip)
    {
        if (clip==null)
        {
            Debug.Log("clip==null");
            return;
        }
        var gameObject = new GameObject($"SoundEffect({clip.name})");
        var audioSource = gameObject.AddComponent<AudioSource>();
        audioSource.clip = clip;
        audioSource.Play();
        Destroy(gameObject, audioSource.clip.length + 3f);
        DontDestroyOnLoad (gameObject);
    }

 

 

유니티 버전

Application.version

 

 

 

 

Predicate 샘플

    public static class Array
    {
        public static int[] FindIndexAll<T>(T[] array, System.Predicate<T> match)
        {
            var indexList = new List<int>();
            for (int i = 0; i < array.Length; i++)
            {
                if (match.Invoke(array[i]))
                {
                    indexList.Add(i);
                }
            }
            return indexList.ToArray();
        }
    }
    // Start is called before the first frame update
    void Start()
    {
        var strTest = new string[] { "A", "B", "C", "A", "B" };
        var indexes = Array.FindIndexAll(strTest, x => x == "A");
        Debug.Log(string.Join(",",indexes)); //0,3을 반환
    }

 

 

 

 

현재 언어가 영어인지

if(Application.systemLanguage == SystemLanguage.English)

 

 

 

인풋필드가 활성화 되어있는지

if((InputField!=null)&& (InputField.isFocused))
{

}

 

 

스크린사이즈 변경

    private void Awake()
    {

        if (Debug.isDebugBuild)
        {
            var hei = 480;
            Screen.SetResolution(Screen.width * hei / Screen.height, hei, true);
        }
    }

 

 

fps측정

    float fps = 60;
    private void OnGUI()
    {
        fps = Mathf.Lerp(fps, (1 / Time.deltaTime), 0.05f);
        GUI.Box(new Rect(100, 0, 200, 24), "FPS: " + (int)fps);
    }

 

 

 

 

스크롤뷰의 콘텐츠 위치를 항상 아래쪽으로 잡을때

scrollRect.verticalScrollbar.value = 0;

 

 

 

 

이름으로 블렌드 쉐이프 설정

var skinnedMeshRenderer = GetComponent<SkinnedMeshRenderer>();
var blendShapeIndex=skinnedMeshRenderer.sharedMesh.GetBlendShapeIndex("ShapeKeyName");
skinnedMeshRenderer.SetBlendShapeWeight(blendShapeIndex, weight);

 

 

 

 

마우스 위치 표시

Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
    Debug.DrawLine(ray.origin, hit.point,Color.red);
}
else
{
    Debug.DrawLine(ray.origin, ray.origin + ray.direction * 5);
}

 

 

자식리스트를 불러옴

//켜진것만
var childList = new List<GameObject>();
for (int i = 0; i < transform.childCount; i++)
{
    var child = transform.GetChild(i).gameObject;
    if (child.activeInHierarchy)
    {
        childList.Add(child);
    }
}

//전부
var childList = new List<GameObject>();
for (int i = 0; i < transform.childCount; i++)
{
    childList.Add(transform.GetChild(i).gameObject);
}

 

 

 

 

 

uuid

빌드조건에 따라 쉽게 바뀌니 주의

안드로이드면 키스토어만 바뀌어도 기준값이 바뀌고

애플이면 xcode에서 직접 빌드했는지 아닌지에 따라서도 바뀐다

SystemInfo.deviceUniqueIdentifier

 

 

 

 

자주쓰는 애니메이션 이벤트

    public void InstantiateSE(AudioClip clip)
    {
        if (clip==null)
        {
            Debug.Log("clip==null");
            return;
        }
        var gameObject = new GameObject($"SoundEffect({clip.name})");
        var audioSource = gameObject.AddComponent<AudioSource>();
        audioSource.clip = clip;
        audioSource.Play();
        Destroy(gameObject, audioSource.clip.length + 3f);
    }

 

 

scrollRect(Scroll Rect) 스크롤좌표 초기화

scrollRect.horizontalNormalizedPosition = 0f;
scrollRect.verticalNormalizedPosition = 0f;

 

 

 

 

 

 

 

GPGS토큰

string googleIdToken = null;
string googleAccessToken = null;

if (PlayGamesPlatform.Instance.IsAuthenticated())
{
	googleIdToken = PlayGamesPlatform.Instance.GetIdToken();
    //googleIdToken = ((PlayGamesLocalUser)Social.localUser).GetIdToken(); 이거도 된다고는 함
	googleAccessToken = PlayGamesPlatform.Instance.GetServerAuthCode();
}
else
{
	Debug.LogError("GPGS에 연결되어있지않아 토큰을 받아올수 없음");
}
//ID토큰은 공개적인거고 Access토큰은 비공개적인것인듯하다(불확실)

 

 

 

 

 

 

최대값을 지정 해 줌으로서 min값을 구할때 쓸 수 있다

long minScore = long.MaxValue;

 

 

게임종료

일반적인 상황에서는 쓸일이 없지만

번들로더같은곳에서 다운로드 취소버튼을 누른다던지 PC용게임을 만들때 사용함

    public void Exit()
    {
#if UNITY_EDITOR
        UnityEditor.EditorApplication.isPlaying = false;
#else
        Application.Quit();
#endif
    }

 

 

 

 

Enumerable To List

IEnumerable<DataSnapshot> childrens;
List<DataSnapshot> snapshots= Enumerable.ToList(childrens);

 

Enumerable To Array

IEnumerable<DataSnapshot> childrens;
DataSnapshot[] snapshots= Enumerable.ToArray(childrens);

 

 

레이캐스트2D

유니티4버전이라서 추가 업뎃 해야할듯

더보기
RaycastHit2D ray;
Vector3 pos = Camera.main.ScreenToWorldPoint (Input.mousePosition);
ray = Physics2D.CircleCast ((Vector2)pos,0.1f,Vector2.zero);


Debug.DrawRay (mpos,ray.direction,Color.red);
if(ray.transform != null) //빔을 쏴서 존재하는지
{
    Debug.Log (ray.point);
    Debug.DrawLine(mpos, ray.point, Color.blue);
    Debug.Log (ray.transform.GetInstanceID ().ToString());
}

 

레이캐스트 everything

var everything = ~0;

 

 

레이캐스트 샘플

유니티 공식문서 코드에서 조금만 수정했다

주의 : hit.transform은 절대 절대 절대 절대 절대 쓰지말자

float maxDistance = 1000;
if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out RaycastHit hit, maxDistance >= 0 ? maxDistance : Mathf.Infinity))
{
    Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * hit.distance, Color.yellow);
}
else
{
    Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * maxDistance, Color.white);
}

 

레이캐스트 2점쇼바

public class Piston : MonoBehaviour
{
    public Transform pistonStart;
    public Transform pistonEnd;

    void Update()
    {
        var forward = (pistonEnd.position - pistonStart.position).normalized;
        var distance = Vector3.Distance(pistonStart.position, pistonEnd.position);
        if (Physics.Raycast(pistonStart.position, forward, out RaycastHit hit, distance,1))
        {
            transform.position = pistonEnd.position - forward*(distance - hit.distance);
        }
        else
        {
            transform.position= pistonEnd.position;
        }
    }
}

 

 

레이캐스트 화면터치

Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray))
{
    //클릭시 실행
}

 

레이캐스트 화면기준 발사 3D

//var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
var screenCenter = new Vector3(Screen.width / 2, Screen.height / 2, 0);
var ray = Camera.main.ScreenPointToRay(screenCenter);
float maxDistance = 1000;
if (Physics.Raycast(ray, out RaycastHit hit, maxDistance >= 0 ? maxDistance : Mathf.Infinity))
{
    Debug.DrawLine(ray.origin, ray.GetPoint(hit.distance), Color.green);
}
else
{
    Debug.DrawLine(ray.origin, ray.GetPoint(1000), Color.yellow);
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

디버그 로그 비활성

v1

더보기

디버그모드일때만 작동

public class SampleClass : MonoBehaviour
{
    static class Debug
    {
        public static void Log(string log)
        {
#if UNITY_EDITOR
            if (UnityEditor.EditorUserBuildSettings.development)
#else
        if (Debug.isDebugBuild)
#endif
            {
                UnityEngine.Debug.Log(log);
            }
        }
        public static void LogError(string log)
        {
#if UNITY_EDITOR
            if (UnityEditor.EditorUserBuildSettings.development)
#else
        if (Debug.isDebugBuild)
#endif
            {
                UnityEngine.Debug.LogError(log);
            }
        }

    }

 

 

v2

맨 처음에 한번만 적어주면 됨

#if UNITY_EDITOR
            if (UnityEditor.EditorUserBuildSettings.development)
#else
        if (Debug.isDebugBuild)
#endif
            {
Debug.logger.logEnabled=false
            }

 

 

 

 

 

Define 전역설정

특정 패키지가 임포트 되었을때만 활성화 된다던지가 가능

 

 

 

충돌반사

public float damageSpeed = 3;
public void OnCollisionEnter(Collision collision)
{
    if (collision.relativeVelocity.magnitude > damageSpeed)
    {
        explosionRot = Vector3.Reflect(-collision.relativeVelocity, collision.contacts[0].normal);
        explosionPower = collision.relativeVelocity.magnitude * 0.5f;
    }
}

 

 

충돌반사 네트워킹

https://ahzkwid.booth.pm/items/4471500 에서 사용중이다

public void OnCollisionEnter(Collision collision)
{
    //https://ahzkwid.booth.pm/items/4471500
    if (GetComponent<VRC_Pickup>() == null)
    {
        return;
    }
    if (Networking.IsOwner(gameObject))
    {
        if (collision.relativeVelocity.magnitude > damageSpeed* DamageSpeedMultiple)
        {
            explosionRot = Vector3.Reflect(-collision.relativeVelocity, collision.contacts[0].normal);
            explosionPower = collision.relativeVelocity.magnitude * 0.5f;
            SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, nameof(ExplosionEffect));
            Delete();
        }
    }
}

 

 

충돌파워에 따른 사운드

void OnCollisionEnter(Collision collision)
{
    if (impactSound.isPlaying)
    {
        return;
    }
    var volume = collision.relativeVelocity.magnitude;
    volume = Mathf.Clamp(volume, 0.1f, 1f);
    impactSound.volume = volume;
    impactSound.Play();
}

 

 

 

 

일정시간뒤 자동 삭제

using UnityEngine;
public class DelayDestroy : MonoBehaviour
{
    public float delay = 3;
    void Start()
    {
        Destroy(gameObject, delay);
    }
}

 

 

부위데미지

var partDamage = 1f;
switch (humanBodyBones)
{
    case HumanBodyBones.LeftUpperLeg:
    case HumanBodyBones.RightUpperLeg:
        partDamage = 0.9f;
        break;
    case HumanBodyBones.LeftLowerLeg:
    case HumanBodyBones.RightLowerLeg:
        partDamage = 0.7f;
        break;
    case HumanBodyBones.Spine:
    case HumanBodyBones.Chest:
    case HumanBodyBones.UpperChest:
    case HumanBodyBones.Hips:
        partDamage = 1f;
        break;
    case HumanBodyBones.Neck:
    case HumanBodyBones.Head:
        partDamage = 2f;
        break;
    case HumanBodyBones.LeftShoulder:
    case HumanBodyBones.RightShoulder:
    case HumanBodyBones.LeftUpperArm:
    case HumanBodyBones.RightUpperArm:
        partDamage = 0.8f;
        break;
    case HumanBodyBones.LeftLowerArm:
    case HumanBodyBones.RightLowerArm:
        partDamage = 0.7f;
        break;
    case HumanBodyBones.LeftHand:
    case HumanBodyBones.RightHand:
    case HumanBodyBones.LeftFoot:
    case HumanBodyBones.RightFoot:
    case HumanBodyBones.LeftToes:
    case HumanBodyBones.RightToes:
        partDamage = 0.5f;
        break;
}

 

 

Enum1에서 Enum2로 변환

var enum2 = (Enum2)System.Enum.Parse(typeof(Enum2), enum1.ToString());

 

 

 

 

해당 게임오브젝트의 최상위루트 반환

v1

더보기

적용예제 : https://github.com/ahzkwid/AhzkwidAvatarTools

Transform GetRoot(Transform transform)
{

    var parents = transform.GetComponentsInParent<Transform>(true);
    Transform root = null;
    if (parents.Length == 1)
    {
        root = transform;
    }
    else
    {
        root = System.Array.Find(parents, parent => parent.GetComponentsInParent<Transform>(true).Length == 1);
    }
    return root;
}

 

 

근데 내장함수 있었음;;

v2

transform.root

'Unity > C#' 카테고리의 다른 글

유니티 버그, 에러, 오류모음  (0) 2020.07.20
사소한 c# 테크닉  (0) 2020.07.08
스파인 관련함수  (0) 2020.07.02
posted by 모카쨩

저사양 유저용 블로그 진입