Goal: Render distance lines between two points on the surface of a mesh-based primitive (i.e. sphere, cube, etc).
Current Solution: Iteratively traverse distance line between two end points and "reverse" raycast through this point somehow. Since the distance line directly connects both vertices through the mesh, the according points on the mesh surface are required.
Ray ray = new Ray();
RaycastHit raycastHit;
ray.origin = posOnDistanceLine;
ray.direction = raycastNormal.normalized;
// Reverse ray since we can't raycast from inside the mesh
ray.origin = ray.GetPoint(1);
ray.direction = -ray.direction;
Lines are then drawn using Unity's LineRenderer which is being populated with positions of vertices whenever a change in normals (to previous raycast) is identified.
Issues:
Horrible performance (as 100 rays are cast whenever the end points move).
Solution doesn't always work and produces unexpected, jagged lines / points.
Question:
Is there a better approach to implement this?
If you want to optimize the solution, you might need to make a script for each primitive and utilize the primitive-specific math.
For example, instead of casting rays, you could simply get the radius of the mesh and put the line vertex at the radius * directionFromCenter.
Here is an example script:
[RequireComponent(typeof(LineRenderer))]
public class SurfaceLine : MonoBehaviour, IPointerClickHandler
{
[SerializeField] private float pointsPerUnit;
[SerializeField] private MeshFilter mesh;
private Vector3 start;
private Vector3 end;
private LineRenderer lineRenderer;
void Awake()
{
this.lineRenderer = this.GetComponent<LineRenderer>();
}
public void OnPointerClick(PointerEventData eventData)
{
if(eventData.button == PointerEventData.InputButton.Left)
{
this.start = this.transform.InverseTransformPoint(eventData.pointerCurrentRaycast.worldPosition);
this.Render();
return;
}
if(eventData.button == PointerEventData.InputButton.Right)
{
this.end = this.transform.InverseTransformPoint(eventData.pointerCurrentRaycast.worldPosition);
this.Render();
}
}
private void Render()
{
var distance = Vector3.Distance(this.end, this.start);
var direction = (this.end - this.start).normalized;
var numPoints = Mathf.FloorToInt(distance * this.pointsPerUnit);
numPoints = Mathf.Max(numPoints, 2);
this.lineRenderer.positionCount = numPoints;
var positions = new Vector3[numPoints];
var stepInDir = direction * (distance / (float)numPoints);
for(int i = 0; i < numPoints; i++)
{
positions[i] = this.start + i * stepInDir;
var dirFromCenter = positions[i] - this.mesh.mesh.bounds.center;
positions[i] = this.mesh.mesh.bounds.center + dirFromCenter.normalized * (this.mesh.mesh.bounds.size.x / 2.0f);
}
positions[positions.Length - 1] = this.end;
this.lineRenderer.SetPositions(positions);
}
}
This seems to perform okay in an update loop too. The down side is of course that the solution is not generic. You will need a strategy per primitive.
Alternatively you can at least leverage the pointsPerUnit concept in the script to control the resolution of your line and stick with ray casting. I think the strange artefacts you are seeing is a result of too high point density. Making the points per unit of distance consistent may have better performance too.
Here is the result for the script above:
Related
I have an automatic rigidbody car that is supposed to follow a set of waypoints on a mesh collider map but will get caught on any random waypoint and will rotate about that waypoint. Here is my code for the automatic car:
public class FollowThePath : MonoBehaviour
{
public Transform[] target;
public float speed;
public float damping = 6.0f;
public GameObject centerOfMass;
private int current;
void Start()
{
GetComponent<Rigidbody>().centerOfMass = centerOfMass.transform.localPosition;
}
void Update()
{
if (transform.position != target[current].position)
{
Vector3 pos = Vector3.MoveTowards(transform.position, target[current].position, speed * Time.deltaTime);
GetComponent<Rigidbody>().MovePosition(pos);
var rotation = Quaternion.LookRotation(target[current].position - transform.position);
transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * damping);
}
else current = (current + 1) % target.Length;
}
}
I increased the mass, added a center of mass, froze the y axis position and rotation, lowered the center of mass. I noticed that the car gets stuck particularly, but not limited to curves.
Comparing two Vector3s for equality is generally a bad idea when handling moving objects.
Firstly because they use float internally, which always run the risk of being imprecise.
Secondly because Unity knows this and approximates the equality of two vectors internally. The problem is, that you don't have control over the precision being used by unity. When you implement it yourself like the function below it should be easier for you to figure out if that's the problem.
What's most likely the case is, that if (transform.position != target[current].position) never evaluates to false, since transform.position is (0,0,0) and target[current].position is (0.00000000001,0,0) or something. They are almost equal, but not quite equal, at least using the == operator. Then your code in else is never executed.
You can try something like this, where allowedDifference should be in the scope of 0.0001 for ~0.1m precision, or 0.00000001 for ~0.1mm precision:
public bool ApproximatelyEquals(Vector3 a, Vector3 b, float allowedDifference = 0.00000001f){
return Vector3.SqrMagnitude(a - b) < allowedDifference;
}
//Edit:
To clarify, use it like:
if(!ApproximatelyEquals(transform.position, target[current].position), 0.00000001f)
To further optimize my game, I wanted to combine the meshes of the hallways so that the frame rate would be higher. the code is intended to take all of the children inside of an empty game object(titled walls, floors, etc.) and combine their meshes into one. However, whenever I ran the script, all of the child objects would appear in completely random positions and were invisible. How can I make so that the objects all appeared at their original position and were visible?
How I set up the code is that I would place any repeated objects in an empty gameObject to easily categorize them (titled as Walls, Floors). Afterwards, I would assign the script to the empty gameObject and expect every repeated object in the empty gameObject to combine.
Here's an example:
Here's The Code:
using UnityEngine;
using System.Collections;
// Copy meshes from children into the parent's Mesh.
// CombineInstance stores the list of meshes. These are combined
// and assigned to the attached Mesh.
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
public class CombineMesh : MonoBehaviour
{
void Update()
{
if(Input.GetKeyDown(KeyCode.J))
{
CombineMeshes();
}
}
void CombineMeshes()
{
Quaternion oldRot = transform.rotation;
Vector3 oldPos = transform.position;
transform.rotation = Quaternion.identity;
transform.position = Vector3.zero;
MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>();
CombineInstance[] combine = new CombineInstance[meshFilters.Length];
int i = 0;
while (i < meshFilters.Length)
{
combine[i].mesh = meshFilters[i].sharedMesh;
combine[i].transform = meshFilters[i].transform.localToWorldMatrix;
meshFilters[i].gameObject.SetActive(false);
i++;
}
var MeshFilter = transform.GetComponentInChildren<MeshFilter>();
MeshFilter.mesh = new Mesh();
MeshFilter.mesh.CombineMeshes(combine);
GetComponentInChildren<MeshCollider>().sharedMesh = MeshFilter.mesh;
transform.gameObject.SetActive(true);
transform.rotation = oldRot;
transform.position = oldPos;
}
}
Answering late in case anyone happens to be searching for this information.
Unity's documentation is uncharacteristically unhelpful, as is the internet in general.
Lengthy Explanation
Kayra's Code Corrected
Working Code: Helper Functions
Working Code: CombineMeshes
Lengthy Explanation:
Two things to keep in mind are
Meshes are defined via a Vector3[](see meshFilter.mesh.vertices), describing the coordinates of each vertex relative to its meshFilter's transform (so local space). This is very important later on.
There is no black magic involved - math is not black magic ;)
First, you should understand "transform.localToWorldMatrix" and "transform.worldToLocalMatrix"
These are very misleading names - all these Matrix4x4 actually do in Unity, is describe the linear transformation between
{position: Vector3.zero, rotation: Quaternion.identity, scale: Vector3.one} and
the given transform's {position, rotation, scale}.
Its telling you by how much to move in which direction, by how much to rotate in which direction, and by how much to scale.
In fact: transform.localToWorldMatrix == transform.worldToLocalMatrix.inverse -- they're the exact same thing, just flipped around.
See "Khan Academy" or "3blue1brown" on YouTube for great explanations of Linear Transformation
We usually don't use Matrices in Unity, because we can access/modify the position, rotation, and scale directly -- for us, it's just a different way of storing the same information.
I wrote some working code to visualise what's actually happening in Unity:
/// Demonstration of how transform.localToWorldMatrix works - very important for understanding CombineInstance.transform
/// Place two 3D objects in your scene, attach this script somewhere, and assign the 3D Objects to the public variables
/// This script will auto-run inside a coroutine 2 seconds after Start(), because I use the InputSystem package and maybe you still use if(Input.getKeyDown()).
/// It's the easiest way I know of to guarantee working code in your scene
public class StackOverflowExample : MonoBehaviour
{
public Transform parent;
public Transform child;
private IEnumerator exampleMethod;
/// <summary>
/// Just setting some conditions, in case you try out this code
/// </summary>
private void Start()
{
parent.position = new Vector3(5f, 0f, 3f);
child.parent = parent;
child.localPosition = new Vector3(2f, 0f, 2f);
//Only doing this because I use the InputSystem package, and others maybe don't (yet...)
exampleMethod = TransformMatrixExample();
StartCoroutine(exampleMethod);
}
//Only doing this because I use the InputSystem package, and others maybe don't (yet...)
private IEnumerator TransformMatrixExample()
{
//wait for 2 seconds just because...
yield return new WaitForSeconds(2f);
//parent.position = parent.position - parent.localToWorldMatrix.GetPosition();
// Commented out on purpose. Would result in 0,0,0 parent.position, because parent has no gameObject parent...
//and therefore break the demonstration:
child.position = child.position - parent.localToWorldMatrix.GetPosition();
// child.localPosition (see the inspector) now states (-3f,0f,-1f)
// however, child.Position (just unparent the gameObject in the inspector) is now (2f,0f,2f)
}
}
When you tell each CombineInstance what Matrix4x4 to use in
combine[i].transform = meshFilters[i].transform.localToWorldMatrix;
you are describing by how much to offset each soon-to-be-combined mesh's vertices' coordinates before combining it.
Once again, we usually access/modify the position, rotation, and scale directly - Mesh.CombineMeshes() uses a 4x4 matrix.
Here Matrix4x4.GetPosition() returns the vector leading from Vector3.zero to parent.position - which is contained inside that matrix
We are effectively moving the child object to the place it would be if the parent were at coordinates (0f,0f,0f).
The same thing happens with rotation and scale.
In the while-loop in your code, there's a statement:
combine[i].transform = meshFilters[i].transform.localToWorldMatrix;
The problem with this is that if the parent object (or the mesh you want to merge into) is not positioned at Vector3.zero, all the meshes will still pretend otherwise and offset themselves by the wrong amount. That is why you have to move the parent.transform.position to vector3.zero before assigning to the CombineInstances[].
So in my code example above:
parent.position = new Vector3(5f, 0f, 3f);
child.localPosition = new Vector3(2f, 0f, 2f);
If I first move the parent to V3.zero, the results given by transform.localToWorldMatrix.GetDistance():
combine[i].transform = meshFilters[i].transform.localToWorldMatrix :
parent's offset: V3(0f,0f,0f) --> parent.position
child's offset: V3(2f,0f,2f) --> child.position
This works because now, the vector from child.position to V3.zero == vector from child.position to parent.position.
If I were to use the transforms without first moving the parent to V3.zero, I would get the following results:
combine[i].transform = meshFilters[i].transform.localToWorldMatrix :
parent's offset: V3(5f, 0f, 3f) --> parent.position
child's offset: V3(7f, 0f, 5f) --> parent.position + child.localPosition
Because transform.localToWorldMatrix returns the vector from zero to that transform.position.
Remember Point1 of things to remember?
Vertex coordinates are defined in local space relative to their meshFilter's transform.
In other words:
all of parent mesh's vertices will offset by an additional V3(5f,0f,3f) --> parent.position
all of child mesh's vertices will be offset by an additional V3(7f,0f,5f) --> parent.position + child.localPosition
The statement is effectivley telling Unity the following (pseudocode):
foreach (Vector3 v in meshFilters[i].mesh.vertices)
{
v += meshFilters[i].localToWorldMatrix.GetPosition();
//and now append my vertex to the new MeshFilter's mesh...
//in other workds: Pretend that my parent is at 0,0,0 and I'm in the right spot already
}
The exact same principle holds true for rotation and scale. You can probably decipher that from the working code later on.
kayra yorulmaz's Corrected Code
I'm assuming that CombineMesh.cs is attached to the "Floors" gameObject,
and that "Floors" transform.rotation = (0f,0f,0f).
The Quaternion operations will be explained shortly.
So the kayra yorulmaz's code would have to be written as follows:
using UnityEngine;
// Copy meshes from children into the parent's Mesh.
// CombineInstance stores the list of meshes. These are combined
// and assigned to the attached Mesh.
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshCollider))] //because otherwise line 72 might throw an exception...
public class CombineMesh_Corrected : MonoBehaviour
{
void Update()
{
if(Input.GetKeyDown(KeyCode.J))
{
CombineMeshes();
}
}
void CombineMeshes()
{
Vector3 transformOffset = transform.position;
MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>();
CombineInstance[] combine = new CombineInstance[meshFilters.Length];
int i = 0;
while (i < meshFilters.Length)
{
Quaternion rotationOffset = Quaternion.FromToRotation(transform.eulerAngles, meshFilters[i].transform.eulerAngles);
meshFilters[i].transform.position -= transformOffset;
meshFilters[i].transform.rotation = Quaternion.Euler(meshFilters[i].transform.eulerAngles) * Quaternion.Inverse(rotationOffset);
combine[i].mesh = meshFilters[i].sharedMesh;
combine[i].transform = meshFilters[i].transform.localToWorldMatrix;
meshFilters[i].gameObject.SetActive(false);
//we already stored the 4x4Matrix in combine[i].transform, so it's safe to change back now
meshFilters[i].transform.position += transformOffset;
meshFilters[i].transform.rotation *= rotationOffset;
i++;
}
MeshFilter meshFilter = transform.GetComponent<MeshFilter>();
meshFilter.mesh = new Mesh();
meshFilter.mesh.CombineMeshes(combine);
GetComponentInChildren<MeshCollider>().sharedMesh = meshFilter.mesh;
transform.gameObject.SetActive(true);
}
}
Working Code Example
However, it you create your own Matrix4x4 to describe the necessary linear transformation, you don't have to touch the gameObject's transforms at all.
Remember that Vertices (and therefore the meshes you're combining) are described relative to the meshFilter's transform.
So if we create a Matrix4x4 for each child meshFilter, describing how that child.meshFilter.transform is located relative to parent.meshFilter.transform, we can tell Unity where to place the vertices for the combined mesh:
So here's is the code based off what I just wrote for my own project.
Necessary Helping Functions
using UnityEngine;
public static class StackoverflowHelpers
{
/// <summary>
/// Returns the difference between quaterions, treated as local rotations because of the order...
/// https://answers.unity.com/questions/810579/quaternion-multiplication-order.html
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
/// <returns></returns>
public static Quaternion FromTo(Quaternion from, Quaternion to)
{
return Quaternion.Inverse(from) * to;
}
public static Quaternion Add(Quaternion start, Quaternion difference)
{
return start * difference;
}
public static Quaternion Subtract(Quaternion start, Quaternion difference)
{
return start * Quaternion.Inverse(difference);
}
public static Vector3 RatioBetween( Vector3 fromScale, Vector3 toScale)
{
return new Vector3(
toScale.x/fromScale.x,
toScale.y/fromScale.y,
toScale.z/fromScale.z );
}
}
Actual CombineMesh Method
using System.Collections.Generic;
using UnityEngine;
public static class StackOverflow_CombineMesh
{
public static void Simple(List<MeshFilter> _meshFilters, bool _deleteOriginals = true)
{
CombineInstance[] combineInstances = new CombineInstance[_meshFilters.Count];
Transform parent = _meshFilters[0].transform;
for (int i = 0; i < _meshFilters.Count; i++)
{
/// set up the matrix describing the step from the parent mesh to the child mesh
Transform child = _meshFilters[i].transform;
Vector3 posOffset = child.position - parent.position;
posOffset.x *= 1/parent.localScale.x;
posOffset.y *= 1/parent.localScale.y;
posOffset.z *= 1/parent.localScale.z;
Matrix4x4 ParentToChildMatrix = Matrix4x4.TRS(
posOffset,
StackoverflowHelpers.FromTo(parent.rotation, child.rotation),
StackoverflowHelpers.RatioBetween(parent.lossyScale, child.lossyScale));
combineInstances[i].mesh = _meshFilters[i].mesh;
combineInstances[i].transform = ParentToChildMatrix;
child.gameObject.SetActive(false);
}
_meshFilters[0].mesh = new Mesh();
_meshFilters[0].mesh.CombineMeshes(combineInstances, true, true);
_meshFilters[0].gameObject.SetActive(true);
}
Bear in mind that meshes in unity have a maximum number of vertices (65,535 to be exact) - if you cross that limit your mesh won't render properly after all.
Have fun, keep learning!
Gecko
I think this is a common issue with Unity mesh combine, try changing this line (assuming this is all on the Parent game object):
combine[i].transform = meshFilters[i].transform.localToWorldMatrix;
to this:
combine[i].transform = meshFilters[i].transform.localToWorldMatrix * transform.worldToLocalMatrix
where transform.worldToLocalMatrix is the parent object. You could also try something like:
combine[i].transform = meshFilters[i].transform.localToWorldMatrix * meshFilters[i].transform.parent.transform.worldToLocalMatrix;
Depends on how you have it set up
I made a simple script that goes to one waypoint and then to the next.
My problem is that it seems to be a delay while going from waypoint1 to waypoint2 and i don't know why:
¿Why is that delay happening and how can i remove it?
using UnityEngine;
using System.Collections;
public class Missile : MonoBehaviour
{
public Vector3 finalTarget;
public Transform forwardObject;
public GameObject impactAreaPrefab;
float smoothingDelay = 0.1f;
bool fired = false;
bool powerPhase = true;
Vector3 currentTarget;
private void OnEnable() {
fire(new Vector3(-25.29f, 0.5f, -10.638f));
}
void fire(Vector3 coords) {
currentTarget = forwardObject.position;
finalTarget = coords;
Instantiate(impactAreaPrefab, finalTarget, Quaternion.identity);
fired = true;
}
void Update() {
if (!fired) {
return;
}
if (powerPhase && transform.position == currentTarget) {
powerPhase = false;
currentTarget = finalTarget;
smoothingDelay = 0.05f;
}
transform.position = Vector3.Lerp(transform.position, currentTarget, Time.deltaTime / smoothingDelay);
transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(Vector3.RotateTowards(transform.forward, currentTarget, 1, 0.0f)), Time.deltaTime / smoothingDelay);
}
}
That's happening, because you're using lerp not exactly properly. If you want to get linear movement you should cache your first argument (position/rotation on beginning) and provide increasing third parameter. This delay is happening because if your bullet is very close to final position and it's still trying to get there, but your current distance |finalPos - transform.position| is so small that your step Time.deltaTime/smoothingDelay is almost not moving it.
Vector3 startPos;
Vector3 finalPos;
float currentT = 0.0f;
void Update()
{
currentT += Time.deltaTime;
transform.position = Vector3.Lerp(startPos, finalPos, currentT);
}
Checking if Vector3 == Vector3 is also not a good idea. Use pattern from above and check if currentT is larger or equal to 1. If it's true then you're on final position. You get also some control over movement duration by dividing currentT.
So First thing read these post to get better understanding of Lerp function-
https://chicounity3d.wordpress.com/2014/05/23/how-to-lerp-like-a-pro/
http://www.kinematicsoup.com/news/2016/8/9/rrypp5tkubynjwxhxjzd42s3o034o8
You should have a better understanding of lerp now.
In summary lerp does a really simple thing. Say u have two values X and Y. For this example let us give them some value, X = 0, Y = 1, Now you want to get a value some percent between them, like u want to get a value which is 50% from X and Y. You can guess the answer is 0.5. The lerp equation for this would be
Mathf.Lerp(0, 1, 0.5f);
So simply- given two values, x and y, Mathf.Lerp returns a value that is t percent between them.
Now to properly use Lerp you need to cache the position before starting the lerp. Most times I use a coroutine to get this effect works pretty well and then u can use animation curve to change the third parameter to create some crazy good effects. For example on using a animation curve just comment i will write it.
For this problem of yours you have two options-
1) Lerp like a pro using Animation curve to manipulate the speed. Remember u can create animation curves in runtime too.
IENumerator Move(Transform toMove, Vector3 end, float duration){
Vector3 startPos = toMove.position;
float elapsed = 0f;
while(elapsed < duration){
elapsed += Time.deltaTime;
toMove.position = Vector3.Lerp(startPos, end, elapsed / duration);//manipulate the last parameter to move the object linearly
yield return null;//basically wait for next frame
}
toMove.position = end;//after lerp ends
}
Now you can instead of duration use speed and then with it you calculate the time required and change the speed to make it faster
float distance = Vector3.Distance(startPos, end);
toMove.position = Vector3.Lerp(startPos, end, elapsed / (distance/(speed * multiplier)));
2) Use Vector3.MoveTowards - This function moves a point to a end point with a given maximum step, requires three paramters, (currentPosition, end, step), u can multiply step with variable to control the speed, both work really good.
Using this is much easier in most cases
Example-
float step = speed * Time.deltaTime;//to make it framerate independent
transform.position = Vector3.MoveTowards(transform.position, end, step * multiplier);
Hope this helps. I am sorry I was unable to format my answer properly, hopefully will get better at answering. Any edits to improve the answer are welcomed :)
I recommend using iTween for smooth movement.
I modified iTween at some point for me to be able to do anything I want. like this:
public static void Rotate (Transform transform, Vector3 target, float transitionTime, Action onEnd = null, bool ignoreTimescale = false, iTween.EaseType ease = iTween.EaseType.easeInOutQuad, float delay = 0)
{
Vector3 from, to;
from = transform.localEulerAngles;
to = target;
Action <object> onUpdateAction = (rotation =>
{
transform.localEulerAngles = (Vector3) rotation;
});
Action <object> onCompleteAction = (data =>
{
if (onEnd != null)
onEnd ();
});
Hashtable hash = new Hashtable ();
hash.Add ("from", from);
hash.Add ("to", to);
hash.Add ("time", transitionTime);
hash.Add ("delay", delay);
hash.Add ("easetype", iTween.EaseType.easeInOutQuad);
hash.Add ("ignoretimescale", ignoreTimescale);
hash.Add ("onupdate", onUpdateAction);
hash.Add ("oncomplete", onCompleteAction);
iTween.ValueTo (transform.gameObject, hash);
}
That gives me full control in a variety of scenarios.
Here is the code if you want to implement it.
https://drive.google.com/open?id=1nLEEYTp-q4Kfh2n3nWQJcMXmPNtVPLLP
Since yesterday I have been trying to create a procedural infinite world generation and for my new game and for the moment everything goes pretty well, except that I can't figure out how to add grass to my mesh. My world is generated using procedurally generated meshes of different levels of details. I created my terrain based on 3 "layers", the ground layer, a hills layer and a mountains layer. The texture on top of my terrain is entirely managed by my custom shader (which is very primitive for the moment because there is no blending).
So I really an't figure out how to add grass to such a world. I tried different technique with grass GameObjects, Planes etc but they all not worked... If someone can help me it could be awesome! I can explain you how I manage my vegetation script. I call the GenerateVegetation() function on the chunk load and I just randomly place the trees on top of the terrain using a raycasting to get the point and the normal (I check the slope with the y axis of the vertex's normal).
Here is my vegetation script (very basic for the moment I just done it now)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class VegetationGeneration {
public static IEnumerator Generate(Vector2 chunk) {
// setting up the Vegetation holder child of the global chunk (hierachy);
Transform vegetationChunk = new GameObject ("Vegetation").transform;
vegetationChunk.parent = GameSettings.chunks [chunk];
// Get all the vegetation instances and loop start iterating the placement
foreach(Vegetation vegetation in MapPreview.instance.vegetation) {
float amount = Random.Range (vegetation.amount.x, vegetation.amount.y);
for(int i = 0; i < amount; i++) {
// Splitting the generation calculations each 15 trees (Only while playtime) (Performance)
if (!Application.isEditor && i % 15 == 0) yield return null;
// Getting a random position in the chunk
float chunkDiameter = GameSettings.worldChunkSize / 2f;
Vector3 position = new Vector3 (Random.Range (-chunkDiameter, chunkDiameter), 0f, Random.Range (-chunkDiameter, chunkDiameter)) + new Vector3(chunk.x, 0f, chunk.y);
// Shooting a raycast down to get the hit point and hit normal
// Check the slope angle
RaycastHit hit;
float slope;
if(Physics.Raycast(new Ray(position + Vector3.up * 100f, Vector3.down), out hit, 200f, MapPreview.instance.groundLayer) && (slope = hit.normal.y) > vegetation.maxSlope) {
// possible to spawn the vegetation at the given point
// Spawning the entity with the vegetation chunk parent
GameObject prefab = vegetation.vegetation [Random.Range (0, vegetation.vegetation.Length)];
GameObject entity = GameObject.Instantiate (prefab, vegetationChunk);
// setting parameters
entity.transform.position = hit.point;
entity.transform.localEulerAngles += new Vector3 (0f, Random.Range (0f, 360f), 0f);
// Getting a random scale and converting it to a uniform vector
float scale = Random.Range (vegetation.scale.x, vegetation.scale.y);
entity.transform.localScale = Vector3.one * scale;
// Putting the vegetation into the ground to avoit flying trees
entity.transform.position -= Vector3.up * vegetation.depth.Evaluate (slope);
// Set to a different layre than the ground to avoid trees over other trees (Raycasting)
SetLayer (entity.transform);
}
}
}
}
// A simple Recusrive function that will loop thru every child of the root
// TRansform and apply the right layer to it
static void SetLayer(Transform parent) {
parent.gameObject.layer = LayerMask.NameToLayer (MapPreview.instance.vegetationLayer);
foreach(Transform child in parent) SetLayer (child);
}
}
// Vegetation Object
[System.Serializable]
public class Vegetation {
public string name;
[Tooltip("Amount of vegetation per generated chunk (does not represent the real amount because they won't be placed if the place is not survivable)")]
public Vector2 amount = new Vector2(50,70);
public GameObject[] vegetation;
public Vector2 scale;
[Range(0f,1f)]
public float maxSlope = 0.7f;
public AnimationCurve depth = new AnimationCurve(new Keyframe[] { new Keyframe(0f, 0.05f), new Keyframe(1f, 0.25f) });
}
I would really appreciate some insight.
I have the following code which works really well for scrolling map using the draggable mouse. I am trying to define limits so that I cannot scroll too far in either the x or y co-ordinates. I've seen various examples of code, such as:
"transform.position.x = Mathf.Clamp(-100, 100);" though have not been able to incorporate it into the code. I am a bit of a beginner to all this and have just done a 2D tutorial animating zombies and have a camera that can scroll, but would like to add limits to how far it can scroll in any given directions.
Thanks heaps
Adam
using UnityEngine;
using System.Collections;
public class ViewDrag : MonoBehaviour {
Vector3 hit_position = Vector3.zero;
Vector3 current_position = Vector3.zero;
Vector3 camera_position = Vector3.zero;
float z = 0.0f;
// Use this for initialization
void Start () {
}
void Update(){
if(Input.GetMouseButtonDown(0)){
hit_position = Input.mousePosition;
camera_position = transform.position;
}
if(Input.GetMouseButton(0)){
current_position = Input.mousePosition;
LeftMouseDrag();
}
}
void LeftMouseDrag(){
// From the Unity3D docs: "The z position is in world units from the camera." In my case I'm using the y-axis as height
// with my camera facing back down the y-axis. You can ignore this when the camera is orthograhic.
current_position.z = hit_position.z = camera_position.y;
// Get direction of movement. (Note: Don't normalize, the magnitude of change is going to be Vector3.Distance(current_position-hit_position)
// anyways.
Vector3 direction = Camera.main.ScreenToWorldPoint(current_position) - Camera.main.ScreenToWorldPoint(hit_position);
// Invert direction to that terrain appears to move with the mouse.
direction = direction * -1;
Vector3 position = camera_position + direction;
transform.position = position;
}
}
What exactly is the error you are getting? I imagine it is something along the lines of being unable to modify the return value of position because it is not a variable.
If this is the case, you should however be able to set the whole position vector at once. So try something along the lines of this:
var pos = transform.position;
transform.position = new Vector3(
Math.clampf(pos.x, -100, 100),
Math.clampf(pos.y, -100, 100),
pos.z
);
You may need to swap around clamping Y and Z, depending on how you are using them.