@Ignatz - strange, I’ll have to check. from the telnet connection you’ll have to type lua code followed by carriage return, the server wait for “valid code” to execute. Even with a simple print("Hello")
it does not work ? Sorry for that.
Not your fault, @toffer
I changed your code so it just prints the variable “lin”
But even the test for lin=“bye” doesn’t recognise the word bye, for some reason. It gets the message but doesn’t treat it as text.
I am using your exact code with a statement that prints “lin” when it is received. It just prints blank, and the program doesn’t recognise “bye” as it should.
Funnily, when I first tried it, it did recognise bye. Maybe it’s something on the PC side, but I can’t think what.
Just popping back in to say I got the opportunity to test my above code on two devices, (apple testflight is linked to id not device, so I signed in on a friend’s), and it worked great.
I plan to put together a Multiplayer
class of sorts to make connecting to, and sending data between, instances of a game easier.
That would be great, thanks!
As promised, here is the first second version, along with a main tab that shows how to use it.
--# Main
function setup()
local connectionMade = function()
output.clear()
parameter.clear()
print("Connected!")
end
multihandler = Multiplayer(receiveData, connectionMade)
parameter.action("Host Game", function()
multihandler:hostGame()
end)
parameter.action("Search for and Join 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)
opos, cpos = vec2(WIDTH / 2, HEIGHT / 2), vec2(WIDTH / 2, HEIGHT / 2)
end
function receiveData(d)
cpos = loadstring("return " .. d)()
end
function draw()
background(255, 255, 255, 255)
multihandler:update()
if multihandler.connected then
local p1, p2 = opos, cpos
if not multihandler.is_host then p1, p2 = cpos, opos end
fill(255, 0, 0)
ellipse(p1.x, p1.y, WIDTH / 4)
fill(0, 0, 255)
ellipse(p2.x, p2.y, WIDTH / 4)
else
fill(0)
text("Waiting for connection...", WIDTH / 2, HEIGHT / 2)
end
end
function touched(t)
if multihandler.connected then
opos = vec2(t.x, t.y)
multihandler:sendData("vec2(" .. t.x .. ", " .. t.y .. ")")
end
end
--# 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
enjoy!
EDIT: updated to allow one to automatically find another that is hosting
A quick description of the class: it simply handles all of the connections between two iPad’s and allows you to send and receive data. That’s all. It’s up to your project to tell it when to start, what to connect to, what to send, and to handle the received data.
B-)
I’ve updated the above code to add the ability to auto find and connect to a broadcasting client, though currently it just joins the first one it finds. I think I may work on it more to end up with a list of games you could join.
Man, I can’t wait for this update!! @Luatee I see your upcoming game may have multiplayer, eh? I can help with that, I was quite good with Java sockets and I can try to help you once the update comes out. As always, super interested in Aedifico, seems like the game I’ve always wanted to play/make. Good job so far. Thanks everyone for the examples as well!
@Luatee, great idea. yep that is the most reliable, with only a slight lag between a click and a result on the client player.
@Luatee, I would say it takes about a hundredth of a second for the action to make it to the host, then back.
I will try to take a short video.
@Luatee, here is the code:
function setup()
physics.continuous = true
multihandler = Multiplayer(receiveData, gameSetup)
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()
parameter.clear()
output.clear()
print("Connected!")
-- Game walls to keep balls on screen
walls = {}
walls[1] = physics.body(EDGE, vec2(0,0), vec2(WIDTH, 0))
walls[2] = physics.body(EDGE, vec2(WIDTH,0), vec2(WIDTH, HEIGHT))
walls[3] = physics.body(EDGE, vec2(WIDTH,HEIGHT), vec2(0, HEIGHT))
walls[4] = physics.body(EDGE, vec2(0,HEIGHT), vec2(0, 0))
local pos1, pos2 = vec2(WIDTH / 4, HEIGHT / 2), vec2(WIDTH * 3/4, HEIGHT / 2)
local rad = WIDTH / 16
-- Variables
balls = {}
if multihandler.is_host then
-- Create physics bodies
balls[1] = physics.body(CIRCLE, rad)
balls[2] = physics.body(CIRCLE, rad)
balls[1].position, balls[2].position = pos1, pos2
controlling, other = balls[1], balls[2]
else
balls[1] = { position = pos1, radius = rad }
balls[2] = { position = pos2, radius = rad }
controlling, other = balls[2], balls[1]
end
moveForce = 100
target = vec2(0, 0)
syncdata = true
end
function receiveData(d)
local tb = loadstring("return " .. d)()
if syncdata then
if multihandler.is_host then
other:applyForce(tb.move)
else
balls[1].position, balls[2].position = tb.pos, tb.pos2
end
end
end
function draw()
background(255)
multihandler:update()
if multihandler.connected then
fill(255, 0, 0)
ellipse(balls[1].position.x, balls[1].position.y, balls[1].radius * 2)
fill(0, 0, 255)
ellipse(balls[2].position.x, balls[2].position.y, balls[2].radius * 2)
fill(0)
text("Tap the screen to move", WIDTH / 2, HEIGHT / 2)
if multihandler.is_host then
multihandler:sendData("{ pos = " .. vec2ToStr(balls[1].position) .. ", pos2 = " .. vec2ToStr(balls[2].position) .. " }")
end
else
fill(0)
text("Waiting for connection...", WIDTH / 2, HEIGHT / 2)
end
end
function vec2ToStr(vec)
return "vec2" .. tostring(vec)
end
function touched(t)
if multihandler.connected then
if t.state == ENDED then
local target = vec2(t.x, t.y)
local move = (target - controlling.position) * moveForce
if multihandler.is_host then
controlling:applyForce(move)
else
multihandler:sendData("{ move = " .. vec2ToStr(move) .. " }")
end
end
end
end
and here is a short video of it in action.
https://www.youtube.com/watch?v=Ym1Fg77MsJM
the left iPad is the ‘client’, so you hopefully can get an idea of how minimal the lag is.
@JakAttak Oh lordie! I’m a long way away from multiplayer but I think a good test would be using the physics engine, from the look of the code though it seems very plausible.
@Luatee, physics certainly seems possible, but it might be a bit difficult to implement for a couple of reasons.
-
If you simply send the positions of the bodies, collisions may not work right
-
If you send the movement data (ex. anything you’d pass to applyForce), the two instances aren’t guaranteed to keep in sync (positions could get off)
-
If you send the movement data and a packet gets dropped, things get off
So, a combination of 1 and 2 might be possible, and I will certainly play around with it, but it might be best if I implement TCP for this kind of thing, which I will also look into doing.
EDIT: After playing with it, I found that having one client do all the physics and another simply draw objects using data it gets from the ‘server’ client is the most accurate physics, with only a slight lag on the ‘client’ client.
@JakAttak what if the you have a host player and a connecting player. Use the whole physics engine from the host’s iPad and allow the connecting player to manipulate it? Send the positions, angles and velocities to physics bodies local to the connecting player, this does mean the connecting player and the host will have different experiences. It shouldn’t be buggy at all, maybe a bit slower to react for the connecting player.
@JakAttak Nice code. Too bad my second iPad is an iPad 1 and I can’t try your code with it. It almost works if I try it just on my iPad Air. I get as far as it showing the 2 balls.