Hi!
After the discussion with Ruzihm in the comments. I've now created a simple version of my game to better ask the question I'm having.
The question now is, since I'm not able to manually create a connection to the testObject field in the inspector. How do I now tell Unity to use my instantiated objects while the game is running?
And is this a good solution for a RTS game that may have 100s of Units active at a time? The end goal here is to apply this force to a radius around the cursor. Which I was thinking of using Physics.OverlapSphere
Here's the minimal scenario of what I have:
New Unity scene
Attached the InputManager to the main camera.
Created a capsule and a plane.
Added ApplyForce to the Capsule
Created a prefab from the capsule and deleted it from the scene.
In the InputManager I added the ability to press space to Instantiate a capsule with the ApplyForce script attached..
Drag the capsule prefab to the InputManager "objectToGenerate"
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace GL.RTS.Mites
{
public class InputManager : MonoBehaviour
{
public GameObject testObject;
public ApplyForce onSpawnTest;
public GameObject objectToGenerate;
void Start()
{
onSpawnTest = testObject.GetComponent<ApplyForce>();
}
void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
Instantiate(objectToGenerate);
}
if (Input.GetMouseButton(0))
{
onSpawnTest.PushForward();
}
}
}
}
The ApplyForce script that I attach to the Capsule:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace GL.RTS.Mites
{
public class ApplyForce : MonoBehaviour
{
public float moveSpeed;
Rigidbody rb;
void Start()
{
rb = GetComponent<Rigidbody>();
Debug.Log("A Mite has spawned!");
}
public void PushForward()
{
rb.AddRelativeForce(Vector3.up * moveSpeed * Time.deltaTime);
Debug.Log("A force of: " + moveSpeed + " is being added.");
}
}
}
Well, you are creating your new instances of your object, but your input manager immediately forgets about them (note that you do nothing with the return value). The InputManager only knows about the ApplyForce that was created in its Start (and then interacts with it depending on mouse input) and your ApplyForce script knows nothing about any InputManager. So, it should come as no surprise that only the first instance reacts to the mouse input.
So, something has to be done to your InputManager and/or your ApplyForce. Your InputManager could remember the instances it creates (which isn't enough, because what if for example, a map trigger creates new player controllable units) or it could go looking for units each time.
Your ApplyForce could register with the InputManager when they are created, but then you would need to loop through the units and find out which ones are under the mouse, anyway.
Since you only want to select ones based on what is near or under your cursor and only when input occurs and not like every frame, I would go with the simplest approach, just letting your InputManager find the units when it needs them. Something like below, explanation in comments:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace GL.RTS.Mites
{
public class InputManager : MonoBehaviour
{
public GameObject testObject;
public ApplyForce onSpawnTest;
public GameObject objectToGenerate;
private Camera mainCam;
// which layers to consider for cursor detection
[SerializeField] LayerMask cursorLayerMask;
// how big for cursor detection
[SerializeField] float cursorRadius;
void Awake()
{
// cache main camera
mainCam = Camera.main;
}
void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
Instantiate(objectToGenerate);
}
if (Input.GetMouseButton(0))
{
Collider[] colls = FindCollidersUnderCursor();
// check each collider for an applyforce and use it if present
foreach( Collider coll in colls)
{
ApplyForce af = coll.GetComponent<ApplyForce>();
if (af != null)
{
af.PushForward();
}
}
}
}
Collider[] FindCollidersUnderCursor()
{
// find ray represented by cursor position on screen
// and find where it intersects with ground
// This technique is great for if your camera can change
// angle or distance from the playing field.
// It uses mathematical rays and plane, no physics
// calculations needed for this step. Very performant.
Ray cursorRay = mainCam.ScreenPointToRay(Input.mousePosition);
Plane groundPlane = new Plane(Vector3.up, Vector3.zero);
if (groundPlane.Raycast(cursorRay, out float cursorDist))
{
Vector3 worldPos = cursorRay.GetPoint(cursorDist);
// Check for triggers inside sphere that match layer mask
return Physics.OverlapSphere(worldPos, cursorRadius,
cursorLayerMask.value, QueryTriggerInteraction.Collide);
}
// if doesn't intersect with ground, return nothing
return new Collider[0];
}
}
}
Of course, this will require that every unit you're interested in manipulating has a trigger collider.
Related
So I made a script that should In theory make the Character Controller's Collider follow the Player camera. Here is the Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
public class CCCameraFollower : MonoBehaviour
{
public GameObject Camera;
public CharacterController character;
// Start is called before the first frame update
void Start()
{
character = GetComponent<CharacterController>();
}
// Update is called once per frame
void LateUpdate()
{
character.center = Camera.transform.position;
}
}
This works Fine/Okay when I try it out, however as soon as I enter Climb() In my Climber script:
void Climb()
{
InputDevices.GetDeviceAtXRNode(climbingHand.controllerNode)
.TryGetFeatureValue(CommonUsages.deviceVelocity, out Vector3 velocity);
character.Move(transform.rotation * -velocity * Time.fixedDeltaTime);
cachedVelocity = -velocity;
Debug.Log(cachedVelocity);
}
When this is Climb() Runs, this happens:
Image that Shows The Issue
I don't see a reason for this to happen, maybe its very obvious. I don't know... Anyways, my question is: "How do I make the Collider of the CC Follow the Player Camera?".
In the CCCameraFollower.LateUpdate() method you are actually moving the center of the character controller and not the position of the Camera. Change to this and it will probably work as you want.
void LateUpdate()
{
Camera.transform.position = character.center;
}
I am trying to make a zombie wave game and current have a Prefab for my enemies. If I have the prefab be in the scene when I hit run, they are attached to the NavMesh and track the player perfectly. I want to achieve this but with the enemy being spawned from an empty GameObject so I can get the waves spawning in. I have achieved them Spawning but they have the error,
"SetDestination" can only be called on an active agent that has been placed on a NavMesh.
UnityEngine.AI.NavMeshAgent:SetDestination(Vector3)
EnemyAI:Update() (at Assets/Scripts/EnemyAI.cs:25)
Here is my EnemyAI Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class EnemyAI : MonoBehaviour
{
public float lookRadius = 10f;
Transform target;
NavMeshAgent agent;
public GameObject Player;
void Start()
{
agent = GetComponent<NavMeshAgent>();
}
// Update is called once per frame
void Update()
{
float distance = Vector3.Distance(Player.transform.position, transform.position);
if (distance <= lookRadius)
{
agent.SetDestination(Player.transform.position);
}
}
}
And my spawning script, which is attached to an empty game object,
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Spawning : MonoBehaviour
{
public GameObject prefab;
public int CountofCubes;
private IEnumerator coroutine;
public float spawnRate;
IEnumerator Start()
{
while (true)
{
for (int i = 0; i < CountofCubes; i++)
{
Instantiate(prefab, new Vector3(Random.Range(-25.0f, 25.0f), 0.5f, Random.Range(-25.0f, 25.0f)), Quaternion.identity);
}
yield return new WaitForSeconds(spawnRate);
}
}
}
Any help would be great thanks!
I had the same issue and I don't have an explanation but only a workaround:
In the Start fucntion, I added:
navAgent = GetComponent<NavMeshAgent>();
navAgent.enabled = false;
// Invoke is used as a workaround for enabling NavMeshAgent on NavMeshSurface
Invoke("EnableNavMeshAgent", 0.025f);
And the EnableNavMeshAgent function is just :
private void EnableNavMeshAgent ()
{
navAgent.enabled = true;
}
If I set the invoke delay to a value less than 0.025 second the error keep going but for 0.025 I only have it twice and the behaviour is the one I wanted after that.
Some reasons this might happen:
The agent is not on level with any navmesh i.e. can be too far above or below. You want it to be "close to the NavMesh surface". Use raycasting on the floor to position the agent or add a rigidbody and drop it from above the floor. After you do this you might need to disable and enable the agent.
Moving the transform yourself rather than using Wrap function. There's property where you can check if the simulated and actual position are in sync.
Corrupted navmesh so you might need to re-bake it.
It is essentially trying to tell you your agent is not on the mesh so it cannot find a path. Try playing with where you're placing the agent.
I kind of remember running into a similar problem a while back and problem was I forgot to bake the NavMesh in Window > AI > Navigation. Just in case.
I'm making a third person sports game and I wish to shoot the ball straight forward from the player.
I have a working script for shooting the ball, however it is attached to the main camera so only shoots in the direction the camera is facing.
I would like to alter this code to attach it to the player instead of the camera.
PS: I am still very new to C#, I assume the problem is in the "camera.main.transform" section but I don't know the code to change it to player.
my code is below;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ShootScript : MonoBehaviour
{
public GameObject bulletPrefab;
public float shootSpeed = 300;
Transform cameraTransform;
void Start()
{
cameraTransform = camera.main.transform;
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Mouse0))
{
shootBullet();
}
}
void shootBullet()
{
//Get the Rigidbody that is attached to that instantiated bullet
Rigidbody projectile = GetComponent<Rigidbody>();
//Shoot the Bullet
projectile.velocity = cameraTransform.forward * shootSpeed;
}
}
First of all: You currently do GetComponent<Rigidbody> on that object itself .. this makes no sense since it would fire away "yourself" instead of a bullet. You have to Instantiate a bullet and apply forces/velocity here. You kind of missed the instantiation.
Then simply attach this script to your player Object instead and use transform.forward. transform returns the Transform component of the GameObject this script is attached to.
public class ShootScript : MonoBehaviour
{
// Hint: By making the prefab field Rigidbody you later can skip GetComponent
public Rigidbody bulletPrefab;
public float shootSpeed = 300;
void Update()
{
if (Input.GetKeyDown(KeyCode.Mouse0))
{
shootBullet();
}
}
void shootBullet()
{
// Instantiate a new bullet at the players position and rotation
// later you might want to add an offset here or
// use a dedicated spawn transform under the player
var projectile = Instantiate (bulletPrefab, transform.position, transform.rotation);
//Shoot the Bullet in the forward direction of the player
projectile.velocity = transform.forward * shootSpeed;
}
}
So, my boss moves in a specific direction when it detects the player. The problem I'm having is how to get the boss to move depending on where the player is within a certain proximity. So if the boss is on the player's left, he'll move to the left. If he's on the player's right, he'll move to the right. But I can't figure out how to make him react based on distance. Right now I'm just doing a Debug.Log to save a few seconds.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class phantom : MonoBehaviour {
private Rigidbody2D rb;
private Animator anim;
public Transform Target;
void Start ()
{
rb = GetComponent<Rigidbody2D> ();
anim = GetComponent<Animator> ();
}
void Update ()
{
if (transform.position.x > Target.position.x ) {
Debug.Log ("left");
}
if (transform.position.x < Target.position.x ) {
Debug.Log ("right");
}
}
}
You could use the Vector3.Distance method to determine the distance between your two object based on thier respective transform. That way, you can modify your boss's behavior according to his proximity to the player. The smaller the magnitude value is, the closer your two transforms are.
Ex:
int distanceYouWant;
if(Vector3.Distance(transform.position, Target.position).magnitude < distanceToDoStuff)
{
Debug.Log("Boss do stuff!");
}
Here is the link to the Unity scripting API doc: https://docs.unity3d.com/ScriptReference/Vector3.Distance.html
Hope this helps!
I figured it out. I just made the function not in Update but with an OnTriggerEnterStay2d (Collider2D other). Then I placed a trigger collider on the same GameObject and only when it detects the Target (the player) does the debug come up.
camera transform
I am trying to move my camera based on the players' movements on Y axis in Unity.
However, it does not work...
What did I do wrong? I have attached image of my script (C#) here.
and, Yes, I did attach this script with Main Camera.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraController : MonoBehaviour {
GameObject player;
// Use this for initialization
void Start () {
this.player = GameObject.Find("cat");
}
// Update is called once per frame
void Update () {
Vector3 playerPos = this.player.transform.position;
transform.position = new Vector3(
transform.position.x, playerPos.y, transform.position.z);
}
}
Make the player GameObject public and just drag and drop your player in the inspector in unity see if that works? Are you getting any exceptions? Also add Debug.Log (player.transform.position.ToString ()) to see if it is showing the right values. Are you sure you player object name is cat and not Cat, it is case sensitive. Check on those things and let me know if you figured it out!