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

 

 

 

 

 

 

인터페이스를 사용하는 이유:

 

사용하지 않으면 다른 클래스의 타입을 모두 검사해야하기 때문에

즉, OnDamage()메서드를 실행하기만 하면 되서

 

 

 

얘네들은 원래 Gun안에 있어야 하지만.

 

이렇게 바꿔서 스크립터블 오브젝트로 동작할 수 있도록 상속

에셋을 생성하는 메뉴를 만들기 위해 특성을 클래스에 추가

CreateAssetMenu는 스크립터블 오브젝트 타입에 추가할 수 있다.

해당 타입의 에셋을 생성할 수 있는 버튼을 Assets와 Create(+버튼) 메뉴에 추가

"(경로)"  "(기본 파일명)", order =메뉴 상에서 순서

 

 

이번에 중점적으로 볼 것은 라인렌더이다. => Gun.cs

라인 렌더러 사이즈 2로 바꾼후 수치를 입력하면 좌표로 쏜다는 걸 알 수 있다.

 

 

 

this.bulletLineRenderer.enabled = false;

 

 

 

 

선을 그려서 총을 쏠때 방향을 확인해야 한다.

 

 

hit.normal => 노말을 쓴다는 건 애니메이션 방향을 정해주기 위해?

 

 

else 문은 타겟이 맞지 않았을 때 ex)허공에 쏠 때

탄알의 궤적을 잡아주려하기 위해

 

실행을 하면 Null 오류가 날 것인데 그러면 if로 조건을 넣어주면 된다.

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; // 재장전 소요 시간
}

 

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();
        }
    }

}

하이어라이키에 있는 프리팹을 Gun프리팹에 모두 할당시키기 위해 Apply All

 

 

 

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

Sprite Shooter - Destroy Barrel and Layer  (0) 2024.03.13
Zombie - IK  (0) 2024.03.07
Tank - Rigidbody.MovePosition  (0) 2024.03.06
UniRun  (4) 2024.03.05
Quaternion  (1) 2024.03.04

 

 

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

public class TankInput : MonoBehaviour
{
    public string moveAxisName = "Vertical";
    public string rotateAxisName = "Horizontal";
    
    public float move { get; private set; }
    public float rotate { get; private set; }

    void Update()
    {
        move = Input.GetAxis(moveAxisName);
        rotate = Input.GetAxis(rotateAxisName);
    }
}

 

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

public class TankMovement : MonoBehaviour
{
    public float moveSpeed = 5f;
    public float rotateSpeed = 180f;
    private TankInput tankInput;
    private Rigidbody tankRigidbody;
    
    void Start()
    {
        tankInput = GetComponent<TankInput>();
        tankRigidbody = GetComponent<Rigidbody>();
    }

    void Update()
    {
        Move();
        Rotate();
    }

    void Move()
    {
        Vector3 moveDistance = tankInput.move * transform.forward * moveSpeed * Time.deltaTime;
        tankRigidbody.MovePosition(tankRigidbody.position + moveDistance);
    }

    void Rotate()
    {
        float turn = tankInput.rotate * rotateSpeed * Time.deltaTime;
        tankRigidbody.rotation = tankRigidbody.rotation * Quaternion.Euler(0, turn, 0);
    }

}

 

 

 

https://docs.unity3d.com/ScriptReference/Rigidbody.MovePosition.html

 

Unity - Scripting API: Rigidbody.MovePosition

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

 

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

Zombie - IK  (0) 2024.03.07
Zombie - Line Render  (0) 2024.03.07
UniRun  (4) 2024.03.05
Quaternion  (1) 2024.03.04
Dodge game  (0) 2024.03.04

 

 

 

 

 

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

 

Unity - Scripting API: Animator.SetBool

Use Animator.SetBool to pass Boolean values to an Animator Controller via script. Use this to trigger transitions between Animator states. For example, triggering a death animation by setting an “alive” boolean to false. See documentation on Animation

docs.unity3d.com

 

테스트를 해보려는 도중 점프가 되지 않아서 원인을 찾아보니 Simulated를 체크하지 않았다.

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class TokoController : MonoBehaviour
{
    public AudioClip deathClip;
    public float jumpForce = 700f;
    private int jumpCount = 0;
    private bool isGrounded = false;
    private bool isDead = false;

    private Rigidbody2D playerRigidbody;
    private Animator animator;
    private AudioSource playerAudio;

    void Start()
    {
        //게임 오브젝트로부터 사용할 컴포넌트들을 가져와 변수에 할당
        playerRigidbody = GetComponent<Rigidbody2D>();
        animator = GetComponent<Animator>();
        playerAudio = GetComponent<AudioSource>();
    }

    void Update()
    {
        if (isDead)
        {
            //사망시 더 이상 처리를 하지 않고 종료
            return;
        }
        if (Input.GetMouseButtonDown(0) && jumpCount < 2)
        {
            jumpCount++; //점프가 두번됨
            //점프 직전 속도를 순간적으로 제로(0,0)으로 변경
            playerRigidbody.velocity = Vector2.zero;
            //리지드바디에 위쪽으로 힘 주기
            playerRigidbody.AddForce(new Vector2(0, jumpForce));
            //오디오 소스 재생
            playerAudio.Play();
            Debug.Log("마우스가 클릭됨");

        }
        else if(Input.GetMouseButtonUp(0) && playerRigidbody.velocity.y > 0) // 마우스에서 손을 떼는 순간 속도의 y값이 양수라면(위로 상승) => 현재 속도를 절반으로 변경
        {
            playerRigidbody.velocity = playerRigidbody.velocity * 0.5f;
        }
        //애니메이터의 Grounded 파라미터를 isGrounded 값으로 갱신
        animator.SetBool("Grounded", isGrounded);
        Debug.LogFormat("{0}", playerRigidbody.velocity.y);

    }

    private void Die()
    {

    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        //트리거 콜라이더를 가진 물체와 충돌 감지
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        //바닥에 닿았음을 감지

        //어떤 콜라이더와 닿았으며, 충돌표면이 위쪽을 보고 있으면
        if (collision.contacts[0].normal.y > 0.7f)
        {
            //isGrouded가 true로 변경하고, 누적 점프횟수를 0으로 초기화
            isGrounded = true;
            jumpCount = 0;
            //Debug.Log("충돌 발생");
        }
    }

    private void OnCollisionExit2D(Collision2D collision)
    {
        //바닥에서 벗어났음을 감지하는 처리
        //어떤 콜라이더에서 떼어진 경우
        isGrounded = false;
    }


}

 

velocity.y의 값을 출력해 보았는데 음수가 나왔다.

Rigidbody의 velocity.y 값이 음수가 나오는 이유는 중력의 영향 때문이다.

Rigidbody의 velocity는 해당 객체의 현재 속도를 나타내며, 유니티에서는 아래쪽 방향을 음의 방향으로 간주한다..

따라서 캐릭터가 점프 후에 일정 높이에 도달하면 중력에 의해 아래로 가속됩니다. 이 때 velocity.y 값은 음수가 된다.

그리고 바닥에 닿으면 다시 상승하기 시작할 때 velocity.y 값은 양수가 될 것이다.

즉, velocity.y 값이 음수가 되는 것은 캐릭터가 점프 후 중력에 의해 아래로 가속되는 정상적인 동작이다.

 

 

 

private void Die()
{
    animator.SetTrigger("Die");
    playerAudio.clip = deathClip;
    playerAudio.Play();

    //속도를 제로로 변경
    playerRigidbody.velocity = Vector2.zero;
    //사망 상태를 true로 변경
    isDead = true;
}

다른 Set계열 메서드와 달리 파라미터에 할당할 새로운 값은 입력하지 않는다.

SetTrigger()메서드는 '방아쇠''를 당길 뿐이다.

트리거 타입의 파라미터는 즉시 true가 되었다가 곧바로 false가 되기 때문에 별도의 값을 지정하지 않는다.

animator.SetTrigger("Die")가 실행되면 곧바로 Die상태러 전환되는 애니메이션 클립이 재생된다.

 

배경을 설정할 것인데 태그에다 이름을 지정하면 order in layer 처럼

레이어의 순서가 정해진다.

 

배경을 스크롤링 할것이므로 스크립트 추가

 

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

public class ScrollingObject : MonoBehaviour
{
    public float speed = 10f;
   

    void Update()
    {
        this.transform.Translate(Vector3.left * speed  * Time.deltaTime);
    }
}

배경과 발판에 스크립트를 부착해서 왼쪽으로 10f 속도로 이동하게 하였기 때문에

플레이어인 Toko는 지나가다 발판이 없어서 떨어진다.

 

Sky에 박스 콜라이더를 넣어주고 isTrigger를 체크하지 않으면

발판과 동시에 

인식을 제대로 하지 못하므로 isTrigger을 체크해야한다.

 

https://docs.unity3d.com/kr/2021.3/Manual/CollidersOverview.html

 

콜라이더 - Unity 매뉴얼

Collider 컴포넌트는 물리적 충돌을 위해 게임 오브젝트의 모양을 정의합니다. 보이지 않는 콜라이더는 게임 오브젝트의 메시와 완전히 똑같을 필요는 없습니다. 메시의 대략적인 근사치로도 효

docs.unity3d.com

 

배경을 무한 반복을 스크립트 추가

 

offset은 Vector2타입지만 transform.position은 Vector3타입이다.

그래서 (Vector2)로 형변환하여 사용

Vector2 값을 Vector3변수에 할당하는 것은 가능한데,,,

이 경우 z값이 0인 Vector3로 자동 형변환되어 할당된다.

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

public class BackgroudLoop : MonoBehaviour
{
    private float width; //배경의 가로 길이
    private void Awake()
    {
       BoxCollider2D col = GetComponent<BoxCollider2D>();
        this.width = col.size.x;
        Debug.Log(this.width);
    }

    void Update()
    {
        if(transform.position.x <= -this.width)
        {
            this.Reposition();
        }
    }

    void Reposition()
    {
        //현재 위치에서 오른쪽으로 가로 길이 * 2 만큼 이동
        Vector2 offset = new Vector2(this.width * 2f, 0);
        transform.position = (Vector2)this.transform.position + offset;
    }


}

sky를 복사해서 기존에 있는 스크린에 넣는다면

예를 들어 화면이 sky -> sky(1) -> sky -> sky(1)이런식으로 두배 간격으로 복사된다.

 

이제 발판을 더 만들 것인데 프리팹으로 만들었고

프리팹을 가져와서 생성된 게임오브젝트에 설정을 바꾸었다.

 

만들어진 발판 프리팹에 Obstacles를 추가할건데 이것을 추가할 때는

프리팹에서 만드는 것이 좋다.

 

그후 발판은 fore 방해물은 middle로 설정하였다.

 

발판 스크립트를 만들었고 방해물 3개가 각각 랜덤으로 나올수 있게 설정

그리고 방해물 회피 성공시 +1

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

public class Platform : MonoBehaviour
{
    public GameObject[] obstacles;
    private bool stepped = false;

    private void OnEnable()
    {
        stepped = false;
        for (int i = 0; i < obstacles.Length; i++)
        {
            if (Random.Range(0, 3) == 0)
            {
                obstacles[i].SetActive(true);
            }
            else
            {
                obstacles[i].SetActive(false);
            }
        }
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.collider.CompareTag("Player") && !stepped)
        {
            stepped = true;
            Debug.Log("점수 1점 추가");
        }
    }
}

 

그후 아까 했던 플랫폼의 모든 프리팹에 적용 되도록 Apply All

 

UI를 만들진 않았지만 UI라던지 게임을 관리할 게임 매니저를 만들었다.

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

public class GameManager : MonoBehaviour
{
    public static GameManager instance;
    public bool isGameover = false; //게임오버 상태
    public GameObject gameoverUI; //게임 오버 시 활성화 할 UI 게임 오브젝트 

    private int score = 0; //게임점수
    void Awake()
    {//싱글턴 변수 instance가 비어있는가?
        if (instance == null)
        {
            //instance가 비어있다면 그곳에 자기 자신을 할당
            instance = this;
        }
        else
        {
            Destroy(this.gameObject);
        }
    }
    private void Update()
    {
        if (isGameover && Input.GetMouseButtonDown(0))
        {
            //게임오버 상태에서 마우스 왼쪽 버튼 클릭하면 현재 씬 재시작
            SceneManager.LoadScene(SceneManager.GetActiveScene().name);
            //현재 활성화된 씬의 정보를Scene 타입의 오브젝트로 가져오는 메서드 씬의 이름을 변수 name으로 제공
        }
    }
    public void AddScore(int newScore)
    {
        //게임오버가 아니라면
        if (!isGameover)
        {
            //점수를 증가
            score += newScore;
            //scoreTect.text = "Score : " + score;
        }
    }
    public void OnPlayerDead()
    {
        isGameover = true;
        //  gameoverUI.SetActive(true);
    }

}

 

이제 platform이 랜덤으로 생성될 것이니 하이어라이키에 있는 플랫폼프리팹을 지워준후

많아질 데이터를 관리하기 위해 오브젝트 풀링사용하여

플랫폼 스폰서를 만들어서 복제된 sky(1)에 할당

오브젝트 풀링
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlatformSpawner : MonoBehaviour
{
    public GameObject platformPrefab;
    public int count = 3;
    private GameObject[] platforms;

    private float lastSpawnTime = 0;
    private float timeBetSpawn = 0;

    private int currentIndex = 0;

    private void Start()
    {
        platforms = new GameObject[count];

        //미리만들자 
        for (int i = 0; i < count; i++)
        {
            platforms[i] = Instantiate(platformPrefab, new Vector3(0, -25, 0), Quaternion.identity);
        }
    }

    private void Update()
    {
        if (Time.time >= lastSpawnTime + timeBetSpawn)
        {
            lastSpawnTime = Time.time;
            timeBetSpawn = Random.Range(1.25f, 2.25f);
            float yPos = Random.Range(-3.5f, 1.5f);

            //OnEnable호출을 위해 
            this.platforms[currentIndex].SetActive(false);
            this.platforms[currentIndex].SetActive(true);

            //재배치 
            this.platforms[currentIndex].transform.position = new Vector2(20, yPos);

            //인덱스 증가 
            currentIndex++;

            //마지막 순번에 도달 했다면 순번 리셋 
            if (currentIndex >= count)
            {
                currentIndex = 0;   //0, 1, 2, 0, 1, 2, 0, 1, 2....
            }
        }
    }
}



오브젝트 풀링 사용하는 이유

유니티에서 오브젝트를 생성하기 위해서는 Instantiate를 사용하고 삭제할 때는 Destroy를 사용

하지만 Instantiate, Destroy 이 두 함수는 무게가 상당히 크다.

Instantiate(오브젝트 생성)은 메모리를 새로 할당하고 리소스를 로드하는 등의 초기화 과정이 필요하고,
Destroy(오브젝트 파괴)는 파괴 이후에 발생하는 가비지 컬렉팅으로 인한 프레임 드랍이 발생할 수 있다.


쉽게 말해 "재사용" 이라고 볼 수 있다.


메모리를 할당 해두기 때문 메모리를 희생하여 성능을 높이는 것이지만,
실시간 작동하는 게임에서 프레임 때문에 선호된다.

--- Inspector ---

 

녹화_2024_03_05_22_52_50_643.mp4
2.89MB

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

Zombie - Line Render  (0) 2024.03.07
Tank - Rigidbody.MovePosition  (0) 2024.03.06
Quaternion  (1) 2024.03.04
Dodge game  (0) 2024.03.04
코루틴 연습  (0) 2024.03.03

 

 

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;
using UnityEngine.UIElements;

public class Capsule : MonoBehaviour
{
    public float speed = 5f;
    void Start()
    {
        
    }

    void Update()
    {
        Move();
    }

    private void Move()
    {
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        Vector3 movement = new Vector3(h, 0, v);
        this.transform.Translate(movement * speed * Time.deltaTime);
        
    }
}

 

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

public class Sphere : MonoBehaviour
{
    public float radius = 3f; // 원의 반지름
    public float rotationSpeed = 30f; // 캡슐의 회전 속도 (각도/초)

    private Transform capsuleTransform; // 부모 오브젝트인 캡슐의 Transform

    private void Start()
    {
        // 부모 오브젝트인 캡슐의 Transform 가져오기
        capsuleTransform = transform.parent;
    }

    private void Update()
    {
        if (capsuleTransform != null)
        {
            // 현재 시간에 따른 회전 각도 계산
            float rotationAngle = Time.time * rotationSpeed;

            // 원 주위를 도는 위치 계산
            float x = Mathf.Cos(rotationAngle) * radius;
            float z = Mathf.Sin(rotationAngle) * radius;

            // 부모 오브젝트의 위치를 기준으로 위치 설정
            transform.position = capsuleTransform.position + new Vector3(x, 0f, z);
        }
    }
}

 

Quaternion.

https://docs.unity3d.com/kr/2020.3/Manual/class-Quaternion.html

 

중요 클래스 - Quaternion - Unity 매뉴얼

Unity는 Quaternion 클래스를 사용하여 게임 오브젝트의 3차원 방향을 저장하고, 이를 통해 한 방향에서 다른 방향으로의 상대 회전을 설명합니다.

docs.unity3d.com

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

 

Unity - Scripting API: Quaternion

They are compact, don't suffer from gimbal lock and can easily be interpolated. Unity internally uses Quaternions to represent all rotations. They are based on complex numbers and are not easy to understand intuitively. You almost never access or modify in

docs.unity3d.com

using UnityEngine;

public class Sphere : MonoBehaviour
{
    public float radius = 3f; // 원의 반지름
    public float rotationSpeed = 30f; // 캡슐의 회전 속도 (각도/초)

    private Transform capsuleTransform; // 부모 오브젝트인 캡슐의 Transform

    private void Start()
    {
        // 부모 오브젝트인 캡슐의 Transform 가져오기
        capsuleTransform = transform.parent;
    }

    private void Update()
    {
        if (capsuleTransform != null)
        {
            // 캡슐의 회전 각도 계산
            float rotationAngle = Time.time * rotationSpeed;

            // 쿼터니언으로 캡슐의 회전을 설정
            Quaternion rotation = Quaternion.Euler(0f, rotationAngle, 0f);

            // 캡슐의 회전을 기반으로 원 주위를 도는 위치 계산
            Vector3 positionOffset = rotation * (Vector3.forward * radius);

            // 부모 오브젝트의 위치를 기준으로 위치 설정
            transform.position = capsuleTransform.position + positionOffset;
        }
    }
}

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

Tank - Rigidbody.MovePosition  (0) 2024.03.06
UniRun  (4) 2024.03.05
Dodge game  (0) 2024.03.04
코루틴 연습  (0) 2024.03.03
StarCraft - Tank  (0) 2024.02.29

 

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

public class Player : MonoBehaviour
{
    public float speed = 1f;
    public Rigidbody playerRigidbody;
    public GameObject bulletPrefab;

    void Update()
    {
        PlayerMove();
    }

    private void PlayerMove()
    {
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        Vector3 movement = new Vector3(h, 0, v) * speed;
        transform.Translate(movement * Time.deltaTime);
    }

    private void OnTriggerEnter(Collider other)
    {
        // 충돌한 오브젝트가 총알인지 확인
        if (other.CompareTag("Bullet"))
        {
            // 플레이어를 파괴
            Destroy(gameObject);
            Debug.Log("Game over");
        }
    }

}

 

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

public class Enemy : MonoBehaviour
{
    [SerializeField] GameObject player;
    [SerializeField] public GameObject bulletPrefab;
    private float spawnInterval = 3f; // 총알 생성 간격
    private Vector3 spawnPosition; // 총알이 생성될 위치
    void Start()
    {
        spawnPosition = this.gameObject.transform.position; // 총알이 생성될 위치 초기화
        StartCoroutine(SpawnBullet()); // 코루틴 시작
    }

    IEnumerator SpawnBullet()
    {
        while (player != null)
        {
            // 총알 생성
            Instantiate(bulletPrefab, spawnPosition, Quaternion.identity);

            // 일정한 간격 기다리기
            yield return new WaitForSeconds(spawnInterval);
        }
    }
}

 

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

public class Bullet : MonoBehaviour
{
    public float speed = 2f;
    public GameObject player;
    void Start()
    {
        player = GameObject.FindGameObjectWithTag("Player");
            // Rigidbody 컴포넌트 가져오기
            Rigidbody bullets = GetComponent<Rigidbody>();
            Vector3 direction = (player.transform.position - transform.position).normalized;
            transform.forward = direction;
            // 총알의 이동 방향 설정 및 속도 적용
            bullets.velocity = transform.forward * speed;

            // 5초 후에 총알 파괴
            Destroy(gameObject, 4.5f);
    }
    private void OnTriggerEnter(Collider other)
    {
        // 충돌한 오브젝트가 총알인지 확인
        if (other.CompareTag("Player"))
        {
            Destroy(gameObject);
        }
        if (other.CompareTag("Wall"))
        {
            Destroy(gameObject);
        }
    }

}

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

UniRun  (4) 2024.03.05
Quaternion  (1) 2024.03.04
코루틴 연습  (0) 2024.03.03
StarCraft - Tank  (0) 2024.02.29
StarCraft - DropShip Logic  (0) 2024.02.28
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.SearchService;

public class coTest : MonoBehaviour
{
    void Start()
    {
        //StartCoroutine(this.Sol1());
        //StartCoroutine(this.Sol2());
    }

    void Update()
    {
       
    }

    //1. 주어진 시간(예: 5초) 동안 특정 작업을 반복 실행하고 그 후에 작업을 중지하는 코루틴을 작성하세요.
    //IEnumerator Sol1()
    //{
    //    Debug.LogFormat("시작"); // 처음에 0초 출력

    //    yield return new WaitForSeconds(1); // 1초 대기

    //    for (int i = 1; i <= 5; i++)
    //    {
    //        Debug.LogFormat("{0}초 경과", i);
    //        yield return new WaitForSeconds(1); // 각 반복마다 1초씩 대기
    //    }

    //    Debug.Log("정지");
    //}

    //2. 리스트에 있는 항목을 하나씩 출력하고 각 항목을 출력한 후에 잠시 대기하는 코루틴을 만들어보세요.
    //IEnumerator Sol2()
    //{
    //    string[] items = { "cap", "clothes", "pants", "shoes" };
    //        foreach (string item in items)
    //        {
    //            Debug.Log(item);
    //            yield return new WaitForSeconds(1);
    //        }
    //        Debug.Log("모든 아이템 출력 완료");
    //}

    //3.플레이어가 특정 키를 누를 때까지 게임을 일시 중지하고, 그 키를 누르면 다시 게임을 계속하는 코루틴을 작성하세요.
 

}

 

using System.Collections;
using UnityEngine;

public class GameLogic : MonoBehaviour
{
    private bool gamePaused = false;

    void Start()
    {
        StartCoroutine(GameLoop());
    }
    //3. 플레이어가 특정 키를 누를 때까지 게임을 일시 중지하고, 그 키를 누르면 다시 게임을 계속하는 코루틴을 작성하세요.
    IEnumerator GameLoop()
    {
        while (true)
        {
            if (!gamePaused)
            {
                // 게임이 진행 중인 경우 여기에 원하는 게임 로직을 추가
                Debug.Log("게임 진행 중");
            }

            // 플레이어가 스페이스바를 누르면 일시 중지 상태를 변경
            if (Input.GetKeyDown(KeyCode.Space))
            {
                gamePaused = !gamePaused;

                if (gamePaused)
                {
                    Debug.Log("게임 일시 중지");
                    Time.timeScale = 0f; // 게임 시간 정지
                }
                else
                {
                    Debug.Log("게임 재개");
                    Time.timeScale = 1f; // 게임 시간 재개
                }
            }

            yield return null;
        }
    }
}

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

Quaternion  (1) 2024.03.04
Dodge game  (0) 2024.03.04
StarCraft - Tank  (0) 2024.02.29
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 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

+ Recent posts