网络游戏编程-联机版植物大战虫子
这是多年前写的小游戏初版,游戏逻辑部分后来已经重构,变成了支持通用塔防游戏的结构,重构版源码后续整理好再介绍,这里先介绍下网络消息、创建房间部分逻辑。 下面是之前打包的初步版本。
联机版植物大战虫子游戏下载地址http://yun.baidu.com/share/link?shareid=2456982233&uk=3442849180
游戏方法:
1,双击开启服务器 EngineDemoServer_Release.exe。(服务器ip和端口配置在data\Sys\server.ini)
2,双击开启客户端 EngineDemoClient_Release。 一般开启两个客户端创建房间进行游戏。也可以开启一个客户端然后创建ai玩家进行游戏。
测试用登录id test** (注意重复登录会被踢),测试用登录密码 1111
EngineDemoRobotClient_Release.exe是测试用机器人客户端,可选择性开启
截图
服务器
多线程安全问题。接收网络包为了提高吞吐量一般放在子线程中,此时若直接处理消息包,如果需要调用标准的windows控件来显示是不起作用的,如果调用引擎的其它接口比如执行脚本甚至会引起崩溃。所以在子线程收到消息包时将其保存到对列,主线程循环中再从队列弹出并处理,注意对列操作前要加锁。
另一篇文章单独介绍了消息封包和IOCP,这里不再重复。
客户端网络部分
//========================================================
// @Date: 2016.05
// @File: Include/Net/NetClient.h
// @Brief: NetClient
// @Author: LouLei
// @Email: twopointfive@163.com
// @Copyright (Crapell) - All Rights Reserved
//========================================================#pragma once#ifndef __NetClient__H__
#define __NetClient__H__#include "General/singleton.h"#define NET_CLIENT_PORT_MIN 10001
#define NET_CLIENT_PORT_MAX 20001#define G_Client NetClient::GetSingleton()class PacketQueue;
class PacketBase;
class TcpStream;typedef void* PSocket;
#include "General/Thread.h"typedef void (*OutPutFun)(const char *msg);
//?? 可能多个实体 连接多个服务器game chat? 代理模式?
class NetClient:public MemberSingleton<NetClient>
{
public:NetClient();~NetClient(); //!子线程可调用,控制台可以立即输出,但ui无法立即刷新,需要主线程异步处理void LogSub(const char* lpszFormat, ...);void SetLogFun(OutPutFun fun);bool InitNetwork(const char *serv_addr, unsigned int serv_port);bool Close();//!向服务器发消息bool SendMsg(int packetID, char* buffer, int bufferSize);bool SendPacketToServer( PacketBase* packet);//for inner serverint InnerRecvPacket( PacketBase* packet );int RecvStream( PSocket socket, char* buffer, int bufferSize );void SetSpecialPacketID(int login,int disconnect);#ifdef WIN32APP//!线程回调int RecvThreadProc( void* threadParam );void* m_receiveThread;
#endif#ifdef ANDROIDAPP//!网络环境在android中用java开启//!网络开启是否成功void SetRunning(bool running);//!处理tcp流int AndroidTcpStream(int streamSize,const char* stream);
#endif//!处理消息队列void MainThreadProcessPacketPool();static const char* classname(){return "NetClient";}PacketQueue* m_packetQueue;PSocket m_socket; char m_serverAddress[256];unsigned int m_serverPort; unsigned int m_port; //1: connected to net server//0: connected to inner serverbool m_bConnecting;//断开协议idint m_disconnectPacketID;OutPutFun m_logFun;char m_logBuff[1024*10];int m_logSize;CriticalSection m_logCriticalSection;//!64k合设置的系统缓存一样大,可以大于(浪费),可以小于(低效)char* m_receiveBuffer;TcpStream* m_tcpStream;//有些消息阻塞后续消息的发送,比如登录bool m_waitingPacket;//
#define MaxPingCount 8int m_iPingCount;float m_fPingLatency[MaxPingCount];float m_fLastPingTime;float m_fPingLatencyAvg;
};#endif//========================================================
// @Date: 2016.05
// @File: Include/Net/NetClient.h
// @Brief: NetClient
// @Author: LouLei
// @Email: twopointfive@163.com
// @Copyright (Crapell) - All Rights Reserved
//========================================================#pragma once#ifndef __NetRobotClient__H__
#define __NetRobotClient__H__#include "General/singleton.h"#define NET_CLIENT_PORT_MIN 10001
#define NET_CLIENT_PORT_MAX 20001class PacketQueue;
class PacketBase;
class TcpStream;//
typedef void* PSocket;#include "General/Thread.h"typedef void (*OutPutFun)(const char *msg);#define NET_ROBOTCLIENT_PORT_MIN 6000
#define NET_ROBOTCLIENT_PORT_MAX 9000
//模拟客户端的机器人
class NetRobot
{
public:friend class NetRobotsClient; NetRobot();virtual ~NetRobot();virtual void Update(){};virtual void* RelocateSyncGameInfo(){return NULL;};bool InitNetwork(const char *serv_addr, unsigned int serv_port);bool Close();//!向服务器发消息bool SendMsg(int packetID, char* buffer, int bufferSize);void SendPacketToServer( PacketBase* packet);int RecvStream( PSocket socket, char* buffer, int bufferSize );void Receive();//protected:PSocket m_socket; //!自己的座位号int m_playerID;unsigned int m_port;int m_index;char m_name[128];bool m_bConnecting;//!每个socket配备独立缓冲区,减小缓冲可以使机器人占用内存减少//!64k合设置的系统缓存一样大,可以大于(浪费),可以小于(低效)char* m_receiveBuffer;TcpStream* m_tcpStream;bool m_waitingPacket;//某些消息在没收到反馈前不能发第二次
};//若干机器人共用一个线程,否则线程开多了会卡(一个线程至少1M的栈),因此不能在进程中直接开多个客户端。
//模拟多个机器人的客户端
class NetRobotsClient:public MemberSingleton<NetRobotsClient>
{
public:NetRobotsClient();PacketQueue* m_packetPool;char m_serverAddress[256];unsigned int m_serverPort; //!子线程可调用,控制台可以立即输出,但ui无法立即刷新,需要主线程异步处理void LogSub(const char* lpszFormat, ...);//!或者使用virtual funvoid SetLogFun(OutPutFun fun);virtual const char* GetPacketLogStr(PacketBase* packet);template <class Robot>/*virtual*/bool InitRobot(int robotNum);NetRobot* GetRobot(const char* name);NetRobot* GetRobot(int playerID);//NetRobot* GetRobot(int index);bool InitNetwork(const char *serv_addr, unsigned int serv_port);bool Close();static const char* classname(){return "NetRobotsClient";}//!线程回调typedef struct ThreadParm {HANDLE pThread; int index;int startRobotIndex;int endRobotIndex;} ;int MsgReceiverProc( void* threadParam );HANDLE* m_receiveThread;int m_receiveThreadNum;//!处理消息队列void MainThreadProcessPacketPool();NetRobot** m_robots;int m_robotNum;bool m_bRunning;OutPutFun m_logFun;char m_logBuff[1024*10];int m_logSize;CriticalSection m_logCriticalSection;
};template <class Robot>
bool NetRobotsClient::InitRobot(int robotNum)
{m_robotNum = robotNum;m_robots = new NetRobot*[m_robotNum];for (int i=0;i<m_robotNum;i++){m_robots[i] = new Robot;m_robots[i]->m_index = i;sprintf(m_robots[i]->m_name,"robot%04d",i);}return true;
}#endif//========================================================
// @Date: 2016.05
// @File: Include/Net/NetUdp.h
// @Brief: NetUdp
// @Author: LouLei
// @Email: twopointfive@163.com
// @Copyright (Crapell) - All Rights Reserved
//========================================================#pragma once#ifndef __NetUdp__H__
#define __NetUdp__H__#include "General/singleton.h"#define NET_CLIENT_PORT_MIN 10001
#define NET_CLIENT_PORT_MAX 20001#define G_UdpPoint NetPointUdp::GetSingleton()class PacketQueue;
class PacketBase;typedef void* PSocket;
#include "General/Thread.h"//UDP不存在粘包问题 send、recv个数是一致的
typedef void (*OutPutFun)(const char *msg);class NetPointUdp:public MemberSingleton<NetPointUdp>
{
public:NetPointUdp();~NetPointUdp(); //!子线程可调用,控制台可以立即输出,但ui无法立即刷新,需要主线程异步处理void LogSub(const char* lpszFormat, ...);void SetLogFun(OutPutFun fun);virtual const char* GetPacketLogStr(PacketBase* packet);//无需指定端口bool InitNetwork(int broadcastPort);bool Close();//!向服务器发udp 暂未实现 server必须在外网或同一nat,否则发不过去。在不同nat时服务器必须立刻返回消息,否则链路很短的时间内(200ms)将断开。//!向某地址发udp 只能是同一nat bool SendMsgToAdress(char* buffer, int bufferSize,const char* address,int port);bool SendPacketToAdress( PacketBase* packet,const char* address,int port);//!局域网udp广播 暂时只实现此处bool SendPacketBroadcast( PacketBase* packet,int port);int RecvBuffer( PSocket socket, char* buffer, int bufferSize );#ifdef WIN32APP//!线程回调int RecvThreadProc( void* threadParam );void* m_receiveThread;
#endif//!处理消息队列void MainThreadProcessPacketPool();static const char* classname(){return "NetPointUdp";}bool m_bRunning;PSocket m_broadcastSocket;PacketQueue* m_packetQueue;char m_myAddress[256];char m_hostName[256];//广播到的端口,即对方侦听接收的端口,而自己发送的端口是自动分派的。int BroadcastPort; OutPutFun m_logFun;char m_logBuff[1024*10];int m_logSize;CriticalSection m_logCriticalSection;
};#endif
消息包处理
主循环消息包分发技巧。一种方法是在界面更新函数中处理消息队列,这样可以将一系列消息处理内聚到一起。可能存在的问题是大量不同的界面会无差别的处理相同的消息,以致很多消息要放到基类而形成一个超级类。所以此处将消息包处理通过文件来聚合,同时不必放在界面处理中,通过一个小的设计模式还可以避免一个很大的switch语句的使用,避免需要同时包含所有的消息。每个消息对应一个类,消息解析统一由服务器端编写,客户端负责检查并实现该类的处理函数。
游戏消息协议 游戏的初步构思是若干开放式小游戏在mmorpg的世界中进行,经过的玩家都可以观战到。 //======================================================== // @Date: 2016.05 // @File: SourceDemoServer/Packet/Protocol.h // @Brief: Protocol // @Author: LouLei // @Email: twopointfive@163.com // @Copyright (Crapell) - All Rights Reserved //========================================================#pragma once#ifndef __Protocol__H__ #define __Protocol__H__#define PlayerNameLenMax 32 #define RoomNameLenMax 32 #define LobbyNameLenMax 32 #define IPAddressMax 16//即时刷新过滤,商城道具列表基本不变无需刷新,俱乐部成员列表点击刷新,房间列表在打开列表界面时即时刷新 enum PacketUpdateFilter {PF_RoomListUpdate = 1,PF_PlayerListUpdate = 1<<2, }; //传输大的列表时将消息分包 enum PacketListFlag { List_Begin = 1, List_Data = 2,List_End = 4, }; //一个ID将一系列功能相关的消息封装起来处理。 enum Protocol {//<<基本BEGIN_NORMAL = 10000,C2S_DisConnection ,//断开连接S2C_ConnectionShutdown ,//被踢//>>//<<登录相关 C2S_Verificate ,//验证客户端合法性S2C_Verificate ,C2S_Regist ,//注册S2C_Regist ,C2S_Login ,//登录S2C_Login ,C2S_EnterLobby ,//进入大厅S2C_EnterLobby ,//C2S_ExitLobby ,//退出大厅S2C_ExitLobby ,////>>//<<聊天相关C2S_Chat ,//聊天S2C_Chat ,//C2S_ChatPic ,//图片聊天,大消息S2C_ChatPic ,//C2S_ChatOption ,//聊天设置S2C_ChatOption ,//C2S_MsgList ,//消息列表S2C_MsgList ,//C2S_MsgListUpdate ,//移除 等S2C_MsgListUpdate ,//添加 移除 变更等//>>//<<邮件相关C2S_MailList ,//邮件列表S2C_MailList ,////C2S_MailListUpdate ,//移除 等S2C_MailListUpdate ,//增加 移除 接收 变更等C2S_MailOP ,//移除 等//S2C_MailOP ,//添加 移除 变更等//>>//<<玩家、好友相关C2S_PlayerInfo ,//玩家信息S2C_PlayerInfo ,//C2S_PlayerList ,//玩家列表S2C_PlayerList ,//S2C_PlayerListUpdate ,//添加 移除 名字 位置变更等C2S_FriendList ,//好友列表S2C_FriendList ,//C2S_FriendListUpdate ,//添加 移除 位置变更等S2C_FriendListUpdate ,////>>//<<RPG游戏内相关S2C_PlayerBaseInfo ,//玩家基本信息S2C_PlayerVisible ,//玩家进入退出视野 monster npc?C2S_PlayerMove ,//玩家移动,只给在其视野内的玩家发送S2C_PlayerMove ,C2S_PlayerPoint ,//玩家点数S2C_PlayerPoint ,C2S_PlayerAnim ,//玩家动画S2C_PlayerAnim ,C2S_PlayerState ,//玩家状态S2C_PlayerState ,S2C_NpcVisible ,//npc 单向消息S2C_NpcMove ,S2C_NpcAnim,S2C_NpcState,S2C_MonsterVisible ,//monster 单向消息S2C_MonsterMove ,C2S_MonsterHurt ,//monster 点数S2C_MonsterHurt ,S2C_MonsterAnim,S2C_MonsterState,C2S_UsingList ,S2C_UsingList ,C2S_Using ,//物品操作,具体操作见操作码 :捡取、npc处购买出售、摆摊购买出售、使用、other使用(比如给其他玩家加血)S2C_Using ,C2S_WeaponList ,S2C_WeaponList ,C2S_WearList ,S2C_WearList ,C2S_Weapon ,//装备操作S2C_Weapon ,S2C_WeaponOther ,//其它玩家穿脱装备C2S_MissionList ,S2C_MissionList ,C2S_Mission ,//任务操作S2C_Mission ,C2S_SkillList ,//已学技能列表S2C_SkillList ,C2S_Skill ,//技能操作S2C_Skill ,S2C_OtherSkill ,//其它玩家、宠物释放、打断技能C2S_ShortcutList ,//快捷槽列表S2C_ShortcutList ,C2S_Shortcut ,//快捷槽操作S2C_Shortcut ,C2S_Bag ,//背包操作C2S_Fight ,//战斗操作//>>//<<小游戏房间相关C2S_RoomList ,//房间列表S2C_RoomList ,//S2C_RoomListUpdate ,//列表跟新:添加 移除 名字 状态 成员变更等。 只给打开了房间列表界面的玩家发送。房间列表更新频度,房间个数r,玩家个数n,平均视野内玩家个数v,前者复杂度rXn,后者nXv(?一帧内变化的房间一起发)C2S_CreateRoom ,//创建房间S2C_CreateRoom ,//自己创建C2S_EnterRoom ,//进入房间S2C_EnterRoom ,//创建后进入或选择进入S2C_OtherEnterRoom ,C2S_CreateAiRoomPlayer ,//主机创建ai玩家并进入房间S2C_CreateAiRoomPlayer ,S2C_AiEnterRoom ,C2S_ExitRoom ,//退出房间S2C_ExitRoom ,C2S_KickOtherRoomPlayer ,//房主踢人 或踢ai玩家S2C_OtherExitRoom ,//别人退出房间 ?? 利用S2C_RoomPlayerUpdateC2S_RoomUpdate ,//房主改变,房间类型,名字等改变S2C_RoomUpdate ,//C2S_RoomPlayerUpdate ,//玩家avatar改变,房主,队伍位置改变等S2C_RoomPlayerUpdate ,////S2C_RoomPlayerListUpdate ,//不添加该消息因为仅可以代替S2C_OtherEnterRoom S2C_OtherExitRoom//>>//<<小游戏相关//一个玩家不可以多视口同时进行两个小游戏,因为只能处在一个房间中//小游戏内部消息添加命令类型C2S_MiniPlayerReady ,//玩家准备好S2C_MiniPlayerReady ,S2C_MiniGameReady ,//游戏准备好C2S_MiniGameStart ,//房主点击游戏开始S2C_MiniGameStart ,//C2S_MiniGameStartRes ,//某客户端开启失败C2S_MiniGameEnd ,//房主端判断游戏结束S2C_MiniGameEnd ,//C2S_MiniGameCmd ,//游戏内命令,客户端发给服务器要求转发房间内其它人C2S_MiniGameCmdAll ,//游戏内命令,客户端发给服务器要求转发房间内所有人S2C_MiniGameCmd ,//<<不需要以下消息//C2S_MiniGameCmd_2H ,//客户端发给服务器要求转发主机//C2S_MiniGameCmd_2Other ,//客户端发给服务器要求转发房间内其它所有人//S2C_MiniGameCmd_H2 ,//主机发给服务器要求转发房间内其它所有人//S2C_MiniGameCmd_C2 ,//客户端发给服务器要求转发房间内其它所有人//C2S_MiniGameAICmd ,//主机客户端上的ai玩家发给服务器要求转发,和主机真人具有相同socket。 直接使用C2S_MiniGameCmd即可//S2C_MiniGameAICmd , //主机ai发addplant给server,server要转发屏蔽主机是可以的,主机上ai玩家和主机使用的是同一个游戏环境,此时已经是新的环境了//其它玩家发addplant给server,server只要转发一份给主机,即使主机有ai//命令可以带有发送者字段,发送者客户端添加//>>//<<小游戏观战相关//观战玩家可以看到minigame中的人在玩vr游戏? 给这些人带上vr头盔标志?//一个玩家可以同时看到多个minigame?(最多可以设置上限,服务器是否限制某片区域内minigame数量?比如若干房间都想在一个地点进行赛车游戏)//C2S_PlayerReady ,//玩家准备好//S2C_PlayerReady ,//S2C_GameReady ,//游戏准备好C2S_MiniGameOtherOption ,//设置不观战黑名单的游戏S2C_MiniGameOtherVisible ,////S2C_MiniGameOtherEnd ,//use visibleC2S_MiniGameOtherCmd ,//观战游戏内命令S2C_MiniGameOtherCmd ,//>>//<<活动相关C2S_EventList ,//活动列表S2C_EventList ,//C2S_EventPlayerInfo ,//单个活动及玩家相关信息S2C_EventPlayerInfo ,//C2S_EventPlayerOp ,//活动中玩家操作S2C_EventPlayerOp ,////>>C2S_PacketUpdateFilter ,//即时过滤开关UDP_S2L_LanServerCreate ,//创建局域网服务器时发送局域网广播,以便同局域网的客户端可以发现并联机UDP_S2L_LanServerClose ,UDP_C2L_LanClientSearchServer ,//客户端启动或刷新lanserver列表时请求,收到的此消息的lanserver回复UDP_LanServerCreateEND_MAX,};//通用错误码 enum {Err_WrongMoney = -11, //金币钱不足Err_UnKnow = -999, //未知错误 };#endif // _PROTOCOL_PACKETID_H
植物大战虫子小游戏内部命令
enum MiniPVBCmd { CMD_PlantAdd, //种植植物 CMD_PlantDead, //植物死亡 CMD_BugAdd, //产生虫子 CMD_BugDead, //虫子死亡 CMD_BugMove, //虫子移动
CMD_Anim, //播放动画
};
后续对界面做了改版,ui是自己p的图,改来改去就是不好看,将就着用了。
rpg玩家可以参与小游戏内使用技能杀虫子的构思被砍掉了,可能会破坏塔防类游戏的玩法。
服务器端房间相关消息处理:写的不好,仅供参考。游戏核心逻辑还没动头呢就要先写一堆基础代码,不是一班的烦。
//========================================================
// @Date: 2016.05
// @File: SourceDemoServer/Packet/PacketRoomServer.cpp
// @Brief: PacketRoomServer
// @Author: LouLei
// @Email: twopointfive@163.com
// @Copyright (Crapell) - All Rights Reserved
//========================================================#include "General/Pch.h"
#include "Net/NetServerIocp.h"
#include "Game/NetPlayer.h"
#include "Game/NetRoom.h"
#include "Game/NetLobby.h"
#include "Packet/Protocol.h"
#include "Packet/PacketRoom.h"
#include "Packet/PacketPlayerFriend.h"
#include "Rpg/MiniGameStyle.h"
#include "Game/NetWeapon.h"
#include "General/Pce.h"//==================^_^==================^_^==================^_^==================^_^
bool C2SPacketRoomList::Process()
{NetPlayer* fromPlayer = G_Lobby->GetPlayerBySocket(m_fromSocket);NetRoomList& sendList = G_Lobby->m_roomList;NetRoomList::iterator it = sendList.begin();int flag = 0;S2CPacketRoomList packet;int accumNum = 0;int curNum = 0;for ( ; it!=sendList.end();it++ ) {//if ( (*it)->m_playerLocation != PL_OFFLINE) {if (accumNum==0){flag |= List_Begin;}if (accumNum==sendList.size()-1){flag |= List_End;}//LobbyRoomInfo& roominfo = packet.roomInfos[curNum];strcpy(roominfo.roomName,(*it)->m_roomName);roominfo.roomID = (*it)->m_roomID;roominfo.ownerID = (*it)->m_chiefPlayerID;roominfo.roomType = (*it)->m_roomType;roominfo.roomState = (*it)->m_roomState;//accumNum++;curNum++;if (curNum==MaxRoomList|| flag&List_End){packet.infoNum = curNum;G_Lobby->SendPacketToPlayer(&packet,fromPlayer);flag = 0;curNum = 0;}}}return true;
}
//==================^_^==================^_^==================^_^==================^_^
bool C2SPacketCreateRoom::Process()
{NetPlayer* fromPlayer = G_Lobby->GetPlayerBySocket(m_fromSocket);if(fromPlayer==NULL) {G_Server->LogSub("[%d] C2SPacketCreateRoom玩家不存在。\n",m_fromSocket);return false;}if(fromPlayer->m_roomID!=-1) {S2CPacketCreateRoom packet;packet.m_res = S2CPacketCreateRoom::Err_InRoom;G_Lobby->SendPacketToPlayer(&packet,fromPlayer);G_Server->LogSub("[%d] C2SPacketCreateRoom已经在房间内。\n",roomType);return false;}MiniGameStyle* miniGameStyle = G_StyleMgr->GetStyle<MiniGameStyle>(roomType);if (miniGameStyle==NULL){S2CPacketCreateRoom packet;packet.m_res = S2CPacketCreateRoom::Err_NoRoomType;G_Lobby->SendPacketToPlayer(&packet,fromPlayer);G_Server->LogSub("[%d] C2SPacketCreateRoom房间类型不存在。\n",roomType);return false;}//int roomID = fromPlayer->m_playerKey; //bug 房主退出后新建房间id重复//int roomID = G_Lobby->m_roomList.size(); //bug 0 1 2; 1room解散后 新建的2重复//ASSERT G_Lobby->GetRoom(roomID)==NULL;int roomID = G_Lobby->NextValidRoomID(); NetRoom* room = new NetRoom;room->m_roomID = roomID;room->m_chiefPlayerID = fromPlayer->ObjID();room->m_roomType = (MiniGameRoomType)roomType;room->m_maxPlayerNum = miniGameStyle->maxPlayerNum;room->m_sideNum = miniGameStyle->sideNum;if (room->m_maxPlayerNum<=0){//至少1个人room->m_maxPlayerNum = 1;}strcpy(room->m_roomName,roomName);G_Lobby->AddRoom(room);{//S2CPacketCreateRoom packet;packet.roomID = roomID;packet.roomType = roomType;strcpy(packet.roomName,roomName);G_Lobby->SendPacketToPlayer(&packet,fromPlayer);}{//创建后进入room->AddPlayer(fromPlayer);fromPlayer->m_bAi = false;S2CPacketEnterRoom packet;packet.roomID = roomID;packet.roomType = (MiniGameRoomType)roomType;packet.chiefPlayerID = fromPlayer->ObjID();packet.roomSlot = fromPlayer->m_roomSlot;strcpy(packet.roomName,roomName);//此时应该只有一个人MyRoomInfo* info = G_SyncMyRoomInfo;info->m_roomPlayers.clear();MyRoomPlayerInfo playerinfo;NetPlayer* roomPlayer;for (int i=0;i<MaxRoomPlayer;++i){if (room->m_players[i]){roomPlayer = room->m_players[i];playerinfo.roomSlot = roomPlayer->m_roomSlot;strcpy(playerinfo.playerName,roomPlayer->m_playerName);playerinfo.playerID = roomPlayer->ObjID();playerinfo.level = roomPlayer->m_level;playerinfo.isAI = roomPlayer->m_bAi;strcpy(playerinfo.icon,roomPlayer->m_headIcon);//my avatar没必要发playerinfo.style = roomPlayer->GetStyle()->ID;WeaponPart* weaponPart = roomPlayer->GetPart<WeaponPart>();playerinfo.weaponNum = weaponPart->m_weaponWearList.size();int i = 0;for (NetWeaponPtrList::iterator it=weaponPart->m_weaponWearList.begin();it!=weaponPart->m_weaponWearList.end();++it){playerinfo.weaponStyle[i] = (*it)->StyleID();playerinfo.weaponID[i] = (*it)->ObjID();i++;}info->m_roomPlayers.push_back(playerinfo);}}G_Lobby->SendPacketToPlayer(&packet,fromPlayer);}{//房间列表改变S2CPacketRoomListUpdate packet;packet.updateType = S2CPacketRoomListUpdate::Room_Add;packet.roominfo.roomID = roomID;packet.roominfo.roomType = roomType;packet.roominfo.ownerID = room->m_chiefPlayerID;strcpy(packet.roominfo.roomName,roomName);G_Lobby->SendPacketToLobbyOther(&packet,fromPlayer);}return true;
}//==================^_^==================^_^==================^_^==================^_^
bool C2SPacketEnterRoom::Process()
{NetPlayer* fromPlayer = G_Lobby->GetPlayerBySocket(m_fromSocket);if(fromPlayer==NULL) {G_Server->LogSub("[%d] C2SPacketEnterRoom玩家不存在。\n",m_fromSocket);return false;}NetRoom* room = G_Lobby->GetRoom(roomID);if(room==NULL) {S2CPacketEnterRoom packet;packet.roomID = roomID;packet.m_res = S2CPacketEnterRoom::Err_NoRoom;//房间不存在//packet.roomType = 0;//strcpy(packet.roomName,roomName);G_Lobby->SendPacketToPlayer(&packet,fromPlayer);G_Server->LogSub("[%d] C2SPacketEnterRoom房间不存在。\n",roomID);return false;}if (fromPlayer->m_roomID==roomID){S2CPacketEnterRoom packet;packet.roomID = roomID;packet.m_res = S2CPacketEnterRoom::Err_AlreadyInRoom;//已经在房间内//packet.roomType = 0;//strcpy(packet.roomName,roomName);G_Lobby->SendPacketToPlayer(&packet,fromPlayer);G_Server->LogSub("[%d] C2SPacketEnterRoom已经在房间内。\n",roomID);return false;}if (room->m_roomState==RS_Gaming){S2CPacketEnterRoom packet;packet.roomID = roomID;packet.m_res = S2CPacketEnterRoom::Err_Gaming;//游戏中//packet.roomType = 0;//strcpy(packet.roomName,roomName);G_Lobby->SendPacketToPlayer(&packet,fromPlayer);G_Server->LogSub("[%d] C2SPacketEnterRoom游戏中。\n",roomID);return false;//如果类似rpg,游戏进行中玩家也可以随意进入退出游戏,房间就变成了‘任务房间’、‘副本房间’。//去掉return,给当事人发送进入房间消息后,再发送游戏开始消息。}if (room->IsFull()){S2CPacketEnterRoom packet;packet.roomID = roomID;packet.m_res = S2CPacketEnterRoom::Err_FullRoom;//满员//packet.roomType = 0;//strcpy(packet.roomName,roomName);G_Lobby->SendPacketToPlayer(&packet,fromPlayer);G_Server->LogSub("[%d] C2SPacketEnterRoom房间满员。\n",roomID);return false;}int res = room->AddPlayer(fromPlayer);if (res!=0){return false;}else{fromPlayer->m_bAi = false;{ //通知当事人S2CPacketEnterRoom packet;packet.m_res = 0;packet.roomID = roomID;packet.roomType = room->m_roomType;strcpy(packet.roomName,room->m_roomName);packet.chiefPlayerID = room->m_chiefPlayerID;packet.roomSlot = fromPlayer->m_roomSlot;MyRoomInfo* info = G_SyncMyRoomInfo;info->m_roomPlayers.clear();MyRoomPlayerInfo playerinfo;NetPlayer* roomPlayer;for (int i=0;i<MaxRoomPlayer;++i){if (room->m_players[i]){roomPlayer = room->m_players[i];playerinfo.roomSlot = roomPlayer->m_roomSlot;strcpy(playerinfo.playerName,roomPlayer->m_playerName);playerinfo.playerID = roomPlayer->ObjID();playerinfo.level = roomPlayer->m_level;strcpy(playerinfo.icon,roomPlayer->m_headIcon);playerinfo.isAI = roomPlayer->m_bAi;//avatarif (roomPlayer->m_bAi){playerinfo.style = dynamic_cast<MiniAiNetPlayer*>(roomPlayer)->m_npcStyle;}else{playerinfo.style = roomPlayer->GetStyle()->ID;}WeaponPart* weaponPart = roomPlayer->GetPart<WeaponPart>();playerinfo.weaponNum = weaponPart->m_weaponWearList.size();int i = 0;for (NetWeaponPtrList::iterator it=weaponPart->m_weaponWearList.begin();it!=weaponPart->m_weaponWearList.end();++it){playerinfo.weaponStyle[i] = (*it)->StyleID();playerinfo.weaponID[i] = (*it)->ObjID();i++;}info->m_roomPlayers.push_back(playerinfo);}}G_Lobby->SendPacketToPlayer(&packet,fromPlayer);}{//通知房间内其它玩家S2CPacketOtherEnterRoom packet;packet.m_res = 0;packet.roomID = roomID;packet.playerID = fromPlayer->ObjID();packet.roomSlot = fromPlayer->m_roomSlot;packet.level = fromPlayer->m_level;strcpy(packet.playerName,fromPlayer->m_playerName);//avatarpacket.style = fromPlayer->GetStyle()->ID;WeaponPart* weaponPart = fromPlayer->GetPart<WeaponPart>();packet.weaponNum = weaponPart->m_weaponWearList.size();int i = 0;for (NetWeaponPtrList::iterator it=weaponPart->m_weaponWearList.begin();it!=weaponPart->m_weaponWearList.end();++it){packet.weaponStyle[i] = (*it)->StyleID();packet.weaponID[i] = (*it)->ObjID();i++;}room->SendPacketToRoomOther(&packet,fromPlayer);}{//通知大厅内其它玩家房间列表改变S2CPacketRoomListUpdate packet;packet.updateType = S2CPacketRoomListUpdate::Room_UpdateMember;packet.roominfo.roomID = roomID;G_Lobby->SendPacketToLobbyAll(&packet);}fromPlayer->m_playerLocation = PL_INROOM;{//玩家列表改变S2CPacketPlayerListUpdate packet;packet.updateType = S2CPacketPlayerListUpdate::Player_UpdateLocation;packet.playerinfo.playerID = fromPlayer->ObjID();packet.playerinfo.playerLocation = fromPlayer->m_playerLocation;G_Lobby->SendPacketToLobbyAll(&packet);}}return true;
}//==================^_^==================^_^==================^_^==================^_^
bool C2SPacketCreateAiRoomPlayer::Process()
{NetPlayer* fromPlayer = G_Lobby->GetPlayerBySocket(m_fromSocket);if(fromPlayer==NULL) return false;NetRoom* room = G_Lobby->GetRoom(roomID);if(room==NULL) {S2CPacketCreateAiRoomPlayer packet;packet.m_res = S2CPacketCreateAiRoomPlayer::Err_NoRoom;//房间不存在G_Lobby->SendPacketToPlayer(&packet,fromPlayer);G_Server->LogSub("[%d] C2SPacketCreateAiRoomPlayer房间不存在。\n",roomID);return false;}if(room->m_chiefPlayerID != fromPlayer->ObjID()){S2CPacketCreateAiRoomPlayer packet;packet.m_res = S2CPacketCreateAiRoomPlayer::Err_NotChief;//不是房主G_Lobby->SendPacketToPlayer(&packet,fromPlayer);G_Server->LogSub("[%d] C2SPacketCreateAiRoomPlayer不是房主。\n",roomID);return false;}//ai玩家允许重名//NetPlayer* aiplayer = room->GetPlayerByName(playerName);//if(aiplayer!=NULL) //{// S2CPacketCreateAiRoomPlayer packet;// packet.m_res = S2CPacketCreateAiRoomPlayer::Err_AlreadyName;//已经存在同名玩家// G_Server->SendPacketToPlayer(&packet,m_fromSocket);// return false;//}if (room->IsFull()){S2CPacketCreateAiRoomPlayer packet;packet.m_res = S2CPacketCreateAiRoomPlayer::Err_FullRoom;//满员G_Lobby->SendPacketToPlayer(&packet,fromPlayer);G_Server->LogSub("[%d] C2SPacketCreateAiRoomPlayer房间满员。\n",roomID);return false;}MiniAiNetPlayer* aiplayer = new MiniAiNetPlayer;int res = room->AddPlayer(aiplayer);if (res!=0){delete aiplayer;}else{aiplayer->m_bAi = true;aiplayer->m_npcStyle = npcStyle;aiplayer->SetID(G_Lobby->NextValidMonsterID());Ptr2Socket(aiplayer->m_socket) = Ptr2Socket(m_fromSocket);sprintf(aiplayer->m_playerName,playerName);static int test = 0;//从数据库读取一系列数据aiplayer->m_level = level;sprintf(aiplayer->m_headIcon,"default%d",test%4);test++;//aiplayer->npcStyle = npcStyle;{//通知房间内所有玩家S2CPacketAiEnterRoom packet;packet.m_res = 0;packet.roomID = roomID;packet.playerID = aiplayer->ObjID();packet.roomSlot = aiplayer->m_roomSlot;packet.level = aiplayer->m_level;strcpy(packet.playerName,aiplayer->m_playerName);packet.style = npcStyle;room->SendPacketToRoomAll(&packet,false);}{//通知大厅内其它玩家房间列表改变S2CPacketRoomListUpdate packet;packet.updateType = S2CPacketRoomListUpdate::Room_UpdateMember;packet.roominfo.roomID = roomID;G_Lobby->SendPacketToLobbyAll(&packet);}}return true;
}//==================^_^==================^_^==================^_^==================^_^
bool C2SPacketExitRoom::Process()
{//真人, ai退出会发C2SPacketKickOtherRoomPlayerNetPlayer* fromPlayer = G_Lobby->GetPlayerBySocket(m_fromSocket);if(fromPlayer==NULL) {//S2CPacketExitRoom packet;//packet.m_res = S2CPacketExitRoom::Err_NotInRoom;//玩家不存在//G_Server->SendPacketToPlayer(&packet,m_fromSocket);//G_Server->LogSub("[%d] C2SPacketExitRoom玩家不存在。\n",playerID);return false;}//assert fromPlayer->m_roomID == roomIDroomID = fromPlayer->m_roomID;//如果游戏正在进行中,不会发生exit消息,而是直接发送gameend消息。NetRoom* room = G_Lobby->GetRoom(roomID);if(room==NULL) {S2CPacketExitRoom packet;packet.m_res = S2CPacketExitRoom::Err_NoRoom;//房间不存在G_Lobby->SendPacketToPlayer(&packet,fromPlayer);G_Server->LogSub("[%d] C2SPacketExitRoom房间不存在。\n",roomID);return false;}if(room->m_roomState == RS_Gaming) {S2CPacketExitRoom packet;packet.m_res = S2CPacketExitRoom::Err_Gaming;//游戏进行中不能退出G_Lobby->SendPacketToPlayer(&packet,fromPlayer);G_Server->LogSub("[%d] C2SPacketExitRoom游戏进行中不能退出房间。\n",roomID);return false;}int playerID = fromPlayer->ObjID();int res = room->RemovePlayer(playerID);if (res!=0){S2CPacketExitRoom packet;packet.m_res = S2CPacketExitRoom::Err_NotInRoom;//不在房间内G_Lobby->SendPacketToPlayer(&packet,fromPlayer);G_Server->LogSub("[%d] C2SPacketExitRoom不在房间内。\n",playerID);return false;}else{if(room->GetRealPlayerNum()==0){//最后一个退出,解散房间G_Lobby->RemoveRoom(roomID);{ //通知当事人S2CPacketExitRoom packet;packet.m_res = 0;G_Lobby->SendPacketToPlayer(&packet,fromPlayer);}{//通知大厅内其它玩家房间列表改变S2CPacketRoomListUpdate packet;packet.updateType = S2CPacketRoomListUpdate::Room_Remove;packet.roominfo.roomID = roomID;G_Lobby->SendPacketToLobbyAll(&packet);}{fromPlayer->m_roomID = -1;fromPlayer->m_playerLocation = PL_INLOBBY;//玩家列表改变S2CPacketPlayerListUpdate packet;packet.updateType = S2CPacketPlayerListUpdate::Player_UpdateLocation;packet.playerinfo.playerID = playerID;packet.playerinfo.playerLocation = fromPlayer->m_playerLocation;G_Lobby->SendPacketToLobbyAll(&packet);}}else{//不是最后一个退出if (room->m_chiefPlayerID == playerID){//房主退出,变更房主room->AutoChief();S2CPacketRoomUpdate packet;packet.roomID = room->m_roomID;packet.chiefID = room->m_chiefPlayerID;packet.roomType = room->m_roomType;strcpy(packet.roomName,room->m_roomName);room->SendPacketToRoomAll(&packet);}{ //通知当事人S2CPacketExitRoom packet;packet.m_res = 0;G_Lobby->SendPacketToPlayer(&packet,fromPlayer);}{//通知房间内其它玩家S2CPacketOtherExitRoom packet;packet.m_res = 0;packet.roomID = roomID;packet.playerID = playerID;//strcpy(packet.roomName,roomName);room->SendPacketToRoomOther(&packet,fromPlayer);}{//通知大厅内其它玩家房间列表改变S2CPacketRoomListUpdate packet;packet.updateType = S2CPacketRoomListUpdate::Room_UpdateMember;packet.roominfo.roomID = roomID;G_Lobby->SendPacketToLobbyAll(&packet);}{fromPlayer->m_roomID = -1;fromPlayer->m_playerLocation = PL_INLOBBY;//玩家列表改变S2CPacketPlayerListUpdate packet;packet.updateType = S2CPacketPlayerListUpdate::Player_UpdateLocation;packet.playerinfo.playerID = playerID;packet.playerinfo.playerLocation = fromPlayer->m_playerLocation;G_Lobby->SendPacketToLobbyAll(&packet);}}}return true;
}
//==================^_^==================^_^==================^_^==================^_^
bool C2SPacketKickOtherRoomPlayer::Process()
{可能是真人或ai, ai只存在于房间列表//assert player->m_roomID == roomIDNetRoom* room = G_Lobby->GetRoom(roomID);NetPlayer* fromPlayer = G_Lobby->GetPlayerBySocket(m_fromSocket);if(room==NULL) {S2CPacketExitRoom packet;packet.m_res = S2CPacketExitRoom::Err_NoRoom;//房间不存在G_Lobby->SendPacketToPlayer(&packet,fromPlayer);G_Server->LogSub("[%d] C2SPacketExitRoom房间不存在。\n",roomID);return false;}//不是房主没有权限if(room->m_chiefPlayerID!=fromPlayer->ObjID()){// S2CPacketKickOtherRoomPlayer packet;// packet.m_res = S2CPacketKickOtherRoomPlayer::Err_NotChief;// G_Lobby->SendPacketToPlayer(&packet,fromPlayer);G_Server->LogSub("[%d] C2SPacketKickOtherRoomPlayer不是房主没有权限。\n",roomID);return false;}//真人或aiNetPlayer* beKickPlayer = room->GetPlayerByID(playerID);if(beKickPlayer==NULL) {S2CPacketExitRoom packet;packet.m_res = S2CPacketExitRoom::Err_NotInRoom;//不在房间内G_Lobby->SendPacketToPlayer(&packet,fromPlayer);G_Server->LogSub("[%d] C2SPacketExitRoom玩家不存在。\n",playerID);return false;}bool bAi = beKickPlayer->m_bAi;int res = room->RemovePlayer(playerID);if (res!=0){S2CPacketExitRoom packet;packet.m_res = S2CPacketExitRoom::Err_NotInRoom;//不在房间内G_Lobby->SendPacketToPlayer(&packet,fromPlayer);G_Server->LogSub("[%d] C2SPacketExitRoom不在房间内。\n",playerID);return false;}else{//房主最后踢了房主if(room->GetRealPlayerNum()==0){//最后一个退出,解散房间G_Lobby->RemoveRoom(roomID);{//通知被踢玩家S2CPacketExitRoom packet;packet.m_res = 0;//strcpy(packet.roomName,roomName);G_Server->SendPacketToSocket(&packet,beKickPlayer->m_socket);}{//通知大厅内其它玩家房间列表改变S2CPacketRoomListUpdate packet;packet.updateType = S2CPacketRoomListUpdate::Room_Remove;packet.roominfo.roomID = roomID;G_Lobby->SendPacketToLobbyAll(&packet);}{beKickPlayer->m_roomID = -1;beKickPlayer->m_playerLocation = PL_INLOBBY;//玩家列表改变S2CPacketPlayerListUpdate packet;packet.updateType = S2CPacketPlayerListUpdate::Player_UpdateLocation;packet.playerinfo.playerID = playerID;packet.playerinfo.playerLocation = beKickPlayer->m_playerLocation;G_Lobby->SendPacketToLobbyAll(&packet);}}else{//房主踢了房主if (room->m_chiefPlayerID == playerID){//房主退出,变更房主room->AutoChief();S2CPacketRoomUpdate packet;packet.roomID = room->m_roomID;packet.chiefID = room->m_chiefPlayerID;packet.roomType = room->m_roomType;strcpy(packet.roomName,room->m_roomName);room->SendPacketToRoomAll(&packet);}if(bAi){//通知房间内所有玩家S2CPacketOtherExitRoom packet;packet.m_res = 0;packet.roomID = roomID;packet.playerID = playerID;//strcpy(packet.roomName,roomName);room->SendPacketToRoomAll(&packet);}else{{//通知被踢玩家S2CPacketExitRoom packet;packet.m_res = 0;//strcpy(packet.roomName,roomName);G_Server->SendPacketToSocket(&packet,beKickPlayer->m_socket);}{//通知被踢除外的玩家S2CPacketOtherExitRoom packet;packet.m_res = 0;packet.roomID = roomID;packet.playerID = beKickPlayer->ObjID();//strcpy(packet.roomName,roomName);room->SendPacketToRoomOther(&packet,beKickPlayer);}}{//通知大厅内其它玩家房间列表改变S2CPacketRoomListUpdate packet;packet.updateType = S2CPacketRoomListUpdate::Room_UpdateMember;packet.roominfo.roomID = roomID;G_Lobby->SendPacketToLobbyAll(&packet);}if(bAi==false){beKickPlayer->m_roomID = -1;beKickPlayer->m_playerLocation = PL_INLOBBY;//玩家列表改变S2CPacketPlayerListUpdate packet;packet.updateType = S2CPacketPlayerListUpdate::Player_UpdateLocation;packet.playerinfo.playerID = playerID;packet.playerinfo.playerLocation = beKickPlayer->m_playerLocation;G_Lobby->SendPacketToLobbyAll(&packet);}}}return true;
}bool C2SPacketRoomUpdate::Process()
{NetPlayer* fromPlayer = G_Lobby->GetPlayerBySocket(m_fromSocket);if(fromPlayer==NULL) return false;NetRoom* room = G_Lobby->GetRoom(roomID);if(room==NULL) {S2CPacketExitRoom packet;packet.m_res = S2CPacketEnterRoom::Err_NoRoom;//房间不存在G_Lobby->SendPacketToPlayer(&packet,fromPlayer);G_Server->LogSub("[%d] C2SPacketRoomUpdate房间不存在。\n",roomID);return false;}if(room->m_chiefPlayerID!=fromPlayer->ObjID()){//不是房主 没有权限S2CPacketRoomUpdate packet;packet.m_res = -1;room->SendPacketToRoomAll(&packet);}else{//房主退出,变更房主room->m_chiefPlayerID = chiefID;room->m_roomType = (MiniGameRoomType)roomType;strcpy(room->m_roomName,roomName);S2CPacketRoomUpdate packet;packet.roomID = room->m_roomID;packet.chiefID = room->m_chiefPlayerID;packet.roomType = room->m_roomType;strcpy(packet.roomName,room->m_roomName);room->SendPacketToRoomAll(&packet);{//通知大厅内其它玩家房间列表改变S2CPacketRoomListUpdate packet;packet.updateType = S2CPacketRoomListUpdate::Room_Update;packet.roominfo.roomID = roomID;packet.roominfo.roomType = room->m_roomType;packet.roominfo.ownerID = room->m_chiefPlayerID;strcpy(packet.roominfo.roomName,room->m_roomName);G_Lobby->SendPacketToLobbyAll(&packet);}}return true;
}//==================^_^==================^_^==================^_^==================^_^
bool C2SPacketRoomPlayerUpdate::Process()
{NetPlayer* fromPlayer = G_Lobby->GetPlayerBySocket(m_fromSocket);if(fromPlayer==NULL) return false;int roomID = fromPlayer->m_roomID;NetRoom* room = G_Lobby->GetRoom(roomID);if(room==NULL) {S2CPacketExitRoom packet;packet.m_res = S2CPacketEnterRoom::Err_NoRoom;//房间不存在G_Lobby->SendPacketToPlayer(&packet,fromPlayer);
// G_Server->LogSub("[%d] C2SPacketRoomUpdate房间不存在。\n",roomID);return false;}if (updateType == RoomPlayer_AvatarChange){//不会收到此类型,会收到weapon update}else if (updateType == RoomPlayer_SlotChange){S2CPacketRoomPlayerUpdate packet;packet.updateType = updateType;//房主可以变更其它人的位置NetPlayer* changePlayer = G_Lobby->GetPlayerByID(playerID);NetPlayer* beChangePlayer = room->GetPlayerBySlot(toRoomSlot);if(changePlayer==NULL) {
// packet.m_res = S2CPacketRoomPlayerUpdate::Err_NoPL;G_Lobby->SendPacketToPlayer(&packet,fromPlayer);return false;}else if (room->m_chiefPlayerID!=fromPlayer->ObjID()&&(changePlayer!=fromPlayer || beChangePlayer!=NULL)){//不是房主 没有权限packet.m_res = -1;G_Lobby->SendPacketToPlayer(&packet,fromPlayer);return false;}else{//成功换队if (beChangePlayer){Swap(changePlayer->m_roomSlot,beChangePlayer->m_roomSlot);packet.playerID = beChangePlayer->ObjID();packet.toRoomSlot = beChangePlayer->m_roomSlot;room->SendPacketToRoomAll(&packet);}else{changePlayer->m_roomSlot = toRoomSlot;}packet.m_res = 0;packet.playerID = changePlayer->ObjID();packet.toRoomSlot = changePlayer->m_roomSlot;room->SendPacketToRoomAll(&packet);{//通知大厅内其它玩家房间列表改变S2CPacketRoomListUpdate packet;packet.updateType = S2CPacketRoomListUpdate::Room_Update;packet.roominfo.roomID = roomID;packet.roominfo.roomType = room->m_roomType;packet.roominfo.ownerID = room->m_chiefPlayerID;strcpy(packet.roominfo.roomName,room->m_roomName);G_Lobby->SendPacketToLobbyAll(&packet);}}}else if (updateType == RoomPlayer_StyleChange){S2CPacketRoomPlayerUpdate packet;packet.updateType = updateType;NetPlayer* changePlayer = G_Lobby->GetPlayerByID(playerID);if(changePlayer==NULL) {// packet.m_res = S2CPacketRoomPlayerUpdate::Err_NoPL;G_Lobby->SendPacketToPlayer(&packet,fromPlayer);return false;}else {packet.miniPlayerStyle = miniPlayerStyle;G_Lobby->SendPacketToPlayer(&packet,fromPlayer);return false;}}return true;
}
完
网络游戏编程-联机版植物大战虫子相关推荐
- 谈 Scratch 版“植物大战僵尸”
请先查看 Scratch经典游戏作品:植物大战僵尸 并下载资源. Python 版"植物大战僵尸"下载链接:https://download.csdn.net/download ...
- 基于Netty的联机版坦克大战
基于Netty的联机版坦克大战 项目介绍 项目github地址:基于Netty的联机版坦克大战 该项目实现了联机版坦克大战,项目包括客户端与服务端 项目使用技术: 使用Netty实现客户端和服务端之间 ...
- 基于ComblockEngine+Unity的联机版坦克大战(一)
文章目录 阶段目标 环境搭建 流程设计 相关说明 相关代码 上述源码地址 写在前面的一段话: 之前准备用LuaServer写一个简单moba手游,后来觉得,LuaServer毕竟是前公司内部的产品,不 ...
- 植物大战僵尸java圣诞版,植物大战僵尸2圣诞节版
游戏介绍 植物大战僵尸2圣诞节版是一款全新的版本,植物们与僵尸们史诗般的斗争一触即发的展开,趣味性十足的玩法内容,超多的关卡.不同的游戏模式带给玩家无穷乐趣,喜欢的小伙伴们快来下载植物大战僵尸2圣诞节 ...
- 植物大战僵尸java圣诞版,植物大战僵尸2国际版圣诞节版
植物大战僵尸2国际版圣诞节版是一款基于圣诞节开发的游戏版本,在植物大战僵尸2国际版圣诞节版中小伙伴可以体验到一系列非常精致的圣诞节游戏主题,还有新的植物和关卡哦,操作较为简单可玩性相当不错,感兴趣的小 ...
- 植物大战僵尸java圣诞版,植物大战僵尸圣诞版-植物大战僵尸2圣诞节版v1.9.1 安卓版-腾牛安卓网...
植物大战僵尸2圣诞节版是一款经典的塔防类手机休闲游戏,要考验玩家的脑力,还有丰富的想象力,将不同功能的植物组合在一起玩抗击僵尸,圣诞版到来,僵尸也换上新衣服,变的更加强力,快用你植物进行反击吧. 官方 ...
- 植物大战僵尸android版手机版,植物大战僵尸95版手机版
植物大战僵尸95版是近期非常不错的一款经典怀旧休闲系列游戏,游戏中玩家能体验到非常不错的植物大战僵尸策略世界的完美乐趣,多种不同的趣味游戏内容,经典的休闲游戏时光,给你带来最新的冒险之旅,相信不少的用 ...
- [Java]用面向对象的知识来做一个简易版植物大战僵尸
package java07;//先设置植物的基础属性 public class Zhiwu {String name;int hp;int attack;//构造方法public Zhiwu(Str ...
- Python编程|手把手教植物大战僵尸,代码开源
前言 如题,手把手教Python实现植物大战僵尸游戏,代码简单易学,无需额外安装Python包,只要有pygame即可,文末获取全部素材及源代码~ 视频演示效果:https://www.bilibil ...
最新文章
- 网博士自助建站系统_自助建站的优缺点介绍
- java解决特殊字符输出
- 注释,无处不在的注释
- Linux系统调用Hook姿势总结
- Spring Boot(4)--- spring boot的三种启动方式
- linux内存管理(十二)-直接页面回收
- oracle传输表空间功能测试(含详细过程)
- linux采用scp命令拷贝文件到本地,拷贝本地文件到远程服务器
- 综述|线结构光中心提取算法研究
- swift——富文本文字的简单使用
- Kaggle Tabular Playground Series - Jan 2022 学习笔记2(使用时间序列的线性回归)
- Coredump-N, segfault at 0 ip 0000000000000000 sp; 被kernel 抓到
- 金仓数据库KingbaseES服务启动失败原因
- MobaXterm 保持连接
- 藏语计算机基础知识,2017年青海民族大学计算机学院738藏语与现代汉语基础考研题库...
- 【问题解决】java.sql.SQLException: null, message from server: “Host ‘xxx.xx.xx.xxx‘ is blocked because of
- python多个函数_请教:一个类中可以定义多个同名函数?
- 怎么教你如何查看电脑的蓝牙版本【解决方案】
- html 实现格子效果图,css 实现的九宫格图片展示
- Python 讲堂 parse_args()详解
热门文章
- vue3导入vant UI库
- Jmeter 读取本地json值并修改
- java的getshape()_World Wind Java开发之六——解析shape文件(转)
- 微云存照片会变模糊吗_深度剖析微云台:吓人的技术还是没啥用?
- Docker——Docker 常用命令
- ERR_FAILED 200 解决方案
- 2016php笔试题
- 如何把sql查询出来的结果当做另一个sql的条件查询
- 真正可以在线上编辑的PDF免费工具
- windows自带压缩工具 makecab压缩 expand解压 解决ERROR: is not a file