Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Messages - ArenMook

Pages: [1] 2 3 ... 1445
NGUI 3 Support / Have a question that hasn't already been answered here?
« on: February 17, 2018, 10:46:00 PM »
NGUI's support forums are a wealth of information that have been filled by 6 years of dedicated support, but times are changing and so should the support. The best place to ask new questions that haven't already been answered on the forums is in Discord: -- look for the #ngui-support section.

For those that don't know, Discord is a text / voice communication program, and it's accessible via your browser.

I hate forums. Always did. Chat is much easier, and with much better response times to boot as I'm always there when I'm awake.

NGUI 3 Support / Re: Is NGUI still actively supported?
« on: February 17, 2018, 10:38:51 PM »
I've been... neglecting the forums lately, I admit. I've moved all support to Discord back in December ( What I've been telling people (and what the's contact form says) is -- if you can't find the answer to your question by googling it / searching's forums, then you're welcome to ask in discord and I'll respond (and if it's a question specifically to me, be sure to tag me with @Aren so I see it).

Dev Blog / Re: July 21, 2017 - Grass
« on: December 31, 2017, 10:38:21 PM »
Yeah, I don't want to deal with support, making all the necessary art, preview vids, and all the other hassle related to submitting to the asset store nowadays. I might release it as a public github project later, however.

NGUI 3 Support / Re: Poor Scrollview Performance
« on: November 29, 2017, 08:47:39 AM »
What's the point of making duplicate posts on the same issue?

NGUI 3 Support / Re: Batching Issue!
« on: November 29, 2017, 08:44:13 AM »
NGUI doesn't use Unity's batching system. It does batching itself automatically.

Dynamic fonts break up batching, so if you have this:

Sprite (depth 0)
Label (depth 1)
Sprite (depth 2)
Label (depth 3) will have 4 draw calls because it's the only way to render everything properly. Instead you should be organizing your UI like this:

Sprite (depth 0)
Sprite (depth 1)
Label (depth 2)
Label (depth 3)

This will result in 2 draw calls because sprites will be batched with sprites and labels will be batched with labels.

If you stop using Unity's dynamic fonts and instead use atlas fonts, as recommended by NGUI's labels, you will draw the entire UI in 1 draw call and the depth order won't matter, making your job easier.

NGUI 3 Support / Re: Issues with Nintendo 3DS
« on: November 29, 2017, 08:40:43 AM »
You should be downloading NGUI from Unity 5.6 rather than 2017. It will download the Unity 5 version for you. That said, 5.4 isn't officially supported anymore by NGUI, so you may need to tweak it to get it working.

NGUI 3 Support / Re: Custom touch input for VR
« on: November 29, 2017, 08:39:23 AM »
UICamera.onCustomInput is just a delegate that will be called by UICamera when processing events. What you do inside the function is up to you, but you will ultimately need to call UICamera's Notify function to trigger actual events. You can use UICamera.ProcessOthers as an example.

Looking over your code I'm not sure what "HoverPosition" is or where it's coming from that you assign your original touch position as. I also think that the tap count should be 0, not 1. You haven't actually tapped anything yet at that point, merely pressed on something.

You don't seem to assign UICamera.GetTouch anywhere, and it's needed to actually retrieve your touch info. You have the function, you just don't assign it as a UICamera.GetTouch delegate anywhere.

NGUI 3 Support / Re: Lag in Scrollview Drag
« on: November 29, 2017, 08:19:19 AM »
Anchoring sounds like your problem. That, and dynamic fonts. CacheFontForText is not an NGUI function, so I assume it's something to do with Unity's dynamic font system. Your anchoring causes things to move, which causes draw buffers to be marked as changed, which in turn causes draw calls to be re-created, which leads to rebuilding all of your text. There in lies your slow-down.

TNet 3 Support / Re: data that all players can access?
« on: November 25, 2017, 10:22:40 PM »
Admin file not being created should actually be resolved in the current Pro version. I noticed it about a week ago and fixed it.

TNet 3 Support / Integrating TNet with Steam Networking
« on: November 23, 2017, 03:08:47 AM »
Wrote this in the Dev Blog forum as it's rather descriptive (and long). If you're a Steamworks developer using TNet, and you want to be able to use Steam's networking with your TNet-powered game, you will find this useful:

Dev Blog / Nov 23, 2017 -- Integrating TNet with Steam Networking
« on: November 23, 2017, 03:07:24 AM »
Back in Windward days, I would sometimes get comments from players saying they can't join their friends' games, no matter what they tried. Let's face it, while us devs find it a trivial task to open up a port on the router, and TNet does indeed us UPnP to do this automatically, the players are less savvy and can sometimes be behind such firewalls that even UPnP can't breach. Fortunately, Steam has ways around it, and they've indeed taken care of all of this with their networking API. It uses UDP to simulate TCP-like functionality, but since it's UDP, NAT punchthrough is easy to do. Better still, if NAT punchthrough fails, Steam allows using its servers as relays to still make it possible for two players to play together.

Of course there are limitations: first, both players must be using Steam. But hey, let's face it -- Steam is the best platform out there for gamers. Is there a reason NOT to use it? Second, the packet size is limited to just over 1 MB -- but quite frankly if your game is sending out packets greater than 1 MB in size, you're probably doing something wrong. And last but not least, the API itself is a little... weird. To explain just what I mean by that, let's look at the steps required with the latest version of TNet (from the Pro repository as of this writing).

First, you will want to grab Steamworks.NET here:

Next, let's start by making a new controller / wrapper class. I called mine "Steam" for simplicity.
  1. using UnityEngine;
  2. using Steamworks;
  3. using TNet;
  5. public partial class Steam
  6. {
  7.     CSteamID userID;
  9.     void Awake ()
  10.     {
  11.         SteamAPI.Init();
  12.         SteamUserStats.RequestCurrentStats();
  13.         userID = SteamUser.GetSteamID();
  14.         DontDestroyOnLoad(gameObject);
  15.     }
  17.     void OnDestroy () { SteamAPI.Shutdown(); }
  19.     void Update () { SteamAPI.RunCallbacks(); }
  20. }
With the script attached to a game object in your first scene, Steamworks API will be initialized, and it will be shut down when your application does. The Update() function simply lets Steam do its thing.

Next, we need to create a special connection wrapper class for Steam to use with TNet. By default TNet will use its sockets for communication, but since we'll be using Steam here, we should bypass that. Fortunately the latest Pro version of TNet has a way to specify an IConnection object for every single TcpProtocol which essentially inserts its operations in between of TNet's sockets, making all of this possible. I chose to make this class inside the Steam class, but it's up to you where you place it.
  1. public partial class Steam
  2. {
  3.         [System.NonSerialized] static System.Collections.Generic.Dictionary<CSteamID, TcpProtocol> mOpen = new System.Collections.Generic.Dictionary<CSteamID, TcpProtocol>();
  4.         [System.NonSerialized] static System.Collections.Generic.HashSet<CSteamID> mClosed = new System.Collections.Generic.HashSet<CSteamID>();
  6.         class P2PConnection : IConnection
  7.         {
  8.                 public CSteamID id;
  9.                 public bool connecting = false;
  10.                 public bool disconnected = false;
  12.                 public bool isConnected { get { return !disconnected; } }
  14.                 public bool SendPacket (Buffer buffer) { return SteamNetworking.SendP2PPacket(id, buffer.buffer, (uint)buffer.size, EP2PSend.k_EP2PSendReliable); }
  16.                 public void ReceivePacket (out Buffer buffer) { buffer = null; }
  18.                 public void OnDisconnect ()
  19.                 {
  20.                         if (!disconnected)
  21.                         {
  22.                                 disconnected = true;
  24.                                 var buffer = Buffer.Create();
  25.                                 buffer.BeginPacket(Packet.Disconnect);
  26.                                 buffer.EndPacket();
  27.                                 SteamNetworking.SendP2PPacket(id, buffer.buffer, (uint)buffer.size, EP2PSend.k_EP2PSendReliable);
  28.                                 buffer.Recycle();
  30.                                 lock (mOpen)
  31.                                 {
  32.                                         mOpen.Remove(id);
  33.                                         if (!mClosed.Contains(id)) mClosed.Add(id);
  34.                                 }
  36.                                 if (TNManager.custom == this) TNManager.custom = null;
  37.                         }
  38.                 }
  39.         }
  40. }
So what does the P2PConnection class do? Not much.  It keeps the Steam ID identifier since that's the "address" for each "connection". I use both terms in quotations because instead of addresses, Steam's packets are sent directly to players, and players are identified by their Steam ID. Likewise, there are no "connections" established with Steam's API. Remember how I said that the API itself is a bit weird? Well, this right here is what I meant. Instead of the expected workflow where a connection must first be established and acknowledged before packets start flowing, Steam's approach is different. You simply start sending packets to your friend like you're the best buddies in the world. Your friend's client gets the packets along with a special notification of whether to accept the incoming packets or not. If the client chooses to accept the packets, they can be received immediately. There is no "decline" option. In fact, no notification is sent back to the first player at all, and trying to do so will actually auto-accept the packets! So the options are: accept packets, or ignore them, leaving the other player wondering.

Anyway, so back to P2PConnection. ReceivePacket() can't be handled here, because packets don't arrive via sockets. Instead they arrive in one place, and must then be queued in the right place -- which we'll get to in a bit. For now, the only two useful functions in that class are SendPacket -- which simply calls the appropriate SteamNetworking API function, and the OnDisconnect notification. This one needs some explanation.

Since there is no concept of "connections" with Steam's API, we have to account for this ourselves. So to keep it short, we're simply sending a Disconnect packet to the other player when we're done. We're also keeping a list of known open and closed "connections" (and I'm going to tire of using quotation marks by the end of this post...). So to sum it up, when TNet says that the connection is closed, we still send out a Disconnect packet to the other player, ensuring that they know to stop sending us packets.

Moving on -- we have the custom connection class for TNet. We should now use it. Let's start by writing the Connect function:
  1.         void ConnectP2P (CSteamID id)
  2.         {
  3.                 if (TNManager.custom == null && !TNManager.isConnected && !TNManager.isTryingToConnect && mInst != null)
  4.                 {
  5.                         CancelInvoke("CancelConnect");
  7.                         var p2p = new P2PConnection();
  8.                = id;
  9.                         p2p.connecting = true;
  10.                         TNManager.custom = p2p;
  11.                         TNManager.client.stage = TcpProtocol.Stage.Verifying;
  13.                         var buffer = Buffer.Create();
  14.                         var writer = buffer.BeginPacket(Packet.RequestID);
  15.                         writer.Write(Player.version);
  16.                         writer.Write(TNManager.playerName);
  17.                         writer.Write(TNManager.playerData);
  18.                         var size = buffer.EndPacket();
  19.                         SteamNetworking.SendP2PPacket(id, buffer.buffer, (uint)size, EP2PSend.k_EP2PSendReliable);
  20.                         buffer.Recycle();
  22.                         Invoke("CancelConnect", 8f);
  23.                 }
  24. #if UNITY_EDITOR
  25.                 else Debug.Log("Already connecting, ignoring");
  26. #endif
  27.         }
Inside the ConnectP2P function we create our custom P2PConnection object and assign it as TNManager.custom -- meaning it will be used by TNManager's TcpProtocol for all communication instead of sockets. We also immediately send out a packet requesting the ID. TNet does this whenever a TCP connection is established, so we should follow the same path. This packet will be received by the other player (the one hosting the game server), and a response will be sent back, actually activating the connection.

One other thing the function does is it calls the "CancelConnect" function via a delayed invoke, which will simply act as a time-out:
  1.         void CancelConnect ()
  2.         {
  3.                 var p2p = TNManager.custom as P2PConnection;
  5.                 if (p2p != null && p2p.connecting)
  6.                 {
  7.                         TNManager.client.stage = TcpProtocol.Stage.NotConnected;
  8.                         TNManager.onConnect(false, "Unable to connect");
  9.                         TNManager.custom = null;
  10.                 }
  11.         }
It's also useful to have a String-accepting version of the Connect function, for convenience:
  1.         static public bool Connect (string str)
  2.         {
  3.                 ulong steamID;
  5.                 if (mInst != null && isActive && !str.Contains(".") && ulong.TryParse(str, out steamID))
  6.                 {
  7.                         mInst.ConnectP2P(new Steamworks.CSteamID(steamID));
  8.                         return true;
  9.                 }
  10.                 return false;
  11.         }
So -- we now have a way to start the connection with a remote player. We now need to handle this operation on the other side. To do that, we need to subscribe to a few events. First is the P2PSessionRequest_t callback -- this is the notification that effectively asks you if you want to receive packets from the other player. Ignoring it is one option, but simply calling AcceptP2PSessionWithUser is more useful. Just in case though, we only do it if there is a game server running. We also need to handle the error notification:
  1.         // Callbacks are added to a list so they don't get discarded by GC
  2.         List<object> mCallbacks = new List<object>();
  4.         void Start ()
  5.         {
  6.                 // P2P connection request
  7.                 mCallbacks.Add(Callback<P2PSessionRequest_t>.Create(delegate (P2PSessionRequest_t val)
  8.                 {
  9.                         if (TNServerInstance.isListening) SteamNetworking.AcceptP2PSessionWithUser(val.m_steamIDRemote);
  10.                 }));
  12.                 // P2P connection error
  13.                 mCallbacks.Add(Callback<P2PSessionConnectFail_t>.Create(delegate (P2PSessionConnectFail_t val)
  14.                 {
  15.                         Debug.LogError("P2P Error: " + val.m_steamIDRemote + " (" + val.m_eP2PSessionError + ")");
  16.                         CancelInvoke("CancelConnect");
  17.                         CancelConnect();
  18.                 }));
  19.         }
With this done, the server-hosting client is now able to start accepting the packets. We now need to actually receive them. To do that, let's expand the Update() function:
  1.         // Buffer used to receive data
  2.         static byte[] mTemp;
  4.         void Update ()
  5.         {
  6.                 SteamAPI.RunCallbacks();
  8.                 uint size;
  9.                 if (!SteamNetworking.IsP2PPacketAvailable(out size)) return;
  11.                 CSteamID id;
  13.                 lock (mOpen)
  14.                 {
  15.                         for (;;)
  16.                         {
  17.                                 if (mTemp == null || mTemp.Length < size) mTemp = new byte[size < 4096 ? 4096 : size];
  19.                                 if (SteamNetworking.ReadP2PPacket(mTemp, size, out size, out id))
  20.                                 {
  21.                                         int offset = 0;
  23.                                         while (offset + 4 < size)
  24.                                         {
  25.                                                 int expected = mTemp[offset++];
  26.                                                 expected |= (mTemp[offset++] << 8);
  27.                                                 expected |= (mTemp[offset++] << 16);
  28.                                                 expected |= (mTemp[offset++] << 24);
  30.                                                 var buffer = Buffer.Create();
  31.                                                 var writer = buffer.BeginWriting();
  32.                                                 writer.Write(expected);
  33.                                                 writer.Write(mTemp, offset, expected);
  34.                                                 buffer.BeginReading(4);
  35.                                                 AddPacketP2P(id, buffer);
  37.                                                 offset += expected;
  38.                                         }
  39.                                 }
  41.                                 if (!SteamNetworking.IsP2PPacketAvailable(out size))
  42.                                 {
  43.                                         UnityEngine.Profiling.Profiler.EndSample();
  44.                                         return;
  45.                                 }
  46.                         }
  47.                 }
  48.         }
The code above simply checks -- is there a packet to process? If so, it enters the receiving loop where data is read into a temporary buffer, and then placed into individual buffers that TNet expects. Basically the stuff that TNet does under the hood when receiving packets. Since we're doing the receiving, we also need to do the splitting. Each packet is added to the appropriate queue by calling the AddPacketP2P function which we will write now:
  1.         static void AddPacketP2P (CSteamID id, Buffer buffer)
  2.         {
  3.                 TcpProtocol tcp;
  5.                 if (mOpen.TryGetValue(id, out tcp))
  6.                 {
  7.                         // Existing connection
  8.                         var p2p = tcp.custom as P2PConnection;
  9.                         if (p2p != null && p2p.connecting) p2p.connecting = false;
  10.                 }
  11.                 else if (TNServerInstance.isListening)
  12.                 {
  13.                         // New connection
  14.                         var p2p = new P2PConnection();
  15.                = id;
  17.                         lock (mOpen)
  18.                         {
  19.                                 tcp = TNServerInstance.AddPlayer(p2p);
  20.                                 mOpen[id] = tcp;
  21.                                 mClosed.Remove(id);
  22.                         }
  23.                 }
  24.                 else if (TNManager.custom != null)
  25.                 {
  26.                         // New connection
  27.                         var p2p = TNManager.custom as P2PConnection;
  28.                         if (p2p == null) return;
  30.                = id;
  31.                         tcp = TNManager.client.protocol;
  33.                         lock (mOpen)
  34.                         {
  35.                                 mOpen[id] = tcp;
  36.                                 mClosed.Remove(id);
  37.                         }
  38.                 }
  39.                 else return;
  41.                 tcp.AddPacket(buffer);
  42.         }
The AddPacketP2P function checks if it's an existing connection first. If it is, the connection is marked as no longer trying to connect, and the packet is added to the TcpProtocol's receiving queue. If the connection is not yet open, we check to see if a game server is running. If it is, a new P2PConnection is created and a new player gets created on the server. This player won't have an IP address or an open TCP socket. Instead, it has the reference to the P2PConnection which it will use for communication.

Last but not least, if the game server is not running, the function checks to see if the TNManager has its own P2P reference set. We assigned it in ConnectP2P(), so this means that this check effectively makes sure that we are trying to connect. If this check passes, the packet is added to the TNManager client's incoming queue.

If all else fails, the packet is simply ignored.

So that's that! This is all you need to be able to effectively overwrite TNet's networking functionality with Steam Networking. Before you go though, you may want to make it possible for people to be able to right-click their friends in the Steam friends list and use the "Join game" option. To do this, you need to set the rich presence's "+connect" key:
  1.         public void AllowFriendsToJoin (bool allow)
  2.         {
  3.                 if (allow) SteamFriends.SetRichPresence("connect", "+connect " + userID);
  4.                 else SteamFriends.SetRichPresence("connect", "");
  5.         }
Simply call Steam.AllowFriendsToJoin(true) when you want to make it possible for them to join. Personally, I placed it inside a function called from TNManager.onConnect, but it's up to you where you need it to be.

You will also need to subscribe to the Join Request like so:
  1.                 // Join a friend
  2.                 mCallbacks.Add(Callback<GameRichPresenceJoinRequested_t>.Create(delegate (GameRichPresenceJoinRequested_t val)
  3.                 {
  4.                         var addr = val.m_rgchConnect;
  5.                         addr = addr.Replace("+connect ", "");
  6.                         if (!Connect(addr)) TNManager.Connect(addr);
  7.                 }));
When a player chooses the "Join Friend" option from within a game, GameRichPresenceJoinRequested_t will be triggered. When a player uses the same option while the game isn't launched, GameRichPresenceJoinRequested_t won't be sent. Instead a "+connect <string>" command-line option will be sent to the game's executable -- so you will want to handle that yourself.

Anyway, that's it! This is all you need to make your TNet-powered game be able to use Steam Networking. I hope this helps someone!

TNet 3 Support / Re: object[] support missing in TNet 3.1.0
« on: November 23, 2017, 01:36:42 AM »
This... I'm not sure what you're doing there, but trying to serialize an array of objects doesn't seem like a good idea. Each object can be a different type, with its own serialization. It's asking for trouble.

You adding System.Object[] means something was saved as "System.Object[]" -- text form of it. I'm surprised that worked at all, to be honest.

NGUI 3 Support / Re: Flickering text on UILabels in scrollview.
« on: November 23, 2017, 01:33:11 AM »
When you scroll, assuming culling is disabled on the scroll view, there is no overhead. Nothing gets changed, a single transform simply gets moved, that's it.

NGUI 3 Support / Re: Lag in Scrollview Drag
« on: November 23, 2017, 01:31:50 AM »
I can only suggest making good use of the Profiler to find the culprit. I certainly see no issues with this on my end, and I've had some long scroll views.

NGUI 3 Support / Re: One-sided UISprite functionality
« on: November 23, 2017, 01:30:39 AM »
Open up NGUI's shaders such as Unlit - Transparent Colored. Find the line "Cull Off" and change it to whatever you need it to be.

Pages: [1] 2 3 ... 1445