본문 바로가기
게임 개발/유니티 게임 개발 일지

#17 PlayerSpawner, ItemSpawner, ObstacleSpawner 구현

by FlowTree 2020. 8. 11.
반응형
#이번 기능들은 간단한 기능들이 많아서 기능 구조도, 플로우차트는 생략한다. ItemSpawner만 작성하기

1.PlayerSpawner

1.1.정의

  • 게임 시작 시 플레이어 스폰 포인트를 랜덤하게 하나 선택하여 그 곳에 플레이어를 배치하는 기능이다.

1.2.기획의도

  • 플레이어 랜덤 스폰: 같은 맵에서 반복 플레이 시 조금이라도 지루함을 완화하기
  • 장애물 랜덤 배치 기능을 통해 맵 구조를 변경하여 지루함을 완화할 수 있으나 플레이어 랜덤 스폰 기능을 결합하여 익숙하지만 새로운 맵에서 하는 듯한 느낌을 강화 = 기능을 두개 결합하여 지루함 완화 강화

1.3.구성 요소

  • 플레이어 스폰 포인트
    - 플레이어가 스폰할 수 있는 투명한 위치 오브젝트
    - 스폰 포인트를 맵에 배치하여 위치를 준비한다.
    - 스폰 포인트는 필요시 개수를 추가, 감소 가능해야함(밸런스 조절용)
    - 현재 스폰 포인트는 7개
      #맵의 구역마다 스폰포인트를 한 개씩 할당하면 8개인데 한 구역이 작아서 다른 큰 구역과 병합하여
        스폰포인트를 7개로 설정함
  • 플레이어 오브젝트
    - 게임을 플레이하는 플레이어 오브젝트
    - 플레이어 스폰 포인트에 배치되는 오브젝트

1.4.기능 명세

1.4.1.플레이어 스폰 포인트 랜덤 선택

  • 게임 시작 시 플레이어 스폰 포인트들 중 하나의 스폰 포인트를 선택하는 기능이다.  

1.4.2.규칙

  • 게임 시작 시 맵에 배치한 플레이어 스폰 포인트들 중 하나를 랜덤하게 선택한다.
  • 플레이어 오브젝트의 위치를 선택된 플레이어 스폰 포인트 위치로 변경한다. 

1.5.구현 과정

PlayerSpawner 스크립트

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

public class PlayerSpawner : MonoBehaviour
{
    public PlayerStats player;
    public Transform[] playerSpawnPoints;

    void Start()
    {
        RandomSelectSpawnPoint();
    }

    void RandomSelectSpawnPoint()
    {
        int number = Random.Range(0, playerSpawnPoints.Length);
        player.transform.position = playerSpawnPoints[number].transform.position;
    }

    //매 시작 시 플레이어 스폰 포인트를 랜덤하게 하나 선택
    //해당 위치를 플레이어 위치로 변경

}

1.맵에 빈 게임 오브젝트를 생성 > PlayerSpawner로 변경

2.PlayerSpawner에 PlayerSpawner 스크립트를 할당

3.PlayerSpawner 스크립트에서 스폰 포인트 개수를 7개로 변경
4.맵에 배치한 플레이어 스폰 포인트 오브젝트를 할당


2.ItemSpawner

2.1.정의

  • 플레이어 주변에 랜덤한 주기로 체력, 마나 포션을 생성하는 기능이다.

2.2.기획의도

  • 체력 포션 제공
    - 게임 플레이 시 플레이어 스스로 체력 회복하는 기능이 없기 때문에 체력 포션을 제공하여 플레이 지속성을 고려한다.
  • 마나 포션 제공
    - 플레이어의 마나는 초당 1씩 회복 하지만 스킬을 사용하기 위해선 25초 이상 모아야한다.
      스킬을 사용하고나면 일반 공격만 사용이 가능하여 전투 패턴이 단조로워지는 문제가 발생한다.
      이 때 마나 포션을 제공하여 마나 회복 시간을 단축하여 스킬 사용 기회를 제공한다. 
  • 랜덤 포션 생성과 랜덤 생성 주기
    - 항상 동일한 시간에 동일한 포션이 생성되면 플레이어가 예측이 가능해져서 플레이의 난이도가 쉬워진다.
      포션 생성 시 랜덤성을 부여하여 적절한 전투 난이도를 유지한다.
    - 생성 시간에 약간의 랜덤성을 통해 플레이 시 다양한 상황에 운적 재미를 제공한다.
      예) 적들이 몰려오는데 스킬을 이미 사용해서 마나가 없다 > 갑작스런 마나 포션 생성됨 > 마나 회복 후 스킬을
      사용하여 적들을 한 방에 처리 > 특정 상황일 때의 운을 통해 재미 경험 가능
  • 포션 랜덤 생성 위치
    - 플레이어를 중심으로 일정 반경 안에서 랜덤한 위치에 포션을 생성하여 플레이어에게 선택권을 제공한다.
      예) 체력이 부족한 상황, 적들 근처에 체력 포션이 스폰 > 위험을 감수하고 먹으러 간다 or 안전하게 도망친다.
    - 생성 위치를 예측 불가능하게 하여 쉬운 포션 획득을 방지한다.
      

2.3.구성요소

2.3.1포션

체력 포션

외형 -원형 유리병에 담긴 빨간 포션
크기 -플레이어 캐릭터 크기의 반
-약 0.9m
-크기가 어느정도 있어야 플레이어가 한 눈에 알아볼 수 있음
색상 -빨간색
-포션 바닥에 포션색과 유사한 붉은빛을 내어 가시성을 높임
파티클 -포션색과 유사한 붉은색 사각형 파티클을 바닥에서 위로 뿌림
-동적인 이펙트를 통해 플레이어에게 확실하게 포션 위치를 알림
체력 회복량 -100
-플레이어 최대 체력의 1/10(포션 생성 주기가 길지 않기 때문에) 

 

마나 포션

외형 -원기둥형 유리병에 담긴 푸른 포션
크기 -플레이어 캐릭터 크기의 반
-약 0.9m
-크기가 어느정도 있어야 플레이어가 한 눈에 알아볼 수 있음
색상 -푸른색
-포션 바닥에 포션색과 유사한 푸른빛을 내어 가시성을 높임
파티클 -포션색과 유사한 푸른색 사각형 파티클을 바닥에서 위로 뿌림
-동적인 이펙트를 통해 플레이어에게 확실하게 포션 위치를 알림
마나 회복량 -20
-플레이어 최대 마나의 1/5
(마나 회복 기능이 있기 때문에 포션을 먹는다고 무조건 스킬을 사용할 수 없게 함, 포션 섭취 후 스킬 남용 방지, 일반 공격과 스킬 사용 병행 유도) 

 

2.3.2ItemSpawner 파라미터

이름 데이터 타입 내용
maxDistance float -플레이어를 중심으로 아이템이 생성될 반경
-10f
timeBetSpawn float -아이템 생성 시점
-아이템 생성에 필요한 시간이다.
timeBetSpawnMin float -아이템 최소 생성 시점
-아이템 랜덤 생성 주기 계산할 때 사용하는 최소값
-3f
timeBetSpawnMax float -아이템 최대 생성 시점
-아이템 랜덤 생성 주기 계산할 때 사용하는 최대값
-10f
lastSpawnTime float -아이템을 마지막으로 생성 시점
itemDestroyTime float -생성된 아이템 파괴 제한 시간
-itemDestroyTime초가 지난 후 생성된 아이템을 파괴
-5f

 

2.4.기능 명세

2.4.1.기능 구조도

ItemSpawner 기능 구조도

포션 기능 구조도

 

2.4.2.플로우 차트

ItemSpawner

#Potion 플로우차트는 제외, 기능 자체가 단순해서 규칙으로 충분히 쉽게 이해 가능

 

2.4.3.세부 기능

2.4.3.1.ItemSpawner

  • 아이템 생성 시점 초기화
    - 게임 시작 시 아이템 마지막 생성 시점을 0초로 초기화한다.
    - 게임 시작 시 아이템 랜덤 생성 주기로 계산한 값을 아이템 생성 시점으로 설정한다.
  •  아이템 생성 조건
    - 현재 시간이 아이템 생성 시점 + 마지막 생성 시점을 지났을 때 아이템을 생성한다.
  • 아이템 랜덤 생성 주기
    - 아이템 생성 시점 = 아이템 생성 최소 ~ 최대 생성 시점 사이의 랜덤한 값
  • 아이템 생성
    - 아이템 생성 조건을 충족했을 경우 마지막 생성 시점을 현재 시간으로 갱신한다.
    - 아이템 생성 시점을 아이템 랜덤 생성 주기로 갱신한다.
    - 아이템을 생성 위치에 생성한다.
  • 아이템 생성 위치
    - 플레이어 위치를 기준으로 하는
    maxDistance 반경 안의 랜덤한 위치를 생성한다.
    - 생성된 위치와 가장 가까운 네비메쉬 위를 아이템 생성 위치로 설정한다.
    - 아이템 생성 위치의 높이를 0.7f 높여서 맵의 바닥 위로 살짝 공중에 떠있게 한다.

 

2.4.3.2.포션

  • 회복 기능
    - 생성된 체력 포션에 플레이어가 접촉했을 경우 플레이어의 체력을 체력 회복량만큼 회복한다. 
    - 생성된 마나 포션에 플레이어가 접촉했을 경우 플레이어의 마나를 마나 회복량만큼 회복한다.
  • 이펙트
    - 플레이어에게 포션의 위치와 포션의 종류를 쉽게 알려주기 위한 기능이다.
  • 회전 기능
    - 포션이 제자리에서 느리게 회전하는 기능이다.
    - 생성된 포션이 정적으로 있는 것보다 동적인 상태를 보여주는게 더 가시성이 좋아서 회전 기능을 추가함

2.5.구현 과정

ItemSpawner 스크립트

using UnityEngine;
using UnityEngine.AI; // 내비메쉬 관련 코드

// 주기적으로 아이템을 플레이어 근처에 생성하는 스크립트
public class ItemSpawner : MonoBehaviour 
{

    public GameObject[] items; // 생성할 아이템들
    public Transform playerTransform; // 플레이어의 트랜스폼

    public float maxDistance = 10f; // 플레이어 위치로부터 아이템이 배치될 최대 반경

    public float timeBetSpawnMax = 10f; // 최대 시간 간격
    public float timeBetSpawnMin = 3f; // 최소 시간 간격
    private float timeBetSpawn; // 생성 간격

    private float lastSpawnTime; // 마지막 생성 시점

    private void Start() 
    {
        // 생성 간격과 마지막 생성 시점 초기화
        timeBetSpawn = Random.Range(timeBetSpawnMin, timeBetSpawnMax);
        lastSpawnTime = 0;
    }

    // 주기적으로 아이템 생성 처리 실행
    private void Update() {
        // 현재 시점이 마지막 생성 시점에서 생성 주기 이상 지남
        // && 플레이어 캐릭터가 존재함
        if (Time.time >= lastSpawnTime + timeBetSpawn && playerTransform != null)
        {
            // 마지막 생성 시간 갱신
            lastSpawnTime = Time.time;
            // 생성 주기를 랜덤으로 변경
            timeBetSpawn = Random.Range(timeBetSpawnMin, timeBetSpawnMax);
            // 아이템 생성 실행
            Spawn();
        }
    }

    // 실제 아이템 생성 처리
    private void Spawn() 
    {
        // 플레이어 근처에서 내비메시 위의 랜덤 위치 가져오기
        Vector3 spawnPosition = GetRandomPointOnNavMesh(playerTransform.position, maxDistance);
        // 바닥에서 0.7만큼 위로 올리기
        spawnPosition += Vector3.up * 0.7f;

        // 아이템 중 하나를 무작위로 골라 랜덤 위치에 생성
        GameObject selectedItem = items[Random.Range(0, items.Length)];
        GameObject item = Instantiate(selectedItem, spawnPosition, Quaternion.identity);

        // 생성된 아이템을 5초 뒤에 파괴
        Destroy(item, 5f);
    }

    // 내비메시 위의 랜덤한 위치를 반환하는 메서드
    // center를 중심으로 distance 반경 안에서 랜덤한 위치를 찾는다
    private Vector3 GetRandomPointOnNavMesh(Vector3 center, float distance) 
    {
        // center를 중심으로 반지름이 maxDistance인 구 안에서의 랜덤한 위치 하나를 저장
        // Random.insideUnitSphere는 반지름이 1인 구 안에서의 랜덤한 한 점을 반환하는 프로퍼티
        Vector3 randomPos = Random.insideUnitSphere * distance + center;

        // 내비메시 샘플링의 결과 정보를 저장하는 변수
        NavMeshHit hit;

        // maxDistance 반경 안에서, randomPos에 가장 가까운 내비메시 위의 한 점을 찾음
        NavMesh.SamplePosition(randomPos, out hit, distance, NavMesh.AllAreas);

        // 찾은 점 반환
        return hit.position;
    }
}

 

체력 포션 스크립트

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

public class HealingPotion : MonoBehaviour, IItem
{
    public float health = 50;

    public void Use(GameObject target)
    {
        //전달받은 게임 오브젝트로부터 PlayerStats 컴포넌트 가져오기
        PlayerStats player = target.GetComponent<PlayerStats>();

        //PlayerStats 컴포넌트가 있다면
        if(player != null)
        {
            //체력 회복 실행
            player.RestoreHealth(health);
        }

        //사용되었으므로 자신을 파괴
        Destroy(gameObject);
    }
}

 

마나 포션 스크립트

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

public class ManaPotion : MonoBehaviour, IItem
{
    public float mana = 50;

    public void Use(GameObject target)
    {
        //전달받은 게임 오브젝트로부터 PlayerStats 컴포넌트 가져오기
        PlayerStats player = target.GetComponent<PlayerStats>();

        //PlayerStats 컴포넌트가 있다면
        if (player != null)
        {
            //체력 회복 실행
            player.RestoreMana(mana);
        }

        //사용되었으므로 자신을 파괴
        Destroy(gameObject);
    }
}

 

회전 기능 스크립트

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

public class Rotator : MonoBehaviour
{
    public float rotationSpeed = 60f;

    //해당 오브젝트가 제자리 회전하는 기능
    void Update()
    {
        transform.Rotate(0f, rotationSpeed * Time.deltaTime, 0f);
    }
}

 

포션 충돌 시 포션 먹는 기능 - PlayerStats 스크립트에 추가

    //트리거와 충돌 시 -  데드존, 아이템 사용 
    private void OnTriggerEnter(Collider other)
    {
        if(other.tag == "Deadzone" && !dead)
        {
            Die();
            Debug.Log("사망");
        }

        // 아이템과 충돌한 경우 해당 아이템을 사용하는 처리
        else if (!dead)
        {
            //충돌한 상대방으로부터 IItem 컴포넌트 가져오기 시도
            IItem item = other.GetComponent<IItem>();

            //충돌한 상대방으로부터 IItem 컴포넌트를 가져오는 데 성공했다면
            if (item != null)
            {
                //Use 메서드를 실행하여 아이템 사용
                item.Use(gameObject);
                //아이템 습득 소리 재생
                //playerAudioPlayer.PlayOneShot(itemPickupClip);
            }
        }

    }

 

ItemSpawner 구현 과정

1.하이어라키 > 빈 게임 오브젝트 생성 > ItemSpawner로 변경

2.ItemSpawner에 ItemSpawner 스크립트 할당
3.ItemSpawner 스크립트에 포션 프리팹, 플레이어 트랜스폼을 할당

4.아이템 생성 반경, 아이템 최소, 최대 생성 시점 설정

 

포션 구현 과정

1.예전에 구매한 Polygon - Dungeons에 포션 프리팹이 있어서 그것을 사용했다.

https://assetstore.unity.com/packages/3d/environments/dungeons/polygon-dungeons-pack-102677?locale=ko-KR

 

2.포션의 가시성을 높이는 빛, 파티클, 회전 기능은 골드 메탈님의 유튜브 영상을 보고 구현했다.

#포션 파티클, 라이트, 회전 기능 참고 링크

www.youtube.com/watch?v=u2DLOay5oO8

 


3.ObstacleSpawner

3.1.정의

  • 게임 시작 시 랜덤한 수의 장애물을 배치하는 기능이다.

3.2.기획의도

  • 맵의 통로에 장애물을 배치하여 동일한 맵에서 반복 플레이를 해도 지루하지 않고 익숙하면서 새로운 느낌을 주

#절차적 맵 생성으로 게임을 시작할 때마다 새로운 구조의 맵을 구현하고자 했으나 기술적인 문제로 실패하여 
어떻게 하면 동일한 맵에서 반복 플레이를 해도 지루하지 않을까 고민하여 생각한 기능이다.

3.3.구성 요소

  • 장애물 배치 포인트
    - 맵의 특정 통로에 장애물을 배치하는 포인트
    - 장애물 배치 포인트 기본값: 비활성화
  • 장애물
    - 플레이어, 적의 이동을 제한하는 오브젝트

3.4.기능 명세

3.4.1.장애물 배치 개수 랜덤 선택

  • 게임 시작 시 장애물을 배치할 개수를 랜덤 선택하는 기능이다.
  • 장애물 배치 개수는 0 ~ 전체 장애물 배치 포인트 개수에서 랜덤한 수를 선택한다. 

3.4.2.장애물 배치 포인트 랜덤 선택

  • 선택된 장애물 배치 개수만큼 랜덤하게 장애물 배치 포인트를 선택하는 기능이다.
  • 장애물 배치 포인트를 선택할 때 중복없이 랜덤 선택한다.
  • 선택된 장애물 배치 포인트를 제외하고 나머지 장애물 배치 포인트에서 랜덤 선택한다.
  • 장애물 배치 포인트를 모두 선택했을 경우 선택된 장애물 배치 포인트를 활성화하여 장애물을 배치한다.

3.5.구현 과정

Obstacle Spawner 스크립트

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

public class ObstacleSpawner : MonoBehaviour
{
    private int selectedNum;
    public GameObject[] obstaclePoints; //모든 장애물 포인트들

    private List<GameObject> allObstacles = new List<GameObject>(); //뽑기할 때 이용할 장애물 포인트들
    public List<GameObject> selectedObstacles = new List<GameObject>(); //맵에 배치할 장애물 포인트들

    void Start()
    {
        SaveObstacle();
        SelectObstacle();
        DeployObstacle();
    }
    
    //모든 장애물 포인트를 리스트에 저장, 뽑기 준비
    void SaveObstacle()
    {
        for(int i = 0; i < obstaclePoints.Length; i++)
        {
            allObstacles.Add(obstaclePoints[i]);
        }
    }

    void SelectObstacle()
    {
        //설치할 장애물 수 랜덤 선택하기
        selectedNum  = Random.Range(0, obstaclePoints.Length);
        Debug.Log("장애물 배치할 숫자"+selectedNum);

        //선택한 숫자만큼 중복 없이 장애물 포인트 선택하기
        for( int i = 0; i < selectedNum; i++)
        {
            int select = Random.Range(0, allObstacles.Count);

            Debug.Log("뽑은 번호" + select);
            Debug.Log("배치할 장애물 번호"+allObstacles[select].name);
            
            selectedObstacles.Add(allObstacles[select]);
            allObstacles.RemoveAt(select); //RemoveAt(int 인덱스) 리스트 해당 인덱스의 데이터를 제거
        }
    }

    //장애물 배치
    void DeployObstacle()
    {
        for(int i = 0; i < selectedObstacles.Count; i++)
        {
            selectedObstacles[i].SetActive(true);
        }

    }
}

 

중복 없는 랜덤 선택 기능
아래의 참고 링크를 보고 배열과 리스트를 이용하여 중복 없는 랜덤 선택 기능을 구현했다.

 

중복 없는 랜덤 선택 기능 참고 링크

goraniunity2d.blogspot.com/2019/07/blog-post.html?m=1

 

1.하이어라키 > 빈 게임 오브젝트, ObstacleSpawner로 변경 > ObstacleSpawner 스크립트 할당 > 장애물 배치 포인트 할당

 

반응형

댓글