【JAVA课设】【游戏社交系统】

一、项目简介

系统简介

游戏集合平台,解决多游戏资源管理、用户社交(好友)功能整合的问题,采用 Java Swing(游戏界面)、文件 IO(资源加载)、自定义类(游戏逻辑)等技术,实现推箱子、弹弹堂等游戏的集成与好友互动。

二、个人任务简述

1、用户交互功能模块

实现登录(log_in_interface)、注册(register_interface)、个人信息(person_info)等界面,支持平台基础操作。

2、社交互动功能模块

完成好友搜索(search_friends)、好友请求(friendsRequest)、好友列表(friend_column)管理,实现用户社交关联。

3、游戏娱乐功能模块

集成推箱子(Box_Pusher)、弹弹堂(DDtank)等游戏,完成游戏资源加载(如 DDtank_material)与玩法逻辑实现。

4、聊天通讯功能模块

基于 ChatClient、ChatServer 实现客户端 - 服务端聊天交互,搭配 JIMSendTextPane 完成消息发送面板功能。

5 数据访问功能模块 采用 DAO 模式(DAO 包)管理数据库读写,通过 data_base、user_data 实现用户数据的存储与访问。

5、游戏联机功能

本项目的联机功能基于Java Socket实现客户端与服务端的双向通信,构建了一套稳定的实时对战与社交互动体系,支持多玩家跨主机接入。

三、个人负责功能详解

1 用户交互功能模块

功能1:界面组件布局与交互元素初始化

java 复制代码
private void addComponents() {
    // 用户名标签与输入框
    JLabel lblNickname = new JLabel("Nickname:");
    lblNickname.setBounds(100, 50, 113, 19);
    contentPane.add(lblNickname);
    lblNickname.setForeground(new Color(0, 0, 255));
    lblNickname.setHorizontalAlignment(SwingConstants.CENTER);
    lblNickname.setFont(new Font("宋体", Font.PLAIN, 16));

    textField = new JTextField();
    textField.setBounds(205, 50, 126, 30);
    contentPane.add(textField);
    textField.setColumns(20);

    // 密码标签与密码输入框
    JLabel lblPassword = new JLabel("Password:");
    lblPassword.setBounds(100, 95, 113, 19);
    contentPane.add(lblPassword);
    lblPassword.setForeground(new Color(0, 0, 255));
    lblPassword.setHorizontalAlignment(SwingConstants.CENTER);
    lblPassword.setFont(new Font("宋体", Font.PLAIN, 16));

    passwordField = new JPasswordField();
    passwordField.setBounds(205, 95, 126, 30);
    contentPane.add(passwordField);
    // 回车键快捷登录绑定
    passwordField.addKeyListener(new KeyAdapter() {
        @Override
        public void keyPressed(KeyEvent e) {
            if(e.getKeyCode() == KeyEvent.VK_ENTER)
                log_in();
        }
    });
    passwordField.setColumns(20);

    // 登录按钮
    JButton btnLogIn = new JButton("Log in");
    btnLogIn.setBounds(170, 150, 158, 30);
    contentPane.add(btnLogIn);
    btnLogIn.addMouseListener(new MouseAdapter() {
        @Override
        public void mouseClicked(MouseEvent e) {
            log_in();
        }
    });
    btnLogIn.setFont(new Font("宋体", Font.PLAIN, 16));
    btnLogIn.setForeground(new Color(0, 0, 255));

    // 注册跳转按钮
    JButton btnRegisterNow = new JButton("Register Now!");
    btnRegisterNow.setBounds(170, 190, 158, 30);
    contentPane.add(btnRegisterNow);
    btnRegisterNow.addMouseListener(new MouseAdapter() {
        @Override
        public void mouseClicked(MouseEvent e) {
            register_interface reg = new register_interface();
            reg.setVisible(true);
        }
    });
    btnRegisterNow.setForeground(new Color(255, 0, 255));
    btnRegisterNow.setFont(new Font("宋体", Font.PLAIN, 16));
}
  1. 视觉交互优化:通过设置标签、按钮的字体、颜色与对齐方式,提升界面可读性,避免原生Swing组件的单调感,给用户更舒适的视觉体验;

功能2:账号密码有效性校验

java 复制代码
/**
 * 检查登陆是否成功
 * @param name 用户名
 * @param password 密码
 * @return 0-账号/密码为空;-1-登录成功;1-登录失败(用户不存在或密码错误)
 */
int login_succ(String name,String password) {
    int tl = name.length(), pl = password.length();
    // 1. 账号或密码为空校验
    if(tl == 0 || pl == 0)
        return 0;

    // 2. 调用数据库工具类,获取用户信息
    Map<String, Object> userInfo = data_base.queryUserByUsername(name);

    // 3. 判断用户是否存在
    if (userInfo.isEmpty()) {
        return 1; // 用户不存在,登录失败
    }

    // 4. 提取数据库中的密码,对比输入密码
    String dbPassword = (String) userInfo.get("***");
    if (dbPassword != null && dbPassword.equals(password)) {
        return -1; // 密码匹配,登录成功
    } else {
        return 1; // 密码不匹配,登录失败
    }
}

分层校验设计:采用"用户是否存在→密码是否匹配"的分层校验逻辑,既保障了校验的严谨性,也便于后续扩展更细致的错误提示(如区分"用户不存在"与"密码错误");

功能3:登录核心流程与交互反馈

java 复制代码
void log_in() {
    // 获取用户登陆信息并去空格,避免无效输入
    String name = textField.getText().trim();
    @SuppressWarnings("deprecation")
    String password = passwordField.getText().trim();

    // 账号密码有效性校验
    if(login_succ(name, password) == -1) {
        user_data dao = new user_data();
        person_info user=new person_info();
        user = dao.getUserByName(name);

        // 尝试创建客户端并与服务端建立连接
        ChatClient clientSocket = new ChatClient("172.19.76.57");
        if(clientSocket.getclientSocket() != null && clientSocket.start(name)) {
            // 登录成功,跳转至主界面
            try {
                platform_interface platform = new platform_interface(clientSocket, user);
                platform.setVisible(true);
                exit_(); // 关闭当前登录界面
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        }
        else if(clientSocket.getclientSocket() == null) {
            // 网络连接失败提示
            JOptionPane.showMessageDialog(null, " Connect refused!\n Please check your Internet connection! ",
                    " Internet Error ", JOptionPane.ERROR_MESSAGE);
        }
        else {
            // 账号已在线提示
            JOptionPane.showMessageDialog(null, " Account already logged in!\nPlease check the "
                    + "security of your account! ", " Log In Error ", JOptionPane.ERROR_MESSAGE);
        }
    }
    // 账号密码校验失败提示
    else {
        JOptionPane.showMessageDialog(null, " Log in failed!\n Please check your account! ",
                " Log in Error ", JOptionPane.ERROR_MESSAGE);
    }
}

网络连接校验:在账号密码校验通过后,额外校验与服务端的连接状态,兼顾了本地数据有效性与远程服务可用性,保障用户登录后的正常使用。

功能4:背景图片加载与异常兜底

java 复制代码
// 加载图片的辅助方法
private ImageIcon loadImageIcon(String path) {
    try {
        // 1. 优先从classpath加载(适配jar打包场景)
        URL resource = log_in_interface.class.getClassLoader().getResource(path);
        if (resource != null) {
            BufferedImage img = ImageIO.read(resource);
            return new ImageIcon(img);
        }
        
        // 2. 类路径加载失败,尝试本地文件系统加载
        File imageFile = new File(path);
        if (imageFile.exists()) {
            BufferedImage img = ImageIO.read(imageFile);
            return new ImageIcon(img);
        }
        
        // 3. 尝试常见路径变体,提升加载成功率
        String[] alternativePaths = {
            "./images/" + path,
            "images/" + path,
            "./Chaplay/images/" + path
        };
        
        for (String altPath : alternativePaths) {
            imageFile = new File(altPath);
            if (imageFile.exists()) {
                BufferedImage img = ImageIO.read(imageFile);
                return new ImageIcon(img);
            }
        }
        
        System.err.println("无法找到图片: " + path);
        return new ImageIcon(); // 返回空图标
    } catch (IOException e) {
        System.err.println("加载图片时发生错误: " + path + ", 错误: " + e.getMessage());
        return new ImageIcon(); // 返回空图标
    }
}

提升界面美观度:通过背景图片加载,摆脱原生Swing界面的单调感,提升用户的视觉体验,让登录界面更具亲和力。

2 社交互动功能模块

基于Java Swing的好友搜索与申请功能实现解析

在社交互动模块中,好友搜索与申请是建立新好友关系的核心入口,search_friends 类实现了用户名搜索、用户有效性校验、好友申请发送三大核心功能,本文选取其中关键代码片段,拆解其交互逻辑与设计亮点。

用户有效性校验

java 复制代码
//判断名字是否在数据库当中(封装数据库查询,废弃ResultSet)
public boolean friendsFind(String name) {
    // 1. 调用data_base静态方法,获取封装后的用户信息Map
    Map<String, Object> userInfo = data_base.queryUserByUsername(name);

    // 2. 通过Map是否为空判断用户是否存在(无需处理ResultSet,无SQLException)
    // 空Map表示未查询到用户,非空表示用户存在
    return !userInfo.isEmpty();
}

解耦数据库操作 :不直接在社交交互模块编写JDBC与ResultSet处理逻辑,而是调用data_base工具类的queryUserByUsername()方法,将数据访问层与业务交互层分离,降低模块耦合度,提升代码可维护性;

好友申请发送

java 复制代码
JButton btnSendingRequest = new JButton("Sending request!");
//发送好友申请
btnSendingRequest.addMouseListener(new MouseAdapter() {
    @Override
    public void mouseClicked(MouseEvent e) {
        String name = textField.getText();

        // 场景1:输入用户名为空
        if(name.length() == 0) {
            JOptionPane.showMessageDialog(null, " Name empty!\n Please input your friends name! ",
                    " Input Error ", JOptionPane.ERROR_MESSAGE);
        }
        // 场景2:该用户已为当前用户好友
        else if(platform_interface.myInfo.getfriends_nameall().indexOf(name)>=0) {
            JOptionPane.showMessageDialog(null, " User " + name + " has been your friend!\n Please check your input! ",
                    " Input Error ", JOptionPane.ERROR_MESSAGE);
        }
        // 场景3:用户存在,发送好友申请
        else if(friendsFind(name)) {
            try {
                // 发送申请格式:@friends request from + 当前用户名(与服务端约定格式)
                platform_interface.clientSocket.sendMsg(name,
                        new String("@friends request from ") + platform_interface.myInfo.getname());
                JOptionPane.showMessageDialog(null, " Request sent successfully!\n Please wait for your friends to "
                                + "confirm! ",
                        " Request sent successfully ", JOptionPane.INFORMATION_MESSAGE);
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        }
        // 场景4:用户不存在
        else {
            JOptionPane.showMessageDialog(null, " User " + name + " not found!\n Please check your input! ",
                    " Input Error ", JOptionPane.ERROR_MESSAGE);
        }
    }
});

约定统一的消息格式 :发送@friends request from + 用户名格式的消息,与服务端约定统一的通信协议,便于服务端解析并转发好友申请,是跨客户端交互的核心保障;

  1. 复用全局客户端实例 :调用platform_interface.clientSocket.sendMsg()发送申请,复用已建立的Socket连接,无需重新创建连接,提升通信效率,同时保障连接的统一性;

好友请求窗口(friendsRequest)------ 待处理申请列表呈现

friendsRequest 类是独立的好友请求窗口,负责加载并展示所有待处理的好友申请,是用户处理好友关系的核心界面。

java 复制代码
public class friendsRequest extends JFrame {

	private static final long serialVersionUID = 1L;
	private JPanel contentPane;
	private JPanel scrollContent; // 滚动面板的内容面板

	/**
	 * Create the frame.
	 */
	public friendsRequest() {
		setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		setBounds(100, 100, 400, 360);
		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		contentPane.setLayout(new BorderLayout(0, 0));
		setContentPane(contentPane);

		// 1. 创建滚动面板,支持大量申请时滚动查看
		JScrollPane scrollPane = new JScrollPane();
		contentPane.add(scrollPane, BorderLayout.CENTER);

		// 2. 创建滚动面板内容面板,设置垂直流式布局
		scrollContent = new JPanel();
		scrollContent.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 5)); // 垂直排列,间距5
		// 3. 将内容面板绑定到滚动面板,解决组件无法滚动问题
		scrollPane.setViewportView(scrollContent);

		// 4. 遍历等待列表,加载所有待处理好友申请
		for(int i = 0; i < platform_interface.waitingList.size(); ++i) {
			// 获取申请人姓名
			String name = platform_interface.waitingList.get(i);

			// 5. 调用数据库工具类,查询申请人详细信息
			Map<String, Object> userInfo = data_base.queryUserByUsername(name);

			// 6. 提取头像编号,设置默认值避免空指针
			int pic = 0;
			if (!userInfo.isEmpty()) {
				pic = (Integer) userInfo.get("pic");
			}

			// 7. 创建请求项组件,添加到内容面板
			requestItem request = new requestItem(pic, name);
			scrollContent.add(request);
		}

		// 8. 刷新界面,确保组件正常显示
		scrollContent.revalidate();
		scrollContent.repaint();
	}
}

功能实现与设计亮点解析
界面刷新机制 :通过revalidate()repaint()刷新内容面板,确保申请项组件正常渲染,避免组件添加后无法显示的问题。

3、游戏娱乐功能模块

消消乐

消除判断(三消核心逻辑)

java 复制代码
//判断是否有3个以上的连续的相同方块按钮
//kinds[0]为1表示横向消除,10表示纵向消除 11 表示横纵都消除
private boolean isThreeLinked(int y, int x, int[] kinds) {
    int tmp;
    int linked = 1;
    kinds[0] = 0;
    // 横向连续判断
    if (x + 1 < matrixC) {
        tmp = x + 1;
        while (tmp < matrixC && pictures[y][x] == pictures[y][tmp]) {
            linked++;
            tmp++;
        }
    }
    if (x - 1 >= 0) {
        tmp = x - 1;
        while (tmp >= 0 && pictures[y][x] == pictures[y][tmp]) {
            linked++;
            tmp--;
        }
    }
    // 如果横向有超过三个连续块,标记为横向消除
    if (linked >= 3) {
        kinds[0] += 1;
    }
    
    // 纵向连续判断
    linked = 1;
    if (y + 1 < matrixR) {
        tmp = y + 1;
        while (tmp < matrixR && pictures[y][x] == pictures[tmp][x]) {
            linked++;
            tmp++;
        }
    }
    if (y - 1 >= 0) {
        tmp = y - 1;
        while (tmp >= 0 && pictures[y][x] == pictures[tmp][x]) {
            linked++;
            tmp--;
        }
    }
    // 如果纵向连续相同超过三个方块,标记为纵向消除
    if (linked >= 3) {
        kinds[0] += 10;
    }
    
    if (kinds[0] == 0)
        return false;
    return true;
}

图片交换与消除动画(游戏交互与视觉反馈)

图片交换逻辑(用户操作响应)

java 复制代码
//交换图片
protected void swapPictures(int x, int y) {
    if ((x >= minX && x <= maxX) && (y >= minY && y <= maxY)) {
        int cAbs, rAbs;
        cAbs = (x - minX) / PICTURE_WIDTH;
        rAbs = (y - minY) / PICTURE_HEIGHT;
        if (!isDoubleClicked) {
            // 第一次点击,记录选中图片坐标
            isDoubleClicked = true;
            cCur = cAbs;
            rCur = rAbs;
        } else {
            // 第二次点击,判断是否为相邻图片
            isDoubleClicked = false;
            if ((1 == Math.abs(rCur - rAbs) && cCur == cAbs)
                || (1 == Math.abs(cCur - cAbs) && rCur == rAbs)) {
                // 记录交换前后坐标,触发交换动画
                sr = rCur;
                sc = cCur;
                dr = rAbs;
                dc = cAbs;
                isExchanging = true;
                isAnimation = true;
                animation.start();
            }
        }
    }
}

爆炸消除动画(视觉反馈)

java 复制代码
private void callAnimation() {
    for (int i = 0; i < matrixR; i++)
        for (int j = 0; j < matrixC; j++)
            if (pictures[i][j] == EMPTY)
                explosion[i][j] = true;
    isExplosion = true;
    isAnimation = true;
    animation.start();
}

class Animation implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        if (isExplosion) {
            explosionCur++;
            if (explosionCur >= explosionCnt) {
                // 动画播放完毕,重置状态
                explosionCur = 0;
                for (int i = 0; i < matrixR; i++)
                    for (int j = 0; j < matrixC; j++)
                        explosion[i][j] = false;
                isExplosion = false;
                animation.stop();
            }
            repaint(); // 重绘界面,呈现动画帧
        }
    }
}

图片下落与补位(消除后流程衔接)

该方法实现消除后上方图片下落、空位置补新图的逻辑,是衔接单次消除与后续游戏流程的关键。

java 复制代码
//图片下降
private void downPictures() {
    int tmp;
    for (int i = matrixR - 1; i >= 0; i--) {
        for (int j = 0; j < matrixC; j++) {
            if (pictures[i][j] == EMPTY) {
                // 遍历上方图片,寻找非空图片下落补位
                for (int k = i - 1; k >= 0; k--) {
                    if (pictures[k][j] != EMPTY) {
                        tmp = pictures[k][j];
                        pictures[k][j] = pictures[i][j];
                        pictures[i][j] = tmp;
                        break;
                    }
                }
            }
        }
    }
    // 为空位置补充新图片
    initPictureMatrix();
    isFalling = false;
    repaint();
    // 检测连锁反应,触发连续消除
    globalSearch(2);
}

推箱子

多关卡地图工厂(地图数据管理)

java 复制代码
static class MapFactory
{
     static byte sampleMap[][][] = {
        {
            { 0, 0, 1, 1, 1, 0, 0, 0 },
            { 0, 0, 1, 4, 1, 0, 0, 0 },
            { 0, 0, 1, 9, 1 ,1 ,1, 1 },
            // 省略部分地图数据...
        },
        {
            { 1, 1, 1, 1, 1, 0, 0, 0, 0 },
            { 1, 9, 9, 5, 1, 0, 0, 0, 0 },
            // 省略部分地图数据...
        },
        {
            { 0, 0, 1, 1, 1, 1, 0, 0 },
            { 0, 0, 1, 9, 9, 1, 1, 1 },
            // 省略部分地图数据...
        }
     };
     static int count = sampleMap.length;
     public static byte[][] getMap(int grade)
     {
         byte temp[][];
         if (grade >= 0 && grade < count)
             temp = sampleMap[grade];
         else //默认选第一关
             temp = sampleMap[0];
         int row = temp.length;
         int column = temp[0].length;
         byte[][] result = new byte[row][column];
         for (int i = 0; i < row; i++)
             for (int j = 0; j < column; j++)
                 result[i][j] = temp[i][j];
         return result;
     }
     public static int getCount()
     {
         return count;
     }
}

人物与箱子移动逻辑(游戏核心交互)

moveUp()方法为例,拆解人物移动与箱子推动的核心逻辑,这是推箱子游戏的核心交互实现,直接决定游戏的可玩性与逻辑严谨性。

java 复制代码
private void moveUp()
{
    // 前方是墙壁,无法移动
    if (map[row - 1][column] == WALL)
        return;
    // 前方是BOX或BOXONEND(箱子在目标点上)
    if (map[row - 1][column] == BOX || map[row - 1][column] == BOXONEND)
    {
        // 检查箱子前方是否可移动(目标点或草地)
        if (map[row - 2][column] == END || map[row - 2][column] == GRASS)
        {
            // 保存当前地图状态,用于悔棋
            Map currMap = new Map(row, column, map);
            list.add(currMap);
            // 判断箱子移动后的状态(是否在目标点上)
            byte boxTemp = map[row - 2][column] == END? BOXONEND : BOX;
            // 判断人物移动后的状态(是否在目标点上)
            byte manTemp = map[row - 1][column] == BOX? MANUP : MANUPONEND;
            
            // 执行移动:箱子前移两步,人物前移一步
            map[row - 2][column] = boxTemp;
            map[row - 1][column] = manTemp;
            // 恢复人物原位置的状态(草地或目标点)
            map[row][column] = grassOrEnd(map[row][column]);
            // 更新人物坐标
            row--;
        }
    }
    // 前方是GRASS(草地)或END(目标点),仅人物移动
    else
    {
        if (map[row - 1][column] == GRASS || map[row - 1][column] == END)
        {
            // 保存当前地图状态,用于悔棋
            Map currMap = new Map(row, column, map);
            list.add(currMap);
            // 判断人物移动后的状态
            byte temp = map[row - 1][column] == END? MANUPONEND : MANUP;
            map[row - 1][column] = temp;
            // 恢复人物原位置的状态
            map[row][column] = grassOrEnd(map[row][column]);
            row--;
        }
    }
}

悔棋功能

java 复制代码
public void undo()
{
    if (acceptKey)
    {
        if (list.size() > 0)
        {
            // 获取最后一次保存的地图状态
            Map priorMap = (Map) list.get(list.size() - 1);
            // 恢复地图数据与人物坐标
            map = priorMap.getMap();
            row = priorMap.getManX();
            column = priorMap.getManY();
            // 重绘界面,呈现悔棋后的状态
            repaint();
            // 移除已撤销的历史状态,避免重复悔棋
            list.remove(list.size() - 1);
        }
        else
        {
            DisplayToast("不能再撤销了!");
        }
    }
    else
    {
        DisplayToast("此关已完成,不能撤销!");
    }
}

// 鼠标右键触发悔棋
@Override
public void mousePressed(MouseEvent e)
{
    if (e.getButton() == MouseEvent.BUTTON3)
    {
        undo();
    }
}
  1. 基于历史记录的恢复 :通过ArrayList存储每次有效移动的地图状态,采用"先进后出"的栈式结构,实现最近操作优先撤销,符合用户的操作习惯;
  2. 状态锁控制 :通过acceptKey标识控制

4、聊天通讯功能模块

聊天消息发送逻辑

java 复制代码
send_msg_1.addKeyListener(new KeyAdapter() {
    @Override
    public void keyPressed(KeyEvent e) {
        // 点击Ctrl键发送消息(注:代码中注释写"回车",实际是Ctrl键)
        if (e.getKeyCode() == KeyEvent.VK_CONTROL) {
            // 检查是否选择了有效好友
            if (fList.isEmpty() || friend_current < 0 || friend_current >= fList.size()) {
                JOptionPane.showMessageDialog(null, "Please select a friend to chat with!", "No Friend Selected", JOptionPane.WARNING_MESSAGE);
                return;
            }
            String msg = send_msg_1.getText();
            // 本地聊天记录存储
            fList.get(friend_current).msgList.add(msg);
            fList.get(friend_current).usrList.add(1); // 标记为自己发送的消息
            try {
                // 将消息写入本地聊天记录文件
                fileList.get(friend_current).write(new String(1 + ":" + msg + "\r\n").getBytes());
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            // 界面添加消息气泡
            addTextMessage(msg, 1, myInfo.getpic(), fList.get(friend_current).msgList.size());
            send_msg_1.setText(""); // 清空输入框
            // 通过客户端发送消息给服务端(转发给好友)
            clientSocket.sendMsg(fList.get(friend_current).getname(), msg);
        }
    }
});
  1. 合法性校验:检查是否选择了好友,避免无目标时发送消息;
  2. 本地数据存储 :把消息存到当前好友的聊天记录列表(msgList),并用usrList标记消息来源(1代表自己发送);
  3. 本地文件持久化 :将消息写入本地聊天记录文件(fileList对应好友的日志文件),保障聊天记录不丢失;
  4. 界面与网络同步 :在聊天窗口添加消息气泡,清空输入框,同时调用clientSocket.sendMsg将消息发送给服务端,由服务端转发给目标好友。

5、游戏联机功能


在线对战(联机)初始化逻辑

java 复制代码
synchronized public void onlinePK() {
    aiMode = false; // 关闭AI模式,切换为联机模式
    netMode = true; // 开启网络联机模式标识
    mapIndex = 2;   // 设置联机对战对应的地图
    playerName = new String(strRead);
    myTurn = Integer.valueOf(strRead); // 从服务端获取自己的回合编号
    strRead = null;
    
    // 若回合编号为-1,说明连接失败
    if (myTurn == -1) {
        JOptionPane.showMessageDialog(null, "连接失败");
        return;
    }
    
    playerName = enemyName; // 记录对手昵称
    messageGot = false;     // 重置消息接收标识
    loadMap();              // 加载联机地图
    showMenu = false;       // 隐藏游戏菜单
    reinit();               // 重新初始化游戏状态
}
  • synchronized修饰:保障多线程环境下(如服务端异步返回数据),该方法不会被并发执行,避免模式/参数混乱;

  • reinit():重置游戏核心状态,让联机双方从统一的初始状态开始对战,保障公平性。

消息发送机制(emitString() 方法)

联机对战的核心是"状态变更即发消息",当当前玩家执行关键操作(移动、使用道具、瞄准、发射)时,通过emitString()将操作信息发送给对手,让对手同步更新本地状态。

1. 方法核心实现

java 复制代码
public void emitString(String mode, String str)
{
    System.out.println(mode + "@" + playerName + "@" + str);
    String Str = mode + "@" + playerName + "@" + str;
    platform_interface.clientSocket.sendGameMsg(Str); // 调用底层通信接口发送消息
}
  • 消息格式设计:采用「模式@玩家名@内容」的分隔格式,便于对手解析时快速提取关键信息。
  • 底层依赖 :依赖platform_interface.clientSocketsendGameMsg()方法实现网络传输,该方法封装了TCP/UDP通信的细节,对外提供简单的字符串发送接口。

示例:发射炮弹时的消息发送

java 复制代码
public void fire()
{
    if (fireable) // 仅当可发射时执行
    {
        // 联机模式下,当前玩家回合发送三连消息(角度+力度+发射)
        if (netMode && turns == myTurn)
        {
            emitString("DDTANK", "A" + calAngle);
            emitString("DDTANK", "P" + power);
            emitString("DDTANK", "FIRE");
        }
        // 后续炮弹初始化逻辑...
    }
}

三连消息确保对手能完整同步炮弹发射的核心参数(角度、力度),从而还原一致的炮弹轨迹。

消息解析机制(parseMessage() 方法)

方法核心流程

java 复制代码
 // 角度消息解析
 else if (strRead.substring(0, 1).equals("A"))
 {
     calAngle = Double.valueOf(strRead.substring(1));
     System.out.println("angel: " + calAngle);
     messageGot = false;
 }
 // 力度消息解析
 else if (strRead.substring(0, 1).equals("P"))
 {
     power = Integer.valueOf(strRead.substring(1));
     System.out.println("power: " + power);
     messageGot = false;
 }
 // 发射消息解析
 else if (strRead.equals("FIRE"))
 {
     roles[turns].fire();
     firstPressed = false;
 }
  • 核心目的:同步对手的瞄准角度、炮弹力度,最终触发本地的炮弹发射方法,还原对手的炮弹飞行轨迹,保证双方看到的爆炸效果、伤害结算一致。

四、课程设计感想

  1. 通过 Java Socket 套接字编程实现客户端与服务端的双向通信,支撑用户登录状态校验、实时聊天等社交核心功能,解决跨主机的信息交互问题,实现小型桌面社交平台的网络互通能力。
  2. 新增多角色选择功能,配置不同角色的属性(血量、伤害、移动速度)差异化;基于数据库(MySQL)存储玩家对战战绩与胜率。
相关推荐
kylezhao20192 小时前
C# 文件的输入与输出(I/O)详解
java·算法·c#
千层冷面2 小时前
数据库分库分表
java·数据库·mysql·oracle
赵谨言2 小时前
Python串口的三相交流电机控制系统研究
大数据·开发语言·经验分享·python
民乐团扒谱机2 小时前
【微实验】数模美赛备赛:多目标优化求解实战(MATLAB实现,以流水车间调度为例)
开发语言·数学建模·matlab·甘特图·遗传算法·多目标优化·优化模型
努力的小陈^O^3 小时前
问题:Spring循环依赖问题排查与解决
java·开发语言·前端
Ccjf酷儿3 小时前
C++语言程序设计 (郑莉)第十章 泛型程序设计与C++标准模板库
开发语言·c++
HehuaTang3 小时前
requests 调大并对齐 limits 提升POD高负载场景下性能
java·docker·kubernetes
FreeBuf_3 小时前
利用零宽度字符的隐形JavaScript混淆工具InvisibleJS浮出水面
开发语言·javascript·ecmascript
SuperherRo3 小时前
JAVA攻防-Shiro专题&key利用链&CB1链分析&入口点&调用链&执行地&Class加载
java·shiro·反序列化·cb1链