Java-黄金矿工(二)

实现

1-2 创建服务端和客户端

Java-黄金矿工(一) https://www.cnblogs.com/xbxxx/p/18207095

3.游戏界面实现

主要使用自带的paint方法画出来的,利用repaint方法一直重绘刷新界面。

(1)新建GameFrame类(游戏界面)

继承JFrame。

public class GameFrame extends JFrame {}

(2)各小类

GameGrame类为游戏界面主类,以下类均会被在GameGrame类中调用,因其中引用变量互有穿插,可能会有些混乱,以下直接放入各个被调用类的源代码,源代码后会有简要注释。

①Bg.java(背景类)

主要把up.jpg和down.jpg放入界面中,另外加上剩余时间和每个矿工已获得的金钱。

点击查看代码
import javax.swing.*;
import java.awt.*;

public class Bg {//背景类
    Image bgUp = Toolkit.getDefaultToolkit().getImage("imgs/Up.jpg");//上方
    Image bgDown = Toolkit.getDefaultToolkit().getImage("imgs/down.jpg");//下方
    void paintSelf(Graphics g){
        g.drawImage(bgUp,0,30,null);//(0,30)在窗口里面的坐标
        g.drawImage(bgDown,0,151,null);//在窗口里面的坐标
        g.setColor(new Color(143,112,1));//设置字体颜色
        g.setFont(new Font("幼圆",Font.BOLD,20));//设置字体“幼圆”,加粗,字号20
        //下面是写金钱和倒计时的
        if (publicMeans.isServer){//服务端就直接写Miner.money的
            g.drawString("金钱:" + Miner.money1,275,60);
            g.drawString("金钱:" + Miner.money2,609,60);//495+114=609
            g.setFont(new Font("幼圆",Font.BOLD,20));//倒计时的字体
            g.drawString("时间:" + GameFrame.ss + "秒",800,60);//服务端就直接调用那个ss
        }
        else {//客户端就写publicMeans里面的
            g.drawString("金钱:" + publicMeans.money1,275,60);
            g.drawString("金钱:" + publicMeans.money2,609,60);//495+114=609
            g.setFont(new Font("幼圆",Font.BOLD,20));
            g.drawString("时间:" + publicMeans.timeSs + "秒",800,60);
        }
    }
}

②Miner.java(矿工类)

基本为矿工的属性,比如矿工的状态(状态1、状态2、状态3)和矿工的钱。

点击查看代码
import java.awt.*;

public class Miner {//矿工
    public static int money1 = 0,money2 = 0;//矿工1和2的钱
    public static int state1 = 1,state2 = 1;//矿工1和2的状态,3种状态
    private Image minerState1 = Toolkit.getDefaultToolkit().getImage("imgs/minerState1.jpg");
    private Image minerState2 = Toolkit.getDefaultToolkit().getImage("imgs/minerState2.jpg");
    private Image minerState3 = Toolkit.getDefaultToolkit().getImage("imgs/minerState3.gif");
    //状态1:普通状态,手在上,没有放线
    //状态2:放线状态,手在下,正在放线
    //状态3:是个动图,这个里面好像看不了,是抓住金块后,往上拉
    void paintSelf(Graphics g){
        if (publicMeans.isServer){
            if (state1 == 1){
                g.drawImage(minerState1,372,51,null);
            } else if (state1 == 2) {//矿工也变了
                g.drawImage(minerState2,372,51,null);
            } else if (state1 == 3) {
                g.drawImage(minerState3,372,51,null);
            }

            if (state2 == 1){
                g.drawImage(minerState1,495,51,null);
            } else if (state2 == 2) {
                g.drawImage(minerState2,495,51,null);
            } else if (state2 == 3) {
                g.drawImage(minerState3,495,51,null);
            }
        }
        else{
            if (publicMeans.state_miner1 == 1){
                g.drawImage(minerState1,372,51,null);
            } else if (publicMeans.state_miner1 == 2) {
                g.drawImage(minerState2,372,51,null);
            } else if (publicMeans.state_miner1 == 3) {
                g.drawImage(minerState3,372,51,null);
            }

            if (publicMeans.state_miner2 == 1){
                g.drawImage(minerState1,495,51,null);
            } else if (publicMeans.state_miner2 == 2) {
                g.drawImage(minerState2,495,51,null);
            } else if (publicMeans.state_miner2 == 3) {
                g.drawImage(minerState3,495,51,null);
            }
        }
    }
}

③Line.java(线类)

为矿工下放的线的基本属性,比如坐标、角度、方向、状态(摇摆、抓取、未抓到收回、抓取收回)等。

点击查看代码
import java.awt.*;

public class Line {//线
    GameFrame frame;
    Line(GameFrame frame){this.frame = frame;}

    //判断线是否触碰到金块,若触碰到,则线的状态为3(抓取收回),金块的状态为1/2(被矿工1或2抓住)
    //只有在金块的状态为0的情况下,金块才可以被抓取
    void logic(){
        for(Object obj:this.frame.objectList){//遍历金子
            if (obj.flag == 0){//如果线的末端坐标碰到金子了 此时线的状态为1(抓取状态)时才可以抓到金子
                if (endX1 > obj.x && endX1 < obj.x + obj.width && endY1 > obj.y && endY1 < obj.y + obj.height && Line.state1 == 1){
                    state1 = 3;//就改线的状态 抓取收回=3
                    obj.flag = 1;//改金子的状态 1:被矿工1抓住
                }
                if (endX2 > obj.x && endX2 < obj.x + obj.width && endY2 > obj.y && endY2 < obj.y + obj.height && Line.state2 == 1){
                    state2 = 3;
                    obj.flag = 2;//这里是矿工2抓住
                }
            }
        }
    }

    public static int x1 = 410,y1 = 117,endX1,endY1;//矿工1的起点坐标和终点坐标
    public static int x2 = 530,y2 = 117,endX2,endY2;//矿工2的起点坐标和终点坐标
    public static double length1 = 50, length2 = 50;//线长
    public static double angle1 = 0.9, angle2 = 0.1;//角度,与x轴的夹角
    public static int dir1 = -1, dir2 = 1;//方向:顺时针=1 逆时针=-1
    public static int state1 = 0, state2 = 0;//线的状态:摇摆=0 抓取=1 收回(未抓到)=2 抓取收回=3
    void paintSelf(Graphics g){
        if (publicMeans.isServer){
            logic();//看这个方法 一直重绘,一直重绘
            g.setColor(new Color(51,51,51));//线的颜色
            switch (state1){
                case 0://摇摆(线与x轴的角度发生变化),此时就是状态0
                    if (angle1 < 0.1) { dir1 = 1; } else if (angle1 > 0.9) { dir1 = -1; }
                    angle1 += 0.005 * dir1;//角度每次都变化一点点,因为画面一直在重绘,就是repaint
                    lines1(g);//看这个,这会就是画好线了,只要是线的状态是0,就一直在摇摆
                    break;
                case 1://抓取(线变长),如果线的末端坐标在窗体内,则继续延长  状态1,去抓取,线延长  现在就去抓取了
                    endX1 = (int)(x1 + length1 * Math.cos(angle1 * Math.PI));
                    endY1 = (int)(y1 + length1 * Math.sin(angle1 * Math.PI));
                    if (endX1>0 && endX1<985 && endY1<719){//这里的985*719是窗口大小,如果末端坐标没有到达窗口边缘,就会一直延长
                        length1 += 10;
                        lines1(g);//画线
                    }
                    else{ state1 = 2; }//如果到达窗口边缘了,就收回,即状态2,没有抓到的收回线
                    break;
                case 2://收回(线变短) 没有抓到金块
                    Miner.state1 = 3;//此时矿工状态变为3,拉回的动态图
                    if (length1 > 50){//为什么>50,因为线的初始长度是50,线的状态为0时,线长一直是50在摇摆
                        length1 -= 10;//线长度减小
                        lines1(g);//画线
                    }
                    else{ state1 = 0; Miner.state1 = 1; }//如果到了50,就改变线的状态为0,摇摆状态,同时改变矿工的状态为1,普通状态
                    break;
                case 3://抓取收回(线变短,令金块的位置=线的末端坐标,这样可以使金块跟着线走)
                    //状态3,抓到金块了,线收回,被抓到的金块的坐标也跟着变化
                    if (length1 > 50){//一样,线长>50就一直往回收,去看金块类,回到线这里
                        for(Object obj:this.frame.objectList){//遍历刚才存储的金块集合
                            if (obj.flag == 1){//金块状态是1,代表金块被矿工1抓住
                                Miner.state1 = 3;//矿工1要往上拉,矿工1的状态变成3,动图的那个
                                if(obj.width == 133){//如果金块的宽是133,代表是大金子
                                    obj.x = endX1 - 66;//就把大金子的坐标放在线的末端,为什么要-66,线的末端要拉住金子的中间部分
                                    obj.y = endY1;
                                    length1 -= 0.5;//大金子比较重,所以线收的比较慢,线的长度也要减小的小一点
                                    lines1(g);//画线
                                }
                                else if(obj.width == 72){
                                    obj.x = endX1 - 36;
                                    obj.y = endY1;
                                    length1 -= 1;//这里是中金子,线回收的比大金子快
                                    lines1(g);//画线
                                }
                                else if (obj.width == 24) {
                                    obj.x = endX1 - 12;
                                    obj.y = endY1;
                                    length1 -= 1.2;//这里是小金子,线回收的比中金子快
                                    lines1(g);//画线
                                }
                                //如果length小于初始长度了,则把金块扔出去,即更改金块的坐标为画面外
                                //同时修改金块的状态为3(金块抓取完成被扔出)
                                //同时修改线的状态0(摇摆)
                                if(length1 <= 50){//回收的过程中,检测到length<50了,
                                    obj.x = -150;
                                    obj.y = -150;//金子坐标放在画面外
                                    obj.flag = 3;//金子状态改为3=抓取完成被扔出
                                    state1 = 0;//线的状态为0,摇摆
                                    Miner.state1 = 1;//矿工1的状态变为1,普通状态
                                    Miner.money1 += obj.money;//把钱添加到矿工1的钱里面
                                }
                            }
                        }
                    }
                    break;
            }
            switch (state2){//这里是线2 基本和线1一样
                case 0:
                    if (angle2 < 0.1) { dir2 = 1; } else if (angle2 > 0.9) { dir2 = -1; }
                    angle2 += 0.005 * dir2;
                    lines2(g);
                    break;
                case 1:
                    endX2 = (int)(x2 + length2 * Math.cos(angle2 * Math.PI));
                    endY2 = (int)(y2 + length2 * Math.sin(angle2 * Math.PI));
                    if (endX2>0 && endX2<985 && endY2<719){
                        length2 += 10;
                        lines2(g);
                    }
                    else{ state2 = 2; }
                    break;
                case 2:
                    Miner.state2 = 3;
                    if (length2 > 50){
                        length2 -= 10;
                        lines2(g);
                    }
                    else{ state2 = 0; Miner.state2 = 1; }
                    break;
                case 3:
                    if (length2 > 50){
                        Miner.state2 = 3;
                        for(Object obj:this.frame.objectList){
                            if (obj.flag == 2){
                                if (obj.width == 133){
                                    obj.x = endX2 - 66;
                                    obj.y = endY2;
                                    length2 -= 0.5;
                                    lines2(g);
                                }
                                else if(obj.width == 72){
                                    obj.x = endX2 - 36;
                                    obj.y = endY2;
                                    length2 -= 1;
                                    lines2(g);
                                }
                                else if(obj.width == 24){
                                    obj.x = endX2 - 12;
                                    obj.y = endY2;
                                    length2 -= 0.8;
                                    lines2(g);
                                }
                                if (length2 <= 50){
                                    obj.x = -150;
                                    obj.y = -150;
                                    obj.flag = 3;
                                    state2 = 0;
                                    Miner.state2 = 1;
                                    Miner.money2 += obj.money;
                                }
                            }
                        }
                    }
                    break;
            }
        }
        else{
            g.drawLine(x1,y1,publicMeans.endX1,publicMeans.endY1);
            g.drawLine(x1-1,y1,publicMeans.endX1-1,publicMeans.endY1);
            g.drawImage(hook,(int)(x1+publicMeans.length1*Math.cos(publicMeans.angle1*Math.PI)-10),
                    (int)(y1+publicMeans.length1*Math.sin(publicMeans.angle1*Math.PI)-10),null);
            g.drawLine(x2,y2,publicMeans.endX2,publicMeans.endY2);
            g.drawLine(x2-1,y2,publicMeans.endX2-1,publicMeans.endY2);
            g.drawImage(hook,(int)(x2+publicMeans.length2*Math.cos(publicMeans.angle2*Math.PI)-10),
                    (int)(y2+publicMeans.length2*Math.sin(publicMeans.angle2*Math.PI)-10),null);
        }
    }

    private Image hook = Toolkit.getDefaultToolkit().getImage("imgs/hookBall.png");
    void lines1(Graphics g){//获取末端坐标,矿工1的线
        endX1 = (int)(x1 + length1 * Math.cos(angle1 * Math.PI));
        endY1 = (int)(y1 + length1 * Math.sin(angle1 * Math.PI));//获取到了线的末端坐标,下面就是画线了
        //两句g.drawLine为了加粗线
        g.drawLine(x1,y1,endX1,endY1);
        g.drawLine(x1-1,y1,endX1-1,endY1);
        //画粉色小球,小球大小是20*20
        g.drawImage(hook,(int)(x1+length1*Math.cos(angle1*Math.PI)-10),(int)(y1+length1*Math.sin(angle1*Math.PI)-10),null);
    }

    void lines2(Graphics g){//画线2的,就是矿工2的线
        endX2 = (int)(x2 + length2 * Math.cos(angle2 * Math.PI));
        endY2 = (int)(y2 + length2 * Math.sin(angle2 * Math.PI));
        g.drawLine(x2,y2,endX2,endY2);
        g.drawLine(x2-1,y2,endX2-1,endY2);
        g.drawImage(hook,(int)(x2+length2*Math.cos(angle2*Math.PI)-10),(int)(y2+length2*Math.sin(angle2*Math.PI)-10),null);
    }
}

④Hook.java(钩子类)

点击查看代码
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

public class Hook {
    private BufferedImage hook1,hook2;
    {
        try {
            hook1 = ImageIO.read(new File("imgs\\hook.png"));
            hook2 = ImageIO.read(new File("imgs\\hook.png"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    int w = hook1.getWidth();
    int h = hook1.getHeight();
    public void paintSelf1(Graphics g)
    {
        hookMain(g,hook1,Line.endX1,Line.endY1,Line.angle1);
    }
    public void paintSelf2(Graphics g){
        hookMain(g,hook2,Line.endX2,Line.endY2,Line.angle2);
    }


    private void hookMain(Graphics g,BufferedImage hook,int x,int y,double angle){
        Graphics2D g2 = (Graphics2D)g;
        g2.translate(x - 22,y);
        g2.rotate(angle,w >> 1,h >> 1);
        g2.drawImage(hook,0,0,w,h,0,0,w,h,null);
        g2.translate(-x + 22,-y);
    }
}

⑤Object.java(金块)

点击查看代码
import java.awt.*;

public class Object {//这里是新建一个类,代表金块,下面是金子的属性,坐标,尺寸,图片,价值就是钱,还有flag标志
    public int x,y;//坐标
    public int width,height;//尺寸
    public Image img;//图片
    public int money = 0;
    public int flag;//0=能移动 1=被矿工1抓住 2=被矿工2抓住 3=抓取完成被扔出
    void paintSelf(Graphics g){
        g.drawImage(img,x,y,null);
    }
    public Rectangle getRec(){
        return new Rectangle(x,y,width,height);
    }
    public static Image bigGold = Toolkit.getDefaultToolkit().getImage("imgs/bigGold.png");
    public static Image middleGold = Toolkit.getDefaultToolkit().getImage("imgs/middleGold.png");
    public static Image smallGold = Toolkit.getDefaultToolkit().getImage("imgs/smallGold.png");
}

class BigGold extends Object{//大金块继承上面的金块类

    BigGold(){//Math.random()是[0,1]的随机数
        //金块位置:地下的下半部分x∈[0,852],y∈[420,598]
        //横坐标 852=985-133:985是窗口宽度,133是金块宽度,坐标是指左上角坐标,所以金块坐标不能超过985-133
        //如果超过了,就会超出画面
        //纵坐标同理
        int a = (int)(Math.random()*852);
        int b = (int)(Math.random()*178+420);//纵坐标 719-121=598 598/2=299 719-299=420 598-420=178
        int c = 133;//宽度
        int d = 121;//高度

        this.x = a; this.y = b; this.width = c; this.height = d;
        this.img = Toolkit.getDefaultToolkit().getImage("imgs/bigGold.png");
        this.flag = 0;//初始为0,代表金块可以被抓取
        this.money = 120;//大金子价值120元
    }
}
//下面的中等金块和小金子同理
class MiddleGold extends Object{

    MiddleGold(){
        //金块位置:地下的下半部分x∈[0,913],y∈[151,653]
        int a = (int)(Math.random()*913);//横坐标 985-72=913
        int b = (int)(Math.random()*477+176);//纵坐标 719-66=653 653-176=477
        int c = 72;//宽度
        int d = 66;//高度

        this.x = a; this.y = b; this.width = c; this.height = d;
        this.img = Toolkit.getDefaultToolkit().getImage("imgs/middleGold.png");
        this.flag = 0;
        this.money = 60;//中金子60元
    }
}

class SmallGold extends Object{

    SmallGold(){
        //金块位置:地下的下半部分x∈[0,961],y∈[151,697]
        int a = (int)(Math.random()*961);//横坐标 985-24=961
        int b = (int)(Math.random()*521+176);//纵坐标 719-22=697 697-176=521
        int c = 24;//宽度
        int d = 22;//高度

        this.x = a; this.y = b; this.width = c; this.height = d;
        this.img = Toolkit.getDefaultToolkit().getImage("imgs/smallGold.png");
        this.flag = 0;
        this.money = 20;//小金子20元
    }
}

(3)在GameFrame中调用各小类

①创建变量

    Bg bg = new Bg();//背景类
    Miner miner = new Miner();//矿工
    Line line = new Line(this);//线,这个东西比较多 end
    Image canvasImage;
    BigGold bigGold;//大金块
    MiddleGold middleGold;//中等金块
    SmallGold smallGold;//小金子
    List<Object> objectList = new ArrayList<>();//存储金块、石块

②创建金块

点击查看代码
    private void CreateGold()//这个是把创建金子放在了方法里面
    {//新建list集合,放置金子
        //这里我随便设的,大金子最多5个,中金子最多8个,小金子最多10个
        for (int i = 0; i < 5; i++){
            bigGold = new BigGold();//新建大金子
            for(Object obj:objectList){//需要遍历集合里的所有金块
                if (bigGold.getRec().intersects(obj.getRec())){//这个是判断两个金块有没有重叠
                    isOverlap = true;//如果有重叠了,就isOverlap=true
                    break;//跳出
                }
            }
            //遍历完成后,没有发现重叠,就把金子放到集合里面
            if (!isOverlap){objectList.add(bigGold);}
            else{isOverlap = false;}//重置这个isOverlap,因为下面的中金子和小金子还要用它
        }
        for(int i = 0; i < 8; i++){
            middleGold = new MiddleGold();
            for(Object obj:objectList){
                if (middleGold.getRec().intersects(obj.getRec())){
                    isOverlap = true;
                    break;
                }
            }
            if (!isOverlap) {objectList.add(middleGold);}
            else{isOverlap = false;}
        }
        for(int i = 0; i < 10; i++){
            smallGold = new SmallGold();
            for(Object obj:objectList){
                if (smallGold.getRec().intersects(obj.getRec())){
                    isOverlap = true;
                    break;
                }
            }
            if (!isOverlap) {objectList.add(smallGold);}
            else{isOverlap = false;}
        }
        //这里的中金子和小金子一样,和大金子一样
    }

③绘制画面

    @Override
    public void paint(Graphics g) {//绘制画面  刚才的repaint是一直走的这个paint方法
        canvasImage = this.createImage(985,719);//这里是新建一个画布,把所有的东西放在上面
        Graphics gCanvasImage = canvasImage.getGraphics();
        bg.paintSelf(gCanvasImage);//把背景放在上面
        miner.paintSelf(gCanvasImage);//矿工
        line.paintSelf(gCanvasImage);//线
        //金子 这里的金子绘制,服务端就直接用objectList
        if (publicMeans.isServer){
            for (Object obj:objectList){
                obj.paintSelf(gCanvasImage);
            }
        }
        else {//客户端就使用publicMeans.gold
            for (Object obj:publicMeans.gold){
                obj.paintSelf(gCanvasImage);
            }
        }

        g.drawImage(canvasImage,0,0,null);//把画布画出来
    }

④创建传递变量

点击查看代码
    //第1套
    public static int state_miner1 = 0, state_miner2 = 0;
    public static int state_line1 = 0, endX1 = 0, endY1 = 0, dir1 = -1;
    public static int state_line2 = 0, endX2 = 0, endY2 = 0, dir2 = 1;
    public static double angle1 = 0.9, length1 = 50;
    public static double angle2 = 0.1, length2 = 50;
    public static List<Object> gold = new ArrayList<>();
    public static int money1 = 0, money2 = 0;
    public static int timeSs = 0;
    //第2套
    public static int Last_state_miner1 = Integer.MAX_VALUE, Last_state_miner2 = Integer.MAX_VALUE;
    public static int Last_state_line1 = Integer.MAX_VALUE, Last_endX1 = Integer.MAX_VALUE, Last_endY1 = Integer.MAX_VALUE, Last_dir1 = Integer.MAX_VALUE;
    public static int Last_state_line2 = Integer.MAX_VALUE, Last_endX2 = Integer.MAX_VALUE, Last_endY2 = Integer.MAX_VALUE, Last_dir2 = Integer.MAX_VALUE;
    public static double Last_angle1 = Double.MAX_VALUE, Last_length1 = Double.MAX_VALUE;
    public static double Last_angle2 = Double.MAX_VALUE, Last_length2 = Double.MAX_VALUE;
    public static List<Object> Last_gold = new ArrayList<>();
    public static int Last_money1 = Integer.MAX_VALUE, Last_money2 = Integer.MAX_VALUE;
    public static int Last_timeSs = Integer.MAX_VALUE;//这里把last的初始值设置为int的最大值
    //因为在第一次进入界面时,肯定要把所有的数据发送给客户端一遍,然后再发送变化的值
    private void UpdateThis(){
        //是把当前的所有状态(矿工、线、钱和金子)都更新到第一套变量里面 这一次的数据
        state_miner1 = Miner.state1; state_miner2 = Miner.state2;
        state_line1 = Line.state1; endX1 = Line.endX1; endY1 = Line.endY1; dir1 = Line.dir1;
        state_line2 = Line.state2; endX2 = Line.endX2; endY2 = Line.endY2; dir2 = Line.dir2;
        angle1 = Line.angle1; length1 = Line.length1;
        angle2 = Line.angle2; length2 = Line.length2;
        money1 = Miner.money1; money2 = Miner.money2;
        timeSs = ss;
        gold.clear();
        for (Object obj:objectList){gold.add(obj);}
    }
    private void UpdateLast(){
        //这里是把第一套更新到第二套里面 Last是指上一次的数据
        Last_state_miner1 = state_miner1; Last_state_miner2 = state_miner2;
        Last_state_line1 = state_line1; Last_endX1 = endX1; Last_endY1 = endY1; Last_dir1 = dir1;
        Last_state_line2 = state_line2; Last_endX2 = endX2; Last_endY2 = endY2; Last_dir2 = dir2;
        Last_angle1 = angle1; Last_length1 = length1;
        Last_angle2 = angle2; Last_length2 = length2;
        Last_money1 = money1; Last_money2 = money2;
        Last_timeSs = timeSs;
        Last_gold.clear();
        for (Object obj1:gold){Last_gold.add(obj1);}
    }
    //刷新数据,如果不刷新数据,再次点击开始游戏时,画面会出现问题
    public void IniData(){
        Miner.state1 = 1;Miner.state2 = 1;Miner.money1 = 0;Miner.money2 = 0;
        objectList.clear();
        Line.state1 = 0;Line.length1 = 50;Line.angle1 = 0.9;Line.dir1 = -1;
        Line.state2 = 0;Line.length2 = 50;Line.angle2 = 0.1;Line.dir2 = 1;

        gold.clear();

        Last_state_miner1 = Integer.MAX_VALUE; Last_state_miner2 = Integer.MAX_VALUE;
        Last_state_line1 = Integer.MAX_VALUE; Last_endX1 = Integer.MAX_VALUE; Last_endY1 = Integer.MAX_VALUE; Last_dir1 = Integer.MAX_VALUE;
        Last_state_line2 = Integer.MAX_VALUE; Last_endX2 = Integer.MAX_VALUE; Last_endY2 = Integer.MAX_VALUE; Last_dir2 = Integer.MAX_VALUE;
        Last_angle1 = Double.MAX_VALUE; Last_length1 = Double.MAX_VALUE;
        Last_angle2 = Double.MAX_VALUE; Last_length2 = Double.MAX_VALUE;
        Last_gold.clear();
        Last_money1 = Integer.MAX_VALUE; Last_money2 = Integer.MAX_VALUE;
        Last_timeSs = Integer.MAX_VALUE;

        publicMeans.gold.clear();
        publicMeans.timeSs = -1;

        time = 60;
        isFirst = true;
        num = 0;
    }

⑤创建定时器

    //现在判断游戏是否结束 :1.倒计时结束 2.金子被抓完
    public static int ss;
    private static Timer timer = null;//timer是定时器
    private static void timer(){
        timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                time--;
                ss = time % 60;//ss就是剩余时间
                if (ss == 0){
                    //如果ss=0了,说明倒计时结束,游戏结束 就是这里
                    Server.Send("游戏结束");
                    publicMeans.isEnd = true;
                    timer.cancel();//停止这个定时器
                }
            }
        },0,1000);//1000是指1000ms毫秒,也就是1秒执行1次
    }

⑥创建判断赢家的方法

    public static String winner = "null";
    public static void JudgeWinner(){
        //判断赢家
        if (publicMeans.isServer){
            //服务端就直接通过Miner.money判断
            if (Miner.money1 > Miner.money2){
                winner = "玩家1获胜。";
            } else if (Miner.money1 < Miner.money2) {
                winner = "玩家2获胜。";
            } else {
                winner = "平手。";
            }
            Server.textArea.append("游戏结束," + winner + "\r\n");//打印到服务端的文本框中
        }
        else {
            //客户端就需要判断publicMeans里面的变量
            if (publicMeans.money1 > publicMeans.money2){
                winner = "玩家1获胜。";
            } else if (publicMeans.money1 < publicMeans.money2) {
                winner = "玩家2获胜。";
            } else {
                winner = "平手。";
            }
            Client.textArea.append("游戏结束," + winner + "\r\n");//打印到客户端的文本框中
        }
    }

⑦创建launch()方法,用于画面显示

其中包括
按键的判断:按方向键的下“↓”,矿工就会放钩子
变量的传递:主要是服务端向客户端传递,客户端通过这些被传递的数据进行画面绘制

点击查看代码
    private boolean isFirst = true;
    //倒计时
    private static int time = 60;//秒
    private static int num = 0;//新建一个int变量,记录一下金子集合也就是objectList里面有几个被抓完扔出了
    void launch(){//publicMeans.isServer在一开始就已经判断过了
        this.setVisible(true);//窗口是否可见
        this.setBounds(publicMeans.GameWinX,publicMeans.GameWinY,publicMeans.GameWinW,publicMeans.GameWinH);
        //如果是服务端,就设置服务端的窗口标题为"黄金矿工联机版(server)"
        if (publicMeans.isServer){this.setTitle("黄金矿工联机版(server)");}
        //如果是客户端,就为"黄金矿工联机版(client)"
        else {this.setTitle("黄金矿工联机版(client)");}
        this.setResizable(false);//窗口不能更改大小
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);//关闭窗口的方法
        //EXIT_ON_CLOSE是关闭整个进程,DISPOSE_ON_CLOSE只关闭当前界面,DO_NOTHING_ON_CLOSE不能关闭
        //然后是按键,↓,e.getKeyCode() == 40,这里的40指的就是↓键
        addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                //首先,在服务端摁↓,直接,改变线的状态,0摇摆变为1放线,矿工状态1手在上变为2手在下
                //这里摁完后,状态改变了
                if (publicMeans.isServer && Line.state1 == 0 && e.getKeyCode() == 40) {Line.state1 = 1; Miner.state1 = 2;}
                //看客户端,在客户端摁↓,客户端向服务端发送"keys:40;"
                if (!publicMeans.isServer && publicMeans.state_line2 == 0 && e.getKeyCode() == 40) {
                    Client.Send("keys:" + 40 + ";");//这里是客户端,要根据publicMeans里线2的状态判断
                }
            }
        });

        if (publicMeans.isServer){timer();}
        //timer();//调用刚才写的那个方法
        CreateGold();//创造金子 因为把创造金子的代码放进这个方法里面了,所以需要调用执行

        String message = "";
        while (true){//先看这里,这里是个死循环,代表一直在重绘
            //在死循环里判断isEnd是否为true
            if (publicMeans.isEnd){
                //如果为true,则表示游戏结束
                publicMeans.isEnd = false;//重置一下这个变量,防止再次开始游戏时有bug
                //跳出之前需要判断一下赢家+刷新数据
                JudgeWinner();//判断赢家
                IniData();//刷新数据
                Close();//关闭界面
                break;//跳出循环
            }

            if (publicMeans.isServer){
                //这里是服务端  我这里写了两套变量
                //这一行,判断刚才那个变量是否为40,若是,就代表客户端摁了↓,改变矿工2的状态和线的状态,然后把这个变量置为0
                //因为如果不置为0,这个循环会一直进来,矿工2和线的状态一直是这样,所以要置为0
                if (publicMeans.clientKey == 40){Line.state2 = 1;Miner.state2 = 2;publicMeans.clientKey = 0;}
                repaint();//重绘
                UpdateThis();//更新第一套
                //下面的if就是判断第一套和第二套有什么不一样
                //有不一样的就放进message里面,并且加上说明 比如上次是1,这次是2
                //比如第一个,矿工1的状态不一样了,就把 "miner1:2;" 这一段添加到message中
                //数据格式"miner1:0;miner2:0;" 注意这个分号
                if (state_miner1 != Last_state_miner1){message += "miner1:" + state_miner1 + ";";}
                if (state_miner2 != Last_state_miner2){message += "miner2:" + state_miner2 + ";";}
                if (state_line1 != Last_state_line1 || endX1 != Last_endX1 || endY1 != Last_endY1 || length1 != Last_length1 || dir1 != Last_dir1){
                    message += "line1:" + Line.state1 + " " + Line.endX1 + " " + Line.endY1 + " " + Line.angle1 + " " + Line.length1 + " " + Line.dir1 + ";";
                }
                if (state_line2 != Last_state_line2 || endX2 != Last_endX2 || endY2 != Last_endY2 || length2 != Last_length2 || dir2 != Last_dir2){
                    message += "line2:" + Line.state2 + " " + Line.endX2 + " " + Line.endY2 + " " + Line.angle2 + " " + Line.length2 + " " + Line.dir2 + ";";
                }
                if (money1 != Last_money1){message += "money1:" + money1 + ";";}
                if (money2 != Last_money2){message += "money2:" + money2 + ";";}
                //现在准备给客户端传值,传倒计时
                if (timeSs != Last_timeSs){message += "time:" + timeSs + ";";}//现在已经把倒计时添加到message里面了
                //看这里,这里用isFirst,是为了让if里面的语句走一次
                //因为首先要把objectList里面的金子都发给客户端,让客户端去画
                //然后不能每次都发送所有的,因为金子不是一直都在变化的,只有被抓取后金子才会改变
                if (isFirst){
                    for (Object obj:objectList){
                        message += "firstGold:" + obj.x + " " + obj.y + " " + obj.width + ";";
                    }
                    isFirst = false;
                }
                for (int i = 0; i < Last_gold.size(); i++){
                    //这里本来是应该走被注释掉的代码,但是还不知道为什么有bug,就是不会进入if语句
                    //现在也能正常运行
                    message += "gold:" + i + " " + gold.get(i).x + " " + gold.get(i).y + " " + gold.get(i).flag + ";";
//                    if (gold.get(i).x != Last_gold.get(i).x || gold.get(i).y != Last_gold.get(i).y || gold.get(i).flag != Last_gold.get(i).flag){
//                        message += "gold:" + i + " " + gold.get(i).x + " " + gold.get(i).y + " " + gold.get(i).flag + ";";
//                        System.out.println("gold:" + i + " " + gold.get(i).x + " " + gold.get(i).y + " " + gold.get(i).flag);
//                    }
                }
                //以上就是把改变的数据放在message中
                Server.Send(message);//这个就是刚才写的“发送”  这里一起发送 然后客户端去解析
                message = "";
                UpdateLast();//运行完1遍后更新第二套
                //为什么?因为要根据这两套变量判断有哪些数据发生变化了,如果发生变化了,就把它们放进一个字符串里,最后发送给客户端
                //客户端读取并解析这些变量,并展示到客户端的界面上
                //所以说服务端的数据和客户端的数据是两套数据
                //服务端需要一直把变化的数据发给客户端

                //这里是在一直循环的,我们可以在这里实时检测金子有没有被抓完
                //如果金子的flag=3,代表金子被抓完丢掉了
                if (!isFirst){
                    for (Object obj:objectList){
                        if (obj.flag == 3){num++;}
                    }
                    if (num == objectList.size()){
                        //如果num=集合里的金子数量,表示所有的金子都被抓住扔出了
                        Server.Send("游戏结束");//告诉客户端游戏结束了 去解析一下
                        publicMeans.isEnd = true;
                        timer.cancel();//关闭倒计时的那个定时器
                    }
                    num = 0;//重置一下num,要不num会一直加
                }
            }
            else {
                repaint();//重绘
            }

            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

(4)创建线程,用于打开游戏界面(OpenThread.java)

public class OpenThread extends Thread{//在准备好进入游戏界面时,开启这个线程
    public GameFrame gameFrame;
    @Override
    public void run() {
        gameFrame = new GameFrame();
        gameFrame.launch();
    }
}

4.在publicMeans.java中创建所需变量

import java.awt.*;
import java.awt.geom.AffineTransform;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

public class publicMeans {
    public static boolean isServer = false;//记录此程序是服务端还是客户端,服务端:true;客户端:false
    public static boolean isConn = false;//记录是否连接成功
    public static boolean isEnd = false;//表示游戏是否结束
    private static int screenW = (int)Toolkit.getDefaultToolkit().getScreenSize().width;
    private static int screenH = (int)Toolkit.getDefaultToolkit().getScreenSize().height;
//    private static GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
//    private static AffineTransform tx = gc.getDefaultTransform();
//    public static double uiScaleX = tx.getScaleX();
//    public static double uiScaleY = tx.getScaleY();

    public static int GameWinW = 985;
    public static int GameWinH = 719;

    public static int ChatW = 375;
    public static int ChatH = 719;

    //聊天窗口在左,游戏窗口在右
    public static int ChatX = screenW/2-(GameWinW+ChatW)/2;
    public static int ChatY = screenH/2-ChatH/2;
    public static int GameWinX = ChatX + ChatW;
    public static int GameWinY = ChatY;

    //游戏窗口在左,聊天窗口在右
//    public static int GameWinX = screenW/2-(GameWinW+ChatW)/2;
//    public static int GameWinY = screenH/2-GameWinH/2;
//    public static int ChatX = GameWinX + GameWinW;
//    public static int ChatY = GameWinY;

    public static String ip = null;
    public static int port = 0;
    //以下就是客户端所需要的数据,客户端根据以下数据进行画面重绘
    public static int state_miner1 = 0, state_miner2 = 0;
    public static int state_line1 = 0, endX1 = 0, endY1 = 0, dir1 = -1;
    public static int state_line2 = 0, endX2 = 0, endY2 = 0, dir2 = 1;
    public static double angle1 = 0.9, length1 = 50;
    public static double angle2 = 0.1, length2 = 50;
    //public static Object gold = new Object();
    public static List<Object> gold = new ArrayList<>();
    public static int money1 = 0, money2 = 0;
    public static int timeSs = -1;//这个用来记录倒计时,是客户端引用的
    public static int clientKey = 0;
}

5.分别在服务端和客户端中调用打开游戏界面的线程+消息解析

①在服务端和客户端双方连接成功后,“开始游戏”的按钮才会能被点击,服务端点击按钮后,会向客户端发送“开始游戏”,告诉客户端可以打开游戏界面一起游戏了;所以客户端需要在解析服务端消息时调用线程;
②客户端按“↓”时,需要告诉服务端我放钩子了,解析的时候需要注意。

服务端的“开始游戏”按钮(Server.java):

        btBegin.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {//点击按钮会执行这个里面
                if (publicMeans.isConn){//如果连接成功,点击开始游戏才可以进入游戏界面
                    Send("开始游戏");//给客户端发送”开始游戏“
                    Server.textArea.append("开始游戏。\r\n");
                    btBegin.setEnabled(false);//这里是开始游戏按钮不能点击,因为已经开始游戏了
                    OpenThread openThread = new OpenThread();
                    openThread.start();
                }
            }
        });

服务端解析(ServerThread.java):

    private static String[] message = null;
    public static void Analysis(){//解析客户端发来的信息
        message = info.split(";");
        for (int i = 0; i < message.length; i++){
            if (message[i].startsWith("keys:")){
                publicMeans.clientKey = 40;//然后把这个变量改变
            }
        }
    }
//这里是ServerThread.java中的run()里面的while(true)
while(true){//这里使用死循环,是因为要一直监视客户端有没有发过来信息
                while((info = br.readLine()) != null){//刚才的"keys:40;"就来这里了
                    //br.readLine()就是读取客户端发过来的信息,br.readLine()读取一行,然后使用while((info = br.readLine()) != null)
                    //读取发过来的所有信息
                    Analysis();//解析客户端发过来的信息
                }
            }

客户端解析(ClientThread.java):

    @Override
    public void run() {//线程开始,自动运行
        try{
            socket = new Socket(publicMeans.ip,publicMeans.port);//服务端的ip地址和端口号 这里的ip和port也就是刚才文本框里输入的
            publicMeans.isServer = false;//如果成功进入到这一步,则代表此窗口是客户端窗口
            publicMeans.isConn = true;//连接成功
            Client.textArea.append("连接成功。\r\n");//"\r\n"的意思是换行
            Client.btConn.setEnabled(false);//禁用客户端的连接按钮

            os = socket.getOutputStream();
            pw = new PrintWriter(os);

            is = socket.getInputStream();
            isr = new InputStreamReader(is);
            br = new BufferedReader(isr);//这里就是客户端接收的地方
            while(true){//这里使用死循环,是因为要一直监视服务端有没有发过来信息
                //刚才在服务端里面,开始游戏的时候服务端向客户端发送了“开始游戏”,这里解析
                while((info = br.readLine()) != null){
                    if (info.startsWith("开始游戏")){
                        Client.textArea.append("开始游戏。\r\n");
                        openThread = new OpenThread();
                        openThread.start();//这个OpenThread就是刚才新建的线程,当客户端收到开始游戏时,进入到游戏界面
                    } else if (info.startsWith("游戏结束")) {
                        publicMeans.isEnd = true;
                        System.out.println(3);
                        openThread.gameFrame.Close();//调用线程里的gameFrame,gameFrame是游戏界面,调用它的Close方法,关闭界面
                    } else {
                        Analysis();//解析服务端发过来的内容
                    }
                }
            }//跟服务端一个意思
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    
    private static String[] message = null;
    private static String[] messageTemp = null;
    private static Object obj;
    public static void Analysis(){//"miner1:0;miner2:0;"
        message = info.split(";");//首先以分号为间隔分组,放到message数组中 miner1:0 miner2:0
        for (int i = 0; i < message.length; i++){//然后循环message数组 此时message[0]=miner1:0 message[1]=miner2:0
            //进入判断
            //比如第一个if 判断message[0]是不是以"miner1"开头的,miner1:0,是,就进入,把publicMeans里的变量置为这个接收到的数据
            //因为接收的数据是string格式的,所以使用Integer.parseInt转为int格式
            if (message[i].startsWith("miner1:")) {publicMeans.state_miner1 = Integer.parseInt(message[i].substring(7));}//miner1:0
            else if (message[i].startsWith("miner2:")){publicMeans.state_miner2 = Integer.parseInt(message[i].substring(7));}
            else if (message[i].startsWith("line1:")){
                messageTemp = message[i].substring(6).split(" ");
                publicMeans.state_line1 = Integer.parseInt(messageTemp[0]);
                publicMeans.endX1 = Integer.parseInt(messageTemp[1]);
                publicMeans.endY1 = Integer.parseInt(messageTemp[2]);
                //这里需要的数据angle1是double格式的,所以使用Double.parseDouble把string转为double
                publicMeans.angle1 = Double.parseDouble(messageTemp[3]);
                publicMeans.length1 = Double.parseDouble(messageTemp[4]);
                publicMeans.dir1 = Integer.parseInt(messageTemp[5]);
            }
            else if (message[i].startsWith("line2:")){
                messageTemp = message[i].substring(6).split(" ");
                publicMeans.state_line2 = Integer.parseInt(messageTemp[0]);
                publicMeans.endX2 = Integer.parseInt(messageTemp[1]);
                publicMeans.endY2 = Integer.parseInt(messageTemp[2]);
                publicMeans.angle2 = Double.parseDouble(messageTemp[3]);
                publicMeans.length2 = Double.parseDouble(messageTemp[4]);
                publicMeans.dir2 = Integer.parseInt(messageTemp[5]);
            }
            else if (message[i].startsWith("money1:")){publicMeans.money1 = Integer.parseInt(message[i].substring(7));}
            else if (message[i].startsWith("money2:")){publicMeans.money2 = Integer.parseInt(message[i].substring(7));}
            else if (message[i].startsWith("gold:")){
                messageTemp = message[i].substring(5).split(" ");
                if (publicMeans.gold.size() == 0){continue;}
                publicMeans.gold.get(Integer.parseInt(messageTemp[0])).x = Integer.parseInt(messageTemp[1]);
                publicMeans.gold.get(Integer.parseInt(messageTemp[0])).y = Integer.parseInt(messageTemp[2]);
                publicMeans.gold.get(Integer.parseInt(messageTemp[0])).flag = Integer.parseInt(messageTemp[3]);
            }
            else if (message[i].startsWith("time:")){//传过来的是"time:8" 我们只需要把8赋值给publicMeans.timeSs
                publicMeans.timeSs = Integer.parseInt(message[i].substring(5));
                //这里的substring就是从第五位开始截取 第0位t 1=i 2=m 3=e 4=: 5=8 所以括号里面写5
            }
            else if (message[i].startsWith("firstGold:")){
                messageTemp = message[i].substring(10).split(" ");
                switch (messageTemp[2]){
                    case "133":
                        obj = new BigGold();
                        break;
                    case "72":
                        obj = new MiddleGold();
                        break;
                    case "24":
                        obj = new SmallGold();
                        break;
                    default:break;
                }
                obj.x = Integer.parseInt(messageTemp[0]);
                obj.y = Integer.parseInt(messageTemp[1]);
                publicMeans.gold.add(obj);
            }
            else {}
        }
    }

6.结束

posted @ 2024-05-22 21:36  小豹小小小  阅读(115)  评论(0编辑  收藏  举报