I'm new to coding and trying to make a game where objects appear on a wall, and players will connect 2 that match via a line (this game will feel 2D but is in fact 3D).
My first step is just to be able to draw a line between 2 points. Later I'll try to figure out whether to use bools, or triggers, or colliders or what to determine whether the player connected the correct objects.
I'm having a lot of trouble with this. I want to click the screen once to determine a starting point, then a second time to determine an end point for the line renderer in unity. Then have this all repeat with a new line.
This script is almost working, but for some reason my first point is always set to (0, 0, 0), and I have no idea why. This is my current script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DrawLine_2PT : MonoBehaviour
{
// Apply these values in the editor
public LineRenderer Line;
public GameObject newLine;
public float minimumVertexDistance = 0.1f;
public Vector3 GetWorldCoordinate(Vector3 mousePosition)
{
Vector3 mousePos = new Vector3(mousePosition.x, mousePosition.y, 1);
return Camera.main.ScreenToWorldPoint(mousePos);
}
void Start()
{
// set the color of the line
Line.startColor = Color.red;
Line.endColor = Color.red;
// set width of the renderer
Line.startWidth = 0.3f;
Line.endWidth = 0.3f;
Line.positionCount = 0;
}
void Update()
{
if ((Input.GetMouseButtonDown(0)) && Line.positionCount == 0)
{
Vector3 mousePos = GetWorldCoordinate(Input.mousePosition);
Line.SetPosition(0, mousePos);
Line.positionCount = Line.positionCount + 2;
}
if (Input.GetMouseButtonDown(0) && Line.positionCount == 2)
{
Vector3 mousePos = GetWorldCoordinate(Input.mousePosition);
Line.SetPosition(1, mousePos);
Instantiate(newLine, transform.position, Quaternion.Euler(0, 0, 0));
GetComponent<DrawLine_2PT>().enabled = false;
}
}
}
The last bit spawns a new line after the 2nd point is made, and turns off the initial line renderer. Again, seems to be working except for the first point always spawning at (0, 0, 0) instead of the mouse position.
Any/all advice is much appreciated.
Thanks
You are doing
Line.positionCount = Line.positionCount + 2;
after you call
Line.SetPosition(0, mousePos);
so this has no effect yet since there are no points yet when you try to set it.
And then both positions you add just keep the default position 0,0,0 until you override the second one later.
You should first increase the count and then set the position.
Also note that actually both your code blocks will be executed in the same frame since after increasing the count the second condition matches as well. Is this intended?
I would rather expect something like e.g.
void Update()
{
if ((Input.GetMouseButtonDown(0))
{
if(Line.positionCount == 0))
{
Line.positionCount = 1;
var mousePos = GetWorldCoordinate(Input.mousePosition);
Line.SetPosition(0, mousePos);
}
else
{
Line.positionCount = 2;
var mousePos = GetWorldCoordinate(Input.mousePosition);
Line.SetPosition(1, mousePos);
Instantiate(newLine, transform.position, Quaternion.Euler(0, 0, 0));
GetComponent<DrawLine_2PT>().enabled = false;
}
}
}
And if you want to you could even visually update the line while the user is drawing it like e.g.
void Update()
{
if ((Input.GetMouseButtonDown(0))
{
if(Line.positionCount == 0))
{
Line.positionCount = 1;
Vector3 mousePos = GetWorldCoordinate(Input.mousePosition);
Line.SetPosition(0, mousePos);
}
else
{
Line.positionCount = 2;
var mousePos = GetWorldCoordinate(Input.mousePosition);
Line.SetPosition(1, mousePos);
Instantiate(newLine, transform.position, Quaternion.Euler(0, 0, 0));
GetComponent<DrawLine_2PT>().enabled = false;
}
}
else if(Line.positionCount == 1)
{
var mousePos = GetWorldCoordinate(Input.mousePosition);
Line.SetPosition(1, mousePos);
}
}
Related
I am trying to achieve something similar to the classic snake game, but, the player only moves one unit every time a button is pressed and the "tail (blue square)" needs to move to the previous position of the head
I want the blue square to always stay at the last position of the white square, not on top of it.
Here is my code:
[SerializeField] Transform segmentPrefab;
Vector2 moveInput;
Vector2 currentHeadPos;
//I want to add more segments to the tail so I created a list
List<Transform> segmentList;
void Start()
{
//I add the head as the first object of the list
segmentList = new List<Transform>();
segmentList.Add(transform);
}
void OnMove(InputValue input)
{
//I tried/expected to save the head Pos here and use it to move the segment with this
currentHeadPos = transform.position;
moveInput = input.Get<Vector2>();
//Horizontal movement
if (moveInput.x != 0)
{
transform.position = new Vector2(transform.position.x + moveInput.x, transform.position.y);
}
//Vertical movement
if (moveInput.y != 0)
{
transform.position = new Vector2(transform.position.x, transform.position.y + moveInput.y);
}
//I loop through the list and move the last item to the position in front of it
for (int i = segmentList[segmentList.Count - 1; i > 0, i --]
{
segmentList[i].position = segmentList[i - 1].position;
}
}
//This works fine, is to add new objects to the list
void Connect()
{
Vector3 offset = new Vector3(-1, 0f, 0f);
Transform newSegment = Instantiate(segmentPrefab);
newSegment.position = segmentList[segmentList.Count - 1].position + offset;
segmentList.Add(newSegment);
}
private void OnTriggerExit2D(Collider2D collision)
{
if (collision.CompareTag("Conector"))
{
Connect();
}
}
I am fairly new to unity and coding and I can't figure out how to avoid the blue square ending on top of the one everytime it moves
I'm going to assume that the segment at segmentList[0] is your white square, and all segments further down the list are your blue squares.
The following lines of code are moving your transform found at segmentList[0]:
//Horizontal movement
if (moveInput.x != 0)
{
transform.position = new Vector2(transform.position.x + moveInput.x, transform.position.y);
}
//Vertical movement
if (moveInput.y != 0)
{
transform.position = new Vector2(transform.position.x, transform.position.y + moveInput.y);
}
If your transform at segmentList[0] has already moved, then when you update segmentList[1] = segmentList[0].position, you have already updated segmentList[0] so it is actually the current position.
If you move the following code to earlier in your OnMove method, you should get your expected results:
//I loop through the list and move the last item to the position in front of it
for (int i = segmentList[segmentList.Count - 1; i > 0, i --]
{
segmentList[i].position = segmentList[i - 1].position;
}
I have a grid consisting of 16 tiles. Now basically I want the user to find a path to the final location by moving randomly on the choices he has within the grid.
As of now I managed to create the functions for moving a step up, down, left and right. The issue arises when I'm trying to code in random movement. Ideally this is setup in a way that he can't go off bounds from the grid.
This is what I got going:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Move : MonoBehaviour
{
void Up()
{
//get the Input from Horizontal axis
float horizontalInput = Input.GetAxis("Horizontal");
//get the Input from Vertical axis
float verticalInput = Input.GetAxis("Vertical");
//update the position
transform.position = transform.position + new Vector3(0, 1.5f, 0);
//output to log the position change
Debug.Log(transform.position);
}
void Down()
{
//get the Input from Horizontal axis
float horizontalInput = Input.GetAxis("Horizontal");
//get the Input from Vertical axis
float verticalInput = Input.GetAxis("Vertical");
//update the position
transform.position = transform.position + new Vector3(0, -1.5f, 0);
//output to log the position change
Debug.Log(transform.position);
}
void Left()
{
//get the Input from Horizontal axis
float horizontalInput = Input.GetAxis("Horizontal");
//get the Input from Vertical axis
float verticalInput = Input.GetAxis("Vertical");
//update the position
transform.position = transform.position + new Vector3(-1.5f, 0, 0);
//output to log the position change
Debug.Log(transform.position);
}
void Right()
{
//get the Input from Horizontal axis
float horizontalInput = Input.GetAxis("Horizontal");
//get the Input from Vertical axis
float verticalInput = Input.GetAxis("Vertical");
//update the position
transform.position = transform.position + new Vector3(1.5f, 0, 0);
//output to log the position change
Debug.Log(transform.position);
}
void Start()
{
var finalLocation = new Vector3(-0.5f, 0.5f, 0);
var currentLocation = transform.position;
while (currentLocation != finalLocation)
{
int randomNum = Random.Range(0, 3);
if (randomNum == 0)
{
Up();
}
else if (randomNum == 1)
{
Down();
}
else if (randomNum == 2)
{
Left();
}
else if (randomNum == 3)
{
Right();
}
}
}
}
UPDATED WORKING CODE:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Move : MonoBehaviour
{
void Up()
{
//update the position
transform.position = transform.position + new Vector3(0, 1.5f, 0);
//output to log the position change
Debug.Log(transform.position);
}
void Down()
{
//update the position
transform.position = transform.position + new Vector3(0, -1.5f, 0);
//output to log the position change
Debug.Log(transform.position);
}
void Left()
{
//update the position
transform.position = transform.position + new Vector3(-1.5f, 0, 0);
//output to log the position change
Debug.Log(transform.position);
}
void Right()
{
//update the position
transform.position = transform.position + new Vector3(1.5f, 0, 0);
//output to log the position change
Debug.Log(transform.position);
}
void Update()
{
var finalLocation = new Vector3(2.5f, 2.0f, -2.0f);
var currentLocation = transform.position;
int randomNum = Random.Range(0, 4);
if (currentLocation != finalLocation) {
if (randomNum == 0)
{
Up();
}
else if (randomNum == 1)
{
Down();
}
else if (randomNum == 2)
{
Left();
}
else if (randomNum == 3)
{
Right();
}
return;
}
}
}
My last issue is how I can limit this randomness to only stick to the grid and not go off grid. Any thoughts?
Here are some problems:
horizontalInput and verticalInput are never used inside your functions
while(currentLocation != finalLocation) This condition can fail in some situations. Unity uses float for the coordinates, meaning it needs to be exactly on the same position, every decimal place need to be the same.
Random.Range(0, 3) will return a random number between 0 (inclusive) and 3 (exclusive), so the only possible values will be 0, 1 and 2. The script will never call the Right() function.
Unity uses one thread to run your scripts by default, if you put a while loop to move the object to a location it will freeze the entire game till the object is at the proper place. I recommend you to use the Update() function, it gets called every frame.
Several problems:
Your object is stuck in an endless while loop because it isn't allowed to exit (and do things like render the frame, take input from the user, etc) until your random movement script reaches its finalLocation. You aren't giving the game time to do anything else.
You almost certainly want this to be a coroutine or an Update function.
Random.Range returns an int in the range min (inclusive) to max (exclusive), so your call to it will never return 3.
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'm trying to get an object to move along a path and reset/move back to a starting position, ready to move down the path again.
The line in question reads sign_obj.transform.position = startpos; but it doesn't work even tho the debug line after it is executed!
(I'd pull out my hair if I had any!)
here's the code in full;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/* SignController
*
* controls the placement of and movement of signs
*
* by: Maurice Thompson-Hamilton
* */
public class SignController : MonoBehaviour {
public GameObject sign_center;
public GameObject sign_left;
public GameObject sign_right;
public float sign_distance;
public float sign_speed;
public bool enableSigns = true;
public Vector3 startPos = new Vector3();
private List<GameObject> hazards = new List<GameObject>();
private Vector3 sign_move;
// Use this for initialization
void Start () {
// starting pos of signs
if (startPos == Vector3.zero) { startPos = new Vector3(-12.5f, 14f, 165f); }
// set move distance if not set
if (sign_distance == 0) { sign_distance = 5; }
// set move speed if not set
if (sign_speed == 0) { sign_speed = 1.25f; }
//GameObject tmp = sign_center;
sign_center.transform.Rotate(new Vector3(0, 90f, 0));
sign_left.transform.Rotate(new Vector3(0, 90f, 0));
sign_right.transform.Rotate(new Vector3(0, 90f, 0));
sign_center.transform.position = startPos;
//Debug.Log(string.Format("sign_center {0}", startPos));
startPos.z += sign_distance;
sign_left.transform.position = startPos;
//Debug.Log(string.Format("sign_left {0}", startPos));
startPos.z += sign_distance;
sign_right.transform.position = startPos;
//Debug.Log(string.Format("sign_right {0}", startPos));
// add signs to list
hazards.Add(Instantiate(sign_center, startPos, Quaternion.identity) as GameObject);
hazards.Add(Instantiate(sign_left, startPos, Quaternion.identity) as GameObject);
hazards.Add(Instantiate(sign_right, startPos, Quaternion.identity) as GameObject);
// Debug.Log(string.Format("number of signs :{0}", hazards.Count));
}
// Update is called once per frame
void Update () {
//debug - test movement
//hazards[0].transform.Translate (Vector3.forward);
if (enableSigns) {
Vector3 tmp = new Vector3(0, 0, 0);
Vector3 player_pos = GameObject.FindGameObjectWithTag("PlayerTag").transform.position;
Debug.Log(player_pos.ToString());
// sign counter
int sc = hazards.Count;
if (sc > 0) {
foreach (GameObject sign_obj in hazards) {
// get current sign's pos
tmp = sign_obj.transform.position;
// get sample of new pos
tmp.z -= sign_distance;
// test if sample pos would move sign past player's view
//if (player_pos.z > tmp.z) {
int past_player = player_pos.z.CompareTo(tmp.z);
//Debug.Log(string.Format("past_player={0}, player_pos={1}, tmp.z={2}", past_player, player_pos.z, tmp.z));
if (past_player > 0) {
// if so, set translation vector
sign_move = new Vector3(0, 0, (float)sign_distance * sign_speed * Time.deltaTime);
// move sign
sign_obj.transform.Translate(sign_move);
} else {
// otherwise, reset sign pos to starting pos
sign_obj.transform.position = startPos;
Debug.Log(string.Format("startpos#{0}, sign_obj#{1}", startPos, sign_obj.transform.position));
//tmp.z = 165f;
/*
Destroy(sign_obj);
hazards.Remove(sign_obj);
*/
}
//sign_obj.transform.position = tmp;
//Debug.Log(string.Format("player#{0} - tmp#{1} - sign#{2}",player_pos, tmp, sign_obj.transform.position));
/* if visible
* - move towards player camera
* - if behind player
* - - remove sign (Destroy?)
* else
* */
}
}
}
}
}
I have left in the various tests (commented out) to see where the issue is. Copy, paste and un-comment as necessary.
I'm assuming that you want the signs' initial Z position to be greater than the players' initial Z position, and that the signs must move towards the player.
First thing you gotta do is correct the sign_speed in your code: it should be negative to move towards the players (lower) Z position.
if (sign_speed == 0) { sign_speed = -1.25f; }
Second thing is that you swapped the logic when calculating if the signs are past the player.
int past_player = tmp.z.CompareTo(player_pos.z);
If my assumptions are the inverse of what you want, just adapt my answer according to your needs: verify if the speed is set to the right (positive or negative) value, and check if the past_player comparison is being done right.
Currently, the line starts where I click and locks to those coordinates.
However, I want the line to start where I click on the screen and not start moving until I let go of the mouse.
(Game currently has a ball falling).
using UnityEngine;
using System.Collections;
public class TrailCollider : MonoBehaviour
{
private LineRenderer line; // Reference to LineRenderer
private Vector3 mousePos;
public PhysicsMaterial2D phys;
private Vector3 startPos; // Start position of line
private Vector3 endPos; // End position of line
void Update ()
{
// On mouse down new line will be created
if(Input.GetMouseButtonDown(0))
{
if(line == null)
createLine();
mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
mousePos.z = 0;
line.SetPosition(0,mousePos);
line.SetPosition(1,mousePos);
startPos = mousePos;
}
else if(Input.GetMouseButtonUp(0))
{
if(line)
{
mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
mousePos.z = 0;
line.SetPosition(1,mousePos);
endPos = mousePos;
addColliderToLine();
line = null;
}
}
else if(Input.GetMouseButton(0))
{
if(line)
{
mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
mousePos.z = 0;
line.SetPosition(1,mousePos);
}
}
}
// Following method creates line runtime using Line Renderer component
private void createLine()
{
line = new GameObject("Line").AddComponent<LineRenderer>();
line.material = new Material(Shader.Find("Diffuse"));
line.SetVertexCount(2);
line.SetWidth(0.1f,0.1f);
line.SetColors(Color.black, Color.black);
line.useWorldSpace = true;
}
// Following method adds collider to created line
private void addColliderToLine()
{
BoxCollider2D col = new GameObject("Collider").AddComponent<BoxCollider2D> ();
col.transform.parent = line.transform; // Collider is added as child object of line
col.sharedMaterial = phys;
float lineLength = Vector3.Distance (startPos, endPos); // length of line
col.size = new Vector3 (lineLength, 0.1f, 1f); // size of collider is set where X is length of line, Y is width of line, Z will be set as per requirement
Vector3 midPoint = (startPos + endPos)/2;
col.transform.position = midPoint; // setting position of collider object
// Following lines calculate the angle between startPos and endPos
float angle = (Mathf.Abs (startPos.y - endPos.y) / Mathf.Abs (startPos.x - endPos.x));
if((startPos.y<endPos.y && startPos.x>endPos.x) || (endPos.y<startPos.y && endPos.x>startPos.x))
{
angle*=-1;
}
angle = Mathf.Rad2Deg * Mathf.Atan (angle);
col.transform.Rotate (0, 0, angle);
}
}