¡Hi! I'm having a problem trying to move an object that I instantiated. What I want is that when the object appears, it "jumps" towards the character to get it using this DOTween command:
DOJump(Vector3 endValue, float jumpPower, int numJumps, float duration, bool snapping).
I'm looking for an effect similar to this:
https://youtu.be/Mk4R241AZxk?t=786
The current code:
void Update()
{
if (Input.GetMouseButtonDown(0))
{
StartCoroutine(SpawnItem(0.5f, 0.8f));
}
}
IEnumerator SpawnItem(float delayStart, float delayDisplay)
{
Transform item = poolItems.GetRandom();
Instantiate(item, new Vector3(-21.09f, 5.43f, -17.55f), Quaternion.Euler(new Vector3(0, 127.29f, 0)));
archeologistAnimator.SetBool("Action", true);
sarcophagusAnimator.SetBool("SarcophagusOpen", true);
yield return new WaitForSeconds(delayDisplay);
item.transform.DOJump(new Vector3(-5.22f, 2.44f, 40.4f), 2, 1, 5);
}
"poolItems" is another script file where I call the functions that allows me to list prefab objects and get one at random. The numbers in vector 3 are coordinates of where I want the object to be instantiated in the scene and where I want it to jump to, respectively.
But it doesn't do anything nor does the console give me an error. Any ideas?
You are changing the transform of your original item, not your newly created instance.
I have not tested this and only changed the needed lines but it should be doing what you want. You could add your directly to the Dotween sequence instead of using the yield return.
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
StartCoroutine(SpawnItem(0.5f, 0.8f));
}
}
private IEnumerator SpawnItem(float delayStart, float delayDisplay)
{
Transform item = poolItems.GetRandom();
var itemInstance = Instantiate(item, new Vector3(-21.09f, 5.43f, -17.55f), Quaternion.Euler(new Vector3(0, 127.29f, 0)));
archeologistAnimator.SetBool("Action", true);
sarcophagusAnimator.SetBool("SarcophagusOpen", true);
yield return new WaitForSeconds(delayDisplay);
itemInstance.transform.DOJump(new Vector3(-5.22f, 2.44f, 40.4f), 2, 1, 5);
}
Two tips:
Avoid magic numbers and give more context.
It would be hard for someone else to reproduce since your poolItems class is unknown and would need a stand in.
Related
In my game I have a game object called ExclamationMark which I want to spawn above enemies heads when the player gets into range and they become "Alerted".
I've made this simple script to do that, but for some reason it will only work on one game object.
My enemy script:
void CheckForPlayer()
{
// Define player and get position
var player = GameObject.FindWithTag("Player");
var playerPos = (int)player.transform.position.x;
if (transform.Find("Graphics"))
{
// Define gameobject position
var enemyPos = transform.Find("Graphics").gameObject.transform.position.x;
// Define range to spawn tiles in
var range = 5;
var rangeInfront = enemyPos + range;
var rangeBehind = enemyPos - range;
if (playerPos >= rangeBehind && playerPos <= rangeInfront)
{
enemyIsActive = true;
if (transform.Find("ExclamationMark"))
{
var exMark = transform.Find("ExclamationMark").gameObject.GetComponent<ExclamationMarkSpawn>();
exMark.SpawnExclamationMark();
}
}
else
{
enemyIsActive = false;
}
}
}
My ! script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ExclamationMarkSpawn : MonoBehaviour {
public GameObject spawnPos;
public GameObject exclamationMark;
public GameObject exclamationMarkAudio;
public void SpawnExclamationMark()
{
StartCoroutine(GameObject.FindGameObjectWithTag("MainCamera").GetComponent<CameraShake>().Shake(0.2f, 0.2f, 0.2f));
Instantiate(exclamationMark, spawnPos.transform.position, Quaternion.identity);
if (exclamationMarkAudio)
Instantiate(exclamationMarkAudio, spawnPos.transform.position, Quaternion.identity);
StartCoroutine(DestroyExclamationMark());
}
IEnumerator DestroyExclamationMark()
{
yield return new WaitForSeconds(1);
var children = new List<GameObject>();
foreach (Transform child in transform) children.Add(child.gameObject);
children.ForEach(child => Destroy(child));
}
}
Just to be sure: I assume every player has its own instance of both of your scripts attached (some maybe nested further in their own hierarchy).
I assume that since you are using transform.Find which looks for the object by name within it's own children.
In general using Find and GetComponent over and over again is very inefficient! You should in both classes rather store them to fields and re-use them. Best would be if you can actually already reference them via the Inspector and not use Find and GetComponent at all.
In general finding something by name is always error prone. Are you sure they are all called correctly? Or are others maybe further nested?
Note: Find does not perform a recursive descend down a Transform hierarchy.
I would prefer to go by the attached components. You say it has e.g. a RigidBody. If this is the only Rigidbody component in the hierarchy below your objects (usually this should be the case) then you could instead rather simply use
// pass in true to also get disabled or inactive children
Rigidbody graphics = GetComponentInChildren<Rigidbody>(true);
the same for the ExclamationMarkSpawn
// Would be even beter if you already reference these in the Inspector
[SerializeField] private Rigidbody graphics;
[SerializeField] private ExclamationMarkSpawn exclamationMark;
[SerializeField] private Transform player;
private void Awake()
{
if(!player) player = GameObject.FindWithTag("Player");
if(!graphics) graphics = GetComponentInChildren<Rigidbody>(true);
if(!exclamationMark) exclamationMark = GetComponentInChildren<ExclamationMarkSpawn>(true);
}
private void CheckForPlayer()
{
// If really needed you can also after Awake still use a lazy initialization
// this adds a few later maybe unnecessary if checks but is still
// cheaper then using Find over and over again
if(!player) player = FindWithTag("Player");
if(!graphics) graphics = GetComponentInChildren<Rigidbody>(true);
if(!exclamationMark) exclamationMark = GetComponentInChildren<ExclamationMarkSpawn>(true);
var playerPos = (int)player.position.x;
// always if making such a check also give a hint that something might be missing
if (!graphics)
{
// by adding "this" you can now simply click on the message
// in the console and it highlights the object where this is happening in the hierarchy
Debug.LogWarning("graphics is missing here :'( ", this);
return;
}
// Define gameobject position
var enemyPos = graphics.transform.position.x;
// Define range to spawn tiles in
// this entire block can be shrinked down to
if (Mathf.Abs(playerPos - enemyPos) <= 5)
{
enemyIsActive = true;
if (exclamationMark) exclamationMark.SpawnExclamationMark();
}
else
{
enemyIsActive = false;
}
}
The same also in ExclamationMarkSpawn.cs.
I would additionally only allow 1 exclamation mark being visible at the same time. For example when a player jitters in the distance especially assuming both, the player and the enemy, I would move the entire instantiation to the routine and use a flag. Especially since this is called every frame in Update while the player stays in the range!
Also re-check and make sure your enemies are not maybe referencing the same spawnPos and thus all instantiating their exclamation marks on top of each other.
public class ExclamationMarkSpawn : MonoBehaviour
{
public Transform spawnPos;
public GameObject exclamationMark;
public GameObject exclamationMarkAudio;
[SerializeField] private CameraShake cameraShake;
// only serialized for debug
[SerializeField] private bool isShowingExclamation;
private void Awake()
{
if(!cameraShake) cameraShake = Camera.main.GetComponent<CameraShake>();
// or assuming this component exists only once in the entire scene anyway
if(!cameraShake) cameraShake = FindObjectOfType<CameraShake>();
}
public void SpawnExclamationMark()
{
StartCoroutine(ShowExclamationMark());
}
private IEnumerator ShowExclamationMark()
{
// block concurrent routine call
if(isShowingExclamation) yield brake;
// set flag blocking concurrent routines
isShowingExclamation = true;
// NOTE: Also for this one you might want to rather have a flag
// multiple enemy instances might call this so you get concurrent coroutines also here
StartCoroutine(cameraShake.Shake(0.2f, 0.2f, 0.2f));
Instantiate(exclamationMark, spawnPos.position, Quaternion.identity);
if (exclamationMarkAudio) Instantiate(exclamationMarkAudio, spawnPos.position, Quaternion.identity);
yield return new WaitForSeconds(1);
var children = new List<GameObject>();
foreach (var child in transform.ToList()) children.Add(child.gameObject);
children.ForEach(child => Destroy(child));
// give the flag free
isShowingExclamation = false;
}
}
Try this;
if (transform.Find("ExclamationMark"))
{
var exMark = transform.Find("ExclamationMark").gameObject.GetComponent<ExclamationMarkSpawn>();
exMark.SpawnExclamationMark(transform.position); //Add transform.position here
}
public void SpawnExclamationMark(Vector3 EnemyPos)
{
StartCoroutine(GameObject.FindGameObjectWithTag("MainCamera").GetComponent<CameraShake>().Shake(0.2f, 0.2f, 0.2f));
Instantiate(exclamationMark, EnemyPos, Quaternion.identity);
if (exclamationMarkAudio)
Instantiate(exclamationMarkAudio, EnemyPos, Quaternion.identity);
StartCoroutine(DestroyExclamationMark());
}
I have a spawner object. Every time a gameobject is spawned, I want that object to move randomly (wandering). The problem in my script is that the gameobject movement is very random (jittery). How can I solve this?
void Start ()
{
InvokeRepeating("SpawnNPC", Spawntime, Spawntime);
}
// Update is called once per frame
void Update () {
population = GameObject.FindGameObjectsWithTag("NPCobject");
for (int i = 0; i < population.Length; i++)
{
getNewPosition();
if (population[i].transform.position != pos)
{
population[i].transform.position = Vector3.MoveTowards(population[i].transform.position, pos, .1f);
}
}
}
void getNewPosition()
{
float x = Random.Range(-22, 22);
float z= Random.Range(-22, 22);
pos = new Vector3(x, 0, z);
}
I made the New randomize vector in different method, because I plan to change it with pathfinder function and make it in different thread/task.
You are choosing a new direction every single frame. That will always be very jittery. You wan't to only change direction after, at least, a small interval. Here is a link to one way to do that from Unity's website. https://docs.unity3d.com/ScriptReference/MonoBehaviour.InvokeRepeating.html
What about using Navigation? As you said wandering, I thought it would give you a nice result and also make your code simple.
The following screenshot is a sample with Navigation. The moving game objects are also changing their direction nicely, although it cannot be seen in the sample because the game object is capsule...
Ground game object in the sample program has NavMesh. See here to build NavMesh.
Agent game object has NavMeshAgent Component. See here to set it up.
Th Behaviour class below is for Agent game object.
using UnityEngine;
using UnityEngine.AI;
public class NavAgentBehaviour : MonoBehaviour {
public Transform[] Destinations { get; set; }
// Use this for initialization
void Start ()
{
InvokeRepeating("changeDestination", 0f, 3f);
}
void changeDestination()
{
var agent = GetComponent<NavMeshAgent>();
agent.destination = Destinations[Random.Range(0, Destinations.Length)].position;
}
}
The next Behaviour class is just for spawning the Agent and setting up the destinations. On Unity, set it to whatever game object in the scene, and allocate game objects to the fields.
using UnityEngine;
public class GameBehaviour : MonoBehaviour {
public GameObject Agent;
public Transform SpawnPoint;
public Transform Destination1;
public Transform Destination2;
public Transform Destination3;
// Use this for initialization
void Start()
{
Agent.SetActive(false);
InvokeRepeating("Spawn", 0f, 2f);
}
void Spawn() {
var newAgent = Instantiate(Agent);
newAgent.GetComponent<NavAgentBehaviour>().Destinations = new[] { Destination1, Destination2, Destination3 };
newAgent.transform.position = SpawnPoint.position;
newAgent.SetActive(true);
}
}
Increase the number of destination, to make the moves look more random. By the way, the destinations do not need to be specified by game objects, which is only for making it easy to see the sample program's behaviour.
The source of the jitteriness comes from the fact that you are updating the position to move every frame so your objects never have a consistent location to move to. I would instead suggest attaching a new script to each of your objects that individually handles their movement. In that script you could do something like the following, which has a delay to keep the target position for more than 1 frame.
float delaytimer;
Vector3 pos;
void Start () {
getNewPosition(); // get initial targetpos
}
void Update () {
delaytimer += Time.deltaTime;
if (delaytimer > 1) // time to wait
{
getNewPosition(); //get new position every 1 second
delaytimer = 0f; // reset timer
}
transform.position = Vector3.MoveTowards(transform.position, pos, .1f);
}
void getNewPosition()
{
float x = Random.Range(-22, 22);
float z= Random.Range(-22, 22);
pos = new Vector3(x, 0, z);
}
You are changing the direction they are moving in every frame, thats what is causing the jitter.
You could wait a few moments before you change the direction, perhaps something like this.
// Outside update
float betweenChanges = 2;
float lastChange = 0;
// Inside update
if(Time.realtimeSinceStartup > lastChange)
{
// Change directions
// ...
lastChange = Time.realTimeSinceStart + betweenChanges;
}
You could also solve this by using InvokeRepeating or a Coroutine.
If you dont want all the NPC's to change direction at the same time and still plan on controlling every NPC from the same class like in your example, you should perhaps add a timer for each NPC instance instead and use that to decide when to change its direction.
A better idea would be to let each NPC have its own Movement-script.
I've got this problem: I need that enemies move towards a target position, but my problem is that i wrote this inside the Update function, so the "foreach" statement is done in just one frame. Also, the "waypoint is reached" sentence is never shown. How can I solve this?
Here is the code: `public class WayPointMovement : MonoBehaviour {
public EnemyDataSpawn enemy;
private Vector3 lastPos;
void Start ()
{
lastPos = gameObject.transform.position;
}
void Update ()
{
gameObject.transform.LookAt (LevelManager.current.playerObject.transform);
//WaypointData is a scriptable object that contains the definition of "x" and "z" coordinates.
foreach (WaypointData waypoints in enemy.enemy.waypoint) {
Debug.Log (waypoints.x);
gameObject.transform.position = Vector3.MoveTowards (gameObject.transform.position, new Vector3 (lastPos.x + waypoints.x, 0f, lastPos.z + waypoints.z), Time.deltaTime * enemy.enemy.easySpeedMovement);
if (gameObject.transform.position.x == lastPos.x + waypoints.x && gameObject.transform.position.z == lastPos.z + waypoints.z) {
Debug.Log("waypoint reached");
lastPos = gameObject.transform.position;
}
}
}
}`
You need to rework Update so that it only does one frame's worth of work. That means that you have to remember a) which waypoint you're going toward, and b) how far you were through going to it. You only move on to the next waypoint when you actually get to it.
There are two ways to track the progress through waypoints:
1. You can simply delete waypoints once they are reached. Then, the waypoint you're trying to get to is always the first one in the list.
2. If you want to keep the waypoints, then you need an extra variable to track which waypoint you're currently moving toward. This could be something like an index into an array or list, or it could be an enumerator (IEnumerator<WaypointData>) on which you call MoveNext each time you finish a waypoint.
You've indicated (in comments on this answer) that you don't want to delete waypoints as you reach them. The most flexible arrangement, allowing you to add new waypoints as you go, is to use an index variable:
int waypointIndex;
void Start()
{
waypointIndex = 0;
}
void Update()
{
if (waypointIndex < waypoints.Count)
{
update position;
if (reached waypoint)
{
waypointIndex++;
}
}
}
Then, in your Update method, instead of writing a loop, you update your game object's position exactly once, check if it has reached the waypoint just once, and if it has, move on to the next waypoint before returning. If multiple moves are needed to get to a waypoint, you allow those to be multiple calls to Update; each Update is only one step of the movement.
You will need better logic for determining when you have reached a waypoint. As other commenters have indicated, with floating-point values, simply checking if they are exactly equal is not likely to work. Instead, you can check whether the distance to the waypoint is less than Time.deltaTime * enemy.enemy.easySpeedMovement, and if it is, then instead of using Vector3.MoveTowards, just set the position to the waypoint exactly, and treat that step as having reached the waypoint.
Here's the rough logic (in pseudocode) (but not as pseudocode as when I first wrote it):
Vector3 lastPos;
int waypointIndex;
void Start()
{
lastPos = gameObject.transform.position;
waypointIndex = 0;
}
void Update()
{
if (waypointIndex < waypoints.Count)
{
waypointPosition = new Vector3 (lastPos.x + waypoints.x, 0f, lastPos.z + waypoints.z);
if (Vector3.Distance(gameObject.transform.position, waypointPosition) > length of one step)
{
gameObject.transform.position = Vector3.MoveToward(gameObject.transform.position, waypointPosition);
}
else
{
gameObject.transform.position = waypointPosition;
log("Waypoint reached");
waypointIndex++;
}
}
}
Use Vector3.sqrMagnitude for comparing closeness. You can define how "close" is close enough. I'm going to use a waypoint as a Vector3 in this example. You can determine the waypoint Vector3 however you like.
// define this globally
public float closeEnough = 5f;
// Within your compare method
if((gameObject.transform.position - waypoint).sqrMagnitude < closeEnough)
{
Debug.Log("waypoint reached");
}
Note: The result of the Vector3 - Vector3, is another Vector3.
I wanted to use my coroutine to smoothly interpolate the position and rotation of multiple GameObjects with the script attached to them. When I start the coroutine, the smooth part works fine, but all of my Objects get moved to the same position, which was not what I intended. I want to understand why that is the case, and if there is a smart way to deal with it.
This is what my coroutine looks like:
IEnumerator interpolate_Plate(Transform targ)
{
float passedTime = 0;
while (!transform.Equals(targ))
{
passedTime += Time.deltaTime;
transform.position = Vector3.Lerp(transform.position, targ.position, passedTime);
transform.rotation = Quaternion.Lerp(transform.rotation, targ.rotation,passedTime);
yield return null;
}
yield break;
}
I was thinking about creating a mastercoroutine with a list in it and then call the smoothing part.
Is the problem just, that the Reference of targ always gets reset for all coroutinecalls on the stack?
As requested by you the function that calls the coroutine:
public void move_Plate(Transform newPosition)
{
StartCoroutine(interpolate_Plate(newPosition));
}
Okay so I found the solution, since Unity or C# works with pointers and so on the problem was. that I continously changed the transforms of all Objects, since I used the pointer to the transform of the next Object. But I moved that Object to so it all ended up on the last Object I moved.
How to prevent this:
I created a new class which stores my values so position and rotation of the old one. I store an Instance of that in my Class where I move the plates.
I now changed from coroutine to the update method as suggested in the comments. With a flag I check if I should move the Object or not. Then I move it smoothly to the new Position.
Code:
private Trans MoveTo;
private bool move;
void Update()
{
if (move)
{
float passedTime = 0;
passedTime += Time.deltaTime;
transform.position = Vector3.Lerp(transform.position, MoveTo.position, passedTime);
transform.rotation = Quaternion.Lerp(transform.rotation, MoveTo.rotation, passedTime);
}
}
Seems to work.
I've got an issue when I try to move smoothly a Gameobject.
I receive every second through a TCP Protocol a position, where my Gameobject have to move. So I have my start position, my end position, I can calculate the distance between the two position , and I know my Gameobject have to move with a constant speed to my end point in 1 second.
I try 3 solutions that are : Learp, MoveToward and SmoothDamp , but none of them work, my Gameobject just teleport from point A to point B every time.
Here's what I try in my code (my Gameobject is referenced in a dictionnary, my Gameobject are planes) :
// Distance between my 2 points
float step = Vector3.Distance(planeId_Object_Dictionnary[newPlane.Flight].transform.position, new Vector3((float)newPlane.X, (float)newPlane.Afl / (3.2808f * 1852f), (float)newPlane.Y));
//Use of Lerp
//planeId_Object_Dictionnary[newPlane.Flight].transform.position = Vector3.Lerp(planeId_Object_Dictionnary[newPlane.Flight].transform.position, new Vector3((float)newPlane.X, (float)newPlane.Afl / (3.2808f * 1852f), (float)newPlane.Y), step);
//Use of MoveTowards
planeId_Object_Dictionnary[newPlane.Flight].transform.position = Vector3.MoveTowards(planeId_Object_Dictionnary[newPlane.Flight].transform.position, new Vector3((float)newPlane.X, (float)newPlane.Afl / (3.2808f * 1852f), (float)newPlane.Y), step);
//Use of SmoothDamp
//planeId_Object_Dictionnary[newPlane.Flight].transform.position = Vector3.SmoothDamp(planeId_Object_Dictionnary[newPlane.Flight].transform.position, new Vector3((float)newPlane.X, (float)newPlane.Afl / (3.2808f * 1852f), (float)newPlane.Y), ref newPlane.GroundSpeed, 1);
The code is a function that is called in my Update this way :
void Update () {
lock (threadLock)
{
// Message receive from my TCP Protocol
while (q_orders.Count > 0)
{
switch (q_orders.Dequeue())
{
case OrderType.trackmovedevent:
aircraftMajInfos(q_args.Dequeue()); // My function to move my Object
break;
}
}
}
}
Its better to use a tween engine , like http://dotween.demigiant.com/.
If you install Dotween then you can simply use
transform.DOMove(new vector3(1 ,0 , 1) , duration);
You can also set Ease for tweens. or use Oncomplete fucntions;
transform.DOMove(new vector3(1 ,0 , 1) , duration)
.SetEase(Ease.OutCubic)
.OnCompelete(() => { shouldClose = true; });
I must say that you have understand all three functions totally wrong. They should be called in multiple updates, not just once.
In this situation I recommend MoveTowards. Because SmoothDamp doesn't move the object at a constant speed, and to use Lerp, you need to caltulate the ratio between the two points (note that you CAN move the object constantly if you add the amount to the t parameter every fixed update).
Here's a code snippet I wrote for the MonoBehaviour of your gameobject
const float moveTime = 1;
Vector3 target, step;
void Update () {
transform.position = Vector3.MoveTowards(transform.position, target,
step / moveTime * Time.deltaTime);
}
// Call this method to set the new target (the currently received position from network)
public void SetTarget (Vector3 positon) {
target = positon;
step = Vector3.Distance(transform.position, target);
}
Time.deltaTime is the interval between updates (where the Update () function called)
I would suggest the following:
Create a new script "ObjectMover" (maybe not the best name) and apply it on the Game Objects that you want to be able to move.
In the script create a public method: MoveToNewPos(Vector3 newPos) which stores the new position that you are aiming for with this object.
Inside the Update method of "ObjectMover" use lerp or calculate the stepping yourself using step / moveTime * Time.deltaTime to move towards the wanted new position and only adjust the speed to you desired look and feel.
From your "main" script do theObjectIWantToMove.GetComponent().MoveToNewPos(newPos); and it will smoothly move there.