파티클 생성
플레이어 하위에 Effects - Particle System을 생성한다.
적당히 꾸며준다.
- Duration : 얼마나 실행할 것인가
- Looping : 반복 실행
- Start Lifetime : 생존 시간
- Start Speed : 시작 속도
- Start Size : 파티클 사이즈
- Simulation Space : 어느 곳에서 애니메이션화할지(오브젝트, 월드, 커스텀 등)
Emission
- Rate over Time : 시간 당 방출량
Shape
- Shape : 방출 모양
- Radius : 뿜어져 나오는 범위
Limit Velocity over Lifetime
- Speed : 파티클 속도
- - Dampen : 감소 속도, 저항
Color over Lifetime : 생존 동안의 색상 변화Size over Lifetime : 생존 동안의 크기 변화
Collision
- Dampen : 감소 속도, 저항
- Bounce : 탄성
Renderer
- Material : 재질
- Order in Layer : 레이어 순서
스크립트를 만들어 플레이어가 움직일 때마다 생성되도록 애니메이터가 있는 공간에 달아준다.
[SerializeField] private bool createDustOnWalk = true;
[SerializeField] private ParticleSystem dustParticleSystem;
public void CreateDustParticles()
{
if (createDustOnWalk)
{
dustParticleSystem.Stop();
dustParticleSystem.Play();
}
}
동작할 때마다 파티클을 달아줄 애니메이션을 선택하여 Animation Event를 달아준다. 애니메이션 이벤트는 같이 설정되어있는 곳에 애니메이션이 있어야 호출할 수 있다.
투사체 소멸 효과도 만들어준다. 파티클을 만들어서 달아준 뒤, ProjectileManager에 스크립트를 추가한다.
투사체가 적이나 벽에 닿았을 때, 파티클이 터지는 코드인데 이 부분은 솔직히 이해 못해서 다시 봐야할 것 같다.
public void CreateImpactParticlesAtPostion(Vector3 position, RangedAttackData attackData)
{
_impactParticleSystem.transform.position = position;
ParticleSystem.EmissionModule em = _impactParticleSystem.emission;
em.SetBurst(0, new ParticleSystem.Burst(0, Mathf.Ceil(attackData.size * 5)));
ParticleSystem.MainModule mainModule = _impactParticleSystem.main;
mainModule.startSpeedMultiplier = attackData.size * 10f;
_impactParticleSystem.Play();
}
원거리 투사체 스크립트 부분도 수정한다.
private void DestroyProjectile(Vector3 position, bool createFx)
{
if (createFx)
{
_projectileManager.CreateImpactParticlesAtPostion(position, _attackData);
}
gameObject.SetActive(false);
}
사운드 컨트롤
사운드 매니저 스크립트를 만든다.
싱글톤화 시킨 후, 사운드 이펙트에 맞춘 볼륨, 이펙트 볼륨, 배경음악 볼륨과 오브젝트 풀, 오디오소스와 실제로 오디오를 가지고 있는 오디오클립을 준비한다.
public static SoundManager instance;
[SerializeField][Range(0f, 1f)] private float soundEffectVolume;
[SerializeField][Range(0f, 1f)] private float soundEffectPitchVariance;
[SerializeField][Range(0f, 1f)] private float musicVolume;
private ObjectPool objectPool;
private AudioSource musicAudioSource;
public AudioClip musicClip;
private void Awake()
{
instance = this;
musicAudioSource = GetComponent<AudioSource>();
musicAudioSource.volume = musicVolume;
musicAudioSource.loop = true;
objectPool = GetComponent<ObjectPool>();
}
private void Start()
{
ChangeBackGroundMusic(musicClip);
}
public static void ChangeBackGroundMusic(AudioClip music)
{
instance.musicAudioSource.Stop();
instance.musicAudioSource.clip = music;
instance.musicAudioSource.Play();
}
public static void PlayClip(AudioClip clip)
{
GameObject obj = instance.objectPool.SpawnFromPool("SoundSource");
obj.SetActive(true);
SoundSource soundSource = obj.GetComponent<SoundSource>();
soundSource.Play(clip, instance.soundEffectVolume, instance.soundEffectPitchVariance);
}
SoundSource는 사운드 클립을 컨트롤 하기 위해 만든 것이다. Play를 걸면 해당 클립을 설정한 볼륨과 피치로 재생시킨다. Invoke는 지연 실행으로, 노래가 끝난 2초 뒤에 끝낸다.
private AudioSource _audioSource;
public void Play(AudioClip clip, float soundEffectVolume, float soundEffectPitchVariance)
{
if (_audioSource == null)
_audioSource = GetComponent<AudioSource>();
CancelInvoke();
_audioSource.clip = clip;
_audioSource.volume = soundEffectVolume;
_audioSource.Play();
_audioSource.pitch = 1f + Random.Range(-soundEffectPitchVariance, soundEffectPitchVariance);
Invoke("Disable", clip.length + 2);
}
public void Disable()
{
_audioSource.Stop();
gameObject.SetActive(false);
}
유니티에서 SoundManager와 SoundSource를 만들어준 뒤, 각각 스크립트를 연결시킨다.
SoundSource에 Audio Source를 달아주고 프리팹화 시킨다. SoundManager 역시 Audio Source와 배경음악을 달아준다.
Shooting 소리나 데미지를 입었을 때 등 효과음을 넣어주기 위해 스크립트에 AudioClip을 부른 뒤, 해당 조건에 재생되도록 한다.
public AudioClip shootingClip;
----------------- 생략 -----------------
if (shootingClip)
SoundManager.PlayClip(shootingClip);
UI
UGUI를 사용하여 UI를 구현하기 위해 Canvas를 생성하고 Scale을 Scale With Screen Size로 바꿔준 뒤, 16:9로 변경한다. Scale With Screen Size는 화면이 커질수록 UI 요소도 커져서 멀티 해상도에 대응이 가능하다. 반대로 Constant Physical Size는 화면 크기와 해상도에 관계없이 UI 요소가 동일한 물리적인 크기로 유지된다.
UI 배치는 큰 묶음과 앵커에 신경쓰면 좋다.
현재 앵커가 중앙에 걸려있으므로 화면 사이즈가 변할 때, 중앙을 기준으로 위치가 유지된다.
Wave를 띄우기 위해 Canvas 하위에 빈 오브젝트를 생성해서 image를 추가하여 앵커로 위치를 조정한다.
생성한 캔버스를 꽉 채우기 위해서는 alt + 클릭으로 stretch를 선택하면 된다.
UI를 적당히 꾸며준 뒤, UI를 처리하는 스크립트를 GameManager에서 처리한다.
플레이어의 체력을 불러와서 %로 계산해준 뒤, Silder UI에 반영한다.
GameOver가 되면 GameOver UI가 실행되도록 하고, 버튼을 눌렀을 때 각각 게임 Scene화면을 다시 불러오고 어플리케이션을 종료시킨다. 예전에는 Application.LoadScene로 씬을 불러왔는데 이제는 사용되지 않기 때문에 SceneManager로 build Setting에 걸려있는 인덱스 번호로 불러온다.(Scene 이름으로 불러올 수도 있다.)
using TMPro;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
private HealthSystem playerHealthSystem;
[SerializeField] private TextMeshProUGUI waveText;
[SerializeField] private Slider hpGaugeSlider;
[SerializeField] private GameObject gameOverUI;
private void Awake()
{
instance = this;
Player = GameObject.FindGameObjectWithTag(playerTag).transform;
playerHealthSystem = Player.GetComponent<HealthSystem>();
playerHealthSystem.OnDamage += UpdateHealthUI;
playerHealthSystem.OnHeal += UpdateHealthUI;
playerHealthSystem.OnDeath += GameOver;
gameOverUI.SetActive(false);
}
private void UpdateHealthUI()
{
hpGaugeSlider.value = playerHealthSystem.CurrentHealth / playerHealthSystem.MaxHealth;
}
private void GameOver()
{
gameOverUI.SetActive(true);
}
private void UpdateWaveUI()
{
// waveText.text =
}
public void RestartGame()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
public void ExitGame()
{
Application.Quit();
}
유니티로 돌아와서 GameManager와 버튼에 필요한 것들을 연결해준다.
'부트캠프 > Study' 카테고리의 다른 글
<Unity> UI (0) | 2023.12.14 |
---|---|
<Unity> 숙련 - 2D 게임 개발(8) (0) | 2023.12.13 |
<Unity> 숙련 - 2D 게임 개발(6) (1) | 2023.12.12 |
ToString() 메서드 (1) | 2023.12.11 |
<Unity> 숙련 - 2D 게임 개발(5) (0) | 2023.12.10 |