- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章Java实现简易版联网坦克对战小游戏(附源码)由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
介绍 。
通过本项目能够更直观地理解应用层和运输层网络协议, 以及继承封装多态的运用. 网络部分是本文叙述的重点, 你将看到如何使用java建立tcp和udp连接并交换报文, 你还将看到如何自己定义一个简单的应用层协议来让自己应用进行网络通信. 。
获取源码 。
基础版本 。
游戏的原理, 图形界面(非重点) 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
//类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();
}
}
}
}
|
以上就是整个游戏图形交互的主要部分, 保证了游戏能正常显示后, 下面我们将关注于游戏的逻辑部分. 。
游戏逻辑 。
在游戏的逻辑中有两个重点, 一个是坦克, 另一个是子弹. 根据面向对象的思想, 分别把这两者封装成两个类, 它们所具有的行为都在类对应有相应的方法. 。
坦克的字段 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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;
//炮筒的方向
|
由于在tankclient类中的paint方法中需要画出图形, 根据面向对象的思想, 要画出一辆坦克, 应该由坦克调用自己的方法画出自己. 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
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();
//每次画完改变坦克的坐标, 连续画的时候坦克就动起来了
}
|
上面提到了改变坦克坐标的move()方法, 具体代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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;
}
|
上面提到了根据坦克的方向改变坦克的左边, 而坦克的方向通过键盘改变. 代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
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();
}
|
上面提到了坦克开火的方法, 这也是坦克最后一个重要的方法了, 代码如下, 后面将根据这个方法引出子弹类. 。
1
2
3
4
5
6
7
8
9
10
11
|
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;
}
|
子弹类, 首先是子弹的字段 。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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;
//子弹所属阵营, 我方坦克自能被地方坦克击毙
|
子弹类中同样有draw(), move()等方法, 在此不重复叙述了, 重点关注子弹打中坦克的方法. 子弹是否打中坦克, 是调用子弹自身的判断方法判断的. 。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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
;
}
|
补充, 坦克和子弹都以图形的方式显示, 在本游戏中通过java的原生api获得图形的矩形框并判断是否重合(碰撞) 。
1
2
3
|
public
rectangle getrect() {
return
new
rectangle(x, y, width, height);
}
|
网络联机 。
客户端连接上服务器 。
附上这部分的代码片段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
//客户端
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 |
。
1
2
3
4
5
6
7
8
9
10
11
|
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中的发送接口发送某个消息就方便多了. 下面代码可能解释的更清楚. 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
//如果没有多态的话, 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 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
//下面是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();
}
}
|
tankmovemsg 。
下面将介绍tankmovemsg协议, 消息类型为2, 需要的数据有坦克id, 坦克坐标, 坦克方向, 炮筒方向. 每当自己坦克的方向发生改变时, 向服务器发送一个tankmovemsg消息, 经服务器转发后, 其他客户端也能收该坦克的方向变化, 然后根据数据找到该坦克并设置方向等参数. 这样才能相互看到各自的坦克在移动. 。
下面是发送tankmovemsg的地方, 也就是改变坦克方向的时候. 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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);
//发送
}
}
|
missilenewmsg 。
下面将介绍missilenewmsg协议, 消息类型为3, 需要的数据有发出子弹的坦克id, 子弹id, 子弹坐标, 子弹方向. 当坦克发出一发炮弹后, 需要将炮弹的信息告诉其他客户端, 其他客户端根据子弹的信息在游戏中创建子弹对象并加入到容器中, 这样才能看见相互发出的子弹. 。
missilenewmsg在坦克发出一颗炮弹后生成. 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
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();
}
}
|
tankdeadmsg和missiledeadmsg 。
下面介绍tankdeadmsg和missiledeadmsg, 它们是一个组合, 当一台坦克被击中后, 发出tankdeadmsg信息, 同时子弹也死亡, 发出missiledeadmsg信息. missiledeadmsg需要数据发出子弹的坦克id, 子弹id, 而tankdeadmsg只需要坦克id一个数据. 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
//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();
}
}
|
到此为止, 基础版本就结束了, 基础版本已经是一个能正常游戏的版本了. 。
改进版本. 。
定义更精细的协议 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
//修改后, 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();
}
}
|
坦克战亡后服务器端的处理 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
//服务端添加的代码片段
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);
}
|
完成这个版本后, 多人游戏时游戏性更强了, 当一个玩家死后他可以重新开启游戏再次加入战场. 但是有个小问题, 他可能会加入到击败他的坦克的阵营, 因为服务器为坦克分配的id好是递增的, 而判定坦克的阵营仅通过id的奇偶判断. 但就这个版本来说服务器端处理死亡坦克的任务算是完成了. 。
客户端线程同步 。
在完成基础版本后考虑过这个问题, 因为在游戏中, 由于延时的原因, 可能会造成各个客户端线程不同步. 处理手段可以是每隔一定时间, 各个客户端向服务器发送自己坦克的位置消息, 服务器再将该位置消息通知到其他客户端, 进行同步. 但是在本游戏中, 只要坦克的方向一发生移动就会发送一个tankmovemsg包, tankmovemsg消息中除了包含坦克的方向, 也包含坦克的坐标, 相当于做了客户端线程同步. 所以考虑暂时不需要再额外进行客户端同步了. 。
添加图片 。
在基础版本中, 坦克和子弹都是通过画一个圆表示, 现在添加坦克和子弹的图片为游戏注入灵魂. 。
总结与致谢 。
总结 。
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我的支持.
原文链接:https://www.cnblogs.com/tanshaoshenghao/p/10708586.html 。
最后此篇关于Java实现简易版联网坦克对战小游戏(附源码)的文章就讲到这里了,如果你想了解更多关于Java实现简易版联网坦克对战小游戏(附源码)的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我是一名优秀的程序员,十分优秀!