본문 바로가기
디지털노마드

[챗GPT게임개발20] ChatGPT를 활용하여 공격 애니메이션 추가하기

by 인텔리노마드라이프 2024. 5. 10.
728x90

안녕하세요. 인텔리원스튜디오(IntelliOneStudio)입니다.

오늘은 ChatGPT를 활용하여 Unity 플랫포머 게임에서 공격 애니메이션을 추가 해볼께요.

[오늘의 목표]
1. 공격 애니메이션 만들기
2. PlayerController 스크립트에 공격 애니메이션 추가하기  
3. 실행 결과 살펴 보기 

 

 ChatGPT 답변 내용 중 제가 별도로 표시한 파란색 내용을 기준으로 읽으시면 좀 더 이해가 쉽게 될거예요.

공격 애니메이션 만들기

먼저 공격 애니메이션을 추가해볼께요.

1. 플레이어를 선택하고 애니메이션 메뉴를 클릭해요 
2. PlayerPunch 애니메이션 클립을 생성해요. 
3. 이제 필요한 스크라이트를 애니메이션으로 이동해요. 

 

애니메이션이 잘 동작하는 지 확인해요.

[공격 애니메이션 결과]

 

PlayerController 스크립트에 공격 애니메이션 추가하기

이제 공격 애니메이션 스크립트를 기존 PlayerController 에 추가해볼께요.

[질문] 안녕 ChatGPT, 플레이어가 공격 하는 애니메이션을 추가로 적용하고 싶어. 
다음의 코드를 학습하고 비슷한 방식으로 공격 동작을 추가해줘. 단 기존 소스의 로직은 그대로 유지가 되어야해
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Platformer.Gameplay;
using static Platformer.Core.Simulation;
using Platformer.Model;
using Platformer.Core;

namespace Platformer.Mechanics
{
    public class PlayerController : KinematicObject
    {
        public AudioClip jumpAudio;
        public AudioClip respawnAudio;
        public AudioClip ouchAudio;

        public float maxSpeed = 7;
        public float jumpTakeOffSpeed = 7;

        // 벽 타기 관련 변수 추가
        public LayerMask wallLayer;
        public float wallSlideSpeed = 1.5f;
        public float wallJumpXSpeed = 5f;
        public float wallJumpYSpeed = 7f;

        private bool isTouchingWall;
        private bool wallSliding;
        private bool wallJumping;

        public JumpState jumpState = JumpState.Grounded;
        private bool stopJump;
        public Collider2D collider2d;
        public AudioSource audioSource;
        public Health health;
        public bool controlEnabled = true;

        bool jump;
        Vector2 move;
        SpriteRenderer spriteRenderer;
        internal Animator animator;
        readonly PlatformerModel model = Simulation.GetModel<PlatformerModel>();

        public Bounds Bounds => collider2d.bounds;

        void Awake()
        {
            health = GetComponent<Health>();
            audioSource = GetComponent<AudioSource>();
            collider2d = GetComponent<Collider2D>();
            spriteRenderer = GetComponent<SpriteRenderer>();
            animator = GetComponent<Animator>();
        }

        protected override void Update()
        {
            if (controlEnabled)
            {
                move.x = Input.GetAxis("Horizontal");
                if ((jumpState == JumpState.Grounded || wallSliding) && Input.GetButtonDown("Jump"))
                {
                    jumpState = JumpState.PrepareToJump;
                    Debug.Log("Jump State: PrepareToJump");
                }
                else if (Input.GetButtonUp("Jump"))
                {
                    stopJump = true;
                    Schedule<PlayerStopJump>().player = this;
                    Debug.Log("Jump Button Released");
                }
            }
            else
            {
                move.x = 0;
            }

            UpdateWallSlide();
            UpdateJumpState();
            base.Update();
        }

        void UpdateWallSlide()
        {
            // 벽 감지 (OverlapCircle 또는 BoxCast 사용)
            isTouchingWall = Physics2D.OverlapCircle(collider2d.bounds.center, collider2d.bounds.extents.x + 0.1f, wallLayer);

            // 벽 슬라이딩 상태 확인
            if (isTouchingWall && !IsGrounded && move.x != 0)
            {
                wallSliding = true;
                velocity.y = Mathf.Clamp(velocity.y, -wallSlideSpeed, float.MaxValue);
                Debug.Log("Wall Sliding");
            }
            else
            {
                wallSliding = false;
            }
        }

        void UpdateJumpState()
        {
            jump = false;
            switch (jumpState)
            {
                case JumpState.PrepareToJump:
                    if (wallSliding)
                    {
                        jumpState = JumpState.WallJumping;
                        wallJumping = true;
                        jump = true;
                        stopJump = false;
                        Debug.Log("Jump State: WallJumping");
                    }
                    else
                    {
                        jumpState = JumpState.Jumping;
                        jump = true;
                        stopJump = false;
                        Debug.Log("Jump State: Jumping");
                    }
                    break;
                case JumpState.Jumping:
                    if (!IsGrounded)
                    {
                        Schedule<PlayerJumped>().player = this;
                        jumpState = JumpState.InFlight;
                        Debug.Log("Jump State: InFlight");
                    }
                    break;
                case JumpState.InFlight:
                    if (IsGrounded)
                    {
                        Schedule<PlayerLanded>().player = this;
                        jumpState = JumpState.Landed;
                        Debug.Log("Jump State: Landed");
                    }
                    break;
                case JumpState.WallJumping:
                    if (!wallJumping)
                    {
                        jumpState = JumpState.InFlight;
                        Debug.Log("Jump State: InFlight (from WallJumping)");
                    }
                    break;
                case JumpState.Landed:
                    jumpState = JumpState.Grounded;
                    Debug.Log("Jump State: Grounded");
                    break;
            }
        }

        protected override void ComputeVelocity()
        {
            if (jump)
            {
                if (wallJumping)
                {
                    // 벽 점프 로직
                    velocity.x = wallJumpXSpeed * (spriteRenderer.flipX ? 1 : -1);
                    velocity.y = wallJumpYSpeed;
                    wallJumping = false;
                    Debug.Log("Wall Jump Performed");
                }
                else if (IsGrounded)
                {
                    velocity.y = jumpTakeOffSpeed * model.jumpModifier;
                    Debug.Log("Regular Jump Performed");
                }
                jump = false;
            }
            else if (stopJump)
            {
                stopJump = false;
                if (velocity.y > 0)
                {
                    velocity.y = velocity.y * model.jumpDeceleration;
                    Debug.Log("Jump Stopped");
                }
            }

            if (move.x > 0.01f)
                spriteRenderer.flipX = false;
            else if (move.x < -0.01f)
                spriteRenderer.flipX = true;

            animator.SetBool("grounded", IsGrounded);
            animator.SetFloat("velocityX", Mathf.Abs(velocity.x) / maxSpeed);

            targetVelocity = move * maxSpeed;
        }

        // 벽 점프 상태를 추가
        public enum JumpState
        {
            Grounded,
            PrepareToJump,
            Jumping,
            InFlight,
            WallJumping,
            Landed
        }
    }
}
 

[답변]

기존의 PlayerController 클래스에 공격 상태를 독립적으로 관리하는 방식으로 코드를 수정하여 플레이어가 점프 중에도 공격할 수 있도록 할 것입니다. 이를 위해 별도의 공격 상태 관리 시스템인 AttackState를 추가하고, 기존 로직은 그대로 유지하면서 공격 관련 로직을 통합하겠습니다.

AttackState 정의

공격 상태를 정의하기 위한 새로운 열거형을 추가합니다:

public enum AttackState
{
    Idle,
    PreparingAttack,
    Attacking
}

 

PlayerController 클래스 수정

기존 클래스에 공격 상태 관리를 위한 필드와 메소드를 추가합니다. 이는 점프 상태와 별개로 동작합니다:

public AttackState attackState = AttackState.Idle;

protected override void Update()
{
    if (controlEnabled)
    {
        move.x = Input.GetAxis("Horizontal");
        
        // Jump input
        if ((jumpState == JumpState.Grounded || wallSliding) && Input.GetButtonDown("Jump"))
        {
            jumpState = JumpState.PrepareToJump;
        }
        else if (Input.GetButtonUp("Jump"))
        {
            stopJump = true;
            Schedule<PlayerStopJump>().player = this;
        }

        // Attack input
        if (Input.GetButtonDown("Fire1") && attackState == AttackState.Idle)
        {
            attackState = AttackState.PreparingAttack;
        }

        UpdateWallSlide();
        UpdateJumpState();
        UpdateAttackState();
    }

    base.Update();
}

void UpdateAttackState()
{
    switch (attackState)
    {
        case AttackState.PreparingAttack:
            animator.SetTrigger("attack");
            attackState = AttackState.Attacking;
            break;
        case AttackState.Attacking:
            if (animator.GetCurrentAnimatorStateInfo(0).IsName("PlayerPunch") && animator.GetCurrentAnimatorStateInfo(0).normalizedTime >= 1.0)
            {
                attackState = AttackState.Idle;
            }
            break;
    }
}

 

ComputeVelocity 수정

공격 중에는 플레이어의 이동을 제한할 수 있습니다:

protected override void ComputeVelocity()
{
    if (jump)
    {
        if (wallJumping)
        {
            velocity.x = wallJumpXSpeed * (spriteRenderer.flipX ? 1 : -1);
            velocity.y = wallJumpYSpeed;
            wallJumping = false;
        }
        else if (IsGrounded)
        {
            velocity.y = jumpTakeOffSpeed * model.jumpModifier;
        }
        jump = false;
    }
    else if (stopJump)
    {
        if (velocity.y > 0)
        {
            velocity.y = velocity.y * model.jumpDeceleration;
        }
        stopJump = false;
    }

    if (attackState == AttackState.Attacking)
    {
        move.x = 0; // 공격 중 이동 제한
    }

    if (move.x > 0.01f)
        spriteRenderer.flipX = false;
    else if (move.x < -0.01f)
        spriteRenderer.flipX = true;

    animator.SetBool("grounded", IsGrounded);
    animator.SetFloat("velocityX", Mathf.Abs(velocity.x) / maxSpeed);

    targetVelocity = move * maxSpeed;
}


이와 같은 방식으로
PlayerController 클래스를 수정하면, 플레이어는 점프 중에도 공격할 수 있으며, 기존 코드의 기능에 영향을 주지 않으면서 공격 기능을 통합할 수 있습니다. 공격과 점프를 독립적으로 관리함으로써 더 다양하고 동적인 게임 플레이를 구현할 수 있습니다.

 

기본적인 스크립트는 완료되었어요. 

애니메이터를 수정해요 : animator.SetTrigger("attack")

공격 애니메이션이 정확히 실행될 수 있도록 애니메이터를 추가 및 수정해요.

플레이어를 선택하고 애니메이터 메뉴를 오픈해요.  방법은 아래 동영상과 같이 하면 되요.

 

플레이 모드에서 결과를 확인해요.

스크립트, 애니메이션 및 애니메이터 적용 사항을 저장하고 플레이 모드에서 결과를 확인해요.

 

원하는 결과가 나왔어요. 

챗GPT와 개발은 언제나 쉽고 재미있는 것 같아요.

 

오늘은 ChatGPT를 활용하여 Unity 플랫포머 게임에서 공격 애니메이션을 추가 해봤어요.

다음 시간에는 공격 애니메이션을 적을 공격하는 기능을 추가해볼께요.

 

 

 

지금까지,

언제나 성장하는 인텔리원스튜디오(IntelliOneStudio)입니다.

감사합니다.

728x90