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);
}
}
}
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(); }
}