I'm modifying unity's karting microgame and I wanted to make the speed-pads able to be used more than once because the way they are programmed makes it so that they can only be used up until the MaxTime "x" amount of seconds is reached. The player can even pick up the power-up after the MaxTime is reached but it doesn't do anything...
I strongly suggest you download the karting microgame to have a better view of the issue but here is the code for the power-up:
using KartGame.KartSystems;
using UnityEngine;
using UnityEngine.Events;
public class ArcadeKartPowerup : MonoBehaviour {
public ArcadeKart.StatPowerup boostStats = new ArcadeKart.StatPowerup
{
MaxTime = 5f
};
public bool isCoolingDown { get; private set; }
public float lastActivatedTimestamp { get; private set; }
public float cooldown = 5f;
public bool disableGameObjectWhenActivated;
public UnityEvent onPowerupActivated;
public UnityEvent onPowerupFinishCooldown;
private void Awake()
{
lastActivatedTimestamp = -9999f;
}
private void Update()
{
if (isCoolingDown)
{
if (Time.time - lastActivatedTimestamp > cooldown)
{
//finished cooldown!
isCoolingDown = false;
onPowerupFinishCooldown.Invoke();
}
}
}
private void OnTriggerEnter(Collider other)
{
if (isCoolingDown) return;
var rb = other.attachedRigidbody;
if (rb) {
var kart = rb.GetComponent<ArcadeKart>();
if (kart)
{
lastActivatedTimestamp = Time.time;
kart.AddPowerup(this.boostStats);
onPowerupActivated.Invoke();
isCoolingDown = true;
if (disableGameObjectWhenActivated) this.gameObject.SetActive(false);
}
}
}
}
I'm did not manage to place the kart code here because it goes over the character limit, but you can easily check it out by downloading the karting microgame in Unity Hub! In this context, the Kart's script serves the function of provinding the stats that can be boosted.
The idea that I had that might (or not) work is to make it so that every time the "isCollingDown" returns just add another variable that controls how long the effect lasts to the MaxTime variable, something like "MaxTime = MaxTime + EffectDuration." But I don't know how to implement this...
I'm sorry if there is a very obvious answer to this issue but I really don't understand a lot about scripts!
It actually already has a function build in for what you want to achive so you do not have to code anything.
You see the property disableGameObjectWhenActivated. This one should be set true right now. Just set it false and it will have a cooldown instead of beeing disabled afterwards. Now if you want to get rid of the cooldown I would just set the cooldown to 0.
Related
I've recently started coding on Unity, trying to make a game. So long it's been fine, but I faced a problem.
I've implemented a script for the Attack System:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AttackDamage : MonoBehaviour
{
private float attackDamage = 20;
private void OnTriggerEnter2D(Collider2D other)
{
if (other.GetComponent<Health>() != null)
{
Health health = other.GetComponent<Health>();
health.TakeDamage(attackDamage);
}
}
}
And I also implemented one for the Health System:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class Health : MonoBehaviour
{
public Image healthBar;
public float healthAmount = 100;
private void Update()
{
if (healthAmount <= 0)
{
SceneManager.LoadScene(0);
}
}
public void TakeDamage(float Damage)
{
healthAmount -= Damage;
healthBar.fillAmount = healthAmount / 100;
}
public void Healing(float healPoints)
{
healthAmount += healPoints;
healthAmount = Mathf.Clamp(healthAmount, 0, 100);
healthBar.fillAmount = healthAmount / 100;
}
}
And it works prety well.
But as you read on the title, the attack only actually works right after I move. If I try to attack while I'm not moving, the attackArea appears on the scene, but doesn't deal damage. And i can't figure out why.
Do you have any idea on what could be the problem?
Here there's also a video of what actually happens, in the game:
https://drive.google.com/drive/folders/1BTYTNz_yzus-eRLnjsgB0hsYLU5sQm2k?usp=sharing
I have no idea on how to solve this problem, since I've copied the code from a source online, which actually works prorperly.
The code is exactly the same, apart form the script for the Health System, which is not shown on the video, but which also shouldn't make that much of a difference.
So i really don't know how to handle this.
Thanks for the help :)
If you are looking to deal continuous damage while any health-character is in the trigger, use OnTriggerStay2d instead.
Otherwise, if you are looking to deal damage when the user presses a button, you can do the following:
Have a hit-box collider that stores all enemy in range. (By adding enemy to a list when it enters the hit-box, and removing them from list when they exit.)
When the attack is triggered, fetch all enemy in the list from 1. and deal damage to all.
Code-wise, looks something like this:
public class AttackDamage : MonoBehaviour
{
private HashSet<Health> inRange = new HashSet<Health>();
private float attackDamage = 20;
private void OnTriggerEnter2D(Collider2D other)
{
if (other.GetComponent<Health>() != null)
{
// Add to set of characters that are in range of attack
inRange.Add(other.GetComponent<Health>());
}
}
private void OnTriggerExit2D(Collider2D other)
{
var charHealth = other.GetComponent<Health>();
if (charHealth != null) {
// Remove, since it exit the range of the attacking character.
inRange.Remove(charHealth);
}
}
// Call this function whenever you want to do damage to all characters in range.
public void Attack(){
foreach(var character in inRange){
character.TakeDamage(attackDamage);
}
}
}
Then somewhere else... (example, in your player.)
public class YourPlayer {
// ...
// Inspector reference to the hitbox
[SerializeField]
private AttackDamage attackHitbox;
private void Update(){
// Attack when button is pressed (or something).
if (Input.GetKeyDown(KeyCode.A)) {
attackHitbox.Attack();
}
}
// ...
}
Finally, if you are looking to deal damage at specific points in an animation, the term you should search for is Key-Frame. Look for a tutorial on Animation, then Key-Frame.
Once you learn about it, you can use the above mentioned method, but call the damage script on your desired key-frames.
I have a 10-second countdown script that runs on DeltaTime.
In my Update function, I'm trying to make it print, only once, "Hello" whenever it reaches second 8.
The problem is that deltaTime is repeatedly printing "Hello" while it hangs on the 8th second, rather than only printing once, and I don't know how to stop that behavior.
I kept trying to introduce triggers in the if-block that set to 0 as soon as the block is entered but it still keeps continuously printing "Hello" so long as the timer is on second 8.
Countdown Timer
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class CountdownTimer : MonoBehaviour
{
float currentTime = 0f;
float startingTime = 10f;
public int n = 0;
public int switcher = 0;
// Start is called before the first frame update
void Start()
{
currentTime = startingTime;
}
// Update is called once per frame
void Update()
{
currentTime -= 1 * Time.deltaTime; //does it each frame
n = Convert.ToInt32(currentTime);
if (n == 8)
{
switcher = 1;
}
}
}
Update Method in different class
if (CountdownTimer.switcher == 1)
{
CountdownTimer.switcher = 0;
print("hey");
}
Any ideas on how to make print("hey") only happen once? It's important because later I would replace the print code with an important method and I need to make sure the method happens only once.
This is where you want to implement a system of subscriber with event/listener.
Add an event to the countdown, if countdown is meant to be unique, you can even make it static. Also, if the update is no longer needed after the setting of switcher to 1 then you can convert that to coroutine
public class CountdownTimer : MonoBehaviour
{
float currentTime = 0f;
float startingTime = 10f;
public static event Action RaiseReady;
// Start is called before the first frame update
void Start()
{
currentTime = startingTime;
StartCoroutine(UpdateCoroutine());
}
// Update is called once per frame
IEnumerator UpdateCoroutine()
{
while(true)
{
currentTime -= 1 * Time.deltaTime; //does it each frame
int n = Convert.ToInt32(currentTime);
if (n == 8)
{
RaiseReady?.Invoke();
RaiseReady = null; // clean the event
yield break; // Kills the coroutine
}
yield return null;
}
}
}
Any component that needs to know:
public class MyClass : MonoBehaviour
{
void Start()
{
CountdownTimer.RaiseReady += CountdownTimer_RaiseReady;
}
private void CountdownTimer_RaiseReady()
{
Debug.Log("Done");
// Remove listener though the other class is already clearing it
CountdownTimer.RaiseReady -= CountdownTimer_RaiseReady;
}
}
The solution #Everts provides is pretty good, but as you are using unity I would recommend a couple of tweaks to play nicer with the editor. Instead of a generic event I recommend using a UnityEvent from the UnityEngine.Events namespace. I would also advise against statics due to how unity goes about serializing them across scenes. There are some weird edge cases you can get into if you aren't familiar with how unity handles their serialization. If you just need to send a message to another object in the same scene I would actually recommend a game manager. You can safely do a GameObject.Find() in onvalidate() and link your variables to avoid a performance hit at runtime doing the find. If that data needs to carry across to a different scene for this message then use a ScriptableObject instead. It would look something like below.
Put this component on the scene's "Game Manager" GameObject
public class CountingPassthrough : MonoBehaviour
{
public CountdownTimer countdownTimer;
}
put this component on the scene's "Timer" GameObject
public class CountdownTimer : MonoBehaviour
{
public float startingTime = 10f;
public UnityEvent timedOut = new UnityEvent();
private void OnValidate()
{
if(FindObjectOfType<CountingPassthrough>().gameObject.scene == gameObject.scene && FindObjectOfType<CountingPassthrough>() != new UnityEngine.SceneManagement.Scene())
FindObjectOfType<CountingPassthrough>().countdownTimer = this;
}
private void Start()
{
StartCoroutine(TimerCoroutine());
}
// Coroutine is called once per frame
private IEnumerator TimerCoroutine()
{
float currentTime = 0f;
while (currentTime != 0)
{
currentTime = Mathf.Max(0, currentTime - Time.deltaTime);
yield return null;//wait for next frame
}
timedOut.Invoke();
}
}
Put this component on the GameObject you want to use the timer
public class user : MonoBehaviour
{
[SerializeField, HideInInspector]
private CountingPassthrough timerObject;
private void OnValidate()
{
if(FindObjectOfType<CountingPassthrough>().gameObject.scene == gameObject.scene && FindObjectOfType<CountingPassthrough>() != new UnityEngine.SceneManagement.Scene())
timerObject = FindObjectOfType<CountingPassthrough>();
}
private void OnEnable()
{
timerObject.countdownTimer.timedOut.AddListener(DoSomething);
}
private void OnDisable()
{
timerObject.countdownTimer.timedOut.RemoveListener(DoSomething);
}
private void DoSomething()
{
//do stuff here...
}
}
This workflow is friendly to prefabs too, because you can wrap the find() in onvalidate() with if(FindObjectOfType<CountingPassthrough>().gameObject.scene == gameObject.scene) to prevent grabbing the wrong asset from other loaded scenes. And again, if you need this to carry data across scenes then have CountingPassthrough inherit from ScriptableObject instead of MonoBehaviour, create the new ScriptableObject to your project folder somewhere, and ignore that extra if check to constrain scene matching. Then just make sure you use a function to find it that includes assets if you use the cross-scene ScriptableObject approach.
EDIT:Forgot nested prefabs edgecase in unity 2018+ versions. You need to add this to account for it: && FindObjectOfType<CountingPassthrough>() != new UnityEngine.SceneManagement.Scene() I've updated the code snippet above. Sorry about that.
Since Updtade() is called once per frame, then switcher is set to 1 once per frame during the 8th second (and there is a lot of frames in 1 sec).
An answer could be something like this to prevent it from printing again :
if (CountdownTimer.switcher == 1)
{
if (!AlreadyDisplayed)
{
print("hey");
AlreadyDisplayed = true;
}
}
Where AlreadyDisplayed is a Boolean set to false when declared.
This should do what you want to achieve. :)
I have an input script that translates touches to directions (Left, Right, Up, Down) with a magnitude (0-1) in the Update loop, and when an input is detected this script fires a UnityEvent:
public class TouchAnalogStickInput : MonoBehaviour
{
[System.Serializable]
public class AnalogStickInputEvent : UnityEvent<Direction, float> { }
[Header("Events")]
[Space]
[Tooltip("Fired when a successful swipe occurs. Event Args: Swipe Direction, A normalized input magnitude between 0 and 1.")]
public AnalogStickInputEvent OnAnalogStickInput;
void Update()
{
...
if (successfulInputDetected)
{
OnAnalogStickInput.Invoke(mInputDirection, normalizedInputMag);
}
}
}
I'm subscribing to this OnAnalogStickInput event from the Unity Inspector to call my CharacterController2D.Move method, which takes a Direction and a magnitude:
public class CharacterController2D : MonoBehaviour
{
public void Move(Direction movementDirection, float normalizedInputMagnitude)
{
// Move the character.
}
}
Then I have another GameObject that I wish to rotate using the same input script. so I've attached TouchAnalogStickInput and SnapRotator to this GameObject, subscribing the OnAnalogStickInput event to call SnapRotator.Rotate:
public class SnapRotator : MonoBehaviour
{
public void Rotate(Direction movementDirection, float normalizedInputMagnitude)
{
// Rotate object.
}
}
At this point I've come to realize I'm no longer in charge of which game loops these methods are called from, e.g. I should be detecting input in Update, using this input in FixedUpdate for movement, and perhaps in my case I'd like to do the rotation last in LateUpdate. Instead both CharacterController2D.Move and SnapRotator.Rotate are being fired from the Update loop that the input code runs in.
The only other option I can think of is perhaps refactoring the Input script's code into a method call. Then having CharacterController2D and SnapRotator call this method in the Update loop, executing movement/rotation in the FixedUpdate or LateUpdate loop as required, e.g.:
public class CharacterController2D : MonoBehaviour
{
public TouchAnalogStickInput Input;
private var mMovementInfo;
void Update()
{
// Contains a Direction and a Normalized Input Magnitude
mMovementInfo = Input.DetectInput();
}
void FixedUpdate()
{
if (mMovementInfo == Moved)
// Move the character.
}
}
My question is: What's the best practice for decoupling scripts like these in Unity? Or am I taking re-usability too far/being overly afraid of coupling sub classes/components in game development?
Solution
In case it's helpful for anyone else here's my final solution, in semi-sudocode, credit to Ruzihm:
Utility class to store information about a detected input:
public class InputInfo
{
public Direction Direction { get; set; } = Direction.None;
public float NormalizedMagnitude { get; set; } = 0f;
public TouchPhase? CurrentTouchPhase { get; set; } = null;
public InputInfo(Direction direction, float normalizedMagnitude, TouchPhase currentTouchPhase)
{
Direction = direction;
NormalizedMagnitude = normalizedMagnitude;
CurrentTouchPhase = currentTouchPhase;
}
public InputInfo()
{
}
}
Input class:
public class TouchAnalogStickInput : MonoBehaviour
{
[System.Serializable]
public class AnalogStickInputEvent : UnityEvent<InputInfo> { }
[Header("Events")]
[Space]
[Tooltip("Fired from the Update loop when virtual stick movement occurs. Event Args: Swipe Direction, A normalized input magnitude between 0 and 1.")]
public AnalogStickInputEvent OnUpdateOnAnalogStickInput;
[Tooltip("Fired from the FixedUpdate loop when virtual stick movement occurs. Event Args: Swipe Direction, A normalized input magnitude between 0 and 1.")]
public AnalogStickInputEvent OnFixedUpdateOnAnalogStickInput;
[Tooltip("Fired from the LateUpdate loop when virtual stick movement occurs. Event Args: Swipe Direction, A normalized input magnitude between 0 and 1.")]
public AnalogStickInputEvent OnLateUpdateOnAnalogStickInput;
private bool mInputFlag;
private InputInfo mInputInfo;
void Update()
{
// Important - No input until proven otherwise, reset all members.
mInputFlag = false;
mInputInfo = new InputInfo();
// Logic to detect input
...
if (inputDetected)
{
mInputInfo.Direction = direction;
mInputInfo.NormalizedMagnitude = magnitude;
mInputInfo.CurrentTouchPhase = touch.phase;
// Now that the Input Info has been fully populated set the input detection flag.
mInputFlag = true;
// Fire Input Event to listeners
OnUpdateOnAnalogStickInput.Invoke(mInputInfo);
}
void FixedUpdate()
{
if (mInputFlag)
{
OnFixedUpdateOnAnalogStickInput.Invoke(mInputInfo);
}
}
void LateUpdate()
{
OnLateUpdateOnAnalogStickInput.Invoke(mInputInfo);
}
}
}
Character controller that subscribes to the OnFixedUpdateOnAnalogStickInput event.
public class CharacterController2D : MonoBehaviour
{
public void Move(InputInfo inputInfo)
{
// Use inputInfo to decide how to move.
}
}
Rotation class is much the same but subscribes to the OnLateUpdateOnAnalogStickInput event.
Here is one alternative, which mostly requires changes in your TouchAnalogStickInput class.
Set input state flags in Update and fire any relevant Events. Only this time, you fire an "OnUpdate" event (which in your specific case would not have anything registered to it):
void Update()
{
...
inputFlag_AnalogStickInput = false;
...
if (successfulInputDetected)
{
inputFlag_AnalogStickInput = true;
OnUpdateOnAnalogStickInput.Invoke(mInputDirection, normalizedInputMag);
}
}
And in TouchAnalogStickInput.FixedUpdate, call OnFixedUpdateOnAnalogStickInput, which would be registered with your Move
void FixedUpdate()
{
...
if (inputFlag_AnalogStickInput)
{
OnFixedUpdateOnAnalogStickInput.Invoke(mInputDirection, normalizedInputMag);
}
}
And so on with LateUpdate, which fires an event that your Rotate is registered with.
void LateUpdate()
{
...
if (inputFlag_AnalogStickInput)
{
OnLateUpdateOnAnalogStickInput.Invoke(mInputDirection, normalizedInputMag);
}
}
These OnFixedUpdateOn... events, of course, fire on every FixedUpdate where that flag is true. For most cases--including for Move--that is probably appropriate, but in other situations that may not be desirable. So, you can add additional events which fire on only the first FixedUpdate occurrence after an update. e.g.:
void Update()
{
...
firstFixedUpdateAfterUpdate = true;
inputFlag_AnalogStickInput = false;
...
if (successfulInputDetected)
{
inputFlag_AnalogStickInput = true;
OnUpdateOnAnalogStickInput.Invoke(mInputDirection, normalizedInputMag);
}
}
void FixedUpdate()
{
...
if (inputFlag_AnalogStickInput)
{
OnFixedUpdateOnAnalogStickInput.Invoke(mInputDirection, normalizedInputMag);
}
if (inputFlag_AnalogStickInput && firstFixedUpdateAfterUpdate)
{
OnFirstFixedUpdateOnAnalogStickInput.Invoke(mInputDirection, normalizedInputMag);
}
...
firstFixedUpdateAfterUpdate = false;
}
Hopefully that makes sense.
You may wish to look into customising Script Execution Order and having your gesture processor run before everything else.
Hope that helps. =)
I am currently developing a simple 2D game to learn many things about game development.
Nevertheless, I have some issues to create a score system.
I want to update the score every second, and adding 1 point for each second, and stop the count when the game is over.
I start to write this
public class scoremanager : MonoBehaviour {
public int score;
public GUIText distance;
void Start () {
score = 0;
}
void Update () {
StartCoroutine(updateScoreManager(1f, 1));
distance.text = score.ToString();
}
IEnumerator updateScoreManager(float totalTime, int amount) {
score += amount;
yield return new WaitForSeconds (totalTime);
}
}
The issues are that nothing shows up in the GUIText so I don't know if it works. And how can I stop the count when the game is over?
First off, I think you should use uGUI since that's Unity's accepted solution for UI now: http://blogs.unity3d.com/2014/05/28/overview-of-the-new-ui-system/
I can't really say what the problem is with your GUIText since I don't know if the position is off or whatever but I can tell you how to manage the counting better.
You don't need to use the Update function, using the update function defeats the purpose of using a Coroutine to update your text. Using a Coroutine gives you some control over when it starts Updating and when it stops. So one solution is to get rid of the Update function entirely so you can control it better:
public class scoremanager : MonoBehaviour {
public int score;
public GUIText distance;
private Coroutine scoreCoroutine;
void Start () {
score = 0;
scoreCoroutine = StartCoroutine(updateScoreManager(1f, 1));
}
IEnumerator updateScoreManager(float totalTime, int amount) {
while(true) {
score += amount;
distance.text = score.ToString();
yield return new WaitForSeconds (totalTime);
}
}
// example of ending game
void EndGame() {
StopCoroutine(scoreCoroutine);
scoreCoroutine = null;
}
}
This might look weird (why am I using an infinite loop)? But that's the beauty of Coroutines, you can write code like this since the yield statement allows you to return from the code and come back to the same spot later. Normally a while loop would cause your game to crash, but a yield statement let's your create a time while loop!
Hope this helped!
I can see few glitches in your code. Let's cover it first.
You are calling Coroutine in Update with delay time of 1 second. This means your Coroutine is executing 30 or 60 times per second.
You are assigning text in Update too. This may be the reason that it refreshes the text (Not sure about this)
In your Coroutine your delay i.e. WaitForSeconds is of no use here, as nothing is happening after that line of code.
I'm modifying your exact code so that you can understand that what you want can be achieved by this too :)
public class scoremanager : MonoBehaviour {
public int score;
public GUIText distance;
public bool isGameOver = false;
void Start () {
score = 0;
StartCoroutine(updateScoreManager(1f, 1));
}
void Update () {
}
IEnumerator updateScoreManager(float totalTime, int amount) {
while(!isGameOver){
score += amount;
distance.text = score.ToString();
yield return new WaitForSeconds (totalTime);
}
}
}
What are the changes...
No code in Update.
Call Coroutine only once.
In Coroutine it is changing the score and distance both, once per totalTime second(s). Until the isGameOver flag is false. Just make it true when your game is over.
I guess simple solution to your problem is InvokeRepeating
//Invokes the method methodName in time seconds, then repeatedly every repeatRate seconds.
public void InvokeRepeating(string methodName, float time, float repeatRate);
So your transfomed code colud be
public class scoremanager : MonoBehaviour {
public int score;
public GUIText distance;
void Start () {
InvokeRepeating("updateScoreManager",1f,1f);
}
void Update () {
distance.text = score.ToString();
}
void updateScoreManager() {
score += 1;
}
The issues are that nothing shows up in the GUIText so I don't know if
it works
What happens? Is it always zero or nothing is displayed? If nothing - check coordinates, layer etc. to ensure you positioned it properly. Refere to http://docs.unity3d.com/Manual/class-GUIText.html
And how can I stop the count when the game is over?
Use StopCoroutine(updateScoreManager) on game over event and don't forget to set score to zero.
Actually saying your code design is not good. It would be great to implement State, Observer, Component patterns. With state you can implement game states like Game, GameOver. With Observer you can notify your scores component. With component you serve single responsibility principle by placing score logics to score component.
At least consider separate start game initialization, update component, and game over logics.
I'm going the skip the lecture on the pattern you are implementing because I don't know what the rest of your code looks like, but the source of your update problem is here:
IEnumerator updateScoreManager(float totalTime, int amount)
{
score += amount;
yield return new WaitForSeconds(totalTime);
}
In practice, when I run into these kinds of issues I separate the failing logic from the source application to help reduce the amount of noise.
So, when I prototype your algorithim (code below) what happens is that score is always zero because the StartCoroutine should be using the return value of updateScoreManager to do the updating when ever .Update() is called.
If my prototyping seems to match how you are trying to implement this code, then I can post how I would fix this later.
But basically, instead of returning IEnumerator return an int that updates the score and that might get you past this issue.
Unless you can describe the use-case where you need the IEnumerator somewhere else in the codes?
Execute Prototype:
class Program
{
static void Main(string[] args)
{
Ans34612672 Ans34612672 = new Ans34612672();
Ans34612672.Execute();
Console.ReadKey();
}
}
Prototype Wrapper Class:
public class Ans34612672
{
public Ans34612672()
{
}
public bool Execute()
{
bool retValue = false;
scoremanager Manager = new scoremanager();
Manager.Start();
while(Manager.score < 20)
{
Manager.Update();
}
Console.WriteLine(Manager.distance);
Console.ReadKey();
return retValue;
}
}
Prototype Classes:
public class scoremanager : MonoBehaviour
{
public int score;
public GUIText distance;
public void Start()
{
score = 0;
distance = new GUIText();
}
public void Update()
{
IEnumerator UpdateResults = updateScoreManager(1f, 1);
StartCoroutine(UpdateResults);
distance.text = score.ToString();
}
private void StartCoroutine(IEnumerator enumerator)
{
//Implement Logic
}
IEnumerator updateScoreManager(float totalTime, int amount)
{
score += amount;
yield return new WaitForSeconds(totalTime);
}
}
internal class WaitForSeconds
{
private float totalTime;
public WaitForSeconds(float totalTime)
{
this.totalTime = totalTime;
}
}
public class GUIText
{
internal string text;
}
public class MonoBehaviour
{
}
I have one collision script in which I set a boolean to true or false
using UnityEngine;
using System.Collections;
public class IsTriggerLockCamera : MonoBehaviour {
public bool CameraLock = false;
public void OnTriggerStay2D(Collider2D other) {
CameraLock = true;
Debug.Log ("Im inside");
}
public void OnTriggerExit2D(Collider2D other) {
CameraLock = false;
Debug.Log ("I exited");
}
}
I want to access this boolean from my camera script, I tried this
if (CameraLock == true) {
Debug.Log ("Im locked");
}
However, I get an error saying that CameraLock doesn't exist in the current context. The boolean is public so I'm very confused.
EDIT: I feel like I didn't give good enough info so I'll start by posting the whole camera script and then clarifying.
using System;
using UnityEngine;
public class CameraFollowLockY : MonoBehaviour
{
public Transform target;
public float damping = 1;
public float lookAheadFactor = 3;
public float lookAheadReturnSpeed = 0.5f;
public float lookAheadMoveThreshold = 0.1f;
private float m_OffsetZ;
private Vector3 m_LastTargetPosition;
private Vector3 m_CurrentVelocity;
private Vector3 m_LookAheadPos;
private void Start()
{
m_LastTargetPosition = target.position;
m_OffsetZ = (transform.position - target.position).z;
transform.parent = null;
}
private void Update()
{
float xMoveDelta = (target.position - m_LastTargetPosition).x;
bool updateLookAheadTarget = Mathf.Abs(xMoveDelta) > lookAheadMoveThreshold;
if (updateLookAheadTarget)
{
m_LookAheadPos = lookAheadFactor*Vector3.right*Mathf.Sign(xMoveDelta);
}
else
{
m_LookAheadPos = Vector3.MoveTowards(m_LookAheadPos, Vector3.zero, Time.deltaTime*lookAheadReturnSpeed);
}
Vector3 aheadTargetPos = target.position + m_LookAheadPos + Vector3.forward*m_OffsetZ;
Vector3 newPos = Vector3.SmoothDamp(transform.position, aheadTargetPos, ref m_CurrentVelocity, damping);
transform.position = newPos;
transform.position = new Vector3(transform.position.x, 0, transform.position.z);
m_LastTargetPosition = target.position;
if (CameraLock == true) {
Debug.Log ("Im locked");
}
}
The IsTriggerLockCamera is a script I used for an invisible collider in Unity. My camera is focused on the player at all times, but I want it to stop moving when the player is close to reaching the edge of the map, so he can notice that the map is ending. The original plan was, that the collider would send out information when player enters it and then instead of Debug.Log ("Im Locked"); would be some code that would lock the camera in place. I don't know if this solution is very elegant and I'd like to apologize for not clarifying everything properly beforehand, but I started coding probably 2 months ago (I did only Rails websites) and I got into C# game development about a week ago so I'm still missing the terminology required to properly describe the problems I encounter. So far, no suggestion has worked. The closest working suggestion was OnoSendai's suggestion, but apparently it's not allowed to create MonoBehaviour using "new".
Edit2: Making the boolean static didn't work at first, but then I realized that I had to make some changes in my camera script as well, so it works now, but Philip said that it's a bad advice - I personally have no idea why, I assume that it's something like using !important in CSS, you just use it as a last resort because it makes the code not that flexible - so I'm still open to ideas.
You may want to try a full reference to the CameraLock property based on the object instance - as in
var objRef = new IsTriggerLockCamera(); // Just an example of object reference -
// You may already have one
// on your code.
if (objRef.CameraLock) {
Debug.Log ("Im locked");
}
That happens because CameraLock is marked as public, but not as static - it only exists on a instantiated object.
If your camera needs access to the IsTriggerLockCamera then dependency injection is good way to make it clear :
class Camera{
private readonly IsTriggerLockCamera _locker;
public Camera(IsTriggerLockCamera locker){
if (locker== null)
{
throw new ArgumentNullException("locker");
}
_locker = locker;
}
public void whatevermethod(){
if (_locker.CameraLock){
...
}
}
}
You need to set the variable you want to access from other scripts, static as well as public.
using UnityEngine;
using System.Collections;
public class IsTriggerLockCamera : MonoBehaviour {
public static bool CameraLock = false;
public void OnTriggerStay2D(Collider2D other) {
CameraLock = true;
Debug.Log ("Im inside");
}
public void OnTriggerExit2D(Collider2D other) {
CameraLock = false;
Debug.Log ("I exited");
}
}
Then you can access CameraLock :
if (CameraLock == true) {
Debug.Log ("Im locked");
}
I would like to suggest some approach,
Make CameraLock variable as protected.
Create a new class and make IsTriggerLockCamera class as base class.
consume the CameraLock variable and work on it.
Thanks,
C# has a function called { get; set } that you can use with a variable
This program is an example
Static Type
public static class AYO
{
public static string Variable1 { get; set; }
}
public class B
{
public void LOL()
{
string foo;
AYO.Variable1 = "Variable is now set";
foo = AYO.Variable1;
Console.WriteLine(foo);
}
}
Non Static
public class AYO
{
public string Variable1 { get; set; }
}
public class B
{
AYO ayo = new AYO();
public void LOL()
{
string foo;
ayo.Variable1 = "Variable is now set";
foo = ayo.Variable1;
Console.WriteLine(foo);
}
}