Con l’introduzione dello scripting in RGSS in RPGMaker XP, si sono aperte molte frontiere sulla personalizzazione dei nostri giochi. Col tempo sono apparsi molti script per rendere i propri giochi più interessanti e sempre più stupefacenti. La semplicità con la quale si può creare un gioco con RPGMaker ha fatto in modo di avvicinare sia principianti che utenti avanzati nella programmazione a creare nuove esperienze di gioco.
Gli MMORPG, ovvero Massive(ly) Multiplayer Online Role-Playing Game, sono l’esperienza di gioco più coinvolgente di questo secolo, giocare online ti permette di abbattere i limiti fisici, giocare con altre persone ed entrare e vivere nel mondo fantastico del titolo online a cui stiamo giocando.
Anche su RPGMaker molti hanno tentato di realizzare un’esperienza di gioco online, sono tanti gli scripts che sono stati realizzati. In principio per RPGMaker XP vi fu Netplay e Netplayplus, seguiti da tanti altri, compresi alcuni tentativi italiani.
Oggi vi propongo la traduzione di un articolo inglese, di sorlokreaves che utilizzando le sue conoscenze di JAVA e Ruby, ha creato un semplicissimo sistema client/server per RPGMaker VX utilizzando i socket del protocollo TCP.
L’articolo si rivolge a chi ha già alcuni rudimenti di programmazione e vuole realizzare un sistema online nel proprio gioco.
Step 1 : Casi d’uso
Cominciamo col creare un progetto nuovo su RPGMaker VX. Create una mappa iniziale, mettete qualche tile di “terra”. Dopo aggiungete un NPC (evento) con questi comandi:
- Messaggio: “Apertura Socket”
- Script
s = TCPSocket.open('127.0.0.1', 7689) s.close()
- Messaggio: “Successo”
Adesso, provate il gioco e parlate con l’evento, dovreste vedere questo messaggio di errore e il gioco crasherà.
Cos’è appena successo? Bene, prima abbiamo provato a creare un socket. Questo socket assumeva che noi avessimo un server che stava girando su 127.0.0.1 (indirizzo di localhost, vuol dire che il server girava su questo computer) e che era collegato alla porta 7689. Siccome nessun server è stato lanciato, ci si aspetta un crash. -comunque abbiamo ottenuto un diverso messaggio di errore. Se guardate il messaggio d’errore, vedrete che RPGMaker VX non è riuscito a trovare l’oggetto “TCPSocket”. In altre parole RPGMaker VX non da supporto nativo alle librerie Ruby che gestiscono il networking (comunicazione online).-
Nemmeno RPGMaker XP offriva questo supporto nativamente, e l’autore si interroga come RMX-OS (script per RPGMaker XP, che permette di creare giochi online) lo abbia potuto ottenere.
Se avete già esperienza con i linguaggi di programmazione, sapete che di solito la parte di networking è collegata direttamente al sistema operativo. Se siete curiosi di vedere come Ruby si connetta attraverso i socket, potete scaricare il sorgente di Ruby 1.8 e analizzare il file thread.rb
Scarica Ruby qui: http://ftp.de.debian.org/debian/pool/main/r/ruby1.8/ruby1.8_1.8.7.249.orig.tar.gz
Step 2 : Prendendo in prestito la Libreria di Codice
Se avete familiarità con l’ambiente di scripting di RGSS, avrete notato che non supporta la primitiva “require”. Questo ha senso dal momento che “require” cerca il percorso alla libreria standard, potresti avviare delle librerie di Ruby che i videogiocatori non hanno. C’è da dire però che RPGMaker è in grado di eseguire codice nativo dalle DLLs. Per esempio questa funzionerà bene:
Win32API.new(DLL, 'connect', 'ppl', 'l').call(1, 2, 3)
Potremmo considerare di combinare insieme le DLL per creare insieme una “finta” libreria per il networking. Eventualmente potremmo migrare ad una cosiddetta libreria nativa, così potremmo guadagnare i benefici di primitive di basso livello. Un esempio è dato dalla libreria:
http://rubyforge.org/projects/win32utils/
Se la estraete e l’analizzate noterete che il codice sorgente ha molti require tag. Se volete utilizzarla dovrete essenzialmente sostituire queste con funzionalità compatibili.
Comunque per questo tutorial utilizzeremo la libreria Win32 API utilizzata da Blizzard in RMX-OS, che dice di aver preso da Ruby 1.8.1. Questo non creerà problemi di licenza, poichè Ruby permette l’utilizzo di copie di librerie modificate e ridistribuite.
Il codice di networking di RMX-OS è costruito con chiamate a DLL, questo è il codice da copiare:
#============================================================================== # ** Module Win32 - Handles numerical based data. #------------------------------------------------------------------------------ # Author Ruby # Version 1.8.1 #============================================================================== module Win32 #---------------------------------------------------------------------------- # ● Retrieves data from a pointer. #---------------------------------------------------------------------------- def copymem(len) buf = "\0" * len Win32API.new('kernel32', 'RtlMoveMemory', 'ppl', '').call(buf, self, len) buf end end # Extends the numeric class. class Numeric include Win32 end # Extends the string class. class String include Win32 end #============================================================================== # ** Module Winsock - Maps out the functions held in the Winsock DLL. #------------------------------------------------------------------------------ # Author Ruby # Version 1.8.1 #============================================================================== module Winsock DLL = 'ws2_32' #---------------------------------------------------------------------------- # * Accept Connection #---------------------------------------------------------------------------- def self.accept(*args) Win32API.new(DLL, 'accept', 'ppl', 'l').call(*args) end #---------------------------------------------------------------------------- # * Bind #---------------------------------------------------------------------------- def self.bind(*args) Win32API.new(DLL, 'bind', 'ppl', 'l').call(*args) end #---------------------------------------------------------------------------- # * Close Socket #---------------------------------------------------------------------------- def self.closesocket(*args) Win32API.new(DLL, 'closesocket', 'p', 'l').call(*args) end #---------------------------------------------------------------------------- # * Connect #---------------------------------------------------------------------------- def self.connect(*args) Win32API.new(DLL, 'connect', 'ppl', 'l').call(*args) end #---------------------------------------------------------------------------- # * Get host (Using Adress) #---------------------------------------------------------------------------- def self.gethostbyaddr(*args) Win32API.new(DLL, 'gethostbyaddr', 'pll', 'l').call(*args) end #---------------------------------------------------------------------------- # * Get host (Using Name) #---------------------------------------------------------------------------- def self.gethostbyname(*args) Win32API.new(DLL, 'gethostbyname', 'p', 'l').call(*args) end #---------------------------------------------------------------------------- # * Get host's Name #---------------------------------------------------------------------------- def self.gethostname(*args) Win32API.new(DLL, 'gethostname', 'pl', '').call(*args) end #---------------------------------------------------------------------------- # * Get Server (Using Name) #---------------------------------------------------------------------------- def self.getservbyname(*args) Win32API.new(DLL, 'getservbyname', 'pp', 'p').call(*args) end #---------------------------------------------------------------------------- # * HT OnL #---------------------------------------------------------------------------- def self.htonl(*args) Win32API.new(DLL, 'htonl', 'l', 'l').call(*args) end #---------------------------------------------------------------------------- # * HT OnS #---------------------------------------------------------------------------- def self.htons(*args) Win32API.new(DLL, 'htons', 'l', 'l').call(*args) end #---------------------------------------------------------------------------- # * Inet Adress #---------------------------------------------------------------------------- def self.inet_addr(*args) Win32API.new(DLL, 'inet_addr', 'p', 'l').call(*args) end #---------------------------------------------------------------------------- # * Inet NtOA #---------------------------------------------------------------------------- def self.inet_ntoa(*args) Win32API.new(DLL, 'inet_ntoa', 'l', 'p').call(*args) end #---------------------------------------------------------------------------- # * Listen #---------------------------------------------------------------------------- def self.listen(*args) Win32API.new(DLL, 'listen', 'pl', 'l').call(*args) end #---------------------------------------------------------------------------- # * Recieve #---------------------------------------------------------------------------- def self.recv(*args) Win32API.new(DLL, 'recv', 'ppll', 'l').call(*args) end #---------------------------------------------------------------------------- # * Select #---------------------------------------------------------------------------- def self.select(*args) Win32API.new(DLL, 'select', 'lpppp', 'l').call(*args) end #---------------------------------------------------------------------------- # * Send #---------------------------------------------------------------------------- def self.send(*args) Win32API.new(DLL, 'send', 'ppll', 'l').call(*args) end #---------------------------------------------------------------------------- # * Set Socket Options #---------------------------------------------------------------------------- def self.setsockopt(*args) Win32API.new(DLL, 'setsockopt', 'pllpl', 'l').call(*args) end #---------------------------------------------------------------------------- # * Shutdown #---------------------------------------------------------------------------- def self.shutdown(*args) Win32API.new(DLL, 'shutdown', 'pl', 'l').call(*args) end #---------------------------------------------------------------------------- # * Socket #---------------------------------------------------------------------------- def self.socket(*args) Win32API.new(DLL, 'socket', 'lll', 'l').call(*args) end #---------------------------------------------------------------------------- # * Get Last Error #---------------------------------------------------------------------------- def self.WSAGetLastError(*args) Win32API.new(DLL, 'WSAGetLastError', '', 'l').call(*args) end end #============================================================================== # ** Socket - Creates and manages sockets. #------------------------------------------------------------------------------ # Author Ruby # Version 1.8.1 #============================================================================== class Socket #---------------------------------------------------------------------------- # ● Constants #---------------------------------------------------------------------------- AF_UNSPEC = 0 AF_UNIX = 1 AF_INET = 2 AF_IPX = 6 AF_APPLETALK = 16 PF_UNSPEC = 0 PF_UNIX = 1 PF_INET = 2 PF_IPX = 6 PF_APPLETALK = 16 SOCK_STREAM = 1 SOCK_DGRAM = 2 SOCK_RAW = 3 SOCK_RDM = 4 SOCK_SEQPACKET = 5 IPPROTO_IP = 0 IPPROTO_ICMP = 1 IPPROTO_IGMP = 2 IPPROTO_GGP = 3 IPPROTO_TCP = 6 IPPROTO_PUP = 12 IPPROTO_UDP = 17 IPPROTO_IDP = 22 IPPROTO_ND = 77 IPPROTO_RAW = 255 IPPROTO_MAX = 256 SOL_SOCKET = 65535 SO_DEBUG = 1 SO_REUSEADDR = 4 SO_KEEPALIVE = 8 SO_DONTROUTE = 16 SO_BROADCAST = 32 SO_LINGER = 128 SO_OOBINLINE = 256 SO_RCVLOWAT = 4100 SO_SNDTIMEO = 4101 SO_RCVTIMEO = 4102 SO_ERROR = 4103 SO_TYPE = 4104 SO_SNDBUF = 4097 SO_RCVBUF = 4098 SO_SNDLOWAT = 4099 TCP_NODELAY = 1 MSG_OOB = 1 MSG_PEEK = 2 MSG_DONTROUTE = 4 IP_OPTIONS = 1 IP_DEFAULT_MULTICAST_LOOP = 1 IP_DEFAULT_MULTICAST_TTL = 1 IP_MULTICAST_IF = 2 IP_MULTICAST_TTL = 3 IP_MULTICAST_LOOP = 4 IP_ADD_MEMBERSHIP = 5 IP_DROP_MEMBERSHIP = 6 IP_TTL = 7 IP_TOS = 8 IP_MAX_MEMBERSHIPS = 20 EAI_ADDRFAMILY = 1 EAI_AGAIN = 2 EAI_BADFLAGS = 3 EAI_FAIL = 4 EAI_FAMILY = 5 EAI_MEMORY = 6 EAI_NODATA = 7 EAI_NONAME = 8 EAI_SERVICE = 9 EAI_SOCKTYPE = 10 EAI_SYSTEM = 11 EAI_BADHINTS = 12 EAI_PROTOCOL = 13 EAI_MAX = 14 AI_PASSIVE = 1 AI_CANONNAME = 2 AI_NUMERICHOST = 4 AI_MASK = 7 AI_ALL = 256 AI_V4MAPPED_CFG = 512 AI_ADDRCONFIG = 1024 AI_DEFAULT = 1536 AI_V4MAPPED = 2048 #---------------------------------------------------------------------------- # ● Returns the associated IP address for the given hostname. #---------------------------------------------------------------------------- def self.getaddress(host) gethostbyname(host)[3].unpack('C4').join('.') end #---------------------------------------------------------------------------- # ● Returns the associated IP address for the given hostname. #---------------------------------------------------------------------------- def self.getservice(serv) case serv when Numeric return serv when String return getservbyname(serv) else raise 'Please us an interger or string for services.' end end #---------------------------------------------------------------------------- # ● Returns information about the given hostname. #---------------------------------------------------------------------------- def self.gethostbyname(name) raise SocketError::ENOASSOCHOST if (ptr = Winsock.gethostbyname(name)) == 0 host = ptr.copymem(16).unpack('iissi') [host[0].copymem(64).split("\0")[0], [], host[2], host[4].copymem(4).unpack('l')[0].copymem(4)] end #---------------------------------------------------------------------------- # ● Returns the user's hostname. #---------------------------------------------------------------------------- def self.gethostname buf = "\0" * 256 Winsock.gethostname(buf, 256) buf.strip end #---------------------------------------------------------------------------- # ● Returns information about the given service. #---------------------------------------------------------------------------- def self.getservbyname(name) case name when /echo/i return 7 when /daytime/i return 13 when /ftp/i return 21 when /telnet/i return 23 when /smtp/i return 25 when /time/i return 37 when /http/i return 80 when /pop/i return 110 else raise 'Service not recognized.' end end #---------------------------------------------------------------------------- # ● Creates an INET-sockaddr struct. #---------------------------------------------------------------------------- def self.sockaddr_in(port, host) begin [AF_INET, getservice(port)].pack('sn') + gethostbyname(host)[3] + [].pack('x8') rescue end end #---------------------------------------------------------------------------- # ● Creates a new socket and connects it to the given host and port. #---------------------------------------------------------------------------- def self.open(*args) socket = new(*args) if block_given? begin yield socket ensure socket.close end end nil end #---------------------------------------------------------------------------- # ● Creates a new socket. #---------------------------------------------------------------------------- def initialize(domain, type, protocol) SocketError.check if (@fd = Winsock.socket(domain, type, protocol)) == -1 @fd end #---------------------------------------------------------------------------- # ● Accepts incoming connections. #---------------------------------------------------------------------------- def accept(flags = 0) buf = "\0" * 16 SocketError.check if Winsock.accept(@fd, buf, flags) == -1 buf end #---------------------------------------------------------------------------- # ● Binds a socket to the given sockaddr. #---------------------------------------------------------------------------- def bind(sockaddr) SocketError.check if (ret = Winsock.bind(@fd, sockaddr, sockaddr.size)) == -1 ret end #---------------------------------------------------------------------------- # ● Closes a socket. #---------------------------------------------------------------------------- def close SocketError.check if (ret = Winsock.closesocket(@fd)) == -1 ret end #---------------------------------------------------------------------------- # ● Connects a socket to the given sockaddr. #---------------------------------------------------------------------------- def connect(sockaddr) SocketError.check if (ret = Winsock.connect(@fd, sockaddr, sockaddr.size)) == -1 ret end #---------------------------------------------------------------------------- # ● Listens for incoming connections. #---------------------------------------------------------------------------- def listen(backlog) SocketError.check if (ret = Winsock.listen(@fd, backlog)) == -1 ret end #---------------------------------------------------------------------------- # ● Checks waiting data's status. #---------------------------------------------------------------------------- def select(timeout) SocketError.check if (ret = Winsock.select(1, [1, @fd].pack('ll'), 0, 0, [timeout, timeout * 1000000].pack('ll'))) == -1 ret end #---------------------------------------------------------------------------- # ● Checks if data is waiting. #---------------------------------------------------------------------------- def ready? not select(0) == 0 end #---------------------------------------------------------------------------- # ● Reads data from socket. #---------------------------------------------------------------------------- def read(len) buf = "\0" * len Win32API.new('msvcrt', '_read', 'lpl', 'l').call(@fd, buf, len) buf end #---------------------------------------------------------------------------- # ● Returns recieved data. #---------------------------------------------------------------------------- def recv(len, flags = 0) buf = "\0" * len SocketError.check if Winsock.recv(@fd, buf, buf.size, flags) == -1 buf end #---------------------------------------------------------------------------- # ● Sends data to a host. #---------------------------------------------------------------------------- def send(data, flags = 0) SocketError.check if (ret = Winsock.send(@fd, data, data.size, flags)) == -1 ret end #---------------------------------------------------------------------------- # ● Writes data to socket. #---------------------------------------------------------------------------- def write(data) Win32API.new('msvcrt', '_write', 'lpl', 'l').call(@fd, data, 1) end end #============================================================================== # ** TCPSocket - Creates and manages TCP sockets. #------------------------------------------------------------------------------ # Author Ruby # Version 1.8.1 #============================================================================== class TCPSocket < Socket #---------------------------------------------------------------------------- # ● Creates a new socket and connects it to the given host and port. #---------------------------------------------------------------------------- def self.open(*args) socket = new(*args) if block_given? begin yield socket ensure socket.close end end nil end #---------------------------------------------------------------------------- # ● Creates a new socket and connects it to the given host and port. #---------------------------------------------------------------------------- def initialize(host, port) super(AF_INET, SOCK_STREAM, IPPROTO_TCP) connect(Socket.sockaddr_in(port, host)) end end #============================================================================== # ** SocketError #------------------------------------------------------------------------------ # Default exception class for sockets. #============================================================================== class SocketError < StandardError ENOASSOCHOST = 'getaddrinfo: no address associated with hostname.' def self.check errno = Winsock.WSAGetLastError raise Errno.const_get(Errno.constants.detect { |c| Errno.const_get(c).new.errno == errno }) end end
Nell’editor degli scripts (F11), fate tasto destro su “Main”, e scegliete “Inserisci”, scrivete “Ruby Library Code” nel nome e incollate il codice precedente. Scegliete OK e salvate il gioco.
Se provate il gioco adesso, finirete con l’avere un errore. Quindi cancellate l’evento che abbiamo creato in precedenza, e aggiungete il codice che segue direttamente nello script Main, esattamente dopo Graphics.freeze
s = TCPSocket.open('127.0.0.1', 7689) s.close()
Adesso salva il gioco, e avvia il game testing. Otterrai il seguente messaggio d’errore:
Questo è il tipo d’errore che stavamo cercando! Congratulazioni, la struttura base del codice TCP che hai aggiunto era corretta.
Step 3 : Inizializzare e Testare la Connessione con gli NPCs
Abbiamo bisogno di testare una connessione TCP -inoltre dovremmo sistemare il bug che non permette agli NPC di attivare il nostro codice. Questi sono i nostri prossimi compiti.
Se hai trovato nel codice l’origine dell’errore, sei arrivato a queste linee di codice:
def self.check errno = Winsock.WSAGetLastError raise Errno.const_get(Errno.constants.detect { |c| Errno.const_get(c).new.errno== errno }) end
L’errore è da riteneresi nello statement “raise”. Se non capisci la logica a blocchi di Ruby, questo ti sconforterà e non vorrai proseguire. Ma prova questa piccola astrazione :
def self.check errno = Winsock.WSAGetLastError constName = Errno.constants.detect { |c| Errno.const_get(c).new.errno== errno } raise Errno.const_get(constName) end
Prova questo codice ancora e noterai che l’errore sta nuovamente sulla linea di “raise”. Il programmatore originale voleva trovare gli errori di Win32 attraverso il loro ID, ma non ha messo in conto che un socket inizializzato a null avrebbe potuto generare un errore senza codice ID. Per risolvere basta questo:
def self.check errno = Winsock.WSAGetLastError constName = Errno.constants.detect {|c| Errno.const_get(c).new.errno == errno } if constName raise Errno.const_get(constName) else raise "Unknown network error code: #{errno}" end end
Adesso se proverai ad avviare il codice dell’evento, vedrai l’errore “Unknown network error code”.
A questo punto dovremmo dire che TCPSocket.open() non è tecnicamente corretto, poichè si aspetta che noi passiamo alla funzione un blocco di codice da eseguire. Questo potrebbe andare bene per una procedura socket immediata come ricevere l’ora attuale, o le condizioni meteo da un server centrale, ma noi abbiamo bisogno di una connessione persistente con il server per il nostro engine. Come ti aspetterai il nostro codice supererà il limite del comando Script dell’Evento. Quindi, cambia il codice script dell’evento con:
tcptest()
…e aggiungi il codice seguente al modulo “Main”, direttamente prima dello statement “begin”
def tcptest #Createa a socket s = TCPSocket.new('127.0.0.1', 7689) #Send a test message s.send("Testing...\n") #Receive a result from the server msg = '' while buffer = s.recv(1024) #Read UP TO 1024 bytes buffer.gsub!(0.chr, '') #Remove null bytes msg += buffer #Append received data break if buffer.count("\n")>0 #Stop if we've reached the newline end #Done; close the socket, print our message s.close() print "Received: #{msg}" end
Il nostro codice è molto semplice, abbiamo usato “new” invece di “open”, e abbiamo usato “send” e “recv” per mandare e ricevere i dati. Un problema di recv, è che non aspetterà che il server abbia finito di comunicare la risposta per intero, così abbiamo bisogno di memorizzare l’intero messaggio di bit non appena vengono ricevuti. D’altronde una delle garanzie di TCP è che non riceveremo messaggi disordinati, questo è un punto che ci risparmierà molti mal di testa.
Adesso quello che abbiamo bisogno è il server. Credo che Ruby non sia molto indicato come codice client\server, proveremo così a scriverne uno in Java. Un problema con Java è che compilare il codice ed eseguirlo potrebbe essere difficoltoso. Piuttosto che fornirvi un tutorial completo su Eclipse o javac, ti chiederò di scaricare e installare Textpad.
NB: Alcuni utenti hanno segnalato che è necessario anche scaricare JDK. Apri una finestra di comando, e scrivi javac -version. Se ottieni un messaggio d’errore, allora devi scaricare JDK.
Adesso crea un nuovo file, chiamalo esattamente “SimpleServer.java“, e dentro scrivi questo codice:
import java.io.*; import java.net.*; class SimpleServer { public static void main(String argv[]) throws Exception { String clientSentence; String capitalizedSentence; ServerSocket welcomeSocket = new ServerSocket(7689); for ( ; ; ) { System.out.println("Waiting for connection..."); Socket connectionSocket = welcomeSocket.accept(); System.out.println(" >Socket connected"); BufferedReader inFromClient = new BufferedReader(new InputStreamReader(connectionSocket.getInputStream())); DataOutputStream outToClient = new DataOutputStream(connectionSocket.getOutputStream()); clientSentence = inFromClient.readLine(); System.out.println(" >Received: " + clientSentence); capitalizedSentence = clientSentence.toUpperCase() + '\n'; outToClient.writeBytes(capitalizedSentence); System.out.println(" >Replied: " + capitalizedSentence); } } }
Abbastanza semplice? Abbiamo ignorato la parte di controllo degli errori, sarà relativamente semplice far crashare il server. Ma per una demo veloce, ce la farà. Il server legge le frasi mandate dal client, e le riporta in maiuscolo. Poichè abbiamo mandato la stringa “Testing…” attraverso il client, il server restituirà “TESTING…”.
Per compilare il codice del server, in TextPad, clicca su “Tools-> External Tools-> Compile Java”.
Se il codice non contiene errori, vedrai il messaggio di avvenuta compilazione. Adesso per avviare il server, clicca su “Tools->External Tools->Run Java Application”, a questo punto apparirà una finestra di comando con su scritto “Waiting for connection…”.Questo significa che il server è pronto per accettare una connessione alla porta 7689. Adesso avvia il gioco, parla con l’evento NPC, e vedrai un messaggio con la risposta del server:
La finestra del server avrà ulteriori messaggi:
Dal momento che abbiamo chiuso la connessione, il server sta aspettando un’altra connessione in ingresso. Potremmo ri-parlare con l’evento, oppure chiudere e aprire il gioco di nuovo e parlare con l’evento. Avremo bisogno che il client tenga la connessione per altri messaggi, ma per adesso abbiamo provato che il networking di RPGMaker VX funziona. Premi CTRL+C per chiudere la finestra, premi Y se necessario per confermare la chiusura del server.
Step 4 : Un passo indietro
A questo punto è opportuno fermarsi un attimo e capire cosa abbiamo realizzato. C’è stato molto “codice incollato”, ma ecco cosa abbiamo fatto:
- Abbiamo creato un server che può “ricevere” connessioni sulla porta 7689, e un client che può richiedere una connessione a questa porta.
- Una volta connessi, client e server possono mandare e ricevere una sequenza di caratteri.
E questo è quanto. Aggiungendo altri clients, qualche tipo di sincronizzazione e partizionando i nostri dati in “messaggi” o “eventi di gioco”, sono solo dettagli di quello che il server TCP in realtà può fare. Per esempio possiamo mandare le coordinate di un evento, di un giocatore. In questo modo possiamo trattare i giocatori come eventi NPC in altri giochi del RPG che volete realizzare. Possiamo mandare tiles, per permettere ai giocatori di costruire le loro case, o personalizzare gli avatar di ogni giocatore, oppure coordinare un battle system. Tutte queste cose possono essere realizzate mandando stringhe al server TCP.
Step 5 : Una Prova leggermente più impressionante
Continua nella seconda parte…