ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 2D 슈팅게임 03. 점프 및 공격 모션
    유니티 프로젝트/2D.SpaceShooter

    오늘은 Player의 점프 및 Attack 모션을 진행해 보겠습니다.

     

    이동 구현과 마찬가지로 Rigidbody를 이용해서 점프를 시각적으로 보여줄 예정입니다.

    백문 불여일견. 바로 진행하겠습니다.

     

    Jump 스크립트 입니다.

    Rigdbody는 rb란 변수로 선언해 담아 왔고

    Animator는 ani란 변수로 선언해 담아왔습니다.

     

    jump를 하는데 필요한 jumpSpeed (점프 스피드)와 

    2단 이상의 중복 점프를 막기 위해 점프 중인지 체

    크할 불 변수 isJump를 선언하였습니다.

    점프 구현을 생각하니 초기 점프 구현 부분에 상당히 애먹었던 적이 있습니다.

    다행히 CollisionFlags 변수의 Below 이용해 if(cc.collisionFlags == CollisionFlags.Below)의 조건으로 이를 해결하였는데

     

    문제를 해결하면서 알게 된 지식은

    //

    Below는 캐릭터 컨트롤러의 충돌 영역 중 아래쪽 부분에 충돌했을 때 true의 값이 반환되며 Above와 Side는 각 위쪽과 옆쪽을 충돌했을대 true가 된다는 점이었습니다.

    //

     

    Jump 버튼을 클릭했을 때 일어날 부분으로 다시 돌아가서

     

    JumpBtn함수에! isJump의 조건문을 걸어 실행하게 했습니다.

     

    SounSE.instance.PlaySound(SounSE.instance.jump)는 점프 사운드를 내는 부분이고

     

    isJump를 다시 true로 바꿔주어 중복 현상을 방지하는 부분입니다.

     

    rb.AddForce(Vector2.up * jumpSpeed, ForceMode2D.Impulse) 역동 적인 동작을 연출하기 위하여

    AddForce를 이용한 ForceMode2D.Impulse를 사용한 모습입니다.

    Rigidbody가 2D 이기에 모드도 2D로 해줘야 합니다.

     

    마지막으로 ani.Play 함수를 이용해 Jump를 실행시켜 주었습니다.

     

    OnCollisionEnter2 D 함수에서는

    닿고 있는 collision의 태그가 Ground 일시

    isJump를 펄스로 바꿔주어

    Player가 Ground위에 있을 시 에만 점프가 되도록 하였습니다.

     

     

    그리고 이 함수를 게임상에 존재하는 jump버튼의 OnClick 이벤트에서 클릭 시 실행되게 했습니다.

     

    잘 해결된 줄 알았는데, 지난번 만들 때도 까먹더니 이번에도 깜빡했습니다..

    벽에 부딪히면 내려오지 않는 상태를 유지하고 있는 오류인데

    하지만 해결법을 알고 있습니다.

     

    많은 방법이 있겠지만, 저는 Physics Material 2D를 사용해 해결하려고 합니다.

    Friction : 마찰력

    Bounciness : 바운스(통통 볼)

     

    양 벽의 BoxCollider에  Friction(마찰력)을 0으로 한 Wall을 입혀 줍니다.

    마찰력인 0의 벽에 부딪히기에 점프 시 마찰이 안되게 하여 해결하였습니다.


    Player Attack구현

     

    우선 Attack Animation에 쓸 Sprite와 Attack 버튼을 누를 시 날아가는 공격 이미를 무료 다운로드해왔습니다.

    이 공격 물체에 BoxCollider를 사용해도 되지만

    조금이나마 더 속도가 빠르다는 capsule Collider 2D를 사용하였습니다.

     

    Attack을 누를 시 물체가 날라가는 스크립트입니다.

    atkSpeed인 물체 스피드를 Public으로 선언해 주어 인스펙터 창에서 조절이 가능하게 하였고

    lifeTime을 주어 소멸될 시간을 정해 주었습니다.

     

    AtkMove의 코 루틴 함수입니다.

    우선 transform의 TransLate를 향해 이동을 하며 위치는 vector2.Up방향 * 스피드 * Time.deltaTime으로 거리를 일정케 해 주었습니다.

     

    lifeTime += Time.deltaTime 으로 1초씩 더해 주며, 아래 조건문인 if(lifeTime > 2) 일시 

    1.AllStopCorutine과 다르게 일정 코 루틴 함수만 Stop 시켜주는 StopCorutine를 사용하여 "AtkMove"를 멈춰주고

    2.gameObject (공격 물체)를 비활성화시켜주빈다

    3.transfrom (공격 물체)의 포지션을 0,0으로 초기화시켜줬습니다.


    이번 예와는 맞지 않지만 갑자기 생각난 건데 transfrom과 gameObject 중 뭘 사용해야 되는지 몰라서 막막했던 적이 

    있습니다. "어렵게 생각하지 말고 쓰고 싶은 거 쓰면 안 됨?"이라는 친구의 말을 듣고 당혹과 충격이었지만.. 딱히 반박할 말이 생각나지 않아서 넘어갔던 적이 있는데,, 그게 어떤 상황이었는지 기억이 안 납니다...ㅎㅎ..


    마지막으로 AtkMove 코 루틴 함수를 매번 활성화 시 실행되는 OnEnable함수에서 실행하여

    Atk(공격 물체) 가 활성화 되면 코루틴 함수 AtkMove가 실행되게 하였습니다.

    OnEnable함수 와 Disable 함수를 적극 활용하여 다양하게 연출을 해봐야겠습니다.

    이렇게 만든 Atk(공격물체) 들을 미리 생성해 두어 오브젝트 풀 방식을 사용했습니다.

     

    오브젝트 풀 방식은 여러 가지가 있지만, 게임 오브젝트를 새로 생성과 파괴를 하는 것은 순간적으로 큰 성능 소모 및 프레임 저하를 일으킬 수 있다고 합니다.

    생성 파괴를 하는 것보단 활성/비활성을 하는 것으로 프레임 저하를 방지할 수 있다고 합니다.

     

    오브젝트 풀링을 사용한 스크립트의 전체 코드입니다.

    public GameObject [] atk을 사용하여 Atk(공격 물체) 들을 받아와 주었고

    중첩 발사를 막기 위해 불 변서 isAtk를 선언해 주었습니다.

     

    AttackBtn 함수에서

    중복 공격을 막기 위해 isAtk를 true로 즉시 바꿔주고

    Invoke 함수를 사용해 0.8초 후 isAtk를 false로 바꿔줍니다.

    (즉 공격 대기 시간은 0.8초가 됩니다.)

    ani.Play 함수를 사용해 "Atk" 모션을 취하게 하였고

     

    첫 번째 조건문인 isAtk == false 일 시 (즉 공격 중이 아닐 시 에만 실행)

    for문을 통해 0 ~ atk.Length -1까지 검사하여 모든 오브젝트들을 검사합니다.

    검사를 해서 if (! atk [i]. activeSelf)로 비활성화된 오브젝트가 있다면

    atk [i]. transform.position = new Vector2(transform.position.x, transform.position.y + 1)

    그 i번째 atk(공격 물체)의 위치를 player의 x축 / player의 y축 +1로 설정하여 
    atk [i]. SetActive(true);

    활성화를 해주고

    리턴으로 함수를 나가는 함수입니다.

    플레이어의 이동 / 점프 / 공격 모션을 마치며 

     

    추가적으로 타이틀 창 ->인게임 창에 서 Timer가 존재하는데 현재는 간단한 타이머지만

    타이머가 0 이하가 되면 게임을 종료되게 할 예정입니다.

    직관적인 변수 min : sec : msec를 선언했으며. time -= Time.deltaTime을 이용해 시간을 빼 주는 동작입니다.

    Onenable에서 실행 시 time = 180으로 선언되며 -=Time.deltaTime으로 인해

    179.9048이 됐을 시 int형인 179와 뺀 뒤 * 100을 해 주기에 msec = 9048 이 되며 

    sec는 60의 나머지들 즉 sec = sec < 60 이 됩니다.

    min은 60으로 나눴을 시 떨어지는 몫이기에 초기에 3으로 시작합니다. (180 / 60 = 3)

     

    마지막으로 timerText.text = $"{min:00} : {sec:00} : {msec:00}"를 이용해 2자리씩 지정을 해 주었습니다.

     

    여기서 $ 는 string.Format()과 기능은 동일하지만 익숙해지면 더 읽기 쉽고 편리하게 출력 서신을 정할 수 있기에,

    손에 익히고 새로운 것을 받아들이기 위해 보관법을 사용하고 있습니다.

     

    timerText.text = string.Format("{0:00}:{1:00}:{2:00}", min, sec, msec)로 사용하여도 기능은 동일합니다.

     

     

     

    댓글

김효겸 / Tel. 010-7735-0580 / E-mail. dollzzang2@hanmail.net