ich hatte mal vor Ewigkeiten ein Network Pong bereitgestellt, das war allerdings scheiße (waren sozusagen meine ersten Netzwerk-Erfahrungen). Nun hab ich eine neue Version, aber ich komm nicht dazu, weiterzumachen, und Milchshake hatte mich gefragt, ob ich den Code posten kann, weil er gerade auch mit Synchronisation zu kämpfen hat. Daher werd ich den jetzt einfach hier mal veröffentlichen, vielleicht hat jemand Lust und gute Ideen, die Synchronisation zu perfektionieren.
Es wurde übrigens auch noch nicht grad ausgiebig getestet (weil man ja immer einen zweiten Part braucht (localhost ist nicht allzu sinnvoll, weil man da ja keine Latenz hat)).
Hier erstmal noch eine Erklärung zum Code:
Das ganze läuft nach dem "Dead Reckoning"-Prinzip, d.h. der jeweils andere Rechner schaut in die Zukunft, um zu wissen, wo sich der andere Spieler gerade befindet. Das funktioniert so:
- In jedem Schleifendurchlauf wird die aktuelle Zeit in Millisekunden ermittelt
- Wenn die Positionen benötigt werden, wird anhand der zuletzt "gewußten" Position, der Bewegungsrichtung und der Zeit die momentane Position errechnet
- Wenn eine Richtungsänderung (oder Stillstand) auftritt, dann wird dies dem anderen Rechner mitgeteilt
- Server und Client machen am Anfang einen kleinen Verbindungstest und sorgen dafür, daß die "Uhren" sozusagen auf die gleiche Zeit gestellt werden
- Die Anfangs-Synchronisation macht nur einen einzigen Test. Das ist nicht grad realistisch, eigentlich sollten mehrere Tests gemacht werden und ein Durchschnittswert für die Latenz errechnet werden. Aber der Code an der Stelle ist eh noch sehr dürftig, daher hab ich da noch nix dran geändert
- Natürlich kommt bei einer Richtungsänderung die Information beim anderen Rechner zu spät an, in dieser Zeit ist die Anzeige auf beiden Bildern natürlich nicht gleich (geht auch gar nicht). Optimal wäre es, wenn die Rechner dann interpolieren würden, damit wenigstens die Bewegungen einigermaßen flüssig aussehen und nicht so stark ruckeln (z.B. wenn der Schläger stillsteht und er sich dann plötzlich losbewegt, dann ist der Schläger beim anderen Rechner plötzlich schon ein ganzes Stück verschoben, das könnte man wenigstens "smoothen")
- Letzendlich sollte es noch so sein, daß, wenn der Ball links rauszugehen droht, auch der entsprechende Rechner das überprüft (da er die aktuelleren Daten hat), und analog dazu natürlich der andere Rechner wenn der Ball rechts rausgeht. Somit ist das ganze am fairsten, aber das ist (soweit ich mich erinnern kann) noch nicht so implementiert. Problem ist natürlich, daß somit leichter gecheatet werden kann, aber das muß meiner Meinung nach jetzt nicht großartig beachtet werden.
So, nun der Code:
Code: Alles auswählen
; ******************************
; *** ***
; *** NETWORK PONG 2 ***
; *** ***
; *** 2007 by ZeHa ***
; *** http://www.dr-wuro.com ***
; *** ***
; ******************************
;
; License:
; Do what you want, just keep original
; credits. Maybe you can complete some
; stuff, here's the forum link with
; further explanations (German):
; http://www.purebasic.fr/german/viewtopic.php?p=184141
InitSprite()
InitKeyboard()
InitNetwork()
#SCREEN_WIDTH = 320
#SCREEN_HEIGHT = 240
#UPPER_BORDER = 0.08 * #SCREEN_HEIGHT
#LOWER_BORDER = 0.92 * #SCREEN_HEIGHT
#PLAYER_HEIGHT = 0.10 * #SCREEN_HEIGHT
#PLAYER_WIDTH = 0.25 * #PLAYER_HEIGHT
#BALL_WIDTH = #PLAYER_WIDTH
#BALL_HEIGHT = #BALL_WIDTH
#FOREGROUND_COLOR = $00DD00
#BACKGROUND_COLOR = $663300
#PLAYER_SPEED = 0.001 * #SCREEN_HEIGHT
#BALL_SPEED = 0.0004 * #SCREEN_HEIGHT
Enumeration
#PLAYER
#BALL
EndEnumeration
Structure Object
type.l
lastXPos.l
lastYPos.l
XDirection.f
YDirection.f
lastTime.l
speed.f
EndStructure
Structure Player Extends Object
score.l
EndStructure
Structure Ball Extends Object
; nothing more
EndStructure
Global *player1.Player
Global *player2.Player
Global *myself.Player
Global *opponent.Player
Global *ball.Ball
Global server.l
Global serverName$
Global port
Global connection
Global send
Global sendball
Global *networkBuffer.Object
Global firstTime.l
Global timediff.l
Global serverDiff.l
Global windowed
*networkBuffer = AllocateMemory(SizeOf(Object))
Procedure exit()
CloseScreen()
If windowed
CloseWindow(0)
EndIf
WritePreferenceString("server", serverName$)
WritePreferenceString("port", Str(port))
ClosePreferences()
End
EndProcedure
Procedure.l newPlayer(x, y)
*new.Player = AllocateMemory(SizeOf(Player))
*new\lastXPos = x
*new\lastYPos = y
*new\XDirection = 0
*new\YDirection = 0
*new\speed = #PLAYER_SPEED
*new\type = #PLAYER
ProcedureReturn *new
EndProcedure
Procedure.l newBall(x, y)
*new.Ball = AllocateMemory(SizeOf(Ball))
*new\lastXPos = x
*new\lastYPos = y
*new\XDirection = 0
*new\YDirection = 0
*new\speed = #BALL_SPEED
*new\type = #BALL
ProcedureReturn *new
EndProcedure
Procedure getXPos(*o.Object, time.l)
ProcedureReturn (*o\lastXPos + *o\XDirection * (time - *o\lastTime) * (*o\speed))
EndProcedure
Procedure getYPos(*o.Object, time.l)
ProcedureReturn (*o\lastYPos + *o\YDirection * (time - *o\lastTime) * (*o\speed))
EndProcedure
Procedure setXPos(*o.Object, time.l, x.l)
*o\lastXPos = x
*o\lastTime = time
EndProcedure
Procedure setYPos(*o.Object, time.l, y.l)
*o\lastYPos = y
*o\lastTime = time
EndProcedure
Procedure renderPlayer(*p.Player, time.l)
Box(getXPos(*p, time), getYPos(*p, time), #PLAYER_WIDTH, #PLAYER_HEIGHT, #FOREGROUND_COLOR)
EndProcedure
Procedure renderBall(*b.Ball, time.l)
Box(getXPos(*b, time), getYPos(*b, time), #BALL_WIDTH, #BALL_HEIGHT, #FOREGROUND_COLOR)
EndProcedure
Procedure moveUp(*o.Object, time.l)
If (*o\YDirection = -1)
ProcedureReturn
EndIf
*o\lastXPos = getXPos(*o, time)
*o\lastYPos = getYPos(*o, time)
*o\lastTime = time
*o\YDirection = -1
EndProcedure
Procedure moveDown(*o.Object, time.l)
If (*o\YDirection = 1)
ProcedureReturn
EndIf
*o\lastXPos = getXPos(*o, time)
*o\lastYPos = getYPos(*o, time)
*o\lastTime = time
*o\YDirection = 1
EndProcedure
Procedure moveLeft(*o.Object, time.l)
If (*o\XDirection = -1)
ProcedureReturn
EndIf
*o\lastXPos = getXPos(*o, time)
*o\lastYPos = getYPos(*o, time)
*o\lastTime = time
*o\XDirection = -1
EndProcedure
Procedure moveRight(*o.Object, time.l)
If (*o\XDirection = 1)
ProcedureReturn
EndIf
*o\lastXPos = getXPos(*o, time)
*o\lastYPos = getYPos(*o, time)
*o\lastTime = time
*o\XDirection = 1
EndProcedure
Procedure stopMoving(*o.Object, time.l)
If (*o\XDirection = 0 And *o\YDirection = 0)
ProcedureReturn
EndIf
*o\lastXPos = getXPos(*o, time)
*o\lastYPos = getYPos(*o, time)
*o\lastTime = time
*o\XDirection = 0
*o\YDirection = 0
EndProcedure
Procedure render(time.l)
ClearScreen(#BACKGROUND_COLOR)
StartDrawing(ScreenOutput())
Line(0, #UPPER_BORDER, #SCREEN_WIDTH, 0, #FOREGROUND_COLOR)
Line(0, #UPPER_BORDER -4, #SCREEN_WIDTH, 0, #FOREGROUND_COLOR)
Line(0, #LOWER_BORDER, #SCREEN_WIDTH, 0, #FOREGROUND_COLOR)
Line(0, #LOWER_BORDER +4, #SCREEN_WIDTH, 0, #FOREGROUND_COLOR)
renderPlayer(*player1, time)
renderPlayer(*player2, time)
renderBall(*ball, time)
If (0 And send)
DrawText(5, 5, "sending network data", #FOREGROUND_COLOR, #BACKGROUND_COLOR)
EndIf
StopDrawing()
FlipBuffers()
EndProcedure
Procedure controls(time.l)
ExamineKeyboard()
If (KeyboardReleased(#PB_Key_Up) Or KeyboardReleased(#PB_Key_Down))
stopMoving(*myself, time)
send = #True
EndIf
If (KeyboardPushed(#PB_Key_Up))
moveUp(*myself, time)
send = #True
EndIf
If (KeyboardPushed(#PB_Key_Down))
moveDown(*myself, time)
send = #True
EndIf
EndProcedure
Procedure collision(*p.Player, time.l)
If (getYPos(*p, time) < #UPPER_BORDER)
setYPos(*p, time, #UPPER_BORDER)
stopMoving(*p, time)
If (*p = *myself)
send = #True
EndIf
EndIf
If (getYPos(*p, time) > #LOWER_BORDER - #PLAYER_HEIGHT)
setYPos(*p, time, #LOWER_BORDER - #PLAYER_HEIGHT)
stopMoving(*p, time)
If (*p = *myself)
send = #True
EndIf
EndIf
EndProcedure
Procedure network()
If (server)
event = NetworkServerEvent()
If (event = #PB_NetworkEvent_Data)
ReceiveNetworkData(connection, *networkBuffer, SizeOf(Object))
If (*networkBuffer\type = #PLAYER)
CopyMemory(*networkBuffer, *player2, SizeOf(Object))
EndIf
EndIf
If (sendball)
SendNetworkData(connection, *ball, SizeOf(Object))
sendball = #False
EndIf
Else
event = NetworkClientEvent(connection)
If (event = #PB_NetworkEvent_Data)
ReceiveNetworkData(connection, *networkBuffer, SizeOf(Object))
If (*networkBuffer\type = #BALL)
CopyMemory(*networkBuffer, *ball, SizeOf(Object))
ElseIf (*networkBuffer\type = #PLAYER)
CopyMemory(*networkBuffer, *player1, SizeOf(Object))
EndIf
EndIf
EndIf
If (send)
SendNetworkData(connection, *myself, SizeOf(Object))
send = #False
EndIf
Delay(0)
EndProcedure
Procedure ball(*ball.Ball, time.l)
reset = #False
If (getXPos(*ball, time) < 0)
*player2\score +1
reset = #True
EndIf
If (getXPos(*ball, time) > #SCREEN_WIDTH)
*player1\score +1
reset = #True
EndIf
If (getXPos(*ball, time) >= getXPos(*player1, time) + #PLAYER_WIDTH - #BALL_WIDTH/4)
If (getXPos(*ball, time) <= getXPos(*player1, time) + #PLAYER_WIDTH)
If (getYPos(*ball, time) >= getYPos(*player1, time) - #BALL_HEIGHT)
If (getYPos(*ball, time) <= getYPos(*player1, time) + #PLAYER_HEIGHT)
moveRight(*ball, time)
sendball = #True
EndIf
EndIf
EndIf
EndIf
If (getXPos(*ball, time) >= getXPos(*player2, time) - #BALL_WIDTH - #BALL_WIDTH / 4)
If (getXPos(*ball, time) <= getXPos(*player2, time) - #BALL_WIDTH)
If (getYPos(*ball, time) >= getYPos(*player2, time) - #BALL_HEIGHT)
If (getYPos(*ball, time) <= getYPos(*player2, time) + #PLAYER_HEIGHT)
moveLeft(*ball, time)
sendball = #True
EndIf
EndIf
EndIf
EndIf
If (getYPos(*ball, time) >= #LOWER_BORDER - #BALL_HEIGHT)
moveUp(*ball, time)
If (*ball\XDirection = 0)
If (Int(Random(1)))
moveLeft(*ball, time)
Else
moveRight(*ball, time)
EndIf
EndIf
sendball = #True
EndIf
If (getYPos(*ball, time) <= #UPPER_BORDER)
moveDown(*ball, time)
sendball = #True
EndIf
If (reset)
setXPos(*ball, time, #SCREEN_WIDTH/2 - #BALL_WIDTH/2)
setYPos(*ball, time, #SCREEN_HEIGHT/2 - #BALL_HEIGHT/2)
stopMoving(*ball, time)
moveDown(*ball, time)
sendball = #True
EndIf
EndProcedure
Procedure initGame()
*player1 = newPlayer(#SCREEN_WIDTH/16, #SCREEN_HEIGHT/2 - #PLAYER_HEIGHT/2)
*player2 = newPlayer(#SCREEN_WIDTH - #SCREEN_WIDTH/16 - #PLAYER_WIDTH, #SCREEN_HEIGHT/2 - #PLAYER_HEIGHT/2)
If (server)
*myself = *player1
*opponent = *player2
Else
*myself = *player2
*opponent = *player1
EndIf
*ball = newBall(#SCREEN_WIDTH/2 - #BALL_WIDTH/2, #SCREEN_HEIGHT/2 - #BALL_HEIGHT/2)
*ball\XDirection = 0
*ball\YDirection = 1
EndProcedure
Procedure.l getTime()
If (server)
ProcedureReturn ElapsedMilliseconds() + timediff
Else
ProcedureReturn ElapsedMilliseconds() - serverDiff
EndIf
EndProcedure
Procedure runGame()
Repeat
time = getTime()
controls(time)
collision(*myself, time)
network()
collision(*opponent, time)
ball(*ball, time)
render(time)
If (windowed)
WindowEvent()
EndIf
Delay(0)
Until KeyboardReleased(#PB_Key_Escape)
EndProcedure
Procedure synchronize()
If (server)
; send server's current time
firstTime = ElapsedMilliseconds()
SendNetworkData(connection, @firstTime, 4)
Repeat
event = NetworkServerEvent()
Until event = #PB_NetworkEvent_Data
; calculate time difference (server to client)
time2 = ElapsedMilliseconds()
timediff = (time2 - firstTime) / 2
ReceiveNetworkData(connection, @time2, 4)
; send this difference to client
SendNetworkData(connection, @timediff, 4)
Else
Repeat
event = NetworkClientEvent(connection)
Until event = #PB_NetworkEvent_Data
; receive server's current time
serverTime = 0
ReceiveNetworkData(connection, @serverTime, 4)
serverDiff = ElapsedMilliseconds() - serverTime
; answer from client to server
firstTime = ElapsedMilliseconds()
SendNetworkData(connection, @firstTime, 4)
Repeat
event = NetworkClientEvent(connection)
Until event = #PB_NetworkEvent_Data
; receive time difference
ReceiveNetworkData(connection, @timediff, 4)
serverDiff = serverDiff - timediff
EndIf
;For i=1 To 100: Delay(10):Next i
EndProcedure
Procedure initConnection()
If (server)
If CreateNetworkServer(0, port, #PB_Network_TCP)
Repeat
ClearScreen(#BACKGROUND_COLOR)
StartDrawing(ScreenOutput())
DrawText(5, 5, "waiting for a client...", #FOREGROUND_COLOR, #BACKGROUND_COLOR)
StopDrawing()
FlipBuffers()
ExamineKeyboard()
If (KeyboardReleased(#PB_Key_Escape))
exit()
EndIf
If (windowed)
WindowEvent()
EndIf
Delay(0)
event = NetworkServerEvent()
Until event = #PB_NetworkEvent_Connect
connection = EventClient()
synchronize()
Else
MessageRequester("Pong", "Server could not be created!")
exit()
EndIf
Else
ClearScreen(#BACKGROUND_COLOR)
StartDrawing(ScreenOutput())
DrawText(5, 5, "connecting to server...", #FOREGROUND_COLOR, #BACKGROUND_COLOR)
StopDrawing()
FlipBuffers()
connection = OpenNetworkConnection(serverName$, port, #PB_Network_TCP)
If Not connection
MessageRequester("Pong", "Could not connect to server!")
exit()
EndIf
synchronize()
EndIf
EndProcedure
Procedure main()
If (MessageRequester("Pong", "Fullscreen?", #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes)
OpenScreen(#SCREEN_WIDTH, #SCREEN_HEIGHT, 32, "Pong")
windowed = 0
Else
OpenWindow(0, #SCREEN_WIDTH, #SCREEN_HEIGHT, #SCREEN_WIDTH, #SCREEN_HEIGHT, "Pong", #PB_Window_ScreenCentered)
OpenWindowedScreen(WindowID(0), 0, 0, #SCREEN_WIDTH, #SCREEN_HEIGHT, 0, 0, 0)
windowed = 1
EndIf
If (MessageRequester("Pong", "Server?", #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes)
server = 1
Else
server = 0
serverName$ = InputRequester("Pong", "Server Address?", serverName$)
EndIf
initConnection()
initGame()
runGame()
exit()
EndProcedure
OpenPreferences("pongtest.ini")
serverName$ = ReadPreferenceString("server", "localhost")
port = Val(ReadPreferenceString("port", "6666"))
main()