C# Can';t更改衍生游戏对象的精灵(仅限客户端)
我试图在Unity3D中实例化、生成一个精灵,然后将其分配给一个自定义的C# Can';t更改衍生游戏对象的精灵(仅限客户端),c#,unity3d,sprite,unity3d-unet,C#,Unity3d,Sprite,Unity3d Unet,我试图在Unity3D中实例化、生成一个精灵,然后将其分配给一个自定义的游戏对象。这些对象是一个通用的CardContainer,它调用SetCard方法为其提供自定义统计信息。调用SetCard也会将CardContainer分配给它的精灵 我的问题是,每当我更改生成的游戏对象的spriteender.sprite时,sprite更改不会对客户端实例产生影响 它似乎也没有反映在生成对象之前是否对精灵进行了任何更改。这可以改变雪碧吗?我怎么做 我已经设置了一些小的poc测试,但到目前为止没有任何
游戏对象。这些对象是一个通用的CardContainer
,它调用SetCard
方法为其提供自定义统计信息。调用SetCard
也会将CardContainer
分配给它的精灵
我的问题是,每当我更改生成的游戏对象
的spriteender.sprite
时,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;
//cardContainerTesting
Vector3 testingContainerCoords=新建
向量3(0,1,-1);
GameObject testingCardObjectInstance=实例化(testingCardCOntainerGameObject、testingContainerCoords、Quaternion.identity);
NetworkServer.Spawn(testingCardObjectInstance);
spriteender objectSprite=testingCardObjectInstance.GetComponent();
objectSprite.sprite=testingSprite1;
//GenericGameObjectExampleTesting
向量3原点=新向量3(0,0,-1);
GameObject InstancedPrefab=实例化(myPrefableExample,origin,Quaternion.identity);
NetworkServer.Spawn(实例化预制);
SpriteRender示例SpriteRender=实例化预制.GetComponent();
示例spriterenderer.sprite=测试sprite2;
事实上,这相当棘手,取决于你的情况
最好的情况是事先知道哪些精灵可用,并将其存储在列表中。。然后,您可以通过在生成的对象上设置一个[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];
}
}
现在,目标精灵对象用正确的精灵初始化自身
另外,使用钩子
现在,每当服务器上的索引更改时,它实际上会更改。因此,现在您甚至可以在运行时动态切换精灵,只需分配一个新索引:
private void Update()
{
if(!isServer || !Input.GetKeyDown(KeyCode.ArrowUp)) return;
SpriteIndex = (SpriteIndex + 1) % Sprites.Count;
}
另一种方法可能包括传输实际的纹理2d
数据。这有点棘手,因为通过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;
}
//我们要转移的精灵
公共精灵目标精灵;
//产卵的预制场
//在这里直接使用组件类型,这样我们就不用调用一个GetComponent
公共喷灯示例预制件;
[命令]
public void Cmd_Spawn()
{
//在服务器上
var obj=实例化(ExamplePrefact);
//在服务器上,立即设置精灵
对象精灵=目标精灵;
//产卵(精灵尚未设置)
NetworkServer.Spawn(obj.gameObject);
//告诉客户端设置精灵并传递所需数据
Rpc_afterpawn(obj.gameObject,targetSprite.texture.EncodeToPNG(),newspriteinfo(targetSprite));
}
[可序列化]
私有结构SpriteInfo
{
公共Rect;
公共向量2支点;
公共精灵信息(精灵精灵精灵)
{
rect=sprite.rect;
pivot=sprite.pivot;
}
}
[客户端RPC]
私有void Rpc_afterpawn(游戏对象targetObject,字节[]纹理数据,SpriteInfo SpriteInfo)
{
//关于客户
//初始宽度和高度无关紧要
//它们将被加载覆盖
//此外,jpg数据的纹理格式将自动为RGB24
//png为RGBA32
var纹理=新纹理2d(1,1);
//将字节[]加载到纹理中
纹理。加载图像(纹理数据);
var newSprite=Sprite.Create(纹理、spriteInfo.rect、spriteInfo.pivot);
//最后在所有客户端上设置精灵
targetObject.GetComponent().sprite=newSprite;
}
但是请注意:
- 这也是非常有限的,因为UNet只允许64kb的网络缓冲区,所以任何较大的图像/纹理(+其余数据!)都不可能以这种方式传输,而且会变得更复杂
在这方面还要注意,
ency
通常会导致数据大小大于原始图像
- 我现在也不确定
Spawn
和Rpc_afterpawn
的执行顺序在网络上是否可靠。可能发生的情况是,Rpc_afterpawn
在Spawn
实际完成之前到达客户端
您是否事先知道可能的精灵,以便将它们存储在列表或字典中?我的所有精灵都存储在我的“SpriteCollection”字典中,可以通过我的“SetCard”功能从和CardContainer中收集。在我尝试多人游戏之前,它一直工作得很好。在我的另一条评论中,我的角色已经用完了,但我只想感谢大家。你的帖子写得很彻底,一定花了一段时间。我真的很感激!非常感谢。在这个问题上,我尴尬地被困了很长时间。我今天一直没能按照你的指示去做。我已将您的代码一字不差地粘贴到我的项目中。我现在得到一个精灵出现在我的客户,但不是正确的一个。出现的精灵是我的“精灵”列表中的第0项,而不是我输入的第一项或任何其他索引。它是在主机中找到正确的精灵。有什么想法吗?在我看来,SpriteIndex似乎没有被正确地共享。我将你的启动函数代码添加到更新函数中,以确保它运行,但仍然无效。嘿,对不起。。不确定了,因为UNet是很久以前的事了。。。我非常确定它使用Start
工作,而且在调用Start
之前,应该在Spawn之后同步[SyncVar]
。这对我也很管用。。但可能这是一个运行时“bug”,只对我有效,因为我在同一台PC上进行测试。然而,我更新了代码,改为使用钩子和[ClientC]
// 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;
}