블렌더에선 잘보이는데 유니티에서 fbx로 불러오면 다 깨짐

 

그냥 그대로 fbx 추출하면 엉망됨

 

블렌더에서, flip normal 하고 난 후 모든 마테리알을 surface - backface culling 체크 세개 on 해줘야 함

 

커스텀 노드가 적용된 마테리알은 유니티 상에서 그대로 불러올 시 적용 안 됨

따로 추가작업 해줘야 함

 

 

 

이렇게 눈 만 따로 텍스쳐 있을 때,

 

유니티 안에서 구현하는 법 기초

 

단, 단순 2D 표현이라 명암 반영 안됨.

그건 다른 강의 따로 들어야 함

 

 

 

얼굴 될 텍스쳐의 png 파일은 유니티에서 불러온 후

Alpha is Transparency 체크 온

 

 

 

 

새로운 셰이더 그래프 만들기

Create - Shader Graph - URP - Unit Shader Graph

 

 

open shader editor 

 

왼위 + 눌러서 

texture 2d 를 두 개 만들어줌

 

하나는 기본 전체 그림인 Base, 

하나는 얼굴을 표현한 Layer 1

 

 

 

두 개를 바탕에 드래그

 

 

 

Create Node - Sample Texture 2D 만듦

 

 

Base, 

Layer 1 

각각 하나씩 Sample Texture 2D 를 이어줌

 

 

 

Create Node - Blend 도 만들어서 연결해줌

 

Layer1 표정의 Alpha 는 Opacity 에 연결

 

 

블렌드 타입은 오버라이트

 

 

Fragment base color 에 마지막 줄을 연결

 

 

 

이게 뼈대 완성임

 

 

 

끝 지점에 replace color, contrast, hue, channel mixer 등의 양념칠 가능

 

좀 귀찮긴 하지만

변경할 때마다 저장을 눌러줘야 결과값 볼 수 있음

 

 

마테리엘에 만든 셰이더 그래프 할당

 

 

오브젝트 우측 인스펙터 창 , shader graph 펼쳐보기 누르면 베이스 맵, 그 위에 덮어 쓸 layer1 (표정 될 png 텍스쳐) 지정 가능

 

 

아래 오브젝트 - materials - remap 설정칸에 해당 셰이더 그래프를 할당 

 

+

 

smooth shading 하는 법

 

 

어셋 폴더에서 오브젝트 클릭 - 오위 상단 model - 아래 normals calculate -

smooth angle 30도~ 60도~ 90도~ 180 도 원하는 만큼 지정

낮은 값일수록 딱딱해져보임

 

 

반응형
Posted by 이름이 익명
:

 

https://www.youtube.com/watch?v=FBY_cmtCNHw&ab_channel=%EA%B3%A8%EB%93%9C%EB%A9%94%ED%83%88

 

 

적 프리팹을 맵에 놓기

 

 

적(부모 최상단하이어라키)에 Rigidbody, Box Collider, Enemy스크립트 적용

 

프리즈 로테이션 x , z 할당

박스 콜라이더는 메쉬에 비슷하게 조정

에너미 스크립트의 헬스 값은 둘다 50정도로

 

 

Tag 와 Layer 를 Enemy 로 설정

레이어 설정시 확인 메시지 뜨면 맨왼쪽 Yes Children 설정

 

Enemy 태그 없으면 만들기

 

 

 

 

 

Enemy 스크립트를 비주얼 스튜디오에서 킨다

 

 

적 프리팹은

하이어라키 창 자식의 자식 속에 마테리알이 있기 때문에

 

    mat = GetComponent<MeshRenderer>().material;

이게 아니라

 

    mat = GetComponentInChildren<MeshRenderer>().material;

이렇게 되어야 함

 

 

 

 저번에 만든 테스트 에너미는 맵 상에서 삭제

 

 

+

 

a i  만들기

 

 

컴포넌트에 Nav Mesh Agent 추

 

 

Enemy 스크립트 맨 위

 

using UnityEngine; 

밑에

using UnityEngine.AI;

한줄 추가해야함

 

 

    void Update()
    {
        nav.SetDestination(target.position);
    }

실시간으로 타겟의 위치 를 목표로 삼는다는 뜻

 

 

 

Enemy  스크립트에 타겟 지정을 Player로 할당

 

(  public Transform target; 을 만들어서 생긴 변수 칸임 )

 

 

상단 window - AI - Navigation 창

 

 

!!! 막힘

 

여기까지 강의가 6:45

 

 

강의 에선 Bake 버튼이 있지만, 최신버전에선 Bake 버전이 없음

 

아마 floor 바닥 오브젝트에 NavMeshSurface  컴포넌트를 추가해줘야 하는 듯함

 

 

바닥 오브젝트 선택 후 - NavMeshSurface 컴포추 - 아래 Bake 클릭후 청록색 필드 펼쳐지면 성공

 

 

 

Mesh Renderer 가 아니라

텍스쳐를 입힌 Skinned Mesh Renderer 면

오른쪽 스크립트에서도 SkinnedMeshRenderer  라고 써줘야함

 

 

한 오브젝트의 마테리알이 여러개면

mat = 어쩌구

가 아니라

mats = 어쩌구 가 되어야 함

 

 

그리고 맵 상에 TestEnemy 없어야함 얘네들은 마테리알 없어서 마테리알null 오류 남

그래서 지워버림

 

 

자세한건 챗봇이 써줌

 void Awake()
 {
     rigid = GetComponent<Rigidbody>();
     boxCollider = GetComponent<BoxCollider>();

     SkinnedMeshRenderer skinnedMeshRenderer = GetComponentInChildren<SkinnedMeshRenderer>();
     Material[] mats = skinnedMeshRenderer.materials;
     mat = mats[0]; // 첫 번째 Material 할당

     nav = GetComponent<NavMeshAgent>();
     anim = GetComponentInChildren<Animator>();

     Invoke("ChaseStart", 2); // 2초 뒤 ChaseStart 호출
 }

 

위 스크립트는

마테리알 어쩌구 오류났던 거 수정한 부분임

보이드 어웨이크 부문 일부임

 

+

 

 

저번에 만든 벽을 복붙해서 기둥을 만들어보자

 

 

지형이 바뀌었으면 Bake 다시 누르기

 

+

 

        rigid.velocity = Vector3.zero;

  rigid.velocity 하면 취소선 오류남

        rigid.linearVelocity = Vector3.zero;

rigid.linearVelocity 로 변형해야 함

 

 

+

 

애니메이션 지정하기

 

 

create - animation -  animator controller 새로 만들기

 

이름은 AC_Enemy_A

 

 

 

root 뼈 위 오브젝트에게 AC_Enemy_A 컴포 할당 ?

 

 

애니메이션 컨트롤러 두번 클릭해서

Animator 창 들어감

 

 

 

아래 프리팹 을 열어보면 4 개의 애니메이션이 속해있는 걸 볼 수 있

Attak , Die , Idle , Walk

 

 

4개의 애니메이션을 애니메이터 창으로 다 드래그 해 오기

 

idle (사진에선 a pose) 를 우클 - default state 로 만들기

 

 

idle 과 walk 는 우클 make transition 으로 연결

 

 

어택은 워크와 쌍방향 연결

 

 

Any State 에서 Die 로 트랜지션 일방향 연결

 

 

좌상단 파라메터 세 개 생성

bool - isWalk

bool - isAttack

Trigger - doDie

 

 

Idle, Walk, Attack 의 Has Exit Time 은 다 체크 해제

 

 

die 의 컨디션은 doDie

 

 

 

 

워크 어택 트루 팔스는 위처럼 설정

 

 

+

 

 

    if (curHealth > 0)
    { mat.color = Color.white; }
    else
    { 
        mat.color = Color.gray;
        gameObject.layer = 12;
        isChase = false;
        nav.enabled = false;
        anim.SetTrigger("doDie");
    }

 

체력이 0 이 되면

회색빛이 되고

레이어태그가 인덱스넘버12인 EnemyDead가 되고

플레이어 쫓는걸 그만두고

자동이동 ai 를 끄고

죽는 모션을 재생한다는 뜻 

 

 

 

 

+

 

지금껏 만든 Enemy 스크립트 내용 : 

 

 

using System;
using System.Collections;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.Animations;
using UnityEngine.UIElements;

public class Enemy : MonoBehaviour
{

    public int maxHealth;
    public int curHealth;
    public Transform target;
    public bool isChase;

    Material mat;
    Rigidbody rigid;
    BoxCollider boxCollider;
    NavMeshAgent nav;
    Animator anim;


    void Awake()
    {
        rigid = GetComponent<Rigidbody>();
        boxCollider = GetComponent<BoxCollider>();

        SkinnedMeshRenderer skinnedMeshRenderer = GetComponentInChildren<SkinnedMeshRenderer>();
        Material[] mats = skinnedMeshRenderer.materials;
        mat = mats[0]; // 첫 번째 Material 할당

        nav = GetComponent<NavMeshAgent>();
        anim = GetComponentInChildren<Animator>();

        Invoke("ChaseStart", 2); // 2초 뒤 ChaseStart 호출
    }


    void ChaseStart()
{
        isChase = true;
        anim.SetBool("isWalk", true);
    }

    void Update()
    {
        if (isChase && nav.isOnNavMesh)
        {
            nav.SetDestination(target.position);
        }
    }


    void FreezeVelocity()
    {
        if (isChase)
        { 
        rigid.linearVelocity = Vector3.zero;
        rigid.angularVelocity = Vector3.zero;
        }  
}


    void FixedUpdate()
    {
        FreezeVelocity();
    }

    void OnTriggerEnter(Collider other)
    {
        if(other.tag == "Melee")
        {
            Weapon weapon = other.GetComponent<Weapon>();
            curHealth -= weapon.damage;
           Vector3 reactVec = transform.position
           - other.transform.position;
            StartCoroutine(OnDamage(reactVec, false));
        }
        else if (other.tag == "Bullet")
            {
            Bullet bullet = other.GetComponent<Bullet>();
            curHealth -= bullet.damage;
           Vector3 reactVec = transform.position
           - other.transform.position;
             Destroy(other.gameObject);
            StartCoroutine(OnDamage(reactVec, false));
        }
    }

    public void HitByGrenade(Vector3 explosionPos)
    {
    curHealth -= 100;
    Vector3 reactVec = transform.position - explosionPos;
    StartCoroutine (OnDamage(reactVec, true));
    }

    IEnumerator OnDamage(Vector3 reactVec,
    bool isGrenade)
    {
        mat.color = Color.red;
        yield return new WaitForSeconds(0.1f);

        if (curHealth > 0)
        { mat.color = Color.white; }
        else
        { 
            mat.color = Color.gray;
            gameObject.layer = 12;
            isChase = false;
            nav.enabled = false;
            anim.SetTrigger("doDie");

            if (isGrenade)
            {
                reactVec = reactVec.normalized;
                reactVec += Vector3.up * 2.2f ;

                rigid.freezeRotation = false;
                rigid.AddForce(reactVec * 4, ForceMode.Impulse);
                rigid.AddTorque(reactVec * 10, ForceMode.Impulse);

            }
            else 
            {
                reactVec = reactVec.normalized;
                reactVec += Vector3.up;
                rigid.AddForce(reactVec * 5, ForceMode.Impulse);
            }


            Destroy(gameObject, 3);
        }

    }


}

 

 

+

 

 

애니메이터 창에서 만약

motion 을 다른 캐릭터 애니메이션을 써 보면?


기본 idle 애니 사용시 다른 캐릭의 idle 애니 사용해봄
  뼈 구조가 같은 오토리그 프로라
모션 공유는 가능

 

 

 

반응형
Posted by 이름이 익명
:

 

https://www.youtube.com/watch?v=FyJYWRIq0Ss

 

 

 

이번 강의의 플레이어의 마지막 액션 될 수류탄 던지기를 구현해보려 함

수류탄은 필살기 느낌임

 

 

 

아래 에셋에서 수류탄 프리팹을 하이어라키 창으로 드래그해서

수류탄 하나 생성

 

이름은 Throw Grenade

 

미리 준비된 파티클 프리팹을 Throw Grenade 아래 자식오브젝트로 등록

 

(없음 파티클 새로 만들어야 함)

 

수류탄이 터지는 파티클임

loop 에 체크 해제된 파티클임

 

 

파티클에 꼬리를 넣고싶으면 trails 킨 후 

아래 렌더러에 trail material 수정

 

 

리지드 바디와 스피어 콜라이더 추

 

통통 튀어야 하니

피직스 마테리알 추

 

 

 

피직스 마테리알을 스피어 콜라이더에 적용

 

수류탄의 자식 중 메쉬 오브젝트에 trail renderer 추가

 

throw grenade 부모 오브젝트를 layer 설정

playerbullet, yes change children 클릭

 

 

다 만들엇음

throw grenade를 플팹으로 저장

 

프리팹 등록후 position x y z 0 0 0 로 설정

 

프리팹 등록했으면 하이어라키 창에서 지움

 

 

    public GameObject grenadeObj;

플레이어 스크립트 위쪽에 저 문구 추

 

프리팹으로 만든 수류탄을

인스펙터 창에서 지정

 

    bool gDown; // 수류탄키

 

수류탄 버튼을 위한 키보드 입력 만들거임

 

    void GetInput()
    {
        gDown = Input.GetButtonDown("Fire2");
    }
    void Update()
    {
        Grenade();
    }

 

 

edit - project settings - input manager - fire2 키는 mouse 1 , 마우스 우클릭임

 

이 키가 수류탄 던지기가 될 고임

 

 

 

 

 

 

                GameObject instantGrenade 
= Instantiate (grenadeObj, transform.position,
transform.rotation)

instantiate 뜻 :

오브젝트를 파티클처럼

임시로 하나 생성시킨 후 제거하겠다는 뜻

 

 

    void Grenade()
    {
       if (hasGrenades == 0)
       return; // 수류탄 개수가 0개면 발동 안됨
      if (gDown && 
       !isReload && !isSwap)
      {
         Ray ray = followCamera.ScreenPointToRay(Input.mousePosition);
         RaycastHit rayHit;
         if (Physics.Raycast(ray, out rayHit, 100))
         {
         Vector3 nextVec = rayHit.point - transform.position;
         nextVec.y = 7;

         GameObject instantGrenade
         = Instantiate(grenadeObj, transform.position,
         transform.rotation);

         Rigidbody rigidGrenade =instantGrenade.GetComponent<Rigidbody>();
          rigidGrenade.AddForce(nextVec, ForceMode.Impulse);
         rigidGrenade.AddTorque 
         (Vector3.back * 10, ForceMode.Impulse);

         hasGrenades --;
                //인덱스가 유효한지 확인
        if (hasGrenades >= 0 && hasGrenades < grenades.Length) { grenades[hasGrenades].SetActive(false); }

            }
        }
    }

수류탄 투척을 스크립트화 시킨 것

이건 플레이어 스크립트 내부에 적은 거임

 

 

 

         hasGrenades --;
         grenades[hasGrenades].SetActive(false);

 

강의에선 위 처럼 하라 했지만, 빨간 버그걸림

2         hasGrenades --;
                //인덱스가 유효한지 확인
        if (hasGrenades >= 0 && hasGrenades < grenades.Length)
            { grenades[hasGrenades].SetActive(false); }

 

챗봇에게 수정하라 했더니 이렇게 수정함

 

 

 

 

수류탄 프리팹 - open - 자식 오브젝트 중 터지는 파티클은 미리 비활성화 시키기

 

 

 

Grenade 라는 이름의 새 스크립트 생성

 

 

throw grenade 프리팹에 스크립트 적용

 

인스펙터 변수 세 개 생성

 

 

Mesh Obj 는 자식 메쉬 를

Effect Obj 는 자식 폭발 파티클 을

Rigid 는 부모 오브젝트 를 지정

 

 

rigid.Sleep(); / /rigid.velocity=Vector3.zero; 대체코드

rigid velocity vector3 zero 코드는 취소선이 생기는 오류가 있으니

rigid sleep 을 사용함

 

 

+

 

공 모양의 범위 레이 케스트를 활용하여 범위 공격을 구현

 

 

SphereCast

SphereCastAll 

가 있는데 , 

SphereCast  하나만

SphereCastAll   범위내 모두 라는 뜻

 

 

RaycastHit[] rayHits = Physics.SphereCastAll
(transform.position, 15,
Vector3.up, 0f, LayerMask.GetMask("Enemy"));

 

RaycastHit[] rayHits = Physics.SphereCastAll
(시작 위치, 반지름크기,
쏘는방향 여기선 크게 상관없음, 범위 오프셋 거리, 레이어 한정 마스크);

레이 케스트 피직스 엔 5가지 항목이 차례로 있음

시작 위치, 반지름 크기, 쏘는 방향, 범위 오프셋, 물리판정 받는 레이어 지정

 

 

 

    public void HitByGrenade(Vector3 explosionPos)
    {
    curHealth -= 100;
    Vector3 reactVec = transform.position - explosionPos; // 오브젝트 위치와 폭발위치 고려한 반응 계산
    StartCoroutine (OnDamage(reactVec));
    }

Enemy 스크립트에 위 문구 추가함

 

 

 

 

충돌시 뱅그르르 돌아가는 게 어색해서 프리즈 x , z 축 했었음

이걸 풀려면

                rigid.freezeRotation = false;

스크립트에서 이렇게 써주면 x y z 프리즈 로테이션이 풀림

 

 

 

    Destroy(gameObject, 5);

5초 후 자기 자신을 삭제하겠다는 뜻

수류탄은 후폭발 파티클 이펙트 시간도 기다려야 해서 5 초를 설정함

 

 

 

 폭발하며 범위 공격을 가하는 수류탄 구현 완료

 

 

지금까지 

Grenade 스크립트 풀 : 

 

public class Grenade : MonoBehaviour
{

   public GameObject meshObj;
   public GameObject effectObj;
   public Rigidbody rigid;

    void Start()
    {
        StartCoroutine(Explosion());

    }

    IEnumerator Explosion()
    {
    yield return new WaitForSeconds(1f);
    rigid.Sleep(); //rigid.velocity=Vector3.zero; 대체코드
    rigid.angularVelocity = Vector3.zero;
    meshObj.SetActive(false);

    effectObj.SetActive(true);

        RaycastHit[] rayHits = Physics.SphereCastAll
    (transform.position, 3,
    Vector3.up, 0f, LayerMask.GetMask("Enemy"));

      foreach (RaycastHit hitObj in rayHits)
     {
     hitObj.transform.GetComponent<Enemy>().HitByGrenade(transform.position);
     }
    Destroy(gameObject, 5);
    }

}

 

 

Enemy 스크립트 풀 :

 

public class Enemy : MonoBehaviour
{

    public int maxHealth;
    public int curHealth;
    Material mat;

    Rigidbody rigid;
    BoxCollider boxCollider;

    void Awake()
    {
    rigid = GetComponent<Rigidbody>();
    boxCollider = GetComponent<BoxCollider>();
    mat = GetComponent<MeshRenderer>().material;
    }

    void OnTriggerEnter(Collider other)
    {
        if(other.tag == "Melee")
        {
            Weapon weapon = other.GetComponent<Weapon>();
            curHealth -= weapon.damage;
           Vector3 reactVec = transform.position
           - other.transform.position;
            StartCoroutine(OnDamage(reactVec, false));
        }
        else if (other.tag == "Bullet")
            {
            Bullet bullet = other.GetComponent<Bullet>();
            curHealth -= bullet.damage;
           Vector3 reactVec = transform.position
           - other.transform.position;
             Destroy(other.gameObject);
            StartCoroutine(OnDamage(reactVec, false));
        }
    }

    public void HitByGrenade(Vector3 explosionPos)
    {
    curHealth -= 100;
    Vector3 reactVec = transform.position - explosionPos;
    StartCoroutine (OnDamage(reactVec, true));
    }

    IEnumerator OnDamage(Vector3 reactVec,
    bool isGrenade)
    {
        mat.color = Color.red;
        yield return new WaitForSeconds(0.1f);

        if (curHealth > 0)
        { mat.color = Color.white; }
        else
        { 
            mat.color = Color.gray;
            gameObject.layer = 12;

            if (isGrenade)
            {
                reactVec = reactVec.normalized;
                reactVec += Vector3.up * 3 ;

                rigid.freezeRotation = false;
                rigid.AddForce(reactVec * 5, ForceMode.Impulse);
                rigid.AddTorque(reactVec * 15, ForceMode.Impulse);

            }
            else 
            {
                reactVec = reactVec.normalized;
                reactVec += Vector3.up;
                rigid.AddForce(reactVec * 5, ForceMode.Impulse);
            }


            Destroy(gameObject, 2);
        }

    }


}

 

 

플레이어 스크립트 풀 :

 

using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Rendering;

public class PlayerSC : MonoBehaviour
{
    public float speed;
    public GameObject[] weapons;
    public bool[] hasWeapons;

    public GameObject[] grenades;
    public int hasGrenades;
    public GameObject grenadeObj;
    public Camera followCamera;

    public int ammo;
    public int coin;
    public int health;


    public int maxAmmo;
    public int maxCoin;
    public int maxHealth;
    public int maxHasGrenades;

    float hAxis;
    float vAxis;


    bool wDown;   // Walk를 위한 shift 꾹
    bool jDown;
    bool fDown; // 공격키
    bool gDown; // 수류탄키
    bool rDown; // 원거리 무기 재장전

    bool iDown;

    bool sDown1;
    bool sDown2;
    bool sDown3;

    bool isJump;
    bool isDodge;
    bool isSwap;
    bool isReload;
    bool isFireReady = true;
    bool isBorder;

    Vector3 moveVec;
    Vector3 dodgeVec;

    Rigidbody rigid;
    Animator anim;

    GameObject nearObject;
    Weapon equipWeapon;
    int equipWeaponIndex = -1;
    float fireDelay;

    void Awake()
    {
        rigid = GetComponent<Rigidbody>();
        anim = GetComponentInChildren<Animator>();
    }

    void Update()
    {
        GetInput();
        Move();
        Turn();
        Jump();
        Grenade();
        Attack();
        Reload();
        Dodge();
        Swap();
        Interaction(); // 키보드 e 키 눌르면 무기 입수
    }

    void FreezeRotation()
    {
        rigid.angularVelocity = Vector3.zero;
    }
    
    void StopToWall()
    {
     Debug.DrawRay(transform.position, transform.forward*5,
    Color.green);
        isBorder = Physics.Raycast(transform.position,
    transform.forward, 1, LayerMask.GetMask("Wall"));
    }

    void FixedUpdate()
    {
        FreezeRotation();
        Move();
        StopToWall();
    }

    void GetInput()
    {
        hAxis = Input.GetAxisRaw("Horizontal");
        vAxis = Input.GetAxisRaw("Vertical");
        wDown = Input.GetButton("Walk"); // GetButton은 꾹 누름
        jDown = Input.GetButtonDown("Jump");
        fDown = Input.GetButton("Fire1");
        gDown = Input.GetButtonDown("Fire2");
        rDown = Input.GetButtonDown("Reload");
        iDown = Input.GetButtonDown("Interaction");
        sDown1 = Input.GetButtonDown("Swap1");
        sDown2 = Input.GetButtonDown("Swap2");
        sDown3 = Input.GetButtonDown("Swap3");
    }

    void Move()
    {
        moveVec = new Vector3(hAxis, 0, vAxis).normalized;

        if (isDodge)
            moveVec = dodgeVec;

        if (isSwap || isReload  || !isFireReady)
            moveVec = Vector3.zero;

        if (!isBorder)
          transform.position += moveVec 
         * speed * (wDown ? 0.2f : 1.0f) * Time.deltaTime;
        // Walk 걸을 때 속도 감소

        anim.SetBool("isRun", moveVec != Vector3.zero);
        anim.SetBool("isWalk", wDown);
    }

    void Turn()
    {
        // 키보드에 의한 회전 구현
        transform.LookAt(transform.position + moveVec);
        // 마우스에 의한 회전 구현
        if (fDown)
        {
            Ray ray = followCamera.ScreenPointToRay(Input.mousePosition);
            RaycastHit rayHit;
            if (Physics.Raycast(ray, out rayHit, 100))
            {
                Vector3 nextVec = rayHit.point - transform.position;
                nextVec.y = 0;
                transform.LookAt(transform.position + nextVec);
            }


        }
    }

    void Jump()
    {
        // 제자리에서 점프는 점프
        if (jDown 
    && moveVec == Vector3.zero 
    && !isJump && !isDodge && !isSwap)
        {
            rigid.AddForce(Vector3.up * 7, ForceMode.Impulse);
            anim.SetBool("isJump", true);
            anim.SetTrigger("doJump");
            isJump = true;
        }
    }

    void Grenade()
    {
       if (hasGrenades == 0)
       return; // 수류탄 개수가 0개면 발동 안됨
      if (gDown && 
       !isReload && !isSwap)
      {
         Ray ray = followCamera.ScreenPointToRay(Input.mousePosition);
         RaycastHit rayHit;
         if (Physics.Raycast(ray, out rayHit, 100))
         {
         Vector3 nextVec = rayHit.point - transform.position;
         nextVec.y = 7;

         GameObject instantGrenade
         = Instantiate(grenadeObj, transform.position,
         transform.rotation);

         Rigidbody rigidGrenade =instantGrenade.GetComponent<Rigidbody>();
          rigidGrenade.AddForce(nextVec, ForceMode.Impulse);
         rigidGrenade.AddTorque 
         (Vector3.back * 10, ForceMode.Impulse);

         hasGrenades --;
                //인덱스가 유효한지 확인
        if (hasGrenades >= 0 && hasGrenades < grenades.Length)
            { grenades[hasGrenades].SetActive(false); }

         }
        }
    }

    void Attack()
    {
        if (equipWeapon == null)
            return;
        fireDelay += Time.deltaTime;
        isFireReady = equipWeapon.rate < fireDelay;

        if ( fDown && isFireReady && !isDodge && !isSwap )
        {
            equipWeapon.Use();
            anim.SetTrigger(equipWeapon.type 
            == Weapon.Type.Melee ? "doSwing": "doShot");
            fireDelay = 0;
        }

    }

    void Reload()
    {
    if(equipWeapon == null) return;
    if(equipWeapon.type == Weapon.Type.Melee) return;
    if (ammo == 0) return;

    if(rDown && isFireReady &&
    !isJump && !isDodge && !isSwap )
    {
            anim.SetTrigger("doReload");
    isReload = true;

            Invoke("ReloadOut", 0.5f);
        }

    }

    void ReloadOut()
    {
     int reAmmo = ammo < equipWeapon.maxAmmo ? ammo : equipWeapon.maxAmmo;
     equipWeapon.curAmmo = reAmmo;
     ammo -= reAmmo;
     isReload = false;
    }

    void Dodge()     // 방향키 눌러서 점프는 닷지
    {
        if (jDown 
    && moveVec != Vector3.zero 
    && !isJump && !isDodge && !isSwap)
        {
            dodgeVec = moveVec;
            speed *= 1.5f;
            anim.SetTrigger("doDodge");
            isDodge = true;

            Invoke("DodgeOut", 0.5f);
        }
    }

    void DodgeOut()
    {
        speed /= 1.5f; // 원래 속도로 되돌리기
        isDodge = false;
    }

    void Swap()
{

        if (sDown1 && (!hasWeapons[0] || equipWeaponIndex == 0))
            return;
        if (sDown2 && (!hasWeapons[1] || equipWeaponIndex == 1))
            return;
        if (sDown3 && (!hasWeapons[2] || equipWeaponIndex == 2))
            return;

        int weaponIndex = -1;
        if (sDown1) weaponIndex = 0;
        if (sDown2) weaponIndex = 1;
        if (sDown3) weaponIndex = 2;

        if ((sDown1 || sDown2 || sDown3) && !isJump && !isDodge)
        {
            if (equipWeapon != null)
            equipWeapon.gameObject.SetActive(false);

            equipWeaponIndex = weaponIndex;
             equipWeapon = weapons[weaponIndex].GetComponent<Weapon>() ;
            equipWeapon.gameObject.SetActive(true);

            anim.SetTrigger("doSwap");

            isSwap = true;

            Invoke("SwapOut", 0.4f);

        }
    }

    void SwapOut()
    {
        isSwap = false;

    }

    void Interaction()
    {
        if (iDown && nearObject != null 
    && !isJump && !isDodge)
        {
            if (nearObject.tag == "Weapon")
            {
                Item item = nearObject.GetComponent<Item>();
                int weaponIndex = item.value;
                hasWeapons[weaponIndex] = true;

                Destroy(nearObject);
            }
        }
    }

    void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.tag == "Floor")
        {
            anim.SetBool("isJump", false);
            isJump = false;
        }
    }

    void OnTriggerEnter(Collider other)
    {
        if(other.tag == "Item")
     {
    Item item = other.GetComponent<Item>();
    switch(item.type) 
             { 
              case Item.Type.Ammo:
                  ammo += item.value;
                  if (ammo > maxAmmo) ammo = maxAmmo;
                  break;
              case Item.Type.Coin:
                  coin += item.value;
                  if (coin > maxCoin) coin = maxCoin;
                  break;
              case Item.Type.Heart:
                  health += item.value;
                  if (health > maxHealth) health = maxHealth;
                  break;
                case Item.Type.Grenade:
                if (hasGrenades < maxHasGrenades) 
                   grenades[hasGrenades].SetActive(true); 
                   hasGrenades += item.value;
                 if (hasGrenades == maxHasGrenades)
                    return;
                    break;
            }
           Destroy(other.gameObject);
        }

    }

    void OnTriggerStay(Collider other)
    {
        if (other.tag == "Weapon")
            nearObject = other.gameObject;
    }

    void OnTriggerExit(Collider other)
    {
        if (other.tag == "Weapon")
            nearObject = null;
    }
}
반응형
Posted by 이름이 익명
:

https://www.youtube.com/watch?v=IoaPxcSHwEM

 

허수아비 처럼 데미지 테스트하는 에너미를 만들어보자

 

 

3d object cube 하나 생성

 

리지드 바디 컴포 넣기

콘스트레인 프리즈 로테이션 x 축, z 축 체크 온

 

 

 

Enemy 라는 이름의 스크립트 하나 생성 

 

위에 만든 큐브에 컴포 적용

큐브 이름 바꾸기 : Test Enemy

 

 

저번에 만든 총알들 프리팹 태그 Bullet 태그 만들어서 태그 지정

 

 

    public int maxHealth;
    public int curHealth;

 

체력 설정할 땐,

최대체력 max hp 와

현재체력 current hp 

두 개가 항상 세트로 있다

 

Weapon weapon = other.GetComponent<Weapon>();

 

다른 스크립트에서 weapon 값을 가지고 오겠다는 뜻

 

 

    void OnTriggerEnter(Collider other)
    {
        if(other.tag == "Melee")
        {
            Weapon weapon = other.GetComponent<Weapon>();
            curHealth -= weapon.damage;
        }
        else if (other.tag == "Bullet")
            {
            Bullet bullet = other.GetComponent<Bullet>();
            curHealth -= bullet.damage;
            }

    }

근접 공격 받으면 무기 데미지를 체력에서 빼고

원거리 공격 받으면 총알 데미지를 체력에서 빼겠다는 뜻

 

 

 

테스트 에너미의 체력을 둘 다 60으로 설정한다

 

 

총알과 무기(햄머) 콜라이더에 is trigger 체크 on

 

무기 햄머는 캐릭터 - 관절 - 오른손 - 무기햄머 이렇게 하이어라 펼쳐봐야 함

 

 

 

지금까지 만들었으면, 총알이 안 사라지고 남아있는 버그가 있음

 

 

using UnityEngine;

public class Bullet : MonoBehaviour
{

    public int damage;

    void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.tag == "Floor")
        {
            Destroy(gameObject, 1.0f); // 부딪친후 3초 후 삭제
        }
    }
    void OnTriggerEnter(Collider other)
    { 
        if (other.gameObject.tag == "Wall")
        {
            Destroy(gameObject, 0.0f);
        }
    }
}

 

Bullet 스크립트를 위와 같이 변경하면 됨

 

 

+

 

피격시 빨갛게 되기 처리 구현해보기

 

 

 

    mat = GetComponent<MeshRenderer>().material;

 

스크립트에서 마테리알을 가져올땐 위와 같이 메쉬 렌더러를 이용한다 함

 

 

        if (curHealth > 0)
        { mat.color = Color.white; }
        else
        { 
            mat.color = Color.gray;
            Destroy(gameObject, 2);
        }

현재 체력이 1 이상이면 살았으니 하얀색을 띈 다는 뜻임

 

현재 체력이 0 또는 0 보다 낮으면 회색을 띈다는 뜻임, 게다가 2초 후 사라진다는 뜻임.

 

            Destroy(gameObject, 2);

 

디스트로이 뒤에 붙은 숫자는 2 초 후에 사라지겠다는 걸 의미함

 

 

             Destroy(other.gameObject);

그냥 gameObject는 자기 자신을 사라지게 하겠다

other.gameObject는 다른 오브젝트를 제거하겠다는 뜻

 

 

 

우측 상단 , 물리 Layer 하나 추가

 

이름은 Enemy

그리고 하나더, EnemyDead

 

Test Enemy 오브젝트 레이어를 Enemy 로 설정

 


edit - project settings - physics - settings
layer collision matrix 
EnemyDead 는 Wall, Floor, EnemyDead 만 남기기

 

 

 

EnemyDead 레이어 번호를 기억하자

 

강의에선 14번인데, 사람마다 숫자가 다를 수 있음 (나는 12번이었음)

 

            gameObject.layer = 12;

이 오브젝트의 피직스 레이어를 12번으로 바꾸겠다는 뜻

 

 

 

+

 

 

후처리 더 다듬어보자

 

 

    void OnTriggerEnter(Collider other)
   	{
          Vector3 reactVec = transform.position
           - other.transform.position;
                       StartCoroutine(OnDamage(reactVec));
         }
         
         
           IEnumerator OnDamage(Vector3 reactVec)
  {

          reactVec = reactVec.normalized;
          reactVec += Vector3.up;
          rigid.AddForce(reactVec * 5, ForceMode.Impulse);
}

 

충돌을 받을 때,

공격한 반대 방향으로 넉벡을 주겠다는 뜻

 

vector3 up 은 위쪽으로 튀어 오르겠다는 뜻

애드 포스도 있으니 위로도 튀고 뒤쪽으로도 튄다는 뜻

 

+

 

 

댓글 정보 추

 


휘두른거랑 데미지 들어가는거랑 안 맞을 때
: Swing 코루틴에서 맨위의 waitforSeconds를 0.4초 정도로 하면 됩니다. 

아마 애니메이션과 딜레이가 좀 안맞아서 그런것 같습니다. 저는 0.45 /0.1/ 0.3으로 했습니다

 

 

반응형
Posted by 이름이 익명
:

https://www.youtube.com/watch?v=dynN70kyD9A

 

 

 

 

플레이어가 다른 물체 콜라이더와 충돌 후

한 방향으로 계속 회전되는 문제

 

플레이어 스크립트 열어서 해결해야 함

 

 

    void FreezeRotation()
    {
        rigid.angularVelocity = Vector3.zero;
    }
    void FixedUpdate()
    {
        FreezeRotation();
    }

 

넣으면 됨

 

+

 

 

충돌 레이어 설정

 

인스펙터 창 오 위에 레이어 라는 게 있음

 

'탄피의 물리충돌은 플레이어의 물리충돌과 서로 영향을 안주게 한다'

레이어로 이런걸 설정 가능함

 

이번 강좌에선 위 Floor, Player, PlayerBullet, BulletCase 네 개의 레이어를 새로 생성함

 

레이어 설정시 

메시지 뜨면 예스 칠드런도 오키 칸을 누름

 

edit - project settings - physics - settings - layer collision matrix

 

각 레이어간 물리충돌 하냐 를 나타낸 것

 

 

'탄피는 바닥 하고 충돌하겠다'

'탄피는  탄피 끼리 충돌하겠다'

를 나타낸 게 위 스샷

 

 

플레이어 총알이 플레이어랑 충돌 안하게 하려고

playerbullet 과 player 사이는 체크 해제

 

 

 layer collision matrix 가 프렌들리 파이어 하고 연관있는 거임

 

 

+

 

플레이어가 벽 관통되는 거 방지하기

 

플레이어 스크립트 만져야 함

 

    bool isBorder;
    
    
    void StopToWall()
    {
     Debug.DrawRay(transform.position, transform.forward*5,
    Color.green);
        isBorder = Physics.Raycast(transform.position,
    transform.forward, 1, LayerMask.GetMask("Wall"));
    }
    
    void FixedUpdate()
    {
        StopToWall();
    }
    
        void Move()
    {
        moveVec = new Vector3(hAxis, 0, vAxis).normalized;
        // Walk 걸을 때 속도 감소

        if (isDodge)
            moveVec = dodgeVec;

        if (isSwap || isReload  || !isFireReady)
            moveVec = Vector3.zero;

        if (!isBorder)
          transform.position += moveVec 
         * speed * (wDown ? 0.2f : 1.0f) * Time.deltaTime;

        anim.SetBool("isRun", moveVec != Vector3.zero);
        anim.SetBool("isWalk", wDown);
    }

 

중간 삽입해야해서 좀 복잡함. 이건 영상을 봐야 함.

 

Wall 이라는 레이어 도 만들어야 함

 

캐릭터 앞 1 만큼의 빛을 쏴서 벽 콜라이더에 부딪치면

플레이어가 이동을 멈추겠다는 뜻임

 

+

 

아이템 충돌 제거

 

 

아이템에 다가가면 아이템이 회전해버리는 콜라이더가 의도치 않게 재생됨

 

Item 스크립트 열기

 

 

스크립트에서 쓰여진 건

우선순위 위에 있는 콤포넌트 부터 적용됨

 

그래서 이스 트리거 없는 콜라이더를 위로 올릴 필요가 있음

 

프리팹 모드로 들어간 후 moveup 눌러 컴포넌트 순서 재정렬

 

참고로 모든 아이템엔 프리팹에 스피어 콜라이더가 있음

 

 

Item 스크립트는 아래와 같음

using UnityEngine;

public class Item : MonoBehaviour
{

public enum Type 
{ Ammo, Coin, Grenade, Heart, Weapon };
public Type type;

public int value;

    Rigidbody rigid;
    SphereCollider sphereCollider;

    void Awake()
    {
     rigid = GetComponent<Rigidbody>();
     sphereCollider = GetComponent<SphereCollider>();

    }

    void Update()
{
        transform.Rotate(Vector3.up * 20 * Time.deltaTime);
}

    void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.tag == "Floor")
        {
    rigid.isKinematic = true;
    sphereCollider.enabled = false;

        }
    }

}

 

+

 

콜라이더 충돌 발생할때마다 너무 미끄러지는게 싫으면

피직스 마테리알을 none 으로 설정

반응형
Posted by 이름이 익명
:


블렌더 애니메이션 기초

 

 



동영상 속 포즈를 따라해서 트레이싱 함


특정 구간 포즈를 따라해서 포즈를 찍는다
전체 구간 키프레임을 찍을 필욘 없다


키워드 : rotomation

 

 

 

 


dope sheet 윈도우 창 키고, 포징 저렇게 구간별 하나씩 함

 



+

 

 

아래는 심화 팁들임

 



직접 포즈 취해본다


'왼팔이 뒤로 갈땐 오른 어깨가 앞으로 간다'
이런 식의 작은 정보들을 모은다

 

 

 

 


더 과장 되게 표현한다

 

뼈의 position 값, size 값을 실제보다 더 높은 수치를 넣어 표현

 

 

 


graph editor 를 만진다


aligned 부드럽게 함
free : 내 맘대로 딱딱한 표현 넣을 수 있음

 

 


타이밍 법칙 

timing


linear 가 아니라 ease 한 출발 도착 법칙. 

관성의 법칙.

 

 

 

 



왼발 앞으로 오른발 뒤로 함.


모든 포즈의 베이스 삼을 땐,
왼발앞 오발뒤 를 베이스 삼는다

 

아이들포즈 부터 공격포즈, 피격포즈까지 

최소한 발바닥 하나는 위치를 공유 해야 함 





+

 



주의점!

트랜지션 어색한 예시

발 설정 잘못해서 슬라이딩이 연출된 예시

왼발앞 오발뒤 를 지키지 않았을 댄 이런 에러가 남

공중에 붕~ 뜬 느낌이 나면 안됨

 

 

+

 

 



갑자기 확! 발동되는 빠른 애니메이션도 표현해보자

애니 사이 중간 트랜지션 없이 즉각발동 되게 함

 

 

 





반응형
Posted by 이름이 익명
:

https://www.youtube.com/watch?v=07q9RUTRq4M&t=4s

 

 

일단 총알부터 만들자

엠티 오브젝트 생성 

 

이름은 Bullet HandGun

x y z 0 0 0 으로 해두고

 

 

Trail Renderer 추

 


트레일 렌더러 세부설정


width 빨간 그래프 

0.5 부터 시작해서 점점 감소되게 설정

time 0.3 정도
min vertex distance 0 

 

마테리알은 default line 적용.

 

 

리지드 바디, sphere 콜라이더도 만들기

 

스피어 콜라이더에

x y z 0 0 0 으로 설정하고

radius 조금 줄여줌

 

 

ctrl D 눌러서 복붙후

아래 이름은 Bullet SubMachineGun 으로 명명

 

+

 

 

네모난 탄피 오브젝트 만들기

(대충 cube 써서 만들어도 될 듯)

 

탄피, bullet case 는 

바닥에 떨어지는 이펙트 용도임

 

탄피에 리지드 바디, 박스 콜라이더  컴포넌트추

 

 

콜라이더 크기 오브젝트에 맞추기

 

 

+

 

 

Bullet 이라는 스크립트 하나 생성

 

 

 

만든 불렛 오브젝트들 세 개 다중선택후

컴포넌트 스크립트 Bullet 적용

 

 

일단 지금까지 스크립트 내용은 : 

using UnityEngine;


public class Bullet : MonoBehaviour
{

    public int damage;


void OnCollisionEnter(Collision collision)
{

        if (collision.gameObject.tag == "Floor")
        {
            Destroy(gameObject, 3); // 부딪친후 3초 후 삭제
        }
        else if (collision.gameObject.tag == "Wall")
        {
            Destroy(gameObject, 0);
        }


    }


}

 

오브젝트 명 Damage
Bullet HandGun 20
Bullet SubMachineGun 7
Bullet Case 0

 

인스펙터 창에서 이렇게 데미지 설정

 

 

+

 

 

프리팹 만든 후

x y z 0 0 0 으로 설정 

 

 

세 bullet 들 프리팹으로 만들었으면 하이어라키 창에서 지움

 

 

+

 

플레이어의 발사 구현, 애니메이션 설정

 

Animator 창에서

 

 

플레이어 오브젝트 열어서 Shot 애니메이션을 드래그.

 

예전 Swing 만들듯이 만듦

 

Any State 에서 우클 화살 트랜지션 연결후 , Exit 로 우클 연결

 

파라메터 + 로 추가후 trigger 섵낵

이름은 doShot

 

Any state 에서 Shot 애니로 들어오는 트랜지션의

컨디션은 doShot 으로 지정

 

 

+

 

플레이어의 발사 구현, 스크립트 만지기

 

Player 스크립트 로 간 후

 

            anim.SetTrigger(equipWeapon.type 
            == Weapon.Type.Melee ? "doSwing": "doShot");

 

웨펀 타입이 밀리인가?

맞으면 doSwing 을 하기

아니면 doShot 을 하기

라는 뜻임

 

 

 하이어라키 창에서

플레이어 오른손 아래 gun 과 sub machine gun 의

타입을 range 로 설정

 

 

 

 

총알이 데미지 있는거지 총은 데미지가 없으니

데미지는 0 

melee area 설정 안함

trail effect 설정 안

 

핸드건 rate, 공속은 0.4 로 설정

 

서브 머신건은 rate 0.15 로 설정

 

 

총에겐

총알이 발사되는 위치, 탄피가 나오는 위치 

가 필요하다

 

weapon 스크립트에 추가함 :

 

    public Transform bulletPos; //총알생성위치
    public GameObject bullet;
    public Transform bulletCasePos; //탄피생성위치
    public GameObject bulletCase;

 

 

 플레이어 아래 엠티 오브젝트 추가

 

Bullet Pos 로 이름 변경

 

총알 나가는 위치 즈음으로

Bullet Pos 의 x y z 를 이동시킴

일단 대략적인 위치를 지정함

 

 

 

탄피가 나가는 위치는 

오른손 아래 , 웨펀 포인트 아래, 핸드건 아래 크리에이트 엠티

 

이름은 Case Pos 로 명명

 

 

 

Case Pos  오브젝트 위치는 총알 옆 탄피 배출구 쪽으로 이동

 

위치 이동이 힘들면 Global / Local 로 전환

 

 

서브머신건 아래 자식오브젝트도 동일하게 Case Pos  만들기

 

 

이제 핸드건, 서브머신건의 스크립트에서 변수를 지정

 

 

 

    Vector3 caseVec = bulletCasePos.forward * -10

파란 Z 축 반대 방향으로 탄피를 나가겠다는 뜻

음수를 곱함

 

 

        Vector3 caseVec = bulletCasePos.forward * Random.Range(-3, -2)
    + Vector3.up * Random.Range(2, 3)

탄피가 z 축 반대 방향으로 나가는데

-2 ~ -3 의 랜덤값으로 나간다

근데 위쪽으로 2~3의 랜덤값으로 튄다

는 뜻

 

caseRigid.AddForce(caseVec, ForceMode.Impulse);

나가는 힘을 주겠다는 뜻

 

 

 

~~~

근데 비주얼 스튜디오에서 오류남

instantBullet 인가 intantBullet 인가 빨간줄 생김

 

그리고 velocity 에 취소선이 생김

 

 

~~~

 

 

벽에 Wall 태그 넣기

태그 Wall 만든 후 적용

 

 

 

테스트 모드 해보기

무기 앞에서 e 눌러 무기 줍기

2 번 3번 키로 무기 스왑

마우스 왼손으로 발사

 

+

 

재장전 구현하기

 

 

 if (type == Type.Range && curAmmo > 0 )

무기 타입이  원거리 이고

지금 탄환이 0보다 클 때 (1이상일때)

라는 뜻

 

            curAmmo--;

curAmmo 라는 변수를 한개씩 줄어들게 한다는 뜻

 

 

인풋 매니저

재장전 Reload 

포지티브 버튼 키보드 R 로 설정

 

 

    if(equipWeapon == null) return;
    if(equipWeapon.type == Weapon.Type.Melee) return;
    if (ammo == 0) return;

만약 무기가 없으면 하지 않겠다

만약 무기 타입이 근거리면 하지 않겠다

만약 총알이 0 개면 하지 않겠다

라는 뜻.

재장전 조건에 쓰일 거임

 

 

 

animator 에서 재장전 

shot 애니 만들듯이 만들기

파라메터 트리거, doReload 로 함

 

 

핸드건 cur ammo 변수, max ammo 변수 설정

처음엔 탄이 다 충전되어있음을 연출시키려고 7 , 7로 설정

서브 머신건은 30 , 30 으로 설정

 

+

 

마우스 위치 로 총알 발사시키기

 

    public Camera followCamera;

플레이어 스크립트에 추

 

 

플레이어 오브젝트에서 팔로우 카메라 변수를 메인 카메라를 연결

 

 

        // 마우스에 의한 회전 구현
        Ray ray = followCamera.ScreenPointToRay(Input.mousePosition);

화면상 마우스 위치를 반영한다는 뜻

 

 

out 함수는 리턴저럼 값을 저장하는 개념

 

                nextVec.y = 0;

플레이어가 y 회전,  눕혀지는 걸 방지하는 것

 

    void Turn()
    {
        // 키보드에 의한 회전 구현
        transform.LookAt(transform.position + moveVec);
        // 마우스에 의한 회전 구현
        if (fDown)
    {
            Ray ray = followCamera.ScreenPointToRay(Input.mousePosition);
            RaycastHit rayHit;
            if (Physics.Raycast(ray, out rayHit, 100))
            {
                Vector3 nextVec = rayHit.point - transform.position;
                nextVec.y = 0;
                transform.LookAt(transform.position + nextVec);
            }


        }
    }

마우스 왼쪽 버튼, 파이어 다운 을 누르면

플레이어가 마우스 위치 쪽을 바라보겠다 라는 뜻

 

 

~~~~~~

 

지금까지

 

풀 웨펀 스크립트

 

using Febucci.UI.Effects;
using System.Collections;
using UnityEngine;


public class Weapon : MonoBehaviour
{
        
    public enum Type { Melee, Range }; // ������ ���Ÿ��� 
    public Type type;
    public int damage; //���� ������
    public float rate;  //���ݼӵ�

    public int maxAmmo;
    public int curAmmo;

    public BoxCollider meleeArea; //���ݹ���
    public TrailRenderer trailEffect; //�ܻ�����Ʈ

    public Transform bulletPos; //�Ѿ˻�����ġ
    public GameObject bullet;
    public Transform bulletCasePos; //ź�ǻ�����ġ
    public GameObject bulletCase;

    public void Use()
   {
       if (type == Type.Melee )
        { 
        StopCoroutine("Swing");
        StartCoroutine("Swing"); 

        }
        else if (type == Type.Range && curAmmo > 0 )
        {
            curAmmo--;
            StopCoroutine("Shot");
            StartCoroutine("Shot");

        }

    }

    IEnumerator Swing()
    {
   // �ִϸ��̼ǰ� �� �� ������ �� �Ʒ� �κ��� ����

        yield return new WaitForSeconds(0.12f); // 0.1�� ���
        meleeArea.enabled = true;
        trailEffect.enabled = true;

        yield return new WaitForSeconds(0.1f);
        meleeArea.enabled = false;

        yield return new WaitForSeconds(0.1f);
        trailEffect.enabled = false;

    ResetTrailRenderer(trailEffect);   // Trail Renderer ����
        yield break; 
    }

    void ResetTrailRenderer // Trail Renderer ����
    (TrailRenderer trail)
    { trail.Clear(); }


    IEnumerator Shot()
    {
        // 총알 발사

        GameObject instantBullet =     Instantiate(bullet, bulletPos.position,bulletPos.rotation);
       Rigidbody bulletRigid = instantBullet.GetComponent<Rigidbody>();
        bulletRigid.linearVelocity = bulletPos.forward * 50;

        yield return null;

        // 탄피 발사
        GameObject instantCase =        Instantiate(bulletCase, bulletCasePos.position, bulletCasePos.rotation);
        Rigidbody caseRigid = instantCase.GetComponent<Rigidbody>();
        Vector3 caseVec = bulletCasePos.forward * Random.Range(-3, -2)
        + Vector3.up * Random.Range(2, 3);
        caseRigid.AddForce(caseVec, ForceMode.Impulse);
        caseRigid.AddTorque(Vector3.up * 10, ForceMode.Impulse);

    }
    




}

 

 

풀 플레이어 스크립트 : 

 

using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Rendering;

public class PlayerSC : MonoBehaviour
{
    public float speed;
    public GameObject[] weapons;
    public bool[] hasWeapons;

    public GameObject[] grenades;
    public int hasGrenades;
    public Camera followCamera;

    public int ammo;
    public int coin;
    public int health;


    public int maxAmmo;
    public int maxCoin;
    public int maxHealth;
    public int maxHasGrenades;

    float hAxis;
    float vAxis;


    bool wDown;   // Walk를 위한 shift 꾹
    bool jDown;
    bool fDown; // 공격키
    bool rDown; // 원거리 무기 재장전

    bool iDown;

    bool sDown1;
    bool sDown2;
    bool sDown3;

    bool isJump;
    bool isDodge;
    bool isSwap;
    bool isReload;
    bool isFireReady = true;

    Vector3 moveVec;
    Vector3 dodgeVec;

    Rigidbody rigid;
    Animator anim;

    GameObject nearObject;
    Weapon equipWeapon;
    int equipWeaponIndex = -1;
    float fireDelay;

    void Awake()
    {
        rigid = GetComponent<Rigidbody>();
        anim = GetComponentInChildren<Animator>();
    }

    void Update()
    {
        GetInput();
        Move();
        Turn();
        Jump();
        Attack();
        Reload();
        Dodge();
        Swap();
        Interaction(); // 키보드 e 키 눌르면 무기 입수
    }

    void FixedUpdate()
    {
        Move();
    }

    void GetInput()
    {
        hAxis = Input.GetAxisRaw("Horizontal");
        vAxis = Input.GetAxisRaw("Vertical");
        wDown = Input.GetButton("Walk"); // GetButton은 꾹 누를 때만 적용
        jDown = Input.GetButtonDown("Jump");
        fDown = Input.GetButtonDown("Fire1");
        rDown = Input.GetButtonDown("Reload");
        iDown = Input.GetButtonDown("Interaction");
        sDown1 = Input.GetButtonDown("Swap1");
        sDown2 = Input.GetButtonDown("Swap2");
        sDown3 = Input.GetButtonDown("Swap3");
    }

    void Move()
    {
        moveVec = new Vector3(hAxis, 0, vAxis).normalized;
        // Walk 걸을 때 속도 감소

        if (isDodge)
            moveVec = dodgeVec;

        if (isSwap || isReload  || !isFireReady)
            moveVec = Vector3.zero;

        if (wDown)
            transform.position += moveVec * speed * 0.2f * Time.deltaTime;
        else
            transform.position += moveVec * speed * 1.0f * Time.deltaTime;

        anim.SetBool("isRun", moveVec != Vector3.zero);
        anim.SetBool("isWalk", wDown);
    }

    void Turn()
    {
        // 키보드에 의한 회전 구현
        transform.LookAt(transform.position + moveVec);
        // 마우스에 의한 회전 구현
        if (fDown)
    {
            Ray ray = followCamera.ScreenPointToRay(Input.mousePosition);
            RaycastHit rayHit;
            if (Physics.Raycast(ray, out rayHit, 100))
            {
                Vector3 nextVec = rayHit.point - transform.position;
                nextVec.y = 0;
                transform.LookAt(transform.position + nextVec);
            }


        }
    }

    void Jump()
    {
        // 제자리에서 점프는 점프
        if (jDown 
    && moveVec == Vector3.zero 
    && !isJump && !isDodge && !isSwap)
        {
            rigid.AddForce(Vector3.up * 7, ForceMode.Impulse);
            anim.SetBool("isJump", true);
            anim.SetTrigger("doJump");
            isJump = true;
        }
    }

    // 방향키 눌러서 점프는 닷지

    void Attack()
    {
        if (equipWeapon == null)
            return;
        fireDelay += Time.deltaTime;
        isFireReady = equipWeapon.rate < fireDelay;

        if ( fDown && isFireReady && !isDodge && !isSwap )
        {
            equipWeapon.Use();
            anim.SetTrigger(equipWeapon.type 
            == Weapon.Type.Melee ? "doSwing": "doShot");
            fireDelay = 0;
        }

    }

    void Reload()
    {
    if(equipWeapon == null) return;
    if(equipWeapon.type == Weapon.Type.Melee) return;
    if (ammo == 0) return;

    if(rDown && isFireReady &&
    !isJump && !isDodge && !isSwap )
    {
            anim.SetTrigger("doReload");
    isReload = true;

            Invoke("ReloadOut", 0.5f);
        }

    }

    void ReloadOut()
    {
     int reAmmo = ammo < equipWeapon.maxAmmo ? ammo : equipWeapon.maxAmmo;
     equipWeapon.curAmmo = reAmmo;
     ammo -= reAmmo;
     isReload = false;
    }

    void Dodge()
    {
        if (jDown 
    && moveVec != Vector3.zero 
    && !isJump && !isDodge && !isSwap)
        {
            dodgeVec = moveVec;
            speed *= 1.5f;
            anim.SetTrigger("doDodge");
            isDodge = true;

            Invoke("DodgeOut", 0.5f);
        }
    }

    void DodgeOut()
    {
        speed /= 1.5f; // 원래 속도로 되돌리기
        isDodge = false;
    }

    void Swap()
{

        if (sDown1 && (!hasWeapons[0] || equipWeaponIndex == 0))
            return;
        if (sDown2 && (!hasWeapons[1] || equipWeaponIndex == 1))
            return;
        if (sDown3 && (!hasWeapons[2] || equipWeaponIndex == 2))
            return;

        int weaponIndex = -1;
        if (sDown1) weaponIndex = 0;
        if (sDown2) weaponIndex = 1;
        if (sDown3) weaponIndex = 2;

        if ((sDown1 || sDown2 || sDown3) && !isJump && !isDodge)
        {
            if (equipWeapon != null)
            equipWeapon.gameObject.SetActive(false);

            equipWeaponIndex = weaponIndex;
             equipWeapon = weapons[weaponIndex].GetComponent<Weapon>() ;
            equipWeapon.gameObject.SetActive(true);

            anim.SetTrigger("doSwap");

            isSwap = true;

            Invoke("SwapOut", 0.4f);

        }
    }

    void SwapOut()
    {
        isSwap = false;

    }



    void Interaction()
    {
        if (iDown && nearObject != null 
    && !isJump && !isDodge)
        {
            if (nearObject.tag == "Weapon")
            {
                Item item = nearObject.GetComponent<Item>();
                int weaponIndex = item.value;
                hasWeapons[weaponIndex] = true;

                Destroy(nearObject);
            }
        }
    }

    void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.tag == "Floor")
        {
            anim.SetBool("isJump", false);
            isJump = false;
        }
    }

    void OnTriggerEnter(Collider other)
    {
        if(other.tag == "Item")
     {
    Item item = other.GetComponent<Item>();
    switch(item.type) 
             { 
              case Item.Type.Ammo:
                  ammo += item.value;
                  if (ammo > maxAmmo) ammo = maxAmmo;
                  break;
              case Item.Type.Coin:
                  coin += item.value;
                  if (coin > maxCoin) coin = maxCoin;
                  break;
              case Item.Type.Heart:
                  health += item.value;
                  if (health > maxHealth) health = maxHealth;
                  break;
                case Item.Type.Grenade:
                if (hasGrenades < maxHasGrenades) 
                   grenades[hasGrenades].SetActive(true); 
                   hasGrenades += item.value;
                 if (hasGrenades == maxHasGrenades)
                    return;
                    break;
            }
           Destroy(other.gameObject);
        }

    }

    void OnTriggerStay(Collider other)
    {
        if (other.tag == "Weapon")
            nearObject = other.gameObject;
    }

    void OnTriggerExit(Collider other)
    {
        if (other.tag == "Weapon")
            nearObject = null;
    }
}

 

+

 

 

참고로, 

무기 스왑 1 키 2 키 3 키 눌렀는데 제대로 변경안되는 버그가 있을 땐

 


 int equipWeaponIndex = -1;

이렇게 맨위에 적혀있어야 함


원래 양수 이게 맞나 확인해야함

그냥 양수 1로 하면 안됨

 

 

 

반응형
Posted by 이름이 익명
:

https://www.youtube.com/watch?v=Zfoyagdz1y0

 

 

Weapon 이름의 새 스크립트 생성후

 

 

핸드 뼈  아래에 자식으로 위치시킨 무기 세개, 저번에 만든 거

인스펙터창 에 weapon 스크립트 드래그 해서 적용

 

 

근접공격이기에 망치만 편집해보도록 하겟습니다

 

 

 

using UnityEngine;


public class Weapon : MonoBehaviour
{
        
public enum Type { Melee, Range }; // 물리냐 원거리냐 
public Type type;
    public int damage; //공격 데미지
    public float rate;  //공격속도
    public BoxCollider meleeArea; //공격범위
    public TrailRenderer trailEffect; //잔상이펙트


}

 

weapon 스크립트 내용 여기까지 씀

 

 

영상에선 망치, 내 파일에선 WP_Cylinder 

 

비활성화 된 걸 활성화 시키기

 

Damage 는 20으로 설정

Rate 0.4로. 0.4 초마다 휘두르는 거임

 

 

공격범위 설정을 위해

아래 바로 애드 콤포넌트 . Box Collider 생성

 

 

박스 콜라이더 생성

is trigger on

 

그후 바로 위 

melee area 탭으로 드래그

 

 

 

Melee 태그 하나 만든 후 

해머 웨펀에 지정

 

 

,

 

 

이펙트 만들기

 

해머 아래에 자식에 create empty

 

 

엠피 이름은 Effect

 

Trail Renderer 컴포 하나넣기

 

 

망치 위치 움직여보면 뭔가 잔상이 보임

 

 

트레일 렌더러의 

material 을 default line 으로 설정

 

 

상단 빨간 Width 에서 우클, add key 누른후 점 생성후

그래프를 원하는대로 만들면

꼬리가 가 형태로 변경됨

 

 

길이가 너무 길면 Time 을 5가 아니라 0.7 정도로 낮추기

min vertex distance  값이 줄어들수록 부드럽고, 올릴수록 각진 모양이 됨

 

 

망치 웨펀 오브젝트로 돌아가서,

trail effect 칸에 아까 만든 트레일 렌더러 이펙트를 지정

 

 

설정 끝났으면 해머의

공격범위 박스 콜라이더, 트레일 렌더러 둘다 비활성화

 

 

 

+

 

 

코루틴은 유용하니 꼭 알아둘 것

 

 

 

Use() 메인루틴 -> Swing() 서브루틴 -> Use()메인루틴
코루틴 아니면 순차적 실행

Use() 메인루틴 + Swing() 코루틴

코루틴에선 동시에 실행

 

 

 

 

코루틴에선     yield 라는게 꼭 들어감

 

 

코루틴은 IEnumerator 으로 시작 함

 

 

 

 

 

        yield return new WaitForSeconds(0.1f); 는 0.1 초 대기하겠다는 뜻


 

        yield return 는 값을 적용 시킨다는 뜻

        yield break 는 이제 끝내고 싶다는 마침표 개념

 

 yield break는 맨 아래에 설정해야 함

맨 위에 놓으면 그 다음 이어지는 함수가 다 활성화 안되니 주의

 

 

 

 

StopCoroutine 은

실행 하는 도중에도 중단하고 아예 멈추는 함수

 

        StopCoroutine("Swing");
        StartCoroutine("Swing");

로직이 꼬이지 않게 일단 stop 부터 시작후 start 를 그 후에 배치

 

 

    IEnumerator Swing()
    {

        yield return new WaitForSeconds(0.1f); // 0.1초 대기
        meleeArea.enabled = true;
        trailEffect.enabled = true;

        yield return new WaitForSeconds(0.1f);
        meleeArea.enabled = false;

        yield return new WaitForSeconds(0.1f);
        trailEffect.enabled = false ;

        yield break;
    }

 

0.1초 지나고

공격범위 활성화

잔상효과 활성화

0.1초 지나고

공격범위 끔

0.1초 지나고

잔상효과 끔

이라는 

 

 

 

코루틴 이 아니라 인보크 를 이용하면 더 복잡해졌을 것임 

 

+

 

if (equipWeapon == null)
    return;

만약 equipWeapon이 null이라면, 아무 무기가 없다면

공격을 하지 않고 메서드를 종료합니다.

 

 

+

 

 

애니메이터 창, 파라메터 창, + 버튼 으로 트리거 하나 생성

이름은 doSwing

 

 

 

스왑 애니 만들듯이

저렇게 화살표 우클로 연결

 

위 화살표에선, 컨디션을 doSwap 으로 지정

 

아래 화살표에선 딱히 지정 할게 없음.

트랜지션 듀레이션은 0.1 정도로 취향것 설정

 

 

 

지금 껏 만든 거

e 키로 아이템 앞에 있는 루팅을 먹고

1 키로 아이템 망치 장착하고

마우스 왼클 (mouse 0) 로 망치 휘두름

 

 

 

 

플레이어 스크립트 풀 :

 

using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Rendering;

public class PlayerSC : MonoBehaviour
{
    public float speed;
    public GameObject[] weapons;
    public bool[] hasWeapons;

    public GameObject[] grenades;
    public int hasGrenades;

    public int ammo;
    public int coin;
    public int health;


    public int maxAmmo;
    public int maxCoin;
    public int maxHealth;
    public int maxHasGrenades;

    float hAxis;
    float vAxis;


    bool wDown;   // Walk를 위한 shift 꾹
    bool jDown;
    bool fDown; // 공격키
    bool iDown;

    bool sDown1;
    bool sDown2;
    bool sDown3;

    bool isJump;
    bool isDodge;
    bool isSwap;
    bool isFireReady = true;

    Vector3 moveVec;
    Vector3 dodgeVec;

    Rigidbody rigid;
    Animator anim;

    GameObject nearObject;
    Weapon equipWeapon;
    int equipWeaponIndex = 1;
    float fireDelay;

    void Awake()
    {
        rigid = GetComponent<Rigidbody>();
        anim = GetComponentInChildren<Animator>();
    }

    void Update()
    {
        GetInput();
        Move();
        Turn();
        Jump();
        Attack();
        Dodge();
        Swap();
        Interaction(); // 키보드 e 키 눌르면 무기 입수
    }

    void FixedUpdate()
    {
        Move();
    }

    void GetInput()
    {
        hAxis = Input.GetAxisRaw("Horizontal");
        vAxis = Input.GetAxisRaw("Vertical");
        wDown = Input.GetButton("Walk"); // GetButton은 꾹 누를 때만 적용
        jDown = Input.GetButtonDown("Jump");
        fDown = Input.GetButtonDown("Fire1");
        iDown = Input.GetButtonDown("Interaction");
        sDown1 = Input.GetButtonDown("Swap1");
        sDown2 = Input.GetButtonDown("Swap2");
        sDown3 = Input.GetButtonDown("Swap3");
    }

    void Move()
    {
        moveVec = new Vector3(hAxis, 0, vAxis).normalized;
        // Walk 걸을 때 속도 감소

        if (isDodge)
            moveVec = dodgeVec;

        if (isSwap  || !isFireReady)
            moveVec = Vector3.zero;

        if (wDown)
            transform.position += moveVec * speed * 0.2f * Time.deltaTime;
        else
            transform.position += moveVec * speed * 1.0f * Time.deltaTime;

        anim.SetBool("isRun", moveVec != Vector3.zero);
        anim.SetBool("isWalk", wDown);
    }

    void Turn()
    {
        // 회전 구현
        transform.LookAt(transform.position + moveVec);
    }

    void Jump()
    {
        // 제자리에서 점프는 점프
        if (jDown 
    && moveVec == Vector3.zero 
    && !isJump && !isDodge && !isSwap)
        {
            rigid.AddForce(Vector3.up * 7, ForceMode.Impulse);
            anim.SetBool("isJump", true);
            anim.SetTrigger("doJump");
            isJump = true;
        }
    }

    // 방향키 눌러서 점프는 닷지

    void Attack()
    {
        if (equipWeapon == null)
            return;
        fireDelay += Time.deltaTime;
        isFireReady = equipWeapon.rate < fireDelay;

        if ( fDown && isFireReady && !isDodge && !isSwap )
        {
            equipWeapon.Use();
            anim.SetTrigger("doSwing");
            fireDelay = 0;
        }

    }

    void Dodge()
    {
        if (jDown 
    && moveVec != Vector3.zero 
    && !isJump && !isDodge && !isSwap)
        {
            dodgeVec = moveVec;
            speed *= 1.5f;
            anim.SetTrigger("doDodge");
            isDodge = true;

            Invoke("DodgeOut", 0.5f);
        }
    }

    void DodgeOut()
    {
        speed /= 1.5f; // 원래 속도로 되돌리기
        isDodge = false;
    }

    void Swap()
{

        if (sDown1 && (!hasWeapons[0] || equipWeaponIndex == 0))
            return;
        if (sDown2 && (!hasWeapons[1] || equipWeaponIndex == 1))
            return;
        if (sDown3 && (!hasWeapons[2] || equipWeaponIndex == 2))
            return;

        int weaponIndex = -1;
        if (sDown1) weaponIndex = 0;
        if (sDown2) weaponIndex = 1;
        if (sDown3) weaponIndex = 2;

        if ((sDown1 || sDown2 || sDown3) && !isJump && !isDodge)
        {
            if (equipWeapon != null)
            equipWeapon.gameObject.SetActive(false);

            equipWeaponIndex = weaponIndex;
             equipWeapon = weapons[weaponIndex].GetComponent<Weapon>() ;
            equipWeapon.gameObject.SetActive(true);

            anim.SetTrigger("doSwap");

            isSwap = true;

            Invoke("SwapOut", 0.4f);

        }
    }

    void SwapOut()
    {
        isSwap = false;

    }



    void Interaction()
    {
        if (iDown && nearObject != null 
    && !isJump && !isDodge)
        {
            if (nearObject.tag == "Weapon")
            {
                Item item = nearObject.GetComponent<Item>();
                int weaponIndex = item.value;
                hasWeapons[weaponIndex] = true;

                Destroy(nearObject);
            }
        }
    }

    void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.tag == "Floor")
        {
            anim.SetBool("isJump", false);
            isJump = false;
        }
    }

    void OnTriggerEnter(Collider other)
    {
        if(other.tag == "Item")
     {
    Item item = other.GetComponent<Item>();
    switch(item.type) 
             { 
              case Item.Type.Ammo:
                  ammo += item.value;
                  if (ammo > maxAmmo) ammo = maxAmmo;
                  break;
              case Item.Type.Coin:
                  coin += item.value;
                  if (coin > maxCoin) coin = maxCoin;
                  break;
              case Item.Type.Heart:
                  health += item.value;
                  if (health > maxHealth) health = maxHealth;
                  break;
                case Item.Type.Grenade:
                if (hasGrenades < maxHasGrenades) 
                   grenades[hasGrenades].SetActive(true); 
                   hasGrenades += item.value;
                 if (hasGrenades == maxHasGrenades)
                    return;
                    break;
            }
           Destroy(other.gameObject);
        }

    }

    void OnTriggerStay(Collider other)
    {
        if (other.tag == "Weapon")
            nearObject = other.gameObject;
    }

    void OnTriggerExit(Collider other)
    {
        if (other.tag == "Weapon")
            nearObject = null;
    }
}

 

 

 

웨펀 스크립트 풀 :

 

using Febucci.UI.Effects;
using System.Collections;
using UnityEngine;


public class Weapon : MonoBehaviour
{
        
public enum Type { Melee, Range }; // 물리냐 원거리냐 
public Type type;
    public int damage; //공격 데미지
    public float rate;  //공격속도
    public BoxCollider meleeArea; //공격범위
    public TrailRenderer trailEffect; //잔상이펙트

   public void Use()
   {
       if (type == Type.Melee)
        { 
        StopCoroutine("Swing");
        StartCoroutine("Swing"); 

        }

   }

    IEnumerator Swing()
    {
// 애니메이션과 잘 안 맞으면 이 아래 부분을 조정

        yield return new WaitForSeconds(0.12f); // 0.1초 대기
        meleeArea.enabled = true;
        trailEffect.enabled = true;

        yield return new WaitForSeconds(0.1f);
        meleeArea.enabled = false;

        yield return new WaitForSeconds(0.1f);
        trailEffect.enabled = false;

        // Trail Renderer 리셋
    ResetTrailRenderer(trailEffect); 
    yield break; 
    } 

void ResetTrailRenderer
(TrailRenderer trail)
{ trail.Clear(); }
    



}

 

 

 

골드메탈님 동영상에선 안 나왔지만,

아래 처럼

스크립트 마지막 부분에서

트레일 렌더러를 리셋해야 깔끔하게 재생이 됨

아래 안 하면 끝이 다음 시작때 남는 지저분한 이펙트가 되버림

 

    // Trail Renderer 리셋
    ResetTrailRenderer(trailEffect); 
    yield break; 
    }

    // Trail Renderer 리셋
    void ResetTrailRenderer
(TrailRenderer trail)
{ trail.Clear(); }
반응형
Posted by 이름이 익명
: