这个游戏很简单,可以操纵的飞机不断发子弹,前面不断有敌机来袭,子弹打中敌机然后敌机和子弹消失,最后实现了网络互联功能,允许两个人在局域网中不同机器上操作各自的飞机,平且两个游戏界面是同步的。
我这样设计的:自己维护一个飞机,子弹队列,敌机队列,将网络来的数据打包成另一个飞机,子弹队列,敌机队列,所以这里面传送的数据有自己飞机数据,子弹队列,敌机队列,这些数据通过TCP在服务器和客户端之间不断更新,传递。
图形用opengl渲染,由于python不能直接访问指针,通过网络只能传递字符串型,所以传送二进制数据有点问题,自己写了对函数,用于字符串和二进制数的转换,每个数由长度为4的字符串组成,索引高的为数据高位,最高位为符号位,所以只能保存-1600万到1600万之间的数,对于视频游戏这个范围足矣,但有个缺陷,不知道怎么传浮点数,所以需要浮点数的地方先*1000,传送后除以1000得到浮点数。
为了更像网络游戏,跟CS一样能自动搜索局域网中的服务器,实现了一个较简单实用的方法,大致是这样的:服务器启动后开放一个udp端口udpport,客户端向局域网中所有机器udpport发送验证信息,建立一个临时TCP端口tcpport0,当服务器端客户验证通过后向刚才的客户机tcpport0发送欢迎信息,这样客户机就得到了服务器的ip地址(还可同时进行一些必要的初始化),到此客户端便开始正式通过tcp与服务器交换游戏信息。
pyOpengl安装方法:http://pyopengl.sourceforge.net/documentation/installation.html
游戏代码:
#-*- coding:utf-8 -*-
import sys,struct
import random,math,thread,time,socket,string,re
try:
from OpenGL.GLUT import *
from OpenGL.GL import *
from OpenGL.GLU import *
except:
print '''
ERROR: PyOpenGL not installed properly.
'''
sys.exit()
x_coord=10
y_coord=10
isAserver=False #True为服务端,False为客户端
#数值统统为4个字节,最高位为符号位,1600万足矣,低字节为低位
def westicenumtostring(num):
tempnum=abs(num)
tempnum=int(tempnum)
s=''
i=0
while tempnum>0:
s=s+chr(tempnum%256)
tempnum=int(tempnum/256)
i+=1
while len(s)<3:#补齐3位
s=s+chr(0)
if num<0:
s=s+chr(1)
else:#0和正数最高位为0
s=s+chr(0)
return s
def westicestringtonum(s):
if len(s)!=4:
print 'len(s) must be 4'
return None
n0=ord(s[0])
n1=ord(s[1])
n2=ord(s[2])
n3=ord(s[3])
num=n0+256*n1+256*256*n2
if n3==1:#负数
num=(-num)
if n3!=0 and n3!=1:
return "wrong string when convert ro number"
return num
def drawenemy(x,y): #画敌人形象
glLoadIdentity()
glTranslate(x,y,0.0)
glScale(0.2,0.2,0.2)
glBegin(GL_POLYGON)
glVertex3f(1.0,1.0,0)
glVertex3f(1.0,-1.0,0)
glColor3f(1.0,0.0,0.0)
glVertex3f(-1.0,-1.0,0)
glVertex3f(-1.0,1.0,0)
glEnd()
def drawbullet(x,y):#画子弹形象
glLoadIdentity()
glTranslate(x,y,0.0)
glScale(0.1,0.1,0.1)
glBegin(GL_POLYGON)
glColor3f(0.0,1.0,0.0)
glVertex3f(0.5,1.0,0)
glVertex3f(0.5,-1.0,0)
glVertex3f(-0.5,-1.0,0)
glVertex3f(-0.5,1.0,0)
glEnd()
def drawpalne(x,y,rquad):#画玩家飞机形象
glLoadIdentity()
glTranslate(x,y,0.0)
glRotatef(rquad,0.0,1.0,0.0) # Rotate
glScale(0.5,0.5,0.5)
glBegin(GL_POLYGON) # Start drawing
glColor3f(1.0,0.0,0.0)
glVertex3f(1.0,0.0,0.0)
glColor3f(0.0,1.0,0.0)
glVertex3f(-1.0,0.0,0.0)
glColor3f(0.0,0.0,1.0)
glVertex3f(0.0,3.0,0.0)
glEnd() # We are done with the polygon
glBegin(GL_POLYGON)
glVertex3f(0.0,0.0,0)
glVertex3f(0.0,3.0,0)
glColor3f(1.0,0.0,0.0)
glVertex3f(0.0,0.5,0.5)
glEnd()
class Enemy(): #定义敌人
def __init__(self):
self.reset()
def update(self):
if self.live:
self.y-=0.01
self.draw()
if self.y<0:
self.live=False
def setxy(self,x,y):
self.x=x
self.y=y
def draw(self):
drawenemy(self.x,self.y)
def reset(self):
self.x=x_coord*random.random()
self.y=y_coord
self.live=True #活着状态
class Bullet():#定义子弹
def __init__(self,x,y):
self.reset(x,y)
self.live=False
def update(self):
if self.live:
self.y+=0.05
self.draw()
if self.y>y_coord:
self.live=False
def draw(self):
drawbullet(self.x,self.y)
def reset(self,x,y):
self.x=x
self.y=y
self.live=True
class Plane():
def __init__(self,x,y):
self.x=x
self.y=y
self.rquad=0
def update(self):
self.draw()
if self.rquad<0:
self.rquad+=1.0
if self.rquad>0:
self.rquad-=1.0
def draw(self):
drawpalne(self.x,self.y,self.rquad)
def left(self):
self.x-=0.1
if self.rquad>-40:#限制
self.rquad-=3
def right(self):
self.x+=0.1
if self.rquad<40:
self.rquad+=3
westiceplane=None
myenemylist=None
bulletlist=None
otherplane=None
otherenemylist=None
otherbulletlist=None
frameobj=None
#网络用帧
class netframe():
def __init__(self,player,mybulletlist,enemylist):
self.player=player
self.mybulletlist=mybulletlist
self.enemylist=enemylist
def senddata(self,conn):
global isconnected
senddata=''
senddata+=westicenumtostring(self.player.x*1000)
senddata+=westicenumtostring(self.player.y*1000)
senddata+=westicenumtostring(self.player.rquad*1000)#自己的位置
senddata+=westicenumtostring(len(self.mybulletlist))#子弹个数
for bullet in self.mybulletlist:
senddata+=westicenumtostring(bullet.x*1000)
senddata+=westicenumtostring(bullet.y*1000)#子弹位置
senddata+=westicenumtostring(len(self.enemylist))#敌人个数
for enemy in self.enemylist:
senddata+=westicenumtostring(enemy.x*1000)
senddata+=westicenumtostring(enemy.y*1000)#敌人位置
if isconnected:
try:
conn.send(senddata)
except socket.error:
isconnected=False
#conn.sendall(senddata)
#接收数据
def recvdata(self,conn):
global otherplane
global otherenemylist
global otherbulletlist
global isconnected
if isconnected:
try:
recvdata=conn.recv(4)
otherplanex=westicestringtonum(recvdata)/1000.0
recvdata=conn.recv(4)
otherplaney=westicestringtonum(recvdata)/1000.0
otherplane=Plane(otherplanex,otherplaney)
recvdata=conn.recv(4)
otherplane.rquad=westicestringtonum(recvdata)/1000.0
recvdata=conn.recv(4)#接收子弹数据
bulletnum=westicestringtonum(recvdata)
otherbulletlist=[]
for i in range(bulletnum):
recvdata=conn.recv(4)
bulletx=westicestringtonum(recvdata)/1000.0
recvdata=conn.recv(4)
bullety=westicestringtonum(recvdata)/1000.0
bullet=Bullet(bulletx,bullety)
bullet.live=True
otherbulletlist.append(bullet)
recvdata=conn.recv(4)
enemynum=westicestringtonum(recvdata)
otherenemylist=[]
for i in range(enemynum):
recvdata=conn.recv(4)
enemyx=westicestringtonum(recvdata)/1000.0
recvdata=conn.recv(4)
enemyy=westicestringtonum(recvdata)/1000.0
enemy=Enemy()
enemy.setxy(enemyx,enemyy)
otherenemylist.append(enemy)
except socket.error:
isconnected=False
#游戏服务器
port=8088 #主数据通道
conn=None#socket连接对象,可能是服务器或客户端
serverUDPport=9090
clientTCPport=9091
isconnected=False
class GameServer():
def __init__(self):
print '初始化游戏服务器'
global conn
global otherplane
global otherenemylist
global otherbulletlist
global frameobj
global isconnected
global serverUDPport
global clientTCPport
#服务器接收客户UDP报文,如果验证通过向客户发送TCP回应
UDPsock0=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
UDPsock0.bind(("",serverUDPport))
#还需验证客户端,用udp
recvstring,clientaddr=UDPsock0.recvfrom(1024)
if recvstring=="west":
print "服务器收到验证from ",clientaddr[0]
UDPsock0.close()
#服务器发送tcp回应
tempconn=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tempconn.connect((clientaddr[0],clientTCPport))
tempconn.send("sure")
tempconn.close()
#创建TCP服务器
TCPsock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
TCPsock.bind(('',port))
TCPsock.listen(5)
conn,clientaddr=TCPsock.accept()
print "来自客户端:",clientaddr
isconnected=True
while isconnected: #服务端循环,先发后收
frameobj.senddata(conn)
time.sleep(0.02)
frameobj.recvdata(conn)
conn.close()
#游戏客户端
class GameClient():
def __init__(self):
print '初始化游戏客户端'
global conn
global frameobj
global isconnected
global serverUDPport
global clientTCPport
self.lanserverip=None
#搜索服务器,向局域网内发送udp数据
ip=socket.gethostbyname(socket.gethostname())
match_str="\d+\.\d+\.\d+\."
ipheader=re.match(match_str,ip)
ipheader=ipheader.group()
UDPsock0=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
for i in range(1,256):
self.lanserverip=ipheader+str(i)
UDPsock0.sendto("west",(self.lanserverip,serverUDPport))
UDPsock0.close()
#客户端建立tcp服务器 接收服务器ip和其它信息,之后断开,连接服务器的tcp
TCPsock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
TCPsock.bind(('',clientTCPport))
TCPsock.listen(5)
tempconn,serveraddr=TCPsock.accept()
hellomsg=tempconn.recv(4)
tempconn.close()
self.lanserverip=serveraddr[0]
print "服务器为:",self.lanserverip
print "服务器信息:",hellomsg
#连接tcp服务器发送数据
conn=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
conn.connect((self.lanserverip,port))
isconnected=True
while isconnected:#客户端循环,先收后发
frameobj.recvdata(conn)
frameobj.senddata(conn)
time.sleep(0.02)
conn.close()
def threadfunc():#线程函数
global isAserver
if isAserver:
gameserver=GameServer()
else:
gameclient=GameClient()
def GameInit():
global x_coord
global y_coord
global westiceplane
global myenemylist
global mybulletlist
global frameobj
westiceplane=Plane(x_coord/2,1)#加入飞机
myenemylist=[]
for i in range(4): #加入敌人
westiceenemy=Enemy()
myenemylist.append(westiceenemy)
mybulletlist=[]
for i in range(20):
mywesticebullet=Bullet(westiceplane.x,westiceplane.y)
mybulletlist.append(mywesticebullet)
#新建一个线程处理网络
thread.start_new_thread(threadfunc,())
frameobj=netframe(westiceplane,mybulletlist,myenemylist)
def init():
glClearColor(0.5,0.5,0.5,0.0)
glClearDepth(1.0) # Enables Clearing Of The Depth Buffer
glDepthFunc(GL_LESS) # The Type Of Depth Test To Do
glEnable(GL_DEPTH_TEST) # Enables Depth Testing
glShadeModel(GL_SMOOTH) # Enables Smooth Color Shading
def calcdistance(object0,object1):
return math.sqrt(pow(object0.x-object1.x,2)+pow(object0.y-object1.y,2))
count=0
def display():
#print 'display'
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
westiceplane.update()
for enemy in myenemylist:
enemy.update()
if not enemy.live:
enemy.reset()
for bullet in mybulletlist:
if calcdistance(bullet,enemy)<=0.2:#是否相遇
bullet.live=False
enemy.live=False
#自动发射子弹
global count
if count>=15:
count=0
for bullet in mybulletlist:
if not bullet.live:
bullet.reset(westiceplane.x,westiceplane.y+1)#激活一颗
break
for bullet in mybulletlist:
bullet.update()
count+=1
#显示网络来的数据
global otherplane
global otherenemylist
global otherbulletlist
if isconnected and otherplane:
otherplane.draw()
if isconnected and otherbulletlist and otherenemylist and otherplane:
otherplane.draw()
for myenemy in myenemylist:
for otherbullet in otherbulletlist:
if calcdistance(otherbullet,myenemy)<=0.2:#是否相遇
otherbullet.live=False
myenemy.live=False
for otherenemy in otherenemylist:
if otherenemy.live:
otherenemy.draw()
for otherbullet in otherbulletlist:
if otherbullet.live:
otherbullet.draw()
glutSwapBuffers()
#glFlush()
def reshape(w,h):
print 'reshape'
glViewport(0,0,w,h)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
print 'w:',w,' h:',h
if w!=0 and h!=0:
global x_coord
global y_coord
if(w<=h):
gluOrtho2D(0.0,x_coord,0.0,x_coord*h/w)
else:
gluOrtho2D(0.0,x_coord*w/h,0.0,x_coord)
glMatrixMode(GL_MODELVIEW)
def keyboard(key,x,y):
# if key==chr(27):
# sys.exit(0)
if key=='a'or key=='A':
westiceplane.left()
if key=='d'or key=='D':
westiceplane.right()
GameInit()
glutInit(sys.argv)
glutInitDisplayMode(GLUT_RGBA|GLUT_DOUBLE|GLUT_DEPTH) #使用双缓冲和深度测试
glutInitWindowSize(600,500)
glutInitWindowPosition(100,100)
if isAserver:
glutCreateWindow('Game Server')
else:
glutCreateWindow('Game Client')
init()
glutReshapeFunc(reshape)
glutKeyboardFunc(keyboard)
glutDisplayFunc(display)
glutIdleFunc(display)
glutMainLoop()