오늘 학습 키워드

유니티 심화 팀 프로젝트, 스탠다드 반 강의

오늘 학습 한 내용을 나만의 언어로 정리하기

유니티 심화 팀 프로젝트

합치기

  • 합치다보니 여러가지 문제가 발생했음

지정 위치로 못 가는 문제

  • 가려는 위치로 못가고 maxMoveTime 이상 moveThreshold 만큼 움직이지 못한다면 막혔다고 판단
  • 다음으로 갈 위치 고를 때 막힌 방향 제외하고 감
// MoveNode.cs
public class MoveNode : Node  
{  
    private MonsterAI monsterAI;  
    private float startTime;  
    private float maxMoveTime;  
    private Vector3 lastPosition;  
    private bool checkMove;  
    private float moveThreshold;  
  
    public MoveNode(MonsterAI monsterAI, bool checkMove = false, float maxMoveTime = 0f, float moveThreshold = 0.05f)  
    {  
        this.monsterAI =  monsterAI;  
        this.checkMove = checkMove;  
        this.maxMoveTime = maxMoveTime;  
        this.moveThreshold = moveThreshold;  
    }  
  
    public override NodeState Evaluate()  
    {  
        if (state != NodeState.Running)  
        {  
            startTime = Time.time;  
            lastPosition = monsterAI.transform.position;  
        }  
          
        if (monsterAI.MoveToDestination())  
        {  
            // Debug.Log("Move Done");  
            return EndMove();  
        }  
          
            
        monsterAI.Monster.Animator.StartAnimation(monsterAI.Monster.Animator.data.MoveHash);  
  
        if (checkMove && Time.time - startTime > maxMoveTime)  
        {  
            if (Vector3.Distance(monsterAI.transform.position, lastPosition) < moveThreshold)  
            {  
                // Debug.Log("No Move. Move Done");  
                monsterAI.isStucked = true;  
                monsterAI.stuckPosition = monsterAI.Monster.Animator.spriteRenderer.flipX ? -1 : 1;  
                monsterAI.isMoving = false;  
                return EndMove();  
            }  
              
            startTime = Time.time;  
            lastPosition = monsterAI.transform.position;  
        }  
          
        state = NodeState.Running;  
        return state;  
    }  
  
    NodeState EndMove()  
    {  
        monsterAI.Monster.Animator.StopAnimation(monsterAI.Monster.Animator.data.MoveHash);  
        monsterAI.isReturn = false;  
        state = NodeState.Success;  
        return state;  
    }  
}
// MonsterAI.cs
public void SetRandomDestination()  
{  
    float range;  
      
    if (isStucked)  
    {  
        // Debug.Log("Stucked New Position");  
        if (stuckPosition < 0)  
        {  
            range = Random.Range(0, Monster.data.Patrol / 2f);  
        }  
        else  
        {  
            range = Random.Range(-(Monster.data.Patrol / 2f), 0);  
        }  
  
        isStucked = false;  
        stuckPosition = 0;  
    }  
    else  
    {  
        range = Random.Range(-(Monster.data.Patrol / 2f), Monster.data.Patrol / 2f);  
    }  
      
    float newPosX = Mathf.Clamp(transform.position.x + range, MinX, MaxX);  
    Destination = new Vector2(newPosX, transform.position.y);  
}

공격에 문제가 생김

  • 깜빡하고 IsParried를 안끔
public class MeleeMonsterWeapon : MonsterWeapon  
{  
    [field: SerializeField] public float ParryDelay { get; set; } = 0.01f;  
    public bool IsParried { get; set; }= false;  
      
  
    public override void Parry(int damage)  
    {  
        GetComponent<BoxCollider2D>().enabled = false;  
        Debug.Log("[Monster] Parried!");  
        monster.Parried(damage);  
        IsParried = true;  
    }  
      
    private void OnTriggerExit2D(Collider2D other)  
    {  
        Debug.Log($"[Test] Trigger Exit : {other}");  
        if (other.gameObject.CompareTag("Player") && other.gameObject.layer != LayerMask.NameToLayer("PlayerAttack"))  
        {  
            Debug.Log($"[Test] Player이면서 무기가 아님.");  
            if(other.gameObject.TryGetComponent(out Player player) && !IsParried)  
            {  
                Debug.Log("[Test] Attack!");  
                player.ReceiveMonsterAttack(monster.data.AttackDamage, monster.transform.position);  
            }  
        }  
    }  
  
    public override void EndParry()  
    {  
        IsParried = false;  
    }  
}

오디오를 계속 반복해서 살려놔야하는 애들이 있었음

  • 그런 애들은 따로 딕셔너리에 보관하기로 함
// SoundManager.cs
public AudioSource PlayClip(AudioClip clip, bool loop, bool dontDestroy = false)  
{  
    if (clip == null)  
    {  
        Debug.LogError("Clip is null");  
        return null;  
    }  
    GameObject obj = Instantiate(soundSourcePrefab);  
    SoundSource soundSource = obj.GetComponent<SoundSource>();  
    return soundSource.Play(clip, Instance.SoundEffectVolume, loop, dontDestroy);  
}  
  
  
public SoundSource GetPlayingClip(string key)  
{  
    playingSounds.TryGetValue(key, out SoundSource clip);  
    if (clip != null) return clip.GetComponent<SoundSource>();  
    else return null;  
}  
  
public void StartClip(string key, AudioClip clip, bool loop = false)  
{  
    if (GetPlayingClip(key) != null) return;  
    playingSounds.Add(key, PlayClip(clip, loop, true).GetComponent<SoundSource>());  
}  
  
public void StopClip(string key)  
{  
    if (GetPlayingClip(key) == null) return;  
    Destroy(playingSounds[key].gameObject);  
    playingSounds.Remove(key);  
}
  • 그리고 꺼져야 하는 특정 시점에 StopClip 해주기

스탠다드 반 강의 (주제: 프레임워크)

  • UI Base를 상속하는 Popup UI Base
  • UIManager는 필요한 UI를 꺼내줌
public class UIManager : Singleton<UIManager>
{
	public static int ScreenWidth = 1920;
	public static int ScreenHeight = 1080;
 
 
	public Dictionary<string, UIBase> ui_List = new();
	
	public T Show<T>(params object[] param) where T : UIBase
	{
		string uiName = typeof(T).ToString();
		
		ui_List.TryGetValue(uiName, out UIBase ui);
		if(ui == null)
		{
			ui = Load<T>(uiName);
			// 켜져있는 목록 딕셔너리에 추가하기
			ui_List.Add(uiName, ui);
			ui.Opened(param);
		}
		
		ui.gameObject.SetActive(true);
		return (T)ui;
	}
	
	public T Load<T>()
	{
		// 캔버스부터 만들기
		var newCanvasObject = new GameObject(uiName + " Canvas");
		
		// 캔버스 추가
		var canvas = newCanvasObject.gameObject.AddComponent<Canvas>();
		canvas.renderMode = RenderMode.ScreenSpaceOverlay;
		
		// 캔버스에 스케일러 추가
		var canvasScaler = newCanvasObject.gameObject.AddComponent<CanvasScaler>();
		canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
		canvasScaler.referenceResolution = new Vector2(ScreenWidth, ScreenHeight);
		
		// 캔버스에 레이캐스터 추가
		newCanvasObject.gameObject.AddComponent<GraphicRaycaster>();
		
		// 캔버스에 UI 붙이기
		var prefab = ResourceManager.Instance.LoadAsset<GameObject>(uiName, eAssetType.UI);
		
		var obj = Instantiate(prefab, newCanvasObject.transform);
		obj.name = obj.name.Replace("(Clone)", "");	
		
		
		var result = obj.Getcomponent<T>();
		result.canvas = canvas;
		result.canvas.sortingOrder = ui_List.count;
		
		return result;
	}
	
	public T Get<T>() where T : UIBase
	{
		string uiName = typeof(T).ToString();
		ui_List.TryGetValue(uiName, out UIBase ui);
		
		if(ui == null)
		{
			Debug.LogError($"{uiName} doesn't exist")
			return Default;
		}
		return (T)ui;
	}
	
	
	public void Hide<T>()
	{
		string uiName = typeof(T).ToString();
		
		Hide(uiName);
	}
	
	public void Hide(string uiName)
	{
		ui_List.TryGetValue(uiName, out UIBase ui);
		
		if(ui == null) return;
		
		DestroyImmediate(ui.canvas.gameObject);
		ui_List.Remove(uiName);
	}
}
public class UIBase : MonoBehaviour
{
	[HideInspector]
	public Canvas canvas;
	
	public virtual void Opened(params object[] param)
	
	public virtual void Hide()
	{
		UIManager.Instance.Hide(gameObject.name);
	}
}
  • 팝업에 따라 캔버스를 나눠주는게 최적화 부분에서 더 좋음.
    • 왜냐면 한 캔버스 아래에 여러 UI가 있을 때, 하나라도 바뀌면 모든걸 다시 그리기 때문임
    • 유니티 공식 문서에서도 권장하는 방법이라고 함
  • 다른 캔버스의 경우, 그려지는 순서는 캔버스 안에 있는 Sort Order에 영향을 받음
  • params : 가변 배열을 받겠다는 의미

학습하며 겪었던 문제점 & 에러

문제 1

  • 문제&에러에 대한 정의

이펙트가 너무 뒤에 보였음.

  • 해결 방법

소팅 오더를 크게 해줌

var psRenderer = go.GetComponent<ParticleSystemRenderer>();   
psRenderer.sortingOrder = 200;

문제 2

  • 문제&에러에 대한 정의

이펙트를 좌우 반전 해야 했음.

  • 해결 방법

X축 좌우 반전을 만들어줌

var psRenderer = go.GetComponent<ParticleSystemRenderer>();   
psRenderer.flip = isLeft ? new Vector3(1f, 0f, 0f) : Vector3.zero;