Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

GTGD's Unity Multiplayer Tutorials

Discussion in 'Community Learning & Teaching' started by GTGD, Feb 7, 2012.

?

Please select each option you agree with (you can select multiple options)

Poll closed Dec 22, 2015.
  1. I like that the videos are covering a complete project.

    230 vote(s)
    88.1%
  2. I find the videos are detailed enough for me to understand and follow.

    197 vote(s)
    75.5%
  3. I find the video quality is clear enough.

    186 vote(s)
    71.3%
  4. I find the audio quality is clear enough.

    180 vote(s)
    69.0%
  5. The narrator doesn't send me to sleep.

    143 vote(s)
    54.8%
  6. In general the topics covered are interesting.

    170 vote(s)
    65.1%
  7. I find the website useful.

    130 vote(s)
    49.8%
  8. The Gamer To Game Developer logo looks good.

    119 vote(s)
    45.6%
  9. I would like to suggest topics that GTGD should cover in the future.

    103 vote(s)
    39.5%
Multiple votes are allowed.
  1. gumboots

    gumboots

    Joined:
    May 24, 2011
    Posts:
    298
    Hey GTGD, I've just finished the health label section of video 7 and am having a little issue. I think I can work out why it's happening, I just don't know how to fix it, and you don't have the problem in your test run!

    Basically players can't see health bars of people who were in the game before them. So player 1 can see every subsequent joiner. Player 2 can see every subsequent joiner but not player 1. Player 3 can see every subsequent joiner but not player 1 and 2 and so on.

    Obviously all the code is sound, as the health bars are showing up, it's just something to do with game objects that are already playing that seems to be causing the issue. Any thoughts would be great!

    -------
    EDIT
    -------
    Scratch that, I solved it. When player connected and created Game Objects for all the current players, they were trying to find the Camera.Main, but that doesn't exist until he chooses a team. I set a function in the update to run if myCamera == null and that's to check for camera.main. Easy!
     
    Last edited: Apr 11, 2012
  2. ChaosMax

    ChaosMax

    Joined:
    Jun 23, 2010
    Posts:
    21
    Can anyone confirm that video number 5 works in 3.5?
    (I cant get the mouse arrow to hide)
     
  3. gormee

    gormee

    Joined:
    Mar 20, 2012
    Posts:
    14
    Thanks for the advice GTGD, I would love to show you the gravity gun in action however I am bound by an secrecy as its not a personal project but rather a collaboration with others but I will endeavour to at least have some screens for you.

    Thanks again!
     
  4. GTGD

    GTGD

    Joined:
    Feb 7, 2012
    Posts:
    436
    Hello ChaosMax,

    The CursorControl script is really short and simple so it seems odd that your cursor is not getting locked or is it that the cursor is getting locked but still visible on the screen? I'm using Unity 3.4.0 so I'm not familiar with the changes that have been introduced with Unity 3.5. Have you checked that the variables the CursorControl script are looking at are able to get set to false? Try connecting as a client while running the game in the Unity editor and see if there are any errors. If you typed the script out yourself then check if there are any spelling mistakes with finding the GameObjects in the Start function.

    If the cursor is getting locked but still visible on the screen then maybe this is something new and maybe you have to make use of Screen.showCursor as well. That's all I can really think of with the supplied info.
     
    Last edited: Apr 11, 2012
  5. gormee

    gormee

    Joined:
    Mar 20, 2012
    Posts:
    14
    Hey GTGD, I'm sorry but I forgot to mention that only 4 players will be in the game at any one time and there will only be one instance of each player class. So my gravity gun will be the only one in the game, I tried to implement your 2nd solution but I'm a bit noobish and I'm not sure exactly how to do this so I failed pretty bad :(

    I did manage to get the projectiles moving across the whole network by removing the if(networkView.isMine == true) from the projectileUpdate script however its the movement is extremely jerky and pretty much unplayable. I did base its code on your movementUpdate script and here it is again slightly updated

    Code (csharp):
    1. public class ProjectileUpdate : MonoBehaviour
    2. {
    3.    
    4.     //Variables
    5.     private Vector3 lastPosition;
    6.     private Quaternion lastRotation;
    7.     private Transform myTransform;
    8.    
    9.    
    10.    
    11.  
    12.     // Use this for initialization
    13.     void Awake ()
    14.     {
    15.             myTransform = transform;
    16.            
    17.             networkView.RPC("updateMovement", RPCMode.OthersBuffered, myTransform.position, myTransform.rotation);
    18.            
    19.     }
    20.    
    21.     // Update is called once per frame
    22.     void Update ()
    23.     {
    24.         //If the box has moved at all then fire an RPC to update across the network
    25.        
    26.         if(Vector3.Distance(myTransform.position, lastPosition) >=0.01)
    27.         {
    28.             lastPosition = myTransform.position;
    29.  
    30.             networkView.RPC("updateMovement", RPCMode.OthersBuffered, myTransform.position, myTransform.rotation);
    31.         }
    32.        
    33.         if(Quaternion.Angle(myTransform.rotation, lastRotation) >=1)
    34.         {  
    35.             //Capture the box's rotation before the RPC is fired
    36.             //This determins if the box has turned in the previous if statement
    37.            
    38.             lastRotation = myTransform.rotation;
    39.            
    40.             networkView.RPC("updateMovement", RPCMode.OthersBuffered, myTransform.position, myTransform.rotation);
    41.         }
    42.     }
    43.    
    44.    
    45.    
    46.    
    47.     [RPC]
    48.     void updateMovement (Vector3 newPosition, Quaternion newRotation)
    49.     {  
    50.         transform.position = newPosition;
    51.        
    52.         transform.rotation = newRotation;
    53.     }
    54.    
    55.    
    56. }
    Here is my code for how the builder class's weapon instantiates objects for my mover class's gravity gun

    Code (csharp):
    1.  
    2. var projectile : Transform;
    3. var projectile2 : Transform;
    4. var initialSpeed = 1.0;
    5. var reloadTime = 0.5;
    6. var ammoCount = 20;
    7. var ammoCount2 = 20;
    8. private var lastShot = -10.0;
    9. var spawnBox : int = 1;
    10. var spawnPlatform: int = 2;
    11.  
    12. function Fire () {
    13.     // Did the time exceed the reload time?
    14.     if (Time.time > reloadTime + lastShot  ammoCount > 0) {
    15.         // create a new projectile, use the same position and rotation as the Launcher.
    16.         Network.Instantiate (projectile, transform.position, transform.rotation, spawnBox);
    17.        
    18.         lastShot = Time.time;
    19.         ammoCount--;
    20.     }
    21. }
    22.  
    23. function Fire2 () {
    24.     // Did the time exceed the reload time?
    25.     if (Time.time > reloadTime + lastShot  ammoCount2 > 0) {
    26.         // create a new projectile, use the same position and rotation as the Launcher.
    27.         Network.Instantiate (projectile2, transform.position, transform.rotation, spawnPlatform);
    28.        
    29.         lastShot = Time.time;
    30.         ammoCount--;
    31.     }
    32. }
    and the gravity gun code (please excuse the lack of comments on this one, it wasn't written by me). it basically has 4 different states that it goes through in order for it to pick up an obj.
    Code (csharp):
    1. var catchRange = 30.0;
    2.     var holdDistance = 4.0;
    3.     var minForce = 10;
    4.     var maxForce = 10;
    5.     var forceChargePerSec = 30;
    6.     var layerMask : LayerMask = -1;
    7.      
    8.     enum GravityGunState { Free, Catch, Occupied, Charge, Release};
    9.     private var gravityGunState : GravityGunState = 0;
    10.     private var rigid : Rigidbody = null;
    11.     private var currentForce = minForce;
    12.  
    13.     function FixedUpdate () {
    14.         if(gravityGunState == GravityGunState.Free  Screen.lockCursor == true) {
    15.             if(Input.GetButton("Fire1")) {
    16.                 var hit : RaycastHit;
    17.                 if(Physics.Raycast(transform.position, transform.forward, hit, catchRange, layerMask)) {
    18.                     if(hit.rigidbody) {
    19.                         rigid = hit.rigidbody;
    20.                         gravityGunState = GravityGunState.Catch;
    21.  
    22.                     }
    23.                 }
    24.             }
    25.         }
    26.         else if(gravityGunState == GravityGunState.Catch  Screen.lockCursor == true) {
    27.             rigid.MovePosition(transform.position + transform.forward * holdDistance);
    28.             networkView.RPC("updatePos", RPCMode.All, rigid.transform.position, rigid.transform.rotation);
    29.             if(!Input.GetButton("Fire1"))
    30.                 gravityGunState = GravityGunState.Occupied;
    31.         }
    32.         else if(gravityGunState == GravityGunState.Occupied  Screen.lockCursor == true) {
    33.             rigid.MovePosition(transform.position + transform.forward * holdDistance);
    34.             networkView.RPC("updatePos", RPCMode.All, rigid.transform.position, rigid.transform.rotation);
    35.             if(Input.GetButton("Fire1"))
    36.                 gravityGunState = GravityGunState.Charge;
    37.         }
    38.         else if(gravityGunState == GravityGunState.Charge  Screen.lockCursor == true) {
    39.             rigid.MovePosition(transform.position + transform.forward * holdDistance);
    40.             if(currentForce < maxForce) {
    41.                 currentForce += forceChargePerSec * Time.deltaTime;
    42.             }
    43.             else {
    44.                 currentForce = maxForce;
    45.             }
    46.             if(!Input.GetButton("Fire1")  Screen.lockCursor == true)
    47.                 gravityGunState = GravityGunState.Release;
    48.                
    49.  
    50.         }
    51.         else if(gravityGunState == GravityGunState.Release  Screen.lockCursor == true) {
    52.             rigid.AddForce(transform.forward * currentForce);
    53.             currentForce = minForce;
    54.             gravityGunState = GravityGunState.Free;
    55.  
    56.         }
    57.     }
    58.    
    59.     @RPC
    60.     function updatePos(pos:Vector3, rot:Quaternion){
    61.         if(rigid != null){
    62.             rigid.transform.position = pos;
    63.             rigid.transform.rotation = rot;
    64.         }
    65.     }
    I would really appreciate your input on how to smoothen out the movements of the objs when the gravity gun is trying to move them across the network. I have also spoken to my project group and they would be more than happy for me to demonstrate it for you once we get it working because of all the help you've given so far. :p

    Thanks so much again, the community wouldn't thrive without people like you!
     
    Last edited: Apr 12, 2012
  6. GTGD

    GTGD

    Joined:
    Feb 7, 2012
    Posts:
    436
    Hello gormee,

    if(NetworkView.isMine == true) as it is, was not working for you, but now that you've removed it a new problem has been created!

    For simplicity imagine that there are only two players in the game. Let's say you are the one with gravity gun so in your game, on your computer, when the projectile is moved the RPC in the ProjectileUpdate script is fired off to update the projectile's position and rotation in the other player's computer, but we have a problem, a big problem!

    You see, after a few fractions of a second the other computer will have received the RPC to update the projectile's position. The ProjectileUpdate script on this computer will see that the position of the projectile has changed and will fire off the RPC to everyone else, which includes you, the original sender!!!!.

    In the meantime you were happily moving your projectile with your gravity gun (and sending RPCs) but a few fractions of a second after your original RPC was sent you will receive an RPC from the other player that will cause your projectile to move back a bit in time. In the meantime remember you have been sending RPCs and you are now going to be receiving RPCs from the other player that are slightly in the past and because of this the movement of the projectile will be horrible, jerky and uncontrollable! I hope that made sense.

    You need to resolve the issue of who is sending the update position RPC (Only the player manipulating this object should send the RPC or the server). Once you've got that working you'll notice that the projectile movement will still be a little bit jerky but in a sensible. Once you've gotten that far I can give you some ideas on removing natural jerkiness due to the Movement Update script.

    The video that you'll need to understand most is video 6 health and damage system, once you understand this video, and how the HealthAndDamage script is working, you will find it way easier to come with a solution. Sorry though it is long and torturous at 2h 40 mins and I remember feeling rather fed up after producing it, it took something like 12 hours or more to produce!

    You've gotten this far, which is most of it, so you'll definitely figure this one out.
     
    Last edited: Apr 12, 2012
  7. gormee

    gormee

    Joined:
    Mar 20, 2012
    Posts:
    14
    Cheers GTGD.. Will give it a bash and see how it goes. I've already watched half of vid 6 and I thought the reminder wouldn't be as relevant but I will rewatch it again and try to digest the whole vid this time.

    Thanks again for your help!!!!
     
  8. GTGD

    GTGD

    Joined:
    Feb 7, 2012
    Posts:
    436
    I'm intending to change the style of the series with video 14 and I'd like your opinion on whether you think this is a good idea or not.

    My intention is to reduce the length of all future videos considerably by not typing out all the code and comments. Instead I'll explain how the systems work and I'll open up the fully written code and explain how it is working. I'll of course still show in detail how to setup GameObjects, textures, and where to attach the scripts, but I just wouldn't be typing out the scripts anymore.

    I feel that even people new to Unity, after having watched the first 13 videos (over 17 hours of video) with me typing out code, that they wouldn't need to see me typing out each line of code anymore.

    Please let me know your thoughts on whether you think this new approach would benefit you or not.
     
  9. Reactorcore

    Reactorcore

    Joined:
    May 19, 2011
    Posts:
    105
    That sounds like a good idea. The videos do go on quite slow to be honest, but on the other hand its all valuable as the viewer learns the thought-process behind the code.

    So what I'm saying is trying to preserve that idea of explaining the thought-process behind the code of why you wrote it as such. Shortening the video is ok and makes sense as long as it doesn't leave out the aspect I just mentioned.

    The reason why this is so important is that people will learn to think for themselves on how to think when designing and writing their own code, rather than follow blindy simon-says style.

    The intro presentations of your videos are extremely valuable for this purpose where you do those power-point style presentations and list the goals you're trying to achieve, so definetely keep those too.
     
  10. modulo

    modulo

    Joined:
    Sep 10, 2011
    Posts:
    31
    if the videos are always so well commented, reduce time of videos is not a problem, the forum is to find solutions :)
     
  11. Ithkul

    Ithkul

    Joined:
    Apr 8, 2012
    Posts:
    22
    Yes, shorter videos with prewriten code is the way to go. I have to honestly admit I'm only using the video to listen when I'm stuck on something I don't understand. Thus I'm very grateful for the script downloads you have provided. Saves me alot of time.

    Off topic:
    I wanna add that I'm going to attempt AI inside your prototype. The AI damage taken and received will be handled with the involved client. the client will then send an RPC to update the health of said client. In other words. The client damaged by an AI will supply this info. The client that damaged an AI will also supply this info. This to avoid ping related issues. And avoiding stress to the server.

    The server will be in charge of AI movement and supplying RPC with who the AI is pursuing. The clients will then try to guess the most likely location of said AI. This to avoid to many RPC vectors running wild across the network. Hope to be able to support hundreds of AI with this approach. I will leave out Pathfinding in my first attempt.
     
    Last edited: Apr 14, 2012
  12. technotdc

    technotdc

    Joined:
    Oct 21, 2011
    Posts:
    60
    Hi

    I have a "small" problem and I can not fix ....(I'm on video 10 now)

    Everything seems to work fine, but when a player kills another and I click "Respawn" then change the name automatically.

    example ( pepe to (628)



    whenever I press "respawn" it change the "name" to a new number .....

    ¿ what I'm doing wrong ???


    Thanks
     
    Last edited: Apr 15, 2012
  13. GTGD

    GTGD

    Joined:
    Feb 7, 2012
    Posts:
    436
    LOL technotdc, you are not doing anything wrong, and I explain what is happening in an earlier video. This will happen because our player name is saved to PlayerPrefs and our PlayerName script is retrieving the player's name from PlayerPrefs. We are connecting with multiple players from the same computer so they are all using the same PlayerPrefs and our PlayerName script will not let the player have the same name as another player in the game so it assigns a random number.

    Here's an example:

    Player juan joins the game. The name juan is saved to PlayerPrefs.

    Now you connect with a second client on the same computer. This client is pepe, so now pepe overwrites juan in PlayerPrefs.

    Now you are testing and you use pepe to destroy juan.

    juan respawns and the PlayerName script will access PlayerPrefs to retrieve the player's name. The name in PlayerPrefs is pepe because that was the last name to get saved to PlayerPrefs. The PlayerName script will not allow the respawning player to also have the name pepe because there is already a pepe in the game and so it assigns a random number (say 628) and now this random number is saved to PlayerPrefs.

    Now you use 628 to destroy pepe. When pepe respawns the name in PlayerPrefs is 628 but there is already a 628 in the game so now pepe will get another random number.

    We're only encountering name changing because we have multiple clients running on our own computer. In a normal multiplayer game with friends the name would never change because you would only be using one client on your computer.
     
  14. technotdc

    technotdc

    Joined:
    Oct 21, 2011
    Posts:
    60
    Aaaahhhhhhhh.........ok ........ thank you very much for explaining


    very grateful
     
  15. gormee

    gormee

    Joined:
    Mar 20, 2012
    Posts:
    14
    I really enjoy your tuts because you take the time to explain what you're doing rather than just showing us code and expecting us to follow blindly. As long as you do this I think your tuts will stand out from the rest, I certainly do not mind if you cut out the footage of you actually typing code
     
  16. gormee

    gormee

    Joined:
    Mar 20, 2012
    Posts:
    14
    Hey GTGD, got a quick question regarding the CommLogWindow in your video 8 tutorial. Is it possible to log messages being sent to this window in a text file to be stored at the end of each multiplayer session either at the server's end or on a path local to each player's machine?

    UPDATE:
    Found a working solution.. would be happy to share my solution if anyone wants to know..
     
    Last edited: Apr 16, 2012
  17. GTGD

    GTGD

    Joined:
    Feb 7, 2012
    Posts:
    436
    Actually, I'm interested in your solution gormee because I've learning about saving and loading and I was about to suggest this link that really helped me out How to script a save/load option but I'm keen to learn other ways as well.

    Btw, I'd like to thank everyone who has taken time out to give me feedback on my new intended strategy for the videos. I'm aiming to get video 14 out by the 25th of April and sorry for taking so long, but I'll be focusing time on ensuring that the video is every bit as good as previous ones and in fact better. Video 14 will be about construction blocks and players being able to place them within a virtual grid. The players send their request to the server to place a block and it is the server that takes care of instantiating the block across the network. You'll see that we'll cover a lot of interesting code and there's a lot involved in placing blocks. The only downside will be that we can only place a few hundred blocks before the game can no longer handle it because each block is being rendered individually. I'll have to study the advanced topic of combining and regenerating meshes to overcome that cap, but that's for the future.
     
  18. gormee

    gormee

    Joined:
    Mar 20, 2012
    Posts:
    14
    Hey GTGD, I'm basically saving a text file to the game data folder so I've added the following lines to your communication window code:

    Code (csharp):
    1.  
    2. //Added this to the top
    3. using System.IO;
    4. using System.Text;
    5. using System;
    6.  
    Code (csharp):
    1.  
    2. //Variables
    3. private string path; //path to save chat log to
    4. private string fileName; //file name for the chat log
    5.  
    This names the text file
    Code (csharp):
    1.  
    2. void Start ()
    3.     {
    4.         spawnManager = GameObject.Find("SpawnManager");
    5.        
    6.         spawnScript = spawnManager.GetComponent<SpawnScript>();
    7.        
    8.         //Define the time stamp and initial file name
    9.         var dt = DateTime.Now;
    10.         var timeStamp = String.Format("{0:hh-mm-ss-yyyy-MM-dd}", dt);
    11.         fileName = "/Initial ChatLog.txt";
    12.        
    13.         //Access the initial chatlog to see if it is empty
    14.         var sr = new StreamReader(Application.dataPath + "/" + fileName);
    15.         var fileContents = sr.ReadToEnd();
    16.         sr.Close();
    17.        
    18.         //if its not empty create a new log with a timestamp
    19.         if(fileContents != "")
    20.         {
    21.             fileName = "/ChatLog " + timeStamp + ".txt";   
    22.         }
    23.        
    24.     }
    25.  
    This tells the Unity where to save the text file (saves to game_data folder of an exe build)
    Code (csharp):
    1.  
    2. //Add this line to the update function
    3. path = Application.dataPath + fileName;
    4.  
    This function allows writing to the text file
    Code (csharp):
    1.  
    2. //This code is from MSDN's code base for System.IO FileStream
    3. //This goes into the functions area of the code (e.g. OnGUI, Update, Start)
    4. private static void AddText(FileStream fs, string value)
    5.     {
    6.         byte[] info = new UTF8Encoding(true).GetBytes(value);
    7.         fs.Write(info, 0, info.Length);
    8.     }
    9.  
    This ensures that as each message is sent out, it will also be written to the text file
    Code (csharp):
    1.  
    2. //Edited the RPC code with 3 new lines and adjusted the formatting so that its more readable on the text file
    3. [RPC]
    4.     void SendMessageToEveryone(string message, string pName)
    5.     {
    6.         //This string is displayed by the label in the comm log window
    7.        
    8.         communication = Environment.NewLine + "[" + pName + "]" + ": " + message + Environment.NewLine + communication + Environment.NewLine;
    9.        
    10.         using (FileStream fs = File.Create(path))
    11.         {
    12.             AddText(fs, communication + Environment.NewLine);  
    13.         }
    14.        
    15.     }
    16.    
    17.    
    18.     [RPC]
    19.     void TellEveryonePlayerJoined (string pName)
    20.     {
    21.         communication = Environment.NewLine + "\n" + pName + " has joined the game." + "\n" + "\n" + communication;
    22.     }
    23.  
    The code will create an initial text file when the first message is sent on the network and the text file is located in the game_data folder of the exe build. When a new multiplayer session is started the code will check if the initial chatlog is empty, if it isn't it will create a new chatlog with a timestamp on it (again this happens when the first message is sent on the network, reason for this being the additional code added to SendMessageToEveryone RPC). My code is a modified version of this tut:

    http://www.youtube.com/watch?v=Nc31yvpLlco
     
    Last edited: Apr 17, 2012
  19. ChaosMax

    ChaosMax

    Joined:
    Jun 23, 2010
    Posts:
    21
    Hi GTGD, I ran the game in the Unity editor and see if there were any errors and this is what I got.

    NullReferenceException
    UnityEngine.GameObject.GetComponent[MultiplayerScript] () (at C:/BuildAgent/work/b0bcff80449a48aa/Runtime/ExportGenerated/Editor/UnityEngineGameObject.cs:18)
    CursorControl.Start () (at Assets/Scripts/CursorControl.cs:30)

    above pointing to this line:
    multiScript = multiplayerManager.GetComponent<MultiplayerScript>();

    and
    NullReferenceException: Object reference not set to an instance of an object
    CursorControl.Update () (at Assets/Scripts/CursorControl.cs:42)

    above pointing to this line:
    if(multiScript.showDisconnectWindow == false)
     
  20. GTGD

    GTGD

    Joined:
    Feb 7, 2012
    Posts:
    436
    Thank you for posting this up gormee and the code is nicely commented. When I eventually get to the end of this series I'll give this a shot and I actually feel like having a short additional video based on the code you've added because it is interesting.
     
  21. GTGD

    GTGD

    Joined:
    Feb 7, 2012
    Posts:
    436
    The error that you have usually comes up due to fairly simple mistakes and NullReferenceException indicates to me that something isn't being found and it probably has to do with finding the MultiplayerScript. Check the spelling of the MultiplayerManager GameObject, maybe its name doesn't match what is in the script. If you're using my scripts then the problem is something trivial that you'll need to find. If you typed out the scripts then check the spelling of all names that have something to do with accessing the MultiplayerScript in the CursorControl script carefully.
     
  22. ChaosMax

    ChaosMax

    Joined:
    Jun 23, 2010
    Posts:
    21
    Thanks for the help, I finally found the problem. I had all the code spelled right, but I misspelled the MultiplayerManager game object in the editor.
     
    Last edited: Apr 22, 2012
  23. GTGD

    GTGD

    Joined:
    Feb 7, 2012
    Posts:
    436
    I've just published Video 14 The Construction Block and the style of this video is quite different from previous videos in that I spend more time explaining the scripts rather than typing them out.

    Btw, don't forget to choose the HD viewing setting to view the video at a much higher video quality and do let me know what you think of the different teaching style.
     
    Last edited: Apr 25, 2012
  24. gormee

    gormee

    Joined:
    Mar 20, 2012
    Posts:
    14
    Hey GTGD,

    Haven't had the time to check out your new video yet, been quite busy the last few weeks with other stuff but I finally managed to solve my issue of RPCs being fired by all the players in the network for the block projectile and I just thought I'd share it with you for your reference.

    Basically all I did was to reference the amIOnTheMoverClass bool from the SpawnScript and only allowed him to fire off the RPC if it was true, such a simple solution totally overlooked for 3 weeks zzzz
     
  25. GTGD

    GTGD

    Joined:
    Feb 7, 2012
    Posts:
    436
    Glad to hear that you figured out a simple solution to your problem gormee.
     
  26. MadRobot

    MadRobot

    Joined:
    Jul 12, 2011
    Posts:
    339
    Hey GTGD! I've completed watching all your videos that you've published (up to 14).

    Excellent presentation! I learned a lot and I thank you for the time and effort you put into them.

    I had fun playing with the chat system and have built upon it a little bit. Would you be interested in the code? Ive lost track of where my changes start and the code you explain in the tutorial end, but I think you might like it. I added some in line command users can your on the chat, including private messages and blocking other users. The scroll bar doesn't work great, but oh well. If you are interested, please let me know and you are more than welcome to integrate any of the code in your tutorials if you like.

    Thanks again for your hard work! I'm anxiously awaiting the ready if the series!

    Patrick

    *with apologies for spelling; sent from a mobile
     
  27. GTGD

    GTGD

    Joined:
    Feb 7, 2012
    Posts:
    436
    Thanks Patrick, I'm always interested in learning about additional/new features implemented by my viewers and you've done something useful so please feel free to post up what you've done. In my opinion playing around with other peoples code is a really good way to learn coding and build your own new code.

    I'm intending to get Video 15 out by Wednesday the 9th of May. Unfortunately my video production cycle has slowed down a bit so it looks I might only be producing 5 or so videos over May and June, but I'll see if I can pick up the pace after that period as I hate to leave viewers hanging.
     
  28. MadRobot

    MadRobot

    Joined:
    Jul 12, 2011
    Posts:
    339
    You bet! Here you go. It's long... 804 lines. If anyone wants to give feedback and constructive criticism, please do so. I am always looking to improve. Thanks!

    Chat system. This is based on the Chat system from GTGD tutorial series. As such, there are a few things it is expecting in order to work. Nothing major, or that can't be code out or supplied by other Classes, but be aware this isn't going to be pure drag and drop. I've tried to explain at the beginning what the script is looking for and why.

    I had fun working on this. I hope someone else finds it helpful as well.

    Patrick

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. // Provides a chat window system
    6. //
    7. //
    8.  
    9. // Attach to the game admin object
    10. //
    11. // The game admin object should also have the following classes attached:
    12. //  1) NetworkController    to check if whisper recipient is the server user
    13. //                              (the .Server field simply returns the _playerName
    14. //                              for the user who is hosting the game)
    15. //                          to set the player name
    16. //                          to announce when players join the chatroom
    17. //  2) PlayerListManager    to ensure whisper recipients are valid user names
    18. //                          to ensure ignored users are valid user names
    19. //                              (I've attached the data structure that is
    20. //                              returned by the PlayerListManager so you
    21. //                              can see what this code is asking for and
    22. //                              receiving)
    23. //
    24. // Chat system consisists of 2 parts:
    25. //   1) a chat window, where all the messages are displayed
    26. //   2) a text field below it where the user can type
    27. //
    28. // hot key 't' always opens both window and textfield
    29. // hot key 'return' will close textfield (if no text was entered)
    30. // hot key 'return' (when textfield is already closed) will close the chat window
    31. // When ONLY the chat window is display (but not the text entry area), player can still
    32. //  play the game. This allows them to play without missing out on the conversations.
    33. //
    34. // Expected BroadcastMessages:
    35. //  Expects SetPlayerName (string) to set the player name
    36. //  Expects AdminChatMessage (string) to announce when players join the chatroom
    37. //
    38. // Transmitted BroadcastMessages:
    39. //  Sends CursorLock(bool) to all components on this game object to control locking  unlocking the cursor
    40. //
    41. // TODO features
    42. //  1) add a 'users in room' window at the top, or a command to list current users
    43. //  2) add a /q 'query' switch that provide info on a user such as team, score, achievements, etc
    44. //  3) allow users to highlight and copy text in the chat window
    45. //  4) show user names in bold and message text in normal font
    46. //
    47. // TODO fixes
    48. //  1) sometimes the scroll bar gets moved on its own
    49. //  2) the scroll bar does not stick to the bottom of the chat window
    50. //  3) the chat font is too large
    51.  
    52. public class ChatController : MonoBehaviour {
    53.    
    54.     public  KeyCode         displayChatKey      =   KeyCode.T;
    55.     public  KeyCode         sendMessageKey      =   KeyCode.Return;
    56.    
    57.     // cache info
    58.    
    59.     public  string          _playerName;        //  name of this client
    60.    
    61.     private ChatState       _displayState;
    62.     private ChatState       _displayStatePrevious;
    63.    
    64.     private List<BadUser>   _ignoreUserList;
    65.     private float           _ignoreUserUpdateTime;
    66.    
    67.     // Send message info
    68.    
    69.     private string          _outGoingMessage;   //  the message the player entered
    70.     private string          _whisperCommand;    //  string that indicates a 'whisper' message
    71.     private string          _ignoreCommand;     //  string that indicates an 'ignore' command
    72.     private string          _clearCommand;      //  string that indicates a 'clear' commnand
    73.    
    74.     private string          _helpCommand;       //  string that indicates a 'help' commnand
    75.     private string          _help2Command;      //  string that indicates a 'help' commnand
    76.     private string          _emoteCommand;      //  string that indicates a 'emote' commnand
    77.     private string          _listCommand;       //  string that indicates a 'list bad users' commnand
    78.     private string          _dateCommand;       //  string that indicates a 'show date' commnand
    79.     private string          _pingCommand;       //  string that indicates a 'show ping' commnand
    80.    
    81.     private string          _whisperReceiver;   //  player name to receive the whisper
    82.     private string          _chatLog;           //  all chat exchanges TODO make this a List
    83.     private string          _adminFlag          =   "(Server) ";
    84.    
    85.     private bool            _cursorLocked;      //  is the cursor visible?
    86.    
    87.     // GUI info
    88.    
    89.     private bool            _displayChat;       //  is the chat window being shown?
    90.    
    91.     private Rect            _winRect;
    92.     private int             _winLeft;           //  px
    93.     private int             _winTop;            //  px
    94.     private int             _winWidth;          //  px
    95.     private int             _winHeight;         //  px
    96.     private int             _padding;           //  px
    97.    
    98.     private Rect            _textFieldRect;
    99.     private int             _textFieldHeight;   //  px
    100.     private Vector2         _scrollPosition;    //  px ?
    101.     private GUIStyle        _thisStyle;
    102.    
    103.     // Use this for initialization
    104.     void Awake () {
    105.         Reset ();
    106.         SystemMessage ("Welcome to the game with no name!");
    107.         SystemMessage (" ");
    108.         SystemMessage ("\"/c\" to clear the chat");
    109.         SystemMessage ("\"/h\" for more commands");
    110.         SystemMessage (" ");
    111.         SystemMessage ("It is " + System.DateTime.Now);
    112.     }
    113.    
    114.     // Unity method, but never called automatically
    115.     void Reset () {
    116.         // allow 'Return' key press to be recognized when using the textfield
    117.         Input.eatKeyPressOnTextFieldFocus   =   false;
    118.        
    119.         _displayState               =   ChatState.Hidden;
    120.         _displayStatePrevious       =   ChatState.Hidden;
    121.         _ignoreUserList             =   new List<BadUser>();
    122.         _ignoreUserUpdateTime       =   Time.time + 60 + Random.value;
    123.        
    124.         // define the dimensions of the chat window
    125.         _padding                    =   20;
    126.         _textFieldHeight            =   30;
    127.         _winRect                    =   new Rect ();
    128.         _textFieldRect              =   new Rect ();
    129.         UpdateChatWindowDimensions();
    130.         UpdateTextFieldDimensions();
    131.        
    132.         _whisperCommand             =   "/w";
    133.         _clearCommand               =   "/c";
    134.         _ignoreCommand              =   "/i";
    135.         _helpCommand                =   "/h";
    136.         _help2Command               =   "/?";
    137.         _emoteCommand               =   "/e";
    138.         _listCommand                =   "/l";
    139.         _dateCommand                =   "/d";
    140.         _pingCommand                =   "/p";
    141.        
    142.         _outGoingMessage            =   "";
    143.        
    144.         _thisStyle                  =   new GUIStyle();
    145.         _thisStyle.normal.textColor =   Color.white;
    146.         _thisStyle.wordWrap         =   true;
    147.     }
    148.    
    149.     // Unity method
    150.     void Update () {
    151.         if (Network.peerType == NetworkPeerType.Disconnected) return;
    152.            
    153.        
    154.         // displayChatHot key always shows both the chat window and the text entry field
    155.         if (_displayState != ChatState.ChatAndTextField
    156.             Input.GetKeyDown(displayChatKey)) {
    157.             SetChatState(ChatState.ChatAndTextField);
    158.             return;
    159.         }
    160.            
    161.         if (_displayState != ChatState.Hidden
    162.             Input.GetKeyDown (sendMessageKey)) {
    163.            
    164.             // The sendMessage key closes the text entry field,
    165.             // when it is pressed and there is no text.
    166.             if (_outGoingMessage.Length == 0
    167.                 _displayState == ChatState.ChatAndTextField) {
    168.                 SetChatState(ChatState.ChatWindow);
    169.                 return;
    170.             }
    171.            
    172.             // The sendMessage key closes the chat window,
    173.             // when the text area is already closed.
    174.             if (_displayState == ChatState.ChatWindow) {
    175.                 SetChatState(ChatState.Hidden);
    176.                 return;
    177.             }
    178.            
    179.             // The sendMessage key sends the message,
    180.             // When there is text in the text entry field.
    181.             TransmitMessage();
    182.         }
    183.        
    184.         // Prevent this from spamming every OnGUI update
    185.         if (DisplayStateChanged()) {
    186.             BroadcastMessage("CursorLocked", _displayState != ChatState.ChatAndTextField, SendMessageOptions.DontRequireReceiver);
    187.             SetChatState(_displayState);
    188.         }
    189.        
    190.         // Check the list of ignored users to see if
    191.         // any users are coming out of their 'timeout'
    192.         if (Time.time > _ignoreUserUpdateTime  
    193.             _ignoreUserList.Count > 0)
    194.             UpdateIgnoreUserList();
    195.     }
    196.    
    197.     private void SetChatState (ChatState newState) {
    198.         _displayStatePrevious   =   _displayState;
    199.         _displayState           =   newState;
    200.     }
    201.    
    202.     private bool DisplayStateChanged () {
    203.         return _displayState != _displayStatePrevious;
    204.     }
    205.    
    206.     private void ChatWindow (int windowId) {
    207.         GUI.SetNextControlName ("ChatWindow");
    208.         _scrollPosition =   GUILayout.BeginScrollView (_scrollPosition,
    209.                                                      GUILayout.Width (_winWidth - _padding),
    210.                                                      GUILayout.Height (_winHeight - _padding - 5));
    211.  
    212.             // TODO If the chat window has mouse focus, allow the player
    213.             // to control the scrollbar position. Otherwise, the
    214.             // scrollbar should default to the bottom most position
    215.         GUILayout.Label (_chatLog, _thisStyle);
    216.         GUILayout.EndScrollView();
    217.     }
    218.    
    219.     // Unity method
    220.     // can be called multiple times per frame
    221.     // avoid expensive activity in here
    222.     void OnGUI () {
    223.         if (Network.peerType == NetworkPeerType.Disconnected) return;
    224.        
    225.         // if there are conditions when this window is to be drawn,
    226.         // e.g., only after the user has spawned, or the user is on the server
    227.         // then here is where you would put those checks
    228.        
    229.         switch (_displayState) {
    230.         case ChatState.ChatWindow:
    231.             ShowChatWindow();
    232.             break;
    233.         case ChatState.ChatAndTextField:
    234.             ShowChatWindow();
    235.             ShowTextEntryField();
    236.             break;
    237.         }
    238.     }
    239.    
    240.     private void UpdateChatWindowDimensions() {
    241.        
    242.         _winHeight  =   Screen.height - _padding * 2;
    243.         _winTop     =   Screen.height - _winHeight - _textFieldHeight;
    244.        
    245.         _winWidth   =   Mathf.Clamp ((int)(Screen.width * .3f), 150, 900);
    246.         _winLeft    =   Screen.width - _winWidth - _padding;
    247.        
    248.         _winRect.height =   _winHeight;
    249.         _winRect.y      =   _winTop;
    250.         _winRect.width  =   _winWidth;
    251.         _winRect.x      =   _winLeft;
    252.     }
    253.    
    254.     private void ShowChatWindow () {
    255.         UpdateChatWindowDimensions();
    256.         _winRect    =   GUI.Window (5, _winRect, ChatWindow, "Chat: " + _playerName);
    257.     }
    258.    
    259.     private void UpdateTextFieldDimensions() {
    260.        
    261.         _textFieldRect.height   =   _winHeight;
    262.         _textFieldRect.width    =   _winWidth;
    263.         _textFieldRect.x        =   _winLeft;
    264.         _textFieldRect.y        =   _winTop + _winHeight;
    265.     }
    266.    
    267.     private void ShowTextEntryField () {
    268.         UpdateTextFieldDimensions();
    269.        
    270.         // display the box where the user enters chat messages
    271.         GUILayout.BeginArea (_textFieldRect);
    272.        
    273.         // name the textfield so we can use GUI.FocusControl to access it
    274.         GUI.SetNextControlName ("ChatTextField");
    275.         _outGoingMessage    =   GUILayout.TextField (_outGoingMessage, GUILayout.Width (_winWidth));
    276.        
    277.         // give focus to the textfield so the user can immediately start typing
    278.         // without having to click in the textfield
    279.         GUI.FocusControl("ChatTextField");
    280.        
    281.         GUILayout.EndArea();
    282.     }
    283.    
    284.     // This executes the appropriate RPC calls, depending on if
    285.     // the message is a normal chat message (to everybody) or
    286.     // if the message is a 'whisper' (to a single user).
    287.     // This also handles all the special chat commands.
    288.     private void TransmitMessage () {
    289.        
    290.         // Check for 'clear chat window' command
    291.         if (IsChatCommand(_clearCommand, _outGoingMessage)) {
    292.             // clear
    293.             _chatLog            =   "";
    294.             _outGoingMessage    =   "";
    295.             return;
    296.         }
    297.        
    298.         // Handle direct messages between users
    299.         if (IsChatCommand (_whisperCommand, _outGoingMessage)) {
    300.             // whisper
    301.             SendWhisper ();
    302.             _outGoingMessage    =   "";
    303.             return;
    304.         }
    305.        
    306.         // Handle a user's request to not receive messages from
    307.         // another user
    308.         if (IsChatCommand(_ignoreCommand, _outGoingMessage)) {
    309.             // ignore
    310.             SendIgnore();
    311.             _outGoingMessage    =   "";
    312.             return;
    313.         }
    314.        
    315.         // Handle help request
    316.         if (IsChatCommand (_helpCommand, _outGoingMessage) ||
    317.             IsChatCommand(_help2Command, _outGoingMessage)) {
    318.             // help
    319.             SendHelp ();
    320.             _outGoingMessage    =   "";
    321.             return;
    322.         }
    323.        
    324.         // Handle actions and 'emotes'
    325.         if (IsChatCommand (_emoteCommand, _outGoingMessage)) {
    326.             // emote
    327.             SendEmote ();
    328.             return;
    329.         }
    330.        
    331.         // Handle request for list of ignored users
    332.         if (IsChatCommand (_listCommand, _outGoingMessage)) {
    333.             // ignore list and time remaining
    334.             SendIgnoredList ();
    335.             _outGoingMessage    =   "";
    336.             return;
    337.         }
    338.        
    339.         // Handle request for date  time
    340.         if (IsChatCommand (_dateCommand, _outGoingMessage)) {
    341.             // date, time
    342.             SystemMessage (System.DateTime.Now.ToString());
    343.             _outGoingMessage    =   "";
    344.             return;
    345.         }
    346.        
    347.         // Handle ping request
    348.         if (IsChatCommand (_pingCommand, _outGoingMessage)) {
    349.             // show average ping time between client and server
    350.            
    351.             // handle 'ping' when user is not connected to and is not hosting
    352.             // and other clients
    353.             if (Network.connections.Length == 0) {
    354.                 SystemMessage ("No one to ping!");
    355.                 _outGoingMessage    =   "";
    356.                 return;
    357.                
    358.             }
    359.            
    360.             // show network ping to user
    361.             _outGoingMessage    =   "Ping is ";
    362.             if (Network.isClient) {
    363.                 _outGoingMessage    +=  Network.GetAveragePing(Network.connections[0]);
    364.             } else {
    365.                 float totalPing = 0;
    366.                 for (int i = Network.connections.Length ; i > 0 ; i--) {
    367.                     totalPing   += Network.GetAveragePing(Network.connections[i-1]);
    368.                 }
    369.                 _outGoingMessage    +=  Mathf.RoundToInt(totalPing / (float)Network.connections.Length);
    370.             }
    371.             SendChatMessage();
    372.             return;
    373.         }
    374.        
    375.         // Handle a standard message, broadcast to the entire chat room
    376.         SendChatMessage ();
    377.     }
    378.    
    379.     private void SendChatMessage () {
    380.         // TODO if there are teams, include the team name in parenthesis
    381.         // following the player's name, e.g., CaptainAwesome (n00bs): hi!
    382.        
    383.         // The message will have a prefix of the user's name.
    384.         // An optional 'server' prefix may be prepended, if the user
    385.         // is running the game server.
    386.         string  sender  =   _playerName;
    387.         if (Network.isServer)   sender  =   _adminFlag + _playerName;
    388.        
    389.         networkView.RPC ("LocalChatMessage", RPCMode.AllBuffered, sender + ": " + _outGoingMessage, _playerName);
    390.         _outGoingMessage    =   "";
    391.     }
    392.    
    393.     private void SendWhisper () {
    394.        
    395.         string sender   =   _playerName;
    396.        
    397.         // The message will have a prefix of the user's name.
    398.         // An optional 'server' prefix may be prepended, if the user
    399.         // is running the game server.
    400.         if (Network.isServer)
    401.             sender      =   _adminFlag + _playerName;
    402.        
    403.         // user failed to include a user name
    404.         if (_outGoingMessage.Length <= _whisperCommand.Length) {
    405.             SystemMessage ("Did you forget to whom you are whispering?");
    406.             return;
    407.         }
    408.        
    409.         // prevent user from sending a whisper to a non-existant user
    410.         // but enable the user to send whispers to the server user
    411.         PlayerListManager   plm =   GetComponent<PlayerListManager>();
    412.         NetworkController   nc  =   GetComponent<NetworkController>();
    413.        
    414.         // PlayerListManager.contains(string) returns true if any of the PlayerData
    415.         // objects in PlayerListManager's list of PlayerData objects matches the
    416.         // provided string to the PlayerData.playerName field
    417.         //
    418.         // NetworkController.Server is a string that is the _playerName
    419.         // stored in the NetworkController class.
    420.         string  recipient   =   ExtractRecipient(_outGoingMessage);
    421.         if (plm  !plm.contains(recipient)  !nc.Server.Equals(recipient)) {
    422.             SystemMessage ("User " + recipient + " does not exist. Message not delivered.");
    423.             return;
    424.         }
    425.        
    426.         // prevent user from sending whispers to themself
    427.         if (recipient.Equals(_playerName)) {
    428.             SystemMessage ("Talking to yourself is a sign of insanity.");
    429.             return;
    430.         }
    431.        
    432.         // prevent user from sending messages to someone they have blocked
    433.         if (IsBadUser(recipient)) {
    434.             SystemMessage ("You are ignoring " + recipient + ", so you cannot send them messages.");
    435.             return;
    436.         }
    437.        
    438.         // prevent sending an empty message
    439.         string  message     =   ExtractMessage (recipient, ExtractMessage(_whisperCommand, _outGoingMessage));
    440.        
    441.         if (message.Length==0) {
    442.             SystemMessage ("You seem to have nothing to say.");
    443.             return;
    444.         }
    445.        
    446.         // build the message to send
    447.         string  whisper =   sender + " to " + recipient + ": " + message;
    448.        
    449.         // this makes it appear in our own chat window
    450.         SystemMessage (whisper);
    451.        
    452.         // send whisper message to the recipient
    453.         networkView.RPC ("SendDirectMessage", RPCMode.OthersBuffered, whisper, _playerName, recipient);
    454.     }
    455.    
    456.     private void SendIgnore () {
    457.        
    458.         // check user has provided a username to ignore
    459.         if (_outGoingMessage.Length <= _ignoreCommand.Length) {
    460.             SystemMessage ("Did you forget whom you want to ignore?");
    461.             return;
    462.         }
    463.        
    464.         string  badUser     =   ExtractRecipient(_outGoingMessage);
    465.        
    466.         // ensure user isn't trying to ignore themselves
    467.         if (badUser.Equals(_playerName)) {
    468.             SystemMessage ("You concentrate very hard... Alas, you cannot seem to ignore yourself.");
    469.             return;
    470.         }
    471.        
    472.         // TODO The PlayerListManager and NetworkController accesses
    473.         // should be pulled out and isolated because it is also used
    474.         // for whisper messages. The way it is, you have a lot of duplicate code
    475.        
    476.         // ensure baduser is a valid user
    477.         // prevent user from ignoring a non-existant user
    478.         // but enable the user to ignore the server user
    479.         PlayerListManager   plm =   GetComponent<PlayerListManager>();
    480.         NetworkController   nc  =   GetComponent<NetworkController>();
    481.  
    482.         // PlayerListManager.contains(string) returns true if any of the PlayerData
    483.         // objects in PlayerListManager's list of PlayerData objects matches the
    484.         // provided string to the PlayerData.playerName field
    485.         //
    486.         // NetworkController.Server is a string that is the _playerName
    487.         // stored in the NetworkController class.
    488.         if (plm  !plm.contains(badUser)  !nc.Server.Equals(badUser)) {
    489.             SystemMessage ("User " + badUser + " does not exist. It's easy to ignore nobody!");
    490.             return;
    491.         }
    492.        
    493.         // ensure a timeout is entered
    494.         if (_outGoingMessage.Split(' ').Length < 3) {
    495.             SystemMessage ("Nothing lasts forever. Include how many minutes " + badUser
    496.                            + " needs to be in Timeout.");
    497.             return;
    498.         }
    499.            
    500.         // ensure timeout is a numeric value
    501.         string  timeoutString   =   ExtractIgnoreTime(_outGoingMessage);
    502.         float   timeout;
    503.        
    504.         if (!float.TryParse(timeoutString, out timeout)) {
    505.             // user did not provide a time for the Timeout
    506.             SystemMessage ("You cannot ignore someone for \"" + timeoutString
    507.                              + "\" minutes! That makes no sense.");
    508.             return;
    509.         }
    510.        
    511.         // ensure timeout is a non-zero number (negative number denotes 'ignore forever')
    512.         if (timeout == 0) {
    513.             // user provided a time of 0
    514.             SystemMessage ("By the time I update my database, you won't be ignoring them anymore!");
    515.             return;
    516.         }
    517.        
    518.         // round timeout to 1st decimal place
    519.         timeout =   .1f * Mathf.Floor(timeout * 10);
    520.        
    521.         // Ignore the bad user
    522.         if (IsBadUser(badUser)) {
    523.             // user is already ignored
    524.            
    525.             ChangeUserIgnoreDuration (badUser, timeout);
    526.             string strTimeout;
    527.             if (timeout < 0) strTimeout =   "forever!";
    528.             else strTimeout =   timeout + " minutes";
    529.            
    530.             _outGoingMessage    =   "Updating timeout period for " + badUser + " to " + strTimeout;
    531.         } else {
    532.             // new user for the bad user list
    533.            
    534.             if (timeout<0)
    535.                 _ignoreUserList.Add (new BadUser (badUser, Mathf.RoundToInt(-Time.time)));
    536.             else
    537.                 _ignoreUserList.Add (new BadUser (badUser, timeout));
    538.            
    539.             // let them know they are on the ignore list
    540.             string  ignoreMessage   =   _playerName + " is ignoring you. I wonder for how long?";
    541.             networkView.RPC ("SendDirectMessage", RPCMode.OthersBuffered, ignoreMessage, _playerName, badUser);
    542.            
    543.             if (timeout < 0)    timeoutString   =   "forever!";
    544.             else                timeoutString   +=  " minutes.";
    545.             _outGoingMessage    =   "You are ignoring " + badUser + " for " + timeoutString;
    546.         }
    547.         // this makes it appear in our own chat window
    548.         SystemMessage (_outGoingMessage);
    549.     }
    550.    
    551.     private void SendHelp () {
    552.        
    553.         SystemMessage (" Command List");
    554.         SystemMessage ("\"/h\" or \"/?\" to get this list");
    555.         SystemMessage ("\"/w name message\" to send a private message");
    556.         SystemMessage ("\"/i name minutes\" to ignore a user for a period of time");
    557.         SystemMessage ("\"/l\" to list ignored users");
    558.         SystemMessage ("\"/e\" to emote an action");
    559.         SystemMessage ("\"/d\" to show date and time");
    560.         SystemMessage ("\"/p\" to show ping time to server");
    561.         SystemMessage ("\"/c\" to clear the chat");
    562.     }
    563.    
    564.     private void SendEmote () {
    565.        
    566.         _outGoingMessage    =   "*" + ExtractMessage(_emoteCommand, _outGoingMessage) + "*";
    567.         SendChatMessage();
    568.     }
    569.    
    570.     private void SendIgnoredList () {
    571.        
    572.         if (_ignoreUserList.Count == 0)
    573.             SystemMessage ("You are not ignoring anyone.");
    574.         else {
    575.             string _list        =   "Ignore List";
    576.             for (int i = _ignoreUserList.Count ; i > 0 ; i--) {
    577.                 string  name    =   _ignoreUserList[i-1].Name;
    578.                 float   endTime =   _ignoreUserList[i-1].Timeout;
    579.                 int     remain  =   Mathf.RoundToInt(((float)(endTime - Time.time)) / 60f);
    580.                 _list           =   _list + "\n" + name + " : ";
    581.                 if (endTime < 0)    _list   +=  "forever!";
    582.                 else                _list   +=  remain + " min";
    583.             }
    584.             _list   +=  "\n\nYou can use \"/i name minutes\" to change ignore times.";
    585.             SystemMessage(_list);
    586.         }
    587.     }
    588.    
    589.    
    590.    
    591.     // utilities //
    592.    
    593.    
    594.     // This is a handy prefix for Debug.Log messages.
    595.     // It provides the current frame number, the GameObject id number,
    596.     // the script name, and the current User name.
    597.     private string DebugId {
    598.         get { return Time.frameCount.ToString() + ":"       //  frame count
    599.             + this.ToString() + ":"                         //  script/class name
    600.             + "ID " + gameObject.GetInstanceID() + ": "     //  gameobject name
    601.             + _playerName + ": ";                           //  player name
    602.         }
    603.     }
    604.    
    605.     private bool IsChatCommand (string switchIndicator, string message) {
    606.         return message.StartsWith (switchIndicator);
    607.     }
    608.    
    609.     private string ExtractRecipient (string message) {
    610.         return message.Split(' ')[1];
    611.     }
    612.    
    613.     private string ExtractIgnoreTime (string message) {
    614.         return message.Split(' ')[2];
    615.     }
    616.    
    617.     private string ExtractMessage (string command, string message) {
    618.         int messageStart    =   command.Length +1;
    619.         string extract      =   "";
    620.         if (message.Length > messageStart)
    621.             extract =   message.Substring(messageStart);
    622.         return extract;
    623.     }
    624.    
    625.     private bool IsBadUser (string user) {
    626.         bool    isBad   =   false;
    627.        
    628.         for (int i = _ignoreUserList.Count ; i > 0 ; i--) {
    629.             if (user.Equals(_ignoreUserList[i-1].Name)) {
    630.                 isBad   =   true;
    631.                 break;
    632.             }
    633.         }
    634.         return  isBad;
    635.     }
    636.    
    637.     // changes the 'timeout' period for someone in the ignore list
    638.     // to the new value provided by the user
    639.     private void ChangeUserIgnoreDuration (string user, float timeout) {
    640.        
    641.         for (int i = _ignoreUserList.Count ; i > 0 ; i--) {
    642.             if (_ignoreUserList[i-1].Name.Equals(user)) {
    643.                
    644.                 _ignoreUserList[i-1]    =   new BadUser (_ignoreUserList[i-1].Name, timeout);
    645.                 break;
    646.             }
    647.         }
    648.     }
    649.    
    650.     private void UpdateIgnoreUserList () {
    651.         for (int i = _ignoreUserList.Count ; i > 0 ; i--) {
    652.             if (_ignoreUserList[i-1].Timeout > 0
    653.                 Time.time > _ignoreUserList[i-1].Timeout) {
    654.                 SystemMessage ("System to " + _playerName + ": " +
    655.                                _ignoreUserList[i-1].Name + " is out of Timeout. " +
    656.                                "Let's hope they behave.");
    657.                 _ignoreUserList.RemoveAt(i-1);
    658.             }
    659.         }
    660.         _ignoreUserUpdateTime   =   Time.time + 60 + Random.value;
    661.     }
    662.    
    663.     // RPC //
    664.    
    665.     [RPC]
    666.     // system message or admin message (to ourself only)
    667.     private void SystemMessage (string message) {
    668.         // Send the message as if it came from ourself.
    669.         // Since it is not possible to ignore yourself,
    670.         // system messages cannot be blocked.
    671.         LocalChatMessage (message, _playerName);
    672.     }
    673.    
    674.     [RPC]
    675.     // regular chat messages (to everyone)
    676.     void LocalChatMessage (string message, string sender) {
    677.         // Use the 'sender' to confirm we are willing to
    678.         // recieve this message. Ignore the message if the sender
    679.         // is in our list of ignored users.
    680.         if (!IsBadUser(sender))
    681.             _chatLog    =   _chatLog + "\n\n" + message;
    682.     }
    683.    
    684.     [RPC]
    685.     // 'whisper' chat messages (to recipient only)
    686.     void SendDirectMessage (string message, string sender, string recipient) {
    687.         // Use 'receipient' to confirm we are the intended recipient of
    688.         // the message. Ignore the message if our player name and the
    689.         // recipient name are not the same.
    690.         if (_playerName.Equals(recipient))
    691.             LocalChatMessage (message, sender);
    692.     }
    693.    
    694.    
    695.     // Broadcast message receivers
    696.    
    697.     public void SetPlayerName (string name) {
    698.         _playerName =   name;
    699.     }
    700.    
    701.     // broadcast message reciever for player join/depart from the NetworkController
    702.     public void AdminChatMessage (string message) {
    703.         networkView.RPC ("SystemMessage", RPCMode.AllBuffered, message);
    704.     }
    705. }
    706.  
    707. enum ChatState {
    708.     Hidden,
    709.     ChatWindow,
    710.     ChatAndTextField
    711. }
    712.  
    713. public struct BadUser {
    714.     public  string  Name;
    715.     public  float   Timeout;
    716.                
    717.     public BadUser (string name, float timeout) {
    718.         Name    =   name;
    719.         Timeout =   Time.time + 60 * timeout;
    720.     }
    721. }
    722.  
    723. // The is the struct that PlayerListManager uses to store player data. This
    724. // is the object that is returned above when the ChatController is checking user names
    725.  
    726.  
    727. // This is an extension of the NetworkPlayer struct.
    728. // Because it's not possible to extend NetworkPlayer,
    729. // this contains a lot of duplicate items.
    730. public struct PlayerData {
    731.     private string  playerName;
    732.     private string  teamName;
    733.     private int     score;
    734.    
    735.     override public string ToString() { return playerName + ":" + Guid; }
    736.    
    737.     public string PlayerName {
    738.         set {   playerName  =   value; }
    739.         get {   return playerName; }
    740.     }
    741.    
    742.     public string TeamName {
    743.         set {   teamName    =   value; }
    744.         get {   return teamName; }
    745.     }
    746.    
    747.     public int Score {
    748.         set {   score   =   value; }
    749.         get {   return score; }
    750.     }
    751.    
    752.     // NetworkPlayer functionality //
    753.     private NetworkPlayer   _networkPlayer;
    754.    
    755.     public string IpAddress {
    756.         get {   return _networkPlayer.ipAddress; }
    757.     }
    758.    
    759.     public int Port {
    760.         get {   return _networkPlayer.port; }
    761.     }
    762.    
    763.     public string Guid {
    764.         get {   return _networkPlayer.guid; }  
    765.     }
    766.    
    767.     public string ExternalIP {
    768.         get {   return _networkPlayer.externalIP; }
    769.     }
    770.    
    771.     public int ExternalPort {  
    772.         get {   return _networkPlayer.externalPort; }
    773.     }
    774.    
    775.     public NetworkPlayer networkPlayer {
    776.         set {   _networkPlayer  =   value; }
    777.         get {   return _networkPlayer; }
    778.     }
    779.    
    780.     public bool Equals (PlayerData rhs) {
    781.         return  _networkPlayer.Equals(rhs.networkPlayer)
    782.                 playerName.Equals(rhs.playerName);
    783.     }
    784.    
    785.     public static bool Equals (PlayerData lhs, PlayerData rhs) {
    786.         return lhs.Equals(rhs);
    787.     }
    788.    
    789.     public static bool operator== (PlayerData lhs, PlayerData rhs) {
    790.         return  lhs.Equals(rhs);   
    791.     }
    792.    
    793.     public static bool operator!= (PlayerData lhs, PlayerData rhs) {
    794.         return  !lhs.Equals(rhs);
    795.     }
    796.    
    797.     public static bool compare (PlayerData lhs, NetworkPlayer rhs) {
    798.         return (lhs.networkPlayer.Equals(rhs));
    799.     }
    800.    
    801.     public static bool compare (NetworkPlayer lhs, PlayerData rhs) {
    802.         return compare (rhs, lhs); 
    803.     }
    804. }
    805.  
    806.  
     
    Last edited: May 6, 2012
  29. LearningThis

    LearningThis

    Joined:
    May 6, 2012
    Posts:
    10
    Hello GTGD. Thanks for the videos they are helpful. I'm new to Unity and programming. I'm on video 2. I copied the code into CameraScript file. The CameraScript file is giving a Null Reference error when I try to drag and drop it onto the "CameraHead" and I cannot add the CameraScript file by going to Component > Scripts > CameraScripts (the CameraScript is greyed out).

    The only way I can get the script to CameraScript file to work (somewhat) is by unticking the Main Camera in Inspector and then I am able to select CameraScript by going to Component> Scripts > CameraScripts. When I press Play the entire Game View turns black and I cannot see the Cube or Player in Game View at that point. I can see the Cube and Player in Scene View though and I am able to move the Player with the arrow keys. Any idea of what I can do to fix this?
     
  30. RyanSchurton

    RyanSchurton

    Joined:
    Mar 2, 2012
    Posts:
    141
    I love the name of your tutorial series Gamer To Game Developer. It's perfect keep these coming.
     
  31. GTGD

    GTGD

    Joined:
    Feb 7, 2012
    Posts:
    436
    Video 15 Construction Block Removal is now out!

    To Patrick (MadRobot)- Thanks for posting up your script and I can see that you've used some pretty advanced coding techniques and you've commented your code well. Once I'm done with the Series 1 videos I'll spend some time studying and trying out your code.

    To LearningThis - Check the spelling and case of the CameraHead GameObject that you attached to the player and the spelling and case of the CameraScript file. The Null Reference error is telling us that the CameraScript is unable to find something. Also check that your folder structure matches mine, I haven't encountered a situation where I couldn't add a script from the drop down menu so I'm not sure what your setup is like. Disabling the Camera isn't a solution as you need it to see obviously!
     
  32. MadRobot

    MadRobot

    Joined:
    Jul 12, 2011
    Posts:
    339
    Cool, glad to hear you found it interesting! AND I'm glad to see video 15 is out! Looking forward to the rest!
     
  33. rockysam888

    rockysam888

    Joined:
    Jul 28, 2009
    Posts:
    650
    (bookmarked)
     
  34. hardhittertennis

    hardhittertennis

    Joined:
    May 21, 2012
    Posts:
    3
    Hey, first off let me just say thank you so much for doing this. The layout of the tutorials show you are very prepared for this and love helping people out and we all appreciate it :D.

    Just a quick question, are these videos posted anywhere for download? I am going on a car trip and would love to have these videos for download and i figure others would too. If they are already posted for DL and i completely missed it, then i am very sorry.

    Thanks again!
     
  35. GTGD

    GTGD

    Joined:
    Feb 7, 2012
    Posts:
    436

    No I don't have a download for the videos but I'm pretty sure that you can download them from YouTube using one of the many external websites or programs that allows you to do so.
     
  36. hardhittertennis

    hardhittertennis

    Joined:
    May 21, 2012
    Posts:
    3
    Yup i did think about that i just didnt know if you would like people doing that. But since you gave me the ok i will, thanks again im looking forward to the rest of the tutorials :)

    Also, if we wanted to donate, where would we do that?
     
  37. GTGD

    GTGD

    Joined:
    Feb 7, 2012
    Posts:
    436
    Video 16 Player Resource is now out! You'll notice that the video quality on HD has been improved a lot as I've been experimenting with a lot of different video settings while keeping file size as low as I could.

    To hardhittertennis - Thanks for considering to donate. I'll finish all of the videos and then I'll think about putting up a contribution page. I've also been thinking that maybe I could even prepare a commercial package that has an exclusive video and higher quality videos (meaning video quality) than currently on YouTube and distribute it through a reputable publisher. I'll think about my options as I approach the last video.
     
  38. thekrow73

    thekrow73

    Joined:
    Apr 13, 2012
    Posts:
    2
    I have to give you "props" for doing this series!

    I have done other "unofficial" Unity tutorials and were often turned off either by the quality or quantity of the material. You have covered the perfect topics for creating a very nice base FPS game, all of which I have been trying to piece together with many other tutorials. That being said.. I cannot wait for the rest!

    Keep up the good work! I too cannot wait to donate some funds to your cause!

    thekrow73 aka Bryan
     
  39. GTGD

    GTGD

    Joined:
    Feb 7, 2012
    Posts:
    436
    Thank you and I'm glad the tutorials are useful to you! I'm working on the next video where we'll implement a way to select our weapon and I'll show you a way to make transparent background icons from screen shots without an awful jagged or discolored border. I'm aiming to get the video done by the 7th of June.
     
  40. thekrow73

    thekrow73

    Joined:
    Apr 13, 2012
    Posts:
    2
    I, like others, can't wait! :)
     
  41. dimensioneater

    dimensioneater

    Joined:
    May 26, 2012
    Posts:
    9
    i cant stand just to click the link so i reply

    Thank you for this tutorial thank you very much and hope you'll make many of this im very noob so this is what i needed thanks

    i hope next project will have some tutorial using smartfox and data basing for MMO
     
    Last edited: Jun 2, 2012
  42. TehWut

    TehWut

    Joined:
    Jun 18, 2011
    Posts:
    1,577
    hey!

    I'm only a part of the way through the second video, but I think a tutorial for masterserver, or even anyway to take the game beyond LAN would be the most amazing tutorial ever.
     
  43. Minifig3D

    Minifig3D

    Joined:
    Jun 5, 2012
    Posts:
    4
    I am very much enjoying this series and look forward to its completion. I was trying out the completed GTGDSeries1PrototypeWindows.zip and noticed a few glitches. First, try rocket jumping from the bottom of the lake. something happens wrong in that the player rockets sky high out of the water. Secondly, if the client selects the "fast" quality on starting the program, the swimming speed malfunctions and causes the player to swim super fast. Finally, when launching a rocket while falling, the player sometimes collides with the rocket, and follows it for about half a second, rocketing forwards as if he had used the teleport ability.
    These are just some simple glitches we found while playing around with the demo to see if it was what we were looking for. My friend and I enjoyed it very much overall and are both following your tutorial on how to create the game. (I'm winning by 2 videos, but he is catching up fast...)
    Thanks for the wonderful tutorial series! I hope the next one comes out soon. ;)
     
  44. GTGD

    GTGD

    Joined:
    Feb 7, 2012
    Posts:
    436
    Video 17 Weapon Selection is now out!

    Thanks for the bug report Minifig3D. I was aware of the last glitch but not the first two. I have some ideas for overcoming the last two glitches so I'll spend a bit of time on working those out. I'm glad you're enjoying the series!

    Thanks also to other viewers who are giving suggestions on what they'd like to see in future videos. Once I reach the end of Series 1 I'll talk about the direction for Series 2 and what the time frame might be for that.
     
  45. technotdc

    technotdc

    Joined:
    Oct 21, 2011
    Posts:
    60
    Thanks for video 17...............
     
  46. GTGD

    GTGD

    Joined:
    Feb 7, 2012
    Posts:
    436
    I've published video 18 Build Option Selection and it only took me around 2 hours to record and edit this video since it's so similar to video 17. Oh, and it's less than 20 minutes long!

    The upcoming video 19 will be about implementing the Air Block and that will be a really nice addition to our prototype game.
     
  47. Minifig3D

    Minifig3D

    Joined:
    Jun 5, 2012
    Posts:
    4
    Wow. that was really fast!
     
  48. GTGD

    GTGD

    Joined:
    Feb 7, 2012
    Posts:
    436
    I've gone all out and I've published Video 19 Air Block, which adds a really neat build option to our prototype game!

    You'll notice that I've made a real effort to keep the length of the video down and I've managed to keep it down below 47 minutes. At the moment I'm using as much of my free time as I can to produce these videos because I might become very busy in a few weeks from now.

    As always feel free to let me know what you like about these videos and what you believe could be improved.
     
  49. Minifig3D

    Minifig3D

    Joined:
    Jun 5, 2012
    Posts:
    4
    Wow, you are on a roll, aren't you? Keep up the great work!
     
  50. TheYoss

    TheYoss

    Joined:
    Jun 11, 2012
    Posts:
    1
    Hey GTGD,

    Just wanted to say thank you for the great tutorials, out of all the Unity networking guides I've seen lately, yours is by far the most thorough on the matter. Quick and simple question though as I am very new to networking with Unity what type of setup is this? (Authoritative, Non-Authoritative, LAN only?)

    Also, bajeo I am very interested in the additions you made to GTGDs code (On your website in WebPlayer) and was wondering if you were planning on posting the modification here. There were a lot of features that would make juicy additions to GTGDs tutorials such as your master-server and animation synchronization. Thanks in advance.