카테고리 없음

[Unity Associate Programmer 자격증 준비] 4강. 게임 플레이 메카닉

ibelieveinme 2023. 5. 27. 15:53
728x90
 

4.1강 - 시점 설정 - Unity Learn

개요: 먼저 새 프로토타입을 만들고 시작 파일을 다운로드합니다. 아름다운 섬과 하늘, 파티클 효과 등 다양한 에셋이 있으며, 모두 커스터마이즈할 수 있습니다. 이제 플레이어가 섬을 중심으

learn.unity.com


1. 카메라 초점 생성

카메라의 초첨을 씬 중앙에 맞춰놓고 편리하게 이동 시키기 위해서 빈 오브젝트를 만들고 그 오브젝트 안에 넣고 빈 오브젝트를 회전시키면 편하다. 더욱 섬세한 동작을 위해 스크립트를 추가해도 좋다.

using UnityEngine;

public class RotateCamera : MonoBehaviour{

    [SerializeField] private float rotationSpeed = 5;

    void Update(){
        float horizontalInput = Input.GetAxis("Horizontal");
        transform.Rotate(Vector3.up, horizontalInput * rotationSpeed * Time.deltaTime);
    }
}

위 스크립트는 사용자의 좌우 방향키 입력에 따라 빈 오브젝트를 회전시켜서 카메라를 회전시킨다.

힘을 곱해주지 않으면 움직이지 않으므로 rotationSpeed 값을 올려준다.

 

2. Player 움직이기

using UnityEngine;

public class RotateCamera : MonoBehaviour{

    [SerializeField] private float rotationSpeed = 5;

    void Update(){
        float horizontalInput = Input.GetAxis("Horizontal");
        transform.Rotate(Vector3.up, horizontalInput * rotationSpeed * Time.deltaTime);
    }
}

Input.GetAxis()를 이용해서 물체를 움직여준다.

이 때, 움직일 물체가 공이므로 Translate가 아닌 Rotate 함수로 움직여준다 !

 

3. Player를 카메라 방향으로 회전이동시키기

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    [SerializeField] private float speed = 5.0f;
    [SerializeField] private GameObject focalPoint;

    void Update() {
        float forwardInput = Input.GetAxis("Vertical");
        transform.GetComponent<Rigidbody>().AddForce(focalPoint.transform.forward * forwardInput * speed);
    }
}

 

4. Physics Material 적용하기

*정의: Physics Material은 충돌하는 오브젝트의 마찰 또는 바운스 효과를 조정하는 데 사용한다.

*만드는법: 마우스 오른쪽 버튼 > Create > Physics Material로 만들 수 있다.

 

*속성:

1) Dynamic Friction: 이미 움직이고 있을 때 사용되는 마찰. 0과 1사이의 값. 0이면 얼음처럼 미끄러워지고, 1이면 오브젝트가 큰 힘이나 중력에 미리지 않는 한 아주 빨리 정지함.

2) Static Fiction: 오브젝트가 표면 위에 가만히 놓여 있을 때 사용되는 마찰. 일반적으로 0~1의 값. 0이면 얼음처럼 미끄러워지고 1이면 오브젝트를 움직이기가 매우 힘들어짐.

3) Bounciness: 탄력성. 0~1의 값. 0이면 물체가  bounce 하지 않고, 1이면 에너지 손실 없이 bounce 함.

4) Friction Combine: 충돌하는 두 오브젝트의 마찰이 합쳐지는 방법.

5) Bounce Combine: 충돌하는 두 오브젝트의 탄성을 합치는 방법.

    - Average: 두 마찰 값의 평균이 사용됨

    - Minumum: 두 값 중 작은 값이 사용됨

    - Multiply: 두 값을 곱해서 사용됨

    - Maximum: 두 값중 큰 값이 사용됨

*적용방법: Bouncy라는 이름으로 만든 Physics Material을 Collider가 달려있는 오브젝트의 Material에 추가.

 

통통

5. Player를 따라가는 Enemy 만들기

using UnityEngine;

public class Enemy : MonoBehaviour {

    [SerializeField] private float speed = 3f;
    private Rigidbody enemyRb;
    private GameObject player;

    private void Start(){
        enemyRb = GetComponent<Rigidbody>();
        player = GameObject.Find("Player");
    }

    void Update(){
        enemyRb.AddForce((player.transform.position - transform.position).normalized * speed);
    }
}

Enemy 객체에 동작을 부여할 Enemy.cs 파일을 만들어서 연결해준다.

 

Enemy 스크립트에 필요한 변수 3가지

1) Enemy 객체가 움직이는 speed 값

2) Enemy 객체를 움직이게 할 Rigidbody 컴포넌트

3) 따라갈 Player의 현재위치 값이 필요하다

 

Player의 현재 위치 - Enemy의 현재 위치를 빼서 Enemy의 이동 방향을 찾아주고, 거기에 속도를 곱해서 Enemy 객체에 힘을 가준다. 

 

이 때, (Player의 현재 위치 - Enemy의 현재 위치) * speed 값만 해주면 Player와 Enemy 사이의 거리가 멀어질수록 Enemy 객체에 가해지는 힘이 더 강해지게 된다. 이런 문제 막고 일정한 힘을 가해서 일정 속도를 보장하기 위해 normalized 처리해준다.

using UnityEngine;

public class Enemy : MonoBehaviour {

    [SerializeField] private float speed = 3f;
    private Rigidbody enemyRb;
    private GameObject player;

    private void Start(){
        enemyRb = GetComponent<Rigidbody>();
        player = GameObject.Find("Player");
    }

    void Update(){
        Vector3 lookDirection = (player.transform.position - transform.position).normalized;
        enemyRb.AddForce(lookDirection * speed);
    }
}

한줄이 조금 기니까 변수로 빼서 정리한 것.

 

6. Enemy 생성 관리자 만들기

using UnityEngine;

public class SpawnManager : MonoBehaviour {

    [SerializeField] private GameObject enemyPrefab;
    private float spawnRange = 9;

    void Start(){
        //적 객체를 복제하는 코드
        Instantiate(enemyPrefab, GenerateSpawnPosition(), enemyPrefab.transform.rotation);
    }

    //random 생성위치를 반환하는 함수
    private Vector3 GenerateSpawnPosition(){
        float spawnPosX = Random.Range(-spawnRange, spawnRange);
        float spawnPosZ = Random.Range(-spawnRange, spawnRange);
        Vector3 randomPos = new Vector3(spawnPosX, 0, spawnPosZ);
        return randomPos;
    }
}

Random.Range 함수를 이용해서 -9 ~  9 중에 random X, Z 위치값을 생성하여 해당 위치에 Enemy 객체를 생성한다.

코드 간결 & 직관성을 위해  함수로 빼서 정리한 모습이다.

 

7. 재미를 위한 요소 추가. Player 파워업 아이템 추가.

PowerUp 객체와 충돌했을 때, 어떤 동작을 수행하기 위해 Box Collider 컴포넌트를 추가해준다.

PowerUP 객체와 충돌하고 부딪히는 효과 없이 습득하기 위해 Is Trigger를 체크해준다.

using UnityEngine;

public class PlayerController : MonoBehaviour {

    private bool hasPowerUp = false;
    
    private void OnTriggerEnter(Collider other) {
        if (other.CompareTag("PowerUp")){
            hasPowerUp = true;
            Destroy(other.gameObject);
        }
    }
}

PowerUP 객체에 PowerUp 태그를 만들어서 추가해주고,

PlayerController 클래스에는 PowerUp 객체와 만났을 때를 체크하기 위해 OnTriggerEnter(Collider other) 함수를 만든다.

 

using UnityEngine;

public class PlayerController : MonoBehaviour {
    private float powerupStrength = 15.0f;

    private void OnCollisionEnter(Collision collision){
        if (collision.gameObject.CompareTag("Enemy") && hasPowerUp){
            Rigidbody enemyRigidbody = collision.gameObject.GetComponent<Rigidbody>();
            Vector3 awayFromPlayer = collision.gameObject.transform.position - transform.position;

            enemyRigidbody.AddForce(awayFromPlayer * powerupStrength, ForceMode.Impulse);
        }
    }
}

Enemy 객체에도 Enemy 태그를 만들어서 추가해주고,

PlayerController 클래스에 Enemy 객체와 만났을 때 처리해주기 위해 OnCollisionEnter(Collision collision) 함수를 만든다.

powerUp 객체를 습득한 후, Enemy 객체와 충돌했을 때 반대로 즉시 튕겨 나간다.

 

8. Player 파워업 타이머/표시기 추가

using System.Collections;
using UnityEngine;

public class PlayerController : MonoBehaviour {
    [SerializeField] private GameObject powerUpIndicator;
    private bool hasPowerUp = false;

    private void OnTriggerEnter(Collider other) {
        if (other.CompareTag("PowerUp")){
            hasPowerUp = true;
            Destroy(other.gameObject);
            //powerUpContdownRoutine 코루틴 시작
            StartCoroutine(powerUpCountdownRoutine());
        }
    }

    IEnumerator powerUpCountdownRoutine(){
        powerUpIndicator.SetActive(true);

        //7초간 기다렸다가 아래 문장 수행
        yield return new WaitForSeconds(7);

        hasPowerUp = false;
        powerUpIndicator.SetActive(false);
    }
}

powerUpCountdownRoutine() 코루틴을 생성해주고 코루틴 yield return WaitForSeconds(지연시간) 코드 앞 뒤로 powerUpIndicator 객체를 켜고 꺼준다.

 

9. 시간경과에 따라 다수의 Enemy와 PowerUP 객체 생성

using UnityEngine;

public class Enemy : MonoBehaviour {

    [SerializeField] private float speed = 3f;
    private Rigidbody enemyRb;
    private GameObject player;

    private void Start(){
        enemyRb = GetComponent<Rigidbody>();
        player = GameObject.Find("Player");
    }

    void Update(){
        Vector3 lookDirection = (player.transform.position - transform.position).normalized;
        enemyRb.AddForce(lookDirection * speed);

        //아래로 떨어지면 Enemy 객체 파괴
        if (transform.position.y < -10) Destroy(gameObject);
    }
}

Enemy 클래스의 Update() 문에 position.y 값이 -10 이하이면 객체를 파괴시키는 코드를 추가하고,

 

using UnityEngine;

public class SpawnManager : MonoBehaviour {

    [SerializeField] private GameObject enemyPrefab;
    private float spawnRange = 9;
    private int enemyCount;
    private int waveNumber = 1;

    void Start() {
        SpawnEnemyWave(waveNumber);
    }

    void Update() {
        enemyCount = FindObjectsOfType<Enemy>().Length;
        if (enemyCount == 0) {
            waveNumber++;
            SpawnEnemyWave(waveNumber);
        }
    }

    private void SpawnEnemyWave(int enemiesToSpawn) {
        //적 객체를 복제하는 코드
        for (int i = 0; i < enemiesToSpawn; i++) {
            Instantiate(enemyPrefab, GenerateSpawnPosition(), enemyPrefab.transform.rotation);
        }
    }

    //random 생성위치를 반환하는 함수
    private Vector3 GenerateSpawnPosition(){
        float spawnPosX = Random.Range(-spawnRange, spawnRange);
        float spawnPosZ = Random.Range(-spawnRange, spawnRange);
        Vector3 randomPos = new Vector3(spawnPosX, 0, spawnPosZ);
        return randomPos;
    }
}

SpawnManager 클래스에서 Enemy 객체 개수를 세서 0개가 되면 생성해주는 코드를 추가한한다. Enemy 객체 생성개수는 1개부터 All Clear 할 때마다 1씩 늘려준다.

 

enemyCount = FindObjectsOfType<Enemy>().Length;

 

이 때, FindObjectsType<객체이름>() 함수가 해당 객체이름을 가진 객체 배열을 반환하므로 enemyCount라는 정수로 변환하기 위해서는 배열의 Length를 호출해줘야 한다.

 

using UnityEngine;

public class SpawnManager : MonoBehaviour {

    [SerializeField] private GameObject enemyPrefab;
    [SerializeField] private GameObject powerUpPrefab;

    private float spawnRange = 9;
    private int enemyCount;
    private int waveNumber = 1;

    void Start() {
        SpawnEnemyWave(waveNumber);
        SpawnPowerUpWave();
    }

    void Update() {
        enemyCount = FindObjectsOfType<Enemy>().Length;
        if (enemyCount == 0) {
            waveNumber++;
            SpawnEnemyWave(waveNumber);
            SpawnPowerUpWave();
        }
    }

    private void SpawnEnemyWave(int enemiesToSpawn) {
        //적 객체를 복제하는 코드
        for (int i = 0; i < enemiesToSpawn; i++) {
            Instantiate(enemyPrefab, GenerateSpawnPosition(), enemyPrefab.transform.rotation);
        }
    }
    private void SpawnPowerUpWave() {
        Instantiate(powerUpPrefab, GenerateSpawnPosition(), powerUpPrefab.transform.rotation);
    }

    //random 생성위치를 반환하는 함수
    private Vector3 GenerateSpawnPosition(){
        float spawnPosX = Random.Range(-spawnRange, spawnRange);
        float spawnPosZ = Random.Range(-spawnRange, spawnRange);
        Vector3 randomPos = new Vector3(spawnPosX, 0, spawnPosZ);
        return randomPos;
    }
}

PowerUp 아이템은 생성시와 Enemy 클리어 때마다 1개씩만 생성해준다.

 

728x90