Local Multiplayer Game

@Creator27 It’s still really jerky even if I move my finger slow. Tha faster I move, the larger the jumps from position to position.

@dave1707, try changing the duration of the tween to see what works for you.

@dave1707, if that doesn’t work then try changing updatePositions() to this:

function updatePositions(state, pos)
    local interpolation = tween(0.1, state, {playerX = pos.x, playerY = pos.y})
    state["waypointId"] = interpolation
end

@Creator27 I replaced the whole function and it seems to be just as jerky.

Do you know anyone with an iPad. I would suggest you get them a copy of Codea so you can have access to 2 iPads to work on this. It’s been 4 months and things aren’t going like you want them. It’s would be a lot easier if you can see what’s happening as you make changes.

@dave1707, I (once again) have no hope that this is gonna work, but here it is:

function setup()
    socket = require("socket")
    parameter.action("Create Room", createRoom)
    parameter.action("Join Room", function()
        if other_ip then
            joinRoom(other_ip, false)
        else
            parameter.text("IP", "", function(t)
                other_ip = t
            end)
        end
    end)
    parameter.action("Find Room", function()
        findRoom()
    end)
    client = socket.udp()
    client:settimeout(0)
    -- We need to create a table that can hold information about our server, if you end up making one
    server_info = {players = 0, clients = {}, world_state = {}, puppet_states = {}, ids_taken = {}}
    -- We also need to create a table that can hold info that the client has
    client_info = {serverIp = "", world_state = {}}
    other_ip = nil
    current_time = nil
    current_room_count = 1
    tick = 0
    finding_room = false
    server = false
    joinedRoom = false
    myPlayerServer = nil
    myPlayerClient = nil
    sendNumber = 0
    sendInterval = 0
    playerTick = 0
end

function createRoom()
    -- We need to find your address, so we create a new UDP socket and assign it a random IP
    local ip, port = getLocalIP()
    client:setsockname("*", "14285")
    local ip2, port2 = client:getsockname()
    client:setoption("broadcast", true)
    client:setoption("dontroute", true)
    your_ip = ip2
    your_port = port2
    print("Room created!")
    print("Connect to "..ip)
    server = true
end

function joinRoom(ip, finding_room)
    if server == false then
        if ip ~= "*" then
            if finding_room == false then
                print("Attempting to join the room...")
            end
            -- The port for all the servers is the same, so we only need the IP address of the server
            client:setpeername(ip, "14285")
            client:send("connection confirmation")
            current_time = os.time()
        else
            print("Address not allowed!")
        end
    else
        print("You are already hosting a server!")
    end
end

function findRoom()
    if server == false then
        print("Attempting to find a room...")
        finding_room = true
        local ip, port = getLocalIP()
        local ip1, ip2 = string.match(ip, "(%d+.%d+.%d+.)(%d+)")
        local success, err = pcall(function()
            for z = 1, 255 do
                client:setpeername(ip1..z, "14285")
                client:send("requesting server info")
            end
            client:setpeername("*")
            client:setsockname("*", "14285")
        end)
        if success == false then
            -- An error occured whether that be no internet connection or message wasn't received
            print("An error occured! Please check your internet connection, and try again later.")
            return
        end
        current_time = os.time()
    else
        print("You are already hosting a server!")
    end
end

function sendMessage(msg)
    if server == true then
        for i,c in pairs(server_info["clients"]) do
            client:sendto(tostring(msg), c.ip, c.port)
        end
    end
end

function leaveRoom()
    -- We need to notify the server that we are leaving, so we send a packet to them
    player:send("disconnection confirmation")
end

function resetParameters(params, args)
    parameter.clear()
    for i = 1, #params do
        parameter.action(params[i], args[i])
    end
end

function getLocalIP()
    local udp = socket.udp()
    udp:setpeername("192.167.188.122", "14285")
    local ip, port = udp:getsockname()
    udp:close()
    return ip, port
end

function receiveData()
    if server == true then
        local msg, ip, port = client:receivefrom()
        if msg ~= nil then
            if msg == "connection confirmation" then
                if server_info["players"] < 32 then
                    -- We can now add 1 more to our player count
                    server_info["players"] = server_info["players"] + 1
                    -- We now need to store the clients IP and port, so we can send them messages later
                    local uid = {ip = ip, port = port}
                    if not server_info["clients"][uid] then
                        table.insert(server_info["clients"], uid)
                    end
                    -- We now create a random ID for the connecting peer so we can differentiate between players on the server
                    local playerID = math.random(10000, 99999)
                    if not server_info["ids_taken"][playerID] then
                        server_info["ids_taken"][playerID] = playerID
                    else
                        playerID = math.random(10000, 99999)
                    end
                    client:sendto("valid confirmation"..playerID, ip, port)
                    client:sendto("peer address info"..json.encode(uid), ip, port)
                    -- We need to send the joining client our IP, so they can send messages to us later
                    local our_ip, our_port = getLocalIP()
                    client:sendto("server ip"..our_ip, ip, port)
                    -- We can now send everyone that has joined this room a packet telling them the amount of players in the room
                    for i,c in pairs(server_info["clients"]) do
                        client:sendto("player count"..tostring(server_info["players"]), c.ip, c.port)
                    end
                else
                    -- We don't have enough room for another player, so we send the peer a message telling them to disconnect
                    local reason = "Max players inside the room!"
                    client:sendto("invalid confirmation"..reason, ip, port)
                end
            elseif msg == "requesting server info" then
                -- We need to find your address, so we create a new UDP socket and assign it a random IP
                local our_ip, our_port = getLocalIP()
                -- We now know that somebody is trying to find a server, so we send a packet back to them with our server IP and port
                local uid = {ip = our_ip}
                client:sendto("server info"..json.encode(uid), ip, port)
            elseif msg and string.find(msg, "disconnection confirmation") then
                -- We know that one of the clients connected to this server wants to leave, so we send a packet back telling them that we have been notified
                client:sendto("notified of disconnection", ip, port)
                local uid = {ip = ip, port = port}
                local clientInfo
                local clientState
                for i = 1, #server_info["clients"] do
                    if server_info["clients"][i] == uid then
                        clientInfo = i
                    end
                end
                for i = 1, #server_info["world_state"] do
                    local state = server_info["world_state"][i]
                    if state["addrInfo"].ip == uid.ip and state["addrInfo"].port == uid.port then
                        clientState = i
                    end
                end
                table.remove(server_info["clients"], clientInfo)
                table.remove(server_info["world_state"], clientState)
                print("server: "..json.encode(server_info["world_state"]))
                -- We also need to deduct the player count
                server_info["players"] = server_info["players"] - 1
            elseif msg and string.find(msg, "client state") then
                -- A connected peer has sent their client state to us, so we update our world state accordingly
                local clientState = json.decode(string.sub(msg, 13, #msg))
                local hasClientState = false
                local existingState = nil
                for i,s in pairs(server_info["world_state"]) do
                    if s.playerName == clientState.playerName then
                        hasClientState = true
                        existingState = s
                        break
                    end
                end
                if hasClientState == true then
                    local uid = {ip = ip, port = port}
                    if clientState.sendNumber > existingState.sendNumber then
                        existingState.sendNumber = clientState.sendNumber
                        -- We now also add a new waypoint for our puppet peer to follow
                        local waypoint = {x = clientState.playerX, y = clientState.playerY}
                        -- We now interpolate to the new state position
                        if existingState["waypointId"] == nil then
                            updatePositions(existingState, waypoint)
                        end
                    end
                else
                    if not server_info["world_state"][clientState] then
                        table.insert(server_info["world_state"], clientState)
                    end
                end
            end
        end
    else
        if finding_room == false then
            local result = client:receive()
            if result and string.find(result, "valid confirmation") then
                -- We know that we successfully joined the room, and we can now receive messages from the server!
                print("Successfully joined the room!")
                joinedRoom = true
                current_time = nil
                parameter.clear()
                -- We now create a button that allows the player to leave the room
                local paramName = {"Leave Room"}
                local paramArgs = {function() leaveRoom() end}
                resetParameters(paramName, paramArgs)
                -- We now set our player name to the ID that the server sent us
                local playerID = string.sub(result, 19, #result)
                local playerTab = {playerName = playerID, playerX = WIDTH/2, playerY = HEIGHT/2}
                myPlayerClient = playerTab
                if not client_info["world_state"][myPlayerClient] then
                    table.insert(client_info["world_state"], myPlayerClient)
                end
                client:setoption("dontroute", true)
            elseif result and string.find(result, "peer address info") then
                local addrInfo = json.decode(string.sub(result, 18, #result))
                if myPlayerClient then
                    myPlayerClient["addrInfo"] = addrInfo
                end
            elseif result and string.find(result, "server ip") then
                -- Now that we have the servers IP address, we can message them later if we need to
                local serverIp = string.sub(result, 10, #result)
                client_info["serverIp"] = serverIp
            elseif result and string.find(result, "player count") then
                -- We now know the current amount of players in our room, for now at least
                local count = string.sub(result, 13, #result)
                print("Player count: "..count)
            elseif result and string.find(result, "invalid confirmation") then
                -- We can now see the reason for our invalid confirmation and display it
                local reason = string.sub(result, 21, #result)
                print("Could not join the room: "..reason)
                current_time = nil
                -- We can now also remove the address and port set for our peer
                client:setpeername("*")
            elseif result == "notified of disconnection" then
                -- We know that we can now safely leave the server
                client:setpeername("*")
                local removingState
                for i,s in pairs(client_info["world_state"]) do
                    if s["addrInfo"].ip == myPlayerClient.addrInfo.ip and s["addrInfo"].port == myPlayerClient.addrInfo.port then
                        removingState = i
                    end
                end
                table.remove(client_info["world_state"], removingState)
                print("client: "..json.encode(client_info["world_state"]))
                joinedRoom = false
                other_ip = nil
                print("Successfully left the room!")
                local paramNames = {"Create Room", "Join Room", "Find Room"}
                local paramArgs = {createRoom, function()
                        if other_ip then
                            joinRoom(other_ip, false)
                        else
                            parameter.text("IP", "", function(t)
                                other_ip = t
                            end)
                        end
                    end, findRoom}
                resetParameters(paramNames, paramArgs)
            elseif result and string.find(result, "update client side") then
                -- The server has sent us a update packet, so we fetch the state and update our world state
                local state = json.decode(string.sub(result, 19, #result))
                local oldState = nil
                local foundState = false
                for i,s in pairs(client_info["world_state"]) do
                    if s.playerName == state.playerName then
                        oldState = s
                        foundState = true
                        break
                    end
                end
                if foundState == true then
                    oldState.playerX = state.playerX
                    oldState.playerY = state.playerY
                end
            elseif result == nil then
                if current_time then
                    if finding_room == false then
                        if os.time() > current_time + 5 then
                            -- If we get no response back for 5 seconds or more, we know it didn't work
                            print("Failed to connect, please try again later")
                            current_time = nil
                        end
                    end
                end
            end
        else
            local result, ip, port = client:receivefrom()
            if result and string.find(result, "server info") then
                -- Now we can attempt to join the room with the given IP address
                local tab = json.decode(string.sub(result, 12, #result))
                if finding_room == true then
                    joinRoom(tab.ip, true)
                    finding_room = false
                end
            else
                if finding_room == true then
                    if current_time and os.time() > current_time + 15 then
                        -- If we get no response back for 15 seconds or more, we know we couldn't find a room
                        print("Failed to find a room!")
                        current_time = nil
                    end
                end
            end
        end
    end 
end

function updatePositions(state, pos)
    local interpolation
    state["waypointId"] = interpolation
    interpolation = tween(0.075, state, {playerX = pos.x, playerY = pos.y}, tween.easing.linear, function()
        state["waypointId"] = nil
    end)
end

function touched(touch)
    if touch.state ~= ENDED and touch.state ~= CANCELLED then
        if touch.state == BEGAN or touch.state == CHANGED then
            if joinedRoom == true then
                if myPlayerClient then
                    myPlayerClient.playerX = CurrentTouch.x
                    myPlayerClient.playerY = CurrentTouch.y
                    sendNumber = sendNumber + 1
                end
            end
        end
    end
end

function updatePlayers()
    -- We now update the position of our player puppets, so it looks like the players are being updated in realtime
    if server == false then
        if joinedRoom == true then
            for i,cp in pairs(client_info["world_state"]) do
                fill(0, 255, 0)
                text("client side", cp.playerX, cp.playerY + 30)
                ellipse(cp.playerX, cp.playerY, 35)
            end
        end
    else
        for i,sp in pairs(server_info["world_state"]) do
            fill(255, 13, 0)
            text("server side", sp.playerX, sp.playerY + 30)
            ellipse(sp.playerX, sp.playerY, 35)
        end
    end
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)
    
    -- This sets the line thickness
    strokeWidth(5)
    
    -- Do your drawing here
    updatePlayers()
    receiveData()
    if myPlayerClient and joinedRoom == true and myPlayerClient.addrInfo ~= nil then
        local playerTab = {playerName = myPlayerClient.playerName, playerX = myPlayerClient.playerX, playerY = myPlayerClient.playerY, sendNumber = sendNumber, addrInfo = myPlayerClient.addrInfo}
        sendInterval = sendInterval + 1
        if sendInterval >= 3 then
            client:send("client state"..json.encode(playerTab))
            sendInterval = 0
        end
    end
end
1 Like

An error occured! Please check your internet connection, and try again later.

@dave1707, try it now.

@Creator27 Still jerky.