獲取源碼java
TankClient
類控制, 遊戲的運行和全部的圖形操做都包含在這個類中, 下面會介紹一些主要的方法.//類TankClient, 繼承自Frame類
//繼承Frame類後所重寫的兩個方法paint()和update()
//在paint()方法中設置在一張圖片中須要畫出什麼東西.
@Override
public void paint(Graphics g) {
//下面三行畫出遊戲窗口左上角的遊戲參數
g.drawString("missiles count:" + missiles.size(), 10, 50);
g.drawString("explodes count:" + explodes.size(), 10, 70);
g.drawString("tanks count:" + tanks.size(), 10, 90);
//檢測個人坦克是否被子彈打到, 並畫出子彈
for(int i = 0; i < missiles.size(); i++) {
Missile m = missiles.get(i);
if(m.hitTank(myTank)){
TankDeadMsg msg = new TankDeadMsg(myTank.id);
nc.send(msg);
MissileDeadMsg mmsg = new MissileDeadMsg(m.getTankId(), m.getId());
nc.send(mmsg);
}
m.draw(g);
}
//畫出爆炸
for(int i = 0; i < explodes.size(); i++) {
Explode e = explodes.get(i);
e.draw(g);
}
//畫出其餘坦克
for(int i = 0; i < tanks.size(); i++) {
Tank t = tanks.get(i);
t.draw(g);
}
//畫出個人坦克
myTank.draw(g);
}
/* * update()方法用於寫每幀更新時的邏輯. * 每一幀更新的時候, 咱們會把該幀的圖片畫到屏幕中. * 可是這樣作是有缺陷的, 由於把一副圖片畫到屏幕上會有延時, 遊戲顯示不夠流暢 * 因此這裏用到了一種緩衝技術. * 先把圖像畫到一塊幕布上, 每幀更新的時候直接把畫布推到窗口中顯示 */
@Override
public void update(Graphics g) {
if(offScreenImage == null) {
offScreenImage = this.createImage(800, 600);//建立一張畫布
}
Graphics gOffScreen = offScreenImage.getGraphics();
Color c = gOffScreen.getColor();
gOffScreen.setColor(Color.GREEN);
gOffScreen.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
gOffScreen.setColor(c);
paint(gOffScreen);//先在畫布上畫好
g.drawImage(offScreenImage, 0, 0, null);//直接把畫布推到窗口
}
//這是加載遊戲窗口的方法
public void launchFrame() {
this.setLocation(400, 300);//設置遊戲窗口相對於屏幕的位置
this.setSize(GAME_WIDTH, GAME_HEIGHT);//設置遊戲窗口的大小
this.setTitle("TankWar");//設置標題
this.addWindowListener(new WindowAdapter() {//爲窗口的關閉按鈕添加監聽
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
this.setResizable(false);//設置遊戲窗口的大小不可改變
this.setBackground(Color.GREEN);//設置背景顏色
this.addKeyListener(new KeyMonitor());//添加鍵盤監聽,
this.setVisible(true);//設置窗口可視化, 也就是顯示出來
new Thread(new PaintThread()).start();//開啓線程, 把圖片畫出到窗口中
dialog.setVisible(true);//顯示設置服務器IP, 端口號, 本身UDP端口號的對話窗口
}
//在窗口中畫出圖像的線程, 定義爲每50毫秒畫一次.
class PaintThread implements Runnable {
public void run() {
while(true) {
repaint();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
複製代碼
public int id;//做爲網絡中的標識
public static final int XSPEED = 5;//左右方向上每幀移動的距離
public static final int YSPEED = 5;//上下方向每幀移動的距離
public static final int WIDTH = 30;//坦克圖形的寬
public static final int HEIGHT = 30;//坦克圖形的高
private boolean good;//根據true和false把坦克分紅兩類, 遊戲中兩派對戰
private int x, y;//坦克的座標
private boolean live = true;//坦克是否活着, 死了將再也不畫出
private TankClient tc;//客戶端類的引用
private boolean bL, bU, bR, bD;//用於判斷鍵盤按下的方向
private Dir dir = Dir.STOP;//坦克的方向
private Dir ptDir = Dir.D;//炮筒的方向
複製代碼
public void draw(Graphics g) {
if(!live) {
if(!good) {
tc.getTanks().remove(this);//若是坦克死了就把它從容器中去除, 並直接結束
}
return;
}
//畫出坦克
Color c = g.getColor();
if(good) g.setColor(Color.RED);
else g.setColor(Color.BLUE);
g.fillOval(x, y, WIDTH, HEIGHT);
g.setColor(c);
//畫出炮筒
switch(ptDir) {
case L:
g.drawLine(x + WIDTH/2, y + HEIGHT/2, x, y + HEIGHT/2);
break;
case LU:
g.drawLine(x + WIDTH/2, y + HEIGHT/2, x, y);
break;
case U:
g.drawLine(x + WIDTH/2, y + HEIGHT/2, x + WIDTH/2, y);
break;
//...省略部分方向
}
move();//每次畫完改變坦克的座標, 連續畫的時候坦克就動起來了
}
複製代碼
private void move() {
switch(dir) {//根據坦克的方向改變座標
case L://左
x -= XSPEED;
break;
case LU://左上
x -= XSPEED;
y -= YSPEED;
break;
//...省略
}
if(dir != Dir.STOP) {
ptDir = dir;
}
//防止坦克走出遊戲窗口, 越界時要停住
if(x < 0) x = 0;
if(y < 30) y = 30;
if(x + WIDTH > TankClient.GAME_WIDTH) x = TankClient.GAME_WIDTH - WIDTH;
if(y + HEIGHT > TankClient.GAME_HEIGHT) y = TankClient.GAME_HEIGHT - HEIGHT;
}
複製代碼
public void keyPressed(KeyEvent e) {//接收接盤事件
int key = e.getKeyCode();
//根據鍵盤按下的按鍵修改bL, bU, bR, bD四個布爾值, 回後會根據四個布爾值判斷上, 左上, 左等八個方向
switch (key) {
case KeyEvent.VK_A://按下鍵盤A鍵, 意味着往左
bL = true;
break;
case KeyEvent.VK_W://按下鍵盤W鍵, 意味着往上
bU = true;
break;
case KeyEvent.VK_D:
bR = true;
break;
case KeyEvent.VK_S:
bD = true;
break;
}
locateDirection();//根據四個布爾值判斷八個方向的方法
}
private void locateDirection() {
Dir oldDir = this.dir;//記錄下原來的方法, 用於聯網
//根據四個方向的布爾值判斷八個更細分的方向
//好比左和下都是true, 證實玩家按的是左下, 方向就該爲左下
if(bL && !bU && !bR && !bD) dir = Dir.L;
else if(bL && bU && !bR && !bD) dir = Dir.LU;
else if(!bL && bU && !bR && !bD) dir = Dir.U;
else if(!bL && bU && bR && !bD) dir = Dir.RU;
else if(!bL && !bU && bR && !bD) dir = Dir.R;
else if(!bL && !bU && bR && bD) dir = Dir.RD;
else if(!bL && !bU && !bR && bD) dir = Dir.D;
else if(bL && !bU && !bR && bD) dir = Dir.LD;
else if(!bL && !bU && !bR && !bD) dir = Dir.STOP;
//能夠先跳過這段代碼, 用於網絡中其餘客戶端的坦克移動
if(dir != oldDir){
TankMoveMsg msg = new TankMoveMsg(id, x, y, dir, ptDir);
tc.getNc().send(msg);
}
}
//對鍵盤釋放的監聽
public void keyReleased(KeyEvent e) {
int key = e.getKeyCode();
switch (key) {
case KeyEvent.VK_J://設定J鍵開火, 當釋放J鍵時發出一發子彈
fire();
break;
case KeyEvent.VK_A:
bL = false;
break;
case KeyEvent.VK_W:
bU = false;
break;
case KeyEvent.VK_D:
bR = false;
break;
case KeyEvent.VK_S:
bD = false;
break;
}
locateDirection();
}
複製代碼
private Missile fire() {
if(!live) return null;//若是坦克死了就不能開火
int x = this.x + WIDTH/2 - Missile.WIDTH/2;//設定子彈的x座標
int y = this.y + HEIGHT/2 - Missile.HEIGHT/2;//設定子彈的y座標
Missile m = new Missile(id, x, y, this.good, this.ptDir, this.tc);//建立一顆子彈
tc.getMissiles().add(m);//把子彈添加到容器中.
//網絡部分可暫時跳過, 發出一發子彈後要發送給服務器並轉發給其餘客戶端.
MissileNewMsg msg = new MissileNewMsg(m);
tc.getNc().send(msg);
return m;
}
複製代碼
public static final int XSPEED = 10;//子彈每幀中座標改變的大小, 比坦克大些, 子彈固然要飛快點嘛
public static final int YSPEED = 10;
public static final int WIDTH = 10;
public static final int HEIGHT = 10;
private static int ID = 10;
private int id;//用於在網絡中標識的id
private TankClient tc;//客戶端的引用
private int tankId;//代表是哪一個坦克發出的
private int x, y;//子彈的座標
private Dir dir = Dir.R;//子彈的方向
private boolean live = true;//子彈是否存活
private boolean good;//子彈所屬陣營, 我方坦克自能被地方坦克擊斃
複製代碼
public boolean hitTank(Tank t) {
//若是子彈是活的, 被打中的坦克也是活的
//子彈和坦克不屬於同一方
//子彈的圖形碰撞到了坦克的圖形
//認爲子彈打中了坦克
if(this.live && t.isLive() && this.good != t.isGood() && this.getRect().intersects(t.getRect())) {
this.live = false;//子彈生命設置爲false
t.setLive(false);//坦克生命設置爲false
tc.getExplodes().add(new Explode(x, y, tc));//產生一個爆炸, 座標爲子彈的座標
return true;
}
return false;
}
複製代碼
public Rectangle getRect() {
return new Rectangle(x, y, WIDTH, HEIGHT);
}
複製代碼
//客戶端
public void connect(String ip, int port){
serverIP = ip;
Socket s = null;
try {
ds = new DatagramSocket(UDP_PORT);//建立UDP套接字
s = new Socket(ip, port);//建立TCP套接字
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
dos.writeInt(UDP_PORT);//向服務器發送本身的UDP端口號
DataInputStream dis = new DataInputStream(s.getInputStream());
int id = dis.readInt();//得到服務器分配給本身坦克的id號
this.serverUDPPort = dis.readInt();//得到服務器的UDP端口號
tc.getMyTank().id = id;
tc.getMyTank().setGood((id & 1) == 0 ? true : false);//根據坦克的id號的奇偶性設置坦克的陣營
} catch (IOException e) {
e.printStackTrace();
}finally {
try{
if(s != null) s.close();//信息交換完畢後客戶端的TCP套接字關閉
} catch (IOException e) {
e.printStackTrace();
}
}
TankNewMsg msg = new TankNewMsg(tc.getMyTank());
send(msg);//發送坦克出生的消息(後面介紹)
new Thread(new UDPThread()).start();//開啓UDP線程
}
//服務器
public void start(){
new Thread(new UDPThread()).start();//開啓UDP線程
ServerSocket ss = null;
try {
ss = new ServerSocket(TCP_PORT);//建立TCP歡迎套接字
} catch (IOException e) {
e.printStackTrace();
}
while(true){//監聽每一個客戶端的鏈接
Socket s = null;
try {
s = ss.accept();//爲客戶端分配一個專屬TCP套接字
DataInputStream dis = new DataInputStream(s.getInputStream());
int UDP_PORT = dis.readInt();//得到客戶端的UDP端口號
Client client = new Client(s.getInetAddress().getHostAddress(), UDP_PORT);//把客戶端的IP地址和UDP端口號封裝成Client對象, 以備後面使用
clients.add(client);//裝入容器中
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
dos.writeInt(ID++);//給客戶端的主戰坦克分配一個id號
dos.writeInt(UDP_PORT);
}catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(s != null) s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
複製代碼
消息類型 | 消息數據 |
---|---|
1.TANK_NEW_MSG(坦克出生信息) | 坦克id, 坦克座標, 坦克方向, 坦克好壞 |
2.TANK_MOVE_MSG(坦克移動信息) | 坦克id, 坦克座標, 坦克方向, 炮筒方向 |
3.MISSILE_NEW_MESSAGE(子彈產生信息) | 發出子彈的坦克id, 子彈id, 子彈座標, 子彈方向 |
4.TANK_DEAD_MESSAGE(子彈死亡的信息) | 發出子彈的坦克id, 子彈id |
5.MISSILE_DEAD_MESSAGE(坦克死亡的信息) | 坦克id |
TankClient
類用於控制整個遊戲客戶端, 但爲了解耦, 客戶端將須要進行的網絡操做使用另一個NetClient
類進行封裝.public interface Msg {
public static final int TANK_NEW_MSG = 1;
public static final int TANK_MOVE_MSG= 2;
public static final int MISSILE_NEW_MESSAGE = 3;
public static final int TANK_DEAD_MESSAGE = 4;
public static final int MISSILE_DEAD_MESSAGE = 5;
//每一個消息報文, 本身將擁有發送和解析的方法, 爲多態的實現奠基基礎.
public void send(DatagramSocket ds, String IP, int UDP_Port);
public void parse(DataInputStream dis);
}
複製代碼
NetClient
這個網絡接口類中, 須要定義發送消息和接收消息的方法. 想一下, 若是咱們爲每一個類型的消息編寫發送和解析的方法, 那麼程序將變得複雜冗長. 使用多態後, 每一個消息實現類本身擁有發送和解析的方法, 要調用NetClient
中的發送接口發送某個消息就方便多了. 下面代碼可能解釋的更清楚.//若是沒有多態的話, NetClient中將要定義每一個消息的發送方法
public void sendTankNewMsg(TankNewMsg msg){
//很長...
}
public void sendMissileNewMsg(MissileNewMsg msg){
//很長...
}
//只要有新的消息類型, 後面就要接着定義...
//假如使用了多態, NetClient中只須要定義一個發送方法
public void send(Msg msg){
msg.send(ds, serverIP, serverUDPPort);
}
//當咱們要發送某個類型的消息時, 只須要
TankNewMsg msg = new TankNewMsg();
NetClient nc = new NetClient();//實踐中不須要, 能拿到惟一的NetClient的引用
nc.send(msg)
//在NetClient類中, 解析的方法以下
private void parse(DatagramPacket dp) {
ByteArrayInputStream bais = new ByteArrayInputStream(buf, 0, dp.getLength());
DataInputStream dis = new DataInputStream(bais);
int msgType = 0;
try {
msgType = dis.readInt();//先拿到消息的類型
} catch (IOException e) {
e.printStackTrace();
}
Msg msg = null;
switch (msgType){//根據消息的類型, 調用具體消息的解析方法
case Msg.TANK_NEW_MSG :
msg = new TankNewMsg(tc);
msg.parse(dis);
break;
case Msg.TANK_MOVE_MSG :
msg = new TankMoveMsg(tc);
msg.parse(dis);
break;
case Msg.MISSILE_NEW_MESSAGE :
msg = new MissileNewMsg(tc);
msg.parse(dis);
break;
case Msg.TANK_DEAD_MESSAGE :
msg = new TankDeadMsg(tc);
msg.parse(dis);
break;
case Msg.MISSILE_DEAD_MESSAGE :
msg = new MissileDeadMsg(tc);
msg.parse(dis);
break;
}
}
複製代碼
//下面是TankNewMsg中解析本消息的方法
public void parse(DataInputStream dis){
try{
int id = dis.readInt();
if(id == this.tc.getMyTank().id){
return;
}
int x = dis.readInt();
int y = dis.readInt();
Dir dir = Dir.values()[dis.readInt()];
boolean good = dis.readBoolean();
//接收到別人的新信息, 判斷別人的坦克是否已將加入到tanks集合中
boolean exist = false;
for (Tank t : tc.getTanks()){
if(id == t.id){
exist = true;
break;
}
}
if(!exist) {//當判斷到接收的新坦克不存在已有集合才加入到集合.
TankNewMsg msg = new TankNewMsg(tc);
tc.getNc().send(msg);//加入一輛新坦克後要把本身的信息也發送出去.
Tank t = new Tank(x, y, good, dir, tc);
t.id = id;
tc.getTanks().add(t);
}
} catch (IOException e) {
e.printStackTrace();
}
}
複製代碼
private void locateDirection() {
Dir oldDir = this.dir;//記錄舊的方向
if(bL && !bU && !bR && !bD) dir = Dir.L;
else if(bL && bU && !bR && !bD) dir = Dir.LU;
else if(!bL && bU && !bR && !bD) dir = Dir.U;
else if(!bL && bU && bR && !bD) dir = Dir.RU;
else if(!bL && !bU && bR && !bD) dir = Dir.R;
else if(!bL && !bU && bR && bD) dir = Dir.RD;
else if(!bL && !bU && !bR && bD) dir = Dir.D;
else if(bL && !bU && !bR && bD) dir = Dir.LD;
else if(!bL && !bU && !bR && !bD) dir = Dir.STOP;
if(dir != oldDir){//若是改變後的方向不一樣於舊方向也就是說方向發生了改變
TankMoveMsg msg = new TankMoveMsg(id, x, y, dir, ptDir);//建立TankMoveMsg消息
tc.getNc().send(msg);//發送
}
}
複製代碼
private Missile fire() {
if(!live) return null;
int x = this.x + WIDTH/2 - Missile.WIDTH/2;
int y = this.y + HEIGHT/2 - Missile.HEIGHT/2;
Missile m = new Missile(id, x, y, this.good, this.ptDir, this.tc);
tc.getMissiles().add(m);
MissileNewMsg msg = new MissileNewMsg(m);//生成MissileNewMsg
tc.getNc().send(msg);//發送給其餘客戶端
return m;
}
//MissileNewMsg的解析
public void parse(DataInputStream dis) {
try{
int tankId = dis.readInt();
if(tankId == tc.getMyTank().id){//若是是本身發出的子彈就跳過(已經加入到容器了)
return;
}
int id = dis.readInt();
int x = dis.readInt();
int y = dis.readInt();
Dir dir = Dir.values()[dis.readInt()];
boolean good = dis.readBoolean();
//把收到的這顆子彈添加到子彈容器中
Missile m = new Missile(tankId, x, y, good, dir, tc);
m.setId(id);
tc.getMissiles().add(m);
} catch (IOException e) {
e.printStackTrace();
}
}
複製代碼
//TankClient類, paint()中的代碼片斷, 遍歷子彈容器中的每顆子彈看本身的坦克有沒有被打中.
for(int i = 0; i < missiles.size(); i++) {
Missile m = missiles.get(i);
if(m.hitTank(myTank)){
TankDeadMsg msg = new TankDeadMsg(myTank.id);
nc.send(msg);
MissileDeadMsg mmsg = new MissileDeadMsg(m.getTankId(), m.getId());
nc.send(mmsg);
}
m.draw(g);
}
//MissileDeadMsg的解析
public void parse(DataInputStream dis) {
try{
int tankId = dis.readInt();
int id = dis.readInt();
//在容器找到對應的那顆子彈, 設置死亡再也不畫出, 併產生一個爆炸.
for(Missile m : tc.getMissiles()){
if(tankId == tc.getMyTank().id && id == m.getId()){
m.setLive(false);
tc.getExplodes().add(new Explode(m.getX(), m.getY(), tc));
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
//TankDeadMsg的解析
public void parse(DataInputStream dis) {
try{
int tankId = dis.readInt();
if(tankId == this.tc.getMyTank().id){//若是是本身坦克發出的死亡消息舊跳過
return;
}
for(Tank t : tc.getTanks()){//不然遍歷坦克容器, 把死去的坦克移出容器, 再也不畫出.
if(t.id == tankId){
t.setLive(false);
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
複製代碼
//修改後, TankNewMsg的解析部分以下
public void parse(DataInputStream dis){
try{
int id = dis.readInt();
if(id == this.tc.getMyTank().getId()){
return;
}
int x = dis.readInt();
int y = dis.readInt();
Dir dir = Dir.values()[dis.readInt()];
boolean good = dis.readBoolean();
Tank newTank = new Tank(x, y, good, dir, tc);
newTank.setId(id);
tc.getTanks().add(newTank);//把新的坦克添加到容器中
//發出本身的信息
TankAlreadyExistMsg msg = new TankAlreadyExistMsg(tc.getMyTank());
tc.getNc().send(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
//TankAlreadyExist的解析部分以下
public void parse(DataInputStream dis) {
try{
int id = dis.readInt();
if(id == tc.getMyTank().getId()){
return;
}
boolean exist = false;//斷定發送TankAlreadyExist的坦克是否已經存在於遊戲中
for(Tank t : tc.getTanks()){
if(id == t.getId()){
exist = true;
break;
}
}
if(!exist){//不存在則添加到遊戲中
int x = dis.readInt();
int y = dis.readInt();
Dir dir = Dir.values()[dis.readInt()];
boolean good = dis.readBoolean();
Tank existTank = new Tank(x, y, good, dir, tc);
existTank.setId(id);
tc.getTanks().add(existTank);
}
} catch (IOException e) {
e.printStackTrace();
}
}
複製代碼
//服務端添加的代碼片斷
int deadTankUDPPort = dis.readInt();//得到死亡坦克客戶端的UDP端口號
for(int i = 0; i < clients.size(); i++){//從Client集合中刪除該客戶端.
Client c = clients.get(i);
if(c.UDP_PORT == deadTankUDPPort){
clients.remove(c);
}
}
//而客戶端則在向其餘客戶端發送死亡消息後通知服務器把本身從客戶端容器移除
for(int i = 0; i < missiles.size(); i++) {
Missile m = missiles.get(i);
if(m.hitTank(myTank)){
TankDeadMsg msg = new TankDeadMsg(myTank.getId());//發送坦克死亡的消息
nc.send(msg);
MissileDeadMsg mmsg = new MissileDeadMsg(m.getTankId(), m.getId());//發送子彈死亡的消息, 通知產生爆炸
nc.send(mmsg);
nc.sendTankDeadMsg();//告訴服務器把本身從Client集合中移除
gameOverDialog.setVisible(true);//彈窗結束遊戲
}
m.draw(g);
}
複製代碼