I'm trying to recreate portal scene using
this video. The link to the scene assets and scripts is under the video.
I'm facing a problem with teleportation:
The player should walk through a plane-trigger in the portal to be moved to the location of the second portal. But it seems to me that after teleportation, he appears in the middle of plane-trigger in the second portal. And if he continues moving, he jumps back to the first portal, and then again to the second portal and so on.
Why is it happening and how can it be fixed? As I understand the author of the script, it should teleport player after passing through the trigger, but the script doesn't work like that for me.
Here is the script for teleportation:
using UnityEngine;
using System.Collections;
public class Sender : MonoBehaviour {
public GameObject player;
public GameObject receiver;
private float prevDot = 0;
private bool playerOverlapping = false;
void Start () {
}
void Update()
{
if (playerOverlapping) {
var currentDot = Vector3.Dot(transform.up, player.transform.position - transform.position);
if (currentDot < 10) // only transport the player once he's moved across plane
{
// transport him to the equivalent position in the other portal
float rotDiff = -Quaternion.Angle(transform.rotation, receiver.transform.rotation);
rotDiff += 180;
player.transform.Rotate(Vector3.up, rotDiff);
Vector3 positionOffset = player.transform.position - transform.position;
positionOffset = Quaternion.Euler(0, rotDiff, 0) * positionOffset;
var newPosition = receiver.transform.position + positionOffset;
player.transform.position = newPosition;
playerOverlapping = false;
}
prevDot = currentDot;
}
}
void OnTriggerEnter(Collider other)
{
if (other.tag == "Player")
{
playerOverlapping = true;
}
}
void OnTriggerExit(Collider other)
{
if (other.tag == "Player")
{
playerOverlapping = false;
}
}
}
One way you could do this would be to simply increase the offset of the player's position when teleported, but then you won't achieve the desired smooth transition.
The other method I thought of would be to add a public boolean variable to the Sender script (I'm assuming that the Sender script is on all portals), with a default value of false.
When you teleport, you set the player's position to the new position using the receiving teleporter's position. Because you have access to the receiving teleporter GameObject, you could use GetComponent to get it's Sender script. You could then set this boolean variable to true before teleporting the player.
Once the player is teleported, the OnTriggerEnter method is automatically invoked, which is your problem. You could put an if statement in OnTriggerEnter so that when it was activated, it would only teleport IF the boolean variable was false. Because it's true when the player teleports, you aren't allowed to teleport back until you leave the portal trigger, then you could set the boolean variable back to false. This would mean that the normal behavior of walking through a portal is unaffected, because the default boolean value is false.
Also, this code would probably be much better if you had your actual teleportation in OnTriggerEnter instead of Update, as Absinthe said.
Hope this helps!
I don't know where you're going with your original code (no pun intended) and I didn't watch through the video (TLDR) but you'll need to rethink this from scratch. Using the Update method is totally inappropriate; update fires on every frame - you want to teleport every frame?
Instead use the OnTriggerEnter method:
void OnTriggerEnter(Collider other)
{
other.transform.position = theVectorYouWantToTeleportTo
}
Related
I am creating a game in unity where the player can drag one object (a card) over another (an enemy). If they drag the card over the enemy I want to run some code, and if not I want to return the card to its initial position. I placed a collider on both objects but it isnt working the way it should. I am assuming this is because the object is getting moved back to its initial location before the physics engine sees the collision, but I don't know how to fix this. I am deactivating the collider on the card while moving it to avoid having it trigger collisions until the player has placed it using the onmousedown and onmouseup events. Any tips on how to fix this behavior? can I force the physics engine to check collisions with the onmouseup event?
I know the collisions are working because when I turn off the return to initial position behavior the game functions as expected.
How about useing the collider as trigger and do not use rigidbodys or anything.
Then if there is a trigger enter event set bool as true. If there trigger exit reset the bool to false.
Now if you "Release" the card check if bool is true or false.
true: Set the cards position to the player or what you want
false: Reset the cards position to the start
Now beeing a little mroe fancy you can set a lighted border around the card when bool is active (just check in update)
Example:
public class Card : MonoBehaviour {
private bool isHolding;
private bool isHovering;
public Vector3 startPos;
public void Start() {
startPos = transform.position;
}
public void Update() {
// Code where you check if the card is Clicked and Moved by the player
// If so set isHolding = true
// dont enter the check if holding blablabla when animation stuff is happening
if (doAnimationStuff) {
// do animation
// Destroy Object
return;
}
// Code to check if isHolding == false
if (!isHolding) {
if (!isHovering) {
transform.position = startPos;
} else {
doAnimationStuff = true;
}
}
}
private void OnTriggerEnter(Collider other) {
// Check if other is a player
// if so set isHovering = true
}
private void OnTriggerExit(Collider other) {
// Check if other is a player
// if so set isHovering = false
}
}
I am trying to create a script for my enemy turret, but it is not going well. I have a couple animations of the turret being activated and deactivated. What I need is that based on the distance from the player, it plays either animation. So once it moves inside the detection radius it plays the activation animation and once it is outside it plays the deactivation animation. Most of the other ways I try require me to create an Animation Controller, which I have little experience in using. I want a simple way to play one animation once it is inside and play a different one when it is outside. I think there was a way to store the animation clip in the script, and then play it. I have attached my current script, so you know what I mean.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyTurret : MonoBehaviour
{
public GameObject Player;
public float DistanceToPlayer;
public float DetectionRadius = 75;
// Start is called before the first frame update
void Start()
{
Player = GameObject.FindGameObjectWithTag("PlayerTank");
}
// Update is called once per frame
void Update()
{
DistanceToPlayer = Vector3.Distance(transform.position, Player.transform.position);
if (DistanceToPlayer<=DetectionRadius)
{
Debug.Log("Within Radius");
}
if (DistanceToPlayer >= DetectionRadius)
{
Debug.Log("Outside Radius");
}
}
}
Calculating and checking the distance of the player for every update() is not ideal. It will work, but it will do more work than it needs to when the player isn't even near it. Its not efficient.
What you may want to do if your player is a Rigidbody, is add a SphereCollider to the turret, set isTrigger=true, set the radius to be your detection radius, and handle the OnTriggerEnter() and OnTriggerExit() events to play or stop animations.
You can also add two public Animiation objects to the script, drag and drop your animations in the editor, then you can use animation.Play() and .Stop() etc. to control the animations.
Something similar to this. Not tested fully, but you can get the idea.
public float detectionRadius = 75;
public Animation activateAnimation;
public Animation deactivateAnimation;
void Start()
{
SphereCollider detectionSphere = gameObject.AddComponent<SphereCollider>();
detectionSphere.isTrigger = true;
detectionSphere.radius = detectionRadius;
detectionSphere.center = Vector3.zero;
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "PlayerTank")
{
activateAnimation.Play();
}
}
private void OnTriggerExit(Collider other)
{
if (other.gameObject.tag == "PlayerTank")
{
deactivateAnimation.Play();
}
}
Your animations must not loop otherwise you will have to add more logic to check if animation.isPlaying and do your own animation.Stop() etc.
I'm trying to make a 2D portal in Unity that doesn't just transport the player to the other portal gameobject. But keeps the players position and movements direction and velocity after going through the portal.
I'm be no means an artist, but for example:
The player is a ball bouncing around an area, when he does through the portal his velocity is maintained, along with the fact that he entered he center of the portal.
Where here for example:
If the player enters the bottom half of the portal, he will come out the bottom half of the portal.
When this works, it works great! However, it only works 50% of the time, that 50% can have a bunch of different issues though, sometimes the ball will just not teleport. Sometimes the ball hits the first portal, teleports to the second portal, then back to the first portal, and it does this repeatedly forever. And it experiences these issues seemingly at random.
Here is my Script:
public GameObject otherPortal;
public PortalController otherPortalScript;
private BallController ballController;
public float waitTime = 0.5f;
[HideInInspector]
public bool teleporting;
// Use this for initialization
void Start ()
{
}
// Update is called once per frame
void Update ()
{
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.tag == "Ball")
{
ballController = other.GetComponent<BallController>();
if(!teleporting)
{
var offset = other.transform.position - transform.position;
offset.x = 0;
other.transform.position = otherPortal.transform.position + offset;
otherPortalScript.teleporting = true;
teleporting = true;
StartCoroutine("Teleport");
}
}
}
void OnTriggerExit2D(Collider2D other)
{
if (other.gameObject.tag == "Ball")
{
teleporting = false;
otherPortalScript.teleporting = false;
}
}
IEnumerator Teleport()
{
yield return new WaitForSeconds(waitTime);
teleporting = false;
otherPortalScript.teleporting = false;
ballController.teleporting = false;
}
}
The script is attached to both portals, which are both prefabs of the same object. I set both "otherPortal", "otherPortalScript", & "waitTime" in the editor. "waitTime is something I had" to add after the fact to fix another issue I was having where sometimes "teleporting" never got set to false, I believe the the cause of the that problem is the same cause of this problem, making "waitTime" just a bandage for a larger issue. Also, anytime the Portal Script changes a variable in "ballController" such as "ballController.teleporting = false;", it's only there because the ball is add/removing points from a score system, it doesn't at all affect the ball's movement.
Consider getting rid of the teleporting property of the portals and the ball as well as the waitTime.
Now give the ball a List<PortalController> inUseControllers (note you need to add using System.Collections.Generic). Whenever it collision-enters one portal, check if the list is empty via inUseControllers.Count == 0, and if so, add both involved PortalController's to that list and handle the teleporting movement. When the ball collision-exits a PortalController, remove it from the inUseControllers list; it will thus only be emptied again once the ball leaves every portal zone.
This approach should simplify the code, yet safely protect against accidental back-and-forth circles.
I am trying to recreate a simple laser puzzle mechanic like seen in The Talos Principle - where i have a laser emitter that i can move and rotate, and when the beam (raycast and LineRenderer) hits a specific object, that object will become "active". However when the object is no longer being hit by a laser it should "deactivate".
I am having troubles with the Deactivate part. Is there a way to tell the object that it is no longer being hit by a raycast, or add a collider to the LineRenderer maybe? Or some third way to tell the object that it is no longer being hit by any lasers.
When your target is hit by a raycast, you could use the RaycastHit reference to acquire a script and update the cooldown.
Lets say we have RaySender and RayReceiver.
RaySenderScript
public class RaySenderScript{
RaycastHit Hit;
void FixedUpdate(){
//SendRaycast and store in 'Hit'.
if (Hit.collider != null)
{ //If raycast hit a collider, attempt to acquire its receiver script.
RayReceiverScript = Hit.collider.gameObject.GetComponent<RayReceiverScript>();
if (RayReceiverScript != null)
{ //if receiver script acquired, hit it.
RayReceiverScript.HitWithRay();
}
}
}
}
RayReceiverScript
public class RayReceiverScript{
public float HitByRayRefreshTime = 1f;
float RayRunsOutTime;
public bool IsHitByRay = false;
void Start()
{ //Initialize run out time.
RayRunsOut = Time.time;
}
void Update()
{
if (Time.time > RayRunsOutTime)
{ //check if time run out, if it has, no longer being hit by ray.
IsHitByRay = false;
}
}
public void HitWithRay(){ //method activated by ray sender when hitting this target.
IsHitByRay = true;
RayRunsOutTime = Time.time + HitByRayRefreshTime;
}
}
Sender strikes Receiver with a ray.
Sender has a reference to Receiver, it uses GetComponent() to access it. It can then say receiverScript.HitWithRay();
Receiver keeps checking if it no longer is receiving, if it isnt, it stops being hit by ray.
Add all objects hit by laser to collection and check if current target is in this collection. If it is not there, then it is "deactivated".
I'm having a bit of trouble getting the Vector3 wayPointPosition to my other script called Walking and changing it into the Transform target. My troubles lie in the fact that I'm trying to grab this dynamic variable from WayPointPositioner (it changes depending on what object is clicked in the stage and whether the player overlaps with this waypoint) and import and use it in another script.
Below is the code I'm using.
WayPointPositioner
using UnityEngine;
using System.Collections;
public class WayPointPositioner : MonoBehaviour {
public Vector3 wayPointPosition = Vector3.zero;
private bool checkPlayerWaypointCollision;
void Start()
{
}
void OnTriggerStay2D (Collider2D other)
{
// Check if collision is occuring with player character.
if (other.gameObject.name == "Player")
{
checkPlayerWaypointCollision = true;
}
else
{
checkPlayerWaypointCollision = false;
}
}
//Check if object is clicked
void OnMouseDown ()
{
// If its the player, then return a new position for the player to move to for walking
// Else debug that its not so
if (checkPlayerWaypointCollision == false)
{
Debug.Log ("Object not colliding and retrieving position");
Debug.Log (wayPointPosition);
Debug.Log (gameObject.name);
wayPointPosition = new Vector3 (transform.position.x, transform.position.y, 10);
wayPointPosition = Camera.main.ScreenToWorldPoint(wayPointPosition);
}
else
{
Debug.Log ("Object is colliding, no movement needed");
}
}
}
Walking
using UnityEngine;
using System.Collections;
public class Walking : MonoBehaviour {
public Transform target;
public WayPointPositioner wayPointPosition;
public bool walkingAnimation = false;
private Animator anim;
void Awake ()
{
anim = GetComponent<Animator> ();
wayPointPosition = GameObject.FindGameObjectWithTag ("Waypoint").GetComponent<WayPointPositioner> ();
}
void Start ()
{
}
void Update ()
{
Debug.Log ("This is in Walking, WPP =" + wayPointPosition);
}
}
As you can see I'm trying to import the wayPointPosition from the seperate class which is attached to the gameobjects called "Waypoint" (In my current layout those are empty objects with circle colliders to check if they have been clicked). However when I run this, I am not getting my Vector, but I'm getting the name of the last waypoint in the hierarchy (I have currently 6 waypoints which can be clicked) and not a Vector.
I hope someone is able to help me / point out my mistake. I'm still learning C# so I might've made a strange / odd assumption which isn't working.
Kind regards,
Veraduxxz.
It looks like invoking GameObject.FindGameObjectWithTag("Waypoint").GetComponent<WayPointPositioner>(); retrieves a component from the game object which matches the specified tag, as well as a type argument T which derives from MonoBehavior.
Calling this should actually give you an instance of your WayPointPositioner class, which you can then pass to whichever methods you want, and interact with its Vector3 however you would like.