Zooming out the camera so that it can see all objects - c#

I have a list of objects, these are blue stickmen on the video, I need to make the camera move away by itself and all objects (blue stickmen) always fit into it, you need to take into account that there will be more and more objects each time, so the camera should be dynamic and adapt itself to all objects
https://youtube.com/shorts/x3uSO2L22Kc?feature=share

Practical solution for any camera angle and objects positions
The idea here is fairly simple.
At each step we check if each object is inside of camera view or is camera too far away, then we simply adjust the camera position towards a better one.
Step by step, our camera will follow target objects dynamically, and when stabilized, all target objects will be captured by camera.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraAutoFitSmooth : MonoBehaviour
{
public int maxNumberOfObjs = 100;
public GameObject objPrefab;
public float maxInitRange = 10f;
public float minCameraHeight = 1f;
public float maxCameraMoveSpeed = 9f;
public float marginalPos = 0.1f;
[HideInInspector]
public List<Transform> objs = new List<Transform>();
Camera cam;
void Start()
{
cam = Camera.main;
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
RandomObjs();
}
// Randomly regenerate objects
void RandomObjs()
{
int nowNumberOfObjs = Random.Range(0, maxNumberOfObjs);
for (int i = objs.Count - 1; i > nowNumberOfObjs; i--)
{
Destroy(objs[i].gameObject);
objs.RemoveAt(i);
}
for (int i = objs.Count; i <= nowNumberOfObjs; i++)
objs.Add(Instantiate(objPrefab).transform);
foreach (var obj in objs)
obj.position = Random.insideUnitSphere * maxInitRange;
}
void LateUpdate()
{
SetCameraFitPosition(Time.deltaTime);
}
void SetCameraFitPosition(float deltaTime)
{
Vector3 targetCamPos = cam.transform.position;
if (objs.Count == 1)
{
targetCamPos = objs[0].position - minCameraHeight * cam.transform.forward;
}
else if (objs.Count > 1)
{
float minInsideDiff = 1f, maxOutsideDiff = 0f;
Vector3 center = Vector3.zero;
foreach (var obj in objs)
{
Vector3 screenPos = GetScreenPos(obj.position);
if (IsInsideView(screenPos))
minInsideDiff = Mathf.Min(minInsideDiff, CalculateInsideDiff(screenPos.x), CalculateInsideDiff(screenPos.y), CalculateInsideDiff(screenPos.z));
else
maxOutsideDiff = Mathf.Max(maxOutsideDiff, CalculateOutsideDiff(screenPos.x), CalculateOutsideDiff(screenPos.y), CalculateOutsideDiff(screenPos.z));
center += obj.position;
}
center /= objs.Count;
float nowHeight = Vector3.Project(cam.transform.position - center, cam.transform.forward).magnitude;
float maxDiff = maxOutsideDiff > 0f ? maxOutsideDiff : -minInsideDiff;
float finalHeight = Mathf.Max(nowHeight + maxDiff * maxCameraMoveSpeed, minCameraHeight);
targetCamPos = center - finalHeight * cam.transform.forward;
}
cam.transform.position = Vector3.MoveTowards(cam.transform.position, targetCamPos, maxCameraMoveSpeed * deltaTime);
}
Vector3 GetScreenPos(Vector3 pos)
{
return cam.WorldToViewportPoint(pos);
}
float CalculateOutsideDiff(float pos)
{
float diff = 0f;
if (pos > 1f + marginalPos)
diff = pos - 1f;
else if (pos < -marginalPos)
diff = -pos;
return diff;
}
float CalculateInsideDiff(float pos)
{
float diff = 0f;
if (pos < 1f - marginalPos && pos > marginalPos)
diff = Mathf.Min(1f - pos, pos);
return diff;
}
bool IsInsideView(Vector3 screenPoint)
{
return screenPoint.z > 0f && screenPoint.x > 0f && screenPoint.x < 1 && screenPoint.y > 0f && screenPoint.y < 1;
}
}
If you need more info feel free to contact me :) Cheers!

The following script will position a perspective camera in a top down view, so that all tracked GameObjects (objs) are visible.
It is assumed that the objects are on the zero xz plane and are points, so their actual dimensions are not taken into account. There must be at least one tracked object. The objects may not be spaced in such a way that would require the cameras height to exceed the maximum floating point value.
public GameObject[] objs;//objects that must be fitted
private Camera cam;
float recXmin, recXmax, recYmin, recYmax;
Vector3 center;
void Start()
{
cam = Camera.main;
cam.transform.rotation = Quaternion.Euler(90, 0, 0);
}
void LateUpdate()
{
recXmin = objs[0].transform.position.x;
recXmax = objs[0].transform.position.x;
recYmin = objs[0].transform.position.z;
recYmax = objs[0].transform.position.z;
center = Vector3.zero;
foreach (GameObject obj in objs)
{
if (obj.transform.position.x < recXmin)
{
recXmin = obj.transform.position.x;
}
if (obj.transform.position.x > recXmax)
{
recXmax = obj.transform.position.x;
}
if (obj.transform.position.z < recYmin)
{
recYmin = obj.transform.position.z;
}
if (obj.transform.position.z > recYmax)
{
recYmax = obj.transform.position.z;
}
}
float horizontalHeight = (recYmax - recYmin) / 2 / Mathf.Tan(Mathf.Deg2Rad * cam.fieldOfView / 2);
float verticalHeight = (recXmax - recXmin) / 2 / Mathf.Tan(Mathf.Deg2Rad * Camera.VerticalToHorizontalFieldOfView(cam.fieldOfView, cam.aspect) / 2);
float finalHeight = horizontalHeight > verticalHeight ? horizontalHeight : verticalHeight;
center = new Vector3(recXmin + (recXmax - recXmin) / 2, finalHeight, recYmin + (recYmax - recYmin) / 2);
cam.transform.position = center;
}
void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawLine(new Vector3(recXmin, 0, recYmin), new Vector3(recXmin, 0, recYmax));
Gizmos.DrawLine(new Vector3(recXmax, 0, recYmin), new Vector3(recXmax, 0, recYmax));
Gizmos.color = Color.green;
Gizmos.DrawLine(new Vector3(recXmin, 0, recYmin), new Vector3(recXmax, 0, recYmin));
Gizmos.DrawLine(new Vector3(recXmin, 0, recYmax), new Vector3(recXmax, 0, recYmax));
Gizmos.color = Color.blue;
Gizmos.DrawSphere(center, 0.5f);
}
the script determines the "bounding square" formed by all tracked objects
the bounding square is assumed to be the basis of a pyramid with the camera at its peak
using trigonometry the height of the camera can be calculated, by taking into account the known length of the pyramid's base side and the cameras field of view
the calculation is made twice for the cameras horizontal and vertical field of view
the greater of these two values is then selected
lastly the camera is position into the middle of the pyramids base and at the determined height, so that it ends up at the pyramids peak

Related

Unity 3D 2019: x Position keeps shifting

I have made a grid spawner for obstacles on a grid of x -6 to x 6. The objects move -z axis toward the player.
They do not move along the x axis only z. For some reason despite having coded a spawner that places an object every 2 x values it places them at a fraction off each. And gets less each row. So an object placed at x2 the next row at x2 will be at x1.999987 and then x1.998 and so on.
The x value of each object should only be one of following (-6, -4, -2, 0, 2, 4, 6)
This is causing me an issue because I need to also be able to select two objects and swap them. This requires my script to scan the board for active objects and find x valuesof the objects and swap over. If the x values are not solid whole numbers it wont detect any other objects.
Thank you in advance for any advice :)
Spawn code:
void SpawnGrid()
{
for (int x = 0; x < gridX; x++)
{
for (int z = 0; z < gridZ; z++)
{
float x1 = Mathf.Round(x * gridSpacingOffset);
float z1 = Mathf.Round(z * gridSpacingOffset);
Debug.Log("x1: " + x1);
Debug.Log("x2: " + z1);
Vector3 spawnPosition = new Vector3(x1, 0, z1) + gridOrigin;
PickAndSpawn(spawnPosition, Quaternion.identity);
}
}
}
void PickAndSpawn(Vector3 positionToSpawn, Quaternion rotationToSpawn)
{
// Pick a "random" number between 0 and the length of the obstacle array
int randomIndex = Random.Range(0, itemsToPickFrom.Length);
Debug.Log("RandomIndex is: " + randomIndex);
// Declare obstacle types
obj = ObjectPooler.SharedInstance.GetPooledObject("Obstacle_Type_1");
obj1 = ObjectPooler.SharedInstance.GetPooledObject("Obstacle_Type_2");
obj2 = ObjectPooler.SharedInstance.GetPooledObject("Obstacle_Type_3");
obj3 = ObjectPooler.SharedInstance.GetPooledObject("Obstacle_Type_4");
// Add obstacles to an array
objectTypes[0] = obj;
objectTypes[1] = obj1;
objectTypes[2] = obj2;
objectTypes[3] = obj3;
// Spawn the obstacle chosen by the random index number
if (objectTypes[randomIndex] == null)
return;
objectTypes[randomIndex].transform.position = positionToSpawn;
objectTypes[randomIndex].transform.rotation = rotationToSpawn;
ontheBoard.Add(objectTypes[randomIndex]);
objectTypes[randomIndex].SetActive(true);
}
Object movement code:
void Start()
{
currentPos = transform.position;
}
void Update()
{
destinationPos = currentPos;
destinationPos.z = destinationPos.z - moveStep;
currentPos = transform.position;
}
void FixedUpdate()
{
transform.position = Vector3.MoveTowards(transform.position, destinationPos, moveSpeed * Time.fixedDeltaTime);
}
Swap Code:
private void swapIt(Vector3 pos1, Vector3 pos2)
{
if (swap != true)
{
startPos = pos1;
endPos = pos2;
archMidPos = startPos + (endPos - startPos) / 2 + Vector3.up * archHeight;
swap = true;
}
}
void Update()
{
moveSpeed = obj1.GetComponent<Cube_Control>().moveSpeed;
moveStep = obj1.GetComponent<Cube_Control>().moveStep;
destinationPosObj1 = currentPosObj1;
destinationPosObj1.z = destinationPosObj1.z - moveStep;
currentPosObj1 = obj1.transform.position;
destinationPosObj2 = currentPosObj2;
destinationPosObj2.z = destinationPosObj2.z - moveStep;
currentPosObj2 = obj2.transform.position;
// For testing press SPACEBAR to swap objects
if (Input.GetKeyDown(KeyCode.Space))
{
/*
int randomIndex = Random.Range(0, lvlManager.GetComponent<Grid_Spawn>().ontheBoard.Count);
obj1 = lvlManager.GetComponent<Grid_Spawn>().ontheBoard[randomIndex];
foreach (GameObject ob in lvlManager.GetComponent<Grid_Spawn>().ontheBoard)
{
if (ob.transform.position.x == (obj1.transform.position.x + 4))
{
obj2 = ob;
swapIt(obj1.transform.position, obj2.transform.position);
}
else { Debug.Log("No object nearby to swap"); }
}*/
swapIt(obj1.transform.position, obj2.transform.position);
}
}
private void FixedUpdate()
{
//loat cnt = gs.ontheBoard.Count;
//Debug.Log("ontheboard count: " + cnt);
if (swap == true)
{
if (count < 1.0f)
{
count += 1.0f * Time.fixedDeltaTime;
// Lerp the primary object to the new position in an arching motion
Vector3 archMidPosObj1 = archMidPos;
Vector3 endPosObj1 = endPos;
archMidPosObj1.z = archMidPosObj1.z - moveStep;
endPosObj1.z = endPosObj1.z - moveStep;
Vector3 m1 = Vector3.Lerp(startPos, archMidPosObj1, count);
Vector3 m2 = Vector3.Lerp(archMidPosObj1, endPosObj1, count);
// Move Object 1
obj1.transform.position = Vector3.Lerp(m1, m2, count);
// Calculate the inverse arch (-Y) and Lerp the secondary object in a negative arching motion
float midposY = archMidPos.y - (archMidPos.y * 2);
Vector3 archMidPosReverse = archMidPos;
archMidPosReverse.y = midposY;
Vector3 obj2StartPos = startPos;
archMidPosReverse.z = archMidPosReverse.z - moveStep;
obj2StartPos.z = obj2StartPos.z - moveStep;
Vector3 m3 = Vector3.Lerp(endPos, archMidPosReverse, count);
Vector3 m4 = Vector3.Lerp(archMidPosReverse, obj2StartPos, count);
// Move Object 2
obj2.transform.position = Vector3.Lerp(m3, m4, count);
// Reset swap boolean and count once object has swapped places
if (obj1.transform.position.x == endPos.x)
{
swap = false;
count = 0;
}
}
}
}
}
EDIT 04/04/2020:
Ok so I have noticed that the x value shifts due to multiplying by fixedDeltaTime in FixedUpdate()
If you attach below script to an object on x round number and press play it will move along the z axis, the x will shift from the round number to long decimal float.
If you comment out the line in FixedUpdate (transform.position = Vector3.MoveTowards(transform.position, destinationPos, moveStep * Time.fixedDeltaTime);) and remove comment slashes from statement below now the object stays on the whole x number, however the speed along z axis is no longer constant and loses sync each frame with other objects on same z axis moving at same pace..
Settings I currently user are:
moveSpeed: 4
moveStep: 2.5
Mass: 1
Drag/Angular Drag: None
Use Gravity: No
isKinematic: Yes
Interpolate: None
Collision: Continuous Speculative
Constraints: None
code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Cube_Control : MonoBehaviour
{
public float moveSpeed;
public float moveStep;
public Vector3 currentPos;
public Vector3 destinationPos;
private Rigidbody rb;
// Start is called before the first frame update
void Start()
{
currentPos = transform.position;
currentPos.x = Mathf.Round(currentPos.x);
rb = this.GetComponent<Rigidbody>();
}
void Update()
{
destinationPos = currentPos;
destinationPos.z = destinationPos.z - moveStep;
destinationPos.x = Mathf.Round(destinationPos.x);
currentPos = transform.position;
currentPos.x = Mathf.Round(currentPos.x);
}
// Update is called once per frame
void FixedUpdate()
{
transform.position = Vector3.MoveTowards(transform.position, destinationPos, moveStep * Time.fixedDeltaTime);
//transform.position = Vector3.MoveTowards(transform.position, destinationPos, moveStep);
}
public void setObstacleSpeed(float speed)
{
moveSpeed = speed;
}
public void setObstacleMoveStep(float step)
{
moveStep = step;
}
private void Destroy(GameObject obj)
{
obj.SetActive(false);
}
private void OnTriggerEnter(Collider other)
{
Debug.Log("Ow! I Feel Triggerd " + other);
if (other.gameObject.tag == "WallOfDoom")
{
Debug.Log("I hit the WALL OF DOOOOOOMMM!");
Destroy(this.gameObject);
}
else if (other.gameObject.tag == "Plyr_Bullet")
{
Destroy(this.gameObject);
}
}
private void OnCollisionEnter(Collision collision)
{
Debug.Log("Ow! I Feel Triggerd " + collision);
if (collision.gameObject.tag == "WallOfDoom")
{
Debug.Log("I hit the WALL OF DOOOOOOMMM!");
Destroy(this.gameObject);
}
}
}

Multiple GameObjects Instantiated Between Two Angles

I am trying to spawn n GameObjects between angles equally spaced out.
Ideally, I'd like to be able to adjust the "cone" to so that the enemy can shoot in any direction, in any density.
Can someone see what I have done wrong?
These are enemy projectiles. That I am trying "scatter shot". Think of the dragon from Level 1 in NES Zelda:
Though, I am not entirely sure what is happening with my implementation.
Projectile.cs
public Vector2 moveDirection = Vector2.zero;
public float moveSpeed = 4.0f;
private void FixedUpdate()
{
_body.MovePosition(transform.position + (new Vector3(moveDirection.x, moveDirection.y, 0).normalized) * (moveSpeed * Time.deltaTime));
}
MultiShooter.cs
public GameObject projectileObject;
public Transform projectileEmitter;
[Range(2, 10)] public int numToShoot = 3;
[Space]
[Range(0, 360)] public int angle = 30;
[Range(1, 50)] public float rayRange = 10.0f;
[Range(0, 360)] public float coneDirection = 180;
public void OnStartShooting()
{
for (int i = 1; i <= numToShoot; i++)
{
var projectile = Instantiate(projectileObject);
projectile.transform.position = projectileEmitter.position;
var projectileScript = projectile.GetComponent<Projectile>();
projectileScript.moveDirection = DirFromAngle(((angle / i) + coneDirection)* pointDistance, rayRange);
projectile.SetActive(true);
}
}
public Vector3 DirFromAngle(float angleInDegrees, float range)
{
return Quaternion.AngleAxis(angleInDegrees, Vector3.forward) * transform.up * range;
}
Editor script to show the lines.
private void OnSceneGUI()
{
MultiShooter fow = (MultiShooter)target;
Handles.color = Color.magenta;
Vector3 upDirection = fow.DirFromAngle((-fow.angle / 2.0f) + fow.coneDirection, fow.rayRange);
Vector3 dwDirection = fow.DirFromAngle((fow.angle / 2.0f) + fow.coneDirection, fow.rayRange);
Handles.DrawLine(fow.projectileEmitter.position, upDirection);
Handles.DrawLine(fow.projectileEmitter.position, dwDirection);
}
For the ith object, the fraction of angular distance from one side of the range to the other can be expressed with the formula i/(numToShoot-1) for values ofnumToShoot > 1. If numToShoot == 1, you can just have the percentage be 50% to shoot right in the middle of the range.
Your drawing method seems to work with coneDirection ± angle/2, so we can subtract .5 from this angular percentage to express it in terms of angular distance from the center of the range.
Then we can use the same math as the drawing method with coneDirection + angle percentage * angle range:
public void OnStartShooting()
{
for (int i = 0; i < numToShoot; i++)
{
var projectile = Instantiate(projectileObject);
projectile.transform.position = projectileEmitter.position;
var projectileScript = projectile.GetComponent<Projectile>();
float anglePercentage;
if (numToShoot == 1)
anglePercentage = 0f;
else
anglePercentage = (float)i/(numToShoot-1f) - .5f;
projectileScript.moveDirection = DirFromAngle(
coneDirection
+ anglePercentage * angle, rayRange);
projectile.SetActive(true);
}
}

Dotted line bouncing on Unity

I am trying to replicate a shooter from any bubble shooter. Dotted line that the bubble will follow.
What I am trying to do is, create a dotted line that bounces (reflects) when getting close the camera limits (camera's limits are the image's limits). Also, to stop creating more dots when hit with a Bubble (blue dot).
The line so far bounces, but not correctly (see corners). Also, it does not stop when hit with a Bubble (blue dot).
private void DrawPoints() {
bool hasReversed = false;
bool reversedLeft = false;
var leftEdge = mainCamera.ScreenToWorldPoint(new Vector3(0, 0, 0));
var rightEdge = mainCamera.ScreenToWorldPoint(new Vector3(Screen.width, 0, 0));
var normalDir = shootDir.normalized;
int count = 0;
for (var i = 0; i < dots.Count; i++) {
var dot = dots[i];
dot.GetComponent<SpriteRenderer>().color = color;
var newPos = new Vector2(normalDir.x, normalDir.y) * i * DotGap;
if (hasReversed) {
newPos.x += reversedLeft ? (-rightEdge.x + Constants.BubbleRadius/2) * 2 * count : (rightEdge.x - Constants.BubbleRadius/2) * 2 * count;
//newPos.x += reversedLeft ? (-rightEdge.x + Constants.BubbleRadius) * 2 : (rightEdge.x - Constants.BubbleRadius) * 2;
}
//newPos += normalDir * delta;
dot.transform.localPosition = newPos;
RaycastHit2D hit = Physics2D.Raycast(newPos, shootDir);
if (hit.collider != null) {
float distance = Vector2.Distance(hit.transform.position, newPos);
if (distance < Constants.WhiteCircleRadius + Constants.BubbleRadius) {
dot.SetActive(false);
Debug.Log("Found!: " + distance + " " + hit.collider.name);
break;
} else {
dot.SetActive(true);
}
}
if (dot.transform.localPosition.x <= leftEdge.x + Constants.BubbleRadius) {
hasReversed = true;
reversedLeft = true;
normalDir = Vector2.Reflect(normalDir, Vector2.left);
count++;
}
else if (dot.transform.localPosition.x >= rightEdge.x - Constants.BubbleRadius) {
hasReversed = true;
reversedLeft = false;
normalDir = Vector2.Reflect(normalDir, Vector2.right);
count++;
}
}
}
The image as follows:
The red dot is the pivot (start of the dotted line). The blue dot, is a bubble.
After a bit of fiddling I managed to get it to work pretty decently. I started from scratch as I have never done anything like this and I needed to work through it to understand the problems you were experiencing. A good amount of code is just a modified version of Kunal Tandon's from https://medium.com/#kunaltandon.kt/creating-a-dotted-line-in-unity-ca044d02c3e2, so props to him for such a good tutorial.
Note: to get the dotted line to stop on bubbles I added a collider and a tag to the bubbles. Then I just check for the tag in the code. To ensure the line bounces, I add a different tag to those and just bounce whenever I hit a wall.
using System.Collections.Generic;
using UnityEngine;
public class DottedLine : MonoBehaviour
{
Vector3 mp;
Vector2 mousePosition;
Vector2 start;
public Sprite Dot;
[Range(0.01f, 1f)]
public float Size;
[Range(0.1f, 2f)]
public float Delta;
List<Vector2> positions;
List<GameObject> dots;
private void Start()
{
start = new Vector2(transform.position.x, transform.position.y);
positions = new List<Vector2>();
dots = new List<GameObject>();
}
void Update()
{
mp = Camera.main.ScreenToWorldPoint(Input.mousePosition);
mousePosition = new Vector2(mp.x, mp.y);
if (Input.GetMouseButtonDown(0))
{
DrawPoints(mousePosition);
}
}
public void DrawPoints(Vector2 mousePosition)
{
DestroyAllDots();
Vector2 point = start;
Vector2 end;
Vector2 direction = (mousePosition - start).normalized;
RaycastHit2D ray = Physics2D.Raycast(start, direction);
Vector3 rayPos = ray.point;
float distance = Vector2.Distance(ray.transform.position, start);
end = new Vector2(rayPos.x, rayPos.y);
//Only needs one line drawn
if (ray.collider.tag == "bottom" || ray.collider.tag == "bubble")
{
DrawOneLine(start, end, direction);
}
else if (ray.collider.tag == "wall")
{
//Will create a new line every time it hits a wall.
DrawOneLine(start, end, direction);
while (ray.collider.tag == "wall")
{
Vector3 newRayPos = ray.point;
Vector2 newStart = new Vector2(newRayPos.x, newRayPos.y);
direction = Vector2.Reflect(direction, Vector2.right);
//I had to put in a tolerances so a new raycast could start,
//there is likely a better way, feedback appreciated.
Vector2 rightTol = new Vector2(-.001f, 0);
Vector2 leftTol = new Vector2(.001f, 0);
if (ray.collider.name == "Right Wall")
ray = Physics2D.Raycast(newStart + rightTol, direction);
else
ray = Physics2D.Raycast(newStart + leftTol, direction);
rayPos = ray.point;
end = new Vector2(rayPos.x, rayPos.y);
DrawOneLine(newStart, end, direction, false);
}
}
Render();
}
public void DrawOneLine(Vector2 start, Vector2 end, Vector2 direction, bool drawFirst = true)
{
//Debug.Log($"Start: {start} End: {end} Direction: {direction}");
Vector2 point = start;
while ((end - start).magnitude > (point - start).magnitude)
{
if (drawFirst)
positions.Add(point);
point += (direction * Delta);
drawFirst = true;
}
}
GameObject GetOneDot()
{
var gameObject = new GameObject();
gameObject.transform.localScale = Vector3.one * Size;
gameObject.transform.parent = transform;
var sr = gameObject.AddComponent<SpriteRenderer>();
sr.sprite = Dot;
return gameObject;
}
private void Render()
{
foreach (var position in positions)
{
var g = GetOneDot();
g.transform.position = position;
dots.Add(g);
}
}
public void DestroyAllDots()
{
foreach (var dot in dots)
Destroy(dot);
dots.Clear();
positions.Clear();
}
}
Your dots are overshooting -- i.e. going past the left/right edges of your play area.
You forgot to include a visualization of said limits in your image, so here's my best guess.
You are checking when dot.transform.localPosition.x goes out of bounds, but when does, you're not correcting the dot's position!
The actual bounce is correctly implemented with Vector2.Reflect.
The simplest way to fix this would be to hide the last white dot:
if (dot.transform.localPosition.x <= leftEdge.x + Constants.BubbleRadius) {
dot.SetActive(false);
/* ... */
}
else if (dot.transform.localPosition.x >= rightEdge.x - Constants.BubbleRadius) {
dot.SetActive(false);
/* ... */
}
Misc remarks, not directly link to your problem:
You don't need to raycast from each dot, one raycast per bounce should be enough. You would then place the dots along that line.
new Vector3(0, 0, 0) can be expressed more succinctly as Vector3.zero
It's customary, instead of comparing a vector's magnitude to a value, to instead compare its sqrMagnitude to the square of that value. The performance delta is likely insignificant, it's a matter of taste.

Pinch-To-Zoom with Unity 5 UI

I'm trying to re-implement a pinch-to-zoom system in a Unity UI-based app. About six months ago I was able to hack one together by making the UI canvas a child of a regular GameObject, and manipulating that object's transform, but since updating to Unity 5.5+ I find this doesn't work. The closest I can get allows the pinch gesture to change the canvas' scaleFactor, which a) can make images, panels, etc resize improperly depending on their alignments, and b) won't allow me to pan once zoomed.
What I have so far is this:
public class PinchToZoomScaler : MonoBehaviour {
public Canvas canvas; // The canvas
public float zoomSpeed = 0.5f; // The rate of change of the canvas scale factor
public float _resetDuration = 3.0f;
float _durationTimer = 0.0f;
float _startScale = 0.0f;
void Start() {
_startScale = canvas.scaleFactor;
}
void Update()
{
// If there are two touches on the device...
if (Input.touchCount == 2) {
// Store both touches.
Touch touchZero = Input.GetTouch (0);
Touch touchOne = Input.GetTouch (1);
// Find the position in the previous frame of each touch.
Vector2 touchZeroPrevPos = touchZero.position - touchZero.deltaPosition;
Vector2 touchOnePrevPos = touchOne.position - touchOne.deltaPosition;
// Find the magnitude of the vector (the distance) between the touches in each frame.
float prevTouchDeltaMag = (touchZeroPrevPos - touchOnePrevPos).magnitude;
float touchDeltaMag = (touchZero.position - touchOne.position).magnitude;
// Find the difference in the distances between each frame.
float deltaMagnitudeDiff = prevTouchDeltaMag - touchDeltaMag;
// ... change the canvas size based on the change in distance between the touches.
canvas.scaleFactor -= deltaMagnitudeDiff * zoomSpeed;
// Make sure the canvas size never drops below 0.1
canvas.scaleFactor = Mathf.Max (canvas.scaleFactor, _startScale);
canvas.scaleFactor = Mathf.Min (canvas.scaleFactor, _startScale * 3.0f);
_durationTimer = 0.0f;
} else {
_durationTimer += Time.deltaTime;
if (_durationTimer >= _resetDuration) {
canvas.scaleFactor = _startScale;
}
}
}
}
As I said, this works to a degree, but doesn't give me a nice uniform zooming, not does it allow me to pan the canvas. Thanks in advance for any help.
Attach this script in canvas object which you want to zoom in and zoom out by pinch
using UnityEngine;
using UnityEngine.EventSystems;
public class ObjectScalling : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
private bool _isDragging;
private float _currentScale;
public float minScale, maxScale;
private float _temp = 0;
private float _scalingRate = 2;
private void Start()
{
_currentScale = transform.localScale.x;
}
public void OnPointerDown(PointerEventData eventData)
{
if (Input.touchCount == 1)
{
_isDragging = true;
}
}
public void OnPointerUp(PointerEventData eventData)
{
_isDragging = false;
}
private void Update()
{
if (_isDragging)
if (Input.touchCount == 2)
{
transform.localScale = new Vector2(_currentScale, _currentScale);
float distance = Vector3.Distance(Input.GetTouch(0).position, Input.GetTouch(1).position);
if (_temp > distance)
{
if (_currentScale < minScale)
return;
_currentScale -= (Time.deltaTime) * _scalingRate;
}
else if (_temp < distance)
{
if (_currentScale > maxScale)
return;
_currentScale += (Time.deltaTime) * _scalingRate;
}
_temp = distance;
}
}
}
Reminder: This script only works in canvas objects
You can use this function ( just pass to it negative deltaMagnitudeDiff )
Also it is good to multiplay deltaMagnitudeDiff with a ratio like ( 0.05 )
float currentScale = 1f;
void Zoom (float increment)
{
currentScale += increment;
if (currentScale >= maxScale)
{
currentScale = maxScale;
}
else if (currentScale <= minScale)
{
currentScale = minScale;
}
rectTransform.localScale = new Vector3 (currentScale, currentScale, 1);
pan.ValidatePosition ();
}
For Panning,
you can use something like this :
public class Pan : MonoBehaviour
{
public float Speed;
Vector3 startDragPosition;
public void BeginDrag ()
{
startDragPosition = Input.mousePosition;
}
public void Drag ()
{
transform.localPosition += (Input.mousePosition - startDragPosition) * Speed;
startDragPosition = Input.mousePosition;
ValidatePosition ();
}
public void ValidatePosition ()
{
var temp = transform.localPosition;
var width = ((RectTransform)transform).sizeDelta.x;
var height = ((RectTransform)transform).sizeDelta.y;
var MaxX = 0.5f * width * Mathf.Max (0, transform.localScale.x - 1);
var MaxY = 0.5f * height * Mathf.Max (0, transform.localScale.y - 1);
var offsetX = transform.localScale.x * width * (((RectTransform)transform).pivot.x - 0.5f);
var offsetY = transform.localScale.y * width * (((RectTransform)transform).pivot.y - 0.5f);
if (temp.x < -MaxX + offsetX)
temp.x = -MaxX + offsetX;
else if (temp.x > MaxX + offsetX)
temp.x = MaxX + offsetX;
if (temp.y < -MaxY + offsetY)
temp.y = -MaxY + offsetY;
else if (temp.y > MaxY + offsetY)
temp.y = MaxY + offsetY;
transform.localPosition = temp;
}
Just call the functions ( BeginDrag & Drag ) from the Events Trigger component.
what i did to scale an object using pinch was this, it works on any touch screen when the object is in the middle of the screen:
if (Input.touchCount == 2)
{
//The distance between the 2 touches is checked and subsequently used to scale the
//object by moving the 2 fingers further, or closer form eachother.
Touch touch0 = Input.GetTouch(0);
Touch touch1 = Input.GetTouch(1);
if (isScaling)//this will only be done if scaling is true
{
float currentTouchDistance = getTouchDistance();
float deltaTouchDistance = currentTouchDistance - touchDistanceOrigin;
float scalePercentage = (deltaTouchDistance / 1200f) + 1f;
Vector3 scaleTemp = transform.localScale;
scaleTemp.x = scalePercentage * originalScale.x;
scaleTemp.y = scalePercentage * originalScale.y;
scaleTemp.z = scalePercentage * originalScale.z;
//to make the object snap to 100% a check is being done to see if the object scale is close to 100%,
//if it is the scale will be put back to 100% so it snaps to the normal scale.
//this is a quality of life feature, so its easy to get the original size of the object.
if (scaleTemp.x * 100 < 102 && scaleTemp.x * 100 > 98)
{
scaleTemp.x = 1;
scaleTemp.y = 1;
scaleTemp.z = 1;
}
//here we apply the calculation done above to actually make the object bigger/smaller.
transform.localScale = scaleTemp;
}
else
{
//if 2 fingers are touching the screen but isScaling is not true we are going to see if
//the middle of the screen is looking at the object and if it is set isScalinf to true;
Ray ray;
RaycastHit hitTouch;
ray = cam.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
if (Physics.Raycast(ray, out hitTouch, 100f))
{
if (hitTouch.transform == transform)
{
isScaling = true;
//make sure that the distance between the fingers on initial contact is used as the original distance
touchDistanceOrigin = getTouchDistance();
originalScale = transform.localScale;
}
}
}
}

wander & chase AI code not working

Put together an enemy AI system for an enemy in which it should wander around idly when the player is not within distance to the enemy and when player is within enemy distance it should initiate chase behaviour and chase after player until player has managed to exit out of the enemy's chase radius.
Currently the enemy is able to wonder freely yet when the player comes within proximity of the enemy the enemy will carry on wandering instead of chasing player.
Anyone help me fix this problem?
Code is as follows.
public enum AIState
{
Chasing,
Wander
}
private float maxSpeed;
private float maxRotation;
private float chaseDistance;
private float hysteresis;
private Texture2D texture;
private Vector2 drawingOrigin;
private Vector2 position;
public AIState aiState = AIState.Wander;
private float orientation;
private Random random = new Random();
private Rectangle viewportbounds;
public Rectangle boundingBox;
public Vector2 playerPosition;
private Vector2 heading;
public Virtual_Aliens(Rectangle pos, Rectangle b)
{
position = new Vector2(300, 400);
boundingBox = new Rectangle(pos.X, pos.Y, pos.Width, pos.Height);
viewportbounds = new Rectangle(b.X, b.Y, b.Width, b.Height);
orientation = 0.0f;
heading = new Vector2(0, 0);
maxSpeed = 2.0f;
maxRotation = 0.20f;
hysteresis = 15.0f;
chaseDistance = 250.0f;
Thread.Sleep(200);
random = new Random();
}
public void LoadContent(ContentManager Content)
{
texture = Content.Load<Texture2D>("images/asteroid");
}
private Vector2 OrientationAsVector(float orien)
{
Vector2 orienAsVect;
orienAsVect.X = (float)Math.Cos(orien);
orienAsVect.Y = (float)Math.Sin(orien);
return orienAsVect;
}
Vector2 wanderPosition = new Vector2();
public void Wander()
{
// the max +/- the agent will wander from its current position
float wanderLimits = 0.5f;
// this defines what proportion of its maxRotation speed the agent will turn
float turnFactor = 0.15f;
// randomly define a new position
wanderPosition.X += MathHelper.Lerp(-wanderLimits, wanderLimits, (float)random.NextDouble());
wanderPosition.Y += MathHelper.Lerp(-wanderLimits, wanderLimits, (float)random.NextDouble());
if (wanderPosition != Vector2.Zero)
{
wanderPosition.Normalize();
}
orientation = TurnToFace(wanderPosition, orientation, turnFactor * maxRotation);
heading = OrientationAsVector(orientation);
position += heading * 0.5f * maxSpeed;
WrapForViewport();
}
private void WrapForViewport()
{
if (position.X < 0)
{
position.X = viewportbounds.Width;
}
else if (position.X > viewportbounds.Width)
{
position.X = 0;
}
if (position.Y < 0)
{
position.Y = viewportbounds.Height;
}
else if (position.Y > viewportbounds.Height)
{
position.Y = 0;
}
}
private float WrapAngle(float radian)
{
while (radian < -MathHelper.Pi)
{
radian += MathHelper.TwoPi;
}
while (radian > MathHelper.Pi)
{
radian -= MathHelper.TwoPi;
}
return radian;
}
private float TurnToFace(Vector2 steering, float currentOrientation, float turnSpeed)
{
float newOrientation;
float desiredOrientation;
float orientationDifference;
float x = steering.X;
float y = steering.Y;
// the desiredOrientation is given by the steering vector
desiredOrientation = (float)Math.Atan2(y, x);
// find the difference between the orientation we need to be
// and our current Orientation
orientationDifference = desiredOrientation - currentOrientation;
// now using WrapAngle to get result from -Pi to Pi
// ( -180 degrees to 180 degrees )
orientationDifference = WrapAngle(orientationDifference);
// clamp that between -turnSpeed and turnSpeed.
orientationDifference = MathHelper.Clamp(orientationDifference, -turnSpeed, turnSpeed);
// the closest we can get to our target is currentAngle + orientationDifference.
// return that, using WrapAngle again.
newOrientation = WrapAngle(currentOrientation + orientationDifference);
return newOrientation;
}
public void Update(GameTime gameTime)
{
if (aiState == AIState.Wander)
{
chaseDistance -= hysteresis / 2;
}
else if (aiState == AIState.Chasing)
{
chaseDistance += hysteresis / 2;
}
float distanceFromPlayer = Vector2.Distance(position, playerPosition);
if (distanceFromPlayer > chaseDistance)
{
aiState = AIState.Wander;
}
else
{
aiState = AIState.Chasing;
}
float currentSpeed;
if (aiState == AIState.Chasing)
{
orientation = TurnToFace(playerPosition, orientation, maxRotation);
currentSpeed = maxSpeed;
}
else if (aiState == AIState.Wander)
{
Wander();
}
}
public void Draw(SpriteBatch spriteBatch)
{
boundingBox.X = (int)position.X;
boundingBox.Y = (int)position.Y;
drawingOrigin = new Vector2(texture.Width / 2, texture.Height / 2);
spriteBatch.Draw(texture, boundingBox, null, Color.White, orientation, drawingOrigin, SpriteEffects.None, 0.0f);
}
public Vector2 PlayerPosition
{
set
{
playerPosition = value;
}
get
{
return playerPosition;
}
}
You get the distance from the player using:
float distanceFromPlayer = Vector2.Distance(position, playerPosition);
But you never actually set the variable playerPosition in your class. So effectively if the enemy is within the chase radius from the point (0,0) they will chase your player, but otherwise will they will just wander around.
I would recommend doing one of two things to solve this issue.
First off you could change the parameters of your Update method to take in the Vector2 of the players position.
A second approach (the one I would personally choose) would be to add a new field (class variable) that is of type Player and then in your Virtual_Aliens' constructor pass in an instance of the player class. That way any time you reference playerPosition you would be able to just say player.Position (or however you have your position field named).

Categories