Local Multiplayer Game

@dave1707, I’ve updated the code above so that you control YOUR player and not get mixed with anybody else’s player. Also, I will start to work on that issue you brought up.

@Creator27 I got 2 spheres on each iPad no matter how many times I tapped on join.

@dave1707, hopefully this code works. When you close the project, the client leaving should send a message to the server, which will send a server to all other clients and tell them to remove that entity. Here is the code:


function setup()
    scene = craft.scene()
    craft.scene.main = scene
    socket = require("socket")
    messenger = socket.udp()
    messenger:setoption("broadcast", true)
    messenger:settimeout(0)
    parameter.action("Host", createServer)
    entities = {}
    my_client = nil
    client_entities = {}
    my_entity = nil
    connected = false
    client_count = 0
    my_server = {server = nil, entities = {}, client_data = {}, ids = {}}
    -- Something to land on so the clients dont fall endlessly into the void
    ground = scene:entity()
    ground.model = craft.model(asset.builtin.Primitives.RoundedCube)
    ground.position = vec3(0, -2, 10)
    ground.scale = vec3(10, 0.1, 10)
    gbody = ground:add(craft.rigidbody, STATIC)
    ground:add(craft.shape.model, ground.model)
    -- Setup camera
    scene.camera.position = vec3(0, 8, -10)
    scene.camera.rotation = quat.eulerAngles(25, 0, 0)
end

function joinServer(ip, port)
    if my_client == nil then
        local client = socket.udp()
        client:settimeout(0)
        my_client = client
        client:setpeername(ip, port)
        client:send("join"..string.sub(tostring(client), 17, #tostring(client)))
        parameter.action("Disconnect", function() disconnectFromServer() end)
    end
end

function receiveUpdate()
    if my_client ~= nil then
        local data = my_client:receive()
        if data ~= nil then
            if string.find(data, "somebody joined") then
                local entity = makePlayer(vec3(0, 0, 10))
                table.insert(client_entities, entity)
            elseif string.find(data, "you joined") then
                local c = string.match(data, "%d")
                local tab = json.decode(string.sub(data, 11 + #c, #data))
                -- Make your player first
                local your_player = makePlayer(vec3(0, 0, 10), my_client)
                table.insert(client_entities, your_player)
                -- Then, make everybody else's player
                for i = 1, (tonumber(c) - 1) do
                    local other_player = makePlayer(vec3(0, 0, 10))
                    table.insert(client_entities, other_player)
                end
            elseif string.find(data, "remove player") then
                for i,e in pairs(client_entities) do
                    if e.status == "disconnected" then
                        e:destroy()
                    end
                end
            end
        end
    end
end

function willClose()
    disconnectFromServer()
end

function disconnectFromServer()
    if my_client ~= nil then
        my_entity.status = "disconnected"
        my_client:send("disconnected")
        my_client:close()
        my_client = nil
        my_entity = nil
        parameter.clear()
        parameter.action("Host", createServer)
        for i,e in pairs(client_entities) do
            e:destroy()
        end
    end
end

function createServer()
    if my_server.server == nil then
        server = socket.udp()
        server:setsockname("*", 14285)
        server:setoption("broadcast", true)
        server:settimeout(0)
        my_server.server = server
        server_name = Server_Name
    end
end

function stopServer()
    if my_server ~= nil then
        server:close()
    end
end

function makePlayer(pos, client)
    if not entities[entity] then
        entity = scene:entity()
        entity.model = craft.model(asset.builtin.Primitives.Sphere)
        entity.position = pos
        entity.scale = vec3(0.7, 0.7, 0.7)
        body = entity:add(craft.rigidbody, DYNAMIC)
        entity:add(craft.shape.sphere, 0.85)
        entity.material = craft.material(asset.builtin.Materials.Specular)
        entity.material.diffuse = color(math.random(0, 255), math.random(0, 255), math.random(0, 255))
        body.linearDamping = 0.3
        table.insert(entities, entity)
        if my_entity == nil then
            my_entity = entity
        end
        if client ~= nil then
            entity.master = client
        end
        entity.status = "connected to server"
        return entity
    end
end

function touched(touch)
    if touch.state == MOVING then
        if touch.deltaX > 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(-30, 0, 0))
                end
            end
        elseif touch.deltaX < 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(30, 0, 0))
                end
            end
        elseif touch.deltaY > 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 0, 30))
                end
            end
        elseif touch.deltaY < 0 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 0, -30))
                end
            end
        end
    elseif touch.state == BEGAN then
        if touch.tapCount == 2 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 450, 0))
                end
            end
        end
    end
end

function draw()
    if my_client == nil then
        messenger:sendto("checking for servers", "255.255.255.255", "14285")
    end
    if my_server.server ~= nil then
        local data, ip, port = my_server.server:receivefrom()
        if data == "checking for servers" then
            my_server.server:sendto("server available", ip, port)
        elseif data ~= nil then
            if string.find(data, "join") then
                local uid = {ip = ip, port = port}
                if not my_server.client_data[uid] then
                    table.insert(my_server.client_data, uid)
                end
                for i,c in pairs(my_server.client_data) do
                    if c ~= uid then
                        my_server.server:sendto("somebody joined", c.ip, c.port)
                    else
                        my_server.server:sendto("you joined"..tostring(#my_server.client_data)..json.encode(uid), c.ip, c.port)
                    end
                end
            elseif string.find(data, "disconnected") then
                local uid = {ip = ip, port = port}
                for i,c in pairs(my_server.client_data) do
                    if c ~= uid then
                        my_server.server:sendto("remove player", c.ip, c.port)
                    end
                end
                table.remove(my_server.client_data, my_server.client_data[uid])
            else
                print(data)
            end
        end
    end
    if my_client == nil then
        local data, ip, port = messenger:receivefrom()
        if data ~= nil then
            if data ==  "server available" then
                parameter.action(tostring(ip).." - Join", function() joinServer(ip, port) end)
            end
        end
    end
    receiveUpdate()
end

@dave1707, I’ve just updated the code above so it hopefully works, because it wasn’t before.

@Creator27 I only got 1 sphere per iPad no matter how many times I tapped on join. Each sphere disappeared on the iPad that I pressed disconnect on.

@dave1707, I have programmed the game so that hopefully, each client should send the server it’s position and then the server should send everybody else the coordinates and each client would update each sphere on their device accordingly. Here’s the code:

function setup()
    scene = craft.scene()
    craft.scene.main = scene
    socket = require("socket")
    messenger = socket.udp()
    messenger:setoption("broadcast", true)
    messenger:settimeout(0)
    parameter.action("Host", createServer)
    entities = {}
    my_client = nil
    my_entity = nil
    client_entities = {}
    my_server = {server = nil, clients = {}, client_data = {}, ids = {}}
    -- Something to land on so the clients dont fall endlessly into the void
    ground = scene:entity()
    ground.model = craft.model(asset.builtin.Primitives.RoundedCube)
    ground.position = vec3(0, -2, 10)
    ground.scale = vec3(10, 0.1, 10)
    gbody = ground:add(craft.rigidbody, STATIC)
    ground:add(craft.shape.model, ground.model)
    -- Setup camera
    scene.camera.position = vec3(0, 8, -10)
    scene.camera.rotation = quat.eulerAngles(25, 0, 0)
end

function joinServer(ip, port)
    if my_client == nil then
        local client = socket.udp()
        client:settimeout(0)
        my_client = client
        client:setpeername(ip, port)
        client:send("join"..string.sub(tostring(client), 17, #tostring(client)))
    end
end

function receiveUpdate()
    if my_client ~= nil then
        local data = my_client:receive()
        if data ~= nil then
            if string.find(data, "somebody joined") then
                makeOtherPlayer(vec3(0, 0, 10))
            elseif string.find(data, "you joined") then
                local c = string.match(data, "%d")
                local tab = json.decode(string.sub(data, 11 + #c, #data))
                -- Make your player first
                makeYourPlayer(vec3(0, 0, 10))
                -- Then, make everybody else's player
                for i = 1, (tonumber(c) - 1) do
                    makeOtherPlayer(vec3(0, 0, 10))
                end
            elseif string.find(data, "new player position") then
                -- We first convert the message into the table that contains the new set of coordinates
                local tab = json.decode(string.sub(data, 20, #data))
                for i,e in pairs(client_entities) do
                    if tostring(e) == tab.entity then
                        e.position = vec3(tab.x, tab.y, tab.z)
                    end
                end
            end
        end
        if my_entity ~= nil then
            local tab = {x = my_entity.position.x, y = my_entity.position.y, z = my_entity.position.z, entity = tostring(my_entity)}
            my_client:send("player position"..json.encode(tab))
        end
    end
end

function createServer()
    if my_server.server == nil then
        server = socket.udp()
        server:setsockname("*", 14285)
        server:setoption("broadcast", true)
        server:settimeout(0)
        my_server.server = server
        server_name = Server_Name
    end
end

function makeYourPlayer(pos)
    entity = scene:entity()
    entity.model = craft.model(asset.builtin.Primitives.Sphere)
    entity.position = pos
    entity.scale = vec3(0.7, 0.7, 0.7)
    body = entity:add(craft.rigidbody, DYNAMIC)
    entity:add(craft.shape.sphere, 0.85)
    entity.material = craft.material(asset.builtin.Materials.Specular)
    entity.material.diffuse = color(math.random(0, 255), math.random(0, 255), math.random(0, 255))
    body.linearDamping = 0.3
    my_entity = entity
    table.insert(client_entities, entity)
end

function makeOtherPlayer(pos)
    entity = scene:entity()
    entity.model = craft.model(asset.builtin.Primitives.Sphere)
    entity.position = pos
    entity.scale = vec3(0.7, 0.7, 0.7)
    body = entity:add(craft.rigidbody, DYNAMIC)
    entity:add(craft.shape.sphere, 0.85)
    entity.material = craft.material(asset.builtin.Materials.Specular)
    entity.material.diffuse = color(math.random(0, 255), math.random(0, 255), math.random(0, 255))
    body.linearDamping = 0.3
    table.insert(client_entities, entity)
end

function touched(touch)
    if touch.state == MOVING then
        if touch.deltaX > 0 then
            for i,e in pairs(client_entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(-30, 0, 0))
                end
            end
        elseif touch.deltaX < 0 then
            for i,e in pairs(client_entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(30, 0, 0))
                end
            end
        elseif touch.deltaY > 0 then
            for i,e in pairs(client_entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 0, 30))
                end
            end
        elseif touch.deltaY < 0 then
            for i,e in pairs(client_entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 0, -30))
                end
            end
        end
    elseif touch.state == BEGAN then
        if touch.tapCount == 2 then
            for i,e in pairs(entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 450, 0))
                end
            end
        end
    end
end

function draw()
    if my_client == nil then
        messenger:sendto("checking for servers", "255.255.255.255", "14285")
    end
    if my_server.server ~= nil then
        local data, ip, port = my_server.server:receivefrom()
        if data == "checking for servers" then
            my_server.server:sendto("server available", ip, port)
        elseif data ~= nil then
            if string.find(data, "join") then
                local uid = {ip = ip, port = port}
                if not my_server.client_data[uid] then
                    table.insert(my_server.client_data, uid)
                end
                for i,c in pairs(my_server.client_data) do
                    if c ~= uid then
                        my_server.server:sendto("somebody joined", c.ip, c.port)
                    else
                        my_server.server:sendto("you joined"..tostring(#my_server.client_data)..json.encode(uid), c.ip, c.port)
                    end
                end
            elseif string.find(data, "player position") then
                local uid = {ip = ip, port = port}
                local tab = string.sub(data, 16, #data)
                for i,c in pairs(my_server.client_data) do
                    if c ~= uid then
                        my_server.server:sendto("new player position"..tab, c.ip, c.port)
                    end
                end
            end
        end
    end
    if my_client == nil then
        local data, ip, port = messenger:receivefrom()
        if data ~= nil then
            if data ==  "server available" then
                parameter.action(tostring(ip).." - Join", function() joinServer(ip, port) end)
            end
        end
    end
    receiveUpdate()
end

@Creator27 There are 3 spheres on each iPad with 2 of the spheres constantly redrawing themselves on top (near) each other. Tried to post a video, but it’s not allowed. A picture just shows 2 spheres. So it looks like the 2 spheres are really just 1 sphere but constantly redrawing itself in different positions, but close to each other.

What format video did you try to post? I can see if I can change the allowed file extensions for the forums to include it

I’m just doing the screen video from Codea using the icon in the lower left of the screen. Here’s a picture of the info of the video and the message I get when I try to include it.


@dave1707, after a long break from the project I have tried once again to sync player positions across every device connected to the current server/s. Here’s the code:

function setup()
    scene = craft.scene()
    craft.scene.main = scene
    socket = require("socket")
    messenger = socket.udp()
    messenger:setoption("broadcast", true)
    messenger:settimeout(0)
    parameter.action("Host", createServer)
    parameter.text("Server Name", "")
    parameter.text("Player Name", "")
    servers = {}
    client_entities = {}
    my_client = nil
    my_entity = nil
    my_server = {server = nil, name = "", client_data = {}, master_client = nil}
    -- Something to land on so the clients dont fall endlessly into the void
    ground = scene:entity()
    ground.model = craft.model(asset.builtin.Primitives.RoundedCube)
    ground.position = vec3(0, -2, 10)
    ground.scale = vec3(10, 0.1, 10)
    gbody = ground:add(craft.rigidbody, STATIC)
    ground:add(craft.shape.model, ground.model)
    -- Setup camera
    scene.camera.position = vec3(0, 8, -10)
    scene.camera.rotation = quat.eulerAngles(25, 0, 0)
end

function joinServer(ip, port)
    if Player_Name ~= "" then
        if my_client == nil then
            local client = socket.udp()
            client:settimeout(0)
            my_client = client
            client:setpeername(ip, port)
            client:send("join")
            return client
        end
    else
        print("Please enter a valid player name!")
    end
end

function receiveUpdate()
    if my_client ~= nil then
        local data = my_client:receive()
        if data ~= nil then
            if string.find(data, "somebody joined") then
                makePlayer(vec3(0, 0, 10))
            elseif string.find(data, "you joined") then
                local c = string.match(data, "%d")
                local tab = json.decode(string.sub(data, 11 + #c, #data))
                -- Make your player first
                makePlayer(vec3(0, 0, 10))
                -- Then, make everybody else's player
                for i = 1, (tonumber(c) - 1) do
                    makePlayer(vec3(0, 0, 10))
                end
            elseif string.find(data, "new position for a player") then
                local tab = json.decode(string.sub(data, 26, #data))
                if my_entity.name == tab.entity then
                    print("its me, nothing happened")
                else
                    for i,e in pairs(client_entities) do
                        if e.name == tab.entity and e ~= my_entity then
                            e.position = vec3(tab.x, tab.y, tab.z)
                        end
                    end
                end
            end
        end
        -- Continously send your position to the server
        if my_entity ~= nil then
            local tab = {x = my_entity.position.x, y = my_entity.position.y, z = my_entity.position.z, entity = my_entity.name}
            tab = json.encode(tab)
            my_client:send("my position"..tab)
        end
    end
end

function createServer()
    if Server_Name ~= "" and Player_Name ~= "" then
        if my_server.server == nil then
            local server = socket.udp()
            server:setsockname("*", 14285)
            server:setoption("broadcast", true)
            server:settimeout(0)
            my_server.server = server
            my_server.name = Server_Name
            local ip, port = server:getsockname()
            local client = joinServer(ip, port)
            my_server.master_client = client
        end
    else
        print("You need to include a name for the server!")
    end
end

function stopServer()
    if my_server ~= nil then
        server:close()
    end
end

function makePlayer(pos)
    if not client_entities[entity] then
        entity = scene:entity()
        entity.model = craft.model(asset.builtin.Primitives.RoundedCube)
        entity.position = pos
        entity.scale = vec3(0.5, 1, 0.5)
        body = entity:add(craft.rigidbody, DYNAMIC)
        body.angularFactor = vec3(0, 0, 0)
        entity:add(craft.shape.box, vec3(1.5, 2, 1.5))
        entity.material = craft.material(asset.builtin.Materials.Specular)
        entity.material.diffuse = color(math.random(0, 255), math.random(0, 255), math.random(0, 255))
        if my_entity == nil then
            my_entity = entity
        end
        entity.name = Player_Name
        table.insert(client_entities, entity)
    end
end

function touched(touch)
    if touch.state == MOVING then
        if touch.deltaX > 0 then
            for i,e in pairs(client_entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(-15, 0, 0))
                end
            end
        elseif touch.deltaX < 0 then
            for i,e in pairs(client_entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(15, 0, 0))
                end
            end
        elseif touch.deltaY > 0 then
            for i,e in pairs(client_entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 0, 15))
                end
            end
        elseif touch.deltaY < 0 then
            for i,e in pairs(client_entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 0, -15))
                end
            end
        end
    elseif touch.state == BEGAN then
        if touch.tapCount == 2 then
            for i,e in pairs(client_entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 450, 0))
                end
            end
        end
    end
end

function draw()
    if my_client == nil then
        messenger:sendto("checking for servers", "255.255.255.255", "14285")
    end
    if my_server.server ~= nil then
        local data, ip, port = my_server.server:receivefrom()
        if data == "checking for servers" then
            my_server.server:sendto("server available"..my_server.name, ip, port)
        elseif data ~= nil then
            if string.find(data, "join") then
                local uid = {ip = ip, port = port}
                if not my_server.client_data[uid] then
                    table.insert(my_server.client_data, uid)
                end
                for i,c in pairs(my_server.client_data) do
                    if c ~= uid then
                        my_server.server:sendto("somebody joined", c.ip, c.port)
                    else
                        my_server.server:sendto("you joined"..tostring(#my_server.client_data)..json.encode(uid), c.ip, c.port)
                    end
                end
            elseif string.find(data, "my position") then
                local uid = {ip = ip, port = port}
                local tab = string.sub(data, 12, #data)
                for i,c in pairs(my_server.client_data) do
                    if c ~= uid then
                        my_server.server:sendto("new position for a player"..tab, c.ip, c.port)
                    end
                end
            end
        end
    end
    if my_client == nil then
        local data, ip, port = messenger:receivefrom()
        if data ~= nil then
            if string.find(data, "server available") then
                local server_name = string.sub(data, 17, #data)
                local new_server = {ip = ip, port = port, name = server_name}
                if not servers[new_server] then
                    table.insert(servers, new_server)
                end 
                for i,s in pairs(servers) do
                    parameter.action(s.name.." - Join", function() joinServer(s.ip, s.port) end)
                end
            end
        end
    end
    receiveUpdate()
end

When I run the code, I get 2 objects on each device. I can move 1 object to push the other around, but what I do on one device doesn’t affect anything on the other device.

@dave1707, try this instead I added one minor change:

function setup()
    scene = craft.scene()
    craft.scene.main = scene
    socket = require("socket")
    messenger = socket.udp()
    messenger:setoption("broadcast", true)
    messenger:settimeout(0)
    parameter.action("Host", createServer)
    parameter.text("Server Name", "")
    parameter.text("Player Name", "")
    servers = {}
    client_entities = {}
    my_client = nil
    my_entity = nil
    my_server = {server = nil, name = "", client_data = {}, master_client = nil}
    -- Something to land on so the clients dont fall endlessly into the void
    ground = scene:entity()
    ground.model = craft.model(asset.builtin.Primitives.RoundedCube)
    ground.position = vec3(0, -2, 10)
    ground.scale = vec3(10, 0.1, 10)
    gbody = ground:add(craft.rigidbody, STATIC)
    ground:add(craft.shape.model, ground.model)
    -- Setup camera
    scene.camera.position = vec3(0, 8, -10)
    scene.camera.rotation = quat.eulerAngles(25, 0, 0)
end

function joinServer(ip, port)
    if Player_Name ~= "" then
        if my_client == nil then
            local client = socket.udp()
            client:settimeout(0)
            my_client = client
            client:setpeername(ip, port)
            client:send("join")
            return client
        end
    else
        print("Please enter a valid player name!")
    end
end

function receiveUpdate()
    if my_client ~= nil then
        local data = my_client:receive()
        if data ~= nil then
            if string.find(data, "somebody joined") then
                makePlayer(vec3(0, 0, 10))
            elseif string.find(data, "you joined") then
                local c = string.match(data, "%d")
                local tab = json.decode(string.sub(data, 11 + #c, #data))
                -- Make your player first
                makePlayer(vec3(0, 0, 10))
                -- Then, make everybody else's player
                for i = 1, (tonumber(c) - 1) do
                    makePlayer(vec3(0, 0, 10))
                end
            elseif string.find(data, "new position for a player") then
                local tab = json.decode(string.sub(data, 26, #data))
                if my_entity.name == tab.entity then
                    print("its me, nothing happened")
                else
                    for i,e in pairs(client_entities) do
                        if e.name == tab.entity and e ~= my_entity then
                            e.position = vec3(tab.x, tab.y, tab.z)
                        end
                    end
                end
            end
        end
        -- Continously send your position to the server
        if my_entity ~= nil then
            local tab = {x = my_entity.position.x, y = my_entity.position.y, z = my_entity.position.z, entity = my_entity.name}
            tab = json.encode(tab)
            my_client:send("my position"..tab)
        end
    end
end

function createServer()
    if Server_Name ~= "" and Player_Name ~= "" then
        if my_server.server == nil then
            local server = socket.udp()
            server:setsockname("*", 14285)
            server:setoption("broadcast", true)
            server:settimeout(0)
            my_server.server = server
            my_server.name = Server_Name
            local ip, port = server:getsockname()
            local client = joinServer(ip, port)
            my_server.master_client = client
        end
    else
        print("You need to include a name for the server!")
    end
end

function stopServer()
    if my_server ~= nil then
        server:close()
    end
end

function makePlayer(pos)
    if not client_entities[entity] then
        entity = scene:entity()
        entity.model = craft.model(asset.builtin.Primitives.RoundedCube)
        entity.position = pos
        entity.scale = vec3(0.5, 1, 0.5)
        body = entity:add(craft.rigidbody, DYNAMIC)
        body.angularFactor = vec3(0, 0, 0)
        entity:add(craft.shape.box, vec3(1.5, 2, 1.5))
        entity.material = craft.material(asset.builtin.Materials.Specular)
        entity.material.diffuse = color(math.random(0, 255), math.random(0, 255), math.random(0, 255))
        if my_entity == nil then
            my_entity = entity
        end
        entity.name = Player_Name
        table.insert(client_entities, entity)
    end
end

function touched(touch)
    if touch.state == MOVING then
        if touch.deltaX > 0 then
            for i,e in pairs(client_entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(-15, 0, 0))
                end
            end
        elseif touch.deltaX < 0 then
            for i,e in pairs(client_entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(15, 0, 0))
                end
            end
        elseif touch.deltaY > 0 then
            for i,e in pairs(client_entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 0, 15))
                end
            end
        elseif touch.deltaY < 0 then
            for i,e in pairs(client_entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 0, -15))
                end
            end
        end
    elseif touch.state == BEGAN then
        if touch.tapCount == 2 then
            for i,e in pairs(client_entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 450, 0))
                end
            end
        end
    end
end

function draw()
    if my_client == nil then
        messenger:sendto("checking for servers", "255.255.255.255", "14285")
    end
    if my_server.server ~= nil then
        local data, ip, port = my_server.server:receivefrom()
        if data == "checking for servers" then
            my_server.server:sendto("server available"..my_server.name, ip, port)
        elseif data ~= nil then
            if string.find(data, "join") then
                local uid = {ip = ip, port = port}
                if not my_server.client_data[uid] then
                    table.insert(my_server.client_data, uid)
                end
                for i,c in pairs(my_server.client_data) do
                    if c ~= uid then
                        my_server.server:sendto("somebody joined", c.ip, c.port)
                    else
                        my_server.server:sendto("you joined"..tostring(#my_server.client_data)..json.encode(uid), c.ip, c.port)
                    end
                end
            elseif string.find(data, "my position") then
                local uid = {ip = ip, port = port}
                local tab = string.sub(data, 12, #data)
                for i,c in pairs(my_server.client_data) do
                    my_server.server:sendto("new position for a player"..tab, c.ip, c.port)
                end
            end
        end
    end
    if my_client == nil then
        local data, ip, port = messenger:receivefrom()
        if data ~= nil then
            if string.find(data, "server available") then
                local server_name = string.sub(data, 17, #data)
                local new_server = {ip = ip, port = port, name = server_name}
                if not servers[new_server] then
                    table.insert(servers, new_server)
                end 
                for i,s in pairs(servers) do
                    parameter.action(s.name.." - Join", function() joinServer(s.ip, s.port) end)
                end
            end
        end
    end
    receiveUpdate()
end

Now there’s only 1 object on each device.

@dave1707, when two players join the game, the second player should see every other player as usual, but instead they should be in the position they spawned in when they joined the game.

function setup()
    scene = craft.scene()
    craft.scene.main = scene
    socket = require("socket")
    messenger = socket.udp()
    messenger:setoption("broadcast", true)
    messenger:settimeout(0)
    parameter.action("Host", createServer)
    parameter.text("Server Name", "")
    parameter.text("Player Name", "")
    servers = {}
    client_entities = {}
    your_name = ""
    my_client = nil
    my_entity = nil
    my_server = {server = nil, name = "", client_data = {}, master_client = nil, entities = {}}
    -- Something to land on so the clients dont fall endlessly into the void
    ground = scene:entity()
    ground.model = craft.model(asset.builtin.Primitives.RoundedCube)
    ground.position = vec3(0, -2, 10)
    ground.scale = vec3(10, 0.1, 10)
    gbody = ground:add(craft.rigidbody, STATIC)
    ground:add(craft.shape.model, ground.model)
    -- Setup camera
    scene.camera.position = vec3(0, 8, -10)
    scene.camera.rotation = quat.eulerAngles(25, 0, 0)
end

function joinServer(ip, port)
    if Player_Name ~= "" then
        if my_client == nil then
            local client = socket.udp()
            client:settimeout(0)
            my_client = client
            client:setpeername(ip, port)
            client:send("join")
            return client
        end
    else
        print("Please enter a valid player name!")
    end
end

function receiveUpdate()
    if my_client ~= nil then
        local data = my_client:receive()
        if data ~= nil then
            if string.find(data, "somebody joined") then
                makePlayer(vec3(0, 0, 10))
            elseif string.find(data, "you joined") then
                local c = string.match(data, "%d")
                local tab = json.decode(string.sub(data, 11 + #c, #data))
                -- Make your player first
                makePlayer(vec3(0, 0, math.random(-4, 19)))
                -- Then, make everybody else's player
                for i = 1, (tonumber(c) - 1) do
                    makePlayer(vec3(0, 0, math.random(-4, 19)))
                end
            elseif string.find(data, "client positions") then
                local tab = json.decode(string.sub(data, 17, #data))
                for i,e in pairs(tab) do
                    local entity = json.decode(e)
                    if client_entities[i].name == entity.name then
                        client_entities[i].position = vec3(entity.x, entity.y, entity.z)
                    end
                end
            end
        end
    end
end

function createServer()
    if Server_Name == "" then
        print("Please enter a name for the server")
    elseif Player_Name == "" then
        print("Please enter a valid player name")
    else
        if my_server.server == nil then
            local server = socket.udp()
            server:setsockname("*", 14285)
            server:setoption("broadcast", true)
            server:settimeout(0)
            my_server.server = server
            my_server.name = Server_Name
            local ip, port = server:getsockname()
            local client = joinServer(ip, port)
            my_server.master_client = client
        end
    end
end

function stopServer()
    if my_server ~= nil then
        server:close()
    end
end

function makePlayer(pos)
    if not client_entities[entity] then
        entity = scene:entity()
        entity.model = craft.model(asset.builtin.Primitives.RoundedCube)
        entity.position = pos
        entity.scale = vec3(0.5, 1, 0.5)
        body = entity:add(craft.rigidbody, DYNAMIC)
        body.angularFactor = vec3(0, 0, 0)
        entity:add(craft.shape.box, vec3(1.5, 2, 1.5))
        entity.material = craft.material(asset.builtin.Materials.Specular)
        entity.material.diffuse = color(math.random(0, 255), math.random(0, 255), math.random(0, 255))
        if my_entity == nil then
            my_entity = entity
        end
        entity.name = Player_Name
        your_name = Player_Name
        table.insert(client_entities, entity)
        local tab = {x = pos.x, y = pos.y, z = pos.z}
        my_client:send("add player to server"..json.encode(tab))
        return entity
    end
end

function touched(touch)
    if touch.state == MOVING then
        if touch.deltaX > 0 then
            for i,e in pairs(client_entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(-15, 0, 0))
                end
            end
        elseif touch.deltaX < 0 then
            for i,e in pairs(client_entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(15, 0, 0))
                end
            end
        elseif touch.deltaY > 0 then
            for i,e in pairs(client_entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 0, 15))
                end
            end
        elseif touch.deltaY < 0 then
            for i,e in pairs(client_entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 0, -15))
                end
            end
        end
    elseif touch.state == BEGAN then
        if touch.tapCount == 2 then
            for i,e in pairs(client_entities) do
                if e == my_entity then
                    e:get(craft.rigidbody):applyForce(vec3(0, 450, 0))
                end
            end
        end
    end
end

function draw()
    if my_client == nil then
        messenger:sendto("checking for servers", "255.255.255.255", "14285")
    end
    if my_server.server ~= nil then
        local data, ip, port = my_server.server:receivefrom()
        if data == "checking for servers" then
            my_server.server:sendto("server available"..my_server.name, ip, port)
        elseif data ~= nil then
            if string.find(data, "join") then
                local uid = {ip = ip, port = port}
                if not my_server.client_data[uid] then
                    table.insert(my_server.client_data, uid)
                end
                for i,c in pairs(my_server.client_data) do
                    if c ~= uid then
                        my_server.server:sendto("somebody joined", c.ip, c.port)
                    else
                        my_server.server:sendto("you joined"..tostring(#my_server.client_data)..json.encode(uid), c.ip, c.port)
                    end
                end
            elseif string.find(data, "add player to server") then
                local tab = json.decode(string.sub(data, 21, #data))
                local entity = {name = your_name, x = tab.x, y = tab.y, z = tab.z}
                if not my_server.entities[entity] then
                    if not my_server.entities[entity] then
                        table.insert(my_server.entities, entity)
                    end
                end
                local tab = {}
                for i,e in pairs(my_server.entities) do
                    if not tab[json.encode(e)] then
                        table.insert(tab, json.encode(e))
                    end
                end
                for i,c in pairs(my_server.client_data) do
                    my_server.server:sendto("client positions"..json.encode(tab), c.ip, c.port)
                end
            end
        end
    end
    if my_client == nil then
        local data, ip, port = messenger:receivefrom()
        if data ~= nil then
            if string.find(data, "server available") then
                local server_name = string.sub(data, 17, #data)
                local new_server = {ip = ip, port = port, name = server_name}
                if not servers[new_server] then
                    table.insert(servers, new_server)
                end 
                for i,s in pairs(servers) do
                    parameter.action(s.name.." - Join", function() joinServer(s.ip, s.port) end)
                end
            end
        end
    end
    receiveUpdate()
end

Only one rounded cube per iPad is showing. Maybe you should wait until you have access to 2 iPads/iPhones, that would make it a lot easier for you to see what’s happening. It doesn’t seem like you’re making any progress, but then I’m not really sure what you’re trying to do anyways.

@dev1707, I have started a completely new multiplayer project with code that was taken from somebody else who made me a simple multiplayer project and was slightly tweaked/changed to fit what I wanted. Nothing happens so far, but I just want to you to see if you can get a print statement saying “Successfully connected to the server!” on one iPad. Here’s the code:

function setup()
    socket = require("socket")
    parameter.action("Create Room", createRoom)
    parameter.action("Join Room", function()
        if other_ip and other_port then
            joinRoom(other_ip, other_port)
        else
            parameter.text("IP", "", function(t)
                other_ip = t
            end)
            parameter.text("Port", "", function(t)
                other_port = t
            end)
        end
    end)
    client = socket.udp()
    client:settimeout(0)
    other_ip, other_port = nil, nil
    current_time = nil
    server = false
end

function createRoom()
    -- We need to find your address, so we create a new UDP socket and assign it a random IP
    local udp = socket.udp()
    udp:setpeername("192.167.188.122", "14285")
    local ip, port = udp:getsockname()
    udp:close()
    client:setsockname(ip, port)
    client:setoption("broadcast", true)
    print("Server created!")
    print("Connect to "..ip.." : "..port)
    server = true
end

function joinRoom(ip, port)
    if server == false then
        print("Attempting to join the server...")
        client:setpeername(ip, port)
        client:send("connection confirmation")
        current_time = os.time()
    end
end

function receiveData()
    if server == true then
        local msg, ip, port = client:receivefrom()
        if msg ~= nil then
            if msg == "connection confirmation" then
                client:sendto("valid confirmation", ip, port)
            end
        end
    else
        local result = client:receive()
        if result == "valid confirmation" then
            print("Successfully connected to server!")
            current_time = nil
        else
            if current_time ~= nil then
                if os.time() > current_time + 10 then
                    -- If we get no response back for 10 seconds or more, we know it didn't work
                    print("Failed to connect, please try again later")
                    current_time = nil
                end
            end
        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
    receiveData()
end

The only thing it would print was “Server created” and “Connect to” with an ip and port number. I tried different things with the Create Room and Join Room with different ip and port numbers but didn’t get anything different.

@dave1707, I have no hope that this will work at all, but I’ve added a “Find Room” button that will automatically find a room for you to join. I also changed the address that the server is set when you make a room to “*”, so it accepts any local address on the network. Here’s the code:

function setup()
    socket = require("socket")
    parameter.action("Create Room", createRoom)
    parameter.action("Join Room", function()
        if other_ip and other_port then
            joinRoom(other_ip, other_port)
        else
            parameter.text("IP", "", function(t)
                other_ip = t
            end)
            parameter.text("Port", "", function(t)
                other_port = t
            end)
        end
    end)
    parameter.action("Find Room", function()
        findRoom(current_room_count)
    end)
    client = socket.udp()
    client:settimeout(0)
    other_ip, other_port = nil, nil
    current_time = nil
    current_room_count = 1
    tick = 0
    finding_room = false
    server = false
end

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

function joinRoom(ip, port)
    print("Attempting to join the room...")
    client:setpeername(ip, port)
    client:send("connection confirmation")
    current_time = os.time()
end

function findRoom(count)
    if count == 1 then
        print("Attempting to find a room...")
    end
    finding_room = true
    -- We need to send a broadcast message to every room open, so we create a UDP socket and broadcast a packet
    -- But first, we need to find your IP
    local udp = socket.udp()
    udp:setpeername("192.167.188.122", "14285")
    local ip, port = udp:getsockname()
    udp:close()
    local ip1, ip2 = string.match(ip, "(%d+.%d+.%d+.)(%d+)")
    client:setpeername(ip1..count, "14285")
    client:send("connection confirmation")
    current_time = tick
end

function receiveData()
    if server == true then
        local msg, ip, port = client:receivefrom()
        if msg ~= nil then
            if msg == "connection confirmation" then
                client:sendto("valid confirmation", ip, port)
            end
        end
    else
        local result = client:receive()
        if result == "valid confirmation" then
            print("Successfully joined the room!")
            current_time = nil
        elseif result == nil then
            if current_time ~= nil 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
                else
                    -- If the time limit has passed, we can check the next address for a room
                    if tick > current_time + 0.001 then
                        current_room_count = current_room_count + 1
                        if current_room_count < 256 then
                            findRoom(current_room_count)
                        end
                    end
                end
            end
        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
    receiveData()
    tick = tick + 1
end

@Creator27 Got this error.

Main:71: calling ‘receivefrom’ on bad self (udp{unconnected} expected, got udp{connected})
stack traceback:
[C]: in method ‘receivefrom’
Main:71: in function ‘receiveData’
Main:113: in function ‘draw’

function setup()
    socket = require("socket")
    parameter.action("Create Room", createRoom)
    parameter.action("Join Room", function()
        if other_ip and other_port then
            joinRoom(other_ip, other_port)
        else
            parameter.text("IP", "", function(t)
                other_ip = t
            end)
            parameter.text("Port", "", function(t)
                other_port = t
            end)
        end
    end)
    parameter.action("Find Room", function()
        findRoom(current_room_count)
    end)
    client = socket.udp()
    client:settimeout(0)
    other_ip, other_port = nil, nil
    current_time = nil
    current_room_count = 1
    tick = 0
    finding_room = false
    server = false
end

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

function joinRoom(ip, port)
    if server == false then
        print("Attempting to join the room...")
        client:setpeername(ip, port)
        client:send("connection confirmation")
        current_time = os.time()
    else
        print("You are already hosting a room, so you can't join another!")
    end
end

function findRoom(count)
    if server == false then
        if count == 1 then
            print("Attempting to find a room...")
        end
        finding_room = true
        -- We need to send a broadcast message to every room open, so we create a UDP socket and broadcast a packet
        -- But first, we need to find your IP
        local udp = socket.udp()
        udp:setpeername("192.167.188.122", "14285")
        local ip, port = udp:getsockname()
        udp:close()
        local ip1, ip2 = string.match(ip, "(%d+.%d+.%d+.)(%d+)")
        client:setpeername(ip1..count, "14285")
        client:send("connection confirmation")
        current_time = tick
    else
        print("You are already hosting a server, so you can't join another!")
    end
end

function receiveData()
    if server == true then
        local msg, ip, port = client:receivefrom()
        if msg ~= nil then
            if msg == "connection confirmation" then
                client:sendto("valid confirmation", ip, port)
            end
        end
    else
        local result = client:receive()
        if result == "valid confirmation" then
            print("Successfully joined the room!")
            current_time = nil
        elseif result == nil then
            if current_time ~= nil 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
                else
                    -- If the time limit has passed, we can check the next address for a room
                    if tick > current_time + 0.001 then
                        current_room_count = current_room_count + 1
                        if current_room_count < 256 then
                            findRoom(current_room_count)
                        end
                    end
                end
            end
        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
    receiveData()
    tick = tick + 1
end