메타버스 Picazoo
개요
PicaZoo 씬은 플레이어가 그림 총으로 동물 동상을 찾아야 하는 미니 게임입니다.
이것은 네트워크를 통해 텍스처 수정을 동기화하는 방법을 보여줍니다.
슈팅 게임을 개발하려면 전용 기술 샘플을 참조하십시오:
데스크톱 모드에서 특정 컨트롤러를 사용하여 마우스로 총기를 더 잘 제어합니다.
PaintBlaster
이 씬에는 여러 개의 그림 총이 있습니다:
- 미리 정의된 색깔의 총알을 쏘는 총들
- 랜덤 한 색의 총알을 쏘는 총
총은 PaintBlaster
클래스로 관리되며 다음 2개 목록을 유지 관리합니다 :
- 샷의 목록
- 충격의 목록
목록은 네트워크로 연결되므로 원격 플레이어는 새로운 샷/충격이 발생하는지 확인하고 표시할 수 있습니다.
이렇게 함으로써, 네트워크에 연결된 총알 프리팹을 생성할 필요가 없습니다.
샷
샷의 속성은 BulletShoot
네트워크 구조체로 수집됩니다.
C#
public struct BulletShoot : INetworkStruct
{
public Vector3 initialPosition;
public Quaternion initialRotation;
public float shootTime;
public int bulletPrefabIndex;
public PlayerRef source;
}
먼저, 사용자의 샷 입력은 IsUsed
부울을 설정하는 데 사용되는 InputActionProperty UseAction
에 의해 처리됩니다.
C#
public bool IsUsed
{
get
{
return UseAction.action.ReadValue<float>() > minAction;
}
}
각 LateUpdate()
에서 로컬 플레이어가 총을 잡고 사격하는지 확인합니다.
C#
public void LateUpdate()
{
...
if (Object == null || Object.HasStateAuthority == false) return;
if (useInput) // VR
{
if (IsGrabbedByLocalPLayer)
{
TriggerPressed(IsUsed);
}
else
wasUsed = false;
}
...
PurgeBulletShoots();
PurgeRecentImpacts();
}
C#
public void TriggerPressed(bool isPressed)
{
...
if (isPressed)
Shoot();
...
}
데스크톱 리그의 경우 DesktopAim
클래스에서 트리거가 탐지됩니다.
C#
private void LateUpdate()
{
if (!grabber) return;
if (grabber.HasStateAuthority == false) return;
blaster.TriggerPressed(Mouse.current.leftButton.isPressed);
...
}
샷 관리는 BulletShoots
네트워크 목록에 새로운 샷을 추가하는 것으로 구성됩니다.
C#
public void Shoot()
{
OnShooting(projectileOriginPoint.transform.position, projectileOriginPoint.transform.rotation);
}
C#
private void OnShooting(Vector3 position, Quaternion rotation)
{
...
lastBullet.initialPosition = position;
lastBullet.initialRotation = rotation;
lastBullet.shootTime = Time.time;
lastBullet.bulletPrefabIndex = bulletPrefabIndex;
lastBullet.source = Object.StateAuthority;
BulletShoots.Add(lastBullet);
...
}
샷 목록은 네트워크로 연결되어 있기 때문에 어떤 플레이어가 발사했는지에 관계없이 모든 플레이어가 업데이트된 데이터를 받습니다.
따라서 플레이어는 새로운 샷이 발생했을 때(CheckShoots()
) 로컬 그래픽 객체를 생성하기 위해 새로운 데이터가 수신되었는지 FixedUpdateNetwork()
를 확인하기만 하면 됩니다.
C#
const int MAX_BULLET_SHOOT = 50;
[Networked]
[Capacity(MAX_BULLET_SHOOT)]
public NetworkLinkedList<BulletShoot> BulletShoots { get; }
C#
public override void FixedUpdateNetwork()
{
base.FixedUpdateNetwork();
CheckShoots();
CheckRecentImpacts();
}
C#
void CheckShoots()
{
foreach (var bullet in BulletShoots)
{
if (bullet.shootTime > lastSpawnedBulletTime && bullet.source == Object.StateAuthority)
{
var bulletPrefab = (bullet.bulletPrefabIndex == -1) ? defaultProjectilePrefab : projectilePrefabs[bullet.bulletPrefabIndex];
var projectileGO = GameObject.Instantiate(bulletPrefab, bullet.initialPosition, bullet.initialRotation);
var projectile = projectileGO.GetComponent<PaintBlasterProjectile>();
projectile.sourceBlaster = this;
lastSpawnedBulletTime = bullet.shootTime;
}
}
}
발사체 게임 객체가 스폰 되어 총 자체에 등록됩니다.
이렇게 함으로써 우리는 발사체가 목표물에 명중하는지를 총의 상태 권한만이 확인하도록 할 수 있을 것입니다.
목록 크기를 제한하기 위해 PurgeBulletShoots()
메소드는 목록에서 오래된 샷을 제거합니다.
실제로 모든 샷이 목록에 추가되자마자 모든 플레이어에 의해 처리되었고 네트워크에서 전파가 이루어졌기 때문에 목록에 무한정 유지하는 것은 소용이 없습니다.
또한 상태 권한이 변경되었을 때, 새 기관은 이전 권한과 동일한 시간 기준(IStateAuthorityChanged.StateAuthorityChanged()
)을 가지고 있지 않기 때문에 샷의 목록은 클리어 됩니다.
충격
충격 파라미터를 저장하기 위해 네트워크 구조체 ImpactInfo
가 생성되었습니다.
C#
public struct ImpactInfo : INetworkStruct
{
public Vector2 uv;
public Color color;
public float sizeModifier;
public NetworkBehaviourId networkProjectionPainterId;
public float impactTime;
public PlayerRef source;
}
발사체가 목표물과 충돌할 때 RecentImpacts
네트워크 목록에 새로운 충격이 추가됩니다.
그러면 총기 상태 권한을 가진 플레이어는 CheckRecentImpacts()
메소드로 FixedUpdateNetwork()
의 목록을 확인하여 객체 텍스처 수정을 요청합니다.
C#
const int MAX_IMPACTS = 50;
[Networked]
Capacity(MAX_IMPACTS)]
public NetworkLinkedList<ImpactInfo> RecentImpacts { get; }
C#
void CheckRecentImpacts()
{
foreach (var impact in RecentImpacts)
{
if (impact.impactTime > lastRecentImpactTime && impact.source == Object.StateAuthority)
{
if (Runner.TryFindBehaviour<NetworkProjectionPainter>(impact.networkProjectionPainterId, out var networkProjectionPainter))
{
if (networkProjectionPainter.HasStateAuthority || Object.HasStateAuthority)
{
networkProjectionPainter.PaintAtUV(impact.uv, impact.sizeModifier, impact.color);
}
}
lastRecentImpactTime = impact.impactTime;
}
}
}
샷과 마찬가지로 영향 목록 크기를 제한하기 위해 PurgeRecentImpacts()
메소드는 목록에서 오래된 영향을 제거합니다.
실제로 모든 영향이 목록에 추가되자마자 모든 플레이어에 의해 처리되었고 네트워크에서 전파가 이루어졌기 때문에 목록에 모든 영향을 무한정 유지하는 것은 소용이 없습니다.
PaintBlasterProjectile
FixedUpdate()
동안 총기 상태 권한을 가진 플레이어는 발사체가 물체와 충돌하는지 확인합니다.
발사체가 물체와 충돌하지 않으면 미리 정의된 타이머 후에 소멸됩니다.
C#
private void Update()
{
if (Time.time > endOfLifeTime)
{
Destroy(gameObject);
}
}
발사체가 물체와 충돌하는 경우(레이캐스트 히트로 결정됨):
- 객체 텍스처를 수정할 수 있어야 하는 경우 영향 위치가 계산되어 네트워크에 연결된
RecentImpacts
목록에 추가됩니다(ProjectionPainter
&NetworkProjectionPainter
컴포넌트가 있는 객체) - 발사체가 사라집니다.
- 파티클 시스템이 있는 충격 프리팹은 작은 시각적 효과를 나타내기 위해 생성됩니다.
네트워크 Impact
변수 uv
필드는 RayCast
히트 textureCoord
필드로 채워집니다.
이 textureCoord
필드에 값을 지정하려면 몇 가지 조건이 필요합니다:
- 물체는 우리가 그리고자 하는 것과 같은 메시를 가진 메시 콜라이더를를 가지고 있어야 합니다.
- 메시를 포함하는 FBX에서 "Meshes > read/write" 파라미터는 true로 설정되어야 합니다.
NetworkProjectionPainter & ProjectionPainter
네트워크를 통한 텍스처 수정 동기화는 다음 단계로 요약할 수 있습니다:
NetworkProjectionPainter
는 발사체를 발사한 총으로부터PaintAtUV()
메소드의 질감 수정 요청을 수신했습니다.- 그런 다음 실제로 텍스처 수정을 수행할 로컬
ProjectionPainter
컴포넌트로 요청을 보냅니다. - 텍스처 수정이 종료되면 콜백에 의해
NetworkProjectionPainter
로 통지됩니다, - 객체 상태 권한을 가진 플레이어는 영향에 대한 모든 정보를 포함하는 네트워크 목록을 업데이트합니다,
- 원격 플레이어는 로컬
ProjectionPainter
컴포넌트를 사용하여 객체의 텍스처를 업데이트할 수 있습니다.
이제 어떻게 작동하는지 좀 더 자세히 알아보겠습니다.
단계 1 : 텍스처 변경 요청
먼저, NetworkProjectionPainter
은 발사체를 발사한 총으로부터 PaintAtUV()
메소드의 텍스처 수정 요청을 수신하였습니다.
객체에 대한 상태 권한을 가진 플레이어만이 최종 텍스처 상태를 유지할 책임이 있지만, 원격 플레이어가 상태 권한 없이 객체를 히트할 때 지연되지 않도록 이 원격 플레이어는 PrePaintAtUV()
방식으로 임시 텍스처 수정을 수행합니다.
C#
public void PaintAtUV(Vector2 uv, float sizeModifier, Color color)
{
if (Object.HasStateAuthority)
{
painter.PaintAtUV(uv, sizeModifier, color);
}
else
{
painter.PrePaintAtUV(uv, sizeModifier, color);
}
}
단계 2 : 텍스처 변경
로컬 ProjectionPainter
은 수신된 충격 파라미터(UV 좌표, 발사체의 크기 및 색상)를 사용하여 객체 텍스처 수정을 수행합니다.
텍스처 수정을 몇 가지 단어로 설명하면 다음과 같습니다:
- 페인트 할 물체의 질감이 일시적인 카메라 렌더 질감으로 대체됩니다,
- 각 히트 시 정확한 uv 좌표로 충격 브러시가 텍스처 앞에 표시.
- 그런 다음 플레이어가 새로운 샷 영향을 볼 수 있도록 렌더 텍스처를 업데이트하는 카메라에 의해 캡처됩니다,
- 정기적으로, 이전의 모든 영향을 포함하는 새로운 텍스처에 의해 결정적인 객체 텍스처가 업데이트됩니다. 이 작업은 리소스 소모를 피하기 위해 모든 히트에 대해 수행되지 않습니다.
전체 과정에 대한 자세한 설명은 이 문서텍스처 페인팅에서 확인할 수 있습니다.
단계 3 : 텍스처 변경 콜백
Awake()
동안 , ProjectionPainter
는 리스너(IProjectionListener
인터페이스를 구현한 NetworkProjectionPainter
)를 찾습니다.
C#
private void Awake()
{
listeners = new List<IProjectionListener>(GetComponentsInParent<IProjectionListener>());
}
따라서 텍스처 수정이 종료되면 NetworkProjectionPainter
에 알려줄 수 있습니다.
C#
public void PaintAtUV(Vector2 uv, float sizeModifier, Color color)
{
var paintPosition = UV2CaptureLocalPosition(uv);
Paint(paintPosition, sizeModifier, color);
foreach (var listener in listeners)
{
listener.OnPaintAtUV(uv, sizeModifier, color);
}
}
단계 4 : 목록에 새 영향을 추가하여 원격 플레이어에게 알립니다
객체에 대한 상태 권한을 가진 NetworkProjectionPainter
은 영향에 대한 모든 정보가 포함된 네트워크 배열을 업데이트할 수 있습니다.
C#
public void OnPaintAtUV(Vector2 uv, float sizeModifier, Color color)
{
if (Object.HasStateAuthority)
{
...
ProjectionInfosLinkedList.Add(new ProjectionInfo { uv = uv, sizeModifier = sizeModifier, color = color, paintId = nextStoredPaintId });
...
}
}
텍스처를 수정하는 데 필요한 영향 파라미터는 네트워크 ProjectionInfo
구조체에 저장됩니다.
모든 영향은 네트워크 목록 ProjectionInfosLinkedList
에 저장됩니다.
따라서 목록에 새로운 영향이 추가되면 모든 플레이어에게 알려집니다.
C#
public struct ProjectionInfo:INetworkStruct
{
public Vector2 uv;
public Color color;
public float sizeModifier;
public int paintId;
}
C#
[Networked(OnChanged = nameof(OnInfosChanged))]
[Capacity(MAX_PROJECTIONS)]
public NetworkLinkedList<ProjectionInfo> ProjectionInfosLinkedList { get; }
단계 5 : 원격 플레이어에 의해 업데이트된 텍스처
이제 원격 플레이어는 네트워크 목록 ProjectionInfosLinkedList
와 로컬 ProjectionPainter
컴포넌트를 사용하여 객체 텍스처를 업데이트할 수 있습니다.
C#
static void OnInfosChanged(Changed<NetworkProjectionPainter> changed)
{
changed.Behaviour.OnInfosChanged();
}
void OnInfosChanged()
{
...
painter.PaintAtUV(info.uv, info.sizeModifier, info.color);
...
}
Back to top