无法更改生成的游戏对象的Sprite(仅限客户端)

时间:2019-09-19 02:01:42

标签: c# unity3d sprite unity3d-unet

我试图实例化,生成,然后将sprite分配给Unity3D中的自定义GameObject。这些对象是通用的CardContainer,它们调用SetCard方法来为其提供自定义统计信息。调用SetCard还会为CardContainer分配其Sprite。

我的问题是,每当更改衍生的SpriteRenderer.sprite的{​​{1}}时,sprite更改都不会影响客户端实例。

它似乎也不能反映我是否在生成对象之前对GameObject 进行了任何更改。这有可能改变精灵,我该怎么办?

我已经设置了一些小型poc测试,但到目前为止没有任何效果。他们在这里:

Sprite

1 个答案:

答案 0 :(得分:1)

实际上,这很棘手,取决于您的情况。

最好的情况是,您事先知道可用的Sprite并将其存储,例如然后在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仅允许afaik 64kb的NetworkBuffer,所以任何更大的图像/纹理(+其余数据!)将无法以这种方式进行传输,并且会变得更加复杂。

    在这方面还请注意,EncideToXY的数据大小通常比原始图像大bigger

  • 我现在还不确定SpawnRpc_AfterSpawn的执行顺序在网络上是否可靠。在实际完成Rpc_AfterSpawn之前,Spawn可能会到达客户端。