Local Multiplayer Game

@Creator27 Just woke up, been sleeping all day. I tried your code on 1 iPad and 2 spheres showed up. Tried on 2 iPads and there were 2 spheres on both iPads but there was no communication between the 2 iPads.

@Creator27 This program when run on 2 iPads will move a circle on the other iPad as you move your finger on your iPad. Someone moving their finger on the other ipad would be moving the circle on your ipad. Since you only have 1 ipad, I commented out the if code in the function remoteDevice:getTheirIp so you’ll be sending messages to yourself. You can put print statements in the function remoteDevice:receiveTheirMessage() to see the messages being received. This is close to what you want to do in your code, so maybe if you see what’s happening here you can do it in yours.

I’m feeling a little better, so I though I’d try to help out a little.

viewer.mode=STANDARD

function setup()
    mx,my=0,0
    fill(255)
    delay=0
    rd=remoteDevice()
end

function draw()
    background()
    rd:getIp()
    rd:receiveTheirMessage()
    ellipse(mx,my,50)    
end

function touched(t)
    if t.state==BEGAN or t.state==CHANGED then
        str=string.format("moveXY%04d%04d",t.x//1,t.y//1)
        rd:sendMessage(str)
    end
end

remoteDevice=class()

function remoteDevice:init()
    self.socket=require("socket")
    self.myIp="0.0.0.0"  
    self.theirIp="0.0.0.0"  
    self:getMyIp()
    self:setMySocket()
    self:setTheirSocket()
    self:getTheirIp()
end

function remoteDevice:getIp()
    if not gotMyIp or not gotTheirIp then
        if delay>60 then
            delay=0
            if not gotMyIp then
                rd:getMyIp()
            end
            if not gotTheirIp then
                rd:getTheirIp()
            end
        end
        delay=delay+1
    end
end

function remoteDevice:setMySocket()    
    self.server=self.socket.udp()
    self.server:setsockname(self.myIp,5544)
    self.server:settimeout(0)
end

function remoteDevice:setTheirSocket()
    self.client=self.socket.udp()
    self.client:settimeout(0)
end

function remoteDevice:getMyIp()
    self.server=self.socket.udp()
    self.server:setpeername("1.1.1.1",80)
    self.myIp,self.myPort=self.server:getsockname()
    self.ip1,self.ip2=string.match(self.myIp,"(%d+.%d+.%d+.)(%d+)")
    print("my IP = "..self.ip1..self.ip2)
    gotMyIp=true
end

function remoteDevice:getTheirIp()
    self.client=self.socket.udp()
    self.client:settimeout(0)
    -- send a message to everyone on this network except to myself
    print("Getting their IP")
    for z=1,255 do
        --if z~=tonumber(self.ip2) then
            self.client:setpeername(self.ip1..z,5544)
            self.client:send("get their ip")
        --end
    end
end

function remoteDevice:receiveTheirMessage()
    local data,msg,port=self.server:receivefrom()
    if data~=nil then
        
        if data=="get their ip" then
            self.client=self.socket.udp()
            self.client:settimeout(0)
            self.client:setpeername(msg,5544)
            self.client:send("got their ip"..self.myIp)  
            
        elseif string.sub(data,1,12)=="got their ip" then
            self.theirIp=msg
            print("their IP = "..self.theirIp)
            viewer.mode=FULLSCREEN
            gotTheirIp=true
            
        elseif string.sub(data,1,6)=="moveXY" then
            mx=string.sub(data,7,10)
            my=string.sub(data,11,14)
        end
    end
end

function remoteDevice:sendMessage(tempStr)
    self.client = self.socket.udp()
    self.client:setpeername(self.theirIp,5544)
    self.client:settimeout(0)
    self.client:send(tempStr)
end

@dave1707, thank you for this it’s very helpful. Just one thing though, I only want to send a message to people that are connected to the server that you are on. Is there anyway to possibly do that?

@Creator27 The above code will send a message to the 255 addresses on a router and only get a response from any device that’s running the code. Currently it’s only looking for 1 other device, but it could be changed to handle multiple devices. I never tried this on my iPhone that doesn’t use my router. Not sure what it would do, I’ll have to give it a try.

PS. Tried it on my iPhone and it crashed. I’ll check later to see what it crashed on.

PS. It works on my iPhone when I switch to WiFi.

@Creator27 @dave1707 Here is a multiplayer sample I collected before, maybe you can make a reference.

To dave: I wish you a speedy recovery?

-- MultiPlayerSample

--all code by JakAttak
--# Main
function setup()
    local connectionMade = function()
        output.clear()
        parameter.clear()
        print("Connected!")
        
        gameSetup()
    end
    
    multihandler = Multiplayer(receiveData, connectionMade)
    
    parameter.action("Host Game", function()
        multihandler:hostGame()
    end)
    
    parameter.action("Find Game", function()
        multihandler:findGame()
    end)
    
    parameter.action("Join Game", function()
        if other_ip then
            multihandler:joinGame(other_ip, other_port)
        else
            parameter.text("other_ip", "")
            parameter.text("other_port", "")
            
            print("Fill in the host's ip and port, then click join game again")
        end
    end)
end

function gameSetup()
    canvas = image(WIDTH, HEIGHT)
    parameter.color("pen_col", color(0, 255, 0))
    parameter.integer("pen_size", 2, 100, 10)
    parameter.action("clear", function()
        clear()
        multihandler:sendData("clear")
    end)
    pen_touch = nil
    last_point = vec2(0, 0)
end

function clear()
    canvas = image(WIDTH, HEIGHT)
end

function receiveData(d)
    if d == "clear" then
        clear()
    else
        local tb = load("return " .. d)()
        drawPoint(tb.point, tb.last_point, tb.drawing_line, tb.pen_size, tb.pen_col)
    end
end

function drawPoint(point, lastPoint, drawingLine, penSize, penCol)
    pushStyle()
    setContext(canvas)    -- Start drawing to screen image
    
    fill(penCol) stroke(penCol)    -- Set draw color to color var
    
    strokeWidth(penSize)
    if drawingLine then
        line(point.x, point.y, lastPoint.x, lastPoint.y)    -- draw a line between the two points
    else
        ellipse(point.x, point.y, penSize)    -- Place a dot there
    end
    
    setContext()
    popStyle()
end

function draw()
    background(255, 255, 255, 255)
    
    multihandler:update()
    
    if multihandler.connected then
        sprite(canvas, WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)    -- Draw the image onto the screen
    else
        fill(0)
        text("Waiting for connection...", WIDTH / 2, HEIGHT / 2)
    end
end

function vec2ToStr(vec)
    return "vec2" .. tostring(vec)
end

function colToStr(col)
    return "color(" .. col.r .. ", " ..  col.g .. ", " .. col.b.. ", " .. col.a .. ")"
end

function touched(t)
    if multihandler.connected then
        local p, lp, d, ps, pc = vec2(t.x, t.y), last_point, drawing_line, pen_size, pen_col
        if t.state == BEGAN then
            pen_touch = t.id
        end
        if t.id == pen_touch then
            drawPoint(vec2(t.x, t.y), last_point, drawing_line, pen_size, pen_col)
            drawing_line = true
            last_point = vec2(t.x, t.y)
        end
        if t.state == ENDED then
            drawing_line = false
            pen_touch = nil
        end
        
        multihandler:sendData("{ point = " .. vec2ToStr(p) .. ", last_point = " .. vec2ToStr(lp) .. ", drawing_line = " .. tostring(d) .. ", pen_size = " .. ps .. ", pen_col = " .. colToStr(pc) .. " }")
    end
end

--multiplayer library follows
--# Multiplayer

local socket = require("socket")

Multiplayer = class()

function Multiplayer:init(dcb, ccb)
    self.my_ip, self.my_port = self:getLocalIP(), 5400
    self.peer_ip, self.peer_port = nil, self.my_port
    
    self.client = socket.udp()
    self.client:settimeout(0)
    
    self.connected = false
    self.is_host = false
    self.searching = false
    
    self.dataCallback = dcb or function() end
    self.connectedCallback = ccb or function() end
end

-- Returns this iPad's local ip
function Multiplayer:getLocalIP()
    local randomIP = "192.167.188.122"
    local randomPort = "3102" 
    local randomSocket = socket.udp() 
    randomSocket:setpeername(randomIP,randomPort) 
    
    local localIP, somePort = randomSocket:getsockname()
    
    randomSocket:close()
    randomSocket = nil
    
    return localIP
end

-- Set the connected status and call the connection callback if needed
function Multiplayer:setConnectedVal(bool)
    self.connected = bool
    
    if self.connected then
        self.connectedCallback()
    end
end

function Multiplayer:setHostVal(bool)
    self.is_host = bool
end

-- Prepare to be the host
function Multiplayer:hostGame()
    print("Connect to " .. self.my_ip .. ":" .. self.my_port)
    
    self.client:setsockname(self.my_ip, self.my_port)
    
    self:setConnectedVal(false)
    self.is_host = true
    self.searching = false
end

-- Find a host
function Multiplayer:findGame()
    print("Searching for games...")
    
    self.searching = true
    
    local ip_start, ip_end = self.my_ip:match("(%d+.%d+.%d+.)(%d+)")
    for i = 1, 255 do
        if i ~= tonumber(ip_end) then
            tween.delay(0.01 * i, function()
                self.client:setsockname(ip_start .. i, self.my_port)
                self.client:sendto("connection_confirmation", ip_start .. i, self.my_port)
            end)
        end
    end
end

-- Prepare to join a host
function Multiplayer:joinGame(ip, port)
    self.peer_ip, self.peer_port = ip, port
    
    self.client:setsockname(ip, port)
    
    self.is_host = false
    self.searching = false
    
    self:sendData("connection_confirmation")
end

-- Send data to the other client
function Multiplayer:sendData(msg_to_send)
    if self.peer_ip then
        self.client:sendto(msg_to_send, self.peer_ip, self.peer_port)
    end
end

-- Check for data received from the other client
function Multiplayer:checkForReceivedData()
    local data, msg_or_ip, port_or_nil = self.client:receivefrom()
    if data then
        -- Store the ip of this new client so you can send data back
        self.peer_ip, self.peer_port = msg_or_ip, port_or_nil
        
        if not self.connected and data == "connection_confirmation" then
            self:sendData("connection_confirmation")
            self:setConnectedVal(true)
        end
        
        -- Call callback with received data
        if data ~= "connection_confirmation" then
            self.dataCallback(data)
        end
    end
end

function Multiplayer:update()
    self:checkForReceivedData()
end

@Creator27 Here’s an updated version for my above code. I ran this on 3 iPads and as I moved my finger on the different iPads, a circle on the other 2 iPads moved around. I tried moving a finger on 2 ipads at the same time, it worked, but the messages were getting backed up. So I’m not sure how much or how fast the message traffic can be. Still needs more tweaking, but I thought I’d post what I have just in case I don’t feel like coding at some point.

@binaryblues Thanks for the speedy recovery message. I have good days and bad days, but things are getting better.

PS. Updated the below code.

viewer.mode=STANDARD

function setup()
    ipTab={}
    myX,myY=0,0
    fill(255)
    delay=0
    rd=remoteDevice()
    parameter.action("get other ips",getIps)
end

function draw()
    background()
    rd:getIp()
    rd:receiveTheirMessage()
    for a,b in pairs(ipTab) do
        if b.x~=nil then
            fill(255)
            ellipse(b.x,b.y,50) 
            fill(255,0,0)
            text(b.ip,b.x,b.y+40)
        end
    end 
    fill(0,255,0)
    text(rd.myIp,myX,myY+40)
    text("my circle",myX,myY-40)
    ellipse(myX,myY,40)
end

function touched(t)
    if t.state==BEGAN or t.state==CHANGED then
        myX,myY=t.x//1,t.y//1
        for a,b in pairs(ipTab) do
            a={id="move X Y",x=myX,y=myY,ip=rd.myIp}
            str=json.encode(a)
            rd:sendMessage(str,b.ip)
        end
    end
end

function getIps()
    rd:getTheirIp()
end

remoteDevice=class()

function remoteDevice:init()
    self.socket=require("socket")
    self.myIp="0.0.0.0"  
    self.theirIp="0.0.0.0"  
    self:getMyIp()
    self:setMySocket()
end

function remoteDevice:getIp()
    if not gotMyIp then
        if delay>60 then
            delay=0
            rd:getMyIp()
        end
        delay=delay+1
    end
end

function remoteDevice:setMySocket()    
    self.server=self.socket.udp()
    self.server:setsockname(self.myIp,5544)
    self.server:settimeout(0)
end

function remoteDevice:getMyIp()
    self.server=self.socket.udp()
    self.server:setpeername("1.1.1.1",80)
    self.myIp,self.myPort=self.server:getsockname()
    self.ip1,self.ip2=string.match(self.myIp,"(%d+.%d+.%d+.)(%d+)")
    print("my IP = "..self.ip1..self.ip2)
    gotMyIp=true
end

function remoteDevice:getTheirIp()
    self.client=self.socket.udp()
    self.client:settimeout(0)
    print("Getting their IP")
    -- send a message to everyone on this network except to myself
    for z=1,255 do
        if z~=tonumber(self.ip2) then
            self.client:setpeername(self.ip1..z,5544)
            local temp={id="get their ip"}
            local str=json.encode(temp)
            self.client:send(str)
        end
    end
end

function remoteDevice:receiveTheirMessage()
    local data,msg,port=self.server:receivefrom()
    
    if data~=nil then
        
        local jd=json.decode(data)
        
        if jd.id=="get their ip" then
            self.client=self.socket.udp()
            self.client:settimeout(0)
            self.client:setpeername(msg,5544)
            local temp={id="got their ip",ip=self.myIp}
            local str=json.encode(temp)
            self.client:send(str)  
            
        elseif jd.id=="got their ip" then
            print("their ip ",jd.ip)
            self.theirIp=jd.ip
            viewer.mode=FULLSCREEN
            ipTab[self.theirIp]={ip=self.theirIp,x=0,y=0}
            
        elseif jd.id=="move X Y" then
            ipTab[jd.ip]=jd
        end
    end
end

function remoteDevice:sendMessage(tempStr,theirIp)
    self.client = self.socket.udp()
    self.client:setpeername(theirIp,5544)
    self.client:settimeout(0)
    self.client:send(tempStr)
end

@binaryblues and @dave1707, thank you guys for the examples. I’ve edited my code so that hopefully on the first iPad there will be one ball and on the second iPad there will be two balls. [EDIT]: I had to change the code because sometimes the game wouldn’t create any new player objects. Pretty sure it works now :slight_smile:


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
    connected = false
    client_count = 0
    my_server = {server = nil, clients = {}, client_data = {}}
    -- 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)
        table.insert(my_server.clients, client)
        my_client = client
        local ip1, ip2 = string.match(ip,"(%d+.%d+.%d+.)(%d+)")
        for z = 1, 255 do
            client:setsockname(ip1..z, port)
            client:sendto("join"..tostring(client), ip1..z, port)
        end
    end
end

function receiveUpdate()
    if connected == false then
        local data, ip, port = my_client:receivefrom()
        if data ~= nil then
            for i = 1, tonumber(data) do
                makePlayer(vec3(0, 0, 10))
            end
            my_client:setpeername(ip, port)
            my_client:send("confirmed"..tostring(my_client))
            connected = true
        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
        if my_entity == nil then
            my_entity = entity
        end
        if client ~= nil then
            entity.master = client
        end
        table.insert(entities, 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
                if client_count ~= #my_server.clients then
                    my_server.server:sendto(tostring(#my_server.clients), ip, port)
                    client_count = #my_server.clients
                else
                    print("we already have this player")
                end
            elseif string.find(data, "confirmed") then
                print("noice!")
            end
        end
    end
    if my_client == nil then
        local data, ip, port = messenger:receivefrom()
        if data == "server available" then
            parameter.action(tostring(port).." - Join", function() joinServer(ip, port) end)
        end
    else
        receiveUpdate()
    end
    for i,c in pairs(my_server.clients) do
        if my_client == c and connected == true then
            local data = c:receive()
            if data ~= nil then
                print(data)
            end
        end
    end
end

@Creator27 Tried your latest code on 2 iPads. Same thing, 1 sphere per iPad.

@Creator27 I updated my code above. I think it works better than it did before.

@dave1707, If I’m being completely honest with you I have no hope that this is gonna work, but here it is.

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
    connected = false
    client_count = 0
    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 == "make player" then
            makePlayer(vec3(0, 0, 10))
        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
        if my_entity == nil then
            my_entity = entity
        end
        if client ~= nil then
            entity.master = client
        end
        table.insert(entities, 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
                    my_server.server:sendto("make player", c.ip, c.port)
                end
            elseif string.find(data, "confirmed") then
                
            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(port).." - Join", function() joinServer(ip, port) end)
            end
        end
    end
    receiveUpdate()
end

@Creator27 Ran your latest code on 2 iPads. When I pressed join on 1 iPad, a sphere appeared on it. When I pressed join on the other iPad, a sphere appeared on both iPads. So 1 iPad had 1 sphere and the other had 2. The ipad that created the sphere on the other ipad didn’t have any control on the other sphere. I could move 1 sphere on each ipad, and I could push the second sphere around on the one that had 2 spheres. I’m assuming that both iPad should have 2 spheres and the object is that each iPad should be able to try and push the opponents sphere off the platform.

@dave1707, what happened like with one sphere on iPad and two on the other is exactly what I wanted to happen! The reason there was only one sphere on the second iPad, is because I haven’t programmed anything to happen when you join, except give yourself a player. I’m just really happy that I’ve (partially) got syncing players across devices working!

@dave1707, now that the syncing players across devices is half-functional, I programmed the other half. Here it is:

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
    connected = false
    client_count = 0
    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 == "make player" then
            makePlayer(vec3(0, 0, 10))
            my_client:send("confirmed")
        else
            if data ~= nil then
                if string.find(data, "client count") then
                    local count = tonumber(string.sub(data, 13, #data))
                    for i = 1, count - 1 do
                        makePlayer(vec3(0, 0, 10))
                    end
                end
            end
        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
        if my_entity == nil then
            my_entity = entity
        end
        if client ~= nil then
            entity.master = client
        end
        table.insert(entities, 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
                    my_server.server:sendto("make player", c.ip, c.port)
                end
            elseif string.find(data, "confirmed") then
                my_server.server:sendto("client count"..tostring(#my_server.client_data), ip, port)
            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(port).." - Join", function() joinServer(ip, port) end)
            end
        end
    end
    receiveUpdate()
end

@Creator27 I got 2 spheres on 1 iPad and 3 spheres on the other.

@dave1707, try this.

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
    connected = false
    client_count = 0
    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 == "make player" then
            makePlayer(vec3(0, 0, 10))
            my_client:send("confirmed")
        else
            if data ~= nil then
                if string.find(data, "client count") then
                    local count = tonumber(string.sub(data, 13, #data))
                    for i = 1, count do
                        makePlayer(vec3(0, 0, 10))
                    end
                elseif string.find(data, "new player") then
                    local count = tonumber(string.sub(data, 11, #data))
                    makePlayer(vec3(0, 0, 10))
                    for i = 1, count do
                        makePlayer(vec3(0, 0, 10))
                    end
                end
            end
        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
        if my_entity == nil then
            my_entity = entity
        end
        if client ~= nil then
            entity.master = client
        end
        table.insert(entities, 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 not c == uid then
                        my_server.server:sendto("make player", c.ip, c.port)
                    else
                        my_server.server:sendto("new player"..tostring(#my_server.client_data - 1), c.ip, c.port)
                    end
                end
            elseif string.find(data, "confirmed") then
                my_server.server:sendto("client count"..tostring(#my_server.client_data - 1), ip, port)
            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(port).." - Join", function() joinServer(ip, port) end)
            end
        end
    end
    receiveUpdate()
end

@Creator27 I show 2 spheres on 1 iPad and 3 spheres on the other. If I double tap the screen, one of the spheres jumps.

@dave1707, hopefully the 3 sphere bug is fixed. Try this:

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
    connected = false
    client_count = 0
    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, "make player") then
                local count = string.match(data, "%d")
                local uid = json.decode(string.sub(data, 12 + #count, #data))
                local ip, port = my_client:getpeername()
                if ip ~= uid.ip then
                    -- We know it isnt the new player so we create a new player
                    makePlayer(vec3(0, 0, 10))
                else
                    -- It's the player that just joined so we let him/her see everybody, including his/her player
                    for i = 1, tonumber(count) do
                        makePlayer(vec3(0, 0, 10))
                    end
                end
            end
        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
        if my_entity == nil then
            my_entity = entity
        end
        if client ~= nil then
            entity.master = client
        end
        table.insert(entities, 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
                    my_server.server:sendto("make player"..tostring(#my_server.client_data)..json.encode(uid), c.ip, c.port)
                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(port).." - Join", function() joinServer(ip, port) end)
            end
        end
    end
    receiveUpdate()
end

@Creator27 Sometimes I had 2 spheres on 1 ipad and 3 on the other. Other times I had 1 sphere on 1 iPad and 2 on the other. It kind of depended on which iPad I pressed join first.

@dave1707, I also have no hope that this will work, but I’ve reprogrammed my code so that hopefully when somebody joins everybody else will see them and if you are the player who just joined, you will see everybody connected to the server including yourself. As always, 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
    connected = false
    client_count = 0
    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
                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
            end
        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
        if my_entity == nil then
            my_entity = entity
        end
        if client ~= nil then
            entity.master = client
        end
        table.insert(entities, 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 
            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

I got 2 spheres on each iPad when I started each program. If I restarted one of the iPads, I got 3 spheres. Each time I restarted one iPad I got one more sphere on that iPad.