W298.dev
ProjectsPostsAbout Me
youtube
Pages
ProjectsPostsAbout Me
Posts
TAGS
All
RL-Obstacle-Avoid
RL-Competitive
Robot-Escape
Assembly Definition
ML-Agent
RL-Obstacle-Avoid
RL-Competitive
Unity
RL-Obstacle-Avoid
RL-Competitive
Robot-Escape
Assembly Definition
SERIES
RL-Obstacle-Avoid
RL-Competitive
Robot-Escape

Gun System

1. FireAction 바인딩

1public class RobotInputHandler : MonoBehaviour
2{
3    ...
4    public InputAction fireAction;
5
6    public bool isFire = false;
7    ...
8
9    public UnityEvent fireEvent;
10    public UnityEvent fireStartEvent;
11    public UnityEvent fireEndEvent;
12
13    void Start()
14    {
15        ...
16        fireAction.Enable();
17
18        fireAction.started += context => fireStartEvent.Invoke();
19        fireAction.canceled += context => fireEndEvent.Invoke();
20
21        ...
22    }
23
24    void FixedUpdate()
25    {
26        ...
27        if (isFire)
28        {
29            fireEvent.Invoke();
30        }
31    }
32}
33
https://velog.velcdn.com/images/lutca1320/post/8541073d-69a9-4212-b15f-612db60ea9b2/image.png
UnityEvent 로 GunController 와 바인딩을 진행했다.
  • OnFire : 버튼이 눌려있을 때마다 호출
  • OnFireStart : 버튼이 처음 눌렸을 때 호출
  • OnFireEnd : 버튼에서 땠을 때 호출
이와 같이 나눈 이유는 FULL-AUTO 모드와 SEMI-AUTO 모드 둘 다 사용하기 위해서이다.

2. GunController 구현

내용이 많아서 좀 쪼갰다.

OnFire, OnFireStart, OnFireEnd

  • 아까 위에서 이벤트 바인딩한 함수이다. 이벤트가 발생하면 가장 먼저 실행된다.
  • FIREMODE 에 따라서 로직이 다르다. fireDelay 변수를 설정해 총기의 발사 딜레이를 구현하였다.
실제 딜레이에 맞춰서 FireWeapon 함수가 실행된다.
1public void OnFire()
2{
3    if (fireMode != FIREMODE.FULL) return;
4
5    if (timeLastFired + fireDelay <= Time.time)
6    {
7        FireWeapon();
8    }
9}
10
11public void OnFireStart()
12{
13    if (fireMode != FIREMODE.SEMI || semiFired) return;
14
15    if (timeLastFired + fireDelay <= Time.time)
16    {
17        FireWeapon();
18        semiFired = true;
19    }
20}
21
22public void OnFireEnd()
23{
24    semiFired = false;
25}
26
  • fireDelay = 0.5
    https://velog.velcdn.com/images/lutca1320/post/88a4e73e-accf-4eaf-9979-ee965b9e202f/image.gif
  • fireDelay = 0.05
    https://velog.velcdn.com/images/lutca1320/post/86836ba1-d848-481a-ab23-e32bbcb9640e/image.gif

FireWeapon

1private void FireWeapon()
2{
3    Instantiate(muzzleFlash, muzzleFireStart);
4    // Muzzle Flash Effect 를 `muzzleFireStart` Transform 에 소환한다.
5
6    RaycastHit hit = CheckHit();
7    if (hit.collider) SpawnHitParticle(hit.point, hit.normal);
8    // Hit 체크를 하고, `hit.collider` 가 true면 HitParticle 을 소환한다.
9
10    StartCoroutine(SpawnTrail(hit));
11    // Bullet Trail 을 그린다. (Corutine 사용)
12
13    audioSource.Play(); // 총기 사운드를 플레이한다.
14    camController.ShakeCamera(0.7f, 0.07f); // 카메라 Shake Effect 를 적용한다.
15
16    timeLastFired = Time.time; // 딜레이 설정을 위해 필요
17}
18

SpawnHitParticle

1private void SpawnHitParticle(Vector3 hitPoint, Vector3 hitNormal)
2{
3    Instantiate(hitParticle, hitPoint, Quaternion.LookRotation(hitNormal));
4}
5
파티클은 유니티에서 제공하는 파티클 팩 중 Metal Hit 이펙트를 사용했다.
  • Spark Effect
  • Dust Effect
  • Decal

SpawnTrail

Bullet Trail 을 그린다. 프레임당 3f 씩 움직인다. while 문이 종료되면 Destroy 를 진행한다.
1private IEnumerator SpawnTrail(RaycastHit hit)
2{
3    GameObject trail = Instantiate(bulletTrail, muzzleFireStart.position, muzzleFireStart.rotation);
4
5    Vector3 hitPoint = hit.collider ? hit.point : muzzleFireStart.position + muzzleFireStart.right * 100;
6    Vector3 startPoint = muzzleFireStart.position;
7    float distance = Vector3.Distance(startPoint, hitPoint);
8    Vector3 direction = (hitPoint - startPoint).normalized;
9
10    while (Vector3.Distance(startPoint, trail.transform.position) < distance)
11    {
12        trail.transform.position += direction * 3f;
13        yield return null;
14    }
15
16    trail.transform.position = hitPoint;
17
18    Destroy(trail);
19}
20
BulletTrail Prefab 을 만들어 사용했고, Trail Renderer 를 이용했다.
https://velog.velcdn.com/images/lutca1320/post/a15f8308-0eeb-4568-a2ba-196ed937d820/image.png
이렇게 생겼다.
https://velog.velcdn.com/images/lutca1320/post/dd17604f-5658-44cb-9153-082edd750a52/image.gif

Camera Shake Effect

카메라 Shake Effect 는 다음 장에서 작성하겠다.

Full-Code

1public class GunController : MonoBehaviour
2{
3    public GameObject muzzleFlash;
4    public Transform muzzleFireStart;
5
6    private AudioSource audioSource;
7    public AudioClip audioClip;
8
9    public GameObject bulletTrail;
10    public ParticleSystem hitParticle;
11
12    private PlayerCameraController camController;
13
14    public FIREMODE fireMode = FIREMODE.FULL;
15    public float fireDelay = 0.1f;
16    private float timeLastFired = 0;
17    private bool semiFired = false;
18
19    void Start()
20    {
21        audioSource = GetComponentInChildren<AudioSource>();
22        audioSource.clip = audioClip;
23        camController = transform.root.GetComponent<PlayerCameraController>();
24    }
25
26    private RaycastHit CheckHit()
27    {
28        Physics.Raycast(muzzleFireStart.position, muzzleFireStart.right, out RaycastHit hit, 100);
29        return hit;
30    }
31
32    private IEnumerator SpawnTrail(RaycastHit hit)
33    {
34        GameObject trail = Instantiate(bulletTrail, muzzleFireStart.position, muzzleFireStart.rotation);
35
36        Vector3 hitPoint = hit.collider ? hit.point : muzzleFireStart.position + muzzleFireStart.right * 100;
37        Vector3 startPoint = muzzleFireStart.position;
38        float distance = Vector3.Distance(startPoint, hitPoint);
39        Vector3 direction = (hitPoint - startPoint).normalized;
40
41        while (Vector3.Distance(startPoint, trail.transform.position) < distance)
42        {
43            trail.transform.position += direction * 3f;
44            yield return null;
45        }
46
47        trail.transform.position = hitPoint;
48
49        Destroy(trail);
50    }
51
52    private void SpawnHitParticle(Vector3 hitPoint, Vector3 hitNormal)
53    {
54        Instantiate(hitParticle, hitPoint, Quaternion.LookRotation(hitNormal));
55    }
56
57    private void FireWeapon()
58    {
59        Instantiate(muzzleFlash, muzzleFireStart);
60
61        RaycastHit hit = CheckHit();
62        if (hit.collider) SpawnHitParticle(hit.point, hit.normal);
63
64        StartCoroutine(SpawnTrail(hit));
65
66        audioSource.Play();
67        camController.ShakeCamera(0.7f, 0.07f);
68
69        timeLastFired = Time.time;
70    }
71
72    public void OnFire()
73    {
74        if (fireMode != FIREMODE.FULL) return;
75
76        if (timeLastFired + fireDelay <= Time.time)
77        {
78            FireWeapon();
79        }
80    }
81
82    public void OnFireStart()
83    {
84        if (fireMode != FIREMODE.SEMI || semiFired) return;
85
86        if (timeLastFired + fireDelay <= Time.time)
87        {
88            FireWeapon();
89            semiFired = true;
90        }
91    }
92
93    public void OnFireEnd()
94    {
95        semiFired = false;
96    }
97}
98