0
\$\begingroup\$

I'm trying to add multiplayer to my XNA platform game, and have just failed miserably.

The idea behind my method is that the client knows what tick the server is at, and knows how long it takes to get to the server and back. It sends it's input and position to the server with how long ago the input was put in, and then the server adds it to the list.

Each server tick, the server goes through it's list of updates and sends each player the most relevant input. Then when the client receives the update, it uses the information of how long it took for the player to send it to the server, plus how long it takes to get from the server. It uses this Tick amount to run the information through the player's update, and get a position.

Then it should look at where he is and where the server says he is, and average it out.

It sounds right in my head, but my execution went terribly wrong, and I don't like code dumping but this is a last resort: the important code is near the bottom in UpdateNetwork(), but I added some other parts that you can reference to if you need to

    /// <summary>
    /// Allows the game to run logic such as updating the world,
    /// checking for collisions, gathering input, and playing audio.
    /// </summary>
    /// <param name="gameTime">Provides a snapshot of timing values.</param>
    public override void Update(GameTime gameTime)
    {

        keybState = Keyboard.GetState();
        //Keys are stored in enums which get translated
        //to a string I can pass over
        playerlist[multi.id].GetKeys(keybState);

         //Universal commands (respawn, quit, etc)
        globalKeyboard();

        //Each player, run through them updated
            for (int i = 0; i < playerlist.Count; i++)
            {
                Update_Player(i, 0);

                Update_Flags(i);

                Update_General(i);
            }

           //update network processing center
            UpdateNetwork();

            oldkeybState = keybState;


            //timer class, update based on
            //gameTime's milliseconds
            Trialtime.Count(gameTime);


            Update_Camera();


            Tick++;

            //ping
            Connection.clientSend("PING@" + Tick);

            //predicted servertime
            if (serveroffset < 0)
            {
                offset.Add(offset.getAvgTick() - serveroffset);
            }
            else if (serveroffset > 4)
            {
                offset.Add(serveroffset - 1);
            }
            else
            {
                offset.Add(serveroffset);
            }

            //random variables to display to scree
            int predict = ServerTick + latency.getAvgTick() + offset.getAvgTick();
            packin.Add(latency.getAvgTick() / 2 + offset.getAvgTick());
            packout = packin.getAvgTick() - offset.getAvgTick() * 2;

            //Sends position to the server, plus predict
            SendPosition(playerlist[multi.id].position, predict);

            //sends the predicted server time, which the server then calculates
            // if the guess is too high or too low
            Connection.clientSend("PREDICT@" + predict.ToString());

        base.Update(gameTime);
    }




    /// <summary>
    /// First thing that happens is the player updates his client position
    /// </summary>
    /// <param name="id"></param>
    /// <param name="tickloop"></param>
    void Update_Player(int id, int tickloop)
    {
        //If the sprintTimer is initialized,
        //update the timer
        if (sprintTimer > 0)
        {
            SprintTime(id);
        }

        //Checks player state and reacts
        //accordingly
        for (int i = 0; i <= tickloop; i++)
        {

            if (playerlist[id].state == DEAD)
            {
                deathTimer(id);
            }
            else if (playerlist[id].state == GROUND)
            {
                Control_Keyboard_Ground(id);
                Control_Update_Ground(id);
            }
            else if (playerlist[id].state == AIR)
            {
                Control_Keyboard_Air(id);
                Control_Update_Air(id);
            }
            else if (playerlist[id].state == WALL)
            {
                Control_Keyboard_Wall(id);
                Control_Update_Wall(id);
            }
        }
    }




    /// <summary>
    /// Here's an example of how the informationn is processed
    /// 
    /// </summary>
    private void Control_Keyboard_Air(int id)
    {
        GamePadState currentState = GamePad.GetState(PlayerIndex.One);
        Vector2 ts = currentState.ThumbSticks.Left;

        //First Check if right is held down
        if (playerlist[id].KeysDown[(int)KeyType.Right] == 1)
        {
            List<blockClass> Details = Collision(new Rectangle((int)playerlist[id].position.X + 28, (int)playerlist[id].position.Y,
                20, 20), id);
            if (Details.Count == 0)
            {
                if (playerlist[id].velocity < 6)
                    playerlist[id].velocity += .25f;

                playerlist[id].orientation = SpriteEffects.None;
            }
        }


        if (playerlist[id].KeysDown[(int)KeyType.Left] == 1)
        {
            List<blockClass> Details = Collision(new Rectangle((int)playerlist[id].position.X + -18, (int)playerlist[id].position.Y,
                20, 20), id);
            if (Details.Count == 0)
            {

                if (playerlist[id].velocity > -6)
                    playerlist[id].velocity -= .25f;
                playerlist[id].orientation = SpriteEffects.FlipHorizontally;
            }
        }

    }

    ///<summary>
    ///Process for analyzing collisions mid air
    ///--Ground for gravity
    ///--Wall
    /// </summary>
    void Control_Update_Air(int id)
    {
        //update animation
        if (playerlist[id].prevstate != AIR)
        {
            playerlist[id].prevstate = AIR;
            playerlist[id].animJump();
        }
        if (playerlist[id].gravity < 0)
        {
            //p_type is a variable that initiates a boost
            if (p_type == 1 && playerlist[id].gravity < -3)
            {
                createBoost(playerlist[id].position, Color.DarkRed);
            }

            //Check collision above, if you hit a ceiling then stop
            List<blockClass> Details = Collision(new Rectangle((int)playerlist[id].position.X + 8, (int)playerlist[id].position.Y - 10,
                    24, 20), id);
            if (Details.Count > 0)
            {
                playerlist[id].gravity = 0;
                p_type = 0;
            }
        }

        //Update players vertical position
        playerlist[id].position += up * playerlist[id].gravity;

        //update gravity
        playerlist[id].gravity += 0.3f;

        //If player is moving right
        if (playerlist[id].velocity > 0)
        {
            //Check collision to the right
            List<blockClass> Details = Collision(new Rectangle((int)playerlist[id].position.X + 28, (int)playerlist[id].position.Y,
                20, 20), id);

            //If there is a collision
            if (Details.Count > 0)
            {
                //and one of the blocks is type WALL
                if (Details[0].type == 2)
                {

                    //Set the things to initiate wall controls
                    playerlist[id].position.X = Details[0].position.X - 30;
                    playerlist[id].gravity = 0;
                    playerlist[id].speed = playerlist[id].velocity;
                    playerlist[id].velocity = 0;
                    playerlist[id].state = WALL;
                    playerlist[id].orientation = SpriteEffects.None;
                    p_type = 0;

                }
            }
            else
            {
                //if there is no collision, move right
                playerlist[id].position += right * playerlist[id].velocity;
            }
        } //if player is moving left
        else if (playerlist[id].velocity < 0)
        {
            List<blockClass> Details = Collision(new Rectangle((int)playerlist[id].position.X + -18, (int)playerlist[id].position.Y,
                20, 20), id);
            if (Details.Count > 0)
            {
                if (Details[0].type == 2)
                {
                    playerlist[id].position.X = Details[0].position.X + 28;
                    playerlist[id].speed = -playerlist[id].velocity;
                    playerlist[id].gravity = 0;
                    playerlist[id].velocity = 0;
                    playerlist[id].state = WALL;
                    playerlist[id].orientation = SpriteEffects.FlipHorizontally;
                    outputDebug = "WALL LEFT";
                    p_type = 0;
                }
                else
                {
                    outputDebug = "wall type fail";
                }
            }
            else
            {
                //position
                playerlist[id].position += right * playerlist[id].velocity;
            }
        }

        //Now check for ground
        List<blockClass> cb = Collision(new Rectangle((int)playerlist[id].position.X, (int)playerlist[id].position.Y + 30,
            32, 20), id);

        if (cb.Count > 0)
        {
            if (cb[0].type == 1 || cb[0].type == 3)
            {
                p_type = 0;
                float displace = 27 - (cb[0].position.Y - playerlist[id].position.Y);
                playerlist[id].position.Y -= displace;
                playerlist[id].state = GROUND;
            }
        }

    }




    /// <summary>
    /// Now onto the ugly part that's messing everything up.
    /// 
    /// </summary>
    public void UpdateNetwork()
    {
        //Run the networking input
        Connection.GetMessage();

        //incoming is a list<String> of all the messages
        //in the network buffer
        if (Connection.incoming.Count == 0)
        {
            //does nothing
            //correctPosition();
        }
        else
        {
            //For each message
            for (int i = 0; i < Connection.incoming.Count; i++)
            {
                //I'm using strings in this form:
                //COMMAND@INFORMATION
                String[] convert = Connection.incoming[i].Split('@');

                //if the command is "UP" which is UDPATE
                if (convert[0] == "UP")
                {
                    update = true;

                    //0 is UP
                    //1 is the server's TICK
                    //2 is all the players information

                    //this checks and sets what string
                    //is the latest one
                    if (seconds < Int32.Parse(convert[1]))
                    {
                        seconds = Int32.Parse(convert[1]);
                        packet = i;
                        update_str = convert;
                    }
                } 
                else if (convert[0] == "TICK")
                {
                    if (seconds < Int32.Parse(convert[1]))
                    {
                        seconds = Int32.Parse(convert[1]);
                        tick = true;
                    }
                } //gets the latency round about
                else if (convert[0] == "PING")
                {
                    latency.Add(Tick - Int32.Parse(convert[1]));
                    serveroffset = Int32.Parse(convert[2]);
                }
                Connection.incoming.Remove(Connection.incoming[i]);
            }

            if (tick)
            {
                snapshots.Add(seconds - ServerTick);
                ServerTick = seconds;
                tick = false;
            }

            //Here's the part that's NEW:
            //vvvvvvvvvvvvvvvvvvvvvvvvv
            if (update)
            {
                if (update_str != null)
                {
                    String[] data = update_str[2].Split('$');

                    //0 = id
                    //1 = predicted tick
                    //2 = avg latency tick
                    //3 = position X
                    //4 = position Y
                    //5 = keystrokes
                    int id = Int32.Parse(data[0]);
                    float x = float.Parse(data[1]);
                    float y = float.Parse(data[2]);
                    String strokes = data[3];

                    //last position
                    playerlist[id].clientPosition = playerlist[id].position;

                    //set the players keys to what the server says
                    playerlist[id].SetKeys(strokes);
                    //reset the position to where it was at the server
                    playerlist[id].position = new Vector2(x, y);

                    //run the player through the Update through the amount of ticks
                    //it took to get through server and to player
                    Update_Player(id, latency.getAvgTick());

                    //If the distance is too big, try to average it out
                    if (Vector2.Distance(playerlist[id].position, playerlist[id].clientPosition) > 2) {

                        playerlist[id].position = new Vector2(playerlist[id].position.X + (playerlist[id].position.X - playerlist[id].clientPosition.X) / 2,
                            playerlist[id].position.Y + (playerlist[id].position.Y - playerlist[id].clientPosition.Y) / 2);
                    }
                }
                update = false;
            }
        }


    }

The result is the player flickering back and forth like a madman

\$\endgroup\$
1

3 Answers 3

2
\$\begingroup\$

Packet delivery times won't be consistent, you can't count on them. Thus any approach that uses them in the calculation is going to produce bad answers.

\$\endgroup\$
0
\$\begingroup\$

It's hard to tell from looking at this code but I had a similar problem in my networked game, where the player was jumping back and forth very quickly between two points. In my case this was caused by differences in the actual transfer time of packets from the server to the client. Even if you know the average server-client latency, you don't necessarily know the actual latency of an individual packet. So I wonder if something is getting out of sync here. You might want to create a class on the client that receives and stores the update packets, and then hands them out to your game loop on a smoothed-out basis.

\$\endgroup\$
0
\$\begingroup\$

I'm working in an app with XNA 4 too. I'm just thinking: Can we use a database to control things like that? Some flags to verify which turn is? I don't know if XNA work with databases..

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .