기록 보관소

[Unity/유니티] 기초-3D 쿼터뷰 액션 게임: 원거리 공격 구현[B44] 본문

유니티 프로젝트/3D 쿼터뷰 액션게임

[Unity/유니티] 기초-3D 쿼터뷰 액션 게임: 원거리 공격 구현[B44]

JongHoon 2022. 3. 26. 23:53

개요

유니티 입문과 독학을 위해서 아래 링크의 골드메탈님의 영상들을 보며 진행 상황 사진 또는 캡처를 올리고 배웠던 점을 요약해서 적는다.

현재는 영상들을 보고 따라하고 배우는 것에 집중할 것이며, 영상을 모두 보고 따라한 후에는 개인 프로젝트를 설계하고 직접 만드는 것이 목표다.

https://youtube.com/playlist?list=PLO-mt5Iu5TeYI4dbYwWP8JqZMC9iuUIW2 

 

유니티 강좌 기초 채널 Basic

유니티 개발을 처음 시작하시는 입문자 분들을 위한 기초 채널. [ 프로젝트 ] B00 ~ B12 (BE1) : 유니티 필수 기초 B13 ~ B19 (BE2) : 2D 플랫포머 B20 ~ B26 (BE3) : 2D 탑다운 대화형 RPG B27 ~ B37 (BE4) : 2D 종스크롤

www.youtube.com


3D 쿼터뷰 액션 게임: 원거리 공격 구현[B44]

1. 총알, 탄피 만들기

권총 총알 효과로 쓸 Empty 생성
Trail Renderer 컴포넌트 추가 및 설정
Rigidbody와 SphereCollider 추가 및 설정

  • 개인적으로 총알은 Use Gravity 체크를 빼는 것이 좋을 것 같아서 따로 빼주었다.

Bullet HandGun을 복사해서 Bullet SubMachineGun으로 수정해준다
총알 설정이 끝났으니 탄피 프리펩 추가
크기를 조금 줄여주고
Rigidbody와 Box Collider를 추가하고 설정한다
Bullet 스크립트 파일 생성

//Bullet 스크립트 파일

using System.Collections;
using System.Collections.Generic;
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);
	}
}

총알들과 탄피에 Bullet 스크립트 파일 추가하고 데미지를 설정해준다
완성된 총알과 탄피는 프리펩으로 만들어준다
벽 태그 설정이 안되어 있어서 미리 바꿔주었다


2. 발사 구현

Animator에 Shot 애니메이션 추가
Transition 추가 및 설정

  • Swing 등의 이전에 설정했던 애니메이션과 같다. doShot Trigger형 매개변수를 추가하고, AnyState -> Shot Transition을 위 캡처처럼 Has Exit Time 체크 없이, Transition Duration을 0으로 설정해준다. Shot -> Exit는 Transition Duration을 0.1로만 설정해주면 끝.

총알 발사 위치를 저장할 Empty 생성 및 설정
권총의 탄피 생성 위치를 저장할 Empty 생성 및 설정
기관총의 탄피 생성 위치를 저장할 Empty 생성 및 설정

//Player 스크립트 파일

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

public class Player : 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;
    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;

    Animator anim;
    Rigidbody rigid;

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

    void Awake() {
        rigid = GetComponent<Rigidbody>();
        anim = GetComponentInChildren<Animator>();  //Player 자식 오브젝트에 있으므로
    }

    void Update() {
        GetInput();
        Move();
        Turn();
        Jump();
		Attack();
		Dodge();
		Swap();
		Interation();
    }

    void GetInput() {
        hAxis = Input.GetAxisRaw("Horizontal");	//좌우 방향키
        vAxis = Input.GetAxisRaw("Vertical");	//상하 방향키
        wDown = Input.GetButton("Walk");	//shift 키
		jDown = Input.GetButtonDown("Jump");	//스페이스바
		fDown = Input.GetButton("Fire1");	//마우스 왼쪽 클릭
		iDown = Input.GetButtonDown("Interation");	//E키
		sDown1 = Input.GetButton("Swap1");	//번호 1번 키
		sDown2 = Input.GetButton("Swap2");	//번호 2번 키
		sDown3 = Input.GetButton("Swap3");	//번호 3번 키
	}

    void Move() {
        moveVec = new Vector3(hAxis, 0, vAxis).normalized;  //normalized : 방향 값이 1로 보정된 벡터

		if (isDodge)	//회피 중일때는
			moveVec = dodgeVec; //회피하는 중인 방향으로 유지

		if (isSwap || !isFireReady)	//무기 교체 또는 무기 공격 중일때는
			moveVec = Vector3.zero;	//멈추기

        transform.position += moveVec * speed * (wDown ? 0.3f : 1f) * 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 * 15, 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 Dodge() {
		if (jDown && (moveVec != Vector3.zero) && !isJump && !isDodge && !isSwap) {    //이동하면서 점프
			dodgeVec = moveVec;
			speed *= 2;
			anim.SetTrigger("doDodge");
			isDodge = true;

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

	void DodgeOut() {
		speed *= 0.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 Interation() {
		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)
						return;

					grenades[hasGrenades].SetActive(true);
					hasGrenades += item.value;
					if (hasGrenades > maxHasGrenades)
						hasGrenades = maxHasGrenades;
					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;
	}
}
//Weapon 스크립트 파일

using System.Collections;
using System.Collections.Generic;
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 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) {
			StartCoroutine("Shot");
		}
	}

	IEnumerator Swing() {
		yield return new WaitForSeconds(0.1f);	//0.1초 대기
		meleeArea.enabled = true;
		trailEffect.enabled = true;
		
		yield return new WaitForSeconds(0.3f);  //0.3초 대기
		meleeArea.enabled = false;

		yield return new WaitForSeconds(0.3f);  //0.3초 대기
		trailEffect.enabled = false;
	}

	IEnumerator Shot() {
		//총알 발사
		GameObject intantBullet = Instantiate(bullet, bulletPos.position, bulletPos.rotation);
		Rigidbody bulletRigid = intantBullet.GetComponent<Rigidbody>();
		bulletRigid.velocity = bulletPos.forward * 50;
		yield return null;  //1프레임 대기

		//탄피 배출
		GameObject intantCase = Instantiate(bulletCase, bulletCasePos.position, bulletCasePos.rotation);
		Rigidbody caseRigid = intantCase.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);	//회전
	}
}

권총에 변수 넣어주기
기관총에 변수 넣어주기
테스트를 위해 아이템을 두고 실행
권총 발사. 총알이 벽에 닿자 사라졌다
기관총 발사. 훨씬 빨리 발사되고, 마찬가지로 벽에 닿으니 사라졌다


3. 재장전 구현

재장전을 사용하기위해 R키를 Input Manager에 추가
Reload 애니메이션 추가
Transition 설정

//Weapon 스크립트 파일

using System.Collections;
using System.Collections.Generic;
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--;
			StartCoroutine("Shot");
		}
	}

	IEnumerator Swing() {
		yield return new WaitForSeconds(0.1f);	//0.1초 대기
		meleeArea.enabled = true;
		trailEffect.enabled = true;
		
		yield return new WaitForSeconds(0.3f);  //0.3초 대기
		meleeArea.enabled = false;

		yield return new WaitForSeconds(0.3f);  //0.3초 대기
		trailEffect.enabled = false;
	}

	IEnumerator Shot() {
		//총알 발사
		GameObject intantBullet = Instantiate(bullet, bulletPos.position, bulletPos.rotation);
		Rigidbody bulletRigid = intantBullet.GetComponent<Rigidbody>();
		bulletRigid.velocity = bulletPos.forward * 50;
		yield return null;  //1프레임 대기

		//탄피 배출
		GameObject intantCase = Instantiate(bulletCase, bulletCasePos.position, bulletCasePos.rotation);
		Rigidbody caseRigid = intantCase.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);	//회전
	}
}
//Player 스크립트 파일

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

public class Player : 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;
    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;

    Animator anim;
    Rigidbody rigid;

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

    void Awake() {
        rigid = GetComponent<Rigidbody>();
        anim = GetComponentInChildren<Animator>();  //Player 자식 오브젝트에 있으므로
    }

    void Update() {
        GetInput();
        Move();
        Turn();
        Jump();
		Attack();
		Reload();
		Dodge();
		Swap();
		Interation();
    }

    void GetInput() {
        hAxis = Input.GetAxisRaw("Horizontal");	//좌우 방향키
        vAxis = Input.GetAxisRaw("Vertical");	//상하 방향키
        wDown = Input.GetButton("Walk");	//shift 키
		jDown = Input.GetButtonDown("Jump");	//스페이스바
		fDown = Input.GetButton("Fire1");   //마우스 왼쪽 클릭
		rDown = Input.GetButtonDown("Reload");	//R키
		iDown = Input.GetButtonDown("Interation");	//E키
		sDown1 = Input.GetButton("Swap1");	//번호 1번 키
		sDown2 = Input.GetButton("Swap2");	//번호 2번 키
		sDown3 = Input.GetButton("Swap3");	//번호 3번 키
	}

    void Move() {
        moveVec = new Vector3(hAxis, 0, vAxis).normalized;  //normalized : 방향 값이 1로 보정된 벡터

		if (isDodge)	//회피 중일때는
			moveVec = dodgeVec; //회피하는 중인 방향으로 유지

		if (isSwap || isReload || !isFireReady)	//무기 교체 또는 무기 공격 중일때는
			moveVec = Vector3.zero;	//멈추기

        transform.position += moveVec * speed * (wDown ? 0.3f : 1f) * 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 * 15, 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 && !isReload) {
			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 && !isJump && !isDodge && !isSwap && isFireReady) {
			anim.SetTrigger("doReload");
			isReload = true;

			Invoke("ReloadOut", 3);
		}
	}

	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 *= 2;
			anim.SetTrigger("doDodge");
			isDodge = true;

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

	void DodgeOut() {
		speed *= 0.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 Interation() {
		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)
						return;

					grenades[hasGrenades].SetActive(true);
					hasGrenades += item.value;
					if (hasGrenades > maxHasGrenades)
						hasGrenades = maxHasGrenades;
					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;
	}
}

권총 Ammo 값 설정
기관총 Ammo 값 설정
총알을 쏘니 curAmmo 값이 줄어드는 모습
임의로 Ammo값 주기
R키를 누르니 장전을 한다
다시 총알이 채워졌다


4. 마우스 회전

//Player 스크립트 파일

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

public class Player : 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;
    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;

    Animator anim;
    Rigidbody rigid;

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

    void Awake() {
        rigid = GetComponent<Rigidbody>();
        anim = GetComponentInChildren<Animator>();  //Player 자식 오브젝트에 있으므로
    }

    void Update() {
        GetInput();
        Move();
        Turn();
        Jump();
		Attack();
		Reload();
		Dodge();
		Swap();
		Interation();
    }

    void GetInput() {
        hAxis = Input.GetAxisRaw("Horizontal");	//좌우 방향키
        vAxis = Input.GetAxisRaw("Vertical");	//상하 방향키
        wDown = Input.GetButton("Walk");	//shift 키
		jDown = Input.GetButtonDown("Jump");	//스페이스바
		fDown = Input.GetButton("Fire1");   //마우스 왼쪽 클릭
		rDown = Input.GetButtonDown("Reload");	//R키
		iDown = Input.GetButtonDown("Interation");	//E키
		sDown1 = Input.GetButton("Swap1");	//번호 1번 키
		sDown2 = Input.GetButton("Swap2");	//번호 2번 키
		sDown3 = Input.GetButton("Swap3");	//번호 3번 키
	}

    void Move() {
        moveVec = new Vector3(hAxis, 0, vAxis).normalized;  //normalized : 방향 값이 1로 보정된 벡터

		if (isDodge)	//회피 중일때는
			moveVec = dodgeVec; //회피하는 중인 방향으로 유지

		if (isSwap || isReload || !isFireReady)	//무기 교체 또는 무기 공격 중일때는
			moveVec = Vector3.zero;	//멈추기

        transform.position += moveVec * speed * (wDown ? 0.3f : 1f) * 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 * 15, 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 && !isReload) {
			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 && !isJump && !isDodge && !isSwap && isFireReady) {
			anim.SetTrigger("doReload");
			isReload = true;

			Invoke("ReloadOut", 3);
		}
	}

	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 *= 2;
			anim.SetTrigger("doDodge");
			isDodge = true;

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

	void DodgeOut() {
		speed *= 0.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 Interation() {
		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)
						return;

					grenades[hasGrenades].SetActive(true);
					hasGrenades += item.value;
					if (hasGrenades > maxHasGrenades)
						hasGrenades = maxHasGrenades;
					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;
	}
}

Follow Camera 변수 추가
키보드로 이동
마우스 클릭으로 플레이어 방향 돌리기
빙글빙글 돌면서 쏘기도 가능하다

  • Ray Camera.ScreenPointToRay(Vector3 position) : 스크린에서 월드로 Ray를 쏘는 함수
  • out : return처럼 반환값을 주어진 변수에 저장하는 키워드