다양한 기록

Parrying Sword #8 : [기획][프로그래밍] 와이어 액션 구현 본문

유니티 엔진/Parrying Sowrd

Parrying Sword #8 : [기획][프로그래밍] 와이어 액션 구현

라구넹 2023. 2. 17. 11:56

 

벽 잡기를 만들고 나서도 y축을 활용하는 액션이 부족하다는 생각이 들어

와이어 액션을 구현하였습니다

마법을 쓴다는 이미지로 구상을 했고, 그 결과 위 이미지처럼 제작하게 되었습니다.

 

// 와이어 액션 **************************************
        if ( Input.GetKey(KeyCode.S) && playerCtrl.actionActive && wire != 0 )
        {
            prevWire = wire;
            wire = playerCtrl.ActionWireJump();

            if( prevWire != -1 && wire == 0 || (prevWire == 1 && wire == -1) )
            {
                playerCtrl.ActionWireInertia();
            }
        }

        if( Input.GetKeyUp(KeyCode.S) )
        {
            if( wire > 0 )
            {
                wire = -1;
                playerCtrl.ActionWireInertia();
            }
            else if( wire == 0 )
            {
                wire = -1;
            }
        }
        
        if (wire > 0) return;
        // ************************************************

PlayerMain 클래스에서의 와이어 액션 코드입니다.

여기서 wire 변수가 1이면 잡기 성공, 0이면 너무 가까워서 실패, -1이면 잡을 벽이 없음 판정입니다.

 

잡을 벽이 가까이에 없으면 아예 실패하고

중간에 키 입력을 놓거나, 너무 가까워지면 와이어가 끊기도록 설정합니다.

 

가능하면 리턴을 bool 변수로 다루고 싶었으나, 각각의 상황을 전부 제대로 다루어 놓지 않으면 의도대로 작동하지 않습니다.

다른 클래스와 너무 깊게 엮이는 것 같아 그렇게 마음에 들지는 않지만, 그래도 버그가 나는 대로 내버려 둘 수는 없습니다.

 

    // 와이어액션
    public int ActionWireJump()
    {
        int success = wireAction.ActionWireJump();

        if( success > 0 )
        {
            usingWire = true;
            animator.SetTrigger("WireAction");
        }

        return success;
    }

    public void ActionWireInertia()
    {
        usingWire = false;
        wireAction.inactiveWire();
        rb.velocity = new Vector2(rb.velocity.x * 1.3f, 40.0f);
    }

위에서 언급된 wire 변수의 값을 위 함수에서 제공합니다.

WireAction 클래스에서 값을 받아오고, 애니메이션 상태를 변경 해 줍니다.

 

중요한 것은, 와이어가 끊어졌을 때 반동으로 가해지는 힘을 억지로 바꿔주어야 합니다.

그렇지 않으면 와이어액션 중 가해지는 힘에 의해 저 멀리 날아가는 문제가 생깁니다.

그렇다고 아예 없으면 안되니 velocity를 바꾸어 반동을 조절합니다.

 

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

public class WireAction : MonoBehaviour
{
    private PlayerController playerCtrl;
    private GameObject player;
    private Transform wire;
    private Transform wireHook;
    private SpriteRenderer[] sprites;
    private Transform wirePosition;
    private float distance = 0;
    

    private void Awake()
    {
        player = GameObject.Find("Player");
        playerCtrl = player.GetComponent<PlayerController>();
        sprites = GetComponentsInChildren<SpriteRenderer>();
        wire = transform.Find("Wire");
        wireHook = transform.Find("WireHook");
        wirePosition = player.transform.Find("WirePosition");
    }

    // 템플릿 메소드 *********************************************************
    // -1 탐색 실패, 0 너무 가까움 1 가능
    public int ActionWireJump()
    {
        moveAtPlayer();
        int check = checkDistance();
        if (check < 1) return check;
        modifyTransform();
        activeWire();
        addForce();

        return check;
    }
    // ********************************************************************


    private void moveAtPlayer()
    {
        transform.position = wirePosition.position;
    }

    // -1 탐색 실패, 0 너무 가까움 1 가능
    private int checkDistance()
    {
        Collider2D[] colliders;

        for( int i = 0; i < 2; i++ )
        {
            colliders = Physics2D.OverlapPointAll(new Vector2(transform.position.x + i * playerCtrl.dir,
                                                              transform.position.y + i));
            // Debug.Log(i);
            foreach (Collider2D collider in colliders)
            {
                if (collider.tag == "Road")
                {
                    return 0;
                }
            }
        }

        
        for( int i = 1; i < 26; i++ )
        {
            colliders = Physics2D.OverlapPointAll(new Vector2(transform.position.x + i * playerCtrl.dir,
                                                              transform.position.y + i));
            // Debug.Log(i);
            foreach( Collider2D collider in colliders )
            {
                if( collider.tag == "Road" )
                {
                    distance = i;
                    return 1;
                }
            }
        }

        return -1;
    }

    private void modifyTransform()
    {
        wire.localScale = new Vector3(distance * 1.4f, wire.localScale.y,
                                                    wire.localScale.z);

        transform.localScale = new Vector3( Mathf.Abs( transform.localScale.x ),
                                            transform.localScale.y,
                                            transform.localScale.z);

        wireHook.transform.position = new Vector3(transform.position.x + ( distance + 1 ) * playerCtrl.dir,
                                                transform.position.y + distance,
                                                transform.position.z);
    }

    private void activeWire()
    {
        foreach( SpriteRenderer sprite in sprites )
        {
            sprite.enabled = true;
        }
    }

    private void addForce()
    {
        player.GetComponent<Rigidbody2D>().velocity = new Vector2(70.0f * playerCtrl.dir, 60.0f);
    }

    // 스프라이트 끄기
    public void inactiveWire()
    {
        foreach (SpriteRenderer sprite in sprites)
        {
            sprite.enabled = false;
        }
    }
}

WireAction 클래스입니다.

순서가 중요한 관계로 템플릿 메소드 패턴을 적용하였습니다.

 

시작 지점을 기준으로 x, y축을 대각선으로 1만큼 이동하며 잡을 벽이 있나를 체크하고, 있으면 벽을 잡고 쭉 진행합니다.

 

추가로 와이어를 끊기 위해 스프라이트를 꺼주는 함수를 하나 더 만들어 줍니다.