Невозможно изменить спрайт созданного игрового объекта (только для клиента) - PullRequest
0 голосов
/ 19 сентября 2019

Я пытаюсь создать экземпляр, порождать, а затем назначить спрайт для пользовательского GameObject в Unity3D.Объекты - это обобщенный CardContainer, который вызывает метод SetCard для предоставления ему собственной статистики.Вызов SetCard также присваивает CardContainer его спрайт.

Моя проблема в том, что всякий раз, когда я изменяю SpriteRenderer.sprite порожденного GameObject, изменение спрайта не влияет на экземпляр клиента.

Это также, кажется, не отражает, внес ли я какие-либо изменения в Sprite до Я породил объект.Можно ли изменить спрайт и как я могу это сделать?

Я настроил несколько небольших тестов POC, но до сих пор ничего не получалось.Вот они:

//cardContainerTesting
Vector3 testingContainerCoords= new 
Vector3(0, 1, -1);
GameObject testingCardObjectInstance = Instantiate(testingCardCOntainerGameObject, testingContainerCoords, Quaternion.identity); 
NetworkServer.Spawn(testingCardObjectInstance);
SpriteRenderer objectSprite = testingCardObjectInstance.GetComponent<SpriteRenderer>();
objectSprite.sprite = testingSprite1;


//GenericGameObjectExampleTesting
Vector3 origin = new Vector3(0, 0, -1);
GameObject instantiatedPrefab = Instantiate(myPrefabExample, origin, Quaternion.identity);
NetworkServer.Spawn(instantiatedPrefab);
SpriteRenderer exampleSpriteRenderer = instantiatedPrefab.GetComponent<SpriteRenderer>();
exampleSpriteRenderer.sprite = testingSprite2;

1 Ответ

1 голос
/ 19 сентября 2019

На самом деле это довольно сложно и зависит от вашего случая.

В лучшем случае вы должны заранее знать, какие спрайты доступны, и сохранить их, например, в List<Sprite> .. тогда вы можете просто сказатьклиенты, которые используют спрайт, устанавливая, например, [SyncVar] для порожденного объекта.Что-то вроде

// on the spawned object
public class SpriteController : NetworkBehaviour
{
    // Also good if you reference this already in the Inspector
    [SerializeField] private SpriteRenderer spriteRenderer;

    // Configured via the Inspector befrorehand
    public List<Sprite> Sprites;

    // Whenever this is changed on the Server
    // the change is automatically submitted to all clients
    // by using the "hook" it calls the OnSpriteIndexChanged and passes
    // the new value in as parameter
    [SyncVar(hook = nameof(OnSpriteIndexChanged))] public int SpriteIndex;

    // Will be called everytime the index is changed on the server
    [ClientCallback]
    private void OnSpriteIndexChanged(int newIndex)
    {
        // First when using a hook you have to explicitly apply the changed value at some point
        SpriteIndex = newIndex;

        if (!spriteRenderer) spriteRenderer = GetComponent<SpriteRenderer>();
        spriteRenderer.sprite = Sprites[SpriteIndex];
    }
}

и затем, например, сделать

// If you make this of type SpriteController the Inspector automatically
// references the correct component and you can get rid of the GetComponent call later
public SpriteController testingCardCOntainerGameObject;

var testingCardObjectInstance = Instantiate(testingCardCOntainerGameObject, testingContainerCoords, Quaternion.identity); 

// for testing use 1 since 0 is the default for int ;)
testingCardObjectInstance.SpriteIndex = 1;

NetworkServer.Spawn(testingCardObjectInstance);

Теперь целевой объект-спрайт инициализируется с правильным спрайтом.

Кроме того, используя hook, теперь онфактически изменяется каждый раз, когда изменяется индекс на сервере .Так что теперь вы даже можете динамически переключать Sprite во время выполнения, просто назначив новый индекс:

private void Update()
{
    if(!isServer || !Input.GetKeyDown(KeyCode.ArrowUp)) return;

    SpriteIndex = (SpriteIndex + 1) % Sprites.Count;
}

enter image description here


Альтернативой может бытьпри передаче фактических Texture2D данных.Это немного сложно, поскольку разрешенные параметры / типы данных, передаваемые через UNet, очень ограничены

// the sprite we will transfer
public Sprite targetSprite;

// the prefab to spawn
// directly use the component type here so we get rid of one GetComponent call
public SpriteRenderer examplePRefab;

[Command]
public void Cmd_Spawn()
{
    // ON SERVER

    var obj = Instantiate(examplePRefab);
    // on the server set the sprite right away
    obj.sprite = targetSprite;

    // spawn (sprite will not be set yet)
    NetworkServer.Spawn(obj.gameObject);

    // tell clients to set the sprite and pass required data
    Rpc_AfterSpawn(obj.gameObject, targetSprite.texture.EncodeToPNG(), new SpriteInfo(targetSprite));
}

[Serializable]
private struct SpriteInfo
{
    public Rect rect;
    public Vector2 pivot;

    public SpriteInfo(Sprite sprite)
    {
        rect = sprite.rect;
        pivot = sprite.pivot;
    }
}

[ClientRpc]
private void Rpc_AfterSpawn(GameObject targetObject, byte[] textureData, SpriteInfo spriteInfo)
{
    // ON CLIENTS

    // the initial width and height don't matter
    // they will be overwritten by load
    // also the texture format will automatically be RGB24 for jpg data
    // and RGBA32 for png
    var texture = new Texture2D(1, 1);
    //  load the byte[] into the texture
    texture.LoadImage(textureData);
    var newSprite = Sprite.Create(texture, spriteInfo.rect, spriteInfo.pivot);

    // finally set the sprite on all clients
    targetObject.GetComponent<SpriteRenderer>().sprite = newSprite;
}

enter image description here

Обратите внимание, однако :

  • Это также очень ограничено, так как UNet допускает только сетевой буфер размером 64 кбайт, поэтому любое большее изображение / текстуру (+ остальные данные!) Будет невозможно передать таким способом.и это станет более сложным.

    Также обратите внимание, что EncideToXY часто приводит к большему размеру данных, чем исходное изображение.

  • Я также не уверен прямо сейчас, будет ли порядок выполнения Spawn и Rpc_AfterSpawn надежным в сети.Может случиться, что Rpc_AfterSpawn достигнет клиентов до того, как Spawn будет фактически выполнен.

...