Terrain Tools

Pakage Manager - Unity Registry - Terrain Tools를 검색하면 다운로드 할 수 있다.

단축키

- A : 속도

- S : 사이즈

- D : 회전

Terrain Tools

Sculpt : Ctrl + 좌클릭으로 시작 지점 다른 편에 좌클릭으로 수행

- Bridge - 다리

Bridge
Bridge 2

- Colne - 지형 복사

Clone

- Noise : 울퉁불퉁하게 만듬

Erosion

- Wind : 바람이 쓸고 지나간듯이 지형을 깎음

아래의 Stroke에서 Jitter는 무작위성을 나타낸다.

Jitter

Smooth

- Height : 평평해짐

Smooth Height

Transform

- Pinch : 뽀족하게 만듬

Transform Pinch

- Smudge : 옆으로 밀어버림

Smudge

- Twist : 회오리바람 모양이 생김

Twist

Paint Holes : 구멍 생김 - 동굴, 터널 만들기

Paint Holes

'유데미 강의 > C#과 Unity로 3D 게임 개발하기 : 아르곤 침공' 카테고리의 다른 글

Input Manager와 Input System  (0) 2022.08.25
Timeline  (0) 2022.08.25
Terrain Texture, Tree  (0) 2022.08.25
Terrain  (0) 2022.08.25
아르곤 침공 게임 디자인  (0) 2022.08.25

Terrain

Terrain으로 지형을 생성하고 꾸밀 수 있다.

Terrain

맨 우측 Terrain Settings에서 Terrain에 대한 설정을 할 수 있다.

- 너비, 길이, 높이 조절 가능하다.

Terrain Settings
Resolution

Paint Terrain에서 Terrain의 모양을 꾸밀 수 있다.

Paint 모드

- 좌클릭시 모양에 따라 Terrain이 그려지고

- Shift + 좌클릭시 지워진다.

- Brush Size : 붓 크기 (단축키 -> 키우기: ] / 줄이기: [ )

- Opacity : 그려지는 속도를 조절한다. (값이 작을 수록 조금씩 그려짐)

Paint Terrain

Terrain 작업을 할 땐 좌측 아래를 기준점으로 잡고 시작하는 게 좋다.

Terrain 위젯

Set Height 모드

- Height에서 값을 조정하여 최대 높이에 제한을 둘 수 있다.

- Flatten All을 통해 전체 Terrain의 높이를 설정할 수 있다. (평평해짐)

Set Height
Set Height 2

- Flatten All과 Terrain Settings의 최대 높이 설정을 통해 아래 깊이와 높이의 제한을 설정할 수 있다.

ex) 아래로 100 위로 600

깊이 설정

Terrain을 깊이만큼 내려서(-높이 y축 만큼) (0,0,0)에서 작업해주면 좋다.

기초 설정

추가로 마우스 우클릭 + W,A,S,D로 이동할 때 카메라 이동이 점점 빨라져서 불편하다면 씬의 카메라 아이콘을 클릭하여 Camera Acceleration을 끄면 된다.

카메라 가속 끄기

'유데미 강의 > C#과 Unity로 3D 게임 개발하기 : 아르곤 침공' 카테고리의 다른 글

Input Manager와 Input System  (0) 2022.08.25
Timeline  (0) 2022.08.25
Terrain Texture, Tree  (0) 2022.08.25
Terrain Tools  (0) 2022.08.25
아르곤 침공 게임 디자인  (0) 2022.08.25

아르곤 침공

플레이어 경험

- 혼돈

핵심 메커니즘

- 피하고 쏘기

핵심 게임 루프

- 가능한 높은 점수를 얻기 위해서 최대한 멀리 가고 죽으면 처음부터 다시 시작하기

 

게임 테마

행성 Argon을 침공한 적들이 행성을 파괴하지 못하도록 지키는 것

 

요소들 - 우선순위

1. 카메라 레일 : 레벨에 경로가 있고 카메라가 따라가는 것 - 시네머신 사용

2. 플레이어 움직임 : 수평, 수직 움직임

3. 쏘기 : 플레이어가 상대편에 피해를 입히는 총알을 발사한다.

4. 체력 : 적들에게 체력 추가 - 총알을 맞았을 때 체력 줄어듬

5. 적 경로 : 적들은 정해진 경로를 따라서 이동 - 디자이너가 배치한 경로

6. 점수 : 적들을 죽여서 얻는 점수

7. 게임 루프 : 죽으면 레벨을 재시작한다.

 

고려 해볼만한 것들

- 다중 레벨

- 플레이어 쉴드

- 아이템

- 피격시 일시적 무적 기능

- 무기 업그레이드

 

 

'유데미 강의 > C#과 Unity로 3D 게임 개발하기 : 아르곤 침공' 카테고리의 다른 글

Input Manager와 Input System  (0) 2022.08.25
Timeline  (0) 2022.08.25
Terrain Texture, Tree  (0) 2022.08.25
Terrain Tools  (0) 2022.08.25
Terrain  (0) 2022.08.25

게임 디자인 접근법

순간(moment)을 디자인 하고 레벨로 확장시킨다 -> moment들을 조합하기

ex) 주변 환경 활용 moment - Fly under, Fly over, Fly through a gap(틈 사이로 날아가기), 타이밍 맞춰서 날아가기, 움직이는 플랫폼에 착륙하기, 좁은 터널을 지나가기

ex2) 이미 게임에 있는 요소 활용 - 특정 레벨에서 로켓을 느리게하기(손상된 상태 - 왼쪽 회전만 된다든지), 부스터 아이템, 더 어두운 레벨, 밝은 레벨, 카메라 가까운 레벨, 더 큰 로켓, 조작키를 반대로 하기 등등

 

어플리케이션 종료

Application.Quit();는 빌드된 상태일 때 어플리케이션을 종료하는 함수이다.

Application.Quit();

 

어플리케이션 빌드

File - BuildSettings - Build 선택

Build
build된 모습
빌드 후 플레이

 

코드로 장애물 움직이기

양 옆으로 왔다 갔다 하는 장애물을 만들 예정이다.

[SerializeField]하고 [Range( , )]를 flaot 값 앞에 붙이면 인스펙터에 슬라이더처럼 생긴다.

[Range(,)]
슬라이더

 

Sine

Sin은 펜으로 나무디스크를 돌 때 균일한 속도로 종이가 움직이며 생긴 파형처럼 생성된다.

- 주기 : x축으로 다시 같은 지점까지 올 때까지의 크기 (걸리는 시간)

- 진폭 : y축으로 중앙부터 꼭짓점까지의 크기

SIn Cos

Tau

반지름이 1인 원에서 반지름을 길이 1의 호로 나타냈을 때의 각도가 radian이다. 여기서 3.14~~radian이 ㅠ(파이)고 6.28~~radian T(타우)다.  

타우

 

Nan 오류

어떠한 값을 0으로 나누려 할 때 뜬다.

NaN

float 값을 0과 비교해야할 일이 있을 때 Mathf.Epsilon을 사용한다.

Mathf.Epsilon

 

Oscillator.cs

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

public class Oscillator : MonoBehaviour
{
    private Vector3 startingPosition;
    
    [SerializeField] private float period = 2f;
    
    [SerializeField] private Vector3 movementVector;
    private float movementFactor;
    void Start()
    {
        startingPosition = transform.position;
        Debug.Log(startingPosition);
    }

    void Update()
    {
        //if (period <= 0) return; 
        if (period == Mathf.Epsilon) return; // float 같은 소수에 0과 비교할 때 사용 
        float cycles = Time.time / period; // 시간에 따라 계속 증가
        
        const float tau = Mathf.PI * 2; // 6.283 일정한 값
        float rawSinWave = Mathf.Sin(cycles * tau); // -1~1

        movementFactor = (rawSinWave + 1f) / 2; // 0~2 -> 0~1
        
        Vector3 offset = movementVector * movementFactor;
        transform.position = startingPosition + offset;
    }
}

 

 

플레이 영상

잘 움직이는 모습

플레이 영상

 

치트키

L을 누르면 다음 레벨로 가게, C를 누르면 충돌 효과를 안 받게 하도록 하였다.

bool 타입은 아래처럼 사용하여 꺼져있으면 키고 켜져있으면 끄게 사용 가능하다.

toggle

 

CollisionHandler.cs

using System;
using UnityEngine;
using UnityEngine.SceneManagement;

public class CollisionHandler : MonoBehaviour
{
    private bool isTransitioning = false;
    private bool collisionDisabled = false;
    
    private AudioSource _audioSource;
    private Movement _movement;
    private Collider _collider;
    
    [SerializeField] float levelLoadDelay = 1f;

    [SerializeField] private AudioClip crashSound;
    [SerializeField] private AudioClip landingSound;
    [SerializeField] private ParticleSystem crashParticle;
    [SerializeField] private ParticleSystem landingParticle;

    void Start()
    {
        _audioSource = GetComponent<AudioSource>();
        _movement = GetComponent<Movement>();
        _collider = GetComponent<Collider>();
    }

    void Update()
    {
        RespondToDebugKeys();
    }
    
    void RespondToDebugKeys()
    {
        if (Input.GetKeyDown(KeyCode.L))
        {
            LoadNextLevel();
        }
        else if (Input.GetKeyDown(KeyCode.C))
        {
            collisionDisabled = !collisionDisabled; // toggle collision (true면 false, false면 true)
        }
    }

    void OnCollisionEnter(Collision collision)
    {
        if (isTransitioning || collisionDisabled)
            return;

        switch (collision.gameObject.tag)
        {
            case "Friendly":
                Debug.Log("This thing is firendly");
                break;
            case "Finish":
                StartSuccessSequence();
                break;
            default:
                StartCrashSequence();
                break;
        }
    }

    void StartCrashSequence()
    {
        isTransitioning = true;
        _audioSource.Stop();
        PlaySound(crashSound);
        crashParticle.Play();
        _movement.enabled = false;
        Invoke(nameof(ReloadLevel), levelLoadDelay);
    }

    void LoadNextLevel()
    {
        int currentSceneIndex = SceneManager.GetActiveScene().buildIndex;
        int nextSceneIndex = currentSceneIndex + 1;
        if (nextSceneIndex == SceneManager.sceneCountInBuildSettings)
        {
            nextSceneIndex = 0;
        }
        SceneManager.LoadScene(nextSceneIndex);
    }

    void StartSuccessSequence()
    {        
        isTransitioning = true;
        _audioSource.Stop();
        PlaySound(landingSound);
        landingParticle.Play();
        _movement.enabled = false;
        Invoke(nameof(LoadNextLevel), levelLoadDelay);
    }

    void ReloadLevel()
    {
        int currentSceneIndex = SceneManager.GetActiveScene().buildIndex;
        SceneManager.LoadScene(currentSceneIndex);
    }

    void PlaySound(AudioClip clip)
    {
        GetComponent<AudioSource>().PlayOneShot(clip);
    }


}

 

 

외부 환경 만들기

라이팅 

Main Directional Light(sun) - 위치는 상관 없고 방향이 중요하다.(그림자에 영향을 줌)

Environment Lighting : 반사광

Scene Lights : Point Light, Spot Light

- PointLight : 전구와 비슷함, 범위가 있고 가운데에서 멀어질 수록 밝기가 낮음

ex) 가로등

Point Light

- Spot Light : 극장에서의 스포트라이트와 비슷함

Spot Light

* 라이팅을 어둡게 설정하고 싶은데 씬 창에서 조작하기 불편하다 하면 조명키를 눌러주면 된다. (대신 조명 설정은 못 봄)

씬창 조명 키기

- 장애물과 출발지, 도착지에는 Point Light 캐릭터에는 스포트라이트를 두어 어디로 가는지 어디가 위험한지 직관적으로 표현한다.

캐릭터 Spot Light

Material의 Emssion을 활용하면 발광체를 만들 수도 있다.

Emission

Window - Rendering - Lighting에서 Enviroment의 Skybox Material부터 바꿔줬다.

Lighting

이후 프로젝트 창에서 Material을 하나 생성해서 Shader 탭에서 Skybox 중 Procedual으로 바꿔줬다.

Shader - Skybox - Procedual

 이 쉐이더에서 skybox 부분 요소를 정할 수 있다.

지평선이 덜 어둡게 보인다.

이후 카메라에서 Clear Flags를 Solid Color로 해주고 Background를 검정색으로 하면 뒷 배경도 어두워진다.

뒷 배경도 어두워졌다.

 

피봇 사용하기

씬에서 오브젝트를 다룰 때 Global 대신 Local을 사용하면 그 오브젝트의 피봇을 기준으로 조작할 수 있다.

피봇

Extract Method

Extract Method를 통해 기능마다 함수로 나누어 가독성을 높인다.

- 드래그 후 단축키 Ctrl + R + M *라이더 기준

Extract Method

- 이후 이름 짓기 * 나중에 F2 버튼을 통해 다시 지어도 됨 *라이더 기준

이름 짓기(동사 + 명사)

 

Movement.cs

순서를 맞추면 가독성이 높아진다. Update 안에 ProcessThrust, ProcessRotation -> 이후 순서도 ProcessThrust, ProcessRotation

- 이후에 left right stop 등

using UnityEngine;
using UnityEngine.Serialization;

public class Movement : MonoBehaviour
{
    private Rigidbody _rigid;
    private AudioSource _audioSource;
    
    [SerializeField] private float mod = 100f;
    [SerializeField] private float rotationSpeed = 1f;
    
    [SerializeField] private AudioClip mainEngine;
    [SerializeField] private ParticleSystem thrustParticle;
    [SerializeField] private ParticleSystem rightThrustParticle;
    [SerializeField] private ParticleSystem leftThrustParticle;
    
    void Start()
    {
        _rigid = GetComponent<Rigidbody>();
        _audioSource = GetComponent<AudioSource>();
    }

    void Update()
    {
        ProcessThrust();
        ProcessRotation();
    }

    void ProcessThrust()
    {
        if (Input.GetKey(KeyCode.Space))
        {
            StartThrusting();
        }
        else
        {
            StopThrusting();
        }
    }
    
    void ProcessRotation()
    {
        if (Input.GetKey(KeyCode.A))
        {
            RotateLeft();
        }
        else if (Input.GetKey(KeyCode.D))
        {
            RotateRight();
        }
        else
        {
            StopRotating();
        }
    }

    void StartThrusting()
    {
        _rigid.AddRelativeForce(Vector3.up * (mod * Time.deltaTime));
        if (!_audioSource.isPlaying)
        {
            _audioSource.PlayOneShot(mainEngine);
        }
        if (!thrustParticle.isPlaying)
        {
            thrustParticle.Play();
        }
    }
    
    void StopThrusting()
    {
        thrustParticle.Stop();
        _audioSource.Stop();
    }

    private void RotateLeft()
    {
        ApplyRotation(rotationSpeed);
        if (!rightThrustParticle.isPlaying)
        {
            rightThrustParticle.Play();
        }
    }
    
    private void RotateRight()
    {
        ApplyRotation(-rotationSpeed);
        if (!leftThrustParticle.isPlaying)
        {
            leftThrustParticle.Play();
        }
    }
    
    private void StopRotating()
    {
        rightThrustParticle.Stop();
        leftThrustParticle.Stop();
    }

    void ApplyRotation(float rotationThisFrame)
    {
        // rigid.constraints = (RigidbodyConstraints)((int)RigidbodyConstraints.FreezeRotationX + (int)RigidbodyConstraints.FreezeRotationY);
        // rigid.constraints = RigidbodyConstraints.FreezeRotation;
        
        _rigid.freezeRotation = true; 
        transform.Rotate(Vector3.forward * (rotationThisFrame * Time.deltaTime));
        _rigid.freezeRotation = false;
    }
    
}

파티클 시스템

Particles : Emitter에서 생성되는 것들 * 각 파티클들은 게임 개체가 아님

Particles System : 이미터와 여러 파티클들로 이루어져있고 게임 객체에 추가되는 컴포넌트 - 게임 오브젝트는 부착된 컴포넌트에 따라 유형이 정해짐 (충돌을 해결할 때 게임 개체에 추가되기도 함)

Emitter : 파티클들을 방출(Emitting)하는 물체, 공간, 혹은 지점

Module : 파티클에 추가 효과 부여

파티클 시스템
모듈

 

 

프리펩에 파티클 추가

파티클을 프리펩 인스펙터 창에서 불러올 때 프로젝트 창에서가 아닌 자식 오브젝트로부터 불러오는 게 좋다 -> 위치나 설정이 잘 안 되어 있을 수 있음

파티클 불러오기

 

좌우 부스터 로직

- 첫 if에서는 오른쪽 부스터가 켜질 때(왼쪽으로 회전할 때), 오른쪽 부스터 파티클이 플레이중이지 않을 때

- else if에서는 왼쪽 부스터가 켜질 때(오른쪽으로 회전할 때), 왼쪽 부스터 파티클이 플레이중이지 않을 때

- else에서는 아니면 다 멈추기

사이드 부스터

 

bool 변수

bool 값을 사용하여 Transition중(이미 충돌)이면 return을 반환하게 하였다.(void 함수 나가기)

bool 변수

- 충돌하는 부분과 클리어하는 부분에 isTransitioning에 true 값을 줘서 충돌을 제어한다.

- AudioSource.Stop()은 해당 오디오 소스의 재생되던 사운드가 꺼지게 하는 것이다.

bool ~ = true

 

 

프리펩

프리펩 이동

프로젝트 창에서 더블클릭 할 때 -> 프리펩만 있는 공간으로 이동

프리펩1

하이러키 창에서 >를 클릭하여 프리펩 모드로 들어갈 때 -> 뒷배경이 보이는 프리펩을 볼 수 있다.

프리펩 2

 

프리펩 특징

1. 큐브, 캡슐 등등 프리펩 하위에 그 오브젝트 타입을 생성하면 크기나 위치가 같아진다.

* 부모와는 다르게 자식의 스케일은 1로 되어있음 => 여기서 여러 문제가 발생하여 보통은 부모의 스케일은 기본 값으로 조정하고 자식에서 스케일이나 등을 조절하는 식으로 많이 만든다.

부모 프리펩
자식 프리펩

2. 자식의 위치는 부모의 상대적인 위치이다. => 부모의 위치는 자식의 피봇이 된다

부모가 자식의 피봇이 된다.

3. 부모의 피봇과 자식오브젝트들의 중심 위치가 비슷해야 어색하지 않게 회전할 수 있다.

피봇 위치 맞추기

 4. 콜라이더는 부모에서 한 번에 처리했다. -> 깔끔

자식 콜라이더 삭제
부모 콜라이더 생성

'유데미 강의 > C#과 Unity로 3D 게임 개발하기 : 부스트 프로젝트' 카테고리의 다른 글

간단한 리팩토링  (0) 2022.08.23
파티클 시스템  (0) 2022.08.23
오디오 클립  (0) 2022.08.23
Invoke 함수 사용  (0) 2022.08.12
SceneManager  (0) 2022.08.12

다중 오디오 클립

다중으로 오디오 클립을 캐싱하여 조건마다 소리를 낼 수 있다.

오디오 캐싱

CollisionHandler.cs

using System;
using UnityEngine;
using UnityEngine.SceneManagement;

public class CollisionHandler : MonoBehaviour
{
    private AudioSource _audioSource;
    private Movement _movement;
    
    [SerializeField] float levelLoadDelay = 1f;

    [SerializeField] private AudioClip crashSound;
    [SerializeField] private AudioClip landingSound;

    private void Start()
    {
        _audioSource = GetComponent<AudioSource>();
        _movement = GetComponent<Movement>();
    }

    private void OnCollisionEnter(Collision collision)
    {
        switch (collision.gameObject.tag)
        {
            case "Friendly":
                Debug.Log("This thing is firendly");
                break;
            case "Finish":
                StartSuccessSequence();
                break;
            default:
                StartCrashSequence();
                break;
        }
    }

    void StartCrashSequence()
    {
        PlaySound(crashSound);
        _movement.enabled = false;
        Invoke(nameof(ReloadLevel), levelLoadDelay);
    }

    void LoadNextLevel()
    {
        int currentSceneIndex = SceneManager.GetActiveScene().buildIndex;
        int nextSceneIndex = currentSceneIndex + 1;
        if (nextSceneIndex == SceneManager.sceneCountInBuildSettings)
        {
            nextSceneIndex = 0;
        }
        SceneManager.LoadScene(nextSceneIndex);
    }

    void StartSuccessSequence()
    {
        PlaySound(landingSound);
        _movement.enabled = false;
        Invoke(nameof(LoadNextLevel), levelLoadDelay);
    }

    void ReloadLevel()
    {
        int currentSceneIndex = SceneManager.GetActiveScene().buildIndex;
        SceneManager.LoadScene(currentSceneIndex);
    }

    void PlaySound(AudioClip clip)
    {
        GetComponent<AudioSource>().PlayOneShot(clip);
    }
}

Movement.cs

using UnityEngine;

public class Movement : MonoBehaviour
{
    private bool isAlive;
    
    private Rigidbody _rigid;
    private AudioSource _audioSource;
    
    [SerializeField] private float mod = 100f;
    [SerializeField] private float rotationSpeed = 1f;
    
    [SerializeField] private AudioClip _mainEngine;
    void Start()
    {
        _rigid = GetComponent<Rigidbody>();
        _audioSource = GetComponent<AudioSource>();
    }

    void Update()
    {
        ProcessThrust();
        ProcessRotation();
    }

    void ProcessThrust()
    {
        if (Input.GetKey(KeyCode.Space))
        {
            _rigid.AddRelativeForce(Vector3.up * (mod * Time.deltaTime));
            if (!_audioSource.isPlaying)
                _audioSource.PlayOneShot(_mainEngine);
        }
        else
            _audioSource.Stop();
    }

    void ProcessRotation()
    {
        if (Input.GetKey(KeyCode.A))
        {
            ApplyRotation(rotationSpeed);
        }
        else if (Input.GetKey(KeyCode.D))
        {
            ApplyRotation(-rotationSpeed);
        }
    }

    private void ApplyRotation(float rotationThisFrame)
    {
        // rigid.constraints = (RigidbodyConstraints)((int)RigidbodyConstraints.FreezeRotationX + (int)RigidbodyConstraints.FreezeRotationY);
        // rigid.constraints = RigidbodyConstraints.FreezeRotation;
        
        _rigid.freezeRotation = true; 
        transform.Rotate(Vector3.forward * (rotationThisFrame * Time.deltaTime));
        _rigid.freezeRotation = false;
    }
}

 

플레이 영상

다중 오디오 클립

착지 이후에 충돌 소리가 나는 버그는 나중에 수정하도록 하자 (collision 스크립트를 끈다던지)

'유데미 강의 > C#과 Unity로 3D 게임 개발하기 : 부스트 프로젝트' 카테고리의 다른 글

파티클 시스템  (0) 2022.08.23
bool 변수로 제어하기, 로켓 꾸미기  (0) 2022.08.23
Invoke 함수 사용  (0) 2022.08.12
SceneManager  (0) 2022.08.12
Switch  (0) 2022.08.12

+ Recent posts