Area does not teleport to the right location in godot (c#) - c#

I just started working on a small, single player 2D shooter game. And obviously, it has collectibles like guns. I created a pickable gun, which is a pistol, and coded it so whenever the player touches it, it teleports to the players hand. For some reason, it isn't working
I tried out different coordinates, but it still teleports away from the player (currently 0,0 for testing).
Here's the code:
using Godot;
using System;
public class Pistol : Area2D
{
[Export] public int speed = 200;
public Vector2 playerPosition;
public override void _Ready()
{
var detect = new Character();
playerPosition = detect.characterPosition;
initialPosition = this.Position;
}
public override void _PhysicsProcess(float delta)
{
//* Making the pistol move
var motion = new Vector2();
motion.x = Input.GetActionStrength("ui_left") - Input.GetActionStrength("ui_right");
motion.y = Input.GetActionStrength("ui_up") - Input.GetActionStrength("ui_down");
if (Input.IsActionPressed("ui_right") || Input.IsActionPressed("ui_left"))
{
MoveLocalX(motion.x * speed * delta);
}
if (Input.IsActionPressed("ui_up") || Input.IsActionPressed("ui_down"))
{
MoveLocalY(motion.y * speed * delta);
}
}
private void _on_Pistol_body_entered(object body)
{
this.Position = new Vector2(0, 0);
}
}
Update: the sprite was dislocated from the Area2D pistol.

Ignoring why you want to teleport the collectible, and why the collectible moves on input, and… It is wtf there, but ignoring all that…
The Position is relative to the parent. So Vector2(0, 0) is the position of the parent. You want to work with GlobalPositions instead.
Something like this:
var target = body as Spatial;
if (Object.IsInstanceValid(target))
{
this.GlobalPosition = target.GlobalPosition;
}
You can be more specific and use the player character class (assuming it is called PlayerCharacter here):
var target = body as PlayerCharacter;
if (Object.IsInstanceValid(target))
{
this.GlobalPosition = target.GlobalPosition;
}
You probably want to add a Position2D as child of the player so you can tweak the position for the gun (assuming it is called "GunPosition" here). So you would do something like this:
var target = body as PlayerCharacter;
if (Object.IsInstanceValid(target))
{
this.GlobalPosition = target.GetNode<Position2D>("GunPosition").GlobalPosition;
}

Related

How to change a variable in a shared script, without affecting all the other game objects that share that script?

Ive been at it all day trying to solve this, because I dont want to make a separate script for every bullet, instead I want to have a single EnemyBulletMovement script, that can move each bullet in a different way, depending on the BulletType int that I feed into it when I instantiate it.
The problem is that if I fire multiple bullets at the same time, they will all change BulletType as soon as another bullet is instantiated, because they are all sharing the same script.
My goal is to have all bullets have a private script, but no matter what I try the variable will still be changed for all active bullets every time I try to change it for just one of them.
EnemyController script that Instantiates the bullets and gives them a BulletType value:
GameObject enemyBullet = ObjectPooler.SharedInstance.GetPooledEnemyBullet();
if (enemyBullet != null)
{
Quaternion rotationAmount = Quaternion.Euler(0, 0, 0);
Quaternion postRotation = transform.rotation * rotationAmount;
enemyBullet.transform.position = transform.position;
enemyBullet.transform.rotation = postRotation;
enemyBullet.SetActive(true);
enemyBullet.GetComponent<EnemyBulletMovement>().SetBullet(2);
}
GameObject enemyBullet2 = ObjectPooler.SharedInstance.GetPooledEnemyBullet();
if (enemyBullet2 != null)
{
Quaternion rotationAmount = Quaternion.Euler(0, 3, 0);
Quaternion postRotation = transform.rotation * rotationAmount;
enemyBullet2.transform.position = transform.position;
enemyBullet2.transform.rotation = postRotation;
enemyBullet2.SetActive(true);
enemyBullet2.GetComponent<EnemyBulletMovement>().SetBullet(0);
}
Shared EnemyBulletMotion script, that gets the bulletType from the above script. This is the problem:
public void SetBullet(int typeIndex)
{
bulletType = typeIndex;
Debug.Log(bulletType);
}
private void BulletMotion()
{
if (bulletType == 0)
{
rb.velocity = pos;
}
if (bulletType == 1)
{
axis = Mathf.Sin(Time.deltaTime * -frequency) * magnitude * transform.up; // Up down Wave motion
transform.Rotate(Vector3.forward * sideMag); // Side Sway Motion (+ Wave = Spiral Motion)
rb.velocity = pos + axis; // Combine all Motions
}
if (bulletType == 2)
{
Debug.Log("Shootin");
axis = Mathf.Sin(Time.deltaTime * -frequency) * magnitude * transform.up; // Up down Wave motion
transform.Rotate(Vector3.forward * -sideMag); // Side Sway Motion (+ Wave = Spiral Motion)
rb.velocity = pos + axis; // Combine all Motions
}
}
EDIT: This Debug Shootin never appears in my console though, so maybe the bulletType is not being read correctly here after all? For Clarification, the first Debug.Log(bulletType); return a 0 or a 2 so it is working. But the second Debug.Log("Shootin"); does not get printed, ever.
I tried private, static, and made an Instance out of the whole EnemyBulletMovement script but nothing works.
When I debug this code, the script works, the bulletType will change from 2 to 0 and back, but when it does it will change both bullets, so both will fly the same way, when what I want is to have each bullet follow its own motion. If there is no way to do this I will have to create a separate script for each bulletmotion, but im only showing 2 here and I already have a lot of them, thats why I wanted to try this out and make it work with if statements and then use case statements later.
EDIT: Added ObjectPooler script below.
public List<GameObject> pooledEnemyBullets;
public GameObject EnemyBulletToPool;
public int EnemyBulletAmountToPool;
pooledEnemyBullets = new List<GameObject>();
public static ObjectPooler SharedInstance;
void Awake()
{
SharedInstance = this;
}
void Start()
{
for (int i = 0; i < EnemyBulletAmountToPool; i++)
{
GameObject obj3 = (GameObject)Instantiate(EnemyBulletToPool);
obj3.SetActive(false);
pooledEnemyBullets.Add(obj3);
}
}
public GameObject GetPooledEnemyBullet()
{
//1
for (int i = 0; i < pooledEnemyBullets.Count; i++)
{
//2
if (!pooledEnemyBullets[i].activeInHierarchy)
{
return pooledEnemyBullets[i];
}
}
//3
return null;
}
}
Bullet type should be declared as private int bulletType. If you declare bullet type as private static int bulletType it will be same for all bullets.
If it is already declared as private int bulletType, then you should check the logic where you use object pooler. You may be processing the same bullet over and over, or processing all bullets in the pool.

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);
}
}

Unity 2017.3: transform.position assign attempt is not valid. Input position is { -infinity, 0,0 }

I've been trying to build a raycast controller for a 2D game, and have followed some tutorial series to help me better understand how this is done. So far everything has been going well, and I've actually followed several tutorials on this topic to completion without issue.
However, while using this current character controller I am getting a slew of errors on play, even with no compiler errors present. I have never seen this error, and have not been able to find a solution online about how to fix it. Basically my character disappears on play (I assume its being moved infinitely to the left).
I get two errors: "transform.position assign attempt is not valid. Input position is { -infinity, 0,0 }," and "Invalid world AABB. Object is too large or too far away from origin."
The error occurs on lines 90:
public void LateUpdate()
{
Move(Velocity * Time.deltaTime);
}
and 126:
characterTransform.Translate(deltaMovement, Space.World);
I have tried a bunch of different things, including changing my character's scale, changing Vector2s to Vector3s, removing Space.World, and calling transform.Translate directly (as opposed to using my characterTransform). Nothing seems to work, and for some reason my camera gives me an error sometimes too, though it is hard to reproduce. If I remove either line of code I get no errors, but obviously my character cannot move.
Screenshot of the errors: https://imgur.com/a/24KIN
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Handles the character movement with raycasts
/// </summary>
public class CharacterController2D : MonoBehaviour {
//Defines the number of rays
private const int totalHorizontalRays = 8;
private const int totalVeritcalRays = 4;
private RaycastOrigins raycastOrigins; //References the RayCastOrigins struct
private const float skinWidth = .02f; //Defines the skin width of the rays, which places the origin point of the rays slightly inside the character's box collider
private static readonly float slopeLimitTanget = Mathf.Tan(75f * Mathf.Deg2Rad);
public LayerMask platformMask; //Defines the layermask that will be used to determine how different layers interact with the character
public ControllerParameters2D defaultParameters; //References the ControllerParameters2D class
public ControllerState2D State { get; private set; } //References the ControllerState2D class
public ControllerParameters2D Parameters { get { return overrideParameters ?? defaultParameters; } } //Returns updated parameters, or default parameters if overrideParameters is null
public Vector2 Velocity { get { return velocity; } } //Defines the character's velocity
public bool CanJump { get { return false; } } //Defines whether or not the character can jump
public bool HandleCollisions { get; set; } //Defines whether or not the character needs to handle collisions (because it is colliding with something)
private Vector2 velocity; //The field for the Velocity property
private BoxCollider2D boxCollider; //References the box collider on the character
private ControllerParameters2D overrideParameters; //References the updated parameters (that is, the updated parameters, not the default ones)
private Transform characterTransform; //References the character's transform
private Vector3 localScale; //References the character's scale
//The distance between raycasts
private float horizontalDistanceBetweenRays;
private float verticalDistanceBetweenRays;
private struct RaycastOrigins //Stores the value types that define where the raycasts are created on the box collider
{
public Vector2 topLeft, topRight; //Creates variables to define the upper position of the raycasts
public Vector2 bottomLeft, bottomRight; //Creates variables to define the lower position of the raycasts
}
public void Awake()
{
State = new ControllerState2D(); //Accesses the ControllerState2D script
boxCollider = GetComponent<BoxCollider2D>(); //Accesses the character's box collider
characterTransform = transform; //Accesses the character's transform
localScale = transform.localScale; //Accesses the character's scale
//Gets the ray spacing
horizontalDistanceBetweenRays = CalculateHorizontalRaySpacing();
verticalDistanceBetweenRays = CalculateVerticalRaySpacing();
}
public void Start()
{
}
public void AddForce(Vector2 force)
{
velocity += force;
}
public void SetForce(Vector2 force)
{
velocity = force;
}
public void SetHorizontalForce(float x)
{
velocity.x = x;
}
public void SetVerticalForce(float y)
{
velocity.y = y;
}
public void Jump()
{
}
public void LateUpdate()
{
Move(Velocity * Time.deltaTime); //Moves the character per its velocity, scaled by time
}
public void OnTriggerEnter2D(Collider2D other)
{
}
public void OnTriggerExit2D(Collider2D other)
{
}
private void Move(Vector2 deltaMovement)
{
var wasGrounded = State.IsCollidingBelow; //Keeps track of whether or not the character is grounded
State.Reset(); //Resets the state
if (HandleCollisions) //If the character should handle collisions
{
HandlePlatforms();
CalculateRaycastOrigins();
if (deltaMovement.y < 0 && wasGrounded) //If the character is moving down, and was previously grounded...
{
ClimbDownSlope(ref deltaMovement);
}
if (Mathf.Abs(deltaMovement.x) > .001f) //If the character is moving left or right...
{
MoveHorizontally(ref deltaMovement);
}
MoveVertically(ref deltaMovement); //Calls the MoveVertically method always, since the character always has the force of gravity enacted on it
}
characterTransform.Translate(deltaMovement, Space.World); //Moves the character after all potential movement scenarios have been accounted for
if (Time.deltaTime > 0)
{
velocity = deltaMovement / Time.deltaTime; //Sets the current velocity equal to the change in movement
}
//Clamps the velocity to the maximum x and y velocity defined in Parameters
velocity.x = Mathf.Min(velocity.x, Parameters.maxVelocity.x);
velocity.y = Mathf.Min(velocity.y, Parameters.maxVelocity.y);
if (State.IsMovingUpSlope) //If the character is moving up a slope...
{
velocity.y = 0;
}
}
private void MoveHorizontally(ref Vector2 deltaMovement)
{
var isGoingRight = deltaMovement.x > 0; //Defines if the character is going right
var rayDistance = Mathf.Abs(deltaMovement.x) + skinWidth; //Defines the distance of the raycasts
var rayDirection = isGoingRight ? Vector2.right : -Vector2.right; //Defines in which direction the rays will shoot, depdending on character direction
var rayOrigin = isGoingRight ? raycastOrigins.bottomRight : raycastOrigins.bottomLeft; //Defines the current ray origin
for (var i = 0; i < totalHorizontalRays; i++) //Loops through each of the 8 horizontal rays
{
var rayVector = new Vector2(rayOrigin.x, rayOrigin.y + (i * verticalDistanceBetweenRays)); //Builds the rays (stacking them up with each ray that is added)
Debug.DrawRay(rayVector, rayDirection * rayDistance, Color.red);
var rayCastHit = Physics2D.Raycast(rayVector, rayDirection, rayDistance, platformMask); //Actually draws the rays
if (!rayCastHit) //If the raycast hits something... (rayCastHit is true)
{
continue;
}
if (i == 0 && ClimbUpSlope(ref deltaMovement, Vector2.Angle(rayCastHit.normal, Vector2.up), isGoingRight)) //If the character is now climbing a slope...
{
break;
}
deltaMovement.x = rayCastHit.point.x - rayVector.x; //Clamps horizontal movement based on ray collision
rayDistance = Mathf.Abs(deltaMovement.x); //Clamps the ray distance based on how far the character is allowed to move (i.e. ensures the rays end at walls)
if (isGoingRight) //If the character is going right...
{
deltaMovement.x -= skinWidth; //Ensures that the character moves with the correct value (otherwise would be able to move slightly more based on skinWidth value)
State.IsCollidingRight = true;
}
else //If the character is going left...
{
deltaMovement.x += skinWidth;
State.IsCollidingLeft = true;
}
if (rayDistance < skinWidth + .0001f) //If a collision gets bugged for some reason...
{
break;
}
}
}
private void MoveVertically(ref Vector2 deltaMovement)
{
}
private void ClimbDownSlope(ref Vector2 deltaMovement)
{
}
private bool ClimbUpSlope(ref Vector2 deltaMovement, float angle, bool isGoingRight)
{
return false;
}
private void HandlePlatforms()
{
}
private float CalculateHorizontalRaySpacing()
{
Bounds bounds = boxCollider.bounds; //Sets the 'bounds' variable equal to the bounds of the box collider on the game object
bounds.Expand(skinWidth * -2); //Enforces the skinWidth variable
return bounds.size.y / (totalHorizontalRays - 1); //Ensures that all rays are spaced evenly on the sides of the box collider
}
private float CalculateVerticalRaySpacing()
{
Bounds bounds = boxCollider.bounds; //Sets the 'bounds' variable equal to the bounds of the box collider on the game object
bounds.Expand(skinWidth * -2); //Enforces the skinWidth variable
return bounds.size.x / (totalVeritcalRays - 1); //Ensures that all rays are spaced evenly on the bottom and top of the box collider
}
private void CalculateRaycastOrigins()
{
Bounds bounds = boxCollider.bounds; //Sets the 'bounds' variable equal to the bounds of the box collider on the game object
bounds.Expand(skinWidth * -2); //Enforces the skinWidth variable
//Creates the starting positions of the raycasts
raycastOrigins.bottomLeft = new Vector2(bounds.min.x, bounds.min.y);
raycastOrigins.bottomRight = new Vector2(bounds.max.x, bounds.min.y);
raycastOrigins.topLeft = new Vector2(bounds.min.x, bounds.max.y);
raycastOrigins.topRight = new Vector2(bounds.max.x, bounds.max.y);
}
}
After doing some research, it seems this is a bug that can sometimes occur.
Not sure why I didn't try this sooner, but reinstalling Unity fixed all the issues...

Repeating graphics withing view

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.

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.

Categories

Resources