So in my game I have an object that I need to move smoothly from Vector3 fromPosition to Vector3 toPosition at speed float speed, and then go back to where it started. All very simple, but to try and make life easier when setting up levels I decided to make a custom inspector for this script with buttons that allow me to set the target positions to the current position of the object, so I can just move it to where it needs to be and click a button, rather that typing in all the coordinates. Thought I had it all working but then started seeing some very strange behaviour, after playing around it seems to be as follows: The first time a button is used all is well. Every time the button is used after that, the values change properly in the inspector, but upon hitting Play the values of toPosition and fromPosition are reverted to what they were the first time the button was used. (They don't revert back again on Stop). However if I type the values in manually, it works perfectly. Very strange, does anyone have any idea what might be happening here? The code for the script and custom inspector are bellow.
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class MovingGrapple : MonoBehaviour
{
public Vector3 fromPosition;
public Vector3 toPosition;
public float speed;
Rigidbody thisBody;
Grapple player;
// Start is called before the first frame update
void Start()
{
thisBody = GetComponent<Rigidbody>();
player = GameObject.Find("Head").GetComponent<Grapple>();
}
private void FixedUpdate()
{
thisBody.MovePosition(Vector3.MoveTowards(transform.position, toPosition, Time.fixedDeltaTime * speed));
if(transform.position == toPosition)
{
transform.position = fromPosition;
if (player.activeTarget != null && GetComponentsInChildren<Transform>().Contains(player.activeTarget.transform))
{
player.BreakGrapple();
GameObject.Destroy(player.activeTarget);
}
}
}
public void SetFromPosition()
{
fromPosition = transform.position;
}
public void SetToPosition()
{
toPosition = transform.position;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(MovingGrapple))]
public class MovingGrappleInspector : Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
MovingGrapple myTarget = (MovingGrapple)target;
if (GUILayout.Button("Set From position."))
{
myTarget.SetFromPosition();
}
if (GUILayout.Button("Set To position."))
{
myTarget.SetToPosition();
}
}
}
Thanks.
This will not only happen if you press play .. your changes are never saved!
If possible you should never mix editor scripts with direct access to the target except you know exactly what you're doing!
You would especially need to "manually" mark your changed object as dirty. Otherwise the changes are only temporary until your object is deserialized again (Enter/Exit PlayMode or reload of the Scene or asset).
You could before the changes add a Undo.RecordObject
if (GUILayout.Button("Set From position."))
{
Undo.RecordObject(myTarget, "SetFromPosition");
myTarget.SetFromPosition();
}
if (GUILayout.Button("Set To position."))
{
Undo.RecordObject(myTarget, "SetToPosition");
myTarget.SetToPosition();
}
Also (sounds unlikely in your use-case but)
Important: To correctly handle instances where objectToUndo is an instance of a Prefab, PrefabUtility.RecordPrefabInstancePropertyModifications must be called after RecordObject.
In general rather always go through SerializedProperty where possible like e.g.
[CustomEditor(typeof(MovingGrapple))]
public class MovingGrappleInspector : Editor
{
private SerializedProperty fromPosition;
private SerializedProperty toPosition;
private MovingGrapple myTarget
private void OnEnable()
{
fromPosition = serializedObject.FindProperty("fromPosition");
toPosition = serializedObject.FindProperty("toPosition");
myTarget = (MovingGrapple)target;
}
public override void OnInspectorGUI()
{
DrawDefaultInspector();
// This loads the current real values into the serialized properties
serializedObject.Update();
if (GUILayout.Button("Set From position."))
{
// Now go through the SerializedProperty
fromPosition.vector3Value = myTarget.transform.position;
}
if (GUILayout.Button("Set To position."))
{
toPosition.vector3Value = myTarget.transform.position;
}
// This writes back any changes properties into the actual component
// This also automatically handles all marking the scene and assets dirty -> saving
// And also handles proper Undo/Redo
serializedObject.ApplyModifiedProperties();
}
}
Related
I've been using Unity to create a simple 2D game but the problem is that even when the game object "player" gets destroyed, the gameobject "isDead" (text) doesn't appear.
This is my script.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class youDied_Text : MonoBehaviour
{
private Transform player;
private Text isDead;
// public static bool isDead;
// Start is called before the first frame update
private void Start() {
isDead = GetComponent<Text>();
}
void checkForDeath()
{
if (player==false)
{
isDead.gameObject.SetActive(true);
}
else
{
isDead.gameObject.SetActive(false);
}
}
// Update is called once per frame
void Update()
{
player = GameObject.FindWithTag("Player").transform;
checkForDeath();
}
}
This script is attached in the text which I need to display in UI element.
As was noted currently you would get a NullReferenceException which is definitely not what you want.
There is absolutely no need / redundancy going through Transform at all actually. Simply store the GameObject reference instead
You are currently setting the object to inactive which has the Text attached ... which is the same object your component is attached to as well!
=> As soon as you end up in the second case once you set it to inactive => from now on Update is never called anymore!
In general as it sounds like this should only happen once anyway I would use a more event driven approach and have a component on your player like e.g.
public class Player : MonoBehaviour
{
public UnityEvent onDied;
private void OnDestroy ()
{
onDied.Invoke();
}
}
And then simply attach a listener/callback to that event once without poll checking states. You can do this either via the Inspector directly (just like in e.g. Button.onClick) or via code like e.g.
public class youDied_Text : MonoBehaviour
{
// Already reference things via the Inspector if possible!
[SerializeField] private GameObject player;
[SerializeField] private Text isDead;
private void Awake()
{
if(!isDead) isDead = GetComponent<Text>();
isDead.gameObject.SetActive(false);
// If you want to rather set it via the Inspector remove all the rest here
//if(!player) player = GameObject.FindWithTag("Player"). GetComponent<Player>();
// or even simpler
if(!player) player = FindObjectOfType<Player>();
player.onDied.AddListener(OnPlayerDied);
}
// If you want to rather set it via the Inspector make this public
private void OnPlayerDied()
{
isDead.gameObject.SetActive(true);
}
}
I just learned how to (partially) use unity's custom inspector. I mainly want to use this to serialize specific variables if a bool or enum or whatever is true.
Anyway, in a test project I made a bool called usesSpeed. Then I made a float called speed. But I only want to serialize speed whenever usesSpeed is true. That works well, however the problem is I can't change the value of speed through the inspector at all. I can't "drag" the value or input anything. It's always just 0.
Here's my code.
public class script : MonoBehaviour
{
[HideInInspector] public float speed;
public bool usesSpeed;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
Debug.Log(speed);
}
[CustomEditor(typeof(script))]
public class edtr : Editor
{
public override void OnInspectorGUI()
{
script scrpt = (script)target;
base.OnInspectorGUI();
if (scrpt.usesSpeed)
{
EditorGUILayout.LabelField("Speed stuff");
EditorGUILayout.PropertyField(serializedObject.FindProperty("speed"));
}
}
}
}
All I had to do was just add this at the end and everything works great.
serializedObject.ApplyModifiedProperties();
I have this problem that i just can't figure out how to solve.
I am trying to make a game in Unity, and i have stumbled across a problem that goes like this.
I want to put an object in the scene in a list, when i hit it with a raycast.
With what i have tried so far. Either it puts everything that is tagged the same thing on the list when i press mousebutton on one of the objects, or it only puts in the same thing (Cube in this example).
My code is:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerTagged : MonoBehaviour
{
public float damage = 10f;
public float range = 100f;
public Camera fpsCam;
public List<GameObject> playersTagged;
private void Update()
{
if (Input.GetButtonDown("Fire1"))
{
Shoot();
}
}
void Shoot()
{
RaycastHit hit;
if (Physics.Raycast(fpsCam.transform.position, fpsCam.transform.forward, out hit, range))
{
Debug.Log(hit.transform.name);
Target target = hit.transform.GetComponent<Target>();
if (target != null)
{
target.takeDamage(damage);
if(hit.collider.tag == "Taggable")
playersTagged.Add(GameObject.FindWithTag("Taggable"));
}
}
}
}
use
playersTagged.Add(hit.collider.gameObject);
Instead of using GameObject.FindWithTag() which returns the first object it'll find source
Use gameobject property playersTagged.Add(target.gameobject). Since all components store the reference to the gameobject they are attached to.
You are adding GameObject.FindWithTag("Taggable") which means you are getting any GameObject that has that tag and adding it to the list. But instead, you want to add the hit object, so you need to add hit.collider.gameObject.
Currently, my code allows the player to go to the next scene by clicking. I want to, however, make the fade out into the next scene animation automatic after 4 seconds. How can I do this?
I've tried looking up information, but nothing seems to work.
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
public class LevelChanger : MonoBehaviour
{
// Start is called before the first frame update
float timer = 4f;
public Animator animator;
private int levelToLoad;
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Invoke("FadeToLevel(1)", 2f);
}
}
public void FadeToLevel (int levelIndex)
{
levelToLoad = levelIndex;
animator.SetTrigger("FadeBlack");
}
public void OnF`enter code here`adeComplete()
{
SceneManager.LoadScene(levelToLoad);
}
}
The code works as intended, but I want the animation to happen automatically.
If I understood correctly, what you are aiming is to make an animation to play automatically when the player enters the new scene.
If that's the case, then you are looking for the sceneLoaded() method from SceneManager
Also, this discussion may be useful
I read many pages on google and everything else I could find but nothing helped me, so I have to ask here: I made a script attached to 100 cubes it says:
using UnityEngine;
using System.Collections;
public class IfClose:MonoBehaviour{
public bool Gravitronned=false;
// Use this for initialization
void Start(){
}
// Update is called once per frame
void Update(){
}
void OnTriggerEnter(Collider col)
{
if(col.gameObject.name =="IfInScreenGrv"){
Gravitronned=true;
}
else
{
Gravitronned=false;
}
}
}
and then I have another script that says:
using UnityEngine;
using System.Collections;
public class TimeFreeze:MonoBehaviour{
static bool GravitronedTwice;
// Use this for initialization
void Start(){
}
void Update()
{
GravitronedTwice= gameObject.GetComponent<IfClose>().Gravitronned;
if(GravitronedTwice=true){
if(Input.GetKeyDown(KeyCode.V)){
Physics.gravity =newVector3(0,3.0F,0);
}
}
}
}
so when I press V I want the cube only in this area to get Physics.gravity = new Vector3 (0, 3.0F, 0);
You're calling the value of the bool variable Gravitronned in the second script using GetComponent<>() which is not the right way of going about this.
How about instead of having two bool variables, Gravitronned and GravitronnedTwice, you only have 1 variable in the first script, make it public [even though make it public a bad idea - but that's another topic altogether] and have it access to both the scripts, instead of what you're doing. That way you can change the bool value, as well as access it in both scripts.
Alternatively, you can make the bool Gravitronned private in Script 1, and access it in any other script via C# property.
Script 1
private bool Gravitronned;
public BOOL GRAVITY
{
get Gravitronned;
set Gravitronned;
}