This article is fromInspoy miscellaneous collection | food chicken inspoy learning record
preface
UI is an important part of any game, and as a programmer we don’t have to worry about what makes the UI look good for the time being, but rather how to implement features efficiently. In many games with heavy UI, the UI is often more important than the core gameplay. When the UI becomes large, we need to use the editor to design the UI, and use the code generation tool to generate the relevant code. After that, we only care about how to implement the relevant logic. Because all the UI is code controlled to add and remove dynamically. Here the MVP architecture is used to achieve, Model is mainly in the form of singleton class, used to store related data; View is generated according to the UI Prefab code, manual does not modify, pure automatic generation; Presenter is where we need to write code logic.
Imagined workflow
- UI designers use the Unity Editor to design the UI
- Save the entire UI View as a Prefab
- Generate a VIew and Presenter using a code generation tool (first time only)
- Write code logic in Presenter
- UI needs to be adjusted and Prefab needs to be modified
- Regenerate the View code
- Modify the logic in Presenter
The best thing about this is that when the requirements change, we just need to adjust the layout of the UI controls in the editor, and then generate the relevant code. We don’t need to modify the view-related code, only the game logic needs to be modified. Unity’s Scene file never needs to be modified, no matter how complex the UI is. When working with multiple people, it maximizes the separation of different people’s work and avoids conflict to the greatest extent possible.
UI events
This is an important point. UGUI UI events are based on Unity EventSystem, but we have already implemented our own EventSystem. Here we convert unity.eventsystem events into our own events by dynamically adding components. So you can unified handling Button, for example, we need to monitor the click event, in order to release this event in the form of our SFEvent, I inherit UnityEngine EventSystems. EventTrigger SFUIEventListener wrote a custom component
public class SFUIEventListener : EventTrigger
{
public SFEventDispatcher dispatcher = null;
public static SFEventDispatcher getDispatcherWithGo(GameObject go)
{
// Static method that automatically creates an event dispatcher for UI controls
var listener = go.GetComponent<SFUIEventListener>();
if (listener == null)
{
listener = go.AddComponent<SFUIEventListener>();
}
if (listener.dispatcher == null)
{
listener.dispatcher = new SFEventDispatcher(go);
}
return listener.dispatcher;
}
public override void OnPointerClick(PointerEventData)
{
// Here is a Unity native event
if(dispatcher ! =null) { dispatcher.dispatchEvent(SFEvent.EVENT_UI_CLICK); }}// more...
};Copy the code
MVP
Of course, there are only V and P here
View
The UI needs to be created and designed in an editor, and saved to Prefab.
Canvas - UICamera - UIRoot
UGUI supports 3 of UI rendering: direct overlay, UI camera, as a 3D object is rendered by the main camera. We use a separate UICamera to achieve this, Canvas – Render Mode select Screen Space-camera, then create a Camera child node under Canvas named UICamera and set it to Canvas.
Now we can see that the Game window now shows what UICamera shot, so we need to set UICamera’s properties. First, change Clear Flags to Depth Only, and the default option is to redraw the skybox, so that the view of the camera below it is completely blocked. Culling Mask selects UI to allow the camera to capture only the CONTENT of the UI Layer (all children of the Canvas node are UI Layer by default, and you can see the Tag and Layer above the Transform component to see that the Layer is ALREADY UI).
Camera Projection is an orthogonal ProjectionOrthographic
Next up is UIRoot, which is actually a full-screen transparent Panel, except that its PosZ value is 500, so that the UI camera can see the UI content properly. The value of 500 is arbitrary, only because if PosZ is 0, UICamera will not be able to shoot the UI content. A value greater than one (more than 20) will display normally.
Ok, blah, blah, you can export, export tool involves the editor extension, will not do = = first leave a hole, then fill (not the final export result is about this:
public class SFTestView : SFBaseView
{
public Text lblTitle{ get { returnm_lblTitle; }}public Button btnOk { get { returnm_btnOk; }}private Text m_lblTitle;
private Button m_btnOk;
private SFTestPresenter m_presenter;
void Start()
{
GameObject lblTitleGO = SFUtils.findChildWithParent(gameObject, "lblTitle");
if(lblTitleGO ! =null)
{
m_lblTitle = lblTitleGO.GetComponent<Text>();
}
// other widgets
m_presenter = new SFTestPresenter();
m_presenter.initWithView(this); }}Copy the code
SFBaseView, which inherits from MonoBehavior, provides a public method for adding event listeners:
public void addEventListener(Component widget, string eventType, SFListenerSelector sel)
{
var dispatcher = SFUIEventListener.getDispatcherWithGo(widget.gameObject);
dispatcher.addEventListener(eventType, sel);
}Copy the code
Finally, don’t forget to mount the View script on Prefab
Presenter
The Presenter file is created when the UI View is first exported, and the initial default is very simple:
public class SFTestPresenter
{
SFTestView m_view;
public void initWithView(SFTestView view)
{ m_view = view; }}Copy the code
And then we can start writing the logic, like we want to add a click event to the button that changes the lblTitle text when it’s clicked. Add a click event listener to the initWithView() method of the Presenter class:
m_view.addEventListener(m_view.btnOk, SFEvent.EVENT_UI_CLICK, onButtonClicked);Copy the code
The callback function is then implemented
void onBtnClicked(SFEvent e)
{
m_view.lblTitle.text = "Button Clicked!";
}Copy the code
Finally, I added a UI View to the scene. Here I used a static variable to store the UIRoot node of the current scene. This static variable belongs to the class SFSceneManager, which is mounted in an Empty node (Empty GO) of each scene. Find the UIRoot node for the scene when the scene loads and save it in a static variable, and empty the static variable when the scene unloads.
public class SFSceneManager : MonoBehaviour
{
static public GameObject uiRoot = null;
void Start()
{
var uiRootGO = GameObject.Find("UIRoot");
if (uiRootGO == null)
{
SFUtils.logWarning("UIRoot node not found in current scene"); } uiRoot = uiRootGO; }}Copy the code
So we can add UI anywhere, just write the following code:
GameObject.Instantiate(vwPrefab, SFSceneManager.uiRoot.transform);Copy the code
Finally, you can see the effect
The complete code
The code snippet posted above only contains key parts due to space limitations, the full code can be found on my Github