virtual?

 

요약하자면,,

부모 클래스에서 virtual 키워드를 사용하여 함수를 만들면, 자식 클래스에서 이 함수를 재정의 할 수 있도록 허용

 

특징 :

 1. virtual 이 붙은 함수는 자식 클래스에서 재정의가 가능하다.

 2. 자식 클래스에서는 new 또는 override 키워드가 사용가능하다.

    - override는 재정의를 하겠다는 확장의 의미이고, new 는 기본 클래스를 숨기는 의미이다. 

 3. 자식클래스의 함수 시그니쳐가 동일해야 재정의가 가능하다.

 4. 자식클래스의 함수는 base 키워드를 사용해 부모 클래스의 함수를 호출 할 수 있다.

 5. abstract 와는 달리 자식클래스에서 구현은 선택이다. (구현 안하면 부모의 함수 사용)

 6. static, abstract, private,sealed 키워드와는 사용이 불가능하다.

 

그럼 왜 사용할까?

public class Monster
{
    public virtual void hit()
    {
        Console.WriteLine("Monster hit");
    }
}
 
public class Orc : Monster
{
    public override void hit()
    {
        Console.WriteLine("Orc hit");
    }
}
 
public class Elf : Monster
{
    public new void hit()
    {
        Console.WriteLine("Elf hit");
    }
}
 
public class Wolf : Monster
{
    public void hit()
    {
        Console.WriteLine("Wolf hit");
    }
}

 

class Program
 {
     static void Main(string[] args)
     {
         Monster monster1 = new Monster();
         Orc monster2 = new Orc();
         Elf monster3 = new Elf();
         Wolf monster4 = new Wolf();
 
         monster1.hit();
         monster2.hit();
         monster3.hit();
         monster4.hit();
 
         Monster monster5 = new Orc();
         Monster monster6 = new Elf();
         Monster monster7 = new Wolf();
 
 
         Console.WriteLine("////////////////////");
         monster5.hit();
         monster6.hit();
         monster7.hit();
 
     }
 }

 

이런식으로 하나씩 입력을 하면 원하는 값은 얻을 수 있지만

나중에 더 많아지면 코드가 길어지면 자연스럽게 코드가 무거워지고 가독성이 떨어지게 된다.

 

virtual 키워드로 지정된 메서드는 가상 메서드가 되는데 가상 메서드는 자식 클래스가 오버라이드 할 수 있도록 허용된다.

자식 클래스는 override 키워드를 사용해 부모 클래스의 가상메서드를 재정의 할 수 있다.

override?

 

기반 클래스에서 선언된 메소드를 자식 클래스에서 재정의

기반 클래스에서 오버라이딩할 메소드를 미리 virtual로 한정

파생 클래스는 virtual 메소드를 override한정자를 이용하여 재선언

 

즉, 가상메서드는 자식 클래스가 오버라이드 할 수 있도록 허용된 메서드

 

base?

 

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

public class Person : MonoBehaviour
{
   public virtual void Work()
    {
        Debug.Log("일하다");
    }
}

 

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

public class Salesman : Person {
    public override void Work()
    {
        base.Work();
        Debug.Log("나는 셀러리맨이다");
    }
    private void Start()
    {
        Work();
    }
}

 

보기와 같이 메서드의 이름이 Work로 같이 정의되어도 자식클래서에서 같은 이름 접근가능하고

부모클래스를 호출할 수 있다.

 

혹시나 부모클래스의 메소드가 자식 클래스에 의해 변형이 될가 봐 테스트 해보았고,

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

public class Salesman : Person {
    public override void Work()
    {
        base.Work();
        Debug.Log("나는 셀러리맨이다");
    }
    private void Start()
    {
        Person person = new Person();
        person.Work();
    }
}

 

 

결론은 보기과 같이 "아니" 다.

 

ChatGpt에게 수업이 끝나기 전 간단한 예제를 내달라 했는데 혼자서 입력해봤던 것과 난이도가 같아서

더 어려운 예제를 풀 것이다.

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

public class Fish : MonoBehaviour
{
    //1.물고기를 나타내는 Fish 클래스를 작성합니다.
    //이 클래스에는 Swim() 메서드가 있어야 합니다. 이 메서드는 "물고기가 헤엄칩니다."라는 메시지를 출력합니다.
    public virtual void Swim()
    {
        Debug.Log("물고기가 헤엄칩니다.");
    }

    private void Start()
    {
        Swim();
    }
}

 

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

public class Goldfish : Fish
{
    //2.Fish 클래스를 상속받는 Goldfish 클래스를 작성합니다.
    //이 클래스에는 Swim() 메서드를 오버라이드하여 "금붕어가 헤엄칩니다."라는 메시지를 출력하도록 구현합니다.

    public override void Swim()
    {
        Debug.Log("금붕어가 헤엄칩니다.");
    }
}

 

 

자식클래스가 먼저 호출되고 부모클래스가 잇따라 호출되었다.

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

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

상속이란?

 

다른 클래스로부터 코드를 물려받는 것

 

상속의 대상 : 클래스의 멤버(필드, 메소드, 프로퍼티 등)

새로 선언하는 클래스 이름 뒤에 클론( : ) 과 기반 클래스의 이름을 표기하여 상속

물려주는 클래스 : 기반/부모 클래스, 물려받는 클래스 : 파생/자식 클래스

 

하나의 클래스가 하나의 상속만 되지만, 전이적으로 여러개를 이어서 사용 가능

 

 

상속을 받을 때 클래스명 뒤에 MonoBehaviour를 지우고 상속받을 부모 클래스의 이름을 추가

 

3개의 오브젝트에 각각의 스크립트를 부착하였다.

 

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

public class Monster : MonoBehaviour
{
    public void Attack()
    {
        Debug.Log("공격");
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Orc : Monster
{
    public void WarCry()
    {
        Debug.Log("전투함성");
        Attack();
    }
    //public void Start()
    //{
    //    WarCry();
    //}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Dragon : Orc
{
    public void Fly()
    {
        Debug.Log("날기");
        Attack();
        WarCry();
    }
    private void Start()
    {
        Fly();
    }
}

실행을 해보면 이런식의 순서로 호출된다.

  1. Dragon 클래스의 Start 메서드가 호출
  2. Start 메서드 내에서 Fly 메서드가 호출
  3. Fly 메서드 내에서 Attack 메서드가 호출. 이때, Dragon 클래스가 Orc 클래스를 상속하고 있으므로,                          Orc 클래스의 Attack 메서드가 실행됩니다.
  4. Attack 메서드가 실행되고 "공격"이라는 메시지가 출력
  5. Fly 메서드 내에서 WarCry 메서드가 호출. WarCry 메서드는 Orc 클래스에 정의되어 있다.
  6. WarCry 메서드가 실행되고 "전투함성"이라는 메시지가 출력된다.
  7. WarCry 메서드 내에서 Attack 메서드가 호출되는데, Orc 클래스가 Monster 클래스를 상속하고 있으므로,        Monster 클래스의 Attack 메서드가 실행됩니다.
  8. Attack 메서드가 실행되고 다시 "공격"이라는 메시지가 출력된다.

쉽게 말해 부모 자식 관계가 Monster -> Oak -> Dragon 순으로 되어있으므로

서로 상속되어 클래스를 가져와 사용할 수 있다.

 

주의

Unity에서 MonoBehaviour를 상속한 클래스에서 Start 메서드를 사용할 때,

상속 관계에 있는 클래스의 Start 메서드 실행 순서는 보장되지 않는다.

이는 Unity 엔진이 각각의 MonoBehaviour를 포함한 오브젝트들에 대해 독립적으로 처리하기 때문이다.

 

 

자식클래스는 부모클래스의 속성을 받아서 사용할 수 있지만

반대로 일반적으로 부모클래스는 자식클래스의 속성과 메소드를 사용할 수 없다.

 


 

다형성이란?

 

 

오류1. WarCry()를 하면 몬스터들의 대미지를 올려주려다 오류가 발생했다.

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

public class Orc : Monster
{
    Monster[] monsters = GameObject.FindObjectsOfType<Monster>();
    public void WarCry()
    {
        Debug.Log("전투함성");

        //모든 몬스터 공격력 + 10 증가
       
        for(int i = 0; i < monsters.Length; i++)
        {
            monsters[i].damage += 10;
            Debug.LogFormat("추가된 공격력은 {0}", monsters[i].damage);
        }

    }
    private void Start()
    {
        for (int i = 0; i < monsters.Length; i++)
        {
            Debug.LogFormat("기본 공격력은 {0}", monsters[i].damage);
        }
        WarCry();
    }

}

 

 

이것에 대해 알아보니

Unity에서는 MonoBehaviour의 생성자나 인스턴스 필드 초기화자에서 FindObjectsOfType를 호출하는 것을

허용하지 않으므로 대신에 Awake나 Start 메서드에서 호출해야 한다.

따라서 Orc 클래스의 생성자에서 FindObjectsOfType를 호출하는 것은 허용되지 않는다.

Orc 클래스의 WarCry 메서드 내에서 호출하거나, Orc 클래스의 Awake나 Start 메서드에서 호출해야 한다고 한다.

 

오류2. 호출이 2번된다.

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

public class Monster : MonoBehaviour
{
    public float damage = 10f;
    public void Attack()
    {
        Debug.Log("공격");
    }

}

 

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

public class Orc : Monster
{
    public void WarCry()
    {
        Debug.Log("전투함성");

        //모든 몬스터 공격력 + 10 증가
        Monster[] monsters = GameObject.FindObjectsOfType<Monster>();

        for (int i = 0; i < monsters.Length; i++)
        {
            monsters[i].damage += 10;
            Debug.LogFormat("추가된 공격력은 {0}", monsters[i].damage);
        }

    }
    private void Start()
    {
        Monster[] monsters = GameObject.FindObjectsOfType<Monster>();

        for (int i = 0; i < monsters.Length; i++)
        {
            Debug.LogFormat("기본 공격력은 {0}", monsters[i].damage);
        }
        WarCry();
    }

}

 

주어진 코드에서 Orc 클래스의 Start 메서드에서 WarCry 메서드를 호출하기 전에

FindObjectsOfType<Monster>()를 사용하여 몬스터 배열을 얻고 있다.

FindObjectsOfType<Monster>()는 현재 씬에 있는 모든 Monster 클래스의 인스턴스를 찾아 배열로 반환한다.

Start 메서드에서 이를 호출하여 몬스터 배열을 가져오고,

그 다음에 WarCry 메서드를 호출하는 것은 상황에 따라 문제가 될 수 있다.

만약 Start 메서드가 호출될 때 씬에 이미 생성된 몬스터가 있다면,

WarCry 메서드에서 다시 같은 몬스터 배열을 가져오게 되어 몬스터 배열이 중복되어 보일 수 있다.

따라서 Orc 클래스의 Start 메서드에서 WarCry 메서드를 호출하기 전에 몬스터 배열을 가져오는 부분을 제거하면

이런 문제를 해결할 수 있고, WarCry 메서드에서만 몬스터 배열을 가져오도록하면 된다.

 

 

여기서 자세히 보아야할 것은 다형성을 이용한 패턴이라는 것이다.

 

 

https://docs.unity3d.com/ScriptReference/Object.FindObjectsOfType.html

 

Unity - Scripting API: Object.FindObjectsOfType

This does not return assets (such as meshes, textures or prefabs), or objects with HideFlags.DontSave set. Objects attached to inactive GameObjects are only included if inactiveObjects is set to true. Use Resources.FindObjectsOfTypeAll to avoid these limit

docs.unity3d.com

 

명시한 타입의 모든 오브젝트를 찾아 배열로 반환, 씬에 있는 모든 Monster 타입의 오브젝트가 monsters배열에 저장됨

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

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

 

 

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

+ Recent posts