Finding gameObject via direction of input - unity3d

I have a list of gameObjects added to a List within the area as shown. What I need is a directional input to choose a target from the origin point. I have got it work to get this origin point.
My first attempt of this was to get it via rayCast, but by doing that there were times directional inputs need to directly hit target object by the ray. This is no problem if input was done like case #1.
However, what I need it to really work is when input direction was as if case #2 or #3 the input will still get a target. My second attempt was to do this with sphereCast, but it still needed a target in sphere proximity and multiple targets hit by sphereCast still needed to result in only one and more accurate target selection by input.
Since I have all the transform.position of all the possible targets as well as the origin point I wondered there would be a more elegant way of resolving this via comparing vector3's of these coordinates(origin and targets in the general direction).
Here's my latest approach:
//
// m_targetList is the list containing all GameObjects as GameObjects in other script m_collector. All m_collector does is this.
//
using System.Collections.Generic;
using UnityEngine;
public class TargetSwitcher : MonoBehaviour
{
private TargetCollector m_collector;
private Transform m_origin;
public bool m_targetChanged = false;
public GameObject m_target;
public LayerMask m_targetMask;
private Dictionary<Vector3, float> m_targetInfo = new Dictionary<Vector3, float>();
private void Awake()
{
m_collector = GetComponent<TargetCollector>();
m_origin = GameObject.Find("TargetOrigin").transform;
m_tracker = GameObject.Find("TargetTracker").transform;
m_bound = GetComponent<BoxCollider>();
}
public void UpdateBearing(GameObject origin)
{
m_origin = origin.transform;
foreach (GameObject target in m_collector.m_targetList)
{
Vector3 dir = (target.transform.position - origin.transform.position).normalized
float dist = Vector3.Distance(origin.transform.position, target.transform.position);
m_targetInfo.Add(dir, dist);
}
}
public void SwitchTarget()
{
if (!m_targetChanged)
{
Vector2 dir = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")).normalized;
// Find closest direction value from Dictionary to dir of here
// Compare distance from origin if multiple targets and choose the nearest
}
}
public void ReturnToIdle()
{
m_origin.position = m_target.transform.position;
m_targetChanged = false;
m_targetInfo.Clear();
}
public struct TargetInfo
{
public Vector3 bearing;
public float distance;
public TargetInfo(Vector3 bearing, float distance)
{
this.bearing = bearing;
this.distance = distance;
}
}
}
Generally, I'm trying to compare the normalized vector of directional input to the normalized vector from the origin to each target before SwitchTarget(). The input method here is Gamepad axis x and y as Horizontal and Vertical.
Reposting this question since a provided answer was very far from the question and marked as duplicate(Given answer was about finding gameObject by distance only, this question is about direction and distance portion is to compare second-handedly when multiple items were found in the direction)
Edit
After some trials with dot product now I'm sure this is much likely where I want to head. There are much inconsistency I need to get on with, though.
Here's my most recent attempt:
private void Update()
{
UpdateBearing();
Vector3 input = new Vector3(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"), 0);
if (input != Vector3.zero)
{
SwitchTarget();
}
}
public void UpdateBearing(GameObject origin)
{
m_origin.position = origin.transform.position;
foreach (GameObject target in m_collector.m_targetList)
{
Vector3 dir = (target.transform.position - origin.transform.position).normalized;
if (!m_targetInfo.ContainsKey(target))
{
m_targetInfo.Add(target, dir);
}
}
}
public void SwitchTarget()
{
GameObject oldTarget = m_collector.m_target;
if (!m_targetChanged)
{
Vector3 dir = new Vector3(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"), 0).normalized;
Debug.DrawRay(m_origin.position, dir * 100, Color.yellow, 0.5f);
foreach (KeyValuePair<GameObject, Vector3> possibleTarget in m_targetInfo)
{
float dot = Vector3.Dot(dir, possibleTarget.Value);
if (dot > 0.5f) // Compare DP difference of added dot and input dot
{
GameObject newTarget = possibleTarget.Key;
if (oldTarget != newTarget)
{
Debug.Log(possibleTarget.Value + " // " + dot);
m_target = newTarget;
m_collector.m_target = newTarget;
m_targetChanged = true;
}
}
}
}
}
With this, I'm kind of getting gameObject selection without raycasting and missing any targets. However, I'm sure I need better case comparison than if(dot > 0.5f). Also, my rough assumption is that if I don't update the value of the dictionary m_targetInfo for each Key I'd have another inconsistency if those targets ever move. Anyways, I'm still confused how properly utilize this to achieve my end goal.

Since you have all the desired game objects in the area you can create a for loop and check the angle between your look direction and their position, if it is lower than some value (you can make it super low so it's precise or a little bit higher to allow for some margin of error) put it in a list of gameobjects and if there's more than one object there get the closest one.
The code for getting closest object in angle would look something like this:
GameObject CheckObjects()
{
List<GameObject> InAngle = new List<GameObject>();
for(int i = 0; i < YourObjectsList.Count; i++)
{
GameObject tested = YourObjectsList[i];
Vector3 dir = tested.transform.position - origin.transform.forward;
// I'm assuming here that youre rotating your origin with the
directional input, not then instead of origin.transform.forward
place there your directional vector3
float angle = Vector3.Angle(dir, tested.transform.position);
if(angle <= desiredAngle)
{
InAngle.Add(tested);
}
}
GameObject closest = null;
for(int j = 0; j < InAngle.Count; i++)
{
GameObject tested = InAngle[i];
Vector3 dir1 = tested.transform.position - origin.transform.position;
Vector3 dir2 = closest.transform.position - origin.transform.position;
if(!closest)
{
closest = tested;
}
else
{
if(dir2.sqrMagnitude > dir1.sqrMagnitude)
{
closest = tested;
}
}
}
return closest;
}

Related

Calibrating a gyroscope for different "flat" positions, then extracting x and y tilt

I have a top down 2D ball game like the old Labyrinth ball game. My game is played on a mobile phone. The user controls it by tilting the phone. The idea is that when you set the screen at a slant, the ball realistically rolls to the "bottom" of the slant. For example, if the phone is sitting flat on a table, the ball shouldn't be going anywhere. But if I tilt the device up towards me, like a book, then the ball should fall down to the bottom of the screen. This is easy to do with the accelerometer, BUT...
My question is how to calibrate the gyroscope so the game can easily be played from any starting "flat" 3d rotation of the device. I'd like the user to be able to "calibrate" the game so that however they want to hold the device, they can control the ball properly. I'm thinking:
If the phone is flat on the table, then tilting it will work "normally", as described in the first paragraph.
If the phone is upside down, then it should be the opposite of #1. This is for people who want to are holding the phone over their head in bed, or something.
If the phone is, say, held parallel to a wall (like a wall-mounted TV), then it should act as though the wall were a flat table, if that makes sense.
Currently, my code is:
public void OnHitCalibrate() {
_offset = Input.gyro.attitude;
}
public void Update() {
if(!_hasGyro) return;
Quaternion reading = Input.gyro.attitude;
reading *= Quaternion.Inverse(_offset); //subtract the offset
_gyro.rotation = reading;
}
This gets the KIND of input I want, but I don't understand how the axes of the quaternion returned by Input.gyro.attitude help me find my 2d tilt values. For example, when the device is pointed north, and the attitude is the same as Quaternion.Identity, the values seem to change reasonably -- when I turn the attitude into a euler angle, changes in the x value correspond to my y tilt, and changes in the y value correspond to changes in my x tilt. But when I spin around, all that changes, and when I turn the phone more parallel to the wall, the numbers don't seem directly correlated to the axes in the euler angle, even when I use my OnHitCalibrate function and subtract the offset. Is there any way to interpret the attitude so I can always know which way the device is being turned?
My end goal is to distill the attitude into a Vector2 representing force -- where both x and y are between -1 and 1, where 1 represents maximum force up or too the right, -1 is maximum force down or to the left, and 0 means no change in force for the ball.
UPDATE: Solved it! I've attached the class that handles my gyroscope offsets. Hopefully it helps you! Note that it's coded for Unity C#, and has a few of my custom classes in it already.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum TiltModule { GYROSCOPE, ACCELEROMETER, KEYBOARD_ONLY, NONE }
public class CalibratedGyroscope : SingletonKTBehavior<CalibratedGyroscope>
{
public bool _supported;
private Quaternion _off;
private Vector3 _offEuler;
int _activeSemaphore = 0;
private float _degreesForFullTilt = 10;
public Vector2 _lastTilt;
public void Init() {
_off = Quaternion.identity;
_supported = SystemInfo.supportsGyroscope;
}
public bool Activate(bool isActivated) {
if(isActivated) _activeSemaphore++;
else _activeSemaphore--;
_activeSemaphore = Mathf.Max(_activeSemaphore, 0);
if(_activeSemaphore > 0) {
if(_supported) {
Input.gyro.enabled = true;
} else {
return false; //everything not ok; you requested gyro but can't have it!
}
} else {
if(_supported) {
Input.gyro.enabled = false;
}
}
return true; //everything ok;
}
public void Deactivate() {
_activeSemaphore = 0;
}
public void SetCurrentReadingAsFlat() {
_off = Input.gyro.attitude;
_offEuler = _off.eulerAngles;
}
public Vector3 GetReading() {
if(_supported) {
return (Quaternion.Inverse(_off) * Input.gyro.attitude).eulerAngles;
} else {
Debug.LogError("Tried to get gyroscope reading on a device which didn't have one.");
return Vector3.zero;
}
}
public Vector2 Get2DTilt() {
Vector3 reading = GetReading();
Vector2 tilt = new Vector2(
-Mathf.DeltaAngle(reading.y, 0),
Mathf.DeltaAngle(reading.x, 0)
);
//can't go over max
tilt.x = Mathf.InverseLerp( -_degreesForFullTilt, _degreesForFullTilt, tilt.x) * 2 - 1;
tilt.y = Mathf.InverseLerp( -_degreesForFullTilt, _degreesForFullTilt, tilt.y) * 2 - 1;
//get phase
tilt.x = Mathf.Clamp(tilt.x, -1, 1);
tilt.y = Mathf.Clamp(tilt.y, -1, 1);
_lastTilt = tilt;
return tilt;
}
public string GetExplanation() {
Vector3 reading = GetReading();
string msg = "";
msg += "OFF: " + _offEuler + "\n";
Vector2 tilt = new Vector2(
-Mathf.DeltaAngle(reading.y, 0),
Mathf.DeltaAngle(reading.x, 0)
);
msg += "DELTA: " + tilt + "\n";
//can't go over max
tilt.x = Mathf.InverseLerp( -_degreesForFullTilt, _degreesForFullTilt, tilt.x) * 2 - 1;
tilt.y = Mathf.InverseLerp( -_degreesForFullTilt, _degreesForFullTilt, tilt.y) * 2 - 1;
msg += "LERPED: " + tilt + "\n";
//get phase
tilt.x = Mathf.Clamp(tilt.x, -1, 1);
tilt.y = Mathf.Clamp(tilt.y, -1, 1);
msg += "CLAMPED: " + tilt + "\n";
return msg;
}
public void SetDegreesForFullTilt(float degrees) {
_degreesForFullTilt = degrees;
}
}

how to limit and clamp distance between two points in a Line renderer unity2d

I am making a game which let you click on a ball and drag to draw a line renderer with two points and point it to a specific direction and when release I add force to the ball,
for now, I just want to know how can I limit the distance between those two points like give it a radius.
You can simply clamp it using a Mathf.Min.
Since you didn't provide any example code unfortunately here is some example code I made up with a simple plane with a MeshCollider, a child object with the LineRenderer and a camera set to Orthographic. You probably would have to adopt it somehow.
public class Example : MonoBehaviour
{
// adjust in the inspector
public float maxRadius = 2;
private Vector3 startPosition;
[SerializeField] private LineRenderer line;
[SerializeField] private Collider collider;
[SerializeField] private Camera camera;
private void Awake()
{
line.positionCount = 0;
line = GetComponentInChildren<LineRenderer>();
collider = GetComponent<Collider>();
camera = Camera.main;
}
// wherever you dragging starts
private void OnMouseDown()
{
line.positionCount = 2;
startPosition = collider.ClosestPoint(camera.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, transform.position.z)));
var positions = new[] { startPosition, startPosition };
line.SetPositions(positions);
}
// while dragging
private void OnMouseDrag()
{
var currentPosition = GetComponent<Collider>().ClosestPoint(camera.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, transform.position.z)));
// get vector between positions
var difference = currentPosition - startPosition;
// normalize to only get a direction with magnitude = 1
var direction = difference.normalized;
// here you "clamp" use the smaller of either
// the max radius or the magnitude of the difference vector
var distance = Mathf.Min(maxRadius, difference.magnitude);
// and finally apply the end position
var endPosition = startPosition + direction * distance;
line.SetPosition(1, endPosition);
}
}
This is how it could look like
I've written the following pseudo code, which may help you
float rang ;
Bool drag=true;
GameObject ball;
OnMouseDrag () {
if(drag) {
//Put your dragging code here
}
if (ball.transform.position>range)
Drag=false;
else Drage=true;
}

How to resize all particles from a particle system?

I'm trying to dynamically resize particles using a slider, as well as change their colour.
Particles are used to display datapoints in a 3D scatterplot. I'm using this code: https://github.com/PrinzEugn/Scatterplot_Standalone
private ParticleSystem.Particle[] particlePoints;
void Update () {
pointScale = sizeSlider.value;
for (int i = 0; i < pointList.Count; i++) {
Quaternion quaternion = Camera.current.transform.rotation;
Vector3 angles = quaternion.eulerAngles;
// Set point color
particlePoints[i].startColor = new Color(angles.x, angles.y, angles.z, 1.0f);
particlePoints[i].transform.localScale = new Vector3(pointScale, pointScale, pointScale);
}
}
The issue is that there's no transform method for Particles, and changing the "startColour" doesn't change anything.
The API states that "The current size of the particle is calculated procedurally based on this value and the active size modules."
What does that mean, and how can I change the size of the particles ?
Thanks to previous answers I managed to get this working:
In the PlacePrefabPoints method I add every instantiated prefab to a List, and I add a listener to the slider, which looks like this:
void changedPointSize(){
pointScale = sizeSlider.value;
for (int i = 0; i < objects.Count; i++) {
objects[i].transform.localScale = new Vector3(pointScale, pointScale, pointScale);
}
}
Thanks all !
I just had a look at PointRenderer.cs -> CreateParticles and PlacePrefabPoints give a good hint what has to be changed.
So I guess you would simply change the scale values
foreach (var point in particlePoints)
{
Quaternion quaternion = Camera.current.transform.rotation;
Vector3 angles = quaternion.eulerAngles;
// Set point color
point.startColor = new Color(angles.x, angles.y, angles.z, 1.0f);
point.startSize = sizeSlider.value;
}
and than re-call
GetComponent<ParticleSystem>().SetParticles(particlePoints, particlePoints.Length);
it is questionable though if you really would do this in Update. I would rather do it in sizeSlider.onValueChanged to only do it when neccesarry (you could even make a certain treshold that has to be changed before updating the view) but well for the color there might be no other option than doing it in Update but atleast there I would use a Threshold:
privtae ParticleSystem ps;
// I assume you have that referenced in the inspector
public Slider sizeSlider;
// flag to control whether system should be updated
private bool updateSystem;
privtae void Awake()
{
ps = GetComponent<ParticleSystem>();
}
private void OnEnable()
{
// add a listener to onValueChanged
// it is secure to remove it first (even if not there yet)
// this makes sure it is not added twice
sizeSlider.onValueChanged.RemoveListener(OnsliderChanged());
sizeSlider.onValueChanged.AddListener(OnsliderChanged());
}
private void OnDisable()
{
// cleanup listener
sizeSlider.onValueChanged.RemoveListener(OnsliderChanged());
}
private void OnSliderChanged()
{
foreach (var point in particlePoints)
{
point.startSize = sizeSlider.value;
}
// do the same also for the instantiated prefabs
foreach(Transform child in PointHolder.transform)
{
child.localScale = Vecto3.one * sizeSlider.value;
}
updateSystem = true;
}
private Quaternion lastCameraRot;
public float CameraUpdateThreshold;
private void Update()
{
if(Quaternion.Angle(Camera.current.transform.rotation, lastCameraRot) > CameraUpdateThreshold)
{
foreach (var point in particlePoints)
{
Quaternion quaternion = Camera.current.transform.rotation;
Vector3 angles = quaternion.eulerAngles;
// Set point color
point.startColor = new Color(angles.x, angles.y, angles.z, 1.0f);
}
lastCameraRot = Camera.current.transform.rotation;
updateSystem = true;
}
if(!updateSystem) return;
updateSystem = false;
ps.SetParticles(particlePoints, particlePoints.Length);
}

AI returning to original position

I want my enemy to move back to starting position. He follows me until I get out of his range and then he just stops.
Also i want my skeleton to stop for like 5 sec, and then go back to starting point, any ideas ? I never did anything involving time, exept stopping it.
Here is my script for enemy:
Also here is a screenshoot of inspector on the skeleton: enemy
Here is my script for enemy:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class chase : MonoBehaviour
{
public Transform player;
private Animator anim;
public float LookRadius = 15f;
public Transform enemyStartPos;
// Use this for initialization
void Start()
{
anim = GetComponent<Animator>();
this.enemyStartPos.position = this.transform.position;
}
// Update is called once per frame
void Update()
{
if (!PauseMenu.GameIsPaused)
{
if (Vector3.Distance(player.position, this.transform.position) < 15)
{
Vector3 direction = player.position - this.transform.position;
direction.y = 0;
this.transform.rotation = Quaternion.Slerp(this.transform.rotation, Quaternion.LookRotation(direction), 0.1f);
anim.SetBool("isIdle", false);
if (direction.magnitude > 3)
{
this.transform.Translate(0, 0, 0.05f);
anim.SetBool("isWalking", true);
anim.SetBool("isAttacking", false);
}
else
{
anim.SetBool("isAttacking", true);
anim.SetBool("isWalking", false);
}
}
else
{
if (Vector3.Distance(this.enemyStartPos.position, this.transform.position) >1)
{
Vector3 direction = this.enemyStartPos.position - this.transform.position;
direction.y = 0;
this.transform.rotation = Quaternion.Slerp(this.transform.rotation, Quaternion.LookRotation(direction), 0.1f);
anim.SetBool("isIdle", false);
if (direction.magnitude > 1)
{
this.transform.Translate(0, 0, 0.05f);
anim.SetBool("isWalking", true);
anim.SetBool("isAttacking", false);
}
}
else
anim.SetBool("isIdle", true);
anim.SetBool("isAttacking", false);
anim.SetBool("isWalking", false);
}
}
}
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, LookRadius);
}
}
Before you try to "code" this, think about it in terms of your program design. You have designed and implemented behavior that says "if the bot's position is within X units of the player's position, turn the bot and move it toward the player."
What you describe wanting to do next can be thought of in the same way (which should lead to similar code).. "else, if the bot's position is NOT within X units of the player's position, turn the bot and move it toward [origin]." Note, this means you need to define what [origin] is for the bot. Point being, it makes no difference in the code whether you are moving toward a player or some arbitrary fixed point. The code to move one transform toward another transform is the same.
For "wandering" it's essentially the same thought process: If bot is within X units of [origin] and not following player, pick a random direction and move that way. (better yet, pick a random direction and move that way for some amount of time so your bot doesn't just jitter around origin).

Restricting movement of a 2d object to backward only in UNITY

I'm making a simple project in Unity where there is a Ball attached to a SpringJoint2d component the ball is on an angled slope, like in the image below:
I simply want the user to be able to drag the ball backward along the edge of the slope only,in other words I don't want the user to be able to move the ball away from the slope or into it.
I'v been trying several ways I thought could do the job hers the script of the dragging with what I tried:
(This Is the updated version)
public class ball : MonoBehaviour
{
public Rigidbody2D rb;
public Transform spring;
public Transform calcpoint;
private Vector3 start;
private Vector3 end;
private bool isPressed = false;
RaycastHit2D[] hits = new RaycastHit2D[2];
RaycastHit2D[] hits2 = new RaycastHit2D[2];
float factor = 0;
private void OnMouseDown()
{
if (!isPressed)
{
isPressed = true;
rb.isKinematic = true;
}
}
private void OnMouseUp()
{
isPressed = false;
rb.isKinematic = false;
StartCoroutine(release());
}
/// <summary>
/// release the ball from the spring joint after a small amount of time
/// </summary>
/// <returns></returns>
IEnumerator release()
{
yield return new WaitForSeconds(0.1f);
rb.GetComponent<SpringJoint2D>().enabled = false;
}
// Update is called once per frame
void Update()
{
if (isPressed)
{
if (Vector3.Distance(spring.position, rb.position) > 3f || spring.position.x < (rb.position.x - 1)) return;//restrict the dragging of the ball to not go beyond the spring point and not too far back
float angle = 0;
if (checkGround() > 1)//if we hit the slope with the ray cast downward from the mouse/Tap position
{
angle = Mathf.Abs(Mathf.Atan2(hits[1].normal.x, hits[1].normal.y) * Mathf.Rad2Deg); //get angle
factor = (float)(((45 - angle) * 0.02) + 1) * (angle / 45);//an inaccurate formula to offset the ball to be on top of the slope that works just fine with some glitches
rb.position = hits[1].point + new Vector2(0, factor * 1f);//position the ball at the point were the ray cast downward from the mouse hit
//(that puts the ball center on the line of the slope) so I offset it usinf the formula above
}
}
}
private int checkGround()
{
int h = Physics2D.RaycastNonAlloc(Camera.main.ScreenToWorldPoint(Input.mousePosition), -Vector2.up, hits); //cast downwards
return h;
}
}
here are the settings on the ball:
and the slope setup:
The dragging of the ball works fine ,at one point the player could drag it in the air or into the slope, I managed to fix that with the new code so now the player could only drag it on the edge, though my calculations are still a bit flawed and when the slopes angle is changed the ball would dip a bit inside the slope and that causes some problems at release.
The method used to try to solve the problem is simple, when the player start dragging the ball I cast a ray from the mouse downward and pit the ball on the point of impact with the slope ,offsetting it to sit on top of it,right ow the problem is that the offsetting part is not accurate enough.
I hope I explained myself a bit better this time Thanks:)
After lots of trial and error I did manage to come up with a perfect solution to the problem so I thought I might as well share the answer maybe it will help someone.
here is the updated code I changed my method of restricting the movement completely now I use simple linear line equation as shown below:
private void OnMouseDown()
{
if (!isPressed)
{
//cast a ray on the slope
if (checkGround() > 1)
{
angle = Mathf.Abs(Mathf.Atan2(hits[1].normal.x, hits[1].normal.y) * Mathf.Rad2Deg); //get angle
slope = Mathf.Tan(angle * Mathf.Deg2Rad);//get the slope steepiness
}
isPressed = true;
rb.isKinematic = true;
}
}
private void OnMouseUp()
{
isPressed = false;
rb.isKinematic = false;
StartCoroutine(release());
}
void Update() {
if (isPressed)
{
xMove = Camera.main.ScreenToWorldPoint(Input.mousePosition).x - spring.position.x;//get how much the mouse moved backward or forward
if (xMove < -3f ) xMove = -3f; //restrict the drag range to 3 backward
if (xMove > 0.3f) xMove = 0.3f;//restrict the drag range to 0.3 forward
xpos = spring.position.x+xMove;//since the ball and the spring start at exactly the same position the new ball's x position would be the spring x + the x movement we calculated above
ypos = (xMove * slope)- spring.position.y; //the y posistion would be y=mx+b so the the x movement * the slop steepiness - the starting y position
rb.position = new Vector2(xpos, -ypos);//set the new position of the ball
}
}
private int checkGround()
{
int h = Physics2D.RaycastNonAlloc(Camera.main.ScreenToWorldPoint(Input.mousePosition), -Vector2.up, hits); //cast downwards
return h;
}