First off, sorry it this isn't written very well, I've spend hours debugging this and I'm very stressed. I'm trying to make a moving platform in unity that can move between way-points, I don't want to have to have tons of gameobjects in the world taking up valuable processing power though so I'm trying to use something I can just add to the script through the editor.
The only problem is that it seems to be doing this at an incredible speed:
Black = The Camera View, Blue = The platform and where it should be going based on waypoints, Red = What it is currently doing.
I've spend hours trying to find a fix but I have no idea why it's doing this.
My Script on the Platform:
public Vector3[] localWaypoints;
Vector3[] globalWaypoints;
public float speed;
public bool cyclic;
public float waitTime;
[Range(0, 2)]
public float easeAmount;
int fromWaypointIndex;
float percentBetweenWaypoints;
float nextMoveTime;
void Start()
{
globalWaypoints = new Vector3[localWaypoints.Length];
for (int i = 0; i < localWaypoints.Length; i++)
{
globalWaypoints[i] = localWaypoints[i] + transform.position;
}
}
void Update()
{
Vector3 velocity = CalculatePlatformMovement();
transform.Translate(velocity);
}
float Ease(float x)
{
float a = easeAmount + 1;
return Mathf.Pow(x, a) / (Mathf.Pow(x, a) + Mathf.Pow(1 - x, a));
}
Vector3 CalculatePlatformMovement()
{
if (Time.time < nextMoveTime)
{
return Vector3.zero;
}
fromWaypointIndex %= globalWaypoints.Length;
int toWaypointIndex = (fromWaypointIndex + 1) % globalWaypoints.Length;
float distanceBetweenWaypoints = Vector3.Distance(globalWaypoints[fromWaypointIndex], globalWaypoints[toWaypointIndex]);
percentBetweenWaypoints += Time.deltaTime * speed / distanceBetweenWaypoints;
percentBetweenWaypoints = Mathf.Clamp01(percentBetweenWaypoints);
float easedPercentBetweenWaypoints = Ease(percentBetweenWaypoints);
Vector3 newPos = Vector3.Lerp(globalWaypoints[fromWaypointIndex], globalWaypoints[toWaypointIndex], easedPercentBetweenWaypoints);
if (percentBetweenWaypoints >= 1)
{
percentBetweenWaypoints = 0;
fromWaypointIndex++;
if (!cyclic)
{
if (fromWaypointIndex >= globalWaypoints.Length - 1)
{
fromWaypointIndex = 0;
System.Array.Reverse(globalWaypoints);
}
}
nextMoveTime = Time.time + waitTime;
}
return newPos - transform.position;
}
struct PassengerMovement
{
public Transform transform;
public Vector3 velocity;
public bool standingOnPlatform;
public bool moveBeforePlatform;
public PassengerMovement(Transform _transform, Vector3 _velocity, bool _standingOnPlatform, bool _moveBeforePlatform)
{
transform = _transform;
velocity = _velocity;
standingOnPlatform = _standingOnPlatform;
moveBeforePlatform = _moveBeforePlatform;
}
}
void OnDrawGizmos()
{
if (localWaypoints != null)
{
Gizmos.color = Color.red;
float size = .3f;
for (int i = 0; i < localWaypoints.Length; i++)
{
Vector3 globalWaypointPos = (Application.isPlaying) ? globalWaypoints[i] : localWaypoints[i] + transform.position;
Gizmos.DrawLine(globalWaypointPos - Vector3.up * size, globalWaypointPos + Vector3.up * size);
Gizmos.DrawLine(globalWaypointPos - Vector3.left * size, globalWaypointPos + Vector3.left * size);
}
}
}
UPDATE: Upon further testing I found that if the first object in my localWaypoint array is set to 0,0,0 and my 2nd object is set to 1,0,0 then the platform will spiral to the right, making sure to hit the waypoints as it's spiraling, and then spiraling out into nowhere like in the image above. But if I set my first object to 0,0,0 and my second object to -1,0,0 then the object will act the same way as before, but will spiral to the left as displayed in this image. (The second image has also bee updated to display how the platfrom makes sure to hit both waypoints before is spirals out into nowhere).
I've also noticed that if I set both waypoints to 0,0,0 then the platform stays still, these 2 things prove that it has somthing to do with the way the waypoints are being handled and not some other script or parent object interfering.
Using the updated numbers ([0,0,0], [1,0,0]) works in my test app. However, if I put a rotation on the object's Y axis, then I see behavior like you are seeing. In Update, if you change:
transform.Translate(velocity);
to
transform.Translate(velocity, Space.World);
You should see your desired behavior. Note that "transform.Translate(velocity)" is the same as "transform.Translate(velocity, Space.Self)". Your translation is being rotated.
If you are curious, take a look at this for more information on how the values in the transform are applied:
https://gamedev.stackexchange.com/questions/138358/what-is-the-transformation-order-when-using-the-transform-class
Related
Im making a game where you click and drag with the mouse then release to launch the player. But sometimes the player gets launched in the opposite direction of where it should go. I made a debug output to show you the different values. Here is the output
In that image for example you can see that the Vector2 of force * power is positive on the y axis, but the player launched downwards, and the same happens Viceversa. I think its also worth to note that this happens inconsistantly for some reason. Here is my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Movement : MonoBehaviour
{
public GameObject player;
public float power = 10f;
public Rigidbody2D rb;
public float maxSpeed;
public Vector2 minPower;
public Vector2 maxPower;
TragectoryLine tl;
Camera cam;
public Vector2 force;
public Vector3 startPoint;
public Vector3 endPoint;
public Vector3 currentPoint;
public Vector3 startPointMouse;
public bool isPulling = false;
float distance;
private void Start()
{
cam = Camera.main;
tl = GetComponent<TragectoryLine>();
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
startPointMouse = cam.ScreenToWorldPoint(Input.mousePosition);
startPointMouse.z = 15;
}
if (Input.GetMouseButton(0))
{
startPoint = player.transform.position;
startPoint.z = 15;
isPulling = true;
Vector3 currentPoint = cam.ScreenToWorldPoint(Input.mousePosition);
currentPoint.z = 15;
tl.RenderLine(startPoint, currentPoint);
}
if (Input.GetMouseButtonUp(0))
{
endPoint = cam.ScreenToWorldPoint(Input.mousePosition);
endPoint.z = 15;
isPulling = false;
tl.EndLine();
distance = startPointMouse.magnitude - endPoint.magnitude;
if (distance < 0)
{
distance = -distance;
}
if (distance >= 1)
{
rb.AddForce(force * power, ForceMode2D.Impulse);
}
force = new Vector2(Mathf.Clamp(startPoint.x - endPoint.x, minPower.x, maxPower.x), Mathf.Clamp(startPoint.y - endPoint.y, minPower.y, maxPower.y));
Debug.Log("distance" + distance);
Debug.Log("start" + startPoint);
Debug.Log("end" + endPoint);
Debug.Log("force" +force);
Debug.Log("force * power" + force * power);
}
}
private void FixedUpdate()
{
rb.velocity = Vector3.ClampMagnitude(rb.velocity, maxSpeed);
}
}
Here I added the force using rb.AddForce(force * power, ForceMode2D.Impulse); when the force * power value was positive on the y axis. So why did it go to the opposite direction???
This was working perfectly fine before i tried implementing a feature where the player has to move the mouse a certain distance or else it wont launch. I have tried removing it but it doesnt seem to make a difference. I think I changed something in the code that ruined it but I cant figure out what! Please help!
I'm trying to recreate a solar system and i'm using newton's law. The actual force works great but when i try to "predict" the path of a planet it just doesn't work: to predict it i use a lineRenderer whose points get placed on the position at the Tth instant ( T being time ). When i start the game they are close but as time passes the planet actually goes in "orbit" while the line shoots up.
I can't understand why this happens seen that i calculate the planet's position in the same way as the line's one. I also tried instantiating a ball but same result.
public void UpdateSpeed(Vector3 acceleration,float time)
{
velocity += acceleration * time;
}
public void UpdatePosition(float time)
{
transform.position +=velocity *time;
}
public void UpdateLine(float time)
{
position += velocity * time;
Debug.Log(position);
Instantiate(ball, position, Quaternion.identity);
line.positionCount++;
line.SetPosition(line.positionCount-1, position);
}
and here the function that computes acceleration
public Vector3 CalculateAcceleration(GameObject subj, float time)
{
Vector3 Acceleration = Vector3.zero;
foreach (var pl in planets)
{
if (pl != subj)
{
float sqrDistance= Mathf.Pow(Vector3.Distance(subj.transform.position, pl.transform.position), 2);
Vector3 direction = (pl.transform.position - subj.transform.position).normalized;
Acceleration += direction * (pl.GetComponent<Rigidbody>().mass * GravitationalConstant)/sqrDistance;
}
}
return Acceleration;
}
is the rigidbody attached to the planet making a difference for the position?
If you are having trouble with the orbit, calculate the OrbitFirst then call it to move your object with different time, just modify delta time below
LineRenderer lr;
//Build the orbit path/line
void CalculateOrbitEllipse()
{
Vector3[] points = new Vector3[segments + 1];
for (int i = 0; i < segments; i++)
{
Vector2 position2D = orbitPath.Evaluate((float)i / (float)segments);
points[i] = new Vector3(position2D.x, 0f, position2D.y);
}
points[segments] = points[0];
lr.positionCount = segments + 1;
lr.SetPositions(points);
}
//make the planet move,
//you can invoke this using your time variable, Time.deltaTime see below
IEnumerator AnimationOrbit()
{
if (orbitPeriod < 0.1f)
{
orbitPeriod = 0.1f;
}
float orbitSpeed = 0.1f / orbitPeriod;
while (orbitActive)
{
orbitProgress += Time.deltaTime * orbitSpeed;
orbitProgress %= 1f;
SetOrbitingObjectPosition();
lr = GetComponent<LineRenderer>();
CalculateOrbitEllipse();
yield return null;
}
}
void SetOrbitingObjectPosition()
{
Vector2 orbitPos = orbitPath.Evaluate(orbitProgress);
orbitingObject.localPosition = new Vector3(orbitPos.x, 0, orbitPos.y);
}
so as often happens i'm an idiot. I was calculating in the acceleration function distances with the actual bodies positions instead of the simulated position so i always had a false value that was increasing instead of varying
I have a stationary cube in my scene that I'm orbiting a camera around. I have my MainCamera nested under a GameObject that I'm calling 'OrbitalCamera'.
I setup the script so that a click (or tap) and drag will rotate the camera around the object in space so it feels like you're rotating the cube (ie: if I click the top of a face on the cube, and pull down, I'm rotating the X value) but you'll actually be rotating the camera.
For the most part, my script works. However, after rotating the Y so much, the camera is upside down and the X gets inverted. Here's my script:
public class OrbitalCamera : MonoBehaviour {
public bool cameraEnabled;
[SerializeField] private float touchSensitivity;
[SerializeField] private float scrollSensitivity;
[SerializeField] private float orbitDampening;
protected Transform xFormCamera;
protected Transform xFormParent;
protected Vector3 localRotation;
protected float cameraDistance;
void Start () {
cameraEnabled = true;
xFormCamera = transform;
xFormParent = transform.parent;
cameraDistance = transform.position.z * -1;
}
void LateUpdate () {
if (cameraEnabled) {
// TODO:: FIX PROBLEM WHERE WHEN CAMERA IS ROTATED TO BE UPSIDEDOWN, CONTROLS GET INVERSED
if (Input.GetMouseButton(0)) {
if (Input.GetAxis("Mouse X") != 0 || Input.GetAxis("Mouse Y") != 0) {
localRotation.x += Input.GetAxis("Mouse X") * touchSensitivity;
localRotation.y -= Input.GetAxis("Mouse Y") * touchSensitivity;
}
}
}
Quaternion qt = Quaternion.Euler(localRotation.y, localRotation.x, 0);
xFormParent.rotation = Quaternion.Lerp(xFormParent.rotation, qt, Time.deltaTime * orbitDampening);
}
}
Is there a good method to achieve this type of 360 camera? I'd like dragging from right to left to always move the camera left and dragging left to right to always move the camera right -- no matter how the camera is oriented.
Perhaps you could clamp the above/below pan at 89 degrees?
I recently helped a friend make a mouse gimbal, and found allowing freedom beyond 89 degrees was problematic and unnecessary. It seems like your application is the same, at least for one of the two planes.
In your LateUpdate() call, you could perhaps add:
localRotation.x += Input.GetAxis("Mouse X") * touchSensitivity;
localRotation.x = Clamp(localRotation.x);
Then, of course, create your clamp function, which should be fairly straight forward:
float Clamp(float val) // prevent values from ~90 - ~270
{
int lowVal = 89;
int highVal = 271;
int midVal = 180;
if (val > lowVal & val < highVal)
{
if (val > midVal) val = highVal;
else val = lowVal;
}
return val;
}
A slightly different application, but I'm sure you can see how I've set this up: I apply rotation in two steps. Step 1 - a simple Rotate() call, Step 2 - clamping some/all of the rotation:
using UnityEngine;
public class MouseGimbal : MonoBehaviour
{
[SerializeField] [Range(0,89)] float maxRotationDegrees = 10.0f; // At 90+ gimbal oddities must be dealt with.
[SerializeField] bool ClampToMaxRotationDegrees = true; // Disable for free rotation.
[SerializeField] float rotationSpeed = 10.0f;
const float fullArc = 360.0f;
const float halfArc = 180.0f;
const float nullArc = 0.0f;
void Update () { Tilt(); }
void Tilt()
{
// Apply the 'pre-clamp' rotation (rotation-Z and rotation-X from X & Y of mouse, respectively).
if (maxRotationDegrees > 0) { SimpleRotation(GetMouseInput()); }
// Clamp rotation to maxRotationDegrees.
if (ClampToMaxRotationDegrees) { ClampRotation(transform.rotation.eulerAngles); }
}
void ClampRotation(Vector3 tempEulers)
{
tempEulers.x = ClampPlane(tempEulers.x);
tempEulers.z = ClampPlane(tempEulers.z);
tempEulers.y = nullArc; // ClampPlane(tempEulers.y); // *See GIST note below...
transform.rotation = Quaternion.Euler(tempEulers);
///Debug.Log(tempEulers);
}
float ClampPlane(float plane)
{
if (OkayLow(plane) || OkayHigh(plane)) DoNothing(); // Plane 'in range'.
else if (BadLow(plane)) plane = Mathf.Clamp(plane, nullArc, maxRotationDegrees);
else if (BadHigh(plane)) plane = Mathf.Clamp(plane, fullArc - maxRotationDegrees, fullArc);
else Debug.LogWarning("WARN: invalid plane condition");
return plane;
}
Vector2 GetMouseInput()
{
Vector2 mouseXY;
mouseXY.x = -Input.GetAxis("Mouse X"); // MouseX -> rotZ.
mouseXY.y = Input.GetAxis("Mouse Y"); // MouseY -> rotX.
return mouseXY;
}
void SimpleRotation(Vector2 mouseXY)
{
Vector3 rotation = Vector3.zero;
rotation.x = mouseXY.y * Time.deltaTime * rotationSpeed;
rotation.z = mouseXY.x * Time.deltaTime * rotationSpeed;
transform.Rotate(rotation, Space.Self);
}
void DoNothing() { }
bool OkayHigh(float test) { return (test >= fullArc - maxRotationDegrees && test <= fullArc); }
bool OkayLow(float test) { return (test >= nullArc && test <= maxRotationDegrees); }
bool BadHigh(float test) { return (test > halfArc && !OkayHigh(test)); }
bool BadLow(float test) { return (test < halfArc && !OkayLow(test)); }
}
When I developed an orbital camera, I created 3 objects:
- x_axis (rotate with vertical mouse moviment)
- y_axis (rotate with horizontal mouse moviment)
- camera (look_at object) (translated Vector3(0,0,-10))
In the top
public GameObject[] waypoints;
public Transform target;
public float moveSpeed = 1f;
public float rotationSpeed = 1f;
Transform myTransform;
public float walkSpeed = 10f;
public ThirdPersonCharacter[] thirdPersonCharacter;
Vector3 boundLower;
Vector3 boundUpper;
public Terrain terrain;
Awake and Start:
void Awake()
{
myTransform = transform;
}
void Start()
{
GameObject tpc1 = GameObject.Find("ThirdPersonController");
thirdPersonCharacter[0] = tpc1.GetComponent<ThirdPersonCharacter>();
GameObject tpc2 = GameObject.Find("ThirdPersonController (1)");
thirdPersonCharacter[1] = tpc2.GetComponent<ThirdPersonCharacter>();
waypoints = GameObject.FindGameObjectsWithTag("ClonedObject");
boundLower = terrain.transform.position - terrain.terrainData.size / 2;
boundUpper = terrain.transform.position + terrain.terrainData.size / 2;
}
The Update:
void Update()
{
thirdPersonCharacter[0].m_MoveSpeedMultiplier = walkSpeed;
thirdPersonCharacter[1].m_MoveSpeedMultiplier = walkSpeed;
WayPoints();
CheckBounds();
}
The WayPoints:
int index = 0;
private void WayPoints()
{
if (index == waypoints.Length)
index = 0;
target = waypoints[index].transform;
float distance = Vector3.Distance(myTransform.position, target.transform.position);
myTransform.rotation = Quaternion.Slerp(myTransform.rotation,
Quaternion.LookRotation(target.position - myTransform.position),
rotationSpeed * Time.deltaTime);
myTransform.position += myTransform.forward * moveSpeed * Time.deltaTime;
if (distance < 2f)
index++;
}
And the CheckBounds:
private Vector3 direction = Vector3.forward;
void CheckBounds()
{
foreach (var child in thirdPersonCharacter)
{
var pos = child.transform.position;
pos.x = Mathf.Clamp(pos.x, boundLower.x, boundUpper.x);
pos.z = Mathf.Clamp(pos.z, boundLower.z, boundUpper.z);
if (pos.x == boundLower.x || pos.x == boundUpper.x) direction.x = -direction.x;
if (pos.z == boundLower.z || pos.z == boundUpper.z) direction.z = -direction.z;
child.transform.position = pos;
}
}
Once i added the CheckBounds function and call it from the Update function the characters are walking on place. Not moving. Without the checkBounds they are walking fine between the waypoints. What i want to do is that if the character walked to the terrain edge(bound) just make him turn/rotate back or stop at place.
I must say that the characters(ThirdPersonController) are moving out of the Terrain bounds and falling only if i change the walking speed to 30 for example or more. But in any case i would like to know how to use the CheckBounds for other situations for example if a player is just walking to the Terrain bounds.
There are plenty of ways how to make a Boundary for a game. One example for all is in 1st and 2nd tutorial on Unity web:
https://unity3d.com/learn/tutorials/projects/roll-ball-tutorial/setting-play-area?playlist=17141
https://unity3d.com/learn/tutorials/projects/space-shooter-tutorial/boundary?playlist=17147
You can do it 3 ways:
Create "Boundary" object around the map, create Trigger and once somebody hit the trigger, push him back/stop him/don't allow him pass
Create "Boundary" object which fill whole map. Create Trigger on the OnExit method and once some object will try to leave, stop him/push back/don't allow him to pass
Stick to the programming and some "map" concept where You know the location of Boundaries. In such case You have to check the position and whenever the objects leaves behind the boundary, You have to stop him - this has to happen every update frame, not just on Triggers
Based on the size of the map and complexity of the game, I would pick one of the topper answers. Once the game get complicated You have to create own engine in an engine to handle map control & other things. But first: Step by step.
This code should work (once somebody cross the border, set to the border location + minimal step into center of map):
var pos = child.transform.position;
float step = 1; //adjust to fit scale/size of step
if (pos.x <= boundLower.x) pos.x = boundLower.x + step;
if (pos.x >= boundUpper.x) pos.x = boundUpper.x - step;
if (pos.z <= boundLower.z) pos.z = boundLower.z + step;
if (pos.z >= boundUpper.z) pos.z = boundUpper.z - step;
child.transform.position = pos;
Very confused about what's going on. I will post code, but just a brief description of my game is a space invaders remix. I have a CPU that is currently moving on the X axis, stops when it hits -900f, then moves down on the Z axis. Now I am trying to get to to stop moving down on the Z axis (accomplished), then move in the opposite X direction is was previously going in (before the X axis was moving 7f every update, I want it to move -7f every update from that point forward). My problem is that now when it hits the designated Z axis to stop, then move on the X axis, it starts to move, but then keeps going back to -900 on the X axis. In my code below you will see that I have the program telling the transform.position.x to equal -900f, however, I put it inside an if loop that only run that code if a bool value is true, which I set to false after I want the program to ignore the code that shows the x position to -900f.
public class InvaderController : MonoBehaviour {
public float resistance;
public int resistcount;
public int numhits;
public float newX; //set to public so I can see it change in Unity, will go private
float newZ;
public float invaderSpeed; //this is set to 7 in Unity
public GameObject Invader;
Quaternion rotation;
public GameObject explosion;
int expcount;
bool firstxoff = false;
bool test = true;
public GameObject explosion2;
// Use this for initialization
void Start () {
}
void Awake() {
firstxoff = true;
}
// Update is called once per frame
void Update () {
if (firstxoff == true)
{
firstmove ();
}
}
void firstmove()
{
Vector3 newPos = transform.position;
newPos.x -= invaderSpeed;
//newX = newPos.x;
transform.position = newPos;
if (newPos.x < -900f)
{
Vector3 newPosZ = transform.position;
newPosZ.z -= invaderSpeed;
float x = -900f;
newX = x;
newPosZ.x = newX;
transform.position = newPosZ;
}
moveX1 ();
}
void moveX1()
{
Vector3 newPos = transform.position;
if (newPos.z < 500f)
{
Vector3 newPosX = transform.position;
newX = newX + invaderSpeed;
float z = 500f;
newZ = z;
newPosX.z = newZ;
newPosX.x = newX;
transform.position = newPosX;
if (newX > 800f)
{
firstxoff = false;
}
}
}
void FixedUpdate() //need to figure out how to run update first, then this.
{
//firstxoff = false;
}
First time z is less than 500, your x will increase 7, right?
newX = newX + invaderSpeed;
So you go from -900 to -893. -893 is not greater than 800, so firstxoff is not set to false. The firstmove method is then called again.
newPos.x -= invaderSpeed;
X is now -893 minus 7, which is -900. That cycle will repeat perpetually. Z will always be 500 and x will always be -900.
Hopefully you can figure out the solution. Let me know if not.