My Unity project is a 2D Tron kind of like game, where the character moves around at a constant velocity and changes direction to up, down, left, and right while leaving a line in its path. All that is working fine. But I want to be able to collide with them as well as with the walls around the map. I want to achieve this using raycasting collisions. Basically, I want the character to not be able to move towards the direction it was moving if a wall is in front of it. The thing is that I don't really know how to stop the character when this happens.
An example of what I want is this:
Here is my code for the player.
void Start(){
direction = Vector2.down;
}
void Update(){
Move(up, down, left, right);
Collision();
}
void Move(KeyCode up, KeyCode down, KeyCode left, KeyCode right){
position = transform.position;
direction = (Input.GetKeyDown(up) && direction != Vector2.down)?Vector2.up:direction;
direction = (Input.GetKeyDown(down) && direction != Vector2.up)?Vector2.down:direction;
direction = (Input.GetKeyDown(left) && direction != Vector2.right)?Vector2.left:direction;
direction = (Input.GetKeyDown(right) && direction != Vector2.left)?Vector2.right:direction;
speed = (Input.GetKey(KeyCode.LeftShift))?2: 4;
velocity = direction / speed;
position += velocity;
transform.position = position;
Trail trail = Instantiate(trail1, transform.position, Quaternion.identity) as Trail;
trail.color = (speed == 2)?new Color32(247, 118, 34, 255):new Color32(254, 174, 52, 255);
}
void Collision(){
float rayLength = 10;
RaycastHit2D hit = Physics2D.Raycast(transform.position, direction, rayLength, collisionMask);
Debug.DrawRay(transform.position, direction * rayLength, Color.red);
if(hit){
}
}
So far, this draws a ray into the direction the player is moving towards. To put everything into perspective, lets say the player is moving left at a constant speed, and I am not pressing the key. If a wall is in front of the player, it will not be able to go through the wall.
Related
I am currently making a Katamari/Billy Hatcher game where the player has to roll spheres around. When the game starts the player has normal platformer controls until it approaches a sphere and if the player presses the "attach" button, the player becomes a child of the sphere. The issue I am having is whenever this happens, the player rotates with the sphere. I tried freezing the player's rigid body so it can stop rotating but that just stops the sphere's rotation. Is there any way to stop the rotation of the player, while keeping the sphere rotating?
Picture:
enter image description here
Here's my scripts for the process:
Rigidbody hitRB;
public Vector3 offset = new Vector3(0, 0, 1);
public LayerMask pickupMask;
bool isAttached;
private void TouchingGum()
{
RaycastHit hit = new RaycastHit();
foreach (GameObject gumball in gumBalls)
{
if (Input.GetButtonDown("Attach") && Physics.Raycast(transform.position,transform.forward, out hit, attachRequireDistance, pickupMask))
{
isAttached = true;
Debug.Log(true);
}
else
{
isAttached = false;
}
}
if (isAttached)
{
hitRB = hit.collider.gameObject.GetComponent<Rigidbody>();
Vector3 heldOffset = transform.right * offset.x + transform.up * offset.y + transform.forward * offset.z;
hitRB.isKinematic = true;
hitRB.MovePosition(player.transform.position + heldOffset);
}
else if(!isAttached && !hitRB == null)
{
hitRB.isKinematic = false;
}
If you can, don’t use parent/child relationships in these situations. I feel that there is always a better way. One way of accomplishing this is by taking the player’s transform, and adding the forward direction to an offset:
Vector3 offset = new Vector3(0, 0, 1);
LayerMask pickupMask; //change to mask of objects that can be picked up in editor.
public bool held;
void Update()
{
RaycastHit hit;
if (Input.GetKey(KeyCode.Mouse0) && Physics.Raycast(transform.position, transform.forward, out hit, 2, pickupMask))
{
held = true;
}
else
{
held = false
}
Rigidbody hitRB = hit.collider.gameObject.GetComponent<Rigidbody>();
if (held)
{
Vector3 heldOffset = (transform.right * offset.x) + (transform.up * offset.y) + (transform.forward * offset.z);
// if it still glitches, remove the previous line and add the line after this one.
Vector3 heldOffset = transform.forward * offset.z;
hitRB.isKinematic = true;
hit.collider.gameObject.transform.position = transform.position + heldOffset;
}
if else (!held && !hitRB == null)
{
hitRB.isKinematic = false;
}
}
This script uses raycast and input to detect if the player is clicking the left mouse button and is looking at an object within 2 distance and with a certain layer mask. Then it will set the velocity to the offset plus the player’s position. In other words, this gets the object you looked at while pressing left click, and holds it in front of you (or whatever the offset is).
So, I'm trying to make something similar to Super Mario Galaxy's planet traversal where the player has to jump from planet to planet but the difference here is that the player is orbiting around the player instead of walking on it. I've managed to do the jumping from planet to planet but I have a small problem on where the players up direction is facing.
While landing somewhere in the middle of the planet the player changes his up direction in the opposite way he was facing when he first made the jump, if he lands somewhere close to the edge my logic fails.
As you can see in the photo bellow the Y axis is facing the wrong direction and it should face in the direction where the X axis is facing right now.
What would be the right logic to follow for the player Y axis to face the correct way?
// Jump from a planet
void Update()
{
if (TouchListener.Instance.Tap)
{
isOrbiting = false;
_rb.AddForce(transform.up * jumpForce, ForceMode2D.Impulse);
}
}
// Rotating around the planet
private void FixedUpdate()
{
if (planet != null && isOrbiting)
transform.RotateAround(planet.transform.localPosition, transform.forward * -1, orbitSpeed * Time.deltaTime);
}
// Handling the player entering the orbit. Here's where I inverse the up direction of the player. Also stopping any kind of force or rotation
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Planet"))
{
if (!isOrbiting)
score++;
planet = other.gameObject;
isOrbiting = true;
if (score > 0)
{
_rb.velocity = Vector2.zero;
_rb.angularVelocity = 0f;
_rb.rotation = 0f;
transform.up = transform.up * -1;
}
}
}
Use Quaternion.LookRotation to set the player's up to be facing away from the planet and its forward to be world forward (away from camera):
static void SetPlayerUp(Transform player, Transform planet)
{
player.rotation = Quaternion.LookRotation(Vector3.forward,
player.position - planet.position);
}
Consider calling this instead of _rb.rotation = 0f; and transform.up = transform.up * -1;
This question is about Unity3D.
I want to create a navigation similar to Google Earth where you click and drag on a sphere and let the camera orbit accordingly. It is important that the point that was grabbed is always under the mouse position while dragging. The navigation should also work if I zoom close to the sphere. I do not want to rotate the sphere itself. Just exactly like Google Earth does it.
My attempt is to project the mouse position to the sphere if I start to drag. On the next frame I do the same and calculate the angle between the start drag and end drag position.
private void RotateCamera(Vector3 dragStart, Vector3 dragEnd)
{
// calc the rotation of the drag
float angle = Vector3.Angle(dragStart, dragEnd);
// rotate the camera around the sphere
Camera.main.transform.RotateAround(sphere), Vector3.up, angle);
}
I thought of using Unitys RotateAround method to rotate the camera with the calculated angle. Unfortunately I do not have the rotation vector (using Vector3.up in the example is obviously wrong).
Does somebody know how I can calculate this vector to apply it for the method? Am I on the right direction to implement the Google Earth navigation?
Thank You!
UPDATE
I am very close with a new solution. I project the drag vectors to a down and a right plane to get the angles. Afterwards I rotate the camera around up and left. This works well until I reach the poles of the sphere. The camera rotates a lot around itself if I reach a pole.
private void RotateCamera(Vector3 dragStart, Vector3 dragEnd)
{
Vector3 plane = Vector3.down;
var a = Vector3.ProjectOnPlane(dragStart, plane);
var b = Vector3.ProjectOnPlane(dragEnd, plane);
float up = Vector3.SignedAngle(a, b, plane);
plane = Vector3.right;
a = Vector3.ProjectOnPlane(dragStart, plane);
b = Vector3.ProjectOnPlane(dragEnd, plane);
float left = Vector3.SignedAngle(a, b, plane);
Camera.main.transform.RotateAround(_sphere, Vector3.up, up);
Camera.main.transform.RotateAround(_sphere, Vector3.left, left);
}
Turns out that is was easier than I expected. I thought about calculating the rotation axis and came to the conclusion that is must be the cross product of the start and end vector. Take a look at the solution. The RotateCamera method is where the math magic happens :)
public class GoogleEarthControls : MonoBehaviour
{
private const int SpehreRadius = 1;
private Vector3? _mouseStartPos;
private Vector3? _currentMousePos;
void Start () {
// init the camera to look at this object
Vector3 cameraPos = new Vector3(
transform.position.x,
transform.position.y,
transform.position.z - 2);
Camera.main.transform.position = cameraPos;
Camera.main.transform.LookAt(transform.position);
}
private void Update()
{
if (Input.GetMouseButtonDown(0)) _mouseStartPos = GetMouseHit();
if (_mouseStartPos != null) HandleDrag();
if (Input.GetMouseButtonUp(0)) HandleDrop();
}
private void HandleDrag()
{
_currentMousePos = GetMouseHit();
RotateCamera((Vector3) _mouseStartPos, (Vector3)_currentMousePos);
}
private void HandleDrop()
{
_mouseStartPos = null;
_currentMousePos = null;
}
private void RotateCamera(Vector3 dragStartPosition, Vector3 dragEndPosition)
{
// in case the spehre model is not a perfect sphere..
dragEndPosition = dragEndPosition.normalized * SpehreRadius;
dragStartPosition = dragStartPosition.normalized * SpehreRadius;
// calc a vertical vector to rotate around..
var cross = Vector3.Cross(dragEndPosition, dragStartPosition);
// calc the angle for the rotation..
var angle = Vector3.SignedAngle(dragEndPosition, dragStartPosition, cross);
// roatate around the vector..
Camera.main.transform.RotateAround(transform.position, cross, angle);
}
/**
* Projects the mouse position to the sphere and returns the intersection point.
*/
private static Vector3? GetMouseHit()
{
// make sure there is a shepre mesh with a colider centered at this game object
// with a radius of SpehreRadius
RaycastHit hit;
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit))
{
return hit.point;
}
return null;
}
}
Basic rotation based on mouse drag, based on what you have:
Transform camTransform = Camera.main.transform;
if (Input.GetMouseButton(0))
{
camTransform.RotateAround(currentLookTargetTransform.position, -camTransform.right * Input.GetAxis("Mouse Y") + camTransform.up * Input.GetAxis("Mouse X"), 120 * Time.deltaTime);
}
You can multiply the relative direction by the mouse change value to get the axis. Then you can supplant your clamp points in; but the point was to rotate it relatively.
I want to move an instance of a gameObject along the outline of another gameobject. Its a 2D Project in Unity.
My current Code:
Vector3 mousePosition = m_camera.ScreenToWorldPoint(Input.mousePosition);
RaycastHit2D hit = Physics2D.Raycast(new Vector2(mousePosition.x, mousePosition.y), new Vector2(player.transform.position.x, player.transform.position.y));
if (hit.collider != null && hit.collider.gameObject.tag == "Player") {
if (!pointerInstance) {
pointerInstance = Instantiate(ghostPointer, new Vector3(hit.point.x, hit.point.y, -1.1f), Quaternion.identity);
} else if(pointerInstance) {
pointerInstance.gameObject.transform.position = new Vector3(hit.point.x, hit.point.y, -1.1f);
pointerInstance.gameObject.transform.eulerAngles = new Vector3(0f, 0f, hit.normal.x);
}
}
Unfortunately, the gameObject doesn't rotate towards the mouse and the position on the left side of the playerObject is also sometimes off. I tried to use Instantiate() with Quaternion.LookRotation(hit.normal), but no luck either.
Here a rough sketch of what I want to achieve:
Any help is appreciated. Thanks!
it's better to use Mathematical way instead of physical way(Raycasting),because in raycasting you have to throw ray several time for checking hit point and rotate your object,it makes lag in your game.
Attach this script to your instantiated object:
using UnityEngine;
using System.Collections;
public class Example : MonoBehaviour
{
public Transform Player;
void Update()
{
//Rotating Around Circle(circular movement depend on mouse position)
Vector3 targetScreenPos = Camera.main.WorldToScreenPoint(Player.position);
targetScreenPos.z = 0;//filtering target axis
Vector3 targetToMouseDir = Input.mousePosition - targetScreenPos;
Vector3 targetToMe = transform.position - Player.position;
targetToMe.z = 0;//filtering targetToMe axis
Vector3 newTargetToMe = Vector3.RotateTowards(targetToMe, targetToMouseDir, /* max radians to turn this frame */ 2, 0);
transform.position = Player.position + /*distance from target center to stay at*/newTargetToMe.normalized;
//Look At Mouse position
var objectPos = Camera.main.WorldToScreenPoint(transform.position);
var dir = Input.mousePosition - objectPos;
transform.rotation = Quaternion.Euler(0, 0, Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg);
}
}
Useful explanations
Atan2:
atan2(y,x) gives you the angle between the x-axis and the vector (x,y), usually in radians and signed such that for positive y you get an angle between 0 and π, and for negative y the result is between −π and 0.
https://math.stackexchange.com/questions/67026/how-to-use-atan2
Returns the angle in radians whose Tan is y/x.
Return value is the angle between the x-axis and a 2D vector starting at zero and terminating at (x,y).
https://docs.unity3d.com/ScriptReference/Mathf.Atan2.html
Mathf.Rad2Deg:
Radians-to-degrees conversion constant (Read Only).
This is equal to 360 / (PI * 2).
I've got a quick question regarding 2D Sprite animations that I haven't been able to find specifically answered anywhere:
I have a sprite with walk animations to the right. However, I obviously want to flip the animation to the left when he walks left (2D side-scroller).
I can easily flip the sprite itself, using transform.localscale.x, however, that only flips the sprite. Not the animation clip. (This no longer happens in Unity)
So, while the sprite flips, the minute the animation clip begins playing, it flips back right (as the only animation clip I have is for the right facing sprite).
Is the only way to do this to flip the sprites in Photoshop, or is there a way to do it in Unity?
Thanks!
UPDATE: With the actual versions of unity if you scale the transform by multiplying it by -1, the animation frames are also scaled.
I finally figured it out by doing this:
void Flip()
{
// Switch the way the player is labelled as facing
facingRight = !facingRight;
// Multiply the player's x local scale by -1
Vector3 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
}
This is from Unity's 2D Platformer example.
To implement some sort of checking which makes use of the Flip method, you can do something similar to the below example which is basic movement code. facingRight is set as a value on the class so that the other methods can use it, and it is defaulted to false.
void Update()
{
//On X axis: -1f is left, 1f is right
//Player Movement. Check for horizontal movement
if (Input.GetAxisRaw ("Horizontal") > 0.5f || Input.GetAxisRaw("Horizontal") < -0.5f)
{
transform.Translate (new Vector3 (Input.GetAxisRaw ("Horizontal") * moveSpeed * Time.deltaTime, 0f, 0f));
if (Input.GetAxisRaw ("Horizontal") > 0.5f && !facingRight)
{
//If we're moving right but not facing right, flip the sprite and set facingRight to true.
Flip ();
facingRight = true;
} else if (Input.GetAxisRaw("Horizontal") < 0.5f && facingRight)
{
//If we're moving left but not facing left, flip the sprite and set facingRight to false.
Flip ();
facingRight = false;
}
//If we're not moving horizontally, check for vertical movement. The "else if" stops diagonal movement. Change to "if" to allow diagonal movement.
} else if (Input.GetAxisRaw ("Vertical") > 0.5f || Input.GetAxisRaw("Vertical") < -0.5f)
{
transform.Translate (new Vector3 (0f, Input.GetAxisRaw ("Vertical") * moveSpeed * Time.deltaTime, 0f));
}
//Variables for the animator to use as params
anim.SetFloat ("MoveX", Input.GetAxisRaw ("Horizontal"));
anim.SetFloat ("MoveY", Input.GetAxisRaw ("Vertical"));
}
void FlipHorizontal()
{
animator.transform.Rotate(0, 180, 0);
}
You could also do that on transform itself (without animator). But in that case rotation value can be overriden by animator
This is how I did it - almost the same as the other technique by Jestus with unity script.
var facing : String = "right";
function updateFacing(curr : String){
if(curr != facing){
facing = curr;
var theScale : Vector3 = gameObject.transform.localScale;
theScale.x *= -1;
gameObject.transform.localScale = theScale;
}
}
//put to use
function controls(){
if(Input.GetKey (KeyCode.LeftArrow)){
updateFacing("left");
} else if(Input.GetKey (KeyCode.RightArrow)){
updateFacing("right");
}
}
If you're animating in Unity:
Copy all frames (sprites) of the animation that you want to flip over.
Paste those frames into your new animation and select everything on the first frame.
Change the x scale of the first frame from 1 to -1.
Do the same thing with the very last frame of your animation.
Now it should play facing the other direction!
This is my C# implementation. It uses a string as the direction facing to make it a little easier to debug.
public string facing = "right";
public string previousFacing;
private void Awake()
{
previousFacing = facing;
}
void Update()
{
// store movement from horizontal axis of controller
Vector2 move = Vector2.zero;
move.x = Input.GetAxis("Horizontal");
// call function
DetermineFacing(move);
}
// determine direction of character
void DetermineFacing(Vector2 move)
{
if (move.x < -0.01f)
{
facing = "left";
}
else if (move.x > 0.01f)
{
facing = "right";
}
// if there is a change in direction
if (previousFacing != facing)
{
// update direction
previousFacing = facing;
// change transform
gameObject.transform.Rotate(0, 180, 0);
}
}