유니티 3D 서바이벌 게임 자원 자동 생성기능
자원을 자동으로 생성하기 위해서 맵에 수직으로 Raycast를 발사해 유효한 Vector3값을 가져온다.
이때, 자원을 심을 가로 세로 영역을 정하고 해당 영역에서 랜덤 좌표를 찍어 자원을 심음.
완전한 랜덤으로 자원을 심을 경우 다양한 문제가 발생할 수 있기때문에, 영역을 심으려는 개수 의 제곱근 값으로 나누고 중첩반복문을 통해 심으려는 개수 만큼 심을 수 있게 만듦
심으려는 위치의 지면의 각도가 일정 이상이면 유효한 포인트로 삼지않음.
해당 위치들에 어떠한 오차도 없이 일괄적으로 자원을 배치하면, 너무나 일정하게 자원이 배치되므로 해당좌표에서 Vectro2(Random.Range(-offset,offset), Random.Range(-offset,offset)으로 좌표값을 일부수정. Rotation값도 랜덤으로 나타남.
Scriptable오브젝트로 이를 저장해 해당 자원 배치 위치를 저장함. 따라서, 이를 에디터모드에서 부자연스러운 위치를 수정가능하고 몇 개의 자원이 배치될 지 미리 알 수 있음.
자원의 크기를 다르게 생성하여, 자연스러운 배치를 구현
using System;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
[CreateAssetMenu(menuName = "GatheringData/GatheringSpawnerData",fileName = "GatheringSpawnerData")]
public class GatherPositionScriptable : ScriptableObject
{
[Header ("스폰정보")]
public GameObject gatheringObject;
public Rect spawnArea = new Rect();
public List<Vector3> spawnPositions = new List<Vector3>();
public int count;
public int respawnTime;
public float constraintAngle;
[Header("랜덤 수치조정/스폰 땅 높이 조정")]
public float height = 10f;
public float offset = 8f;
private float _width;
private float _length;
private void OnValidate()
{
if (spawnPositions.Count <= 1)
{
spawnPositions = SelectRandomPointInOblong(spawnArea.width, spawnArea.height,count);
}
}
public List<Vector3> SelectRandomPointInOblong(float width, float length, int number)
{
_width = width;
_length = length;
List<Vector3> result = new List<Vector3>();
int counter = Mathf.Max(2, (int)Mathf.Sqrt(number));
float onePerLength = _length / (counter - 1);
float onePerWidth = _width / (counter - 1);
for (int i = 0; i < counter; i++)
{
for (int j = 0; j < counter; j++)
{
float x = onePerWidth * i + UnityEngine.Random.Range(-offset, offset) + spawnArea.xMin;
float z = onePerLength * j + UnityEngine.Random.Range(-offset, offset) + spawnArea.yMin;
Ray ray = new Ray(new Vector3(x, height, z), Vector3.down * 100f);
if (Physics.Raycast(ray, out RaycastHit hit))
{
Vector3 normal = hit.normal;
float slopeAngle = Vector3.Angle(normal, Vector3.up);
if(slopeAngle > constraintAngle) continue;
if (!hit.collider.gameObject.CompareTag("Respawn")) continue;
result.Add(hit.point);
}
}
}
return result;
}
}
코드를 보면, OnValidate를 사용하여 런타임때 Data를 생성하지않고 에디터모드일때 미리 생성한다.
SelectRandomPointInOblong에서 영역의 가로 세로 값을 받아서 해당 영역에 유효한 배치값을 반환받는다.
해당 포인트에 대한 지면각도는 SlopeAngle은 hit.normal을 사용하여 hit된 point에 대한 법선벡터와 위쪽 벡터의 각을 Vector3.Angle을 통하여 구한다.
또한 해당 위치가 유효한 포인트여도, 위치가 respawn될만한 위치가 아니라면 유효한 포인트로 삼지않는다.
유효한 포인트라면 Add를 통해 Listresult에 값을 담아서 마지막에 반환한다.