I'm currently trying to snap objects (cubes, pyramids, torus) together. I assigned each face of the objects bounding box a "snap allowed" variable and on collision try to find the closest two sides to each other. I then move the main object to the collision side and rotate it accordingly.
The code is as follows:
private static Vector3[] sides = {
Vector3.up,
Vector3.forward,
Vector3.right,
Vector3.down,
Vector3.back,
Vector3.left
};
private void Snap() {
if (!snappedObject)
return;
if (lastSnapping > LeftController.GetLastSnappingToggle()) {
//we dont calculate new sides, only use the old ones
//but we have to check whether we are now further away
float dis = Vector3.Distance(GetWorldPositionFace(this.gameObject, lastSelf),
GetWorldPositionFace(snappedObject, lastOther));
float max = Mathf.Max(Mathf.Abs(size.x), Mathf.Max(Mathf.Abs(size.y), Mathf.Abs(size.z)));
if (dis > max)
return;
ApplyToOther(lastSelf, lastOther, snappedObject);
} else {
//we need to find both new closest sides
MeshSnapping other = snappedObject.GetComponent<MeshSnapping>();
float otherDis = float.MaxValue;
int otherSide = -1;
//find the closest side from the other object
for (int i = 0; i < NUM_SIDES; i++) {
float dis = Vector3.Distance(transform.position, GetWorldPositionFace(snappedObject, i));
if (dis < otherDis && other.sidesAllowed[i]) {
otherDis = dis;
otherSide = i;
}
}
//find the closest side of our object
float selfDis = float.MaxValue;
int selfSide = 0;
for (int i = 0; i < NUM_SIDES; i++) {
float dis = Vector3.Distance(GetWorldPositionFace(this.gameObject, i),
GetWorldPositionFace(snappedObject, otherSide));
if (dis < selfDis && sidesAllowed[i]) {
selfDis = dis;
selfSide = i;
}
}
//are we to far away or at a prohibited side?
float max = Mathf.Max(Mathf.Abs(size.x), Mathf.Max(Mathf.Abs(size.y), Mathf.Abs(size.z)));
if (selfDis > max)
return;
ApplyToOther(selfSide, otherSide, snappedObject);
//save the sides for next iteration
lastSelf = selfSide;
lastOther = otherSide;
}
lastSnapping = Time.time;
}
private void OnCollisionEnter(Collision collision) {
snappedObject = collision.gameObject;
}
private void OnCollisionExit(Collision collision) {
snappedObject = null;
}
private Vector3 GetWorldPositionFace(GameObject other, int i) {
//get the side in local coordinates, scaled to size
Vector3 otherLocalSize = other.transform.localScale;
Vector3 otherSidePoint = new Vector3(otherLocalSize.x * sides[i].x, otherLocalSize.y * sides[i].y, otherLocalSize.z * sides[i].z) / 2f;
//rotate it according to world position
Vector3 dir = (other.transform.rotation * otherSidePoint);
//actually move it to world position
Vector3 center = other.transform.position + dir;
return center;
}
private void ApplyToOther(int selfI, int otherI, GameObject other) {
//first get the midpoint of face of other go
Vector3 edge = GetWorldPositionFace(other, otherI);
Vector3 dir = edge - other.transform.position;
RotateSides(selfI, otherI, dir);
selfI = (selfI + NUM_SIDES / 2) % NUM_SIDES;
//get midpoint of face of self go
edge += GetWorldPositionFace(this.gameObject, selfI) - transform.position;
//now move towards the combination
transform.position = edge;
}
private void RotateSides(int selfI, int otherI, Vector3 dir) {
//rotate self side towards this point
switch (selfI) {
case 0: transform.up = -dir; break;
case 1: transform.forward = -dir; break;
case 2: transform.right = -dir; break;
case 3: transform.up = dir; break;
case 4: transform.forward = dir; break;
case 5: transform.right = dir; break;
}
}
I can find every midpoint of the bounding box by transforming the direction vector, applying the objects current rotation and position to it (see GetWorldPositionFace() ). After finding the best combination, ApplyToOther() moves the objects to position and rotates it according to the selected face normals. So far so good, but the result is not aligned. As you can see, the front faces do not face in the same direction, i.e. I want to rotate the upper cube around the transform.up axis by this amount. This would be the result that I want.
But, if I add
float angle = Vector3.Angle(transform.forward, snappedObject.transform.forward);
transform.Rotate(transform.up, angle);
to the RotateSides() function, the result is this. The rotation axis is wrong.
Using
Quaternion.FromToRotation(transform.up, snappedObject.transform.up)
did not work either.
What did I miss? Thanks for your help!
I figured out my problem. By setting the transform.forward and the transform.up seperately (e.g. with transform.rotate around axis), only one of them was correct. Using Quaternion.LookRotation() solves this.
Related
I created a diagonal line renderer by attaching the following script to an empty game object. How can I extend the line at both ends for a half its length and how can I also extend the line by say 1 unit along the x-axis? Both over a certain period of time.
public class DiagonalLine : MonoBehaviour {
bool firstLineComplete = false;
LineRenderer diagLine;
public Vector3 startPoint = new Vector3 (0, 0, 0);
public Vector3 endPoint = new Vector3 (1.0f, 1.0f, 0);
public float lineDrawSpeed;
// Use this for initialization
void Start () {
diagLine = gameObject.AddComponent<LineRenderer>();
diagLine.material = new Material (Shader.Find ("Sprites/Default"));
diagLine.startColor = diagLine.endColor = Color.green;
diagLine.startWidth = diagLine.endWidth = 0.15f;
diagLine.SetPosition (0, startPoint);
diagLine.SetPosition (1, endPoint);
}
}
This is basic vector math.
You have the line with (from end to start):
Vector3 v = start - end;
and then you extend on each side by half if it:
extensionA = start + (v * 0.5f);
extensionB = end + (v * -0.5f);
If you need to extend by 1 then normalize:
Vector3 v = (start - end).normalized;
extensionA = start + v;
extensionB = end + (v * -1f);
Break your problem into pieces:
1.Extend line by x units on both sides:
This is done with the Ray class. Create a new Ray instance from the startPoint and endPoint variables then use the Ray.GetPoint function to extend the line. You have to do this on both sides to get the new extended lines.
A simple wrapper for the Ray class to simplify this:
Vector3 extendLine(Vector3 startPoint, Vector3 endPoint, ExtendDirection extendDirection, float extendDistance)
{
Ray ray = new Ray();
//Start
if (extendDirection == ExtendDirection.START_POINT)
{
ray.origin = startPoint;
ray.direction = startPoint - endPoint;
}
//End
else if (extendDirection == ExtendDirection.END_POINT)
{
ray.origin = endPoint;
ray.direction = endPoint - startPoint;
}
//Extend
Vector3 newUnityPoint = ray.GetPoint(extendDistance);
//Debug.DrawLine(ray.origin, newUnityPoint, Color.blue);
return newUnityPoint;
}
public enum ExtendDirection
{
START_POINT, END_POINT
}
Extend to the Left end
Vector3 newStartPos = extendLine(startPoint, endPoint, ExtendDirection.START_POINT, 4);
diagLine.SetPosition(0, newStartPos);
Extend to the Right end
Vector3 newEndPos = extendLine(startPoint, endPoint, ExtendDirection.END_POINT, 4);
diagLine.SetPosition(1, newEndPos);
2.For animating/moving it over time, use coroutine and Time.deltaTime. Increment a variable with Time.deltaTime every frame to then use Vector3.Lerp to lerp the from and to value.
See this function for example.
With both combined, below is a complete function to extend both lines over time:
bool isRunning = false;
IEnumerator extentLineOverTime(LineRenderer targetLineRenderer, float extendDistance, float duration)
{
//Calculate Left from extension length
Vector3 fromValLeftPos = targetLineRenderer.GetPosition(0);
//Calculate Right from extension length
Vector3 fromValRightPos = targetLineRenderer.GetPosition(1);
//Calculate Left to extension length
Vector3 newLeftPos = extendLine(fromValLeftPos, fromValRightPos, ExtendDirection.START_POINT, extendDistance);
//Calculate Right to extension length
Vector3 newRightPos = extendLine(fromValLeftPos, fromValRightPos, ExtendDirection.END_POINT, extendDistance);
//Make sure there is only one instance of this function running
if (isRunning)
{
yield break; ///exit if this is still running
}
isRunning = true;
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
//Move to left overtime
Vector3 tempLeftPos = Vector3.Lerp(fromValLeftPos, newLeftPos, counter / duration);
targetLineRenderer.SetPosition(0, tempLeftPos);
//Move to Right overtime
Vector3 tempRightPos = Vector3.Lerp(fromValRightPos, newRightPos, counter / duration);
targetLineRenderer.SetPosition(1, tempRightPos);
yield return null;
}
isRunning = false;
}
USAGE:
LineRenderer diagLine;
public Vector3 startPoint = new Vector3(0, 0, 0);
public Vector3 endPoint = new Vector3(1.0f, 1.0f, 0);
// Use this for initialization
void Start()
{
diagLine = gameObject.AddComponent<LineRenderer>();
diagLine.material = new Material(Shader.Find("Sprites/Default"));
diagLine.startColor = diagLine.endColor = Color.green;
diagLine.startWidth = diagLine.endWidth = 0.15f;
diagLine.SetPosition(0, startPoint);
diagLine.SetPosition(1, endPoint);
//Extend Line Over time
StartCoroutine(extentLineOverTime(diagLine, 4, 3));
}
The StartCoroutine(extentLineOverTime(diagLine, 4, 3)); will extend the line 4 units away from both sides within 3 seconds.
My level selection menu scene will be a graphic map. Think level selection similar to candy crush. I have different sprites for different maps that will be instantiated when the user moves the camera to that map. In order for me to achieve this, I scroll/move the camera using touch on it's Y-axis. But the problem is, I'm having a trouble getting the boundary of the map sprite, I already read a lot of solution about this one. Here is my current script. I don't know what I'm doing wrong. My camera got stuck when it is near the boundary. Thank you for your help
public float scrollSpeed = 10.0f;
public GameObject[] maps;
Camera mainCamera;
private float topBound;
private float bottomBound;
private Vector3 pos;
private SpriteRenderer currentSprite;
private int mapIndex;
void Awake ()
{
mapIndex = 0;
mainCamera = Camera.main;
currentSprite = maps [mapIndex].GetComponent<SpriteRenderer> ();
bottomBound = currentSprite.sprite.bounds.size.y * -1;
topBound = currentSprite.sprite.bounds.size.y + currentSprite.gameObject.transform.position.y;
}
void Update ()
{
if (Input.touchCount > 0 && Input.GetTouch (0).phase == TouchPhase.Moved)
{
Vector2 touchDeltaPosition = Input.GetTouch(0).deltaPosition;
mainCamera.transform.Translate(0f,-touchDeltaPosition.y * scrollSpeed * Time.deltaTime,0f);
}
}
private float getAxisDelta(float axisDelta,float camMin,float camMax,float bottom,float top)
{
//get where edge of camera will have moved if full translate is done
float boundaryMinPixelsDestination = camMin - Mathf.Abs(axisDelta);
float boundaryMaxPixelsDestination = camMax + Mathf.Abs(axisDelta);
Debug.Log("MaxPixels:"+boundaryMaxPixelsDestination+"="+camMax+"+"+Mathf.Abs(axisDelta));
//check to see if you're within the border
if ((boundaryMinPixelsDestination <= bottom && axisDelta > 0) ||
(boundaryMaxPixelsDestination >= top && axisDelta < 0))
{
axisDelta = 0;
}
return axisDelta;
}
private float camBoundPos(string pos)
{
float bounds = 0f;
if (pos == "top")
bounds = mainCamera.transform.position.y + mainCamera.orthographicSize;
if(pos == "bottom")
bounds = mainCamera.transform.position.y - mainCamera.orthographicSize;
return bounds;
}
I'm trying to create a bird moving across screen along x axis.
bird.transform.position = Vector3.Lerp (pos1, pos2, (Mathf.Abs(speed * Time.time) + 1.0f) / 2.0f);
Using this in Update() the bird flies only once. I want that after it flies to the right, it should wait 2-3 seconds and then fly back with a different sprite.
transform.translate doesn't work like this. Any help will be appreciated.
you would need to put in another LERP for going the other direction and have a variable for which way the bird is flying so roughly:
bool goleft;
if(goleft)
{
if(transform.position != pos2)
{
transform.position = Vector3.Lerp (pos1, pos2, (Mathf.Abs(speed * Time.time) + 1.0f) / 2.0f);
}
else
{
goleft = false;
//change the direction the bird is facing here
}
}
else
{
if(transform.position != pos1)
{
transform.position = Vector3.Lerp (pos2, pos1, (Mathf.Abs(speed * Time.time) + 1.0f) / 2.0f);
}
else
{
goleft = true;
//change the direction the bird is facing here
}
}
Hope that helps
Not tested but i would start here:
Vector3[] posts;
int current = 0;
float speed = 5.0f;
float threshold = Single.Epsilon;
float delay = 2.0f;
public void Update() {
if(!waiting && posts.Length > 0)
{
if(!Mathf.Approximately((posts[current].position - transform.position).sqrMagnitude, threshold)
{
float step = speed * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, posts[current].position, step);
}
else StartCoroutine("Next");
}
}
IEnumerator Next() {
waiting = true;
yield return new WaitForSeconds(delay);
current = (current + 1) % posts.Length;
waiting = false;
}
This will also allow you to have as many posts as you want to have, and all your movement dynamics can be handled in Next(), whereas, if you want him to go from post 0...1...2...3...0...1.. or 0...1...2...3...2...1...
If you want the latter you just change current = (current + 1) % posts.Length; to Mathf.PingPong(current + 1, posts.Length);
I'd go a bit like this:
float flipCooldown
float defaultFlipCooldown = 2.0f;
bool isGoingRight;
Vector2 pos1;
Vector2 pos2;
void Start() {
flipCooldown = defaultFlipCooldown;
isGoingRight = true;
pos1 = new Vector2(0, 0);
pos2 = new Vector2(5, 0); // whatever floats your boat
}
void Update()
{
Vector2 initialPosition = null;
Vector2 finalPosition = null;
if (flipCooldown <= 0) {
isGoingRight = !isGoingRight
flipCooldown = defaultFlipCooldown;
ChangeSprite();
}
if (isGoingRight) {
initialPos = pos1;
finalPos = pos2;
} else {
initialPos = pos2;
finalPos = pos1;
}
bird.transform.position = Vector3.Lerp (initialPos, finalPos, (Mathf.Abs(speed * Time.time) + 1.0f) / 2.0f);
flipCooldown -= Time.deltaTime;
}
What you want to get is that the Time.deltaTime is decreasing the cooldown for the bird to turn. You can easily change the cooldown in the defaultFlipCooldown variable. When it's done going one way, it'll just flip the position and the Lerp function will do the rest of the work. The ChangeSprite function will be just a GetComponent<SpriteRenderer>().sprite change.
If you don't want fix positions, you can calculate how much it'll fly, define the final position and just change the pos1 and pos2.
It's also important to note that WaitForSeconds will just work with Coroutines that is a concept for working with threads, never in a method like Update. You can learn more about Coroutines in Unity's manual: http://docs.unity3d.com/Manual/Coroutines.html
(Using unity3d 4.3 2d, it uses box2d like physics).
I have problems with predicting trajectory
I'm using:
Vector2 startPos;
float power = 10.0f;
float interval = 1/30.0f;
GameObject[] ind;
void Start (){
transform.rigidbody2D.isKinematic = true;
ind = new GameObject[dots];
for(int i = 0; i<dots; i++){
GameObject dot = (GameObject)Instantiate(Dot);
dot.renderer.enabled = false;
ind[i] = dot;
}
}
void Update (){
if(shot) return;
if(Input.GetAxis("Fire1") == 1){
if(!aiming){
aiming = true;
startPos = Input.mousePosition;
ShowPath();
}
else{
CalculatePath();
}
}
else if(aiming && !shot){
transform.rigidbody2D.isKinematic = false;
transform.rigidbody2D.AddForce(GetForce(Input.mous ePosition));
shot = true;
aiming = false;
HidePath();
}
}
Vector2 GetForce(Vector3 mouse){
return (new Vector2(startPos.x, startPos.y)- new Vector2(mouse.x, mouse.y))*power;
}
void CalculatePath(){
ind[0].transform.position = transform.position; //set frist dot to ball position
Vector2 vel = GetForce(Input.mousePosition); //get velocity
for(int i = 1; i < dots; i++){
ind[i].renderer.enabled = true; //make them visible
Vector3 point = PathPoint(transform.position, vel, i); //get position of the dot
point.z = -1.0f;
ind[i].transform.position = point;
}
}
Vector2 PathPoint(Vector2 startP, Vector2 startVel, int n){
//Standard formula for trajectory prediction
float t = interval;
Vector2 stepVelocity = t*startVel;
Vector2 StepGravity = t*t*Physics.gravity;
Vector2 whattoreturn = ((startP + (n * stepVelocity)+(n*n+n)*StepGravity) * 0.5f);
return whattoreturn;
}
Using this, I get wrong trajectory.
1. It's like gravity doesn't drag trajectory down at all, and yes i know that gravity is weak because:
t*t*Physics.gravity = 0.03^2 * vector2(0, -9.8) = vector2(0, -0.00882)
But that is the formula :S
2. Since gravity is low, velocity is too strong.
Here is the video:
http://tinypic.com/player.php?v=1z50w3m&s=5
Trajectory formula form:
http://www.iforce2d.net/b2dtut/projected-trajectory
What should I do?
I found that if I set
StepGravity to something stronger like (0, -0.1)
and devide startVel by 8
I get nearly right trajectory, but i don't want that, I need true trajectory path.
Users from answer.unity3d.com said I should ask here, because here is a bigger group of mathematical coders.
And I searched a lot about this problem (that how I found that formula).
you're only calculating the effect of gravity over 1/30th of a second for each step - you need to do it cumulatively. Step 1 should end with a velocity of 0.09G, Step 2 with .18G, step3 with .27G etc.
Here's a very simple example that draws the ballistic trajectory based on start velocity and a supplied time:
using UnityEngine;
using System.Collections;
public class grav : MonoBehaviour {
public Vector3 StartVelocity;
public float PredictionTime;
private Vector3 G;
void OnDrawGizmos()
{
if (G == Vector3.zero)
{
// a hacky way of making sure this gets initialized in editor too...
// this assumes 60 samples / sec
G = new Vector3(0,-9.8f,0) / 360f;
}
Vector3 momentum = StartVelocity;
Vector3 pos = gameObject.transform.position;
Vector3 last = gameObject.transform.position;
for (int i = 0; i < (int) (PredictionTime * 60); i++)
{
momentum += G;
pos += momentum;
Gizmos.DrawLine(last, pos);
last = pos;
}
}
}
In you version you'd want draw your dots where I'm drawing the Gizmo, but it's the same idea unless I'm misunderstanding your problem.
I created a object in unity
GameObject monsterclone =
(GameObject)Instantiate(monsterPrefab, floorPosition, Quaternion.identity);
This object should move in a wave style from a limit1 to limit2.
Then move back from limit2 to limit1.
Y position as well x position have to change in a specific way.
Vector3 nPos = mfloorPos + new Vector3(2f, 0f, 0f);
Vector3 oPos = mfloorPos + new Vector3(-2f, 0f, 0f);
How can I do it?
I can't exactly write the code without knowing more specific but i think this question is already asked any this link will help you out MOVE OBJECT as wave
EDIT:
I think the flat up and float down functionality will work for you for moving one point to another
example:
var floatup;
function Start(){
floatup = false;
}
function Update(){
if(floatup)
floatingup();
else if(!floatup)
floatingdown();
}
function floatingup(){
transform.position.y += 0.3 * Time.deltaTime;
yield WaitForSeconds(1);
floatup = false;
}
function floatingdown(){
transform.position.y -= 0.3 * Time.deltaTime;;
yield WaitForSeconds(1);
floatup = true;
}
example taken from
float amplitudeX = -25.0f;
float amplitudeY = 5.0f;
float omegaX = 0.5f;
float omegaY = 4.0f;
float index;
void Update () {
index += Time.deltaTime;
float x = amplitudeX*Mathf.Cos (omegaX*index);
float y = Mathf.Abs (amplitudeY*Mathf.Sin (omegaY*index));
if(transform.position.x > 24){
transform.eulerAngles = new Vector3(270, -90, 0);
}
if(transform.position.x < -24){
transform.eulerAngles = new Vector3(270, 90, 0);
}
transform.localPosition= new Vector3(x,y,20);
}
If this is a consitant wave and not dependant on speed I would use an animation to create a literal wave curve of the Position.Y value (much in the same principle as Ravindra Shekhawat has explained.) you can find out more about animation here.
Here is some code (untested) that you could go off. It is in c# so I hope it proves no issue with putting in to JavaScript.
bool monsterMoving = false;
void Update(){
//check monster moving to start updating position
if(monsterMoving == true){
//moving animation controls up and down "wave" movement
animation.CrossFade('moving');
//Lerp changes position
Transform.Lerp(transform.Position, oPos, Time.deltaTime);
if (transform.Position == oPos) {
//We are at destination, stop movement
monsterMoving = false;
}
} else {
// if monster stopped moving, return to idle animation (staying still)
animation.CrossFade('idle');
}
}
// function to send a new position to the monster object
void MoveTo(Vector3 newPos){
oPos = newPos;
monsterMoving = true;
}