Unity 개발 일지/Unity 공부

[내일배움캠프] Unity 배치고사 자가 피드백

ohty20012 2025. 5. 28. 20:06
  • 문제
  • 출제 의도 분석
  • 제출한 정답
  • 문제에 대한 피드백

 

더보기
using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerController : MonoBehaviour
{
    [Header("Movement")] 
    private float moveSpeed = 5;
    private Vector2 curMovementInput;

    [Header("Look")] 
    private Transform Player;
    private Transform CameraContainer;
    private float minXLook = -85;
    private float maxXLook = 85;
    private float camCurXRot;
    private float lookSensitivity = 0.1f;
    private Vector2 mouseDelta;
    
    private Rigidbody _rigidbody;

    private void Awake()
    {
        Cursor.lockState = CursorLockMode.Locked;
		    Player = transform;
        CameraContainer = transform.GetChild(0);
        _rigidbody = GetComponent<Rigidbody>();
    }

    private void FixedUpdate()
    {
        Move();
    }

    private void LateUpdate()
    {
        CameraLook();
    }

    void CameraLook()
    {
        camCurXRot +=     ㉠     * lookSensitivity;
        camCurXRot = Mathf.Clamp(camCurXRot, minXLook, maxXLook);
        CameraContainer.localEulerAngles = new Vector3(-camCurXRot, 0, 0);

        Player.eulerAngles += new Vector3(0,     ㉡     * lookSensitivity,0);
    }
    void Move()
    {
        Vector3 dir = Player.forward * curMovementInput.y + Player.right * curMovementInput.x;
        dir *= moveSpeed;
        dir.y = _rigidbody.velocity.y;

        _rigidbody.velocity = dir;
    }

    public void OnMove(InputAction.CallbackContext context)
    {
        if (context.phase == InputActionPhase.Performed)
        {
            curMovementInput = context.ReadValue<Vector2>();
        }
        else if (context.phase == InputActionPhase.Canceled)
        {
            curMovementInput = Vector2.zero;
        }
    }

    public void OnLook(InputAction.CallbackContext context)
    {
        mouseDelta = context.ReadValue<Vector2>();
    }
}

 

마우스의 움직임에 따라 적절한 방향으로 화면이 회전하도록 PlayerController.cs의 빈 칸 (ㄱ), (ㄴ)에 각각 알맞은 코드를 쓰시오.

 

● 출제 의도 분석

  1. Mouse 입력 처리와 그에 따른 카메라 회전 처리가 가능한가

● 제출한 정답

  • (ㄱ) => mouseDelta.y
  • (ㄴ) => mouseDelta.x

● 자가 피드백

  • X
더보기
using System.Collections.Generic;
using UnityEngine;

public interface IDamagable
{
    public void TakePhysicalDamage(int amount);
}

public class Ally : MonoBehaviour, IDamagable
{
    public void TakePhysicalDamage(int amount)
    {
        Debug.Log($"아군 유닛이 {amount}의 피해를 입었습니다.");
    }
}

public class Enemy : MonoBehaviour, IDamagable
{
    public void TakePhysicalDamage(int amount)
    {
        Debug.Log($"적 유닛이 {amount}의 피해를 입었습니다.");
    }
}

public class CampFire : MonoBehaviour
{
    private int damage = 5;
    private float damageRate = 1;

    private List<    ㉠    > things = new List<    ㉡    >();

    private void Start()
    {
        InvokeRepeating("DealDamage", 0, damageRate);
    }

    void DealDamage()
    {
        for (int i = 0; i < things.Count; i++)
        {
            things[i].    ㉢    (    ㉣    );
        }
    }

    private void OnTriggerEnter(Collider other)
    {
        if (other.TryGetComponent(out     ㉤     target))
        {
            things.Add(target);
        }
    }

    private void OnTriggerExit(Collider other)
    {
        if (other.TryGetComponent(out     ㉥     target))
        {
            things.Remove(target);
        }
    }
}

 

캠프 파이어에 너무 가까이 있는 아군, 적 모두에게 주기적으로 damage 만큼의 피해를 주려고 한다. CampFire.cs의 빈 칸 (ㄱ), (ㄴ), (ㄷ), (ㄹ), (ㅁ), (ㅂ)에 각각 알맞은 코드를 쓰시오.

 

● 출제의도 분석

  1. 인터페이스에 대해 이해하고 있고, 활용할 수 있는가

● 제출한 정답

  • (ㄱ) => IDamagable
  • (ㄴ) => IDamagable
  • (ㄷ) => TakePhysicalDamage
  • (ㄹ) => damage
  • (ㅁ) => IDamagable
  • (ㅂ) => IDamagable

● 자가 피드백

  • X
더보기
using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class Skill : MonoBehaviour
{
    private Image remainGaugeBar;
    private TextMeshProUGUI remainText;

    private int remainSkillCnt; // 현재 남은 스킬 사용 가능 횟수
    private readonly int maxSkillCnt = 5; // 최대 스킬 사용 가능 횟수

    private void Awake()
    {
        remainSkillCnt = maxSkillCnt;
        
        //구성을 단순화하기 위해 이렇게 초기화했습니다. GetChild를 활용해서 초기화하는 방법은 권장되지 않습니다.
        remainGaugeBar = transform.GetChild(0).GetComponent<Image>();
        remainText = transform.GetChild(1).GetComponent<TextMeshProUGUI>();
        
        SetSkillUI();
    }

    public void UseSkill()
    {
        if (remainSkillCnt <= 0) return;
        
        Debug.Log("스킬을 사용했다.");
        remainSkillCnt--;
        SetSkillUI();
    }

    void SetSkillUI()
    {
        //TODO
        //remainGaugeBar, remainText을 활용할 것
    }
}

 

스킬의 남은 사용 가능 횟수에 따라 다음과 같이 게이지 바 이미지, 텍스트를 조정하는 SetSkillUI 메서드를 구현하시오.

 

● 출제 의도 분석

  1. UI요소를 통한 상태 시각화를 이해하고 있는가
  2. 코드를 통해 UI의 상태를 업데이트 할 수 있는가

● 제출한 정답

    remainGaugeBar.fillAmount = (float)remainSkillCnt / maxSkillCnt;
    remainText.text = $"{remainSkillCnt} / {maxSkillCnt}";

 

● 자가 피드백

  1. (float)를 통한 캐스팅을 까먹고 안해서 버그가 발생한 것
    → 정수의 나눗셈을 시행할때 자료형 캐스팅 주의하기
더보기

유니티 3D 프로젝트에서 1인칭, 3인칭으로 시점 변경이 가능한 게임을 구현하려고 한다. Player 오브젝트에 부착된 Player Input, Player Controller는 문제 1번과 동일하게 구성되어 있다고 할 때, 질문에 답하시오.

using UnityEngine;

public class Interaction : MonoBehaviour
{
    private float checkRate = 0.05f;
    private float lastCheckTime;
    private float maxCheckDistance = 2;
    private LayerMask layerMask;

    private GameObject curInteractGameObject;

    private Camera camera;
    private bool nowFirstPerson = true;

    private Transform interactionRayPointTransform;
    
    private void Start()
    {
        layerMask = 1 << 6;
        camera = Camera.main;
        lastCheckTime = Time.time;

        //구성을 단순화하기 위해 이렇게 초기화했습니다. GetChild를 활용해서 초기화하는 방법은 권장되지 않습니다.
        interactionRayPointTransform = transform.GetChild(0).GetChild(1);
    }

    private void Update()
    {
        if (Time.time - lastCheckTime > checkRate)
        {
            lastCheckTime = Time.time;
            
            Ray ray = returnInteractionRay();
            RaycastHit hit;

            if (Physics.Raycast(ray, out hit, maxCheckDistance, layerMask))
            {
                if (hit.collider.gameObject != curInteractGameObject)
                {
                    curInteractGameObject = hit.collider.gameObject;
                    Debug.Log($"{curInteractGameObject.name}과 상호작용할 수 있습니다.");
                }
            }
            else
            {
                curInteractGameObject = null;
            }
        }
        
        // 스페이스 바를 눌렀을 때 시점을 전환합니다.
        if (Input.GetKeyDown(KeyCode.Space)) SwitchView();
    }
    
    public void SwitchView()
    {
        if (nowFirstPerson)
        {
            nowFirstPerson = false;
            camera.transform.localPosition = new Vector3(0, 0.5f, -5);
        }
        else
        {
            nowFirstPerson = true;
            camera.transform.localPosition = Vector3.zero;
        }
    }

    private Ray returnInteractionRay()
    {
        Ray ray;
        if (nowFirstPerson)
        {
            //TODO
            //camera를 활용할 것
        }
        else
        {
            //TODO
            //interactionRayPointTransform를 활용할 것
        }
        return ray;
    }
}

 

1인칭 시점에서는 화면의 중앙을 기준으로, 3인칭 시점에서는 interactionRayPointTransform을 기준으로 정면에 있는 물체를 검출하기 위한 Ray를 반환하는 retrunInteractionRay 메서드를 구현하시오. (단, 화면이 이동하지 않았다면 1인칭, 3인칭에서 생성되는 Ray의 origion 및 direction이 동일해야 함)

 ※ 화면의 중앙을 기준으로 생성되는 Ray의 origin과 interactionRayPointTransform의 position은 현재 같은 위치로 조정되어 있음

 

● 출제 의도 분석

  1. Ray를 이해하고 있고, 생성할 수 있는가
  2. 1인칭과 3인칭에 따른 Ray의 생성방식 차이를 이해하고 있는가

● 제출한 정답

private Ray returnInteractionRay()
{
    Ray ray;
    if (nowFirstPerson)
    {
        //TODO
        //camera를 활용할 것
        Vector3 screenCenter = new Vector3(Screen.width / 2f, Screen.height / 2f, 0);
        ray = camera.ScreenPointToRay(screenCenter);
    }
    else
    {
        //TODO
        //interactionRayPointTransform를 활용할 것
        ray = new Ray(interactionRayPointTransform.position, interactionRayPointTransform.forward);
    }
    return ray;
}

 

● 자가 피드백

  • Ray에 대한 공부 부족 : Ray의 매개변수를 정확히 몰라서, 정의로 이동해 코드를 읽으며 문제를 겨우 해결
더보기

아래 CoroutinTest 컴포넌트가 부착된 게임 오브젝트가 활성화 된 상태로 씬에 존재하고, 플레이 중 씬을 로드한 뒤로부터 10초의 시간이 흘렀다고 할 때, 아래 질문에 답하시오. (플레이 중 FPS는 항상 200으로 동일하다고 가정)

using System.Collections;
using UnityEngine;

public class CoroutineTest : MonoBehaviour
{
    private Coroutine myCoroutine;
    private void Start()
    {
        StartTestCoroutine();
        Invoke("StartTestCoroutine", 1);
    }

    void StartTestCoroutine()
    {
        if (myCoroutine != null) StopCoroutine(myCoroutine);
        myCoroutine = StartCoroutine(TestCoroutine());
    }
    IEnumerator TestCoroutine()
    {
        Debug.Log("a");
        yield return null;
        Debug.Log("b");
        yield return new WaitForSeconds(3);
        Debug.Log("c");
    }
}

 

콘솔 창에 출력된 로그를 순서대로 쓰고, 그렇게 출력되는 이유를 설명하시오.

 

● 출제 의도 분석

  1. 코루틴을 활용한 비동기적 흐름 처리 구조를 이해하고 있는가
  2. 코루틴의 실행 타이밍과 흐름 제어 방식을 정확히 이해하고 있는가
  3. 코드 흐름을 시간 축으로 따라가며 정확히 추론할 수 있는가

● 제출한 정답

a b 가 찍히고 1초 후 a b 가 찍히고 3초 후(처음으로부터 4초 후) c가 찍힌다.

Start의 StartTestCoroutine이 호출되고, myCoroutine은 null 이기 때문에 a b가 찍히고 c는 3초를 대기한다.
Invoke로 1초 후에 StartTestCoroutine이 호출되고, myCoroutine은 null이 아니기 때문에 StopCoroutine이 호출돼서 기존의 Debug.Log("c")는 호출되지 않는다.
a b 가 찍히고 3초 후 c가 찍힌다.

 

● 자가 피드백

  • yield return null과 yield return new WaiForSeconds(3)의 실행 흐름 차이에 대해 좀 더 자세히 서술하는 것이 좋을 것 같음
더보기

유니티 2D 프로젝트에서 당신은 게임 전체 씬을 아우르면서 재화를 관리하는 MoneyManager 클래스를 싱글톤 패턴을 활용하여 만들었습ㄴ디ㅏ.

MoneyManager는 게임 내 재화를 체계적으로 관리하기 위한 다양한 메소드를 제공합니다.

- 물건 등을 구매할 때 충분한 재화를 가지고 있는지 확인하는 메소드인 HasEnoughMoney(int amount)

- 재화를 얻거나 사용할 수 있는 메소드인 ChangeMoney(int amount)

- UI에 띄울 문자열 (ex) 10억 1000만 ...)로 환산한느 메소드인 FormatMoney(int money)

이와 관련한 아래의 물음에 답하세요.

using UnityEngine;

public class MoneyManager : MonoBehaviour
{
    public (   A   ) MoneyManager Instance;

    private int money = 0;

    private void Awake()
    {
        if (Instance != null)
        {
            Destroy(gameObject);
            return;
        }
        Instance = (  B  );
        // 씬 간의 이동에도 파괴되지 않도록 처리합니다.
        (     C     )(gameObject);
    }
    
    public bool HasEnoughMoney(int amount)
    {
        return money >= amount;
    }

    public void ChangeMoney(int amount)
    {
        money += amount;
    }

    public string GetFormattedMoney()
    {
        return FormatMoney(money);
    }

    private string FormatMoney(int money)
    {
        // 음수가 아니라고 가정
        Debug.Assert(money >= 0, "너에게 돈을 빌려줄 사람은 없어.");

        if (money == 0)
        {
            return "0";
        }

        string[] units = { "", "만", "억"};
        int unitIndex = 0;
        string result = "";

        // --- ㉠ --- //
        while (money > 0)
        {
            int unitValue = money % 10000;
            money /= 10000;

            if (unitValue > 0)
            {

                result = unitValue.ToString() + units[unitIndex] + " " + result;
            }

            unitIndex++;
        }
        // --- ㉠ --- //

        return result.Trim();
    }
}

 

A, B, C에 들어갈 코드는 무엇인지 작성하고, FormatMoney 메소드의 while문(ㄱ)의 기능을 설명하세요.

 

● 제출 의도 분석

  1. 싱글톤 패턴에 대해 이해하고 있는가
  2. 반복 구조 내에서 로직의 흐름을 분석하고 이해할 수 있는가

● 제출한 정답

  • 돈을 4자릿수로 자르며, 첫 오른쪽 4자리에는 아무것도 안붙이고, 그 다음 4자리 후 "만", 그 다음 4자리 후 "억"을 붙인다.
  • 예를 들면 123456784321의 money가 들어오면, 1234억5678만4321을 result에 할당한다.
  • units는 3의 크기를 갖는 배열이기 때문에, money의 크기가 12자리를 넘어가면, 즉 unitIndex가 3이 되면 IndexOutofBoundexception 에러가 발생할 것으로 예측된다.

● 자가 피드백

  • X
더보기

유니티 3D 프로젝트에서 당신은 특정한 색의 라이트를 켰을 때만 보이는 게임오브젝트를 구현하고자 합니다.

캐릭터가 들고 있는 라이트(lanternLight)의 색과 오브젝트가 드러나게 하는 특정한 색상(reactionColor)가 일치하다면 오브젝트가 보이도록 설계하였습니다.

영상도 포함하였으니, 아래 영상과 코드를 참고하세요.

<영상>

이와 관련한 아래의 물음에 답하세요.

using UnityEngine;
using System.Collections.Generic;
using System.Linq;

public class VisibilityFilter : MonoBehaviour
{
    public Color reactionColor;
    [HideInInspector]
    public Renderer objectRenderer;

    void Start()
    {
        objectRenderer = GetComponent<Renderer>();
        objectRenderer.(  A  ) = false;
    }

    public bool IsLightColorMatching(Color lightColor)
    {
        return lightColor == reactionColor;
    }
}

public class LightControlledVisibility : MonoBehaviour
{
    public Light lanternLight; // 특수한 오브젝트를 보이게하는 랜턴
    public LayerMask layerMask; // 검사대상 오브젝트만 검사하도록 하는 레이어마스크
    private List<VisibilityFilter> trackedFilters = new List<VisibilityFilter>();

    void Start()
    {
        trackedFilters = FindObjectsOfType<VisibilityFilter>().ToList();
    }

    void Update()
    {
        foreach (var filter in trackedFilters)
        {
            filter.objectRenderer.(  A  ) = false;
        }

        RaycastHit[] hits = (가);
        foreach (var hit in hits)
        {
            var hitFilter = hit.transform.GetComponent<VisibilityFilter>();
            if (hitFilter != null && hitFilter.IsLightColorMatching((   B   ).color))
            {
                hitFilter.objectRenderer.(  A  ) = true;
            }
        }
    }
}

 

7번 문제는 총 두개의 질문이 있습니다. 제출 시 7-1, 7-2 로 구분하여 제출해주세요.

[7-1] A, B에 들어갈 코드는 무엇인지 작성하세요. (단, A는 공통적으로 같은 코드가 들어갑니다.)

[7-2] 다음 [조건 1~3]에 맞게 코드를 작성할 때, (가)에 들어갈 코드를 한 줄로 작성하세요.

  • [조건 1] 직선으로 Ray를 발사하여 검출된 모든 대상을 hits에 저장해요.
  • [조건 2] lanternLight로부터 lanternLight 기준 앞(+z) 방향으로 10만큼의 거리를 검사해요.
  • [조건 3] layerMask에 포함되어있는 모든 레이어 중 하나에 해당하는 것만 검출해요.

 

● 출제 의도 분석

  • 컴포넌트의 활성화/비활성화를 이해하고 활용가능한가
  • Raycast를 이해하고 활용해서 충돌체의 처리를 할 수 있는가

● 제출한 정답

  • A => enabled, B => lanternLight
  • ???
  • 예상 정답 : RaycastHit[] hits = Rhysics.RaycastAll(new Ray(lanternLight.transform.position, lanternLight.transform.forward), 10f, layerMask);

● 자가 피드백

  • Raycast에 대한 공부 부족

'Unity 개발 일지 > Unity 공부' 카테고리의 다른 글

어드레서블 (Addressables)  (0) 2025.06.18
GameObject와 MonoBehavior  (0) 2025.05.16
유니티 생명주기(Unity Lifecycle)  (0) 2025.05.14
Unity에서 유닛을 판별하는 방법  (0) 2025.05.02
Mathf 클래스  (0) 2025.05.01