I thought about how can I get "my carpet" rolled on a certain distance. I made 3D model in Blender and animated it using ShapeKeys and then I imported it into Unity and got everything working. Only thing now that left me struggling is how to animate carpet so that when I score certain amount of point "my carpet" rolls out according to that same score. I found some code for that, but I can't find what is a propertyName for Blend shapes in SkinnedMeshRenderer type. Here is my code...
public class AnotherTestingScript : MonoBehaviour
{
private void Start()
{
Animation anim = GetComponent<Animation>();
AnimationCurve curve;
// create a new AnimationClip
AnimationClip clip = new AnimationClip();
clip.legacy = true;
Keyframe[] keys;
keys = new Keyframe[3];
keys[0] = new Keyframe(0.0f, 0.0f);
keys[1] = new Keyframe(100f, 100f);
curve = new AnimationCurve(keys);
clip.SetCurve("", typeof(SkinnedMeshRenderer), "Blend Shape.Key 1", curve);
// now animate the GameObject
anim.AddClip(clip, clip.name);
anim.Play(clip.name);
}
}
Where it says: "clip.SetCurve("", typeof(SkinnedMeshRenderer), "Blend Shape.Key 1", curve);"
I think that there is a problem with propertyPath, I cannot find the right way to call it. So if you have better solution to my entire problem it would mean a lot to me, and if you have the name of propertyName for BlendShapes thank you very much!
Related
right now I'm working on a planet generation project in the Unity engine.
The problem I have happens when I want to place plants on the planet surface.
My approach is to cast rays from the center of the planet out towards the surface to get the positions of the plants (because the surface is no plain sphere, so I don't know the exact positions). But I don't get any hits.
So I tried several things to fix that. And what I found is quite strange for me.
When I create a Ray using "new Ray", I get nothing. But with "UnityEngine.Camera.main.ScreenPointToRay" I get some expected hits. So it cannot be an issue about the GameObject layers.
For me it looks like using "new Ray" is buggy or something.
Anyone here to explain this?
public void CreatePlants(List<PlantDefinition> plantSpeciesDefinitions)
{
foreach (PlantDefinition plantDefinition in plantSpeciesDefinitions)
{
directions = new Vector3[plantDefinition.maximumCount];
for (int i = 0; i < plantDefinition.maximumCount; i++)
{
Vector3 direction = new Vector3(Random.Range(-1f, 1f), Random.Range(-1f, 1f), Random.Range(-1f, 1f));
direction.Normalize();
directions[i] = direction;
Ray ray = new Ray(planetCenter, directions[i]); // does not give any hits
Ray ray2 = UnityEngine.Camera.main.ScreenPointToRay(Input.mousePosition); // does give hits
Physics.Raycast(ray2, out RaycastHit hit);
if(hit.collider != null)
Debug.LogWarning($"Object hit: {hit.collider.gameObject.name}");
}
}
}
By default raycasts in Unity do not hit the "inside" / backfaces of a Collider.
You start your ray inside a sphere and want to hit the surface from the inside.
You will have to enable Physics.queriesHitBackfaces either via code
private void Awake ()
{
Physics.queriesHitBackfaces = true;
}
or via the Physics Settings
private Color solveColor;
void Start()
{
Color[] colors = { Color.cyan, Color.red, Color.green, new Color(245, 195, 29), Color.yellow, Color.magenta };
int lengthOfColors = colors.Length;
int solveColor = UnityEngine.Random.Range(0, lengthOfColors);
}
private void start()
{
GetComponent<MeshRenderer>().material.color = solveColor;
}
private void FixedUpdate()
{
// Set the balls speed when it should travel
if (isTraveling) {
rb.velocity = travelDirection * speed;
}
// Paint the ground
Collider[] hitColliders = Physics.OverlapSphere(transform.position - (Vector3.up/2), .05f);
int i = 0;
while (i < hitColliders.Length)
{
GroundPiece ground = hitColliders[i].transform.GetComponent<GroundPiece>();
if (ground && !ground.isColored)
{
ground.Colored(solveColor);
}
The above code is supposed to pick one color from the colors array and assign it to both the ball and balls painting ability (whenever the ball collides with the ground it changes its color) however the paint the ball leaves is always black and the ball itself is always orange (pretty sure the ball color is coming from its default). I can't figure out why this is happening any help is very appreciated.
Thank you for your time
In the code you provided, nowhere do you set the material color of the ball again aside from Start. If you want to have the particles behind the ball leave different colors, you will need to instantiate a new instance of the material. The reason for this is because materials in Unity are default shared between all instances of that particular material.
All of this info and a bit more can be found on the Material docs page.
As you have a fixed size of colors you are using, I would instead create 6 new materials and make an array of materials instead. Now, instead of randomly picking a color, pick a material and assign it to the ball or your new instanced painting ability. I am also confused as to why you are placing your array of colors inside of your Start function. It would be localized to that function only then. You also appear to have two Start functions, which is odd. One being the Monobehaviour Start and another start. Unless that is intended, your second start will not be run unless you call it.
Now to get to the solution I was talking about.
// assign these in the inspector to your new materials
[SerializeField] private List<Material> materials = new List<Material>();
private MeshRenderer meshRender;
private void Start()
{
meshRenderer = GetComponent<MeshRenderer>();
// set our first random Material
SetNewMaterialColor();
}
private void SetNewMaterialColor()
{
meshRenderer.material = GrabNewMaterial();
}
private void FixedUpdate()
{
// Set the balls speed when it should travel
if (isTraveling) {
rb.velocity = travelDirection * speed;
}
// Paint the ground
Collider[] hitColliders = Physics.OverlapSphere(transform.position - (Vector3.up/2), .05f);
int i = 0;
while (i < hitColliders.Length)
{
GroundPiece ground = hitColliders[i].transform.GetComponent<GroundPiece>();
if (ground && !ground.isColored)
{
// change this from a color to a material instead
ground.Colored(meshRenderer.material);
// set a new material to your main object
SetNewMaterialColor();
}
}
}
private Material GrabNewMaterial()
{
return materials[UnityEngine.Random.Range(0, materials.Count)];
}
You will need to change your Colored function to take in a Material instead of a Color. If you want the implementation to be more dynamic, you can instead create an instance of your material and set the color dynamically, but as you have a fixed size of colors I do not think you need to do that.
Edit: The one other option which involves creating a new shader would be to utilize [PerRendererData] meaning each object for a property field is rendered individually. I would go with the previous option as either option using shaders or instanced materials is a bit more complex.
You would need to use a MaterialPropertyBlock and can then assign the color when you want. It would look something like
public void SetNewColor()
{
// create a new material property block
MaterialPropertyBlock tmpBlock = new MaterialPropertyBlock();
// grab the current block from our renderer
meshRender.GetPropertyBlock(tmpBlock);
// set our changes to the block
tmpBlock.SetColor("_Color", YourColorHere);
// now apply our changes
tmpRend.SetPropertyBlock(tmpBlock);
}
And you would need to create a new shader that laters the Main Color property by using the PerRendererData attribute.
Properties
{
[PerRendererData]_Color("Main Color", Color) = (1,1,1,1)
...
Also, one other question I have is why you are using Physics.OverlapSphere instead of just an OnCollisionEnter? Or if your game is 2D, then OnCollisionEnter2D and let the physics engine handle how collisions work, then just change the colors when the collision occurs?
Edit: Here are the answer to your questions - let me know if you have more.
In the line "[SerializeField] private List materials = new
List();" which section do I need to replace with the
materials and how?
The line as is is fine. By using [SerializeField] it exposes this list to the editor. You will want to create several new duplicate materials that use your 6 different colors. Instead of setting the colors, you will be setting materials now. What I mean by inspector and editor is you can find the object that has this script on it in Unity, select it (it must be a Prefab or in the scene), then a tab of the Unity editor will populate with information about this object. Find the script portion and find the field materials. There should be a drop-down arrow, click it and set the number to 6 (or however many material swaps you want). Now create 6 new materials with your colors and drag them into the boxes that appeared.
Would it be something like writing "./Materials/Ball 1" in the () for
example?
Nope! You would be assigning this data in the inspector, so the data would be stored in the list without referencing them in code.
And I'm not sure how to assign this to my ball using "[SerializeField]
private GameObject paintObject = null;"
Similarly, this would appear in the inspector. However, remove this line as I misunderstood your original question and accidentally left this in. I assumed that your paint object was a Prefab that you were spawning after the ball bounced, not the ground that you were changing the color of.
I get the error "Argument 1: cannot convert from
'UnityEngine.Material' to 'UnityEngine.Color'"
Yep! So as I mentioned in the comments, your function call to your paint object is most likely currently taking a Color parameter. As I changed your implementation to instead directly set Material, you will need to change how that function signature. Specifically the line:
ground.Colored(meshRenderer.material);
You have some object ground that is of type GroundPiece and has a function called Colored. I assume it currently look something like:
public void Colored(Color color){...}
You want to change this instead to:
public void Colored(Material mat{...}
After changing it, instead of changing the ground's color in this script, you would change its material directly. Let me know if you have more questions.
I've created a text mesh in Unity like this:
var theText = new GameObject();
var textMesh = theText.AddComponent<TextMesh>();
var meshRenderer = theText.AddComponent<MeshRenderer>();
textMesh.text = name;
textMesh.transform.position = new Vector3(0, 5, 0);
theText.transform.position = new Vector3(0, 5, 0);
The same kind of transformation works on other objects such as a quad. I do not really know which object I should transform, so I tried both textMesh and theText, also separately.
When I click "Play" in Unity and select the created object in the scene, then the contour of the text is highlighted in orange at the correct position. However, the visible white text is still at (0, 0, 0).
This is not only in scripting; when I create a "3D Object/3D Text" via the Unity UI and drag it around with the mouse, it's the same issue.
Why are you creating the text mesh in code? Just go to GameObjects -> UI -> Text or Text Mesh Pro and create it and place it in your scene. If you need to move it just reference
gameObject.transform.position
in a script attached to it.
edit: try removing
textMesh.transform.position = new Vector3(0, 5, 0);
second edit: sorry wasn't paying attention, you just need to change move theText (the game object)
If you assign a color to your text you will be able to see it. Try this code:
var theText = new GameObject();
var textMesh = theText.AddComponent<TextMesh>();
var meshRenderer = theText.AddComponent<MeshRenderer>();
textMesh.text = name;
textMesh.color = Color.red; //THIS IS THE NEW LINE
textMesh.transform.position = new Vector3(0, 5, 0);
theText.transform.position = new Vector3(0, 5, 0);
Hope I've helped you.
I changed TextMesh to TextMeshPro (which requires the import using TMPro;). TextMeshPro is experimental/unstable at the time of writing, and also the positioning seems to be inconsistent with the positioning of the basic 3D objects, but it's an acceptable solution for me at the moment.
If someone finds a real solution using TextMesh, I'm happy to select their answer as correct!
after importing sprites inside unity , i want to make a Prefab out Of them and assign them a SpriteRenderer and BoxCollider2D component , all automaticly.
every thing is good exept i cant pass the imported Sprite to the SpriteRenderer component any how.
i dont knoow what im missing here. any help will be appritiated.
void OnPostprocessSprites(Texture2D texture, Sprite[] sprites)
{
Sprite sp = sprites[0];
TextureImporter importer = (TextureImporter)assetImporter;
importer.textureType = TextureImporterType.Sprite;
GameObject GO = new GameObject();
GO.name = sp.name;
string ResAddress = importer.assetPath.Remove(importer.assetPath.Length - 4, 4).Replace("Assets/Resources/", "");
Debug.Log("Resource Address = " + ResAddress);// Images/Objects/car acceptable for Resource.load
//GO.AddComponent<SpriteRenderer>().sprite = sp;// not works
GO.AddComponent<SpriteRenderer>().sprite = (Sprite)Resources.Load(ResAddress);// not works
GO.AddComponent<BoxCollider2D>();
Object prefab = PrefabUtility.CreateEmptyPrefab(string.Format("Assets/Resources/X_Temp/{0}.prefab", GO.name));
if (prefab != null)PrefabUtility.ReplacePrefab(GO, prefab, ReplacePrefabOptions.ConnectToPrefab);
}
there is no Error or messages , it makes the prefab with BoxCollider And SpriteRendere Components But SpriteRenderer's Sprite Field Has No Sprite, and set to none or Missing.
how can i fix this?
also asked in Unity Community And No luck.
https://answers.unity.com/questions/1434068/how-to-pass-sprite-to-spriterenderer-in-onpostproc.html
https://forum.unity.com/threads/how-to-pass-sprite-to-spriterenderer-in-onpostprocesssprites-event.505638/
====== UPDATE And NOT RECOMMENDED ANSWER ======
void OnPostprocessSprites(Texture2D texture, Sprite[] sprites)
{
TextureImporter importer = (TextureImporter)assetImporter;
if (AssetDatabase.LoadAssetAtPath<Sprite>(importer.assetPath)==null)
{
AssetDatabase.Refresh();
return;
}
foreach (Sprite sp in sprites)
{
importer.textureType = TextureImporterType.Sprite;
GameObject GO = new GameObject();
GO.name = sp.name;
Sprite sprite = AssetDatabase.LoadAssetAtPath<Sprite>(importer.assetPath);
GO.AddComponent<SpriteRenderer>().sprite = sprite;
string address = string.Format("Assets/Resources/Prefabs/{0}.prefab", GO.name);
PrefabUtility.CreatePrefab(address, GO);
PrefabUtility.ReconnectToLastPrefab(GO);
}
}
not recommended Because Of the following Crash Error :
A default asset was created for 'Assets/Resources/Images/Objects/car.png' because the asset importer crashed on it last time.You can select the asset and use the 'Assets -> Reimport' menu command to try importing it again, or you can replace the asset and it will auto import again.UnityEditor.AssetDatabase:Refresh()
This is really interesting. I don't think that Resources.Load(ResAddress) is returning null otherwise you should see null if you use Debug.Log on it. I assumed that the texture variable from the OnPostprocessSprites function would work if you convert it to Sprite then assign it to the SpriteRenderer like so:
Sprite tempSprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
GO.AddComponent<SpriteRenderer>().sprite = tempSprite;
That didn't work too despite the fact that tempSprite is not null. This quickly made me remember the AssetDatabase API which is specifically used to access and operate on assets.
You are looking for the AssetDatabase.LoadAssetAtPath function. It will return the proper Sprite asset you can use to change the SpriteRenderer.sprite property.
This should work:
Sprite sprite = AssetDatabase.LoadAssetAtPath<Sprite>(assetPath);
GO.AddComponent<SpriteRenderer>().sprite = tempSprite;
It would make sense to call AssetDatabase.Refresh() after all these but it seems to work without calling AssetDatabase.Refresh(). I still think you should call it.
Hi im creating a 2D platformer, and i want to be able to draw for example lines ingame (playmode) with my cursor (like paint) that can act as walkable terrain. Im pretty new to coding and c# which im using, and im having a really hard time imagining how this can be achieved let alone if its possible? Would appreciate if you guys could give me some ideas and maybe could help me push it in the right direction. Thanks!
EDIT:
So i got got some code now which makes me being able to draw in playmode. The question now is how i can implement a type of collider to this? Maybe each dot can represent a little square or something? How can i go through with it? Heres some code. Thanks.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class DrawLine : MonoBehaviour
{
private LineRenderer line;
private bool isMousePressed;
private List<Vector3> pointsList;
private Vector3 mousePos;
// Structure for line points
struct myLine
{
public Vector3 StartPoint;
public Vector3 EndPoint;
};
// -----------------------------------
void Awake()
{
// Create line renderer component and set its property
line = gameObject.AddComponent<LineRenderer>();
line.material = new Material(Shader.Find("Particles/Additive"));
line.SetVertexCount(0);
line.SetWidth(0.1f,0.1f);
line.SetColors(Color.green, Color.green);
line.useWorldSpace = true;
isMousePressed = false;
pointsList = new List<Vector3>();
}
// -----------------------------------
void Update ()
{
// If mouse button down, remove old line and set its color to green
if(Input.GetMouseButtonDown(0))
{
isMousePressed = true;
line.SetVertexCount(0);
pointsList.RemoveRange(0,pointsList.Count);
line.SetColors(Color.green, Color.green);
}
else if(Input.GetMouseButtonUp(0))
{
isMousePressed = false;
}
// Drawing line when mouse is moving(presses)
if(isMousePressed)
{
mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
mousePos.z=0;
if (!pointsList.Contains (mousePos))
{
pointsList.Add (mousePos);
line.SetVertexCount (pointsList.Count);
line.SetPosition (pointsList.Count - 1, (Vector3)pointsList [pointsList.Count - 1]);
}
}
}
}
Regarding means and tools:
Input.mousePosition
Input.GetKey, Input.GetKeyDown, Input.GetKeyUp
Line renderer manual
Line renderer scripting API
Regarding general idea:
Your script may use Input.GetKey to trigger the feature functionality (keyboard key, for example).
When the feature is activated the script awaits of mouse button to be clicked by the means of Input.GetKeyUp and when the event happens it must capture current mouse position using Input.mousePosition.
Only two points are necessary to build a segment of line, so when the script detects input of the second point it may create game object that will represent a piece of walkable terrain.
Visually such an object may be represented with line renderer. To enable iteraction with other game objects (like player character) it is usually enough to enhance object with a collider component (presumably, with a BoxCollider).
Regarding of how-to-add-a-collider:
GameObject CreateLineCollider(Vector3 point1, Vector3 point2, float width)
{
GameObject obj = new GameObject("LineCollider");
obj.transform.position = (point1+point2)/2;
obj.transform.right = (point2-point1).normalized;
BoxCollider boxCollider = obj.AddComponent<BoxCollider>();
boxCollider.size = new Vector3( (point2-point1).magnitude, width, width );
return obj;
}
You can add collider to object with line renderer but you still must orient it properly.
Example of integration in your code:
void Update ()
{
...
// Drawing line when mouse is moving(presses)
if(isMousePressed)
{
mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
mousePos.z=0;
if (!pointsList.Contains (mousePos))
{
pointsList.Add (mousePos);
line.SetVertexCount (pointsList.Count);
line.SetPosition (pointsList.Count - 1, (Vector3)pointsList [pointsList.Count - 1]);
const float ColliderWidth = 0.1f;
Vector3 point1 = pointsList[pointsList.Count - 2];
Vector3 point2 = pointsList[pointsList.Count - 1];
GameObject obj = new GameObject("SubCollider");
obj.transform.position = (point1+point2)/2;
obj.transform.right = (point2-point1).normalized;
BoxCollider boxCollider = obj.AddComponent<BoxCollider>();
boxCollider.size = new Vector3( (point2-point1).magnitude, ColliderWidth , ColliderWidth );
obj.transform.parent = this.transform;
}
}
}