开篇先说明,这个游戏制作也是我跟随别人的教程制作的游戏,因此想要了解更多的内容可以去看siki老师的视频,我这里做笔记的目的有两个,一个是帮助喜欢看文字版教程的朋友进一步的学习,一个是保存自己在学习中的一个逻辑和思路,如果看到这个视频会对你有帮助,那就再好不过了,当然这里的话是其中百分之30的内容,后面的制作我会放在我的其他博客下继续

1.    关于IP和端口号的知识

我们经常申请的端口号是49152-65535,1024以前的基本是知名端口

2.    绑定我们的IP和端口号

namespace tcp服务器端

{

classProgram

{

staticvoid Main(string[] args)

{

//首先,创建我们的Socket,是ipv4的地址,以流的形式传输,使用的是tcp协议

Socketmysocket = newSocket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

//第二部,创建我们的ip地址

IPAddress ipaddress = IPAddress.Parse("127.0.0.1");

//将ip地址和我们指定的端口号进行绑定

IPEndPoint ipendpoint = new IPEndPoint(ipaddress, 88);

//将socket和端口号进行绑定

mysocket.Bind(ipendpoint);

}

}

}

3.  创建我们的服务器端

这里的话,我们让socket开始监听以后,就创建一个接受客户端连接的socket,我们一般是用receive和send来收发数据,这里要注意传递的数据都是字节数组,我们要将它转变成

classProgram

{

staticvoid Main(string[] args)

{

//首先,创建我们的Socket,是ipv4的地址,以流的形式传输,使用的是tcp协议

Socket mysocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);

//第二部,创建我们的ip地址

IPAddress ipaddress =IPAddress.Parse("127.0.0.1");

//将ip地址和我们指定的端口号进行绑定

IPEndPoint ipendpoint = new IPEndPoint(ipaddress,88);

//将socket和端口号进行绑定

mysocket.Bind(ipendpoint);

mysocket.Listen(0);

Socket clientSocket =mysocket.Accept();//接受客户端连接

//向客户端发送一条信息

string msg = "hello客户端";

byte[] data = System.Text.Encoding.UTF8.GetBytes(msg);

clientSocket.Send(data);

//从客户端接受数据

byte[] getdata = newbyte[1024];

int count = clientSocket.Receive(getdata);

//这里表示用getdata这个数组来接受客户端传递的数据,再将传递数据长度的字节数组转换成字符串再输出

string msg2 = System.Text.Encoding.UTF8.GetString(getdata, 0,count);

Console.WriteLine(msg2);

clientSocket.Close();

mysocket.Close();

}

4.    创建我们的客户端

客户端也是一样创建自己的socket,和服务器提供的端口相连接,然后再收发信息即可

5.  using System;

6.  using System.Collections.Generic;

7.  using System.Linq;

8.  using System.Text;

9.  using System.Threading.Tasks;

10.using System.Net.Sockets;

11.using System.Net;

12.

13.namespace tcp客户端

14.{

15.    classProgram

16.    {

17.        staticvoid Main(string[] args)

18.        {

19.            Socket clientSocket = newSocket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

20.            IPEndPoint iPEndPoint = newIPEndPoint(IPAddress.Parse("10.128.230.79"), 88);

21.            clientSocket.Connect(iPEndPoint);

22.            //客户端接受输出

23.            byte[] receivedata = newbyte[1024];

24.            int count = clientSocket.Receive(receivedata);

25.            string message = Encoding.UTF8.GetString(receivedata, 0,count);

26.            Console.WriteLine(message);

27.

28.            //客户端向服务器发送数据

29.            string msg2 = Console.ReadLine();

30.            clientSocket.Send(Encoding.UTF8.GetBytes(msg2));

31.

32.            Console.ReadKey();

33.            clientSocket.Close();

34.

35.

36.        }

37.    }

38.  }

5.如何异步获取到数据

这里我们使用一个BeginReceive开始异步接受数据,传递过来我们的客户端这个参数,然后重复调用一个AsyncCallBack的方法,通过begin和end来实现多次对于信息的接受

staticbyte[] databuffer = newbyte[1024];

clientSocket.BeginReceive(databuffer, 0, 1024, SocketFlags.None,AsyncCallback, clientSocket);

}

staticvoidAsyncCallback(IAsyncResult ar){

Socket clientSocket = ar.AsyncStateas Socket;

int count = clientSocket.EndReceive(ar);

string msg = Encoding.UTF8.GetString(databuffer, 0, count);

Console.WriteLine("从客户端接受信息" + msg);

clientSocket.BeginReceive(databuffer, 0, 1024, SocketFlags.None,AsyncCallback, clientSocket);

}

当然客户端的话,只要while True发送信息即可

//客户端向服务器发送数据

while (true)

{

string msg2 = Console.ReadLine();

clientSocket.Send(Encoding.UTF8.GetBytes(msg2));

}

6.异步与客户端建立连接

想要同时和多个客户端进行连接,也是使用一个mySocket.BeginAccept和endAccept来进行开始接受连接和结束连接,多次执行即可实现和多个客户端建立连接

staticvoid StartSocket()

{

//首先,创建我们的Socket,是ipv4的地址,以流的形式传输,使用的是tcp协议

Socket mysocket = newSocket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

//第二部,创建我们的ip地址

IPAddress ipaddress =IPAddress.Parse("10.128.230.79");

//将ip地址和我们指定的端口号进行绑定

IPEndPoint ipendpoint = newIPEndPoint(ipaddress, 88);

//将socket和端口号进行绑定

mysocket.Bind(ipendpoint);

mysocket.Listen(0);

mysocket.BeginAccept(AcceptCallback, mysocket);//异步接受多个客户端的连接

}

staticvoidAcceptCallback(IAsyncResult ar)

{

Socket mysocket = ar.AsyncState as Socket;

//这里表示完成一次客户端的连接

Socket clientSocket=mysocket.EndAccept(ar);

//向客户端发送一条信息

string msg = "hello客户端";

byte[] data = System.Text.Encoding.UTF8.GetBytes(msg);

clientSocket.Send(data);

//我们异步从客户端那里接受多条数据

clientSocket.BeginReceive(databuffer, 0, 1024, SocketFlags.None,AsyncCallback, clientSocket);

//这里就是继续等待下一个客户端的连接

mysocket.BeginAccept(AcceptCallback, mysocket);

}

7.处理客户端关闭的异常

这里我们使用try catch捕捉异常就可以处理客户端远程强制退出的问题,当我们正常关闭的时候,客户端会向服务器发送长度为0的数据,所以我们这里判断一下如果为0,就关闭与客户端的连接

try

{

clientSocket = ar.AsyncState as Socket;

int count = clientSocket.EndReceive(ar);

if (count == 0)

{

clientSocket.Close();

}

string msg = Encoding.UTF8.GetString(databuffer, 0, count);

Console.WriteLine("从客户端接受信息" + msg);

clientSocket.BeginReceive(databuffer, 0, 1024, SocketFlags.None,AsyncCallback, clientSocket);

}

catch(Exception e)

{

Console.WriteLine(e);

if (clientSocket != null)

{

clientSocket.Close();

}

}

这里我们也可以让客户端正常关闭

当输出c,就断开连接

while (true)

{

string msg2 = Console.ReadLine();

clientSocket.Send(Encoding.UTF8.GetBytes(msg2));

if (msg2 == "c")

{

clientSocket.Close();

return;

}

}

8.tcp中的粘包和分包

这个粘包和分包是tcp的优化性能

粘包是如果信息过短就会将多次信息打一个包发送,如果信息过长则会分成多个包进行发送

在游戏开发中,我们常会产生多次小信息的传输,这里来讲解解决方案

我们可以在数值传输前加一个数据长度作为判断,假设你定义为40个字节,如果长度小于40个字节,就继续接受,如果到达40个字节,就进行传输

这里当我们要传递一个值类型的数据,就可以用BitConverter.GetBytes这种方式,将它转变一个四个字节的字节数组,这样就可以避免粘包的问题

下面就是一个解决的例子,我们用这个方法对字符串进行转换即可,我们把它做成一个工具类可以随时进行调用

//在这里专门创建一个方法,用来计算传递的字符串的长度,并且将它组拼成一个新的数组用来避免粘包的产生

publicstaticbyte[] GetBytes(string data)

{

//将字符串转变为数组

byte[] databuf = Encoding.UTF8.GetBytes(data);

//计算字节数组长度

int datalength = databuf.Length;

//将它转变为4个字节长度的字节数组用来计算长度

byte[]countLength= BitConverter.GetBytes(datalength);

//组拼成新的字节数组,即长度加原本的数组

byte[] newByte = countLength.Concat(databuf).ToArray();

return newByte;

}

9.关于服务器如何去接受客户端分包的信息

首先,我们让客户端向服务器循环发送100条输出,这种数据如果不进行解析就会出现粘包的情况

for (int i = 0; i < 100;i++)

{

//循环向客户端输出100条数据

clientSocket.Send(Message.GetBytes(i.ToString()));

}

然后,我们创建一个Message类来处理各种客户端传递过来的信息,首先,定义一个数组来接受客户端的信息,然后一个startIndex来记录当前传递过来的数据在数组中的位置,RemainLength记录剩余还能使用的长度,addCount用来更新当前数组存放到什么位置

这里讲解一下我们的解析方法,当更新完StartIndex后,我们就要判断它的长度,首先用BitConvert.toInt32来获取数组的长度,如果超过了开始我们定义的数据长度的话,那就是我们现在解析完全的数据,我们用Encoding.Utf8来将字节数组进行转换,然后用Array.copy()来更新我们的数组,也就是将解析完全的数据移除数组,然后再更新我们的StartIndex

classMessage

{

//每条信息存储的数组

byte[] data= newbyte[1024];

//这个是记录当前数组存储到第几个字节的标志位

intstartIndex = 0;

publicbyte[] Data

{

get { return data; }

}

publicint StartIndex

{

get { return startIndex; }

}

publicint RemainLength

{

get { return data.Length - startIndex; }

}

publicvoid AddCount(int count)

{

startIndex += count;

}

publicvoid ReadMessage()

{

while (true)

{

if (startIndex <= 4)

{

return;

}

//读取数组的长度,自动省略前四个字节长度

int count = BitConverter.ToInt32(data, 0);

if (startIndex - 4 >= count)

{

String s =Encoding.UTF8.GetString(data, 4, count);

Console.WriteLine("解析了一条数据:" + s);

Array.Copy(data, count + 4,data, 0, startIndex - count - 4);

startIndex -= (count + 4);

}

else { break; }

}

}

}

然后,我们在服务器中调用这个Message来的方法,首先我们接收到客户端的信息,存放到定义的数组里面,从开始索引开始存储,然后可存储的长度和数组剩余长度有关,如果超出的话就不会再进行接受

每次当我们接受到数据的时候,我们就获取数据长度,然后更新StartIndex,然后调用Message的解析方法,这样就可以循环解析数据并且避免了粘包的问题

lientSocket.BeginReceive(msg3.Data,msg3.StartIndex, msg3.RemainLength, SocketFlags.None, AsyncCallback,clientSocket);

//这里就是继续等待下一个客户端的连接

mysocket.BeginAccept(AcceptCallback, mysocket);

}

staticvoidAsyncCallback(IAsyncResult ar) {

Socket clientSocket = null;

try

{

clientSocket = ar.AsyncState as Socket;

int count = clientSocket.EndReceive(ar);

//解析接收到的数据

msg3.AddCount(count);

msg3.ReadMessage();

if (count == 0)

{

clientSocket.Close();

}

clientSocket.BeginReceive(msg3.Data, msg3.StartIndex, msg3.RemainLength,SocketFlags.None, AsyncCallback, clientSocket);

10.使用mysql做查询操作

因为我们的丛林战争要使用mysql做数据库,这里来讲解一下mysql用法,首先我们要用msql创建一个数据库,定义好它的名字,连接ip地址,端口号,连接的用户名和密码,接着就是打开连接,创建命令语句,执行命令,然后按照列名读取出数据,具体操作可以参照代码和注释啦

classProgram

{

staticvoid Main(string[] args)

{

string con = "Database=test_007;DataSource=localhost;port=3306;User Id=root;Password=1234;";

//创建数据库的连接

MySqlConnection coon = newMySqlConnection(con);

//开启和数据库的连接

coon.Open();

//书写数据库操作的命令和指定的连接

MySqlCommand command = new MySqlCommand("select * from user",coon);

//执行查询操作并且将信息传递给reader

MySqlDataReader reader =command.ExecuteReader();

//这个方法会查询一行数据然后判断下一行是否还有数据

while (reader.Read())

{

//根据列名获得数据

string username = reader.GetString("username");

string password = reader.GetString("password");

Console.WriteLine(username + ":  " + password);

}

//执行结束关闭连接

reader.Close();

coon.Close();

Console.ReadKey();

}

}

11.用Mysql做插入操作

这里的话我们使用的是字符串组拼进行的数据库操作,但是这样的操作一个问题,可能会因为用户的恶意操作对数据库进行污染,类似于密码为deletefromuser这种

stringusername = "haha";string password = "111";

MySqlCommand cmd = new MySqlCommand("insert into user set username='" + username + "'" + ",password='" + password + "'",coon);

//进行不查询的sql语句

cmd.ExecuteNonQuery();

解决这个问题的方法就是用@进行注册变量,然后用AddWithValue来进行组拼

stringusername = "haha";string password = "111111";

MySqlCommand cmd = new MySqlCommand("insert into user setusername=@un,password=@pwd",coon);

cmd.Parameters.AddWithValue("un", username);

cmd.Parameters.AddWithValue("pwd", password);

删除和更新的话就是update和delete,操作和上面类似,就介绍到这里了

12.服务器的分层架构

13.初步创建我们的服务器端

这里我们创建出服务器的Socket,提供接受端口号和ip地址创建IpendPoint的方法,并且使用异步的方法接受客户端传递的值

namespace游戏服务器.Sever

{

classSever

{

//这里的话,我们创建我们游戏的服务器端

privateIPEndPoint ipEndpoint;

private  Socket severSocket;

public Sever()

{

}

//根据我们提供的Ip地址和端口号创立连接

public Sever(string ipStr,int port)

{

SetIpAndPort(ipStr, port);

}

publicvoid SetIpAndPort(string ipStr,int port)

{

ipEndpoint = newIPEndPoint(IPAddress.Parse(ipStr), port);

}

publicvoid Start()

{

severSocket = newSocket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

severSocket.Bind(ipEndpoint);

//表示最大连接长度为无限

severSocket.Listen(0);

//接受客户端的连接

severSocket.BeginAccept(AcceptCallBack, null);

}

//处理连接回调

publicvoidAcceptCallBack(IAsyncResult ar)

{

Socket clientSocket =severSocket.EndAccept(ar);

}

}

14.创建我们的客户端

这里的话是处理我们和服务器端的一些交互和客户端要处理的事物,首先,我们要在客户端里创建自身的socket,并且持有到Sever端的Socket,然后我们在Start调用BeginReceive开始异步接受服务器端传递的信息,这里对于粘包分包的问题我们放在其他类里进行处理,然后我们写回调函数来处理接受的,当接收到的信息为0的时候,我们就断开和服务器之间的连接,这里我们用tryCatch来捕捉异常。然后我们书写一个Close方法,来关闭连接,这里,在Sever端我们提供一个方法来移除客户端,当然要先lock再移除,以防止出错

using System;

usingSystem.Collections.Generic;

usingSystem.Linq;

usingSystem.Text;

usingSystem.Threading.Tasks;

usingSystem.Net.Sockets;

usingSystem.Net;

namespace游戏服务器.Sever

{

classClient

{

private SocketclientSocket;

private Seversever;

publicClient()

{

}

publicClient(Socket clientSocket, Sever sever)

{

//持有一个对socket的引用

clientSocket = this.clientSocket;

this.sever = sever;

}

//用来处理和服务器端之间的通信

publicvoid Start()

{

clientSocket.BeginReceive(null, 0, 0,SocketFlags.None, ReceiveCallBack, null);

}

publicvoidReceiveCallBack(IAsyncResult ar)

{

try

{

int count = clientSocket.EndReceive(ar);

if (count == 0)

{

Close();

}

}

catch (Exception e)

{

Close();

Console.WriteLine(e);

}

}

publicvoid Close()

{

if (clientSocket != null)

{

clientSocket.Close();

sever.RemoverClient(this);

}

}

}

}

15.引入Messgae类来对信息进行处理

using System;

usingSystem.Collections.Generic;

usingSystem.Linq;

usingSystem.Text;

usingSystem.Threading.Tasks;

namespace游戏服务器.Sever

{

classMessage

{

//在这里专门创建一个方法,用来计算传递的字符串的长度,并且将它组拼成一个新的数组用来避免粘包的产生

publicstaticbyte[] GetBytes(string data)

{

//将字符串转变为数组

byte[] databuf = Encoding.UTF8.GetBytes(data);

//计算字节数组长度

int datalength = databuf.Length;

//将它转变为4个字节长度的字节数组用来计算长度

byte[] countLength = BitConverter.GetBytes(datalength);

//组拼成新的字节数组,即长度加原本的数组

byte[] newByte = countLength.Concat(databuf).ToArray();

return newByte;

}

}

}

16.完成Controller层的搭建

首先,我们客户端向服务器发出的请求都是通过Controller层来进行处理的,这里来讲解一下如何初步搭建Controller层

首先,我们要创建一个抽象基类BaseController,我们的子类都是继承自这个Controller,不同的需求对应不同的Controller

然后,我们创建一个Common的类库,这个是服务器端和客户端共享的代码,因为客户端向服务器端发出请求的时候,是通过RequestCode发送消息,而服务器端也需要这个RequestCode知道你调用的是哪一个Controller

这里我们创建一个枚举类型的RequestCode和ActionCode类

然后我们在BaseController里创建一个空的RequestCode和一个默认的实现方法

abstractclassBaseController

{

RequestCode requestCode = RequestCode.None;

//子类可以选择性实现这个方法,这个是默认的处理方法

publicvirtualvoid DefaultHandle()

{

}

}

17.

这里来解释一下客户端和服务器端交互的原理

首先,当我们的客户端向服务器端发起了一个请求,比如注册,这时候我们会向服务器的Controller层发送一个数据长度+RequestCode+ActionCode+数据的这样一个信息,数据长度用来解决粘包分包的问题,RequestCode用来判断用哪一个Controller来执行,ActionCode用来判断用哪一个方法来解决这个请求,这两个Code都是int类型的四个字节的数据

然后我们的Controller层也会根据你的请求进行判断,如果是要做出响应的请求,服务器端就会向客户端回发一个数据长度+RequestCode+数据的信息,我们的客户端会根据对应的RequestCode进行相应的反应,比如注册请求的响应,就会回复一个注册成功

这里的话我们一个服务器端可以同时响应多个客户端的请求,而一个客户端只能回应一个服务器的RequestCode

18.创建我们的ControllerMannager

这里的话,我们需要一个类来管理我们的Controller,并且这个管理类为了避免冗余度过高,我们选择用Sever服务器端作为中介者,来进行交互

首先我们要一个字典,用来存储各种Controller,然后我们就要对它进行初始化了

namespace游戏服务器.Controller

{

classControllerManager

{

//这个是用来管理Controller,来决定什么时候调用什么Controller

//首先创建一个字典,用来存储各种Controller

privateDictionary<RequestCode,BaseController> requestDictionary=newDictionary<RequestCode,BaseController>();

publicControllerManager()

{

Init();

}

void Init()

{

//在这里进行初始化

}

}

}

这里我们在Sever构建一个接口,以后其他类想要使用ControllerMannager,都要通过Sever类来执行

private ControllerManagercontrollerManager = newControllerManager();

19.通过ControllerMannager来处理分发的请求

首先,我们要提供一个DefaultController来判断当客户端传递的是没有ActionCode的时候来怎么处理,当然这里首先我们要在BaseController提供一个方法来获得它的RequestCode

publicRequestCode RequestCode

{

get

{

return RequestCode;

}

}

这样我们就可以在ControllerMannager里实例化DefaultController来进行错误处理了

void Init()

{

//给一个处理错误的Controller

DefaultController defaultController= newDefaultController();

requestDictionary.Add(defaultController.RequestCode, defaultController);

}

然后的话,我们就提供一个方法来处理客户端向服务器端请求Controller的方法,首先,我们要传递过来客户端发送的数据,请求的RequestCode和ActionCode,然后我们获得基类的Controller,根据tryGetValue来判断是否存在客户端请求的requestCode是否有对应的Controller,如果有的话,我们就要用Enum里的GetName,根据类型,来获得ActionCode里的值,再将其转变成为字符串类型,这里我们ActionCode里面定义的是None这个值类型,我们将其转换成字符串类型

接下来的话,我们运用的就是C#里的反射机制,通过controller.getType.getMethod,来根据方法名获取到方法信息,这里的话,我们需要引用一下System.Reflection

如果存在这个方法的话,我们就可以用MethodInfo里的invoke方法,来根据参数调用这个方法了,这里参数需要是object[]类型,我们转换一下就可以使用了前面的参数是指在什么下运行,我们指定为controller,这样的话,我们就可以调用方法了,当然这里有个返回值,判断方法调用是否需要给客户端做出回应,这个我们后面再讲

publicvoidHandleRequest(RequestCode requestCode,ActionCode actionCode,string data)

{

BaseController controller;

//判断是否根据对应的RequestCode获取到对应的Controller

bool isGet=requestDictionary.TryGetValue(requestCode, out controller);

if (isGet == false)

{

Console.WriteLine("无法获得到" + requestCode + "对应的Controller");return;

}

//如果有对应的RequestCode的话,我们就要获取到对应的方法名,这里是获取到对应ActionCode里的值

string methodName = Enum.GetName(typeof(ActionCode), actionCode);

//这里是用到C#中的反射机制,将对应类型的对应的方法名的方法信息获得到

MethodInfo mi= controller.GetType().GetMethod(methodName);

if (mi == null)

{

Console.WriteLine("该方法" + methodName + "不存在");

return;

}

//在controller中调用

object[] paramaters = newobject[] { data };

//根据o来判断是否响应

object o= mi.Invoke(controller, paramaters);

}

}

19.处理客户端请求的响应

首先,我们BaseController默认的方法,传递的除了数据以外,还有对应的客户端和服务器端

publicvirtualstring DefaultHandle(string data,Clientclient,Sever sever)

{

//默认不给客户端返回数据

returnnull;

}

然后的话,我们在ControllerMannager中传递对应的服务器

private Sever sever;

publicControllerManager(Sever sever)

{

this.sever = sever;

InitController();

}

这样的话,在Sever端的构造函数里,我们就可以直接构造出ControllerManager并且将自身传递过去

public Sever(string ipStr,int port)

{

controllerManager = new ControllerManager(this);

SetIpAndPort(ipStr, port);

}

然后我们怎么处理对客户端发送消息的响应呢,我们在Sever端里提供一个处理方法,SendMessage,用来作为客户端和ControllerManager的中介,通过这个方法来处理课都俺的信息

publicvoid SendMessage(Clientclient,RequestCode requestCode,string data)

{

//TODO

}

这样我们就可以在ControllerManager里调用sever的这个方法来处理客户端的请求了

object o=mi.Invoke(controller, paramaters);

if (o == null || string.IsNullOrEmpty(o.ToString()))

{

//如果空就不进行处理

return;

}

//通过sever端进行中介来发送数据

sever.SendMessage(client, requestCode, o asstring);

游戏制作-联网对战丛林战争制作笔记(一)相关推荐

  1. 游戏开发-丛林战争制作2

    20.如何解析我们客户端的信息并且交给ControllerManager来进行处理 首先是Message类,参数分别是数据的长度和一个回掉的函数委托,用来重复解析数据,这里的话前四个是数据长度,后面4 ...

  2. c语言军棋源代码,VC++(MSSQL数据库)军棋游戏大厅联网对战#(源代码+可

     摘要:本文详细介绍了一个在Windows环境下的基于游戏大厅框架的四国军棋网络游戏的设计和实现.该游戏可在局域网上联机对战,实现了游戏.聊天.积分等功能.该软件在Windows环境下用VC++6. ...

  3. 如何在完全不懂服务器开发的情况下做一个实时联网对战的微信小游戏

    微信小游戏即将开放?有我们在,你还赶得上! 根据微信官方对外公开的消息,微信小游戏的脚步越来越接近了.它的开发者资格门槛和使用者门槛都很低,以后必将引爆一波"全民开发小游戏"浪潮. ...

  4. Unity3D游戏制作学习记录02——丛林战争

    Unity3D游戏制作学习记录02--丛林战争 Siki学院的视频教程指路牌:http://www.sikiedu.com/course/61. 一.服务器端--消息接收的异步处理 由于之前使用Rec ...

  5. 单机游戏编辑器《铯际毁灭者》制作《布鲁末日》实现即时战略《蓝色警戒》的缺憾弥补

    <铯际毁灭者>是一款可以编辑即时战略游戏的工具,使用该工具制作了<布鲁末日>,适用于热爱<蓝色警戒>但是苦于其地图编辑器的限制及有限地图的缺憾,本演示提供脚本用于编 ...

  6. Unity3D游戏制作学习记录01——丛林战争

    Unity3D游戏制作学习记录01--丛林战争 Siki学院的视频教程指路牌:http://www.sikiedu.com/course/61. 学业繁忙-和朋友一起跟着siki的丛林战争的教程跟着做 ...

  7. 微信塔防小游戏开发教程,唤境引擎制作塔防游戏分享

    今天带来的是塔防游戏制作攻略! 点击这里来下载工程文件, 点击这里可以下载工程中所用的素材哦~ 预览状态时敌人会从四个生成点随机生成,并且会自动寻路绕过黑色墙体走向红色终点.点击黑色墙体可以创建炮塔, ...

  8. [中级教程] 实例教程_次世代游戏场景刀枪武器架子3D模型制作教程

    实例教程_次世代游戏场景刀枪武器架子3D模型制作教程 链接:https://pan.baidu.com/s/1Ra9T8dpcgxVRQajfRD5JLg 密码:kzcv

  9. C语言开发《扫雷》游戏,你从未体验过的联网对战版

    一.简介 单机版扫雷总会有些枯燥,不妨试试联网对战版扫雷! 开发环境:VS2019 + EasyX_20210224. 游戏玩法:左键按下翻开方块,翻开雷判输,或翻开最后一个方块判赢:逃跑或超时会结束 ...

最新文章

  1. 如何书写高质量的jQuery代码
  2. 大数据WEB阶段(十三)JSP(二)JSP标签、EL表达式、JSTL
  3. Nginx出现403 forbidden
  4. jms 如何测试_使用JMSTester对JMS层进行基准测试
  5. 揭示C语言函数调用的本质解析
  6. 【分布式】Zookeeper在大型分布式系统中的应用
  7. mysql 查看autocommit_手把手教你分析Mysql死锁问题
  8. a start job is running for延迟90s的解决办法
  9. 欧几里得的尺规(三等分一个线段)
  10. CentOS7安装MySQL8报错mariadb-libs is obsoleted by mysql-community-libs-8.0.xx-1.el7.x86_64
  11. Centos 7 制作Oracle 12c Docker Images
  12. 地形图测量中的等高线裁剪方法
  13. NR-PRACH接受端如何检测出preambleid和TA的
  14. 新浪微博短视频服务的优化实践
  15. GeekOS操作系统Project4
  16. Ubuntu卸载预装
  17. 超级详细的 shell编程知识讲解 —— 第二部分(全文3万多字,看完之后不想成为高手也难!)
  18. 【算法系列专栏介绍】
  19. Windows 对文件做MD5值校验
  20. 2021.1.25写写日记

热门文章

  1. 如何去掉界面中缅甸语圈圈(JB,JB2)
  2. 图像压缩算法 JPEG2000
  3. 目前比较全面且实用的Java中文名称批量生成器
  4. 戴眼镜最近眼睛很累,感觉有压力一样,眼压过大;摘掉眼镜后,感觉又好一些,求专业人士原因?
  5. 知识 :卷积神经网络性能优化
  6. 连接已失效_手机乐秀视频剪辑器,已解锁会员!
  7. shell脚本之脚本案例
  8. OllyDBG反汇编入门教程
  9. STM32——定时器中断实验
  10. 樱桃键盘驱动在哪下_闭着眼睛也能买的樱桃轴机械键盘——GANSS GS87C分享