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

namespace yjShin
{
    public class Woman : MonoBehaviour
    {
        private void OnAnimatorIK(int layerIndex)
        {
            Debug.Log("OnAnimatorIK");
        }
    }

}

 

 

 

 

 

오른쪽 팔꿈치의 좌표를 확인

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

namespace yjShin
{
    public class Woman : MonoBehaviour
    {
        private Animator anim;

        private void Start()
        {
            anim = GetComponent<Animator>();
        }
        private void OnAnimatorIK(int layerIndex)
        {
            //Debug.Log("OnAnimatorIK");
            Vector3 rightElbowPosition = anim.GetIKHintPosition(AvatarIKHint.RightElbow);
            Debug.Log(rightElbowPosition);
        }
    }

}

애니메이터에 저장되어 있는 피봇들

 

AvatarIKHint.RightElbow는 애니메이션 시스템에서 제공되는 미리 정의된 상수로, 이를 통해 오른쪽 팔꿈치의 위치를 얻을 수 있습니다. 이 값은 애니메이션 시스템 내부에서 이미 정의되어 있으며, 개발자가 직접 오른쪽 팔꿈치를 찾거나 계산할 필요가 없습니다.

이제 건의 pivot 위치를 잡아 줄 것이다.

 

 [SerializeField] private Transform gunPivot;

 

건의 피봇에 오른쪽 팔꿈치의 위치를 넣어줄 것이다.

건의 피봇의 위치가 팔꿈치에 맞춰졌지만 건의 위치는 다르게 위에 있다.

 

 

건의 위치를 바로 잡아주기 위해

총의 드래그해서 적절한 위치에 이동 시킨후 좌표를 바꿔준다.

 

 

그 후 건의 건을 누르고 복사

 

복사된 좌표를 건에다 붙여준다.

 

 

이제 파지를 할 왼손을 넣어줄 것이다.

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

namespace smilejsu
{
    public class Woman : MonoBehaviour
    {
        private Animator anim;
        [SerializeField] private Transform gunPivot;
        [SerializeField] private Transform leftHandMount;   //왼쪽 손잡이 

        private void Start()
        {
            this.anim = GetComponent<Animator>();    
        }

        private void OnAnimatorIK(int layerIndex)
        {
            Vector3 rightElbowPosition = anim.GetIKHintPosition(AvatarIKHint.RightElbow);
            gunPivot.position = rightElbowPosition;

            this.anim.SetIKPosition(AvatarIKGoal.LeftHand, this.leftHandMount.position);
            this.anim.SetIKRotation(AvatarIKGoal.LeftHand, this.leftHandMount.rotation);
        }
    }

}

 

그 후 인스펙터에 할당

 

 

 

이제 왼쪽팔의 무게와 회전을 Range로 넣어줄 것이다.

 

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

namespace yjShin
{
    public class Woman : MonoBehaviour
    {
        private Animator anim;
        [SerializeField] private Transform gunPivot;
        [SerializeField] private Transform leftHandMount; //왼쪽 손잡이

        [Range(0,1)]
        [SerializeField] private float leftHandPositionWeight = 1f;

        [Range(0, 1)]
        [SerializeField] private float leftHandRotationWeight = 1f;

        private void Start()
        {
            anim = GetComponent<Animator>();
        }
        private void OnAnimatorIK(int layerIndex)
        {
            //Debug.Log("OnAnimatorIK");
            Vector3 rightElbowPosition = anim.GetIKHintPosition(AvatarIKHint.RightElbow);
            //Debug.Log(rightElbowPosition);
            gunPivot.position = rightElbowPosition;

            this.anim.SetIKPosition(AvatarIKGoal.LeftHand, this.leftHandMount.position);
            this.anim.SetIKRotation(AvatarIKGoal.LeftHand, this.leftHandMount.rotation);

            this.anim.SetIKPositionWeight(AvatarIKGoal.LeftHand, this.leftHandPositionWeight);
            this.anim.SetIKRotationWeight(AvatarIKGoal.LeftHand, this.leftHandRotationWeight);
        }
    }

}

Animator들의 속성에 대해 모르는 점이 있으면 다큐멘터리를 적극이용하자

https://docs.unity3d.com/ScriptReference/Animator.html

 

Unity - Scripting API: Animator

Success! Thank you for helping us improve the quality of Unity Documentation. Although we cannot accept all submissions, we do read each suggested change from our users and will make updates where applicable. Close

docs.unity3d.com

 

 

그래서 만들어진 완성본

 

 

 

 

 

 

 

using UnityEngine;

// 주어진 Gun 오브젝트를 쏘거나 재장전
// 알맞은 애니메이션을 재생하고 IK를 사용해 캐릭터 양손이 총에 위치하도록 조정
public class PlayerShooter : MonoBehaviour {
    public Gun gun; // 사용할 총
    public Transform gunPivot; // 총 배치의 기준점
    public Transform leftHandMount; // 총의 왼쪽 손잡이, 왼손이 위치할 지점
    public Transform rightHandMount; // 총의 오른쪽 손잡이, 오른손이 위치할 지점

    private PlayerInput playerInput; // 플레이어의 입력
    private Animator playerAnimator; // 애니메이터 컴포넌트

    private void Start() {
        // 사용할 컴포넌트들을 가져오기
        playerInput = GetComponent<PlayerInput>();
        playerAnimator = GetComponent<Animator>();
    }

    private void OnEnable() {
        // 슈터가 활성화될 때 총도 함께 활성화
        gun.gameObject.SetActive(true);
    }
    
    private void OnDisable() {
        // 슈터가 비활성화될 때 총도 함께 비활성화
        gun.gameObject.SetActive(false);
    }

    private void Update() {
        // 입력을 감지하고 총 발사하거나 재장전
        if (playerInput.fire)
        {
            gun.Fire();
        }
        else if (playerInput.reload)
        {
            {
                if (gun.Reload())
                {
                }
                playerAnimator.SetTrigger("Reload");
            }
        }
        UpdateUI();
    }

    // 탄약 UI 갱신
    private void UpdateUI() {
        if (gun != null && UIManager.instance != null)
        {
            // UI 매니저의 탄약 텍스트에 탄창의 탄약과 남은 전체 탄약을 표시
            UIManager.instance.UpdateAmmoText(gun.magAmmo, gun.ammoRemain);
        }
    }

    // 애니메이터의 IK 갱신
    private void OnAnimatorIK(int layerIndex) {

        //총의 기준점 gunPivot을 3D 모델의 오른쪽 팔꿈치 위치로 이동
        gunPivot.position = playerAnimator.GetIKHintPosition(AvatarIKHint.RightElbow);

        //IK를 사용하여 왼손의 위치와 회전을 총의 왼쪽 손잡이에 맞춤
        playerAnimator.SetIKPositionWeight(AvatarIKGoal.LeftHand, 1.0f);
        playerAnimator.SetIKRotationWeight(AvatarIKGoal.LeftHand, 1.0f);

        playerAnimator.SetIKPosition(AvatarIKGoal.LeftHand, leftHandMount.position);
        playerAnimator.SetIKRotation(AvatarIKGoal.LeftHand, leftHandMount.rotation);
        //IK를 사용하여 오른손의 위치와 회전을 총의 오른쪽 손잡이에 맞춤
        playerAnimator.SetIKPositionWeight(AvatarIKGoal.RightHand, 1.0f);
        playerAnimator.SetIKRotationWeight(AvatarIKGoal.RightHand, 1.0f);

        playerAnimator.SetIKPosition(AvatarIKGoal.RightHand, rightHandMount.position);
        playerAnimator.SetIKRotation(AvatarIKGoal.RightHand, rightHandMount.rotation);

    }
}

 

using System.Collections;
using UnityEngine;
using UnityEngine.UIElements;

// 총을 구현
public class Gun : MonoBehaviour
{
    // 총의 상태를 표현하는 데 사용할 타입을 선언
    public enum State
    {
        Ready, // 발사 준비됨
        Empty, // 탄알집이 빔
        Reloading // 재장전 중
    }

    public State state { get; private set; } // 현재 총의 상태

    public Transform fireTransform; // 탄알이 발사될 위치

    public ParticleSystem muzzleFlashEffect; // 총구 화염 효과
    public ParticleSystem shellEjectEffect; // 탄피 배출 효과

    private LineRenderer bulletLineRenderer; // 탄알 궤적을 그리기 위한 렌더러

    private AudioSource gunAudioPlayer; // 총 소리 재생기

    public GunData gunData; // 총의 현재 데이터

    private float fireDistance = 50f; // 사정거리

    public int ammoRemain = 100; // 남은 전체 탄알
    public int magAmmo; // 현재 탄알집에 남아 있는 탄알

    private float lastFireTime; // 총을 마지막으로 발사한 시점

    private void Awake() {
        // 사용할 컴포넌트의 참조 가져오기
        gunAudioPlayer = GetComponent<AudioSource>();
        this.bulletLineRenderer = GetComponent<LineRenderer>();
        this.bulletLineRenderer.positionCount = 2;
        this.bulletLineRenderer.enabled = false;
    }

    private void OnEnable() {
        ammoRemain = gunData.startAmmoRemain;
        magAmmo = gunData.magCapacity;
        // 총 상태 초기화
        this.state = State.Ready;
        lastFireTime = 0;
    }

    // 발사 시도
    public void Fire() {
        if(this.state == State.Ready && Time.time >= lastFireTime + gunData.timeBetFire)
        {
            lastFireTime = Time.time;
            Shot();
        }
    }

    // 실제 발사 처리
    private void Shot() {
        RaycastHit hit;
        Vector3 hitPosition =Vector3.zero;
        //시작점, 방향, 히트정보, 거리
        if(Physics.Raycast(fireTransform.position, fireTransform.forward, out hit, fireDistance))
        {
            //충돌한 콜라이더의 게임오브젝트에 붙어 있는 IDamageable 인터페이스를 상속받고 있는 컴포넌트를 가져온다.
            IDamageable target = hit.collider.GetComponent<IDamageable>();
            if (target != null)
            {
                target.OnDamage(gunData.damage, hit.point, hit.normal);
            }
            hitPosition = hit.point;
        }
        else
        {
            //탄알이 최대 사정거리까지 날아갔을 때의 위치를 충돌 위치로 사용 => out hit가 빠진다.
            hitPosition = fireTransform.position + fireTransform.forward * fireDistance;
        }
        StartCoroutine(ShotEffect(hitPosition));
        magAmmo--;
        if(magAmmo <= 0)
        {
            state = State.Empty;
        }
    }

    // 발사 이펙트와 소리를 재생하고 탄알 궤적을 그림
    private IEnumerator ShotEffect(Vector3 hitPosition) {

        muzzleFlashEffect.Play();
        shellEjectEffect.Play();

        //총격 소리 재생
        gunAudioPlayer.PlayOneShot(gunData.shotClip);

        //라인랜더러의 시작 위치
        this.bulletLineRenderer.SetPosition(0, fireTransform.position);

        //라인랜더러의 끝 위치
        this.bulletLineRenderer.SetPosition(1, hitPosition);

        // 라인 렌더러를 활성화하여 탄알 궤적을 그림
        bulletLineRenderer.enabled = true;

        // 0.03초 동안 잠시 처리를 대기
        yield return new WaitForSeconds(0.03f);

        // 라인 렌더러를 비활성화하여 탄알 궤적을 지움
        bulletLineRenderer.enabled = false;
    }

    // 재장전 시도
    public bool Reload()
    {
        if (state == State.Reloading || ammoRemain <= 0 || magAmmo >= gunData.magCapacity)
        {
            return false;
        }
        StartCoroutine(ReloadRoutine());
        return true;
    }

    // 실제 재장전 처리를 진행
    private IEnumerator ReloadRoutine() {
        // 현재 상태를 재장전 중 상태로 전환
        state = State.Reloading;

        gunAudioPlayer.PlayOneShot(gunData.reloadClip);
        // 재장전 소요 시간 만큼 처리 쉬기
        yield return new WaitForSeconds(gunData.reloadTime);

        int ammoToFill = gunData.magCapacity - magAmmo;
        
        if(ammoRemain < ammoToFill)
        {
            ammoToFill = ammoRemain;
        }
        magAmmo += ammoToFill;
        ammoRemain -= ammoToFill;
        // 총의 현재 상태를 발사 준비된 상태로 변경
        state = State.Ready;
    }

    private void Update()
    {
        //test
        if(Input.GetMouseButtonDown(0))
        {
            this.Fire();
        }
    }

}

 

using UnityEngine;

// 데미지를 입을 수 있는 타입들이 공통적으로 가져야 하는 인터페이스
public interface IDamageable {
    // 데미지를 입을 수 있는 타입들은 IDamageable을 상속하고 OnDamage 메서드를 반드시 구현해야 한다
    // OnDamage 메서드는 입력으로 데미지 크기(damage), 맞은 지점(hitPoint), 맞은 표면의 방향(hitNormal)을 받는다
    void OnDamage(float damage, Vector3 hitPoint, Vector3 hitNormal);
}
using UnityEngine;

[CreateAssetMenu(menuName = "Scriptable/GunData", fileName = "Gun Data")]
public class GunData : ScriptableObject
{
    public AudioClip shotClip; // 발사 소리
    public AudioClip reloadClip; // 재장전 소리

    public float damage = 25; // 공격력

    public int startAmmoRemain = 100; // 처음에 주어질 전체 탄약
    public int magCapacity = 25; // 탄창 용량

    public float timeBetFire = 0.12f; // 총알 발사 간격
    public float reloadTime = 1.8f; // 재장전 소요 시간
}

'산대특 > 게임 클라이언트 프로그래밍' 카테고리의 다른 글

Sprite Shooter - Player and Monster Animation  (0) 2024.03.14
Sprite Shooter - Destroy Barrel and Layer  (0) 2024.03.13
Zombie - Line Render  (0) 2024.03.07
Tank - Rigidbody.MovePosition  (0) 2024.03.06
UniRun  (4) 2024.03.05
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Tank : MonoBehaviour
{
    public enum Mode
    {
        TankMode,
        SiegeMode
    }
    void Start()
    {
        
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(1))
        {
            Vector3 mousePosScreen = Input.mousePosition;
            Debug.Log(mousePosScreen); // 캔버스의 크기는 좌측하단 0*0 부터 1920 * 1080이지만 추가로 월드좌표가찍힘
            
        }
    }
}

 

이렇게 하면 Local position의 좌표가 찍히는데 이를 월드좌표로 바꿔줄 것이다.

 

월드 좌표로 바꾸어 보았다.

 

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

public class Tank : MonoBehaviour
{
    public enum Mode
    {
        TankMode,
        SiegeMode
    }
    void Start()
    {
        
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(1))
        {
            Vector3 mousePosScreen = Input.mousePosition;
            //Debug.Log(mousePosScreen); // 캔버스의 크기는 좌측하단 0*0 부터 1920 * 1080이지만 추가로 월드좌표가찍힘
            Vector2 mousePosWorld = Camera.main.ScreenToWorldPoint(mousePosScreen);
            Debug.Log(mousePosWorld); // (0.00, 1,00)
        }
    }
}

좌하단 -> 중앙 -> 오른쪽위 순으로 나온 좌표이고

 

탱크의 위치는 0,0,0 으로 초기화

 

카메라는 탱크의 위치에 따라가게 Ctrl + Shift + F로 지정하였다.

 

탱크를 클릭한 위치로 이동하게 하였다.

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

public class Tank : MonoBehaviour
{
    public float Speed = 3f;
    public enum Mode
    {
        TankMode,
        SiegeMode
    }
    void Start()
    {
        
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Vector3 mousePosScreen = Input.mousePosition;
            //Debug.Log(mousePosScreen); // 캔버스의 크기는 좌측하단 0*0 부터 1920 * 1080이지만 추가로 월드좌표가찍힘
            Vector2 mousePosWorld = Camera.main.ScreenToWorldPoint(mousePosScreen);
            Debug.Log(mousePosWorld); // (0.00, 1,00)
        }
        if (Input.GetMouseButton(1))
        {
            Vector3 mousePosScreen = Input.mousePosition;
            Vector2 mousePosWorld = Camera.main.ScreenToWorldPoint(mousePosScreen);
            this.transform.position = Vector3.MoveTowards(this.transform.position, mousePosWorld, Speed * Time.deltaTime);
        }


    }
}

 

  • this.transform.position: 현재 객체(탱크)의 위치를 나타냅니다.
  • mousePosWorld: 마우스의 현재 위치를 나타냅니다.
  • Speed * Time.deltaTime: 탱크가 이동할 속도를 결정합니다. Time.deltaTime은 이전 프레임부터 현재 프레임까지의 경과 시간을 나타내며, 이를 사용하여 프레임 레이트에 관계없이 일정한 속도로 이동할 수 있습니다.

 

스타크래프트 마우스를 클릭하고 있어야 되는 것이 아닌 한번 누르면 이동해야 하고 중간에 경로를 변경할 수 있어야 한다.

 

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

public class Tank : MonoBehaviour
{
    public float Speed = 3f;
    public Vector3 targetPosition = Vector3.zero; // 클릭한 위치를 저장할 변수

    void Update()
    {
        if (Input.GetMouseButtonDown(1)) // 오른쪽 마우스 버튼을 클릭했을 때
        {
            // 마우스 클릭한 위치를 저장
            Vector3 mousePosScreen = Input.mousePosition;
            targetPosition = Camera.main.ScreenToWorldPoint(mousePosScreen);
            //targetPosition.z = 0f; // 2D 게임에서 z 축 값은 0으로 고정
            Debug.Log("New target position: " + targetPosition);
        }

        // 탱크가 저장된 위치로 이동
        if (targetPosition != Vector3.zero)
        {
            transform.position = Vector3.MoveTowards(transform.position, targetPosition, Speed * Time.deltaTime);

            // 탱크가 목표 위치에 도달하면 저장된 위치 초기화
            if (this.transform.position == targetPosition)
            {
                targetPosition = Vector3.zero;
            }
        }
    }
}

 

타켓(클릭된)좌표의 벡터를 0으로 초기화 => Vector3.zero

이동좌표를 정할 때

 

나중에 코드가 길어지면 업데이트에서 처리할일이 많아서 느려질 수 있으므로 코루틴을 사용했다.

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

public class Tank : MonoBehaviour
{
    public float Speed = 3f;
    public Vector3 targetPosition = Vector3.zero; // 클릭한 위치를 저장할 변수

    private void Start()
    {

    }

    void Update()
    {
        if (Input.GetMouseButtonDown(1)) // 오른쪽 마우스 버튼을 클릭했을 때
        {
            StartCoroutine(CoMove()); // 코루틴을 실행합니다.
        }
    }
    IEnumerator CoMove()
    {
        // 마우스 클릭한 위치를 저장
        Vector3 mousePosScreen = Input.mousePosition;
        targetPosition = Camera.main.ScreenToWorldPoint(mousePosScreen);
        //targetPosition.z = 0f; // 2D 게임에서 z 축 값은 0으로 고정
        Debug.Log("New target position: " + targetPosition);
        // 탱크가 저장된 위치로 이동
        while ((targetPosition != Vector3.zero))
        {
            transform.position = Vector3.MoveTowards(transform.position, targetPosition, Speed * Time.deltaTime);

            // 탱크가 목표 위치에 도달하면 저장된 위치 초기화
            if (this.transform.position == targetPosition)
            {
                targetPosition = Vector3.zero;
            }
            yield return null;
        }
    }

}

 

하지만 실행을 해보니 클릭을 할 수록 빨라졌고 코루틴을 중지하는 것이 필요하다고 판단하였다.

 

멤버변수에 현재 실행중인 코루틴을 저장하기 위해 선언하였고

코루틴안에서 실행중이면 정지하도록 하고

다시 코루틴을 시작하게 하였다.

 

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

public class Tank : MonoBehaviour
{
    public float Speed = 3f;
    public Vector3 targetPosition = Vector3.zero; // 클릭한 위치를 저장할 변수
    private Coroutine moveCoroutine; // 현재 실행 중인 코루틴을 저장하기 위한 변수
    private void Start()
    {

    }

    void Update()
    {
        if (Input.GetMouseButtonDown(1)) // 오른쪽 마우스 버튼을 클릭했을 때
        {
            // 기존에 실행 중인 코루틴이 있으면 중지시킴
            if (moveCoroutine != null)
            {
                StopCoroutine(moveCoroutine);
            }
            // 새로운 코루틴 시작
            moveCoroutine = StartCoroutine(CoMove()); // 코루틴을 실행합니다.
        }
    }
    IEnumerator CoMove()
    {
        // 마우스 클릭한 위치를 저장
        Vector3 mousePosScreen = Input.mousePosition;
        targetPosition = Camera.main.ScreenToWorldPoint(mousePosScreen);
        //targetPosition.z = 0f; // 2D 게임에서 z 축 값은 0으로 고정
        Debug.Log("New target position: " + targetPosition);

        // 탱크가 저장된 위치로 이동
        while ((targetPosition != Vector3.zero))
        {
            transform.position = Vector3.MoveTowards(transform.position, targetPosition, Speed * Time.deltaTime);

            // 탱크가 목표 위치에 도달하면 저장된 위치 초기화
            if (this.transform.position == targetPosition)
            {
                targetPosition = Vector3.zero;
            }
            yield return null;
        }
    }

}

 

 

코드를 수정하기 전에도 있었던 문제 같은데 위치에 도달하면 Tank가 사라지는 것을 발견

 

scene에는 있는데 game scene에서 사라지는 이유를 찾다가

layer in order로 맵뒤에 있는건 아닌지 확인도 하였지만

알고보니, 2D인데 클릭한 좌표가 z축이 입력되서 카메라 시점이 같이 움직였기에 시야 밖에서 사라진 것이었다.

z좌표를 0 으로 초기화하는 변수를 지정하고 인자로 넣어주었더니 문제 없이 실행되었다.

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

public class Tank : MonoBehaviour
{
    public float Speed = 3f;
    private Vector3 targetPosition = Vector3.zero; // 클릭한 위치를 저장할 변수
    private Coroutine moveCoroutine; // 현재 실행 중인 코루틴을 저장하기 위한 변수

    void Update()
    {
        if (Input.GetMouseButtonDown(1)) // 오른쪽 마우스 버튼을 클릭했을 때
        {
            // 기존에 실행 중인 코루틴이 있으면 중지시킴
            if (moveCoroutine != null)
            {
                StopCoroutine(moveCoroutine);
            }



            // 새로운 코루틴 시작
            moveCoroutine = StartCoroutine(CoMove()); // 코루틴을 실행합니다.
        }
    }

    IEnumerator CoMove()
    {
        // 마우스 클릭한 위치를 저장
        Vector3 mousePosScreen = Input.mousePosition;
        targetPosition = Camera.main.ScreenToWorldPoint(mousePosScreen);
        Debug.Log("New target position: " + targetPosition);

        // 탱크가 저장된 위치로 이동
        while (Vector3.Distance(transform.position, targetPosition) > 0.01f)
        {
            Vector3 movePos = new Vector3(targetPosition.x, targetPosition.y, 0);
            transform.position = Vector3.MoveTowards(transform.position, movePos, Speed * Time.deltaTime);
            yield return null;
        }

        // 탱크가 목표 위치에 도달하면 저장된 위치 초기화
        targetPosition = Vector3.zero;
    }
}

 

 

더 해야할 부분 : 

아직 미완성인 부분 o버튼 누를경우 이미지 시즈모드로 변경 다시 o누르면 탱크모드

+ 이동중에 o를 누르면 그 자리에서 즉시 이미지 변경

 

우선 O키를 눌러서 콘솔에 찍어보았고

움직이는 중간에 계속 콘솔에 찍혔다.

이 점에 대해 구현을 한다면 O를 누르면 시즈모드가 멈추고 이미지가 변경 되어야 할 것이다.

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

public class Tank : MonoBehaviour
{
    public float Speed = 3f;
    private Vector3 targetPosition = Vector3.zero; // 클릭한 위치를 저장할 변수
    private Coroutine moveCoroutine; // 현재 실행 중인 코루틴을 저장하기 위한 변수

    void Update()
    {
        if (Input.GetMouseButtonDown(1)) // 오른쪽 마우스 버튼을 클릭했을 때
        {
            // 기존에 실행 중인 코루틴이 있으면 중지시킴
            if (moveCoroutine != null)
            {
                StopCoroutine(moveCoroutine);
            }
            // 새로운 코루틴 시작
            moveCoroutine = StartCoroutine(CoMove()); // 코루틴을 실행합니다.
            
        }
        if (Input.GetKeyDown(KeyCode.O))
        {
            Debug.Log("시즈모드 전환");
        }
    }

    IEnumerator CoMove()
    {
        // 마우스 클릭한 위치를 저장
        Vector3 mousePosScreen = Input.mousePosition;
        targetPosition = Camera.main.ScreenToWorldPoint(mousePosScreen);
        Debug.Log("New target position: " + targetPosition);

        // 탱크가 저장된 위치로 이동
        while (Vector3.Distance(transform.position, targetPosition) > 0.01f)
        {
            Vector3 movePos = new Vector3(targetPosition.x, targetPosition.y, 0);
            transform.position = Vector3.MoveTowards(transform.position, movePos, Speed * Time.deltaTime);
            yield return null;
        }

        // 탱크가 목표 위치에 도달하면 저장된 위치 초기화
        targetPosition = Vector3.zero;
    }
}

 

 

 

 

 

[SerializeField] private Sprite image1;

[SerializeField] private Image image1;

두개의 차이

 

 

'산대특 > 게임 클라이언트 프로그래밍' 카테고리의 다른 글

Quaternion  (1) 2024.03.04
Dodge game  (0) 2024.03.04
코루틴 연습  (0) 2024.03.03
StarCraft - DropShip Logic  (0) 2024.02.28
Find the nearest object  (0) 2024.02.27

 

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

public class BionicUnit
{
    public enum BionicUnitType
    {
        Marine,
        Medic
    }
    public BionicUnitType type;
    public string name;
    public BionicUnit(BionicUnitType type, string name)
    {
        this.type = type;
        this.name = name;
    }

}

 

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

public class DropShip : MonoBehaviour
{
    //마린과 메딕을 관리할 리스트
    public List<BionicUnit> units = new List<BionicUnit>();
    //최대로 태울 수 있는 인원의 수
    public const int CAPACITY = 8;
    void Start()
    {
        //생성자를 만든다
        BionicUnit medic = new BionicUnit(BionicUnit.BionicUnitType.Medic, "메딕");
        BionicUnit marine1 = new BionicUnit(BionicUnit.BionicUnitType.Marine, "마린1");
        BionicUnit marine2 = new BionicUnit(BionicUnit.BionicUnitType.Marine, "마린2");
        BionicUnit marine3 = new BionicUnit(BionicUnit.BionicUnitType.Marine, "마린3");
        BionicUnit marine4 = new BionicUnit(BionicUnit.BionicUnitType.Marine, "마린4");
        BionicUnit marine5 = new BionicUnit(BionicUnit.BionicUnitType.Marine, "마린5");
        BionicUnit marine6 = new BionicUnit(BionicUnit.BionicUnitType.Marine, "마린6");
        BionicUnit marine7 = new BionicUnit(BionicUnit.BionicUnitType.Marine, "마린7");
        BionicUnit marine8 = new BionicUnit(BionicUnit.BionicUnitType.Marine, "마린8");

        LoadUnit(medic);
        LoadUnit(marine1);
        LoadUnit(marine2);
        LoadUnit(marine3);
        LoadUnit(marine4);
        LoadUnit(marine5);
        LoadUnit(marine6);
        LoadUnit(marine7);
        LoadUnit(marine8);
        UnLoad(medic);
        UnLoadAll();
    }
    public void LoadUnit(BionicUnit unit)
    {
        if (units.Count >= CAPACITY)
        {
            Debug.Log("<color=red> 자리가 없어서 탑승에 실패 </color>");
        }
        else
        {
            units.Add(unit);
            Debug.LogFormat("{0}이 탑승했습니다. ({1}/{2})", unit.type, units.Count, CAPACITY);
            //Debug.LogFormat("{0}이 탑승했습니다. ({1}/{2})", unit.name, units.Count, CAPACITY);
        }
    }
    public void UnLoad(BionicUnit unit)
    {
        this.units.Remove(unit);
        Debug.LogFormat("{0}이 하차했습니다. ({1},{2})", unit.type, units.Count, CAPACITY);
    }
    public void UnLoadAll()
    {
        Debug.Log("모두 하차 시작");
        foreach (BionicUnit unit in units.ToList())
        //()이나 Start() 메서드에서 units 리스트를 수정하지 않도록 합니다. 리스트를 수정할 필요가 있다면 반복문 밖에서 수정
        {
            UnLoad(unit);
        }
    }
}

이런식으로 .ToList()를 쓰지 않으면 오류가 난다.

이유를 찾아보니 멤버변수에 선언한 "원본" 리스트를 변경시키기 때문에 나는 현상이라고 한다.

따라서 해결방법은 list를 새로 만들어서 다시 copy본으로 저장을 하거나

.ToList()를 사용하면된다.

 

자바스크립트에서 문자열을 자를 때 사용하는 splice와 같은 개념인가?

✔ Array.prototype.splice()

 

 

 

 

 

 

만약 unit을 하나씩이 아닌 원하는 수만큼 내리게 하고 싶다면?

int형으로 인자를 하나 더 받으면 된다.

 

'산대특 > 게임 클라이언트 프로그래밍' 카테고리의 다른 글

Quaternion  (1) 2024.03.04
Dodge game  (0) 2024.03.04
코루틴 연습  (0) 2024.03.03
StarCraft - Tank  (0) 2024.02.29
Find the nearest object  (0) 2024.02.27

 

 

표적이 되는 다른 객체들은 assign할 수 만 있도록 MonsterController 스크립트만 할당 한 후

PlayerController에서 MonsterController 인스턴스로 배열을 선언 한후 

각각의 오브젝트를 assign

 

 

https://learn.microsoft.com/ko-kr/dotnet/api/system.collections.generic.list-1?view=net-8.0

 

List<T> 클래스 (System.Collections.Generic)

인덱스로 액세스할 수 있는 강력한 형식의 개체 목록을 나타냅니다. 목록의 검색, 정렬 및 조작에 사용할 수 있는 메서드를 제공합니다.

learn.microsoft.com

 

https://docs.unity3d.com/ScriptReference/Vector3.Distance.html

 

Unity - Scripting API: Vector3.Distance

Success! Thank you for helping us improve the quality of Unity Documentation. Although we cannot accept all submissions, we do read each suggested change from our users and will make updates where applicable. Close

docs.unity3d.com

 

코딩을 리뷰해보니 어려운 코드는 없었다.

가장 어려운 부분은 구조를 짜는 것이라 생각하는데

그 중에 인스턴스를 생성하거나 리스트를 생성했을때

값이 어떻게 이동되는지 이름만 옮겨지는지 이름과 값이 함께 쌍으로 넘어가는지 이 부분이 어려웠다.

Debug로 콘솔에 많이 찍어보고 값이 어떻게 출력이 되는지 확인하는데 시간을 가장 많이 썼다.


 

 

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

public class PlayerController : MonoBehaviour
{
    public MonsterController[] monsters;
    void Start()
    {
        List<MonsterController> list = new List<MonsterController>();
        for (int i = 0; i < monsters.Length; i++)
        {
            //몬스터컨트롤러의 인스턴스 생성
            MonsterController controller = monsters[i];
            //Debug.Log(controller);
            //플레이어와의 거리 구하기
            float distance = Vector3.Distance(this.transform.position, controller.transform.position);
            //Debug.LogFormat("이름 : {0}, 거리 : {1}",controller.gameObject.name, distance);
            //Debug.Log(controller.gameObject.name);

            if (distance <= 10)
            {
                list.Add(controller); //객체 형태로 담긴다 
            }
        }
        
        for(int i = 0; i < list.Count; i++)
        {
            MonsterController controller = list[i];
            float distance = Vector3.Distance(this.transform.position, controller.transform.position);
            //Debug.LogFormat("이름 : {0} , 거리 : {1}", controller.gameObject.name, distance);
        }





        //거리가 제일 가까운 객체 찾기
        float minDistance = Mathf.Infinity; // 초기값을 무한대로 지정
        MonsterController minObject = null; // 초기값을 담을 인스턴스 생성

        for(int i = 0; i < list.Count;i++)
        {
            MonsterController controller = list[i];
            float distance = Vector3.Distance(this.transform.position, controller.transform.position);
            if (distance < minDistance)
            {
                minDistance = distance;
                minObject = controller;
            }
        }
        Debug.LogFormat("거리가 가장 가까운 객체 : {0}, 거리 : {1}", minObject.name, minDistance);
    }
}​

 

 

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

public class CSightVisualizer : MonoBehaviour
{
    public float radius = 3;
    private void OnDrawGizmos()
    {
        GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, radius);
    }
}

 

 

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

public class MonsterController : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

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

'산대특 > 게임 클라이언트 프로그래밍' 카테고리의 다른 글

Quaternion  (1) 2024.03.04
Dodge game  (0) 2024.03.04
코루틴 연습  (0) 2024.03.03
StarCraft - Tank  (0) 2024.02.29
StarCraft - DropShip Logic  (0) 2024.02.28

흐름 제어 - 코드 실행 순서를 결정하는 것

분기 - 제어 흐름을 여러 갈래로 나누는 것
         단, 프로그램은 한 번에 하나만 실행 가능

분기문1 : if


분기문2 : switch + break

int input = Convert.ToInt32(Console.ReadLine());

int score = (int)(Math.Truncate(input/10.0) * 10);
// 1의 자리 버림

string grade = "";

switch (score)
{
    case 90:
       grade = "A";
       break;
       
    case 80:
       grade = "B";
       break;
       
    case 70:
       grade = "C";
       break;
       
    case 60:
       grade = "D";
       break;
       
    default:
       grade = "F";
       break;
}

 

int input = Convert.ToInt32(Console.ReadLine());

int score = (int)(Math.Truncate(input/10.0) * 10);

string grade = score switch
{
    90 => "A",
    80 => "B",
    70 => "C",
    60 => "D",
    _ => "F"
};



반복문 1 : while
조건을 만족하는 동안 반복

반복문 2 : do while
코드 실행후, 조건을 평가하여 반복 수행

반복문 3 : for
조건을 만족하는 동안 반복(조건 변수 사용)

점프 : 흐름을 특정 위치로 단번에 이동
break, continue, goto, return, throw

break
반복문이나 switch문의 실행을 중단

continue
반복을 건너 뛰어 반복을 계속 수행

goto
지정한 레이블로 제어를 이동

패턴매칭
식이 특정패턴과 일치하는지를 검사

패턴매칭1 : 선언 패턴
주어진 식이 특정형식(int, string) 과 일치하는지를 평가

패턴매칭2 : 형식 패턴
선언 패턴과 거의 비슷하지만 변수를 선언하지 않는다.

패턴매칭3 : 상수 패턴
식이 특정 상수와 일치하는지를 검사

패턴매칭4 : 프로퍼티 패턴
식의 속성이나 필드가 패턴과 일치하는지를 검사

패턴매칭5 관계 패턴
관계 연사자를 이용하여 입력받은 식을 상수와 비교

패턴매칭6 :  논리패턴
복수의 패턴을 논리 연산자(and, or, not)로 조합

패턴매칭7 : 괄호패턴
괄호()로 패턴을 감쌈

패턴매칭8 : 위치 패턴
식의 결과를 분해하고, 분해된 값들이 내장된 복수의 패턴과 일치하는지 검사

패턴매칭9 : var 패턴
null을 포함한 모든 식의 패턴 매칭을 성공시키고, 그 식의 결과를 변수에 할당

패턴매칭10 : 무시 팬턴
var패턴처럼 모든 식과의 패턴 일치 검사를 성공
단, is식에서는 사용할 수 없고, switch식에서만 사용 가능

패턴매칭11 : 목록 패턴
배열이나 리스트가 패턴의 시퀀스가 일치하는지를 검사

 

using System;
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _20240226
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //int a = 1;
            //while(a== 2)
            //{
            //    Console.WriteLine("true");
            //}Console.WriteLine("false");

            //1. 구구단 출력하기: 사용자로부터 숫자를 입력받아 해당 숫자의 구구단을 출력하는 프로그램을 작성하세요.
            //for (int i = 1; i <= 9; i++)
            //{
            //    for(int j = 1; j <= 9; j++)
            //    {
            //        Console.WriteLine($"{i} * {j} = {i * j}");
            //    }
            //}


            //2. 숫자 맞히기 게임: 컴퓨터가 1에서 100 사이의 무작위 숫자를 선택하고, 사용자가 그 숫자를 맞히는 게임을 만드세요.사용자가 입력한 숫자가 정답보다 크면 "더 작은 수를 입력하세요"를, 작으면 "더 큰 수를 입력하세요"를 출력하세요. 정답을 맞힐 때까지 반복합니다.
            //Random random = new Random(); //Random 클래스 인스턴스 생성
            //int rand = random.Next(1, 101);// 1부터 100까지 난수 생성
            //Console.Write("숫자를 입력하세요. >> ");
            //int num = int.Parse(Console.ReadLine());
            //Console.WriteLine("컴퓨터가 입력한 숫자는 {0}입니다.", rand);
            //if (rand == num)
            //{
            //    Console.WriteLine("정답");
            //}
            //else Console.WriteLine("오답");



            //3. 짝수와 홀수 합계: 1에서 100까지의 모든 짝수와 홀수의 합을 구하는 프로그램을 작성하세요.
            //int odd = 0;
            //int even = 0;
            //for (int i = 1; i <= 100; i++)
            //{
            //    if(i % 2 != 0)
            //    {
            //        odd += i;
            //    }else if(i % 2 == 0)
            //    {
            //        even += i;
            //    }

            //}
            //Console.WriteLine(odd);
            //Console.WriteLine(even);

            //4. 사용자가 입력한 수의 팩토리얼 계산: 사용자로부터 정수를 입력받고, 그 수의 팩토리얼을 계산하여 출력하는 프로그램을 작성하세요.
            //int a = Convert.ToInt32(Console.ReadLine());
            //int factorialA = 1;
            //for(int i = 1; i <= a; i++)
            //{
            //    factorialA *= i;
            //}
            //Console.WriteLine(factorialA);
            //5. 세 수 중 최댓값 찾기: 사용자로부터 세 개의 숫자를 입력받고, 그 중 가장 큰 숫자를 출력하는 프로그램을 작성하세요.
            //int a = Convert.ToInt32(Console.ReadLine());
            //int b = Convert.ToInt32(Console.ReadLine());
            //int c = Convert.ToInt32(Console.ReadLine());
            //int maxNum = a;
            
            //if( b > maxNum)
            //{
            //    maxNum = b;
            //}
            //if( c > maxNum)
            //{
            //    maxNum = c;
            //}
            //Console.WriteLine("입력된 숫자중 가장 큰 수는 {0} 입니다. ", maxNum);
        }
    }
}

'낙서장 > C#' 카테고리의 다른 글

[C#] 메서드 오버로딩  (0) 2024.06.02
virtual ,override, base  (2) 2024.03.08
상속과 다형성  (1) 2024.03.08
데이터를 가공하는 연산자  (0) 2024.02.25
데이터를 담는 변수와 상수  (0) 2024.02.22

연산자란?

컴파일러에게 데이터 가공을 지시하는 신호

종류 : 산술 / 관계 / 논리 / 비트 /할당 / 기타 ...

 

증감 연산자 전위, 후위 연산자

int a = 10;
Console.WriteLine(++a);
// a = 11
int b = 10;
Console.WriteLine(b++);
// b = 10
Console.WriteLine(b++);
// b = 11

 

전위 연산자는 보기와 같이 기존에 정해진 값에서 더한 후 그 값을 출력

후위 연산자는 출력된 후 더하므로

다음 출력때 더해진 값을 출력

 

 

 

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _20240223
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //사용자로부터 두 개의 숫자를 입력 받아 덧셈, 뺄셈, 곱셈, 나눗셈 연산을 수행하고 결과를 출력하는 프로그램을 작성하세요.
            //Console.WriteLine(10 + 5);
            //Console.WriteLine(10 - 5);
            //Console.WriteLine(10 * 5);
            //Console.WriteLine(10 / 5);


            //반지름을 입력 받아 원의 넓이를 계산하여 출력하는 프로그램을 작성하세요. (힌트: 원의 넓이 공식 - π * r * r)
            //int r = 5;
            //float circle = r * r * 3.14f;
            //Console.WriteLine(circle);

            //비교 연산자:
            //사용자로부터 두 개의 숫자를 입력 받아 두 숫자가 같은지 여부를 판단하여 결과를 출력하는 프로그램을 작성하세요.
            //Console.WriteLine(10 == 5);

            //사용자로부터 세 개의 숫자를 입력 받아 가장 큰 숫자를 출력하는 프로그램을 작성하세요.
            //int num1 = Convert.ToInt32(Console.ReadLine());
            //int num2 = Convert.ToInt32(Console.ReadLine());
            //int num3 = Convert.ToInt32(Console.ReadLine());
            //int maxNum = Math.Max(num1, Math.Max(num2, num3));
            //Console.WriteLine(maxNum);


            //논리 연산자:
            //사용자로부터 입력받은 숫자가 짝수인지 여부를 판단하여 결과를 출력하는 프로그램을 작성하세요.
            //int num = int.Parse(Console.ReadLine());
            //bool isEven = num % 2 == 0;
            //Console.WriteLine(isEven);


            //사용자로부터 입력받은 숫자가 3의 배수인지 그리고 5의 배수인지를 동시에 판단하여 결과를 출력하는 프로그램을 작성하세요.
            //int num = int.Parse(Console.ReadLine());
            //if(num % 15 == 0)
            //{
            //    Console.WriteLine("3과 5배 공배수");
            //}
            //else if(num % 3 == 0)
            //{
            //    Console.WriteLine("3의 배수입니다.");
            //}else if(num % 5 == 0){
            //    Console.WriteLine("5의 배수");
            //}
            //else {
            //    Console.WriteLine("다른 수");
            //}

            //대입 연산자:
            //사용자로부터 숫자를 입력 받아 그 숫자를 10배로 증가시킨 후 결과를 출력하는 프로그램을 작성하세요.
            //int number = Convert.ToInt32(Console.ReadLine());
            //int result = number * 10;
            //Console.WriteLine("Result: " + result);
            //두 변수의 값을 교환하는 프로그램을 작성하세요. (예: 변수 a에 5, 변수 b에 10이 있다면 a에는 10, b에는 5가 저장되어야 함)
            //int a = 10;
            //int b = 20;
            //int temp = a;
            //a = b;
            //b = temp;
            //Console.Write("a는 {0} b는 {1}", a, b);

        }

    }
}

'낙서장 > C#' 카테고리의 다른 글

[C#] 메서드 오버로딩  (0) 2024.06.02
virtual ,override, base  (2) 2024.03.08
상속과 다형성  (1) 2024.03.08
흐름 제어  (1) 2024.02.26
데이터를 담는 변수와 상수  (0) 2024.02.22

데이터 형식(Data Types):

데이터의 '유형'과 '크기'를 지정

 

1. 기본 데이터 형식 (값 형식)

- 정수형식 (int,byte ..etc)

- 부동 소수형식 (float, double, demical)

2. 복합 데이터 형식

- 클래스

- 구조체(값 형식)

- 인터페이스

스택(Stack):

데이터를 쌓아 올리는 구조의 메모리

쌓인 순서의 역순으로 필요 없는 데이터를 자동으로 제거(자동메모리) => LIFO(Last In First Out)

 

힙(Heap):

자유롭게 데이터를 저장할 수 있는 메모리

별명 : 자유 저장소


 

값 형식(Value Type):

메모리에 값을 담는 형식

스택에 할당

기본 데이터 형식과 구조체가 여기에 해당됨

 

참조형식(Reference Type):

메모리에 다른 변수의 "주소를 담는" 데이터 형식

힙에 할당

 

기본 데이터 형식(Primitive Types)

수 형식(정수 형식, 부동 소수점 형식)

논리 형식

문자열 형식

object 형식

 

복합데이터 형식 : 기본 데이터 형식을 바탕으로 만들어짐

 

박싱 : 값 형식을 object형식에 담아 힙에 올리기
언박싱 : 힙에 올라가 있는 데이터를 object에서 꺼내 값 형식으로 옮기기

 

변수 : 변경이 가능한 수
상수 : 항상 최초의 상태를 유지하는 수 -> 변경하려는 시도시 컴파일 에러

 

열거형식 : 하나의 이름 아래 묶인 상수들의 집합

=>  enum키워드 사용
상수 선언시 ex) enum dia{yes} -> dia.yes;


var키워드 - 컴파일러가 자동으로 형식을 추론
ex) var a = 3; // a는 int 형식
주의 : "지역변수 안"에서만 사용가능(메소드 안에서만 ) + 클래스, 구조체 사용불가

 

using System;
using System.Collections.Generic;
using System.Data.SqlTypes;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _20240222
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //변수(Variables)에 대한 예제 문제:
            //1-1. 특정 프로그램에서 사용자의 이름을 입력받아 변수에 저장하고, 그 이름을 환영하는 메시지를 출력하는 프로그램을 작성하세요.
            //string a = "예준";
            //Console.WriteLine($"어서오세요 {a}");
            //Console.WriteLine("어서오세요 {0}", a);

            //2-2. 특정 도시의 현재 온도를 나타내는 변수를 만들고, 이 변수의 값을 5도 증가시킨 후 새로운 온도를 출력하는 프로그램을 작성하세요.
            //int temp = 24;
            //int now = temp + 5;
            //Console.WriteLine($"현재 온도는 {now}도 입니다");

            //3-3. 사용자로부터 두 개의 숫자를 입력받아 변수에 저장하고, 이 두 숫자를 더한 결과를 출력하는 프로그램을 작성하세요.
            //int a = 10;
            //int b = 20;
            //Console.WriteLine(a + b);

            //상수(Constants)에 대한 예제 문제:
            //2-1. 원주율을 상수로 선언하여 반지름이 5인 원의 넓이를 계산하여 출력하는 프로그램을 작성하세요.
            //float pi = 3.14f;
            //int r = 5;  // => float 과 int 의 곱이 된다.
            //float size = pi * pi * r;
            //Console.WriteLine(size);

            //2-2. 세금 비율을 0.1로 상수로 선언하고, 사용자로부터 어떤 상품의 가격을 입력받아 세금을 계산하여 총 가격을 출력하는 프로그램을 작성하세요.
            //float rate = 0.1f;
            //float price = 50.6f;
            //float total = rate * price;
            //Console.WriteLine(total); //=> 5.06

            //2-3. 1년에는 몇 개의 달이 있는지를 상수로 선언하고, 이 값을 사용하여 1년이 몇 개의 주가 있는지를 출력하는 프로그램을 작성하세요.
            //int CountOfMonth = 12;
            //int day = 365;
            //float CountOfWeek = day / CountOfMonth;
            //Console.WriteLine(CountOfWeek);
            // => 인트형끼리 나누면 소수점 자리가 Truncate을 하지 않아도 된다.


            //float CountOfMonth = 12f;
            //float day = 365f;
            ////float CountOfWeek = Math.Truncate(day / CountOfMonth); ==> 오류
            //float CountOfWeek = day / CountOfMonth;
            //Console.WriteLine(Math.Truncate(CountOfWeek)); // 30


        }
    }
}

'낙서장 > C#' 카테고리의 다른 글

[C#] 메서드 오버로딩  (0) 2024.06.02
virtual ,override, base  (2) 2024.03.08
상속과 다형성  (1) 2024.03.08
흐름 제어  (1) 2024.02.26
데이터를 가공하는 연산자  (0) 2024.02.25

차례대로 Standard, CustomLambert, HalfLambert

 

1. Standard

 

Shader "Custom/lambert"
{
    Properties
    {
        _MainTex("Main Texture", 2D) = "white"{}
        _NormalMap("NormalMap", 2D) = "bump" {}
        _Color("Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        CGPROGRAM
        #pragma surface surf Standard
        #pragma target 3.0
        sampler2D _MainTex;
        sampler2D _NormalMap;     
        float4 _Color;
        struct Input
        {
            float4 color : COLOR;
            float2 uv_MainTex;
            float2 uv_NormalMap;
        };

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            
            float4 c = tex2D(_MainTex, IN.uv_MainTex);
            float4 d = tex2D(_NormalMap, IN.uv_NormalMap);
            o.Normal = UnpackNormal (d);
            o.Albedo = c.rgb * _Color;
            o.Alpha = c.a;
        }
        // 빛을 만든다
       
        ENDCG
    }
    FallBack "Diffuse"
}

 

2. CustomLambert

Shader "Custom/customLambert"
{
    Properties
    {
        _MainTex("Main Texture", 2D) = "white"{}
        _NormalMap("NormalMap", 2D) = "bump" {}
        _Color("Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        CGPROGRAM
        #pragma surface surf _MyLambert
        #pragma target 3.0
        sampler2D _MainTex;
        sampler2D _NormalMap;     
        float4 _Color;
        struct Input
        {
            float4 color : COLOR;
            float2 uv_MainTex;
            float2 uv_NormalMap;
        };

        void surf (Input IN, inout SurfaceOutput o)
        {
            float4 c = tex2D(_MainTex, IN.uv_MainTex);
            float4 d = tex2D(_NormalMap, IN.uv_NormalMap);
            o.Normal = UnpackNormal (d);
            o.Albedo = c.rgb * _Color;
            o.Alpha = c.a;
        }
        // 빛을 만든다
        float4 Lighting_MyLambert(SurfaceOutput s, float3 lightDir, float atten)
        {
            float4 final;
            float ndot1 = dot(s.Normal, lightDir);
            final.rgb = ndot1 * s.Albedo.rgb;
            final.a = s.Alpha;
            return final;
            }
        ENDCG
    }
    FallBack "Diffuse"
}

 

2-2. CustomLambert

Shader "Custom/customLambert"
{
    Properties
    {
        _NormalMap("Normal Map",2D) = "bump"{}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        CGPROGRAM
        #pragma surface surf _MyLambert noambient;
        #pragma target 3.0
        sampler2D _NormalMap;     
        struct Input
        {
            float4 color : COLOR;
            float2 uv_NormalMap;
        };
        void surf (Input IN, inout SurfaceOutput o)
        {
            float4 c = tex2D(_NormalMap, IN.uv_NormalMap);
            o.Normal = UnpackNormal (c);
            o.Albedo = 1;
            
        }

        float4 Lighting_MyLambert(SurfaceOutput s, float3 lightDir, float atten)
        {
            float4 final;
            //return float4(1,0,0,1);
            //노멀벡터와 조명벡터를 내적해라
            float ndot1 = dot(s.Normal, lightDir);
            final = saturate(ndot1);
            return final;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

 

3. HalfLambert

Shader "Custom/customLambert2"
{
    Properties
    {
        _MainTex("Main Texture", 2D) = "white"{}
        _NormalMap("NormalMap", 2D) = "bump" {}
        _Color("Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        CGPROGRAM
        #pragma surface surf _MyLambert
        #pragma target 3.0
        sampler2D _MainTex;
        sampler2D _NormalMap;     
        float4 _Color;
        struct Input
        {
            float4 color : COLOR;
            float2 uv_MainTex;
            float2 uv_NormalMap;
        };

        void surf (Input IN, inout SurfaceOutput o)
        {
            float4 c = tex2D(_MainTex, IN.uv_MainTex);
            float4 d = tex2D(_NormalMap, IN.uv_NormalMap);
            o.Normal = UnpackNormal (d);
            o.Albedo = c.rgb * _Color;
            o.Alpha = c.a;
        }
        // 빛을 만든다
        float4 Lighting_MyLambert(SurfaceOutput s, float3 lightDir, float atten)
        {
            float4 final;
            float ndot1 = dot(s.Normal, lightDir)* 0.5 + 0.5;
            final = pow(ndot1, 3);
            final.rgb = ndot1 * s.Albedo.rgb * _LightColor0.rgb * atten;
            final.a = s.Alpha;
            return final;
            }
        ENDCG
    }
    FallBack "Diffuse"
}

 


 

CustomLambert의 기본형

 

1. #pragma surface surf _MyLambert

2. void surf (Input IN, inout SurfaceOutput o)

3. float4 Lighting_MyLambert(SurfaceOutput s, float3 lightDir, float atten)
 {
  ;
     return float4(1, 0, 0 , 1);

 }

 

우선 이 3가지로 스탠다드를 바꿔주고 메서드를 만들어준다.

이때 매개변수는 변경하거나 수정할 수 없으며 주어진 대로 사용해야 한다.

 

 

float3 lightDir
조명 방향의 벡터
단, 조명을 편하게 사용하기 위해 뒤집혀지고 길이가 1인 단위 벡터 상태

 

float atten(uation) - 감쇠
그림자를 받거나 거리가 멀어지면 점점 조명이 흐려지는 라이트의 거리별 현상

 

dot : 노멀 벡터와 라이트 벡터를 내적 연산해주는 함수

우리는 o.Normal에 값을 넣지 않았지만 s.Normal의 값을 가져올 수 있다.

 

lightDir

lightDir은 vertex에서 바라보는 조명의 방향을 뒤집은 조명 벡터이므로 두 벡터를 단순히 내적하면 cos값이 나온다.

 

나중에 ambient light 또는 추가라이트를 비출 때 이 음수는 문제를 일으킬수 있는데

이(0아래는 전부 0으로 잘라주는)를 해결해주는 함수 saturate, max가 있다.

saturate
max

이 함수들을 사용하면 빨간선처럼 0보다 작은수는 0으로 지정해준다.

 


 

HalfLambert

 

* 0.5 + 0.5는 마법의 숫자이다.

이 공식은 -1 ~ 1까지의 숫자를 0에서 1까지의 범위로 만들어준다.

그 결과 cos라인을 끌어 올려주어 부드러운 결과 값이 나온다.

 

조명의 색상 or 감쇠를 이용할 수 있다.

ex) final.rgb = ndotl * s.Albedo * _LightColor0.rgb * atten;

 

https://docs.unity3d.com/kr/2021.3/Manual/SL-UnityShaderVariables.html

 

빌트인 셰이더 변수 - Unity 매뉴얼

Unity의 빌트인 포함 파일에는 셰이더에 대한 전역 변수(예: 현재 오브젝트의 변환 매트릭스, 광원 파라미터, 현재 시간 등)가 포함되어 있습니다. 이러한 셰이더 프로그램에서 이러한 전역 변수

docs.unity3d.com

 

이에 따라 조명의 색깔을 변경하고 거리에 따른 그림자를 조절할 수 있다.

https://docs.unity3d.com/kr/530/Manual/StandardShaderMaterialParameterAlbedoColor.html

 

알베도 컬러 및 투명도 - Unity 매뉴얼

Albedo 파라미터는 표면의 베이스 컬러를 제어합니다.

docs.unity3d.com

 

Shader "Custom/fire"
{
    Properties
    {
        _MainTex ("Main Texture", 2D) = "white"{}
        _MainTex2 ("Main Texture2", 2D) = "white"{}
        _Speed("Speed", Range(0,10.0)) = 1
        _BrightNess("BrightNess", Range(0,1)) = 1
        _Color("Color",Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent"}

        CGPROGRAM
        #pragma surface surf Standard alpha:fade
        #pragma target 3.0

        sampler2D _MainTex;
        sampler2D _MainTex2;
        float _Speed;
        float _BrightNess;
        float _Color;

        struct Input
        {
            float2 uv_MainTex;
            float2 uv_MainTex2;
           
        };

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            //데이터 
            float2 d_uv = IN.uv_MainTex2;
            d_uv.y -= _Time.y;
           
            float4 d = tex2D(_MainTex2, d_uv * _Speed);

            //d.rgb
            //d.r => 0 

            //체크 
            float2 c_uv = IN.uv_MainTex;
            float4 c = tex2D(_MainTex, c_uv + d); 

            //체크를 보여주고 있음 
            o.Emission = c.rgb * _BrightNess;
            o.Alpha = c.a ;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

 

 


 

 

 

Shader "Custom/vertexcolor"
{
    Properties
    {
        _MainTex("Main Texture", 2D) = "white"{}
        _MainTex2("Main Texture2", 2D) = "white"{}
        _MainTex3("Main Texture3", 2D) = "white"{}
        _MainTex4("Main Texture4", 2D) = "white"{}
        
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
     

        CGPROGRAM
      
        #pragma surface surf Standard fullforwardshadows

        #pragma target 3.0

        sampler2D _MainTex;
        sampler2D _MainTex2;
        sampler2D _MainTex3;
        sampler2D _MainTex4;


        
        struct Input
        {
            float4 color : COLOR;
            float2 uv_MainTex;
            float2 uv_MainTex2;
            float2 uv_MainTex3;
            float2 uv_MainTex4;


        };

       

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
           float4 c =tex2D(_MainTex, IN.uv_MainTex);
           //o.Emission = c.rgb + IN.color.rgb;

           IN.uv_MainTex2 -= _Time.y * 0.2;
           //float2 d_uv2 = IN.uv_MainTex2;
            //d_uv2.y -= _Time.y;
            float4 d = tex2D(_MainTex2,IN.uv_MainTex2);
           o.Emission = lerp(c.r, d.rgb, IN.color.r ); // 보이지 않는 곳을 0 보이는 곳을 1이라고 생각
           

           float4 e = tex2D(_MainTex3, IN.uv_MainTex3);
           o.Emission = lerp(o.Emission, e.rgb, IN.color.g);

           float4 f = tex2D(_MainTex4, IN.uv_MainTex4);
           o.Emission = lerp(o.Emission, f.rgb, IN.color.b);
           
        }
        ENDCG
    }
    FallBack "Diffuse"
}

 


 

Shader "Custom/rock"
{
    Properties
    {
        _MainTex ("Main Texture", 2D) = "white"{}
        _MainTex2 ("Main Texture2", 2D) = "white"{}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        CGPROGRAM
        #pragma surface surf Standard
        #pragma target 3.0

        sampler2D _MainTex;
        sampler2D _MainTex2;

        struct Input
        {
            float4 color : COLOR; //버텍스 컬러 
            float2 uv_MainTex;
        };

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            //이끼 
            float4 d = tex2D(_MainTex2, IN.uv_MainTex);
            //돌맹이 
            float4 c = tex2D(_MainTex, IN.uv_MainTex);
            
            //이끼를 버텍스 컬러 R 부분에 출력하자 
            o.Albedo = lerp(c.rgb, d.rgb, IN.color.r);
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

 


https://docs.unity3d.com/kr/530/Manual/StandardShaderMaterialParameterMetallic.html

https://docs.unity3d.com/kr/2022.1/Manual/StandardShaderMaterialParameterSmoothness.html

https://docs.unity3d.com/Manual/StandardShaderMaterialParameterNormalMap.html

 

Unity - Manual: Normal map (Bump mapping)

Normal map (Bump mapping) Normal maps are a type of Bump Map. They are a special kind of texture that allow you to add surface detail such as bumps, grooves, and scratches to a model which catch the light as if they are represented by real geometry. Unity

docs.unity3d.com

 

평활도 - Unity 매뉴얼

평활도(Smoothness) 개념은 스페큘러 워크플로우와 메탈릭 워크플로우 모두에 적용되며 매우 비슷한 방식으로 작동합니다. 디폴트로 메탈릭 또는 스페큘러 텍스처 맵이 할당되지 않았다면 슬라이

docs.unity3d.com

 

메탈릭모드: 메탈릭 파라미터 - Unity 매뉴얼

Metallic 워크플로에서 작업하는 경우(스페큘러 워크플로와 달리) 표면의 반사도 및 광원 반응이 메탈릭 레벨과 평활도 레벨에 따라 바뀝니다.

docs.unity3d.com

 

Shader "Custom/Metallic"
{
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
        _NormalMap ("NormalMap", 2D) = "bump"{}
        _NormalStength("Normal Stength", Range(0.1, 2)) = 0
        _Occlusion("Occlusion", 2D) = "white"{}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        CGPROGRAM
        #pragma surface surf Standard
        #pragma target 3.0

        sampler2D _MainTex;
        sampler2D _NormalMap;
        sampler2D _Occlusion;
        float _NormalStength;

        struct Input
        {
            float2 uv_MainTex;
            float2 uv_NormalMap;
        };

        half _Glossiness;
        half _Metallic;

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
            o.Albedo = c.rgb;

            float4 n = tex2D(_NormalMap, IN.uv_NormalMap);
            float3 normal = UnpackNormal(n);
            fixed4 occlusion = tex2D(_Occlusion, IN.uv_MainTex);

            o.Occlusion = occlusion;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;

            o.Normal = float3(normal.x * _NormalStength , normal.y * _NormalStength , normal.z);

            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

 

 

 

Shader "Custom/rockGolem2"
{
    Properties
    {
        _MainTex("MainTex",2D) = "white" {}
        _MainTex2("Left Hand",2D) = "white" {}
        _MainTex3("Right Hand",2D) = "white" {}
        _NormalMap ("NormalMap", 2D) = "bump"{}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
        _Occlusion ("Occlusion", 2D) = "white"{}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
       
        CGPROGRAM
        #pragma surface surf Standard
        #pragma target 3.0

        sampler2D _MainTex;
        sampler2D _MainTex2;
        sampler2D _MainTex3;
        sampler2D _NormalMap;
        half _Glossiness;
        half _Metallic;
        sampler2D _Occlusion;



        struct Input
        {
            float2 uv_MainTex;
            float2 uv_MainTex2;
            float2 uv_MainTex3;
            float4 color : COLOR;
            float2 uv_NormalMap;
        };
    
        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            //fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
            //o.Albedo = IN.color.rgb;
            //o.Alpha = c.a;
            IN.uv_MainTex2.y += _Time.y * 0.2;
            IN.uv_MainTex3.y += _Time.y * 0.2;
            fixed4 body = tex2D (_MainTex, IN.uv_MainTex);
            fixed4 lHand = tex2D (_MainTex2, IN.uv_MainTex2);
            fixed4 rHand = tex2D (_MainTex3, IN.uv_MainTex3);


            //float2 d_uv2 = IN.uv_MainTex2;
            //d_uv2.y -= _Time.y;




            //o.Albedo = lerp(0, body, IN.color.r);
            o.Albedo = lerp(body, lHand, IN.color.r);
            o.Albedo = lerp(o.Albedo, rHand, IN.color.b);

            float4 n = tex2D(_NormalMap, IN.uv_NormalMap);
            float3 normal = UnpackNormal(n);
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Normal = normal;
            o.Occlusion = tex2D(_Occlusion, IN.uv_MainTex);


        }
        ENDCG
    }
    FallBack "Diffuse"
}

하이어라키에 Sphere 구체 추가

ㅏ이어

폴더를 만든 후 Materials와 Shaders를 추가해준다

그 후

NewSurfaceShader를 New Material로 드래그해서 Assign

그 후

New Material을 Sphere에 드래그해서 Assign

코드 안에 내용들을 지워주는데 Input에 아무것도 없으면 오류가 난다.

이런식으로 프로티에 작성해주면 인스펙터에 생성이된다.

드래그한 코드

즉 o오브젝트에 Albedo를 float3(red, green, blue)를 할당하는데 red에만 1을 선언한 상태와 결과

 

Emission으로 바구면 평면으로 볼 수 있다.

 

Emission에 컬러속성을 할당 한후 값을 넣어주면

인스펙터에서 컬러창으로 색을 변경할 수 있다.

r g b에 각각 red blue green 변수를 선언후 할당한후

pow를 사용 pow(x,y) => x를 y 제곱

Red, Greed, Blue를 각각 이동하여 색을 변경할 수 있다.

BrightDarkness 변수 선언후 + 를 사용해서

Emission값을 flat(r,g,b)와 함께 사용

 

Texture 2D속성을 사용

float4 c = tex2D(_MainTex, IN.uv_MainTex);

그 후 만들어진 텍스쳐 필드에 텍스쳐 할당

https://docs.unity3d.com/Manual/SL-SurfaceShaders.html

 

Unity - Manual: Writing Surface Shaders

Surface Shaders and rendering paths Writing Surface Shaders In the Built-in Render PipelineA series of operations that take the contents of a Scene, and displays them on a screen. Unity lets you choose from pre-built render pipelines, or write your own. Mo

docs.unity3d.com

 

+ Recent posts