The script is supposed to allow my first person controller/player go up to an object, press the E key and then pickup and carry the object around. There are errors in the script and I don't understand how to program yet. I've also attached the screenshot of the errors in the code for reference.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PickupObject : MonoBehaviour
{
GameObject mainCamera;
bool carrying;
GameObject carriedObject;
public float distance;
public float smooth;
// Start is called before the first frame update
void Start()
{
mainCamera = GameObject.FindWithTag("MainCamera");
}
// Update is called once per frame
void Update()
{
if (carrying)
{
carry(carriedObject);
checkDrop();
}
else
{
pickup();
}
}
void carry(GameObject o)
{
o.GetComponent<Rigidbody>().isKinematic = true;
o.transform.position = Vector3.Lerp (mainCamera.transform.position + mainCamera.transform.forward * distance, Time.deltaTime * smooth);
}
void pickup()
{
if (Input.GetKeyDown KeyCode.E;))
{
int x = Screen.width / 2;
int y = Screen.height / 2;
}
Ray ray = mainCamera.GetComponent<Camera>().ScreentPointToRay(new Vector3(x, y));
RaycastHit hit;
if(Physics.Raycast(ray, out hit))
{
Pickupable p = hit.collider.GetComponent<Pickupable>();
if(p != null)
{
carrying = true;
carriedObject = p.gameObject;
p.gameObject.rigidbody.isKinematic = true;
}
}
}
}
void checkDrop()
{
if(Input.GetKeyDown(KeyCode.E))
{
dropObject();
}
void dropObject()
{
carrying = false;
carriedObject = null;
carriedObject.gameObject.rigidbody.isKinematic = false;
}
}
}
Maybe you should try using Raycasts to pickup items. Create a "Pick Up" tag, add that tag to all pickup-able items, shoot a Raycast from the camera in the direction of the camera if the player presses 'E', check if the hit has the tag, then pick it up. Search up "Raycast tutorial" and you will find many results.
Within pickup you define int x and int y inside of an if block.
The second issue is that within (or after) the pickup method you have one closing } to much.
if (Input.GetKeyDown KeyCode.E;))
{
int x = Screen.width / 2;
int y = Screen.height / 2;
} // <-- SEEMS THAT THIS HERE IS YOUR PROBLEM !
So you basically end your class before the method checkDrop. The rest are just follow up errors: The x and y will be known only within this code block and when you later try to use them in
Ray ray = mainCamera.GetComponent<Camera>().ScreentPointToRay(new Vector3(x, y));
they do not exist.
Also as said you and the class so the method checkDrop is not known in Update. And then you get some additional errors anyway since it is not allowed to define a method outside of a type.
Note that I formatted your code so it should be quite clear now. You probably rather wanted it do to be
void pickup()
{
if (Input.GetKeyDown KeyCode.E;))
{
int x = Screen.width / 2;
int y = Screen.height / 2;
Ray ray = mainCamera.GetComponent<Camera>().ScreentPointToRay(new Vector3(x, y));
RaycastHit hit;
if(Physics.Raycast(ray, out hit))
{
Pickupable p = hit.collider.GetComponent<Pickupable>();
if(p != null)
{
carrying = true;
carriedObject = p.gameObject;
p.gameObject.rigidbody.isKinematic = true;
}
}
}
}
Related
I am working on a flocking system in Unity and am new to c#. I am working with 2 scripts - 1 that manages the overall flock (FlockTest) and the other that manages particle behaviour (FlockParticleBehaviour). I have followed a tutorial which has public boolean values that control seeking behaviour in FlockParticleBehaviour through FlockTest. In play mode, I can toggle these booleans to change the goal seeking behaviour. However, I want to automate this toggling based on time (To add it to an AR session). I have added an if statement to void Update() in the FlockTest and when I hit play, the seekGoal and obedient boolean boxes switch on and off but nothing happens to the particles. I have tried using an invoke method which didn't work(no errors but boxes dont switch on and off) and thought about trying a coRoutine but I am not sure this will work since I don't want to stop and start my script. I am at a loss as to how to get the particles obeying the boolean in update. Am I meant to be referencing in my particle behaviour script's flock function? Very new so would love some help if anyone knows a better way forward!
FlockTest script (contains if statement)
using System.Collections.Generic;
using UnityEngine;
public class FlockTest : MonoBehaviour
{
public GameObject[] particles;
public GameObject particlePrefab;
public int particleCount = 10;
public Vector3 range = new Vector3(5,5,5);
public Vector3 innerLimit = new Vector3(1,1,1);
public bool seekGoal = true;
public bool obedient = true;
public bool willful = false;
[Range(0, 200)]
public int neighbourDistance =50;
[Range(0,2)]
public float maxForce = 0.5f;
[Range(0,5)]
public float maxvelocity = 2.0f;
// Start is called before the first frame update
void Start()
{
int time = (int)Time.time;
particles = new GameObject[particleCount];
for(int i = 0; i < particleCount; i++)
{
Vector3 particlePos = new Vector3(Random.Range(-range.x, range.x), Random.Range(-range.y, range.y), Random.Range(-range.z, range.z));
particles[i] = Instantiate(particlePrefab, this.transform.position + particlePos, Quaternion.identity) as GameObject;
particles[i].GetComponent<FlockParticleBehaviour>().manager = this.gameObject;
}
}
void Update()
// the toggles in the inspector are changing but nothing is happening with the particles.
{
int time = (int)Time.time;
if(time == 3f) {
seekGoal = false;
obedient = false;
willful = true;
}
if(time == 6f)
{
seekGoal = true;
obedient = true;
willful = false;
}
}
}
FlockParticleBehaviour script
using System.Collections.Generic;
using UnityEngine;
public class FlockParticleBehaviour : MonoBehaviour
{
public GameObject manager;
public Vector3 location = Vector3.zero;
public Vector3 velocity;
Vector3 goalPos = Vector3.zero;
Vector3 currentForce; //this is a current force position. pushes particle around by adding all the other forces
// Start is called before the first frame update
void Start()
{
velocity = new Vector3(Random.Range(0.01f, 0.1f), Random.Range(0.01f, 0.1f), Random.Range(0.01f, 0.1f));
location = new Vector3(this.gameObject.transform.position.x, this.gameObject.transform.position.y, this.gameObject.transform.position.z);
}
Vector3 seek(Vector3 target)
{
return(target - location);
}
void applyForce(Vector3 f)
{
Vector3 force = new Vector3(f.x, f.y, f.z);
if(force.magnitude > manager.GetComponent<FlockTest>().maxForce)
{
force = force.normalized;
force *= manager.GetComponent<FlockTest>().maxForce;
}
this.GetComponent<Rigidbody>().AddForce(force);
if(this.GetComponent<Rigidbody>().velocity.magnitude > manager.GetComponent<FlockTest>().maxvelocity)
{
this.GetComponent<Rigidbody>().velocity = this.GetComponent<Rigidbody>().velocity.normalized;
this.GetComponent<Rigidbody>().velocity *= manager.GetComponent<FlockTest>().maxvelocity;
}
Debug.DrawRay(this.transform.position, force, Color.white);
}
Vector3 align()
{
float neighbourdist = manager.GetComponent<FlockTest>().neighbourDistance;
Vector3 sum = Vector3.zero;
int count = 0;
foreach (GameObject other in manager.GetComponent<FlockTest>().particles)
{
if(other == this.gameObject) continue;
float d = Vector3.Distance(location, other.GetComponent<FlockParticleBehaviour>().location);
if (d < neighbourdist) {
sum += other.GetComponent<FlockParticleBehaviour>().velocity;
count++;
}
}
if (count >0)
{
sum /= count;
Vector3 steer = sum - velocity;
return steer;
}
return Vector3.zero;
}
Vector3 cohesion()
{
float neighbourdist = manager.GetComponent<FlockTest>().neighbourDistance;
Vector3 sum = Vector3.zero;
int count = 0;
foreach (GameObject other in manager.GetComponent<FlockTest>().particles)
{
if(other == this.gameObject) continue;
float d = Vector3.Distance(location, other.GetComponent<FlockParticleBehaviour>().location);
if(d < neighbourdist)
{
sum += other.GetComponent<FlockParticleBehaviour>().location;
count++;
}
}
if (count > 0)
{
sum /= count;
return seek(sum);
}
return Vector3.zero;
}
void flock()
{
location = this.transform.position;
velocity = this.GetComponent<Rigidbody>().velocity;
if(manager.GetComponent<FlockTest>().obedient && Random.Range(0,50) <=1)
{
Vector3 ali = align();
Vector3 coh = cohesion();
Vector3 gl;
if(manager.GetComponent<FlockTest>().seekGoal)
{
gl = seek(goalPos);
currentForce = gl + ali +coh;
}
else
currentForce = ali + coh;
currentForce = currentForce.normalized;
}
if(manager.GetComponent<FlockTest>().willful && Random.Range(0,50)<=1)
{
if(Random.Range(0,50)<1) //change direction
currentForce = new Vector3(Random.Range(0.01f, 0.1f), Random.Range(0.01f, 0.1f),Random.Range(0.01f, 0.1f));
}
applyForce(currentForce);
}
// Update is called once per frame
void Update()
{
flock();
goalPos = manager.transform.position;
}
}
Several points:
it is much easier and cleaner to set your flock manager directly as FlockTest, not GameObject to avoid GetComponent calls.
I cannot understand what you want to achieve by calling (int)Time.time and comparing it later with 3 and 6. Time.time returns the number of seconds that passed from the start of the application. So your code in Update method of FlockTest script will not have any chance to be called after the seventh second of your game passed. So obedient will always be true and willful will always be false after the seventh second.
Your Random.Range(0, 50) <= 1 is quite a low chance. It will return an int value from 0 to 49, so it is only a 2% chance that your changes in FlockTest will apply to FlockParticleBehaviour instance. Is it what you wanted to get? You can try to remove this random from the if statement to make this chance 100% and check if this is an issue.
Right now it seems like the chance of changing something is too low to see it in several seconds of the game. As I've said above, after the seventh second your bool values will never change.
I have created a twin stick movement system for mobile. I have a character moving under the influence of one joystick correctly. The other joystick, the design I want is:
When the shoot JS is moved, look in that direction.
When the shoot JS is released, shoot in the last aimed direction.
What's happening is, the character shoots continuously when the game starts and if I move the ShootJS, the character spins in circles. I'm completely flummoxed as to why this is happening.
Here is my code. Thanks in advance to anybody for any help you provide.
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.AI;
using Ludiq;
using Bolt;
public class PlayerJSControlSc : MonoBehaviour
{
public GameObject player;
public NavMeshAgent nav;
public Text stateText;
public float moveSpeed;
public Animator animator;
public FloatingJoystick moveJS;
public FloatingJoystick shootJS;
public float rotationSpeed = 10;
public int ammo;
public int mag;
public Transform shotSpawn;
public GameObject bullet;
public float reloadTime;
public Text ammoCount;
[HideInInspector]
int currentMag;
// Start is called before the first frame update
void Start()
{
stateText.text = "";
nav = player.GetComponent<NavMeshAgent>();
animator = player.GetComponent<Animator>();
moveJS = GameObject.Find("Floating JS_Move").GetComponent<FloatingJoystick>();
shootJS = GameObject.Find("Floating JS_Shoot").GetComponent<FloatingJoystick>();
}
// Update is called once per frame
void Update()
{
movePlayer();
playerShoot();
ammoCount.text = currentMag+ "/" + ammo;
}
public void movePlayer()
{
//float x = Input.GetAxis("Horizontal");
//float y = Input.GetAxis("Vertical");
float x = moveJS.Horizontal;
float y = moveJS.Vertical;
nav.velocity = new Vector3(x * moveSpeed, 0, y * moveSpeed);
if (nav.velocity.x != 0 || nav.velocity.z != 0)
{ animator.SetBool("isRunning", true); }
else { animator.SetBool("isRunning", false); }
}
public void playerShoot()
{
bool isAiming = false;
float x = shootJS.Horizontal; float z = shootJS.Vertical;
if (x != 0 || z != 0)
{
isAiming = true;
/* Vector3 lookDir = new Vector3(x, 0, z);
Quaternion lookRotation = Quaternion.LookRotation(lookDir, Vector3.up);
float step = rotationSpeed * Time.deltaTime;
player.transform.rotation = Quaternion.RotateTowards(lookRotation, transform.rotation, step);*/
float myAngle = Mathf.Atan2(x, z) * Mathf.Rad2Deg;
float bodyRotation = myAngle + player.transform.eulerAngles.y;
player.transform.Rotate( 0,myAngle,0,Space.World);
}
else { shoot();isAiming = false; }
}
void shoot()
{
if (currentMag > 0)
{
Instantiate(bullet, shotSpawn.position, shotSpawn.rotation);
currentMag--;
}
else if (currentMag=0)
{
StartCoroutine(reload());
}
else
return;
}
IEnumerator reload()
{
ammo = ammo - mag;
currentMag = mag;
yield return new WaitForSeconds(reloadTime);
}
}
well you are calling every frame playerShoot() -> not moving -> shoot().
And in the same way also playerShoot() -> not moving -> player.transform.Rotate( 0,myAngle,0,Space.World);.
I think what you rather should do is
doing the shoot only in the very first frame where both inputs are 0
for the move rather set the rotation instead of increasing it
Something like
private bool lastFrameMoved;
public void playerShoot()
{
float x = shootJS.Horizontal;
float z = shootJS.Vertical;
if (x != 0 || z != 0)
{
var lookDir = new Vector3(x, 0, z);
var lookRotation = Quaternion.LookRotation(lookDir, Vector3.up);
player.transform.rotation = lookRotation;
lastFrameMoved = true;
}
else
{
if(lastFrameMoved)
{
shoot();
}
lastFrameMoved = false;
}
}
I solved the look rotation in Bolt (because it helps me visualize). I'll solve the shoot part soon. I'm using the wonderful Joystick Pack by Fenerax Studios.
https://assetstore.unity.com/publishers/32730
So the idea is to have a plane and grid placement system in augmented reality with the ability to place and move characters on grids. I already have an example for a mobile device, I have a script that generates grid and a script that allows me to place objects and it works just fine, however, I can't figure out how to use all of the above and if it's possible in AR. For example, I want to detect a plane then instantiate a level and put some objects on it.
Here's the script that is attached to the GridManager and is used to make a grid:
[SerializeField] private float size = 0.05f;
public Vector3 GetNearestPointOnGrid(Vector3 position)
{
position -= transform.position;
int xCount = Mathf.RoundToInt(position.x / size);
int yCount = Mathf.RoundToInt(position.y / size);
int zCount = Mathf.RoundToInt(position.z / size);
Vector3 result = new Vector3(
(float)xCount * size,
(float)yCount * size,
(float)zCount * size);
result += transform.position;
return result;
}
private void OnDrawGizmos()
{
Gizmos.color = Color.yellow;
for (float x = 0; x < 40; x += size)
{
for (float z = 0; z < 40; z += size)
{
var point = GetNearestPointOnGrid(new Vector3(x, 0f, z));
Gizmos.DrawSphere(point, 0.01f);
}
}
}
and here's the one that's attached to the PlacerManager and used to place objects on the grid:
private Grid grid;
private void Awake()
{
grid = FindObjectOfType<Grid>();
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
RaycastHit hitInfo;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hitInfo))
{
PlaceCubeNear(hitInfo.point);
}
}
}
private void PlaceCubeNear(Vector3 clickPoint)
{
var finalPosition = grid.GetNearestPointOnGrid(clickPoint);
GameObject.CreatePrimitive(PrimitiveType.Cube).transform.position = finalPosition;
}
You can use the raycast options to identify different objects
Raycasting and/or colliders are the way to go.
The sample scene in AR Foundation has a script called PlaceOnPlane.cs that shows how you can detect when a user touches the screen. E.g:
if (Input.touchCount == 1) {
if (m_RaycastManager.Raycast(Input.GetTouch(0).position, s_Hits, TrackableType.PlaneWithinPolygon))
{
// Raycast hits are sorted by distance, so the first one
// will be the closest hit.
var hitPose = s_Hits[0].pose;
if (spawnedObject == null)
{
spawnedObject = Instantiate(m_PlacedPrefab, hitPose.position, hitPose.rotation);
}
}
}
This allows you to get the screen touch position and then raycast from it to the real world scene. In this example, a game object is instantiated in that location. For your case, you can instantiated a level if your hit a plane or a plane exist around the hit position.
I have 2 problems I want to get through.
1st problem. Changing X,Y,Z movement and updating it.
I have a driving mechanic in my game where if the user presses wasd the car moves. The problem is that I want the speed to be reduced from 15f to 5f when S(backwards) is pressed and held until released.
Here is my code below of my player script.
Please refer to void playerwalk() and backwards motion()
public class Move : MonoBehaviour
{
public float speed;
private Rigidbody rb;
private int count;
public Text countText;
// Use this for initialization
void Start()
{
rb = GetComponent<Rigidbody>();
count = 0;
SetCountText();
}
void playerWalk()
{
var x = Input.GetAxis("Horizontal") * Time.deltaTime * 75f;
var z = Input.GetAxis("Vertical") * Time.deltaTime * 15f;
transform.Rotate(0, x, 0);
transform.Translate(0, 0, -z);
}
void backwardMotion()
{
if (Input.GetKeyDown(KeyCode.S))
{
var z = Input.GetAxis("Vertical") * Time.deltaTime * 5f;
}
}
void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag("Collectable"))
{
other.gameObject.SetActive(false);
count = count + 1;
SetCountText();
}
}
void SetCountText()
{
countText.text = "Count: " + count.ToString();
}
// Update is called once per frame
void Update ()
{
backwardMotion();
playerWalk();
}
}
This currently is not affecting anything and the void backwardsmotion() seems to have no effect.
Do I need a while loop?
What is the syntax for do until in C#?
2nd Problem : Do until loop?
To those of you who haven't seen my recent posts, I also have a flashlight mechanic on my car model where if you press L, the flashlights will turn on(toggle) and if you press S the backlights will as you reverse.
Here is my flashlight game mechanic code:
Please refer to Void switchoffLights()
public class headLights : MonoBehaviour
{
public Light frontlights;
public Light frontlights2;
public Light frontlights3;
public Light frontlights4;
public Light backlights;
public Light backlights2;
public Light backlights3;
public Light backlights4;
// Use this for initialization
void Start()
{
frontlights.enabled = !frontlights.enabled;
frontlights2.enabled = !frontlights2.enabled;
frontlights3.enabled = !frontlights3.enabled;
frontlights4.enabled = !frontlights4.enabled;
}
void switchoffLight()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.L))
{
if (frontlights && frontlights2 && frontlights3 && frontlights4 != null)
{
frontlights.enabled = !frontlights.enabled;
frontlights2.enabled = !frontlights2.enabled;
frontlights3.enabled = !frontlights3.enabled;
frontlights4.enabled = !frontlights4.enabled;
}
}
if (Input.GetKeyDown(KeyCode.S))
{
if (backlights && backlights2 && backlights3 && backlights4 != null)
{
backlights.enabled = !backlights.enabled;
backlights2.enabled = !backlights2.enabled;
backlights3.enabled = !backlights3.enabled;
backlights4.enabled = !backlights4.enabled;
}
}
}
}
However the backlights do not turn off when the S key is released.
I could not clearly understand what was going on with other codes and answers which is why I am asking here.
Thank you in advance! I am only just starting for my grade 10 programming class so please explain like you would to a younger person.
You are not applying any change to transform or rotation when pressing
if (Input.GetKeyDown(KeyCode.S))
{var z = Input.GetAxis("Vertical") * Time.deltaTime * 5f;
}
// Where this z go ?????
// Add this or whatever you want "transform.Translate(0, 0, -z);"
For Second question Use GetKeyUp() in another condition or use getkey in same condition
For the first question, you declared var z twice but they are actually individual values.
var z = Input.GetAxis("Vertical") * Time.deltaTime * 15f; // playerWalk()
and
var z = Input.GetAxis("Vertical") * Time.deltaTime * 5f; // backwardMotion()
refer to two different variables with the same name z but each only live in the method it is declared in. The z in playerWalk() and the z in backwardMotion() has nothing to do with each other. The z in backwardMotion() essentially serves no purpose as the variable is declared but never used anywhere in the method.
The correct way to achieve what you want will be:
void playerWalk()
{
var x = Input.GetAxis("Horizontal") * Time.deltaTime * 75f;
var z = Input.GetAxis("Vertical") * Time.deltaTime * 15f;
if (Input.GetKeyDown(KeyCode.S))
{
// If "S" is pressed, the z speed would be reduced to 5f.
z = Input.GetAxis("Vertical") * Time.deltaTime * 5f;
}
transform.Rotate(0, x, 0);
transform.Translate(0, 0, -z);
}
For the second question, there are three way to get input: Input.GetKeyDown, Input.GetKey and Input.GetKeyUp.
Input.GetKeyDown is called only once on the frame you pressed the key.
Input.GetKey is called every frame the key is held down.
Input.GetKeyUp is called only once on the frame you released the key.
In your case, your car backlight would be turned on when it detects a Input.GetKeyDown(KeyCode.S) but nothing lets it turn back off when it detects a release.
The solution would be to simply check for a release and turn off the headlight:
if (Input.GetKeyUp(KeyCode.S)) // Detects a release of "S" key
{
if (backlights && backlights2 && backlights3 && backlights4)
{
backlights.enabled = false;
backlights2.enabled = false;
backlights3.enabled = false;
backlights4.enabled = false;
}
}
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.