目錄
- 實現
- 1-2 建立服務端和客戶端
- 3.遊戲介面實現
- (1)新建GameFrame類(遊戲介面)
- (2)各小類
- ①Bg.java(背景類)
- ②Miner.java(礦工類)
- ③Line.java(線類)
- ④Hook.java(鉤子類)
- ⑤Object.java(金塊)
- (3)在GameFrame中呼叫各小類
- ①建立變數
- ②建立金塊
- ③繪製畫面
- ④建立傳遞變數
- ⑤建立定時器
- ⑥建立判斷贏家的方法
- ⑦建立launch()方法,用於畫面顯示
- (4)建立執行緒,用於開啟遊戲介面(OpenThread.java)
- 4.在publicMeans.java中建立所需變數
- 5.分別在服務端和客戶端中呼叫開啟遊戲介面的執行緒+訊息解析
- 6.結束
實現
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 {}
}
}