The road to independent game development with Unity

brief introduction

        My undergraduate is a major in education at Shanghai Normal University. Due to my interest in the computer field, I have passed the postgraduate entrance examination for the computer major of this school. There may be a dedicated article on the mental journey of cross-examination computers. The reason why I try to learn Unity to develop my own games independently is partly because of my interest in the field of computers, and because I like free creation, and partly because of the persistence of a friend who has been developing games for many years. The difficulties and pressures encountered during the period were also very challenging. We watched the videos of various Unity masters and collected the source code of the project for learning. It is quite a sense of accomplishment to realize one function after another. I will select a few codes and ideas that catch my eye for recording and sharing, which is also a summary for myself.

        The ideas and codes I mentioned later are not suitable for unity beginners, even though I am only a beginner. Because I really don't have a macro design for game software, and I haven't been exposed to any similar projects before. I belong to the situation where I write where I think, so I need this document to review my previous work.

        If you can give me any comments, I will definitely listen carefully, and I will do my best to improve this article.

foreword

        This project is a unity2D turn-based strategy card game. Simply put, each player controls a chess piece to move, and uses the skills and cards of each chess piece to fight with other players. The Photon network framework is used for online settings.

login interface

 Implemented functions

Players enter their nickname and choose a room to join. It is not difficult to realize.

part of the code

using Photon.Pun;//联网
using UnityEngine.UI;//操作UI,好像暂时用不到
using Photon.Realtime;//使用RoomOptions类设置房间相关信息时需要用用到
using TMPro;//操作TextMeshPro文本需要用到

public class NetworkLaunch : MonoBehaviourPunCallbacks{}
//unity中的脚本默认继承MonoBehaviour,这里继承PunCallbacks是因为我们需要重写Photon中的回调函数

private void Start()
{
    PhotonNetwork.ConnectUsingSettings();//连接到服务器
}
public override void OnConnectedToMaster()//回调函数,设置UI可见性
{
    base.OnConnectedToMaster();
    nameUI.SetActive(true);
}
public void OnClickPlayBtn()//输入完昵称,按下按钮调用
{
    nameUI.SetActive(false);
    PhotonNetwork.NickName = playerName.GetComponent<TMP_InputField>().text;
    loginUI.SetActive(true);
}
public void OnClickJoinBtn()//输入完房间名,按下按钮调用
{
    if (roomName.GetComponent<TMP_InputField>().text.Length < 2)
    {
        return;
    }
    RoomOptions roomoptions = new RoomOptions();
    roomoptions.MaxPlayers = 8;
    PhotonNetwork.JoinOrCreateRoom(roomName.GetComponent<TMP_InputField>().text, roomoptions, TypedLobby.Default);
}
public override void OnJoinedRoom()//回调函数,玩家加入房间时调用
{
    base.OnJoinedRoom();
    PhotonNetwork.LoadLevel(1);
}

room waiting interface 

Homeowner's perspective:

 Views from other members:

 After other members prepare

 Implemented functions

        The room name is displayed in the upper left corner, and each player's nickname is displayed above. The host only has the button to start the game, and other members only have the ready button. If other players click the ready button, all players in the room will display the player's ready status. If you click the ready button again, the ready will be canceled. Players who enter last are seated in order. When all players are ready, the homeowner clicks to start the game to take effect, and all players enter the next game scene.

        The design of this room is simply an entry exam for the Photon network framework to realize network synchronization! I will explain in detail below.

part of the code

        I will describe it in the order in which I completed the functions at that time, and there may be some logic missing

Generate room members to the specified location

PhotonNetwork.Instantiate("Roomer", Roomerloc[i], Quaternion.identity, 0);

        Here, Photon's network generated prefab is used, and the prefab needs to be stored in the specified Resource folder, so that Photon will find the specified prefab according to the string of the first parameter. The second parameter is the position of the world coordinates (different from the coordinates in Canvas), and the latter few parameters are not specifically understood, it seems that the default is fine.

        The advantage of using Photon's built-in network generation is that it is easy to synchronize. Once generated, all players can see this game object. But there are also certain disadvantages. Using the Grid layout Group in the canvas can easily arrange the game objects generated in it, but the game objects generated by the Photon network can only be in the world coordinates. How to make the Roomers neatly arranged has become the first difficulty .

    public List<Vector3> Roomerloc = new List<Vector3>();    
    public void InitRoomloc()
    {
        Vector3 v3 = new Vector3(-6, 2, 0);
        for (int i = 0; i < 2; i++)
        {
            for (int j = 0; j < 4; j++)
            {
                Roomerloc.Add(v3);
                v3 += new Vector3(4, 0, 0);
            }
            v3 = new Vector3(-6, -2, 0);
        }
    }

        It is not difficult to solve this problem. I just need to use a simple loop to calculate the location suitable for Roomer and store it in Roomerloc. Next, we need to solve new problems, how to generate the Roomer to the position we want, here I thought of a relatively easy way: set a bool array is_sit to mark which positions are already occupied, and then join the players in the room Just find an unoccupied seat and sit down.

        To be honest, this problem has troubled me for a few days. It turns out that "what is owed still needs to be replaced." The problem of generating synchronization was easily solved by using network generation before, but this seating problem still returns to the synchronization problem. The result of the initial attempt was that the data in is_sit was not synchronized, causing several Roomers to sit in the same position.

How to achieve synchronization

RPC way

gameobject.GetComponent<PhotonView>().RPC("UpdateRoomInfo", RpcTarget.MasterClient,传递参数);

        Scripts that need to pass parameters need to mount the Photonview script and use RPC (Remote Procedure Call) for data transmission. The first parameter is the function name. These functions need to be marked with [PunRPC] before RPC can find them. The second parameter is the object to send data, RpcTarget.All is all objects in the room (including yourself), RpcTarget.MasterClient is only sent to the host. The third parameter passing parameter can be a basic data type (int, int[], etc.) but not a Gameobject object. The passed parameters need to correspond to the formal parameters in the called function.

SetCustomProperty method

    //设置玩家的属性
    ExitGames.Client.Photon.Hashtable table = new ExitGames.Client.Photon.Hashtable();
    table.Add("IsReady", false);
    PhotonNetwork.LocalPlayer.SetCustomProperties(table);

    //获取玩家的属性
    object isready;
    if (player.CustomProperties.TryGetValue("IsReady", out isready)){}
    //使用isready需要强制转换

        We can set player properties, and Photon will automatically synchronize to other hosts for us (but the speed is really slow). According to some experience, a client can only set its own properties, and setting other players' properties will not Synchronous.

        

        Knowing the synchronization tool is not enough, because a new player joins the room not to tell others about his data, but to ask others to synchronize the data (is_sit) to himself. So in the Start method of the game object, RPC is used to request is_sit data from the host, and the host sends is_sit data to everyone. The modification of is_sit only needs to use RPC to modify the data in the host. In short, the host ensures that the data of is_sit is consistent.

Synchronization of initial room entry

        Now that the player is spawned at the specified location, the player who just joined can indeed see a few "Roomer" prefabs in front of him, who are they and what is their readiness? How should these be displayed?

        //显示名字
        if (photonView.IsMine)
        {
            SetRoomerName(PhotonNetwork.NickName);
        }
        else
        {
            SetRoomerName(photonView.Owner.NickName);
        }

        //显示准备状态
        if (photonView.Owner.IsMasterClient)
        {
            roomhostimage.SetActive(true);
        }
        object isready;
        if (photonView.Owner.CustomProperties.TryGetValue("IsReady", out isready))
        {
            SetReadyImage((bool)isready);
        }

        This is the code that is called when each Roomer wakes up. Although "generate" has only one line of code in one terminal, its generation will be called in all terminals, that is to say, all terminals will call each Roomer to generate code. The few lines of if/else that display the name are concise but express a lot of meaning. If a client generates a Roomer created by itself, it will execute the code in the if to obtain the NickName in the global variable PhotonView, because this variable is the player currently operating. If a client generates other Roomer will execute the code in else. The photonview is unique to each roomer, that is, to obtain the NickName of the player who created the Roomer. The display of the ready state is the same, of course, the premise is that we need to modify the ready information of the property player at all times when we click Ready.

Game main interface

        This is the most core part of our game, and it will also be the longest part. I roughly divide the content in the game into four sections:

1. Initialization

2. Player moves

3. Card making

4. PvP

        You can take a brief look at the interface designed for this game, similar to flying chess, players can move freely on the board during the moving phase, and then they can use cards or release skills against other players on the board according to certain rules

 

initialization

        I didn't think about the initialization work at the beginning of writing the code, but with the increase of functions, I found that I must use initialization to do some necessary preparations for the game, especially in the case of online. And these initialization tasks must be performed in a certain order. I will introduce the preparation work I have done for initialization one by one.

Gamer generation

//Networkmanager中的start中生成
gamer = PhotonNetwork.Instantiate("Gamer", Vector3.zero, Quaternion.identity, 0);


//Gamer中的Start中执行
if (photonView.IsMine)
{
    ownid = PhotonNetwork.LocalPlayer.ActorNumber;
}
else
{
    ownid = photonView.Owner.ActorNumber;
}

        Networkmanager Each player has one and only one code for managing communication between clients. Gamer is a "player" generated by the network, which is a game object in the world coordinate system. I know that photon creates an instance of the Player class for each player in the room, and the "pieces" I finally generate are in the canvas. In order to make the connection between their Player and chess pieces, I consider using Gamer as an intermediate quantity. By using the variables in the Gamer to point to the manipulated pieces, the link between operators and the pieces they manipulate is established.

        If you can see this, you should be familiar with ownid. I think the boss uses this variable to record the unique identifier of the player, that is, you can simply judge whether it belongs to a Player through Gamer.ownid. I also follow the gourd to draw a ladle, and it is indeed very helpful for the code behind.

Guess you like

Origin blog.csdn.net/bbbbbbooom/article/details/126618940