How would I implement a User Control swapping method? - c#

I'm trying to swap "Scenes", the equivalent of levels in a game. I already know that Forms is not good for gaming, but what I'm making isn't so much a game as it is an interactive movie built out of some dialog options and animated .gifs.
I plan on building each "Scene" in a separate User Control, and then load up those User Controls onto the main form with a separate script that handles all of the scenes. In this separate script, I was going to add the "ChangeScene" method, so I could essentially trigger a scene to change once it finished, and have all scene changes in one script. I've run into two problems so far.
1: I can't seem to call anything from this Scene Handler script. I've been trying to use "Form1.SuspendLayout" and "Form1.ResumeLayout" to swap between Scenes smoothly, but I can't seem to call that, and then when I try to set a boolean in a Scene to true once the scene finishes, I can't actually check that bool from the Scene Handler script. How can I let this one script use the bools from each of the Scenes?
2: Can I call the "Form1.SuspendLayout" method from this Scene Handler script? So far, I was told I needed a reference to 'System.Windows.Forms.Control.SuspendLayout()', although I don't know how to reference that. It's not a namespace or assembly, so how do I reference it?
Sorry if this question seems like a simple mistake. I'm new to Windows Forms and not too well experienced with C#, so any question I could find about this used terms too complicated for me to understand.
Scene Handler here: class SceneHandler :
{
public virtual UserControl changeScene(UserControl currentScene, UserControl newScene)
{
Form1.SuspendLayout();
currentScene.Visible = false;
currentScene.Dock = DockStyle.None;
newScene.Visible = true;
newScene.Dock = DockStyle.Fill;
return newScene; //use "this.SuspendLayout" and "ResumeLayout"
}
}

Forget SuspendLayout.
Load all the UCs upfront but hidden.
Don't worry about performance before you run into problems!
Put all methods that involve more than one UC in the Form as public methods.
Put a public reference to your mainform in each UC and set it right after loading the UC.
Now each control can call mainform.someMethod().
Feel free to ask for more advice and help!

Related

Unity - Assign Button in Scene to Prefab

I'm having trouble assigning two Buttons to the Prefab after it is instantiated.
The buttons are in the scene and i don't know how to assign them.
Drag and Drop doesn't work of course. I'm aware of that.
When I make something like
btnnext = GameObject.Find("Next").GetComponent<Button>();
in the Start() function of the Prefabs script it doesn't work either.
Are there any other workarounds?
As #slowikowskiarkadiusz said GameObject.Find is not the best solution, because it's slow and error prone. But there is an easy solution.
On the script which is placed on the prefab make a public function, called AssignButton:
class ScriptOnPrefab : MonoBehaviour {
public void AssignButton(Button button) {
btnnext = button;
}
}
In the script where you instantite the prefab, you then link the buttons and assign them:
var instance = Instantiate(prefab);
var scriptOnPrefab = instance.GetComponent<ScriptOnPrefab>();
scriptOnPrefab.AssignButton(button);
Note: For this to work that the ScriptOnPrefab has to be on the root of the prefab, note on child objects.
Note: prefab is linked as a GameObject.
Note: If you link the prefab as ScriptOnPrefab you can skip the GetComponent step and immediatelly call the Assign method.
By 'doesn't work either' you mean that btnnext is null or you mean that .GetComponent<Button>() throws an exception? The first case would mean the object named "Next" that was found doesn't have a Button component on it. You can check if you're expecting it to have the Button component or maybe something slightly different. You could also have multiple objects named "Next" and the one you're getting is not the one you expect to get. The second case would mean that your object is most likely inactive. Find(string) doesn't search within inactive objects.
That being said - Find(string) is not reliable in any capacity and I would advice to avoid it (it's also terribly slow). Instead I would create a script to be placed on the object with the button. Inside of that script in the Awake() method I would assign the instance of the Button component to some kind of a public static field, so the other script can later pick it up (if you are dealing with two buttons it might be a list or two separate fields. Depends on your case I guess).

Hovered state is not triggered from PointerEnter event

I'm currently designing a custom inventory in Unity. Although OnClick method works properly for each slot, the OnHovered one does not go the same way. The latter was added through an EventTrigger component.
https://i.imgur.com/hxQGzl1.png
OnHovered can be found in InventorySlot script linked to the button, it only contains a one-line print, but, which prints nothing actually. Besides, I specify that there's an EventSystem in the scene which includes the Standalone Input Module.
public void OnHovered()
{
Debug.Log("HOVERED");
}
I know this is some very basic stuff but I'm unable to figure out what's the problem about, thank you!
I'm afraid simply adding the event trigger component isn't enough.
You have two options:
1. Have your inventory slot script derive from EventTrigger and implement the desired method.
2. Add the IPointerEnter interface implentation to your script and use the OnPointerEnter method.
Hope I was clear
Good luck

Restart button doesn't work on Unity 5.6

I'm trying to make a restart button on top of a game over screen. But it seems that the button doesn't work. I've put it on the On Click() menu and it kept doing nothing when i clicked on it.
My code:
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
public class ButtonCodes:MonoBehaviour
{
public void RestartGame()
{
SceneManager.LoadScene(0);
}
}
Look into 4.6 UI Buttons.
Create the button in your scene, and in OnClick, drag the gameObject with your ButtonCodes into the target field (left), and choose RestartGame in the right, which will be an option under ButtonCodes.
This might be what you've already done.. if that's the case, try using some Debug.Log("Button was Clicked"); statements. If these show up, it's likely you haven't set up the Build settings to include any levels. (Unity does not add levels here just by pressing play - you must do this yourself)
Assumming there are no errors, the most likely reason it's not working is that you didn't specify any scenes in File/Build Settings (Ctrl+Shift+B)
https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.LoadScene.html
That shows that you need to order your scenes in the Build Settings if you use the sceneBuildIndex. You could also use the second LoadScene that uses sceneName which may be more reliable if you constantly reorder scenes in Build Settings.
Also, if you haven't already, try using Debug.Log to make sure that that part of the code is actually being called.
try to put the name of the scene in the scenemanager and make sure your scene you gonna load is on your build settings
public void RestartGame()
{
SceneManager.LoadScene(//name of your scene);
}
1- Sometimes when i duplicate a button and use it with other functions, it doesn't work.
2- Another reason that i faced was when i use inherited class, it doesn't work many times too.
3- Sometimes you forget to add your "0" scene in to build setting as well.
Thank you all for answering. All of the things that you said is already been done and still, the button won't work. In the end, i solved the problem myself. I realized that my Canvas doesn't have an EventSystem that was an important thing for a button to work.

Add delay in C# in Unity3D projects

I currently work on Unity and using C# language.
By far, I need to do something (in smallest case, load another scene).
My question is, how to make this something happens after a few seconds I put the cursor on the area?
this is my code right now.
if (_newGameButton.Contains (Event.current.mousePosition)) {
Application.LoadLevel (1);
Destroy (this);
}
I want to add delay when _newGameButton actives. Right now, when I move my cursor over _newGameButton it'll load scene 1 immediately. I have tried many ways such as using Invoke and WaitForSeconds. None works. If the mistake is how I use it, how's the right way? Thank you so much for your help.
EDIT: This question is answered and I have another question in Activate and Deactivate Invoke function.
To make timers and delays in Unity, simply use Invoke
void Start()
{
Debug.Log("hello from Start.");
Invoke("Test", 3f);
}
private void Test()
{
Debug.Log("hello from 'Test'");
}
Also very handy is InvokeRepeating
Invoke("Test", 5f, 0.5f);
It's that easy.
In your case
if (_newGameButton.Contains (Event.current.mousePosition))
{
Invoke("YourSceneName");
}
private void ChangeScenes()
{
UnityEngine.SceneManagement.SceneManager.LoadScene("ScreenMain");
}
You MUST USE the scene name. Don't forget you MUST HAVE DRAGGED THE SCENE, TO YOUR SCENE LIST. Look at "Build Settings" "Scenes in Build".
Note
That's not really how you load scenes in Unity these days. They changed the syntax. it's more like this...
UnityEngine.SceneManagement.SceneManager.LoadScene("ScreenMain");
If you want to load asynchronously
AsyncOperation ao;
ao = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync("SceneName");
while (!ao.isDone)
{
Debug.Log("loading " +ao.progress.ToString("f2"));
yield return null;
}
If you have any questions about scene loading, ask them separately as a new question. Note that you should almost certainly NOT do this "Destroy(this);".
Well ill help you understand something a bit bigger then just pausing cause it seems you are trying to create on OnClick event, nowadays its being used differently.
what you realy want to do is just create the SceneLoader script which can be a very general script such as the following,
using UnityEngine;
using UnityEngine.UI;
public class UIController : MonoBehaviour {
public int numOfSceneToLoad; // number of the scene from the build settings
public void LoadSceneNumber(numOfSceneToLoad){
UnityEngine.SceneManagement.SceneManager.LoadScene(numOfSceneToLoad);
}
}
and then you want to attach this script to your main UI Canvas/Holderplace, go to your button and at the bottom of the inspector you will see onClick(),
click on the + sign and then make the setup as follows, drag the Object you have attached this script to and put it under the "runtime" laber in that small onClick() window, then scroll and find this specific function we just created and in there you can modify the value of numOfSceneToLoad to the scene you want to load, and woilla you have a Dynamic script to load any scene you wish to.
on this note ill reffer you to some pretty amazing toturials made by unity that will teach you how to make a proper UI in unity5.
http://unity3d.com/learn/tutorials/topics/user-interface-ui/ui-button
From your description, it looks to me that your _newGameButton is set to inactive by default and you want it to active and user intractable after few delay.
For that you can create a script and keep reference of _newGameButton in it, something like this:
public GameObject _newGameButton;
and on an event say GameOver, implement like this:
void GameOver()
{
Invoke("EnableNewGameButtonAfterDelay", 5f);
}
void EnableNewGameButtonAfterDelay()
{
_newGameButton.SetActive(true);
// now this game object is active and user can click on it.
}
In this example i demonstrated that the game has been over and after 5 second delay user have new game button set to enable. [This code is not in executable form and just to give you an idea]
Hope this helps!

Load a form without showing it

Short version: I want to trigger the Form_Load() event without making the form visible. This doesn't work because Show() ignores the current value of the Visible property:
tasksForm.Visible = false;
tasksForm.Show();
Long version: I have a WinForms application with two forms: main and tasks. The main form is always displayed. The user can either click a button to open the tasks form, or click some buttons that just run a task directly without opening the tasks form.
When a user asks to run a task directly, I'd like to just call some public methods on the tasks form without showing it. Unfortunately, the task logic depends on stuff that happens in the Form_Load() event. The only way I can find to trigger Form_Load() is to call Show(). The best I've been able to do is to show the form in the minimized state:
tasksForm.WindowState = FormWindowState.Minimized;
tasksForm.Show();
I suppose the cleanest solution would be to pull the tasks logic out of the tasks form and into a controller class. Then I can use that class from the main form and from the tasks form, and only load the tasks form when I need it visible for the user. However, if it's an easy thing to load the form without displaying it, that would be a smaller change.
Perhaps it should be noted here that you can cause the form's window to be created without showing the form. I think there could be legitimate situations for wanting to do this.
Anyway, good design or not, you can do that like this:
MyForm f = new MyForm();
IntPtr dummy = f.Handle; // forces the form Control to be created
I don't think this will cause Form_Load() to be called, but you will be able to call f.Invoke() at this point (which is what I was trying to do when I stumbled upon this SO question).
It sounds to me like you need to sit down and re-think your approach here. I cannot imagine a single reason your public methods need to be in a form if you are not going to show it. Just make a new class.
I totally agree with Rich B, you need to look at where you are placing your application logic rather than trying to cludge the WinForms mechanisms. All of those operations and data that your Tasks form is exposing should really be in a separate class say some kind of Application Controller or something held by your main form and then used by your tasks form to read and display data when needed but doesn't need a form to be instantiated to exist.
It probably seems a pain to rework it, but you'll be improving the structure of the app and making it more maintainable etc.
From MSDN:
Form.Load
Occurs before a form is displayed for the first time.
Meaning the only thing that would cause the form to load, is when it is displayed.
Form.Show(); and Form.Visible = true; are the exact same thing. Basically, behind the scenes, Show checks for various conditions, then sets Visible to true. So obviously, setting visible to false (which it already is) before showing the form is meaningless.
But let's forget the technicalities. I completely agree with Rich B and Shaun Austin - the logic shouldn't be in that form anyway.
Sometimes this would be useful without it being bad design. Sometimes it could be the start of a migration from native to managed.
If you were migrating a c++ app to .NET for example, you may simply make yourwhole app a child window of the .NET form or panel, and gradually migrate over to the .NET by getting rid of your c++ app menu, status bar, toolbar and mapping teh .NEt ones to your app using platform invoke etc...
Your C++ app may take a while to load, but the .NET form doesn't..in which you may like to hide the .NEt form until your c++ app has initialised itself.
I'd set opacity=0 and visible=false to false after calling show, then when your c++ app loads, then reverse.
If you make the method public, then you could access it directly.... however, there could be some unexpected side effects when you call it. But making it public and calling it directly will not draw the screen or open the form.
Move mandatory initialization code for the form class out of the Load event handler into the constructor. For a Form class, instantiation of an instance (via the constructor), form loading and form visibility are three different things, and don't need to happen at the same time (although they do obviously need to happen in that order).
None of the answers solved the original question, so, add the below, call .Show() to load the form without showing it, then call .ShowForm() to allow it to be visible if you want to after:
private volatile bool _formVisible;
protected override void SetVisibleCore(bool value)
{
base.SetVisibleCore(_formVisible);
}
public void ShowForm()
{
_formVisible = true;
if (InvokeRequired)
{
Invoke((Action) Show);
}
else
{
Show();
}
}

Categories