I'm trying to create a Line of Sight method for an enemy class. However, it always returns false, no matter how close the player is to the enemy or whether the ray passes through any blocks to get to the player.
public virtual bool PlayerInLOS()
{
Vector3 middleOfPlayer = new Vector3(Level.Player.Position.X, Level.Player.Position.Y - Level.Player.BoundingRectangle.Height / 2, 0);
Vector3 middleOfEnemy = new Vector3(Position.X, Position.Y - localBounds.Height / 2, 0);
Vector3 direction = middleOfPlayer - middleOfEnemy;
float distanceToPlayer = Vector3.Distance(middleOfEnemy, middleOfPlayer);
if (direction != Vector3.Zero)
direction.Normalize();
Ray lineOfSight = new Ray(middleOfEnemy, direction);
float? lineToPlayer = lineOfSight.Intersects(Level.Player.BoundingBox);
foreach (BoundingBox box in Level.boundingBoxes)
{
float? distanceToIntersect = lineOfSight.Intersects(box);
if (distanceToIntersect == null)
continue;
else if (distanceToIntersect < visionLength && distanceToIntersect < distanceToPlayer && distanceToIntersect != null)
return false;
}
// Never gets to this part because it always returns before it exits the for loop
if (lineToPlayer < visionLength)
return true;
else return false;
}
Any ideas? Thanks.
I ended up fixing the problem by implementing an entirely different solution: checking every Vector2 along the distance between the enemy and the player. Works perfectly.
public bool CanSeePlayer()
{
Vector2 middleOfPlayer = new Vector2(Level.Player.Position.X, Level.Player.Position.Y - Level.Player.BoundingRectangle.Height / 2);
Vector2 middleOfEnemy = new Vector2(Position.X, Position.Y - localBounds.Height / 2);
Vector2 direction = middleOfPlayer - middleOfEnemy;
float distanceToPlayer = Vector2.Distance(middleOfEnemy, middleOfPlayer);
if (visionLength > distanceToPlayer) // If the enemy can see farther than the player's distance,
{
if (direction != Vector2.Zero)
direction.Normalize();
for (int y = 0; y < Level.tiles.GetLength(1); ++y) // loop through every tile,
{
for (int x = 0; x < Level.tiles.GetLength(0); ++x)
{
if (Level.GetCollision(x, y) != TileCollision.Passable) // and if the block is solid,
{
Vector2 currentPos = middleOfEnemy;
float lengthOfLine = 0.0f;
Rectangle tileRect = new Rectangle(x * Tile.Width, y * Tile.Height, Tile.Width, Tile.Height);
while (lengthOfLine < distanceToPlayer + 1.0f) // check every point along the line
{
currentPos += direction;
if (tileRect.Contains(currentPos)) // to see if the tile contains it.
{
return false;
}
lengthOfLine = Vector2.Distance(middleOfEnemy, currentPos);
}
}
}
}
// If every tile does not contain a single point along the line from the enemy to the player,
return true;
}
return false;
}
If you need to check if enemy is within angle of sight, and in some distance you could try this code.
public static bool InLOS(float AngleDistance, float PositionDistance, Vector2 PositionA, Vector2 PositionB, float AngleB)
{
float AngleBetween = (float)Math.Atan2((PositionA.Y - PositionB.Y), (PositionA.X - PositionB.X));
if ((AngleBetween <= (AngleB + (AngleDistance / 2f / 100f))) && (AngleBetween >= (AngleB - (AngleDistance / 2f / 100f))) && (Vector2.Distance(PositionA, PositionB) <= PositionDistance)) return true;
else return false;
}
credits: https://gamedev.stackexchange.com/questions/26813/xna-2d-line-of-sight-check
Related
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
I have a coroutine DefaultFixedUpdate() that functions as a replacement for FixedUpdate() and another coroutine called WallSlidingFixedUpdate(). I have two variables that store which of the both is active. activeCoroutine and prevActiveCoroutine with the starting strings nameof(DefaultFixedUpdate) and "no coroutine". In FixedUpdate() from Unity I have this code that should change coroutines if activeCoroutine and prevActiveCoroutine are not the same:
void FixedUpdate()
{
if (activeCoroutine != prevActiveCoroutine)
{
print($"changing coroutines from {prevActiveCoroutine} to {activeCoroutine}");
StopCoroutine(prevActiveCoroutine);
StartCoroutine(activeCoroutine);
prevActiveCoroutine = activeCoroutine;
print("changed coroutines");
}
}
When starting the game DefaultFixedUpdate() starts as expected and prevActiveCoroutine changes as expected to activeCoroutine and when a condition inside DefaultFixedUpdate() is met to switch to WallSlidingFixedUpdate() activeCoroutine changes to the new string as expected. But when StartCoroutine(activeCoroutine) gets called the code inside WallSlidingFixedUpdate() doesn't run for some reason. StopCoroutine(prevActiveCoroutine) does work. The end result is that none of the two corutines runs but expected was that WallSlidingCoroutine() runs.
These are the two Coroutines. The changing of activeCoroutine happens almost at the bottom of both functions.
IEnumerator DefaultFixedUpdate()
{
while (true)
{
//setup
gravity = (velocity.y < 0 || !Input.GetKey(KeyCode.Space)) ? dropGravity : normalGravity;
Vector2 pos = transform.position + col.size.y * 0.5f * Vector3.up;
Vector2 timeStepVelocity = velocity * Time.fixedDeltaTime;
Vector2 pos1 = pos;
velocity.y -= gravity * Time.fixedDeltaTime;
//checking collision
RaycastHit2D[] colChecks = Physics2D.BoxCastAll(pos, col.size, 0, timeStepVelocity, timeStepVelocity.magnitude, 0b1000000);
//remove unwanted collisions that are behind and don't make sense to collide with
List<RaycastHit2D> newColChecks = new();
foreach (RaycastHit2D colCheck in colChecks)
if (Vector2.Dot(colCheck.normal, timeStepVelocity) < 0)
newColChecks.Add(colCheck);
colChecks = newColChecks.ToArray();
//remove unwanted velocities
foreach (RaycastHit2D colCheck in colChecks)
{
if (Mathf.Abs(colCheck.normal.x / colCheck.normal.y) > 1) // left and right
{
velocity.x = Mathf.Sign(colCheck.normal.x) > 0 ? Mathf.Max(velocity.x, 0) : Mathf.Min(velocity.x, 0);
}
else // top bottom
{
velocity.y = Mathf.Sign(colCheck.normal.y) > 0 ? Mathf.Max(velocity.y, 0) : Mathf.Min(velocity.y, 0);
}
}
//calculate correct destination
if (colChecks.Length == 0)
pos += timeStepVelocity;
else
{
//create bounding box for all possible future positions
Vector4 posBox = new Vector4(pos.x, pos.x + timeStepVelocity.x, pos.y, pos.y + timeStepVelocity.y);
//move to surface
pos = colChecks[0].centroid;
//slide along surface until hitting another tangent or the bounding box
Vector2 tangent = new Vector3(colChecks[0].normal.y, -colChecks[0].normal.x);
Vector2 centroid = pos;
//slide until hitting distance bounds
if (Mathf.Abs(tangent.x) > Mathf.Abs(tangent.y)) //top and bottom
{
pos += tangent / tangent.x * (timeStepVelocity.x - pos.x + pos1.x);
tangent = Mathf.Sign(centroid.y - pos1.y) * tangent;
}
else //left and right
{
pos += tangent / tangent.y * (timeStepVelocity.y - pos.y + pos1.y);
tangent = Mathf.Sign(centroid.x - pos1.x) * tangent;
}
//save previous surface that is being slid on
Vector2 prevTangent = Tangent(colChecks[0].normal);
//check if I collided with another tangent along the way
colChecks = Physics2D.BoxCastAll(centroid, col.size, 0, pos, Vector2.Distance(pos, centroid), 0b1000000);
//remove unwanted collisions that are behind and don't make sense to collide with
newColChecks = new();
foreach (RaycastHit2D colCheck in colChecks)
if (Vector2.Dot(colCheck.normal, tangent * Mathf.Sign(Vector2.Dot(pos - centroid, tangent))) < 0 || Vector2.Dot(colCheck.normal, timeStepVelocity) < 0)
newColChecks.Add(colCheck);
colChecks = newColChecks.ToArray();
if (colChecks.Length > 1)
{
pos = centroid + (pos - centroid).normalized * colChecks[1].distance;
}
//repeat
int loopBreak = 0;
string loopBreakMessage = "";
while (true)
{
loopBreakMessage += centroid + "\n";
if (loopBreak > 4)
{
Debug.LogError("loop limit exceeded\n" + loopBreakMessage);
break;
}
loopBreak++;
//check if I collided with another tangent along the way
colChecks = Physics2D.BoxCastAll(centroid, col.size, 0, pos - centroid, Vector2.Distance(pos, centroid), 0b1000000);
//remove unwanted collisions that are behind and don't make sense to collide with
newColChecks = new();
foreach (RaycastHit2D colCheck in colChecks)
if (Tangent(colCheck.normal) != prevTangent)
newColChecks.Add(colCheck);
colChecks = newColChecks.ToArray();
if (colChecks.Length > 2)
{
prevTangent = colChecks[0].normal;
if (!VectorOnSameQuarter(colChecks[1].normal, colChecks[2].normal)) // stuck in a corner
{
pos = centroid + (pos - centroid).normalized * colChecks[2].distance;
break;
}
else //continue along new tangent
{
//corner pos
centroid += (pos - centroid).normalized * colChecks[2].distance;
//end pos if there's no surface along the way
pos = PosAlongVectorInsideRectangle(posBox, centroid, Tangent(colChecks[2].normal));
}
}
else
{
break;
}
}
AlignVelocityWithGround();
}
//check if player is grounded and save ground tangent to remove small hops when going down slopes
PlayerState.OnGround = false;
RaycastHit2D[] jumpChecks = Physics2D.BoxCastAll(pos, col.size, 0, Vector2.down, 1e-3f, 0b1000000);
if (jumpChecks.Length != 0)
{
foreach (RaycastHit2D jumpCheck in jumpChecks)
{
if (jumpCheck.normal.y > Mathf.Abs(jumpCheck.normal.x))
{
PlayerState.OnGround = true;
groundTangent = new Vector2(jumpCheck.normal.y, -jumpCheck.normal.x);
}
}
PlayerState.state = PlayerState.state == PlayerState.State.WallSliding ? PlayerState.State.Default : PlayerState.state;
//check for a wall slide and save wall tangent for smooth sliding
if (!PlayerState.OnGround && velocity.y < 0 && Mathf.Abs(jumpChecks[0].normal.x) > Mathf.Abs(jumpChecks[0].normal.y) && Mathf.Asin(jumpChecks[0].normal.y) * Mathf.Rad2Deg < 15)
{
groundTangent = new Vector2(jumpChecks[0].normal.y, -jumpChecks[0].normal.x);
transform.position = pos - col.size.y * 0.5f * Vector2.up;
activeCoroutine = nameof(WallSlidingFixedUpdate);
yield break;
}
}
transform.position = pos - col.size.y * 0.5f * Vector2.up;
yield return new WaitForFixedUpdate();
}
}
IEnumerable WallSlidingFixedUpdate()
{
print("wallsliding coroutine entered");
while (true)
{
print("wallsliding coroutine happening");
Vector2 pos = transform.position;
pos -= Time.fixedDeltaTime * wallSlideSpeed * groundTangent / groundTangent.y;
transform.position = pos;
if (InputManager.Left && groundTangent.y > 0)
{
activeCoroutine = nameof(DefaultFixedUpdate);
yield break;
}
else if (InputManager.Right)
{
activeCoroutine = nameof(DefaultFixedUpdate);
yield break;
}
yield return new WaitForFixedUpdate();
}
}
You figured your issue about the return type out already -> It needs to be IEnumerator.
Anyway my question is WHY?
Instead of going through all that trouble for switching between different Coroutine instances via a string I would rather have only a single outer scope routine running and then rather switch between which inner block to execute
private enum RoutineType
{
Default,
WallSliding
}
private RouineType activeRoutine;
private IEnumerator FixedUptadeRoutine()
{
while(true)
{
switch(activeRoutine)
{
case RoutineType.WallSliding:
yield return DefaultRoutine();
break;
case default:
yield return WallSlidingRoutine();
break;
}
}
}
private IEnumerator DefaultRoutine()
{
while(true)
{
// NOTE: Btw you want to wait for fixed update FIRST
// Before applying all the values
...
// And now this will simply make sure you change the routine type
activeRoutine = RoutineType.WallSliding;
// and exit this one so the outer routine will handle the switch in the next frame
yield break;
...
}
}
private IEnumerator WallSlidingRoutine()
{
...
}
you can simply yield return any IEnumerator so it will be executed and at the same time the outer routine waits for it to finish. There is no need to have multiple start and stop coroutines.
Holy moly, how did I miss this. The return value of WallSlidingFixedUpdate is IEnumerable and not IEnumerator. I changed it to IEnumertor and now it works.
in the game I am creating when the player dies I destroy that object and move to a different scene which simply says you died, however the camera follows the player, this creates an error when the player is destroyed as it can no longer follow the player I cannot think of how to scan to see if the player has been destroyed (from inside the cameras script)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraMotor : MonoBehaviour
{
public Transform lookAt;
public float boundX = 0.001f;
public float boundY = 0.001f;
private void LateUpdate()
{
Vector3 delta = Vector3.zero;
//to check if inside the bounds on X axis
float deltaX = lookAt.position.x - transform.position.x;
if (deltaX > boundX || deltaX < -boundX)
{
if (transform.position.x < lookAt.position.x)
{
delta.x = deltaX - boundX;
}
else
{
delta.x = deltaX + boundX;
}
}
//to check if inside the bounds on Y axis
float deltaY = lookAt.position.y - transform.position.y;
if (deltaY > boundY || deltaY < -boundY)
{
if (transform.position.y < lookAt.position.y)
{
delta.y = deltaY - boundY;
}
else
{
delta.y = deltaY + boundY;
}
}
transform.position += new Vector3(delta.x, delta.y, 0);
}
}
Check if lookAt is not null before looking at the player...
private void LateUpdate()
{
if(lookAt != null){
Vector3 delta = Vector3.zero;
//to check if inside the bounds on X axis
float deltaX = lookAt.position.x - transform.position.x;
if (deltaX > boundX || deltaX < -boundX)
{
if (transform.position.x < lookAt.position.x)
{
delta.x = deltaX - boundX;
}
else
{
delta.x = deltaX + boundX;
}
}
//to check if inside the bounds on Y axis
float deltaY = lookAt.position.y - transform.position.y;
if (deltaY > boundY || deltaY < -boundY)
{
if (transform.position.y < lookAt.position.y)
{
delta.y = deltaY - boundY;
}
else
{
delta.y = deltaY + boundY;
}
}
transform.position += new Vector3(delta.x, delta.y, 0);
}
}
When the object is destroyed, it's components are destroyed too. So You can simply check if Transform object is null.
if (lookAt == null)
{
Debug.Log("Object has destroyed");
}
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.
I am currently working on a project for one of the courses i am taking.
I am making a 2D game in unity3D, and i have a small problem, every time i run the game my character keeps on falling through the map, even though i have added a rigidbody2D and a boxCollider2D to both my character and the foreground. The code is attached, it is in C# and it is a bit long. Thank you so much in advance ..
enter code here
using System;
using UnityEngine;
using System.Collections;
public class CharacterController2D : MonoBehaviour
{
private const float SkinWidth = .02f;
private const int TotalHorizontalRays = 8;
private const int TotalVerticalRays = 4;
private static readonly float SlopeLimitTanget = Mathf.Tan (75f * Mathf.Deg2Rad);
public LayerMask PlatformMask;
public ControllerParameters2D DefaultParameters;
public ControllerState2D State { get; private set; }
public Vector2 Velocity { get { return _velocity; }}
public bool HandleCollisions { get; set; }
//Return overrideparamteres if it is not null, if it is null it will return DefaultParameters
public ControllerParameters2D Parameters { get { return _overrideParameters ?? DefaultParameters; } }
public GameObject StandingOn { get; private set;}
public Vector3 PlatformVelocity { get; private set;}
public bool CanJump
{
get
{
if(Parameters.JumpRestrictions == ControllerParameters2D.JumpBehavior.CanJumpAnywhere)
return _jumpIn <= 0;
if(Parameters.JumpRestrictions == ControllerParameters2D.JumpBehavior.CanJumpOnGround)
return State.IsGrounded;
return false;
}
}
private Vector2 _velocity;
private Transform _transform;
private Vector3 _localScale;
private BoxCollider2D _boxCollider;
private ControllerParameters2D _overrideParameters;
private float _jumpIn;
private GameObject _lastStandingOn;
private Vector3
_activeGlobalPlatformPoint,
_activeLocalPlatformPoint;
private Vector3
_raycastTopLeft,
_raycastBottomRight,
_raycastBottomLeft;
private float _verticalDistanceBetweenRays,
_horizonatalDistanceBetweenRays;
public void Awake()
{
HandleCollisions = true;
State = new ControllerState2D();
_transform = transform;
_localScale = transform.localScale;
_boxCollider = GetComponent <BoxCollider2D>();
// Absolute Value
var colliderWidth = _boxCollider.size.x * Mathf.Abs(transform.localScale.x) - (2 * SkinWidth);
_horizonatalDistanceBetweenRays = colliderWidth / (TotalVerticalRays - 1);
var colliderHeight = _boxCollider.size.y * Mathf.Abs( transform.localScale.y ) - (2 * SkinWidth);
_verticalDistanceBetweenRays = colliderHeight / (TotalHorizontalRays - 1);
}
public void AddForce(Vector2 force)
{
_velocity = force;
}
public void SetForce(Vector2 force)
{
_velocity += force;
}
public void SetHorizontalForce(float x)
{
_velocity.x = x;
}
public void SetVerticalForce(float y)
{
_velocity.y = y;
}
public void Jump()
{
AddForce(new Vector2(0, Parameters.JumpMagnitude));
_jumpIn = Parameters.JumpFrequency;
}
public void LateUpdate()
{
_jumpIn -= Time.deltaTime;
//We force the player to go up or down based on the gravity
_velocity.y += Parameters.Gravity * Time.deltaTime;
//Move the characther per his velocity scaled by time
Move (Velocity * Time.deltaTime);
}
// Ensures the player doesn't fall off the map or move through the wall
private void Move(Vector2 deltaMovement)
{
var wasGrounded = State.IsCollidingBelow;
State.Reset();
if(HandleCollisions)
{
HandlePlatforms();
CalculateRayOrigins();
if(deltaMovement.y < 0 && wasGrounded)
HandleVerticalSlope(ref deltaMovement);
if(Mathf.Abs(deltaMovement.x) > .001f)
MoveHorizontally(ref deltaMovement);
MoveVertically(ref deltaMovement);
CorrectHorizontalPlacement(ref deltaMovement, true);
CorrectHorizontalPlacement(ref deltaMovement, false);
}
_transform.Translate(deltaMovement, Space.World);
if (Time.deltaTime > 0)
_velocity = deltaMovement / Time.deltaTime;
_velocity.x = Mathf.Min (_velocity.x, Parameters.MaxVelocity.x);
_velocity.y = Mathf.Min (_velocity.y, Parameters.MaxVelocity.y);
if(State.IsMovingUpSlope)
_velocity.y = 0;
//Standing on the platform
if(StandingOn != null)
{
_activeGlobalPlatformPoint = transform.position;
_activeLocalPlatformPoint = StandingOn.transform.InverseTransformPoint(transform.position);
Debug.DrawLine(transform.position, _activeGlobalPlatformPoint);
Debug.DrawLine(transform.position, _activeLocalPlatformPoint + StandingOn.transform.position);
if(_lastStandingOn != StandingOn)
{
//If the last thing we are standing on is not null, send a message to leave it
if(_lastStandingOn != null)
_lastStandingOn.SendMessage("ControllerExist2D", this, SendMessageOptions.DontRequireReceiver);
//Inform what we are standing on that we have entered
StandingOn.SendMessage("ControllerEnter2D", this, SendMessageOptions.DontRequireReceiver);
_lastStandingOn = StandingOn;
}
//Invoke the platform that we are standing on it
else if (StandingOn != null)
StandingOn.SendMessage("ControllerStay2D", this, SendMessageOptions.DontRequireReceiver);
}
else if (_lastStandingOn != null)
{
_lastStandingOn.SendMessage("ControllerExit2D", this, SendMessageOptions.DontRequireReceiver);
_lastStandingOn = null;
}
}
private void HandlePlatforms()
{
//Calculate the velocity of the platform
if(StandingOn != null)
{
var newGlobalPlatformPoint = StandingOn.transform.TransformPoint(_activeLocalPlatformPoint);
var moveDistance = newGlobalPlatformPoint - _activeGlobalPlatformPoint;
//Sticks the player on the platform, wherever the platform teleport the players stays on it
if(moveDistance != Vector3.zero)
transform.Translate(moveDistance, Space.World);
PlatformVelocity = (newGlobalPlatformPoint - _activeGlobalPlatformPoint) / Time.deltaTime;
}
else
PlatformVelocity = Vector3.zero;
StandingOn = null;
}
private void CorrectHorizontalPlacement(ref Vector2 deltaMovement, bool isRight)
{
var halfwidth = (_boxCollider.size.x * _localScale.x) / 2f;
var rayOrigin = isRight ? _raycastBottomRight : _raycastBottomLeft;
if(isRight)
rayOrigin.x -= (halfwidth - SkinWidth);
else
rayOrigin.x += (halfwidth - SkinWidth);
var rayDirection = isRight ? Vector2.right : -Vector2.right;
var offset = 0f;
for(var i = 1; i <= TotalHorizontalRays - 1; i++)
{
var rayVector = new Vector2(deltaMovement.x + rayOrigin.x, deltaMovement.y + rayOrigin.y + (i * _verticalDistanceBetweenRays));
Debug.DrawRay(rayVector, rayDirection * halfwidth, isRight ? Color.cyan : Color.magenta);
var raycastHit = Physics2D.Raycast(rayVector, rayDirection, halfwidth, PlatformMask);
if(!raycastHit)
continue;
offset = isRight ? ((raycastHit.point.x - _transform.position.x) - halfwidth) : (halfwidth - (_transform.position.x - raycastHit.point.x));
}
deltaMovement.x += offset;
}
private void CalculateRayOrigins()
{
var size = new Vector2 (_boxCollider.size.x * Mathf.Abs (_localScale.x), _boxCollider.size.y * Mathf.Abs (_localScale.y)) / 2;
var center = new Vector2(_boxCollider.center.x * _localScale.x, _boxCollider.center.y * _localScale.y);
//Location of the player, then we add the box collider to it relative to the center of the player
_raycastTopLeft = _transform.position + new Vector3 (center.x - size.x + SkinWidth, center.y + size.y - SkinWidth);
_raycastBottomRight = _transform.position + new Vector3 (center.x + size.x - SkinWidth, center.y - size.y + SkinWidth); //Going right
_raycastBottomLeft = _transform.position + new Vector3 (center.x - size.x + SkinWidth, center.y - size.y + SkinWidth); //Going left and down-up
}
//Cast rays to the left or to the right depending on the player's movement
//Determining how far the player can go either to the left, or to the right
private void MoveHorizontally(ref Vector2 deltaMovement)
{
var isGoingRight = deltaMovement.x > 0;
//The distance between the starting point and the final destination
var rayDistance = Mathf.Abs (deltaMovement.x) + SkinWidth;
//Where is the player going? right or left
var rayDirection = isGoingRight ? Vector2.right : -Vector2.right;
//Right? we start from bottom right. Left? we start fro, bottom left
var rayOrigin = isGoingRight ? _raycastBottomRight : _raycastBottomLeft;
//Determines how many rays we want to shoot out to the left or to the right
for(var i = 0; i < TotalHorizontalRays; i++)
{
var rayVector = new Vector2(rayOrigin.x, rayOrigin.y + (i * _verticalDistanceBetweenRays));
//Visual representation about the rays
Debug.DrawRay(rayVector, rayDirection * rayDistance, Color.red);
//Checks if the player hit something or not
var rayCastHit = Physics2D.Raycast(rayVector, rayOrigin, rayDistance, PlatformMask);
if(!rayCastHit) //If there was a raycast then do something, otherwise continue to loop
continue;
//We return true if we are on a horizotnal slope, and check if we are going right or left or hit something while going up
if(i == 0 && HandleHorizontalSlope(ref deltaMovement, Vector2.Angle(rayCastHit.normal, Vector2.up), isGoingRight))
break;
//If we hit something then we can only go that far forward
deltaMovement.x = rayCastHit.point.x - rayVector.x;
rayDistance = Mathf.Abs(deltaMovement.x);
if(isGoingRight)
{
//If we are going right, then we have to substract the skinwidth
deltaMovement.x -= SkinWidth;
State.IsCollidingRight = true;
}
else
{
//The oppoiste of the if statement, if we are going left, we add the skinwidth
deltaMovement.x += SkinWidth;
State.IsCollidingLeft = true;
}
//Handles error collision, if the player hits something and go through it
if(rayDistance < SkinWidth + .0001f)
break;
}
}
private void MoveVertically(ref Vector2 deltaMovement)
{
//Check to see if going up or down
var isGoingUp = deltaMovement.y > 0;
var rayDistance = Mathf.Abs (deltaMovement.y) + SkinWidth;
var rayDirection = isGoingUp ? Vector2.up : -Vector2.up;
var rayOrigin = isGoingUp ? _raycastTopLeft : _raycastBottomLeft;
rayOrigin.x += deltaMovement.x;
var standingOnDistance = float.MaxValue;
for(var Count = 0; Count < TotalVerticalRays; Count++)
{
var rayVector = new Vector2(rayOrigin.x + (Count * _horizonatalDistanceBetweenRays), rayOrigin.y);
Debug.DrawRay(rayVector, rayDirection * rayDistance, Color.red);
var raycastHit = Physics2D.Raycast(rayVector, rayDirection, rayDistance, PlatformMask);
//If the player hit nothing then keep going.
if(raycastHit)
{
continue;
}
if(!isGoingUp)
{
var verticalDistanceToHit = _transform.position.y - raycastHit.point.y;
if(verticalDistanceToHit < standingOnDistance)
{
standingOnDistance = verticalDistanceToHit;
//Platform we are standing on
StandingOn = raycastHit.collider.gameObject;
}
}
//Determine the furthest distance we can move down or up without hitting anything
deltaMovement.y = raycastHit.point.y - rayVector.y;
rayDistance = Mathf.Abs(deltaMovement.y);
if(isGoingUp)
{
deltaMovement.y -= SkinWidth;
State.IsCollidingAbove = true;
}
else
{
deltaMovement.y += SkinWidth;
State.IsCollidingBelow = true;
}
if(!isGoingUp && deltaMovement.y > .0001f)
{
State.IsMovingUpSlope = true;
}
if(rayDistance < SkinWidth + .0001f)
{
break;
}
}
}
private void HandleVerticalSlope(ref Vector2 deltaMovement)
{
//Give us the center of the vertical rays;
var center = (_raycastBottomLeft.x + _raycastBottomRight.x) / 2;
var direction = -Vector2.up;
var slopeDistance = SlopeLimitTanget * (_raycastBottomRight.x - center);
var slopeRayVector = new Vector2 (center, _raycastBottomLeft.y);
Debug.DrawRay(slopeRayVector, direction * slopeDistance, Color.yellow);
var raycastHit = Physics2D.Raycast (slopeRayVector, direction, slopeDistance, PlatformMask);
if (!raycastHit)
return;
// ReSharper disable CompareOfFloatsByEqualityOperator
var isMovingDownSlope = Mathf.Sign (raycastHit.normal.x) == Mathf.Sign (deltaMovement.x);
if(!isMovingDownSlope)
return;
var angle = Vector2.Angle (raycastHit.normal, Vector2.up);
if(Mathf.Abs(angle) < .0001f)
return; //Which means there we are not on a slope, we are on something else
State.IsMovingDownSlope = true;
State.SlopeAngle = angle;
deltaMovement.y = raycastHit.point.y - slopeRayVector.y;
}
private bool HandleHorizontalSlope(ref Vector2 deltaMovement, float angle, bool isGoingRight)
{
//We do not want to move to an angle of 90
if(Mathf.RoundToInt(angle) == 90)
return false;
if(angle > Parameters.SlopeLimit)
{
deltaMovement.x = 0;
return true;
}
if(deltaMovement.y > .07f)
return true;
deltaMovement.x += isGoingRight ? -SkinWidth : SkinWidth;
deltaMovement.y = Mathf.Abs (Mathf.Tan (angle * Mathf.Deg2Rad) * deltaMovement.x);
State.IsMovingUpSlope = true;
State.IsCollidingBelow = true;
return true;
}
public void OnTriggerEnter2D(Collider2D other)
{
var parameters = other.gameObject.GetComponent<ControllerPhysicsVolume2D>();
if(parameters == null)
return;
_overrideParameters = parameters.Parameters;
}
public void OnTriggerExit2D(Collider2D other)
{
var parameters = other.gameObject.GetComponent<ControllerPhysicsVolume2D>();
if(parameters == null)
return;
_overrideParameters = null;
}
}
As #Terrance said you really don't need to write your own code logic for collision detection.
Secondly I noticed OnTriggerEnter2D and OnTriggerExit2D methods in your code, that also points out to one thing that all your boxCollider2d has isTrigger option checked. Therefore, I suggest you to uncheck isTrigger option on both your player and ground as TRIGGER will never stop two objects from crossing each other (you need to have both objects have 'isTrigger' unchecked on their colliders if you don't want them to pass through each other). And use method OnCollisionEnter2D and OnCollisionExit2D to detect the collision.
What is difference between Trigger and a Collider:
Taking real world example Colliders are tangible object e.g you yourself and the floor you are standing on both are solid and tangible.
While trigger is intangible, example of triggers can be the walkthrough security doors; these doors are hollow from inside allowing any person to pass without any hindrance, but if you wear any metal object on yourself and pass through that "Hollow Area" between the door, the door will TRIGGER an alarm. therefore with respect to the game world you can say that the door has a trigger at it hollow area.