- Home /
How to re-create One More Line mechanic?
Hi, we are developing a multiplayer game based on vehicles that move only forward but when they are near a column they can rotate around it and then exit from the circular movement with a linear and tangent one. The mechanic is really similar to One More Line. Here a graphical scheme to represent the mechanic (in green: collider that detects if the button is pressed) We don't know how to make entry end exit tangent to the collider. We found the function LookAt but we don't know how to use it to make only one axis to point the column, not the entire vehicle's model. In the prototype, we started with a vehicle that is going towards the column as showed in the previous sketch, so for the moment, as you can see in the code, I tried to rotate of 90° (Y axis) the vehicle but doing so it happens that the hook collider and the vehicle's collider doesn't touch anymore, so I was trying to find a solution to make the transition from linear direction to tangent in a smooth way. Here is the code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour {
private Rigidbody rb;
public GameObject hook; //sensor that detects if the vehicle is eligible to rotate
public bool rotationStarted; //to detect if the vehicle is rotating or not
public int speed; //speed of vehicle when in linear motion
void Start()
{
rb = GetComponent<Rigidbody>();
rb.AddForce(transform.forward * speed); //adding initial force to Z axis
rotationStarted = false; //the vehicle does not rotate at the beginning
}
void OnTriggerStay(Collider other)
{
//detect if the vehicle is in the action range of the sensor "hook" and if the player is keeping pressed the button
if (other.gameObject.CompareTag("hook") && (Input.GetKey(KeyCode.UpArrow)))
{
//column = GameObject.FindWithTag("hook"); WARNING: this function doesn't work as expected, because it identifies as object the last one to have the tag "hook", not the true touched object
//detect if the vehicle is passing from linear movement to rotation
if (rotationStarted == false)
{
rotationStarted = true;
//WARNING: the following 4 lines are trying to lerp the rotation but it's infinite, so we must find a way to terminate the rotation
var fromAngle = transform.eulerAngles;
var targetAngle = transform.eulerAngles + new Vector3(0, -90, 0);
transform.eulerAngles = Vector3.Lerp(fromAngle, targetAngle, 0.01f);
Debug.Log(transform.eulerAngles + " " + fromAngle + " " + targetAngle);
// transform.Rotate(0, -90, 0); //NOT EFFICIENT: if you press the button when it starts to touch the hook, vehicle's collider doesn't touch the hook anymore; I think we should use Lerp
//transform.LookAt(column.transform); TEMPORALY DISABLED: we must find a way to LookAt only with an axis, not with the entire vehicle's model
}
rb.velocity = Vector3.zero; //stopping the linear movement
transform.RotateAround(hook.transform.position, Vector3.up, 100 * Time.deltaTime); //rotating around the column
Debug.Log("Rotating");
}
//detect if the player release the button
else if (other.gameObject.CompareTag("hook") && Input.GetKeyUp(KeyCode.UpArrow))
{
rotationStarted = false;
rb.AddForce(transform.forward * speed); //let the vehicle moves linearly again
Debug.Log("Going to be linear");
}
}
// DISABLED: it does not works as expected although this --> FOR SECURITY REASONS: in the case the vehicle's collider shouldn't touch the hook anymore while keeping pressed the button, the vehicle stops; this function prevent this bug letting the vehicle going linear
/*private void OnTriggerExit(Collider other)
{
if (other.gameObject.CompareTag("hook"))
{
rotationStarted = false;
rb.AddForce(transform.forward * speed);
Debug.Log("DOH! Problems with colliders, I'm going to be linear");
}
}
*/
void Update()
{
}
}
As you can probably note, there are also little problems of other type but for the moment it would be awesome to understand how to rotate the vehicle in order to make it tangent to hook.
Thanks in advance!
Answer by Bunny83 · Dec 01, 2017 at 08:18 PM
Sorry but your 4 images do not represent the mechanics of "One More Line". In that game you will automatically link to the closest point ahead. However you will continue going forward until you you pass the tangent point of the orbit around the link point from your current course. So in the case illustrated in your images you would run straight into the object.
It actually works like this:
So when you link to an object you will continue to move in a straight line until you reach the tangential point. So the object you are linked to is now exactly 90° to your right or left. At this point you will start moving around the object until you release the button. At this point you will simply move tangentially outward.
So the logic would be more like:
Just move forward.
If the button is pressed down do a Physics.OverlapSphere to get all link points in range and pick the closest one.
Determine if the link point is to the right or left of your current path.
Determine if you're before or after the tangent point be calculating the dot product between either your local left or right vector (depends on the condition above) and the vector from your player to the link point. As long as the dot product is > 0 you are before the tangential point and just keep moving.
As soon as you are behind or at the tangent point (dot
When the button is released you change the movement back to a linear motion.
Once you reach the tangent point you know the radius you have to circle around. I would now calculate the required angular velocity to get the desired tangential velocity. aVel = tVel / radius
.
Now you can simply set the velocity of your object to "0,0,0", move the centerOfMass either to the left or right, "radius" units apart so the centerOfMass is at the same point as our link point. When setting the angularVelocity to our calculated value the rigidbody would rotate around the link point.
When you release the button you revert the changes. You would call "ResetCenterOfMass" or set it to (0,0,0) depending on your object, set the angularVelocity to 0 and set the linear velocity back to moving along the local forward axis.
edit The mechanic looks interesting so i quickly implemented it in this script ^^.
//OneMoreLinePlayer.cs
using UnityEngine;
public class OneMoreLinePlayer : MonoBehaviour
{
public float speed = 3;
public float searchRadius = 20;
public float playerSize = 1;
public LayerMask linkPointLayerMask;
Rigidbody rb;
LineRenderer lineRenderer;
Transform linkPoint = null;
bool circularMotion = false;
Vector3 angularVel;
void Start ()
{
rb = GetComponent<Rigidbody>();
lineRenderer = GetComponent<LineRenderer>();
}
void Update ()
{
if (!circularMotion)
{
rb.velocity = transform.forward * speed;
rb.angularVelocity = Vector3.zero;
}
else
{
rb.velocity = Vector3.zero;
rb.angularVelocity = angularVel * speed;
}
if (Input.GetKeyDown(KeyCode.Space))
{
linkPoint = null;
var colliders = Physics.OverlapSphere(transform.position, searchRadius, linkPointLayerMask);
float dist = float.PositiveInfinity;
for(int i = 0; i < colliders.Length; i++)
{
var link = colliders[i].transform;
Vector3 dir = link.position - transform.position;
float tangentDist = Vector3.ProjectOnPlane(dir, transform.forward).magnitude;
if (tangentDist < (link.localScale.x + playerSize))
continue; // ignore link points which are in our current path
float d = dir.sqrMagnitude;
if (Vector3.Dot(dir, transform.forward) < 0)
d *= 8; // fake distance of linkpoints behind us to be further away
// this prefers link points in front of us.
if (d < dist)
{
linkPoint = link;
dist = d;
}
}
}
if (linkPoint != null && !circularMotion)
{
Vector3 dir = linkPoint.position - transform.position;
if (Vector3.Dot(dir, transform.forward) <= 0)
{
circularMotion = true;
float radius = dir.magnitude;
if (Vector3.Dot(dir, transform.right) > 0)
{
angularVel = transform.up / radius;
transform.right = dir;
}
else
{
angularVel = -transform.up / radius;
transform.right = -dir;
}
rb.centerOfMass = transform.InverseTransformPoint(linkPoint.position);
}
}
if (circularMotion && (!Input.GetKey(KeyCode.Space) || linkPoint == null))
{
circularMotion = false;
rb.ResetCenterOfMass();
linkPoint = null;
}
if (linkPoint != null)
{
lineRenderer.enabled = true;
lineRenderer.SetPosition(0, transform.position);
lineRenderer.SetPosition(1, linkPoint.position);
}
else
{
lineRenderer.enabled = false;
}
}
}
Here's how the result looks like:
The link points a just default sphere primitives which have been placed on a special layer, This layer is selected in the "linkPointLayerMask" in the inspector. The player has a rigidbody, this script, a line renderer and a trail renderer. That's all
Can you share your repository for this? I tried this code but direction on player exit was different every time...
Your answer
Follow this Question
Related Questions
Non slippery movement 1 Answer
Translating Speed into rotation degress\sec 1 Answer
Manual Player Physics Help (C#) 0 Answers
How to rotate around with Rigidbody and respect physics? 0 Answers