How to Create a Custom Unity Function - unity3d

Im trying to create a custom function in unity that performs setActive false but with Delay. Without Using Coroutine or Invoke Methods.
Im trying to create something like the Function Destroy in Unity:
Destroy(Object obj, float time)
But with SetActive.
What I am aiming for is a function like this SetActiveWithDelay(GameObject obj, bool stateToChange, float delayTime) with which we can write a line like SetActiveWithDelay(GameObjectToPass,true,2f) this which setActive the Gameobject to true by 2sec.
This can be easily done with Invoke or the use of Coroutine. But I just am curious about how to make a custom function this way. Makes things a lot simpler, instead of creating an IEnumerator and Using StartCoroutine or Invoke with string.
I looked into the object class on how Destroy was written and tried to replicate it. But it was confusing to me how it works. So, Let me know if anyone has any ideas on how to do this. Thanks!
Here below are some of the things I tried but again the custom function had a use of coroutine. But I feel this is all the same and I can directly use coroutine. So I'm interested to know if there is any other method or if going with coroutine and invoke is a better option.
With Coroutine:
Gameobject GOToMakeFalse;
void Start()
{
//Aiming for a simplified Custom Function to write this way. With Does As it says make gameobject false after two seconds
SetActiveWithDelay(GOToMakeFalse,false, 2f);
}
SetActiveWithDelay(GameObject obj ,bool inState,float delayTime)
{
//But without using a coroutire like this here
StartCoroutine(DelayedSetActive(obj,inState, delayTime));
}
public IEnumerator DelayedSetActive(GameObject GO,bool inBoolState, float dT)
{
yield return new WaitforSeconds(dT);
GO.SetActive(false);
}
And im also curious if we can extend the GameObject Class something like this.
GameObject.SetActiveWithDelay(true,2f);
Even this would be a cool addition and make development easy.

About extension, something like that should work:
public static class GameObjectExtensions
{
public static void SetActiveWithDelay(this GameObject obj, bool inState, float delayTime)
{
// ...
}
}
myGameObject.SetActiveWithDelay(true, 2f);
The important parts are the "static" class and the "this" in the first parameter of the static method.

You can use Async await or a simple timer script if you don't want to use coroutines. For Async you need to add the namespace
using System.Threading.Tasks;
You can read more on how to delay using Async here.

With the Help of #Vionix and #Kandohar. I have written an extension class of SetActiveWithDelay.
I have written 2 versions of it. Version 1 is with using "GameManager" as my GameManager class is a singleton instance & Version 2 is Having a MonoBehaviour script attached to the Gameobject you want to SetActive false and accessing that MonoBehaviour class. Hope this method will be helpful. Any alterations to this code by anyone are happily taken. Here is the Answer I came Up With.
Version-1: If you have a GameManager class that is a singleton instance
public static class GameObjectExtensions
{
public static void SetActiveWithDelay(this GameObject inObject, bool inState, float delayTime)
{
GameManager.Instance?.StartCoroutine(CallWithDelay(inObject, inState, delayTime));
}
static IEnumerator CallWithDelay(this GameObject inObject, bool inState, float delayTime)
{
yield return new WaitForSeconds(delayTime);
inObject.SetActive(inState);
}
}
Version-2: By accessing Monobehaviour class that is attached with the Gameobject we access.
public static class GameObjectExtensions
{
public static void SetActiveWithDelay(this GameObject inObject, bool inState, float delayTime)
{
MonoBehaviour mono = inObject.GetComponent<MonoBehaviour>();
mono.StartCoroutine(CallWithDelay(inObject, inState, delayTime));
}
static IEnumerator CallWithDelay(this GameObject inObject, bool inState, float delayTime)
{
yield return new WaitForSeconds(delayTime);
inObject.SetActive(inState);
}
}
Disclaimer: In Version Two, you need to make sure the Gameobject you're about to Setactive should definitely have a MonoBehaviour Script Attached to it.
With this now we can write GameObjectThatWeWantToChange.SetActiveWithDelay(true,2f);
I Thank #Vionix and #Kandohar for your answer and support. As said earlier if you guys have any idea on making my above snippet / answer better. Please do, and post it here. Thanks!

Related

Is there a way to test the position change of a class with MonoBehaviour?

I would like to learn the basics of testing, how to make a test
I am using the new unity input system (OnMove), I store that input in a vector2, later I use that vector2 in a function that moves the character (ProcessMovementOfShip).
The game works, I can move the player around with WASD, but I would love to have a test that verifies that the function responsible for movement works.
I have tried watching a couple of youtube videos about testing, it feels like the entry into tests are getting to steep, I would love to learn it, I can see the importance of it, I just dont know what I am doing and how to solve the problem at hand and I am starting to feel I should just put the whole thing on a shelf and hopefully return to it later.
How do I test that the player has moved?
PlayMode Test
public class player_movement
{
[UnityTest]
public IEnumerator player_moves_when_processship_is_fed_a_vector()
{
var gameObject = new GameObject();
var playerMovement = gameObject.AddComponent<PlayerMovement>();
Vector2 startPosition = playerMovement.transform.position;
playerMovement.ProcessMovementOfShip(new Vector2(1, 0));
yield return new WaitForFixedUpdate();
Vector2 endPosition = playerMovement.transform.position;
Assert.AreNotEqual(startPosition, endPosition);
}
}
EditMode Test
public class Movement
{
[Test]
public void start_position_of_player_is_0()
{
var gameObject = new GameObject();
var playerMovement = gameObject.AddComponent<PlayerMovement>();
var startPostion = playerMovement.transform.position;
playerMovement.ProcessMovementOfShip(new Vector2(1,0));
var endPosition = playerMovement.transform.position.x;
Assert.AreNotEqual(startPostion, endPosition);
}
}
PlayerMovement.cs
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerMovement : MonoBehaviour
{
[Header("Player Movement")]
[Range(5f, 20f)][SerializeField] float _moveSpeed = 15f;
private Rigidbody2D _rigidBody;
private Vector2 _rawInput;
void Awake()
{
_rigidBody = GetComponent<Rigidbody2D>();
if (_rigidBody == null) Debug.Log("No RigidBody2D detected!");
}
void FixedUpdate()
{
ProcessMovementOfShip(_rawInput);
}
public void ProcessMovementOfShip(Vector2 input)
{
Vector3 delta = input * _moveSpeed * Time.fixedDeltaTime;
delta += transform.position;
_rigidBody.MovePosition(delta);
}
private void OnMove(InputValue value)
{
Vector2 _rawInput = value.Get<Vector2>();
}
}
error
I try to check that the position of the character has changed, I get a "NullReferenceException" System.NullReferenceException : Object reference not set to an instance of an object
You would need to decouple the class with what Unity does and what you do.
I'll go with a simple example to demonstrate.
public class Provider : MonoBehaviour, IProvider
{
[SerializeField] private SomeClass m_someClass;
private Logic m_logic;
void Start()
{
m_logic = new Logic(this);
}
}
public interface IProvider
{}
public class Logic
{
private IProvider m_provider;
public Logic (IProvider provider)
{
m_provider = provider;
}
public int MethodToTest(int someValue)
{
}
}
At this point, we have three parts, the unity part where you will put everything that is Unity related, called Provider. This can be the Unity lifecycle such as Update, Start, any event related that the engine reports and all the connections. In this case, SomeClass is an object with some relevant data.
We have the interface which is the bridge between Provider and Logic. It will have its importance later on in the test.
And then the logic where all the code is stored.
We want to test MethodToTest, in which we are going to take an information from SomeClass and add someValue to it and return that value.
First thing, Logic does not know about Provider, it connects to it via the interface. We want to get some data from SomeClass, we will consider it has a member called Data returning an integer. We can now update the provider and the interface.
public class Provider : MonoBehaviour, IProvider
{
[SerializeField] private SomeClass m_someClass;
private Logic m_logic;
public int SomeClassData => m_someClass.Data;
void Start()
{
m_logic = new Logic(this);
}
}
public interface IProvider
{
int SomeClassData { get; }
}
You may think to pass the SomeClass object from the interface but doing it like this removes the dependency between Logic and SomeClass, they simply don't have any connection.
Now in Logic we can add the code:
public int MethodToTest(int someValue)
{
return m_provider.SomeClassData + someValue;
}
We can now move to the test
[Test]
public void LogicTest_MethodToTest()
{
// this would likely be in the Setup method to reuse provider and logic
IProvider provider = Subsitute.For<IProvider>();
Logic logic = new Logic(provider);
// Use NUnit to have the empty IProvider to return a given value when
// SomeClassData is called (else it returns default 0)
// This means that when MethodToTest will call for SomeClassData, it will return 5
provider.SomeClassData.Returns(5);
int result = logic.MethodToTest(10);
Assert.AreEqual(result, 10);
}
Thanks to the Interface, we no longer need to create all kind of object, the test is limited and NSubstitute takes care of the mocking (creating an empty object of an interface).
Now, this is fine but only one test is not so good, so we can start adding more test. We can copy paste the Test, rename the method and change the value to check but this is redundant. Let's use TestCase instead.
[TestCase(0, 0, 0)]
[TestCase(5, 0, 5)]
[TestCase(5, 5, 10)]
public void LogicTest_MethodToTest(int someClass, int someValue, int expected)
{
// this would likely be in the Setup method to reuse provider and logic
IProvider provider = Subsitute.For<IProvider>();
Logic logic = new Logic(provider);
// Assign the given value for flexible testing
provider.SomeClassData.Returns(someClass);
int result = logic.MethodToTest(someValue);
// compare with expectation
Assert.AreEqual(result, expected);
}
Each value given in the test case will be passed as parameter to the method when NUnit calls it. Instead of static values, you can now run a set of test to make sure the method works in many cases. You should then add corner cases like negative values or max int and so on to fiddle with your method until all green.
In this context, we do not text the Unity part. Simply because we know it works. Unity did the tests already so there is no need to check if the input works or if Start is being called properly.
Logic and the test rely on the fact that SomeClass would return specific values. In this case, we are only testing Logic so we assume SomeClass was implemented and tested properly so we don't need to test it here.
The IProvider can mock values via the return method.
To sum it up: Remove all the logic from Unity class (Provider) and move them to a Logic class. Create a bridge between Unity class and logic via an interface (IProvider). Anything needed in Logic from Provider goes through IProvider so to remove any dependency.
In the test, create the mock IProvider and pass it to the newly created Logic object. You can start testing.
The real benefit is that you now know the method works and if you were to modify it later on, you have your test to confirm it still does it all right.

Multiplayer UI using Mirror unity

im pretty new to the mirror package and im trying to build a simple multiplayer game thats basically played using UI elements only (buttons). A big part of the game is announcements that show up like - "Player X's Turn" for example.
At the moment the announcement only shows up in the host game, if the announcement came from the Networkbehaviour class that belongs to the player it would be easy to solve with a simple ClientRPC function but i want the UI functions to run from a different class that handles the UI elements.
what is the correct way to implement this? does the UIHandler need to be inhereting from any network class? would love some tips regarding this subject.
thanks in advance,
Amit Wolf.
A common strategy is to build a singleton networked event manager that triggers the RPC as an event.
public class EventManager: NetworkBehaviour
{
public static EventManager Instance;
void Awake()
{
if(Instance == null)
Instance = this;
else
Destroy(this);
}
public event Action<int> OnPlayerTurnChanged;
[ClientRpc]
public void ChangeTurn(int playerId)
{
OnPlayerTurnChanged?.Invoke(playerId);
}
}
Then you can subscribe to the event in any other script and perform logic:
public class UIScript: NetworkBehaviour
{
void Awake()
{
EventManager.Instance.OnPlayerTurnChanged+= UpdateUI;
timer = 0f;
}
void UpdateUI(int playerId)
{
//UI Logic to set the UI for the proper player
}
}

Need a way to use button click functionality vs GetKeyDown in Coroutine

This animator script works, however, in place of the Keycode input inside the WHILE LOOP, I need to use a UI button for mobile, which I haven't been able to figure out. I found an answer about putting a wrapper around it to make method available to click event, but have no idea how that's supposed to work within update function. It took me a long time to get this far, being a newbie to unity AND c#, so if you could provide a detailed answer or suggestion, I can get my life back, please help if you can.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using SWS;
public class goat_question : MonoBehaviour
{
private Animator anim;
public GameObject player;
public Text ResultText;
public Text AnswerText;
public Text AnswerText2;
public Button GoatButton;
void Start()
{
anim = GetComponent<Animator>();
Button btn = GoatButton.GetComponent<Button>();
btn.onClick.AddListener(TaskOnClick);
}
void TaskOnClick()
{
Debug.Log("You have clicked the button!");
}
// Update is called once per frame
void Update()
{
if (AnswerText.text.Equals(AnswerText2.text))
{
StartCoroutine(GoatWalkPathCoroutine());
}
IEnumerator GoatWalkPathCoroutine()
{
while (true)
{
if (Input.GetKeyDown(KeyCode.K))
{
anim.Play("goat_hi_walk");
player.GetComponent<splineMove>().enabled = true;
yield return new WaitForSeconds(27);
anim.Play("goat_hi_licking");
}
yield return null;
}
}
}
}
In a separate script for just the UI button, have a bool called isClicked or something, and when the button gets clicked set that to true. In this main script, you can reference the one you just made, and instead of the Input.GetKey, you can say, if(otherScript.isClicked).

Unity Extension Method GameObject

I'm trying to add an extension method to my gameobject, that's works, but my problem is the GameObject share the same result. My goal is to have a different result for each GameObject.
// AddExtension.cs
public static class GameObjectExtensions
{
private static int life;
public static int Life(this GameObject gameObject)
{
return life;
}
public static void ChangeLife(this GameObject gameObject, int numberToAdd)
{
life += numberToAdd;
}
}
And in my main code, I would like to manage GameObject like :
void Start()
{
GameObject.Find("Perso0").ChangeLife(2);
GameObject.Find("Perso1").ChangeLife(4);
GameObject[] rootGOs = UnityEngine.Object.FindObjectsOfType<GameObject>();
foreach (GameObject g in rootGOs)
{
if(g.name == "Perso0")
{
Debug.Log("Perso0 : " + g.Life());
}
if(g.name == "Perso1")
{
Debug.Log("Perso1 : " + g.Life());
}
}
}
But both GameObject have 6 in "Life" ( 2 + 4 )
I whould like to get only 2 for "Perso0" with Life and 4 with "Perso1" with Life
Do you have some clue to helping me ?
Thank you and best Regards
Because your life variable is static, it's going to be the same value you're editing every time you call ChangeLife on a GameObject.
Since extension methods need to belong to static classes, and a static class can only have static members, you cannot achieve the goal you want with extension methods.
Even if you could, it's not the right way to go with the Unity paradigm. With this setup, you're essentially saying "Every GameObject in my scene has a life value," which I don't think you want to do.
Instead, you can create your own components, as below.
public class Enemy : MonoBehaviour
{
[SerializeField] private int _initialHealth = 100;
private int _health = -1;
public int health { get { return _health; } }
private void Awake()
{
_health = _initialHealth;
}
public void IncrementHealth(int health)
{
_health += health;
}
}
This is just an example, but you can make something to suit your needs.
static applied to a member means all instances share a single copy. That is, there's only one life variable, which is modified when you call ChangeLife() on any GameOject.
Since extension methods have to be in a static class, I don't think you can accomplish what you want this way.
However, you should be able to add custom properties to your players and other objects in Unity. I don't remember if they're called "custom properties" exactly, but I know some of the basic tutorials like Roll-a-Ball cover this (or at least used to).

Unity Adding points to a user only once after collision

So I'm getting ready to create my first game, I just finished classes on the C# language so I apologize if I'm using stuff such as interfaces wrong and all that. However, for my question; I'm trying different things and seeing what works. I've created a coin, and a player. The coin works as it should, however sometimes when I collect it, it will give me twice the points it should. The coins value is 15, sometimes when I collect a coin it'll add 15 points, other times it will add 30. How do I prevent this from happening.
Here's my code:
Coin Controller Class:
public class CoinController : MonoBehaviour, IEconomy {
private int MoneyValue;
void Start () {
MoneyValue = 15;
}
void Update () {
}
void OnTriggerEnter(Collider col) {
if (col.CompareTag("Player")) {
Destroy(transform.gameObject);
Value();
}
}
public int Value() {
return EconomyController.Money += MoneyValue;
}
}
Economy Controller:
public class EconomyController : MonoBehaviour{
public static int Money;
void Start() {
Money = 0;
}
}
Economy Interface:
public interface IEconomy {
int Value();
}
I would like to point some things about your code:
A good practice when declaring variables is using lowerCamelCase:
thisIsLowerCamelCase
ThisIsNot
This is a variable name convention that is largely used in programming to differentiate Methods and Classes from variables.
Another thing I've noticed is that your "Money" variable is static and it is still being updated on your CoinController. I'd set this variable to be private int variable and use a setter to update it. With that in mind... Have you tried to use Debug.Log to check if the OnTriggerEnter is triggering twice before the object is destroyed?
Simply write:
Debug.Log ("This should only happen once!");
And play the game. If your console shows this message two times, this trigger is being called twice. Another thing that you might notice is that you are calling the Value () method after you called the Destroy (transform.gameObject).
I would do something like:
public class CoinController : MonoBehaviour{
private int moneyValue = 15;
private EconomyController economyController;
void Start (){
economyController = FindObjectOfType (typeof (EconomyController)) as EconomyController;
}
void OnTriggerEnter (Collider col) {
if (col.CompareTag("Player")) {
AddValue();
}
}
public int AddValue() {
EconomyController.money += moneyValue; //Option one.
EconomyController.AddMoney (moneyValue) ; //Option two.
DestroyGameObject ();
}
private void DestroyGameObject (){
Destroy(transform.gameObject);
}
}
Using the clean code principles, option 2 is using a public void function created inside the EconomyController class changing a private variable.
My intuition tells me that you are probably collecting two coins at the time. I'm not sure how you are setting out the coins but I've had a similar problem before.
Imagine a game of snake. Lets say you've programmed it so once you eat a square you create a new one to a random location. There is a probability that the new square would appear inside the snake so it would instantly be eaten. This could be why it happens only some of the time.
Try disabling the collider before you destroy it.
Destroying a gameobject isn't instant and it's (annoyingly) quite easy to set off triggers multiple times.
void OnTriggerEnter(Collider col) {
if (col.CompareTag("Player")) {
// Pseudo Code: GetComponent<TheColliderItIs>().Enabled = false;
Value();
Destroy(transform.gameObject);
}
}