I am creating something like in the sims where you have a character and you can add different clothes to the character.
All the models I use are using the same bones and rig. I have put these models in different asset bundles. At runtime I read the asset bundles and combine these models in one SkinnedMeshRenderer and thus in one object. But every time I add another object the bone count goes up.
I know why this is happening, but I would like to reduce the number of bones again.
I tried to find the duplicate bones and delete those and the bindpose on the same index as the bone deleted, but this still gives me the error "Number of bindposes doesn't match number of bones" even though they are both 45.
Here is the code that attaches the models:
private void UpdateSkinnedMesh()
{
float startTime = Time.realtimeSinceStartup;
// Create a list of for all data
List combineInstances = new List();
List materials = new List();
List bones = new List();
Transform[] transforms = baseModel.GetComponentsInChildren();
//TEMP
List elements = new List();
elements.Add(body);
if(_currentActiveProps.ContainsKey(ItemSubCategory.Skin))
elements.Add(tutu);
//Go over all active elements
foreach (CharacterElement element in elements)
{
//Get the skinned mesh renderer
SkinnedMeshRenderer smr = element.GetSkinnedMeshRenderer();
//Add materials to the entire list
materials.AddRange(smr.materials);
//Add all submeshes to a combined mesh
for (int sub = 0; sub < smr.sharedMesh.subMeshCount; sub++)
{
CombineInstance ci = new CombineInstance();
ci.mesh = smr.sharedMesh;
ci.subMeshIndex = sub;
combineInstances.Add(ci);
}
//Bones are not saved in asset bundle, get the names and reference them again
foreach (string bone in element.GetBoneNames())
{
foreach (Transform transform in transforms)
{
if (transform.name != bone) continue;
bones.Add(transform);
break;
}
}
//Destroy the temp object
Destroy(smr.gameObject);
}
//Get skinned mesh renderer
SkinnedMeshRenderer r = baseModel.GetComponentInChildren();
//Create enew combined mesh
r.sharedMesh = new Mesh();
r.sharedMesh.CombineMeshes(combineInstances.ToArray(), false, false);
//Add bones and materials
r.bones = bones.ToArray();
r.materials = materials.ToArray();
Debug.Log("Bindposes: " + r.sharedMesh.bindposes.Length);
Debug.Log("Generating character took: " + (Time.realtimeSinceStartup - startTime) * 1000 + " ms");
Debug.Log("Bone count: " + r.bones.Length);
}
I already have asked the question on Unity Answers, but because it takes a few hours before a moderator approves the question, I wanted to try here as well.
Related
My goal was to create an import script (AssetPostprocessor), that handles a few things for me whenever I drag a spritesheet (a picture that contains multiple frames for an animation) into a specific Unity folder.
I want the script to split the picture up into multiple frames (similar to when its done manually, with Sprite Mode = Multiple), and then create an animation out of it. I parse the instructions (name, sprite frame size, frame holds) from the file name that gets handled.
Example: "Player_Walk_200x100_h3-3-3-3-3.png"
So far, I managed to accomplish all of those points, but whenever I create an animation and link the given Sprites to it, the resulting animation is "empty". As far as I could figure out, that's because the Texture2D and Sprite[] given to OnPostprocessSprites() seems to be temporary. The object exists during creation, but seemingly gets discarded later on. How can I solve this? How can I grab a reference to Sprite[] that doesn't get dropped?
Here is a stripped down version of my current core:
void OnPostprocessTexture( Texture2D texture ) {
// the texture is split into frames here, code not included since this part works fine and would complicate things.
}
void OnPostprocessSprites( Texture2D texture, Sprite[] sprites ) {
if( !this.assetPath.Contains( 'spritetest' ) ) return;
Debug.Log( "Number of Sprites: " + sprites.Length ); // shows the correct number of sprites
if( sprites.Length == 0 ) {
AssetDatabase.ImportAsset( this.assetImporter.assetPath );
return;
}
int frameRate = 24;
AnimationClip clip = new AnimationClip();
clip.frameRate = frameRate;
EditorCurveBinding spriteBinding = new EditorCurveBinding();
spriteBinding.type = typeof( SpriteRenderer );
spriteBinding.path = "";
spriteBinding.propertyName = "m_Sprite";
ObjectReferenceKeyframe[] spriteKeyFrames = new ObjectReferenceKeyframe[ sprites.Length ];
for( int i = 0; i < sprites.Length; i++ ) {
spriteKeyFrames[ i ] = new ObjectReferenceKeyframe();
spriteKeyFrames[ i ].time = i;
spriteKeyFrames[ i ].value = sprites[ i ]; // these sprites are empty in the editor
}
AnimationUtility.SetObjectReferenceCurve( clip, spriteBinding, spriteKeyFrames );
AssetDatabase.CreateAsset( clip, "Assets/Assets/spritetest/Test.anim" );
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
I've looked up forum posts, documentations, and even questions here (like Unity Editor Pipeline Scripting: how to Pass Sprite to SpriteRenderer in OnPostprocessSprites() Event? ) but none really managed to resolve this problem: whenever I try to grab the "real" sprite or texture via AssetDatabase, the resulting object is always just null.
For example, if I try doing this:
// attempt 1
Sprite sprites = AssetDatabase.LoadAssetAtPath<Sprite>( this.assetPath );
Debug.Log( "Real Sprite: " + sprite ); // sprite is null
// attempt 2
Object[] objects = AssetDatabase.LoadAllAssetRepresentationsAtPath( this.assetPath );
Debug.Log( "Real Objects: " + objects.Length ); // is always 0
Calling AssetDatabase.SaveAssets() or AssetDatabase.Refresh() beforehand doesn't change the result. I find plenty of people with similar issues, but no code or examples that seem to really resolve this issue.
Here is another person with a similar issue: ( source: https://answers.unity.com/questions/1080430/create-animation-clip-from-sprites-programmaticall.html )
The given link didn't resolve my problem either.
Essentially: How can I create an animation from an imported asset, by using my generated sprites, in AssetPostprocessor?
I am attempting to instantiate differently shaped prefabs next to eachother. I have a folder full of differently sized objects that allow for me to string them together randomly to create a 2D endless runner. My issue is that all the answers online about placing objects beside eachother don't work when different sized objects are used. Here is the code I am using to instantiate the sections. It works when it only randomly selects similar sized objects. When the selected prefab is a different length it leaves either a gap between, or overlaps, the previous object.
for(int i = 0; i < activeSectionsAtOnce; i++)
{
int index = Random.Range(0, sectionPrefabs.Length);
GameObject currentOb = sectionPrefabs[index];
Vector2 position = new Vector2();
if(activeSections.Count == 0)
{
//set a (0, 0) position for first object
position = Vector2.zero;
}
else
{
//get most recently placed object from list
GameObject lastOb = activeSections[activeSections.Count - 1];
//my attempt at getting bounds for the last object and for the next prefab that will be used
Bounds lasObbounds = new Bounds(lastOb.transform.position, Vector2.zero);
Bounds currentBounds = new Bounds(currentOb.transform.position, Vector2.zero);
//Include child objects(all have sprite renderers)
foreach (Renderer r in lastOb.GetComponentsInChildren<Renderer>())
{
lasObbounds.Encapsulate(r.bounds);
}
foreach (Renderer rend in currentOb.GetComponentsInChildren<Renderer>())
{
currentBounds.Encapsulate(rend.bounds);
}
//position object will be instantiated at
position.x = lastOb.transform.position.x + (lasObbounds.size.x / 2) + (currentBounds.size.x/2);
}
//instantiate at selected position
activeSections.Add(
Instantiate(currentOb, position, Quaternion.identity)
);
}
This is in the start method. I'm not sure if the problem is how I'm creating the bounds, or how im mathematicaly creating the x coordinate. This is the expected result that happens when I use same sized prefabs. This is what happens with different prefabs
The shorter protrusion from the first object should be cleanly between the other two without a gap.
Here's an image of one prefab
Thanks for any advice
I'm trying to combine "seats prefabs" into a single mesh at runtime to save some draw calls.
My seat contains 2 MeshFilter, a base and a top (= the seat, red on the picture) with 2 different materials. My seat is created from an imported FBX which has read/Write enabled.
I'm using pooling if it matters : I generate 500 seats, then I pick 250 from the pool to create my stands, then I combine them, release the pooled seats and reuse them to combine another stands.
Here is the issue that appears on some seats (not all, only the 5th and 6th stands so maybe linked to some pooling issue ? reusing the same mesh several times), some "tops" of seats are "linked" together
Here is the function I use :
List<CombineInstance> baseList = new List<CombineInstance>();
List<CombineInstance> topList = new List<CombineInstance>();
Material materialTop = null;
Material materialBase = null;
//Loop through the array with trees
for (int i = 0; i < seats.Count; i++)
{
GameObject currentSeat = seats[i];
//Get all meshfilters from this seat, true to also find deactivated children
MeshFilter[] meshFilters = currentSeat.GetComponentsInChildren<MeshFilter>(true);
//Loop through all children
for (int j = 0; j < meshFilters.Length; j++)
{
MeshFilter meshFilter = meshFilters[j];
CombineInstance combine = new CombineInstance();
//Is it top or base?
MeshRenderer meshRender = meshFilter.GetComponent<MeshRenderer>();
string materialName = meshRender.gameObject.name;
if (materialName.Contains("top"))
{
combine.mesh = meshFilter.mesh;
combine.transform = meshFilter.transform.localToWorldMatrix;
//Add it to the list of leaf mesh data
topList.Add(combine);
if (materialTop == null) materialTop = meshRender.material;
}
else if (materialName.Contains("base"))
{
combine.mesh = meshFilter.mesh;
combine.transform = meshFilter.transform.localToWorldMatrix;
//Add it to the list of wood mesh data
baseList.Add(combine);
if (materialBase == null) materialBase = meshRender.material;
}
}
//Deactivate the seat the pool
GameController.This.Prefabs.DestroyPooledObject(currentSeat);
}
//First we need to combine the bases into one mesh and then the tops into one mesh
Mesh combinedBaseMesh = new Mesh();
combinedBaseMesh.CombineMeshes(baseList.ToArray());
Mesh combinedTopMesh = new Mesh();
combinedTopMesh.CombineMeshes(topList.ToArray());
//Create the array that will form the combined mesh
CombineInstance[] totalMesh = new CombineInstance[2];
//Add the submeshes in the same order as the material is set in the combined mesh
totalMesh[0].mesh = combinedBaseMesh;
totalMesh[0].transform = parent.transform.localToWorldMatrix;
totalMesh[1].mesh = combinedTopMesh;
totalMesh[1].transform = parent.transform.localToWorldMatrix;
//Create the final combined mesh
Mesh combinedAllMesh = new Mesh();
//Make sure it's set to false to get 2 separate meshes
combinedAllMesh.CombineMeshes(totalMesh, false);
var meshFilterParent = parent.GetComponent<MeshFilter>();
if (meshFilterParent == null) meshFilterParent = parent.AddComponent<MeshFilter>();
meshFilterParent.sharedMesh = combinedAllMesh;
var meshRenderer = parent.GetComponent<MeshRenderer>();
if (meshRenderer == null) meshRenderer = parent.AddComponent<MeshRenderer>();
meshRenderer.materials = new
Material[]
{
materialBase,
materialTop
};
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
trying to make a game, i need to make a spawner spawns objects in four colors randomly when we click Fire1 button, in limit number for example 4 times red for time blue.. Etc but randomly and the spawner shows the coming color or the next color, i tried many things but didn't work for me. Any help plz.
I can give you an idea of how to complete it -
First things first I'd say create the 4 game objects you'd like to use in your game in your editor, and assign the appropriate 4 colours in the objects material that you'd like to use.
Create a new folder in your assets called 'Resources' Then Create a Prefab for these objects by dragging them from your project hierarchy into your Resources folder.
Now Create a new empty gameobject and a new C# script in your assets folder, and drag the C# script onto the new empty game object. Inside the C# Script you want a few methods, the following should do the trick given that the prefabs are called shape1, shape2 etc.
//shapes to be spawned
public List<GameObject> shapes;
//shapes that are in game
public List<GameObject> spawnedShape;
//num of next shape to be places
public int next;
//list of shapes that can be spawned
public List<int> shapeCount;
System.Random random;
void Start()
{
random = new System.Random();
for (int i = 0; i < 4; i++)
{
//adds shapes that can be added into shapecount list.
for(int j = 0; j < 4; j++)
{
shapeCount.Add(i);
}
//load game objects from resources into list
GameObject go = Resources.Load("shape" + i) as GameObject;
shapes.Add(go);
}
//set next shape
int next = setNext();
}
void Update()
{
if (Input.GetButton("Fire1")) {
spawnShape();
}
}
public int setNext()
{
if (shapeCount.Count != 0)
{
int num = random.Next(0, shapeCount.Count);
return shapeCount[num];
}
else return -1;
}
void spawnShape()
{
if(next == -1)
{
Debug.Log("Out of shapes");
}else
{
//creates instance of shape
GameObject go = Instantiate(shapes[next]);
//creates and assigns random x, y coordinate
int x = random.Next(0, 500);
int y = random.Next(0, 500);
go.transform.position = new Vector3(x, y, 0);
spawnedShape.Add(go);
}
//gets colour of next shape
next = setNext();
}
Just adjust the code to fit your project, there are more efficient ways of doing it for instance having one shape and adding different materials however this way can allow for all different shapes.
I am trying to make a Scrabble word game using a fixed camera, but I have a simple issue.
I add some boxes as game objects and the number of these boxes is the length of the word so if the word is "Fish" we will add 4 boxes dynamically. I did that successfully, but I can't center those boxes in the screen. I tried to add those game objects as children under another game object, then center the parent, but with no effect.
This is my code:
void Start () {
for (int i=0; i<word.Length; i++) {
GameObject LetterSpaceObj = Instantiate(Resources.Load ("LetterSpace", typeof(GameObject))) as GameObject;
LetterSpaceObj.transform.parent = gameObject.transform;
LetterSpaceObj.transform.localPosition = new Vector2(i*1.5f,0.0f);
LetterSpaceObj.name = "LetterSpace-"+count.ToString();
count++;
}
gameObject.transform.position = new Vector2 (0.0f, 0.0f);
}
This image shows you the idea:
I believe your code is working, but the problem is that your first letter is located at your parent object, and then every letter from then on is added to the right. That means that when you center the parent object what you are doing is putting the first letter in the center of the screen.
If you run the game and use the scene view to look at where the parent object is this can confirm this. What you can do instead is that rather than placing the parent at the center of the screen, offset it by an amount equal to the length of the word.
gameObject.transform.position = new Vector2 (-(word.Length / 2.0f) * 1.5f, 0.0f);
You might also want to consider changing some of those constants, such as the 1.5f into variables with names like LetterSize or basing it off the actual prefab so that way any future changes will work automatically.
This is the last solution after some edits to fix this issue.
GameObject LetterSpaceObjRow;
int count = 1;
string word = "Father";
float ObjectXPos;
float LocalScaleX;
void Start () {
for (int i=0; i<word.Length; i++) {
GameObject LetterSpaceObj = Instantiate(Resources.Load ("LetterSpace", typeof(GameObject))) as GameObject;
LocalScaleX = LetterSpaceObj.transform.localScale.x;
ObjectXPos = i*(LocalScaleX+(LocalScaleX/2));
LetterSpaceObj.transform.parent = gameObject.transform;
LetterSpaceObj.transform.localPosition = new Vector2(ObjectXPos,0.0f);
LetterSpaceObj.name = "LetterSpace-"+count.ToString();
count++;
}
gameObject.transform.position = new Vector2 (-(ObjectXPos/2.0f) , 0.0f);
}