Proof-of-concept: Codea real-time multiplayer

Video shows a rudimentary real-time multiplayer connection via Game Center and running in Codea.

https://youtu.be/WRl_hrIpizs

…this was done using CodeaAddOns.

I would share the Game Center code for this but I pulled it out of an old project and it is truly heinous, involving tons of weirdly interconnected files, and confusing uncommented programming. At this point I’m just demonstrating that this is doable.

Nice. I hope you can boil it down to something understandable.

that’s really exciting! awesome to see such response time, even if it’s a mess i would love to take a look at the code

You really wouldn’t. Besides the Game Center code, there’s a lot of stuff specific to the game that I wrote the original code for, and it’s kind of impossible to tell which is which.

In theory all the Game Center stuff is doable just by following Apple’s documentation.

Then the communication loop goes like this:

  • Player A taps the screen, and the touched function calls an AddOn function with the x and y as parameters.
  • Inside the AddOn function, the x and y are turned into NSData, and sent to Player B using UDP.
  • Player B receives the data, decodes it back into x and y values, and stores it in two variables inside the AddOn class instance.
  • Every draw cycle, Codea calls an AddOn function that retrieves those stored variables.
  • If the variable aren’t 0, 0, it draws an ellipse around that x, y point.

UDP?

Unreliable Data Protocol, I think. TCP/IP is what the internet runs on but UDP is what all real-time games use (afaik). Whereas TCP/IP is all about getting a complete message across, UDP is all about getting a fast message across. It’s basically like a data hose the sender points at the receiver, the idea being that the information is going to come so fast that if you miss a few packets here or there it doesn’t matter. I think.

With UDP you have to build into your logic the fact that some messages might not get through.

Game Center lets you use both TCP/IP and UDP to send data.

so you need Game center or some server to pick up the data and send it to the other player (s) , there’s no way to do it peer to peer? can we connect without game center?

@skar See this link about connecting 2 devices for multiplayer using sockets. I started a battleship game, but I haven’t worked on it for awhile.

https://codea.io/talk/discussion/8334/multiplayer-games-using-sockets

@skar you don’t need Game Center but man it sure is nice to have it.

And I think technically Game Center is peer-to-peer out of the box. You can configure it for use with a server, but by default it seems to just operate as an enhancement to direct phone-to-phone communication.

If anyone wants to try multi player but without a lot of code, here’s a simple example. It’s doesn’t do anything except move a circle on the other device.

Load this code on 2 devices connected to the same router. Run this code on each. The device that runs second should connect automatically. The one that runs first needs a restart to connect. That could be fixed easily but I’m keeping the code small.

Move a finger on your device and the circle on the other device will follow. Move a finger on the other device and the circle on your device will follow.

It shouldn’t take very much more code to actually create a simple multiplayer game.

viewer.mode=STANDARD

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

function draw()
    background()
    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.theirIp="0.0.0.0"  
    self:getMyIp()
    self:setMySocket()
    self:setTheirSocket()
    self:getTheirIp()
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)
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
            
        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

By the way most of this was done by fiddling with the code presented in this thread: https://codea.io/talk/discussion/6171/implementing-addons-for-beginners

…but that only covers how to call C functions from Lua.

The way to call Lua functions from C is covered on this blog page: https://lucasklassmann.com/blog/2019-02-02-how-to-embeddeding-lua-in-c/#calling-a-lua-function-in-c

I used that to do this, for example:


in Codea:

    function functionCalledFromC()
        print("Hello from Function in Lua called from C")
    end


in Xcode:
    
    //value returned is put at top of stack
    lua_getglobal(LuaStack, "functionCalledFromC");
    //verify the top of the stack is a function
    bool isFunction = lua_isfunction(LuaStack, -1);
    if (isFunction) {
        //call the function and check return value
        if (lua_pcall(LuaStack, 0, 1, 0) == LUA_OK) {
            //if value was LUA_OK, take the function off the stack
            lua_pop(LuaStack, lua_gettop(LuaStack));
        }
    }