Repeating graphics withing view - c#

In Unity2D, I have made a script to repeat the background sprite just before the camera can see the end.
Here's my code:
using UnityEngine;
using System.Collections;
[RequireComponent (typeof(SpriteRenderer))]
public class Tiling : MonoBehaviour {
public int offsetX = 2; // the offset so that we don't get any weird errors
// these are used for checking if we need to instantiate stuff
public bool hasARightBuddy = false;
public bool hasALeftBuddy = false;
public bool reverseScale = false; // used if the object is not tilable
private float spriteWidth = 0f; // the width of our element
private Camera cam;
private Transform myTransform;
private float localSc;
void Awake () {
cam = Camera.main;
myTransform = transform;
localSc = transform.localScale.x;
}
// Use this for initialization
void Start () {
SpriteRenderer sRenderer = GetComponent<SpriteRenderer>();
spriteWidth = sRenderer.sprite.bounds.size.x * transform.localScale.x;
}
// Update is called once per frame
void Update () {
// does it still need buddies? If not do nothing
if (hasALeftBuddy == false || hasARightBuddy == false) {
// calculate the cameras extend (half the width) of what the camera can see in world coordinates
float camHorizontalExtend = cam.orthographicSize * Screen.width/Screen.height;
// calculate the x position where the camera can see the edge of the sprite (element)
float edgeVisiblePositionRight = (myTransform.position.x + spriteWidth/2) - camHorizontalExtend;
float edgeVisiblePositionLeft = (myTransform.position.x - spriteWidth/2) + camHorizontalExtend;
// checking if we can see the edge of the element and then calling MakeNewBuddy if we can
if (cam.transform.position.x >= edgeVisiblePositionRight - offsetX && hasARightBuddy == false)
{
MakeNewBuddy (1);
hasARightBuddy = true;
}
else if (cam.transform.position.x <= edgeVisiblePositionLeft + offsetX && hasALeftBuddy == false)
{
MakeNewBuddy (-1);
hasALeftBuddy = true;
}
}
}
// a function that creates a buddy on the side required
void MakeNewBuddy (int rightOrLeft) {
// calculating the new position for our new buddy
Vector3 newPosition = new Vector3 (myTransform.position.x + spriteWidth * rightOrLeft, myTransform.position.y, myTransform.position.z);
// instantating our new body and storing him in a variable
Transform newBuddy = Instantiate (myTransform, newPosition, myTransform.rotation) as Transform;
newBuddy.parent = myTransform.parent;
// if not tilable let's reverse the x size on our object to get rid of missmatches
if (reverseScale == true) {
newBuddy.localScale = new Vector3 (localSc*-1 , 1, 1);
}
if (rightOrLeft == 1) { //if this function was called to make a right buddy (1)
newBuddy.GetComponent<Tiling>().hasALeftBuddy = true;
}
else { //else we just made a left buddy, so it has a right copy
newBuddy.GetComponent<Tiling>().hasARightBuddy = true;
}
}
Now, the script is attached to the background sprite and it works fine.
You'll see that there's a bool reverseScale to reverse the image.
This is because if the image is not repeatable, (the end and the start to not match on a pixel level) we can mirror it by reverting (* -1) the x scale.
The strange thing is, if I launch this with reverseScale disabled, eveything works as I said. If I enable reverseScale, it becomes a mess. Infinite loop of overlapping, badly scaled sprites, crashing the game.
What am I missing? Worst case (but still shouldn't happen), that code snippet should make an image that doesn't match, why is it breaking the program?
EDIT:
I found a solution thanks to Enfyve answer. I was flipping the scale of the whole graphic component instead oa single one for come reason. the flipX field should be used instead. Also, only one every two tiles has to be flipped, to avoid missmatches.

SpriteRenderer already contains a property to draw a sprite flipped, use flipX instead.

Related

How can I work with both scripts waypoints and teleporting together and how to make the teleporting to work better?

In the Waypoints script the script is attached to a character.
The character is waiting 10 seconds before he get into Walk state and start move between waypoints.
In the last waypoint the character is get into Idle state.
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.AI;
public class Waypoints : UnityEngine.MonoBehaviour
{
public List<GameObject> waypoints = new List<GameObject>();
public Animator _animator;
public int waitTimeBeforeGo;
public int num = 0;
public float minDist;
public float speed;
public bool rand = false;
public bool go = true;
public bool loop = false;
public bool waitTime = false;
public float smoothRotation;
public bool inIdle = false;
private bool lastPoint = false;
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
float dist = Vector3.Distance(gameObject.transform.position, waypoints[num].transform.position);
if (waitTime == true)
{
StartCoroutine(WaitBeforeGo());
}
else
{
if (go)
{
_animator.SetBool("Walk", true);
if (dist > minDist && lastPoint == false)
{
Move();
}
else
{
if (!rand)
{
if (num + 1 == waypoints.Count)
{
if (loop == true)
{
num = 0;
}
lastPoint = true;
_animator.SetBool("Idle", true);
inIdle = true;
}
else
{
num++;
}
}
else
{
num = Random.Range(0, waypoints.Count);
}
}
}
}
}
public void Move()
{
Quaternion lookOnLook =
Quaternion.LookRotation(waypoints[num].transform.position - transform.position);
transform.rotation =
Quaternion.Slerp(transform.rotation, lookOnLook, Time.deltaTime * smoothRotation);
gameObject.transform.position += gameObject.transform.forward * speed * Time.deltaTime;
}
IEnumerator WaitBeforeGo()
{
yield return new WaitForSeconds(waitTimeBeforeGo);
waitTime = false;
}
}
And now I have also a Teleporting script that is attached to a teleporting object.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class Teleporting : MonoBehaviour
{
public List<GameObject> teleporters = new List<GameObject>();
public GameObject objectToTeleportMaterial;
public Waypoints waypoints;
public float fadeDuration = 5;
public float fadeInTargetOpacity = 0;
public float fadeOutTargetOpacity = 1;
private List<Vector3> teleportersPositions = new List<Vector3>();
private bool teleported = false;
private Material material;
private GameObject myother;
// Start is called before the first frame update
void Start()
{
}
private void OnTriggerEnter(Collider other)
{
if (other.name == "vanguard_t_choonyung#T-Pose (1)")
{
teleported = false;
myother = other.gameObject;
material = objectToTeleportMaterial.GetComponent<Renderer>().material;
Teleport(material, fadeInTargetOpacity, fadeDuration);
}
}
// Update is called once per frame
void Update()
{
if(waypoints.inIdle == true)
{
material.ToTransparentMode();
}
if (teleported == true)
{
myother.transform.position = teleporters[1].transform.position;
Teleport(material, fadeOutTargetOpacity, fadeDuration);
material.ToOpaqueMode();
teleported = false;
}
}
private void Teleport(Material material, float fadeTargetOpacity, float fadeDuration)
{
StartCoroutine(FadeTo(material, fadeTargetOpacity, fadeDuration));
}
// Define an enumerator to perform our fading.
// Pass it the material to fade, the opacity to fade to (0 = transparent, 1 = opaque),
// and the number of seconds to fade over.
IEnumerator FadeTo(Material material, float targetOpacity, float duration)
{
// Cache the current color of the material, and its initiql opacity.
Color color = material.color;
float startOpacity = color.a;
// Track how many seconds we've been fading.
float t = 0;
while (t < duration)
{
// Step the fade forward one frame.
t += Time.deltaTime;
// Turn the time into an interpolation factor between 0 and 1.
float blend = Mathf.Clamp01(t / duration);
// Blend to the corresponding opacity between start & target.
color.a = Mathf.Lerp(startOpacity, targetOpacity, blend);
// Apply the resulting color to the material.
material.color = color;
// Wait one frame, and repeat.
yield return null;
}
if (targetOpacity == 1)
{
}
if (targetOpacity == 0)
{
teleported = true;
}
}
}
With this script I have some problems.
The character change to transparent mode in the material settins when start teleporting but after he teleported to the new position in the new position he never change back to the Opaque Mode in the material settings.
On the new position when the character start to fade in back for a millisecond the character show like in Opaque Mode but then he is gone and then start to fade in in the second teleporter but never change to Opaque Mode.
This class is a helper class for changing the material modes between Transparent and Opaque :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public static class MaterialExtensions
{
public static void ToOpaqueMode(this Material material)
{
material.SetOverrideTag("RenderType", "");
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
material.SetInt("_ZWrite", 1);
material.DisableKeyword("_ALPHATEST_ON");
material.DisableKeyword("_ALPHABLEND_ON");
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = -1;
}
public static void ToTransparentMode(this Material material)
{
material.SetOverrideTag("RenderType", "Transparent");
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
material.SetInt("_ZWrite", 0);
material.DisableKeyword("_ALPHATEST_ON");
material.EnableKeyword("_ALPHABLEND_ON");
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent;
}
}
What I want to do :
The character walk between the waypoints. When the character get to the last waypoint and get into the idle state wait 1-2 seconds before starting the teleporting.
When starting the teleporting the character should change to transparent mode and slowly fade out and change position.
In the new position the character should fade in slowly and then change slowly back to the Opaque Mode.
The character should start moving between new waypoints in his new position after teleported faded in and after changed back to Opaque Mode.
How can I make the character to wait 1-2 seconds in idle state before start teleporting ?
How can I make the character on his new position after faded in and after change to Opaque Mode to start moving between new waypoints ? I added waypoints in his new position.
How can I fix the problems with the teleporting script ? The teleporting it self is still not smooth. The first time the fade out part is fine but the fade in in the new position is not working good.
Some screenshot about the Transparent and Opaque modes.
The first screenshot showing the alpha on Transparent mode when alpha value is set to 255:
Even if the alpha value is 255 on Transparent mode the character is still looks transparent.
Transparent alpha color is set to 255
The second screenshot also Transparent this time al[ha is set to 0 :
Transparent mode alpha is set to 0 but still some of the character still show
I see now that even on Transparent when alpha is 0 you can see a bit yet of the character he is not vanished a bit of him still left.
Now a screenshot of the Opaque mode one screenshot since nothing change if alpha is 0 or 255 on Opaque mode:
Opaque mode alpha is at 255
So what have I learned from this screenshots and mode so far :
Transparent mode when alpha is set to 0 does not make the character to completely gone. It looks like from a bit far distance but if you looks close you see a bit of the character still left.
Transparent mode when alpha is set to 255 the character is not filled completely he still looks transparent.
Opaque mode when alpha is set to 0 or 255 same result the character is filled like he is in the original.
All this tests I did on the Standard shader.
Transparency issues
Do you use a custom shader? Because I think an easier solution would be to use a material with a standard shader, then you can set the Rendering Mode. Then you don't have to deal with setting properties in the shader. There is a mode specifically for fading in and out (BlendMode.Fade)
I believe it wont really make a difference (maybe a bit performance wise but not much) if you leave the material to be a material capable of transparency, it will still be opaque with an alpha of one so visually it wouldn't be a difference.
If you still want to switch between transparent and opaque rendering modes or use your extension methods, I would suggest putting the switch to opaque mode when the coroutine is finished:
if(targetOpacity == 1f) { material.ToOpaqueMode(); }
and not immediately after starting the coroutine.
Teleporting after x seconds
You could just write another coroutine / function that calls a function after x seconds
using only a coroutine
private IEnumerator TeleportAfter(float time, etc...) {
yield return new WaitForSeconds(time);
Teleport(...);
}
using a coroutine and a wrapper function (easier to call / doesn't need StartCoroutine)*
private IEnumerator TeleportAfter(float time, etc...) {
yield return new WaitForSeconds(time);
Teleport(...);
}
private void TeleportAfterSeconds(float time, etc...) {
StartCoroutine(TeleportAfter(time, etc...));
}

Why is my line renderer not showing up in Unity 2D

I am trying to create a Fruit Ninja style game on Unity 2D and I want to create a trail that follows where the player has "cut". I've tried to instantiate a "cut" object that contains the line renderer every time a user drags. However, the line renderer is not showing up. Can anyone correct any errors or suggest a new method?
public class CreateCuts : MonoBehaviour
{
public GameObject cut;
public float cutDestroyTime;
private bool dragging = false;
private Vector2 swipeStart;
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
dragging = true;
swipeStart = Camera.main.ScreenToWorldPoint(Input.mousePosition);
}
else if (Input.GetMouseButtonUp(0) && dragging)
{
createCut();
}
}
private void createCut()
{
this.dragging = false;
Vector2 swipeEnd = Camera.main.ScreenToWorldPoint(Input.mousePosition);
GameObject cut = Instantiate(this.cut, this.swipeStart, Quaternion.identity) as GameObject;
cut.GetComponent<LineRenderer>().positionCount = 1 ;
cut.GetComponent<LineRenderer>().enabled = true;
cut.GetComponent<LineRenderer>().SetPosition(0, this.swipeStart);
cut.GetComponent<LineRenderer>().SetPosition(1, swipeEnd);
Vector2[] colliderPoints = new Vector2[2];
colliderPoints[0] = new Vector2(0.0f, 0.0f);
colliderPoints[1] = swipeEnd - swipeStart;
cut.GetComponent<EdgeCollider2D>().points = colliderPoints;
Destroy(cut.gameObject, this.cutDestroyTime);
}
}
I expect there to be a line, but nothing shows up. There is also a warning stating that the SetPosition(1, swipeEnd) is out of bounds.
EDIT: Here are the settings of my cut object
1st part of cut object settings
2nd part of cut object settings
Positions tab of line renderer
I want to create a trail that follows where the player has "cut".
The word "trail" indicates that you should rather use a trail renderer!
Manual: https://docs.unity3d.com/Manual/class-TrailRenderer.html
API reference: https://docs.unity3d.com/ScriptReference/TrailRenderer.html
Back to your original question:
Your linerenderer probably is rendered but at a random position, because of Vector2 to Vector3 conversion, i dunno your project structure but this can be the case.
Please post a picture with one of your cut gameobject, that holds your linerenderer, and also extend the positions tab on the linerenderer so we can see your points xyz coordinates
Also apply the changes mentioned by commenters, because you really need 2 verticies for a line :P
Update:
public class CreateCuts : MonoBehaviour
{
public GameObject cut;
public float cutDestroyTime;
private bool dragging = false;
private Vector3 swipeStart;
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
dragging = true;
swipeStart = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Debug.Log("Swipe Start: " + swipeStart);
}
else if (Input.GetMouseButtonUp(0) && dragging)
{
createCut();
}
}
private void createCut()
{
this.dragging = false;
Vector3 swipeEnd = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Debug.Log("SwipeEnd: " + swipeEnd);
GameObject cut = Instantiate(this.cut, swipeStart, Quaternion.identity);
cut.GetComponent<LineRenderer>().positionCount = 2;
// why is it not enabled by default if you just instantiate the gameobject O.o?
cut.GetComponent<LineRenderer>().enabled = true;
cut.GetComponent<LineRenderer>().SetPositions(new Vector3[]{
new Vector3(swipeStart.x, swipeStart.y, 10),
new Vector3(swipeEnd.x, swipeEnd.y, 10)
// z is zero cos we are in 2d in unity up axis is Y we set it to 10 for visibility reasons tho}
});
// Commented out cos atm we are "debugging" your linerenderer
// Vector2[] colliderPoints = new Vector2[2];
// colliderPoints[0] = new Vector2(0.0f, 0.0f);
// colliderPoints[1] = swipeEnd - swipeStart;
// cut.GetComponent<EdgeCollider2D>().points = colliderPoints;
//Destroy(cut.gameObject, this.cutDestroyTime);
}
}

Unity2D - moving 2D object in a grid like, timed fashion

I have been trying to make this work for a while and I am failing.
I have a Rigidbody2D in a top down 2D level and I am trying to simply move it along the coordinates (the levels are gridlike) so one step / button press equals exactly one square walked. It should only be possible to walk in one of the four directions, and no matter when the user stops the walking motion, it should end on a square. A good game equivalent of what I want to achieve is the lufia / breath of fire / any similar RPG series. I've tried using coroutines to get the update function to wait one second after every step, but that does not seem to work. The closest I've come to is the code down below. Thanks guys!
public class PlayerMovement2D : MonoBehaviour
{
Rigidbody2D rbody;
Animator anim;
float speed = 1.25f;
Vector2 pos;
void Start()
{
rbody = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
pos = rbody.position;
}
void FixedUpdate()
{
Vector2 movement_vector = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
if (movement_vector.x != 0 || movement_vector.y != 0)
{
if (movement_vector.x > 0)
{
pos += Vector2.right;
pos.y = 0;
}
else if (movement_vector.x < 0)
{
pos += Vector2.left;
pos.y = 0;
}
else if (movement_vector.y > 0)
{
pos += Vector2.up;
pos.x = 0;
}
else if (movement_vector.y < 0)
{
pos += Vector2.down;
pos.x = 0;
}
anim.SetBool("IsWalking", true);
anim.SetFloat("Input_x", movement_vector.x);
anim.SetFloat("Input_y", movement_vector.y);
}
else
{
anim.SetBool("IsWalking", false);
}
rbody.position = Vector2.MoveTowards(rbody.position, pos, speed * Time.deltaTime);
//transform.position = Vector3.MoveTowards(transform.position, pos, Time.deltaTime * speed);
pos = rbody.position;
}
}
I think what you try to program is similar to the RogueLike game that is part of the tutorial collection in Unity website. Check first the intro video to confirm that is what you are planning to achieve
https://unity3d.com/es/learn/tutorials/projects/2d-roguelike-tutorial/project-introduction?playlist=17150
If so, this is how they handle it:
using UnityEngine;
using System.Collections;
//The abstract keyword enables you to create classes and class members that are incomplete and must be implemented in a derived class.
public abstract class MovingObject : MonoBehaviour
{
public float moveTime = 0.1f; //Time it will take object to move, in seconds.
public LayerMask blockingLayer; //Layer on which collision will be checked.
private BoxCollider2D boxCollider; //The BoxCollider2D component attached to this object.
private Rigidbody2D rb2D; //The Rigidbody2D component attached to this object.
private float inverseMoveTime; //Used to make movement more efficient.
//Protected, virtual functions can be overridden by inheriting classes.
protected virtual void Start ()
{
//Get a component reference to this object's BoxCollider2D
boxCollider = GetComponent <BoxCollider2D> ();
//Get a component reference to this object's Rigidbody2D
rb2D = GetComponent <Rigidbody2D> ();
//By storing the reciprocal of the move time we can use it by multiplying instead of dividing, this is more efficient.
inverseMoveTime = 1f / moveTime;
}
//Move returns true if it is able to move and false if not.
//Move takes parameters for x direction, y direction and a RaycastHit2D to check collision.
protected bool Move (int xDir, int yDir, out RaycastHit2D hit)
{
//Store start position to move from, based on objects current transform position.
Vector2 start = transform.position;
// Calculate end position based on the direction parameters passed in when calling Move.
Vector2 end = start + new Vector2 (xDir, yDir);
//Disable the boxCollider so that linecast doesn't hit this object's own collider.
boxCollider.enabled = false;
//Cast a line from start point to end point checking collision on blockingLayer.
hit = Physics2D.Linecast (start, end, blockingLayer);
//Re-enable boxCollider after linecast
boxCollider.enabled = true;
//Check if anything was hit
if(hit.transform == null)
{
//If nothing was hit, start SmoothMovement co-routine passing in the Vector2 end as destination
StartCoroutine (SmoothMovement (end));
//Return true to say that Move was successful
return true;
}
//If something was hit, return false, Move was unsuccesful.
return false;
}
//Co-routine for moving units from one space to next, takes a parameter end to specify where to move to.
protected IEnumerator SmoothMovement (Vector3 end)
{
//Calculate the remaining distance to move based on the square magnitude of the difference between current position and end parameter.
//Square magnitude is used instead of magnitude because it's computationally cheaper.
float sqrRemainingDistance = (transform.position - end).sqrMagnitude;
//While that distance is greater than a very small amount (Epsilon, almost zero):
while(sqrRemainingDistance > float.Epsilon)
{
//Find a new position proportionally closer to the end, based on the moveTime
Vector3 newPostion = Vector3.MoveTowards(rb2D.position, end, inverseMoveTime * Time.deltaTime);
//Call MovePosition on attached Rigidbody2D and move it to the calculated position.
rb2D.MovePosition (newPostion);
//Recalculate the remaining distance after moving.
sqrRemainingDistance = (transform.position - end).sqrMagnitude;
//Return and loop until sqrRemainingDistance is close enough to zero to end the function
yield return null;
}
}
//The virtual keyword means AttemptMove can be overridden by inheriting classes using the override keyword.
//AttemptMove takes a generic parameter T to specify the type of component we expect our unit to interact with if blocked (Player for Enemies, Wall for Player).
protected virtual void AttemptMove <T> (int xDir, int yDir)
where T : Component
{
//Hit will store whatever our linecast hits when Move is called.
RaycastHit2D hit;
//Set canMove to true if Move was successful, false if failed.
bool canMove = Move (xDir, yDir, out hit);
//Check if nothing was hit by linecast
if(hit.transform == null)
//If nothing was hit, return and don't execute further code.
return;
//Get a component reference to the component of type T attached to the object that was hit
T hitComponent = hit.transform.GetComponent <T> ();
//If canMove is false and hitComponent is not equal to null, meaning MovingObject is blocked and has hit something it can interact with.
if(!canMove && hitComponent != null)
//Call the OnCantMove function and pass it hitComponent as a parameter.
OnCantMove (hitComponent);
}
//The abstract modifier indicates that the thing being modified has a missing or incomplete implementation.
//OnCantMove will be overriden by functions in the inheriting classes.
protected abstract void OnCantMove <T> (T component)
where T : Component;
}
Link to this part of the tutorial:
https://unity3d.com/es/learn/tutorials/projects/2d-roguelike-tutorial/moving-object-script?playlist=17150
You can use Vector2.Lerp method in combination of Unity's Coroutine system.
public class Movement
: MonoBehaviour
{
IEnumerator m_MoveCoroutine;
float m_SpeedFactor;
void Update()
{
// if character is already moving, just return
if ( m_MoveCoroutine != null )
return;
// placeholder for the direction
Vector2 direction; // between { -1, -1 } and { 1, 1 }
// calculate your direction based on the input
// and set that direction to the direction variable
// eg. direction = new Vector2(Input.GetAxisRaw("Horizontal") > 0 ? 1 : -1,...)
// then check if direction is not { 0, 0 }
if( direction != Vector2.zero )
{
// start moving your character
m_MoveCoroutine = Move(direction);
StartCoroutine(m_MoveCoroutine);
}
}
IEnumerator Move(Vector2 direction)
{
// Lerp(Vector2 a, Vector2 b, float t);
Vector2 orgPos = transform.Position; // original position
Vector2 newPos = orgPos + direction; // new position after move is done
float t = 0; // placeholder to check if we're on the right spot
while( t < 1.0f ) // loop while player is not in the right spot
{
// calculate and set new position based on the deltaTime's value
transform.position = Vector2.Lerp(orgPos, newPos, (t += Time.deltaTime * m_SpeedFactor));
// wait for new frame
yield return new WaitForEndFrame();
}
// stop coroutine
StopCoroutine(m_MoveCoroutine);
// get rid of the reference to enable further movements
m_MoveCoroutine = null;
}
}
This method assumes that you can move in the specified direction. But you still should check if your new position is "walkable" before starting the Move Coroutine.
I think you want to do the movement in a coroutine that, while it is active, disabled further movement until it's done.
bool isIdle = true;
void Update() {
if(isIdle) {
// Your movement code that gives pos
StartCoroutine(Move(pos));
}
}
IEnumerator Move(Vector2 destination) {
isIdle = false;
do {
transform.position = Vector2.MoveTowards(transform.position, destination, speed * Time.deltaTime);
yield return new WaitForEndOfFrame();
} while (transform.position != destination);
isIdle = true;
}
Let me know if you don't understand, need further clarification or if this approach doesn't work!

How can i make the Invisible Walls script to work and take effect?

The idea is to make that when the player is walking to the edge of the terrain he will stop wont be able to continue and fall.
And in my case i want the objects that move forward when they collide with the invisible wall the object will turn lerp back and move to the other side of the invisible walls.
Another problem that might come up later i read about is that if the objects moving too fast to the invisible walls there is a bug that let them move through ? Not sure about it.
This is a screenshot showing the invisible walls. I created a box collider set the Is Trigger to be on and set the 500 600 500 same as the terrain size.
This is the script of the Invisible Walls: The script i attached it to the Terrain:
using UnityEngine;
using System.Collections;
public class InvisibleWalls : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
void OnTriggerExit(Collider other)
{
}
}
This is the script that create the space ships clone of them and make them move forward. But when they get to the edge of the terrain they just gone out. And i want them to lkerp/turn back to the other side.
This script is attached to the Spheres GameObject:
using System;
using UnityEngine;
using Random = UnityEngine.Random;
using System.Collections;
using System.Collections.Generic;
public class SphereBuilder : MonoBehaviour
{
public GameObject SpaceShip;
GameObject[] spheres;
public float moveSpeed = 50;
// for tracking properties change
private Vector3 _extents;
private int _sphereCount;
private float _sphereSize;
/// <summary>
/// How far to place spheres randomly.
/// </summary>
public Vector3 Extents;
/// <summary>
/// How many spheres wanted.
/// </summary>
public int SphereCount;
public float SphereSize;
private void Start()
{
spheres = GameObject.FindGameObjectsWithTag("MySphere");
}
private void OnValidate()
{
// prevent wrong values to be entered
Extents = new Vector3(Mathf.Max(0.0f, Extents.x), Mathf.Max(0.0f, Extents.y), Mathf.Max(0.0f, Extents.z));
SphereCount = Mathf.Max(0, SphereCount);
SphereSize = Mathf.Max(0.0f, SphereSize);
}
private void Reset()
{
Extents = new Vector3(250.0f, 20.0f, 250.0f);
SphereCount = 100;
SphereSize = 20.0f;
}
private void Update()
{
UpdateSpheres();
MoveShips ();
}
private void MoveShips()
{
foreach (Transform child in spheres[0].transform)
{
child.transform.position += Vector3.forward * Time.deltaTime * moveSpeed;
}
}
private void UpdateSpheres()
{
if (Extents == _extents && SphereCount == _sphereCount && Mathf.Approximately(SphereSize, _sphereSize))
return;
// cleanup
var spheres = GameObject.FindGameObjectsWithTag("Sphere");
foreach (var t in spheres)
{
if (Application.isEditor)
{
DestroyImmediate(t);
}
else
{
Destroy(t);
}
}
var withTag = GameObject.FindWithTag("Terrain");
if (withTag == null)
throw new InvalidOperationException("Terrain not found");
for (var i = 0; i < SphereCount; i++)
{
var o = Instantiate(SpaceShip);
o.tag = "Sphere";
o.transform.SetParent(gameObject.transform);
o.transform.localScale = new Vector3(SphereSize, SphereSize, SphereSize);
// get random position
var x = Random.Range(-Extents.x, Extents.x);
var y = Extents.y; // sphere altitude relative to terrain below
var z = Random.Range(-Extents.z, Extents.z);
// now send a ray down terrain to adjust Y according terrain below
var height = 10000.0f; // should be higher than highest terrain altitude
var origin = new Vector3(x, height, z);
var ray = new Ray(origin, Vector3.down);
RaycastHit hit;
var maxDistance = 20000.0f;
var nameToLayer = LayerMask.NameToLayer("Terrain");
var layerMask = 1 << nameToLayer;
if (Physics.Raycast(ray, out hit, maxDistance, layerMask))
{
var distance = hit.distance;
y = height - distance + y; // adjust
}
else
{
Debug.LogWarning("Terrain not hit, using default height !");
}
// place !
o.transform.position = new Vector3(x, y, z);
}
_extents = Extents;
_sphereCount = SphereCount;
_sphereSize = SphereSize;
}
}
And this is a small short video clip showing what happen to the space ships when they get to the terrain edge:
Spaceships video clip
Update what i did so far:
In top of script added:
public Terrain terrain;
private Vector3 boundLower;
private Vector3 boundUpper;
In Start function i added:
private void Start()
{
spheres = GameObject.FindGameObjectsWithTag("MySphere");
var withTag = GameObject.FindWithTag("Terrain");
if (withTag == null)
throw new InvalidOperationException("Terrain not found");
boundLower = terrain.transform.position - terrain.transform.size / 2;
boundUpper = terrain.transform.position + terrain.transform.size / 2;
}
But getting errors on both lines: size property not exist:
boundLower = terrain.transform.position - terrain.transform.size / 2;
boundUpper = terrain.transform.position + terrain.transform.size / 2;
And changed the MoveShips function to this:
private Vector3 direction = Vector3.forward;
private void MoveShips() {
foreach (var child in spheres) {
var pos = child.transform.position + direction * Time.deltaTime * moveSpeed;
pos.x = Mathf.Clamp(pos.x, boundLower.x, boundUpper.x);
pos.z = Mathf.Clamp(pos.z, boundLower.z, boundUpper.z);
if (pos.x == boundLower.x || pos.x == boundUpper.x) direction.x = - direction.x;
if (pos.z == boundLower.z || pos.z == boundUpper.z) direction.z = - direction.z;
child.transform.position = pos;
}
}
I would suggest modifying MoveShips() changing Vector3.forward to a variable and flipping it when bounds are reached:
private Vector3 direction = Vector3.forward;
private void MoveShips() {
foreach (var child in spheres) {
var pos = child.transform.position + direction * Time.deltaTime * moveSpeed;
pos.x = Mathf.Clamp(pos.x, boundLower.x, boundUpper.x);
pos.z = Mathf.Clamp(pos.z, boundLower.z, boundUpper.z);
if (pos.x == boundLower.x || pos.x == boundUpper.x) direction.x = - direction.x;
if (pos.z == boundLower.z || pos.z == boundUpper.z) direction.z = - direction.z;
child.transform.position = pos;
}
}
This will remove unnecessary dependence on object collision engine for such a simple thing. Note, how this is making all ships to change direction when furthest reaches the bound. If you want them to move separately, you will need to move this logic to a separate script and attach it to a ship prefab.
And the bounds (boundLower and boundUpper) can be set either as script variables in editor or calculated from terrain, like:
boundLower = terrain.transform.position - terrain.TerrainData.size / 2;
boundUpper = terrain.transform.position + terrain.TerrainData.size / 2;
I would also suggest to move this:
var withTag = GameObject.FindWithTag("Terrain");
if (withTag == null)
throw new InvalidOperationException("Terrain not found");
out of Update() into Start() unless you do something really funky with it in the process.
Lets start working though your problems one by one :
Question : The objects do not collide, why?
Answer : Objects do not collide from with-in the collider, only from the outside.
What you need in your case is 4 box collider, one at each edge of the map
Question : Another problem that might come up later i read about is that if the objects moving too fast to the invisible walls there is a bug that let them move through ? Not sure about it.
This is only a problem with objects moving at bullet-like speeds, you could edit the rigidbody to have detection mode : "continuous" or continuous-dynamic which will avoid this issue
The scripts, i do not think you would need them in this case, your original idea was good, just the implementation with a single collider over the whole terrain, instead of 4 seperate "wall" colliders, was the issue. As for the rest of the logic, i did not try decipher through that, thus i can not comment on it.

How to stop a method after it done in Update() function? - C# - Unity3D

Recently I'm making a chess game which has animation when the chess move. I used two method, RotateTowards and Translate(). RotateTowards() only run in Update() function. Here is my code:
using UnityEngine;
using System.Collections;
public class OnTouch : MonoBehaviour {
public GameObject cube;
public GameObject sphere;
int clicked;
Quaternion lastRot;
// Use this for initialization
void Start () {
clicked = 0;
}
// Update is called once per frame
void Update () {
if(clicked == 1)
{
var rotation = Quaternion.LookRotation(cube.transform.position - transform.position);
print(rotation);
rotation.x = 0;
rotation.z = 0;
cube.transform.rotation = Quaternion.RotateTowards(cube.transform.rotation, rotation, 100 * Time.deltaTime);
if(cube.transform.rotation = ???? <- No Idea){
clicked = 0;
}
}
}
void OnMouseDown()
{
print("clicked");
clicked = 1;
}
}
I attach that code to all chess tile. So, after the cube stop rotating, I tried to click another tile. But, the previous RotateTowards() method keep running, so it ran both. I've try to make IF logic, but I have no idea for
if(cube.transform.rotation == ??? <-- No idea){
clicked = 0;
}
Any solution? Thank you!
it will never reaches your final rotation, it will get very close though so you can check if the angle between your destination rotation and current rotation is smaller than a small degree then you can say it reached.
float DestAngle = 1.0f; //here we take 1 degree as the near enough angle
float angle = Quaternion.Angle(transform.rotation, target.rotation);//we calculate the angle between current rotaion and destination
if (Mathf.Abs (angle) <= DestAngle ) { //we reached }
if (cube.transform.rotation == rotation)

Categories

Resources