Java大作业要求实现游戏能够双人联机对战,在初学了网络编程后, 采用传送键值的方法,实现了游戏画面的基本同步。
那总的来说就是要写一个客户端,一个服务器端,因为要实现双人对战,所以服务器负责协调两个客户端之间信息交流。但交流的信息(数据包)的内容是一个要讨论的问题,暂时想到两种方案。
第一种:传键入的键值,另一个客户端接收键值后在本程序更新画面。
P1键入后 ,将键入信息发送给P2,P2接收后模拟在本程序键入A、W、S、D、space等按键。
P2键入后 ,将键入信息发送给P1,P1接收后模拟在本程序键入UP、DOWN、LEFT、RIGHT、Enter等按键。
第二种:服务器接收客户端键值,加载完画面后,传送给两个客户端整个画面的地图信息,实现客户端画面的同步。
由于原来写的代码对实现第二种方案的复用性不好,这里暂时用第一种方法。
1、服务器端
创建客户端线程实现P1与P2的通信
package bombman;
import javax.swing.*;
import java.awt.*;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
public class BombServer extends JFrame implements Runnable {
private Socket s = null;
private ServerSocket ss = null;
private ArrayList<ChatThread> clients=new ArrayList<ChatThread>();//保存每个客户端连入的变长数组
public BombServer()throws Exception{//初始化
this.setTitle("泡泡堂服务器端");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setBackground(Color.yellow);
this.setBounds(500, 250, 600, 300);
this.setVisible(true);
ss=new ServerSocket(9090);//服务器开辟一个端口
new Thread(this).start();//接受客户连接的死循环开始运行
}
public int clientNum = 0;//给用户数量计数
@Override
public void run() {
try {
while(true)
{
s=ss.accept();//等待连入
ChatThread ct=new ChatThread(s);//当有客户端连入后,为此客户端创建一个线程
clients.add(ct);//并且将此线程加入到线程数组中
clientNum ++;
System.out.println("当前用户数量:"+clientNum);
ct.start();//启动此线程的线程,此后可以实现通信
}
}catch(Exception ex) {
ex.printStackTrace();
//javax.swing.JOptionPane.showMessageDialog(this, "游戏异常退出!");
System.exit(0);
}
}
//类中类的建立,此线程来接收服务器和一个客户端的通信的线程(针对于服务器)
class ChatThread extends Thread{
private Socket s=null;
private BufferedReader br=null;
private PrintStream ps=null;
private boolean canRun=true;
public ChatThread(Socket s)throws Exception
{//利用线程实现输入输出(通信)
this.s=s;
br=new BufferedReader(
new InputStreamReader(s.getInputStream()));
ps=new PrintStream(s.getOutputStream());
}
//重写run,线程执行的方法
public void run() {//把从客户那里得到的信息,传送给其他客户
try {
while(canRun) {
String str=br.readLine();//读取该Socket传来的信息,
System.out.println(str);
if(str.charAt(0) == '1'){//说明这是P2发给P1的字符串
sendMessage(str, 1);
} else if(str.charAt(0) == '2'){//说明这是P1发给P2的字符串
sendMessage(str, 2);
}
}
}catch (Exception ex) {
canRun=false;
clients.remove(this);//将此线程从客户端的数组中删除
}
}
}
//将信息发送给其他的客户端,实现客户端之间的通信
public void sendMessage(String msg, int r) {
/*for(ChatThread ct: clients) {
ct.ps.println(msg);
}*/
if(r == 1){
clients.get(0).ps.println(msg);//向P1发送字符串
} else if(r == 2){
clients.get(1).ps.println(msg);//向P2发送字符串
}
}
public static void main(String[] args) throws Exception {
BombServer server = new BombServer();
}
}
2、客户端发送数据
在键盘监听器中发送信息,当按下一个按键时向另一个玩家发送一个字符串信息(用ps发送,ps已经连接了输出流,字符串信息包括目标玩家信息和按键信息)
字符串的第一个字代表目标玩家,”1“代表P1,”2“代表P2
字符串的第二个字代表按下按键:
玩家1:”W"、“S”、”A"、“D”、“B”,分别表示上、下、左、右、放炸弹
玩家2:”8"、“2”、”4"、“6”、“5”,分别表示上、下、左、右、放炸弹
/**
* !!!!!!!!!!!!!在这里发送信息,当按下按键时发送一个代表自己身份的字符串!!!!!!
*/
// 键盘监听器,继承键值
class BombKeyAdapter extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
// TODO 自动生成的方法存根
// 根据按键来设置玩家的状态,是左 还是右?的朝向
// 第一个 玩家
if(computerplayer1.ispause && !ispause){// 当电脑控制是暂停状态,并且当前游戏面板不是暂停状态,才可以操作
if (e.getKeyCode() == KeyEvent.VK_S) {
BombMainInterface.ps.println("2S");//代表发送给P2,按键S
KeyTruePlayer(1,0,0);
}
else if (e.getKeyCode() == KeyEvent.VK_A) {
BombMainInterface.ps.println("2A");//代表发送给P2,按键A
KeyTruePlayer(1,1,0);
}
else if (e.getKeyCode() == KeyEvent.VK_D) {
BombMainInterface.ps.println("2D");//代表发送给P2,按键D
KeyTruePlayer(1,2, 0);
}
else if (e.getKeyCode() == KeyEvent.VK_W) {
BombMainInterface.ps.println("2W");//代表发送给P2,按键W
KeyTruePlayer(1,3, 0);
}
else if (e.getKeyCode() == KeyEvent.VK_SPACE) {// Enter放置炸弹
BombMainInterface.ps.println("2B");//代表发送给P2,按键B代表放炸弹
KeyTruePlayer(1,4, 1);
}
}
// 第二个玩家
if(computerplayer2.ispause && !ispause){
if (e.getKeyCode() == KeyEvent.VK_DOWN) {
BombMainInterface.ps.println("12");//代表发送给P1,按键VK_DOWN
// 如果玩家与炸弹同一个位置 设置不同状态的图片
KeyTruePlayer(2, 0, 0);
}
else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
BombMainInterface.ps.println("14");//代表发送给P1,按键VK_LEFT
KeyTruePlayer(2,1, 0);
}
else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
BombMainInterface.ps.println("16");//代表发送给P1,按键VK_RIGHT
KeyTruePlayer(2,2,0);
}
else if (e.getKeyCode() == KeyEvent.VK_UP) {
BombMainInterface.ps.println("18");//代表发送给P1,按键VK_UP
KeyTruePlayer(2,3,0);
}
else if (e.getKeyCode() == KeyEvent.VK_ENTER) {// 空格放置炸弹
BombMainInterface.ps.println("15");//代表发送给P1,按键VK_ENTER
KeyTruePlayer(2,4,1);
}
}
}
}
// 如果按键正确就 更新地图 放置炸弹这些。state是判断单纯的走还是放炸弹,
//player:第几个玩家,dir:地图上玩家的朝向,state:是走动 还是放置炸弹 0走,1放炸弹
3、服务端初始化连接数据库
//连接服务器,输入输出流,启动线程
try {
s=new Socket("127.0.0.1",9090); //连接服务器
JOptionPane.showMessageDialog(this, "连接成功");
InputStream is=s.getInputStream(); //接收输入流
br=new BufferedReader(new InputStreamReader(is)); //字符串读入
OutputStream os=s.getOutputStream(); //获取输出流
ps=new PrintStream(os); //字符串打印
new Thread(this).start(); //开启线程
}catch (Exception ex) {
javax.swing.JOptionPane.showMessageDialog(this, "游戏退出异常!");
System.exit(0);
}
4、客户端接收数据并解析数据
@Override
public void run() {
try {
System.out.println("等待读取字符串......");
while(canRun) {
String str=br.readLine();//读取到的字符串
//int score=Integer.parseInt(str);
System.out.println(str);
if(str.charAt(0) == '2'){
if(str.charAt(1) == 'S'){
center.KeyTruePlayer(1,0,0);
} else if(str.charAt(1) == 'A'){
center.KeyTruePlayer(1,1,0);
} else if(str.charAt(1) == 'D'){
center.KeyTruePlayer(1,2,0);
} else if(str.charAt(1) == 'W'){
center.KeyTruePlayer(1,3,0);
} else if(str.charAt(1) == 'B'){
center.KeyTruePlayer(1,4,1);
}
} else if(str.charAt(0) == '1'){
if(str.charAt(1) == '2'){
center.KeyTruePlayer(2,0,0);
} else if(str.charAt(1) == '4'){
center.KeyTruePlayer(2,1,0);
} else if(str.charAt(1) == '6'){
center.KeyTruePlayer(2,2,0);
} else if(str.charAt(1) == '8'){
center.KeyTruePlayer(2,3,0);
} else if(str.charAt(1) == '5'){
center.KeyTruePlayer(2,4,1);
}
}
}
}catch(Exception ex) {
canRun=false;
javax.swing.JOptionPane.showMessageDialog(this, "游戏退出异常!");
ex.printStackTrace();
System.exit(0);
}
}
5、游戏运行
首先打开服务端程序,创建一个等待连接的服务器
然后打开客户端程序,输入玩家1昵称,进入游戏画面
在另一台电脑打开客户端程序,输入玩家2昵称,进入游戏画面
连接到同一个服务器,提示连接成功!
弹出游戏画面,实现游戏基本同步: