文章目录

  • 一、前言
  • 二、Go开发环境搭建(Windows系统)
    • 1、安装Go命令行工具
    • 2、创建GoWorkspace目录
    • 3、配置GOPATH环境变量
    • 4、配置GOPROXY代理
    • 5、安装VSCode
    • 6、VSCode安装Go插件
    • 7、安装Go开发工具链
  • 三、HelloGo 工程
    • 1、创建go脚本: main.go
    • 2、main.go代码
    • 3、生成go.mod文件
    • 4、编译生成可执行程序: go build命令
    • 5、测试运行
  • 四、用Go做个消息广播的服务端
    • 1、思维导图
    • 2、脚本说明
  • 五、开始写服务端Go代码
    • 1、创建项目文件夹和脚本
    • 2、server.go脚本
      • 2.1、成员变量声明
      • 2.2、全局方法,NewServer
      • 2.3、Socket监听连接,Listen和Accept
      • 2.4、启动协程处理用户消息,Handler
      • 2.5、消息广播,通过管道同步
    • 3、user.go脚本
      • 3.1、成员变量声明
      • 3.2、全局方法,NewUser
      • 3.3、用户上线,Online
      • 3.4、用户下线,Offline
      • 3.5、消息处理,DoMessage
    • 4、main.go脚本
    • 5、编译运行
  • 六、Unity客户端
    • 1、创建工程,UnitySocketClient
    • 2、UGUI制作界面
    • 3、C#脚本
      • 3.1、ClientNet.cs脚本
      • 3.2、Main.cs脚本
    • 4、打包客户端
  • 七、运行测试
    • 1、启动Go服务端
    • 2、启动Unity客户端
  • 八、工程源码
  • 九、完毕

一、前言

嗨,大家好,我是新发。
有老同事问我会不会Go语言,人生苦短,Let's Go,我做了一个Go语言基础的思维导图,福利给大家~

嘛,今天就做一个Go语言服务端与Unity通信的小案例吧,效果如下,

工程源码见文章末尾~

二、Go开发环境搭建(Windows系统)

Go语言是一门编译型语言,代码文件以.go为后缀,我们写的.go代码最终要编译为可执行文件(在Windows平台下就是.exe文件),编译需要用到go build命令,go build命令哪来的呢,这就需要我们在系统中安装GO命令行工具。
另外,写.go代码需要一个IDE,推荐使用VSCode,需要而外安装Go插件和工具链。
画个图,方便大家理解~

看起来好像有点小麻烦,不要怕,几分钟搞定,下面我就来教大家~

1、安装Go命令行工具

进入Go官网:https://golang.google.cn/,点击Download Go

然后根据你的操作系统选择对应的文件,它支持WindowsmacOSLinux三个平台,我以Windows为例,点击第一个,如下,

下载完毕后直接双击执行安装即可,

安装完毕后,打开cmd命令行(步骤: 按win + r键,输入cmd按回车),然后执行go version,如果能正常输出版本号,则说明安装成功了,如下,

2、创建GoWorkspace目录

在任意磁盘中创建一个文件夹作为工程 工作空间,建议命名为GoWorkSpace,然后再分别创建binpkgsrc三个文件夹,

三个文件夹的用途如下:

文件夹 用途
bin 用来存放编译后的可执行文件
pkg 用于存放编译后的包文件(一些第三方包文件)
src 是用来存放.go源码文件(就是自己写的.go代码)

3、配置GOPATH环境变量

GOPATH是一个环境变量,用来表明你写的go项目的存放路径,现在我们来设置一下GOPATH环境变量。
我的电脑上鼠标右键,点击属性,然后点击高级系统设置

再点击环境变量

在系统变量下点击新建按钮,


变量名为GOPATH,变量值为刚刚创建的GoWorkSpace路径,然后点击确定

这样,我们的GOPATH环境变量就配置完成了~

4、配置GOPROXY代理

我们在执行go编译时,会自动去下载依赖包,GOPROXY默认配置是:GOPROXY=https://proxy.golang.org,direct,由于国内访问不到,编译时会报错超时,我们需要改成国内的源,打开命令行,执行下面的命令:

go env -w GOPROXY=https://goproxy.cn,direct

如下,

5、安装VSCode

接下来是IDE的安装,建议用VSCode,安装过程很简单,这里不赘述~
VSCode官网:https://code.visualstudio.com/

6、VSCode安装Go插件

VSCode安装完毕后,点击插件安装按钮,搜索go,选择Go插件,点击install按钮,如下,

注:这个Go插件提供了go代码的智能感知、提示、语法高亮、语法检测等功能。

7、安装Go开发工具链

进行go开发还需要下载配套的开发工具链(比如调试器、代码风格格式化等)。
我们打开VSCode,按Ctrl + Shift + P,输入go:install,选择Go: Install/Update Tools,然后全选,最后点击OK按钮,如下,

注:如果你没有Go: Install/Update Tools这个选项,请检查第6步的Go插件是否已正常安装。


耐心等待(大约1分钟左右),下载完毕后可以在VSCode的日志输出中看到All tools successfully installed. You are ready to Go. :),如下,

三、HelloGo 工程

以上,我们的Go开发环境就搭建好了,现在我们来写一个HelloWorld,不,是HelloGo测试一下吧~

1、创建go脚本: main.go

GoWorkSpace/src目录中新建一个HelloGo文件夹,如下,

回到VScode,创建一个main.go脚本,

注:文件名不叫main也可以,不过一般作为程序入口脚本,建议叫main

2、main.go代码

好了,现在我们开始写代码,功能就是打印一句日志:Hello Golang,代码如下:

// 包名,main包为入口包,main包中必须含有一个main方法
package mainimport "fmt"// 程序入口方法,必须叫main
func main() {// 输出日志fmt.Println("Hello Golang")
}

3、生成go.mod文件

go mod全称go modules,在Golang 1.11版本之前,go代码的包依赖没有版本控制的概念,比如你依赖了一个protobuf库,你在go脚本中通过import引入包,如下

import "github.com/micro/protobuf/proto"

它只会从github中下载最新版本的protobuf,可想而知,这对于团队协作是很不友好的,不同人电脑上不同时期引入的第三方包坑内版本存在差异,可能导致程序无法正常工作。
于是呢,在Golang 1.11版本开始,就引入了go mod,由一个go.mod文件来记录依赖包的版本信息。
现在,我们就来生成这个go.mod文件,在VSCode终端中,cd进入HelloGo目录,然后执行命令

go mod init HelloGo

注: 上面的命令的HelloGo是模块名

它会生成一个go.mod文件,如下,

4、编译生成可执行程序: go build命令

go代码最终要生成成可执行程序才能运行,现在我们在HelloGo目录下,执行go build命令,最终它生成了一个HelloGo.exe,如下,

注: 如果要指定生成的exe名字,则可以加上-o参数,例:go build -o MyTest.exe,它就会生成一个MyTest.exe啦~

5、测试运行

现在我们去执行这个HelloGo.exe,可以看到,成功输出了Hello Golang,如下,

如果我们想跳过go build命令,直接测试go脚本,可以使用go run命令,例:

go run main.go

如下,

四、用Go做个消息广播的服务端

接下来,我们用Go来开发一个Socket通信的服务端,实现消息广播的功能吧~

1、思维导图

在开始写代码之前,我们先设计一下服务端的模块,画个图,如下,

2、脚本说明

main.go为程序入口脚本;
server.go负责socket监听和管理;
user.go是用户类脚本,当server.gosocket监听到有客户端连接时,构造一个User对象,后续的socket通信交由user.go脚本代理。

模块很简单,相信大家很容易看懂。

五、开始写服务端Go代码

1、创建项目文件夹和脚本

我们在src目录中创建一个GoSocketServer文件夹,作为项目文件夹,

接着我们在GoSocketServer文件夹中创建main.goserver.gouser.go三个脚本,

2、server.go脚本

我们先封装一下Server类,注意在Go语言中,定义类用的是struct关键字,成员变量名如果是首字母大写,则表示是public的,如果是小写,则表示是private的。

2.1、成员变量声明
// Server.go 脚本
package main// import ...type Server struct {Ip   stringPort int// 在线用户容器OnlineMap map[string]*User// 用户列容器锁,对容器进行操作时进行加锁mapLock   sync.RWMutex// 消息广播的管道Message chan string
}

讲解:
Message成员是一个chan类型,即管道类型,用于goroutine之间的消息同步,当客户端连接服务端时,服务端开启一个goroutine来处理后续的用户消息,消息需要广播给所有在线的客户端,所以这里我们通过Message管道来做一层消息传递。

OnlineMap是在线用户容器(注意User类在user.go脚本中定义,下文会讲),OnlineMap存储当前连接到服务端的用户。OnlineMap的操作存在多线程并行处理的情况,所以我们需要使用一个sync.RWMutex读写锁对它进行加锁处理,声明一个mapLock成员。

2.2、全局方法,NewServer

我们定义一个NewServer全局方法,构造Server对象,提供给外部调用。

func NewServer(ip string, port int) *Server {server := &Server{Ip:        ip,Port:      port,OnlineMap: make(map[string]*User),Message:   make(chan string),}return server
}
2.3、Socket监听连接,Listen和Accept

监听Socket,我们可以使用net模块的Listen方法,函数原型如下,

// net 模块
func Listen(network, address string) (Listener, error)

例:

// import "net"listener, err := net.Listen("tcp", "127.0.0.1:8888")
if err != nil {fmt.Println("net.Listen err:", err)return
}

要监听客户端连接,则用到的是ListenerAccept接口,该方法会阻塞,当接收到socket连接时才会继续往下执行,接口原型如下,

// Listener接口
Accept() (Conn, error)

例:

conn, err := listener.Accept()
if err != nil {fmt.Println("listener accept err:", err)
}

我们把Socket监听连接的逻辑封装到ServerStart方法中,如下,

// Server.go 脚本// 启动服务器的接口
func (this *Server) Start() {// socket监听listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.Ip, this.Port))if err != nil {fmt.Println("net.Listen err:", err)return}// 程序退出时,关闭监听,注意defer关键字的用途defer listener.Close()// 注意for循环不加条件,相当于while循环for {// Accept,此处会阻塞,当有客户端连接时才会往后执行conn, err := listener.Accept()if err != nil {fmt.Println("listener accept err:", err)continue}// TODO 启动一个协程去处理}
}
2.4、启动协程处理用户消息,Handler

上面Start函数中,当Listener接收到连接后,为了不阻塞for循环,我们启动协程去处理用户行为,封装一个Handler方法,

// server.go 脚本func (this *Server) Handler(conn net.Conn) {// ...
}

在上面的Start方法中添加Handler调用,

// server.go 脚本func (this *Server) Start() {// ...for {// Accept,此处会阻塞,当有客户端连接时才会往后执行conn, err := listener.Accept()if err != nil {fmt.Println("listener accept err:", err)continue}// 启动一个协程去处理go this.Handler(conn)}
}

Handle方法里面主要做三件事情:
1、构建User对象;
2、启动一个新的协程从Conn中读取消息;
3、通过User对象执行消息处理。

// server.go 脚本func (this *Server) Handler(conn net.Conn) {// 构造User对象,NewUser全局方法在user.go脚本中user := NewUser(conn, this)// 用户上线user.Online()// 启动一个协程go func() {buf := make([]byte, 4096)for {// 从Conn中读取消息len, err := conn.Read(buf)if 0 == len {// 用户下线user.Offline()return}if err != nil && err != io.EOF {fmt.Println("Conn Read err:", err)return}// 用户针对msg进行消息处理user.DoMessage(buf, len)}}()
}
2.5、消息广播,通过管道同步

收到用户消息时,我们要广播给所有在线的用户,首先是把要广播的消息写到Message管道中,如下,

// server.go 脚本func (this *Server) BroadCast(user *User, msg string) {sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msgthis.Message <- sendMsg
}

接着我们定义一个ListenMessager方法,去监听Message管道,当Message管道中有消息时,把消息写到用户管道中,

// server.go 脚本func (this *Server) ListenMessager() {for {// 从Message管道中读取消息msg := <-this.Message// 加锁this.mapLock.Lock()// 遍历在线用户,把广播消息同步给在线用户for _, user := range this.OnlineMap {// 把要广播的消息写到用户管道中user.Channel <- msg}// 解锁this.mapLock.Unlock()}
}

我们在Start方法中去启动一个协程来执行ListenMessager

// server.go 脚本func (this *Server) Start() {// ...// 启动一个协程来执行ListenMessagergo this.ListenMessager()for {// Accept,此处会阻塞,当有客户端连接时才会往后执行conn, err := listener.Accept()if err != nil {fmt.Println("listener accept err:", err)continue}// 启动一个协程去处理go this.Handler(conn)}
}

3、user.go脚本

User类,主要做的就是消息处理,即用户行为的代理,如果是在skynet中,就是一个用户agent服务。

注:关于skynet,我之前写过几篇文章,感兴趣的同学也可以看看。
【游戏开发实战】手把手教你从零跑一个Skynet,详细教程,含案例讲解(服务端 | Skynet | Ubuntu)
【游戏开发实战】手把手教你在Windows上通过WSL运行Skynet,不用安装虚拟机,方便快捷(WSL | Linux | Ubuntu | Skynet | VSCode)
【游戏开发实战】教你Unity通过sproto协议与Skynet框架的服务端通信,附工程源码(Unity | Sproto | 协议 | Skynet)

3.1、成员变量声明

我们先定义一些基础的成员变量,

// user.go 脚本type User struct {Name string       // 昵称,默认与Addr相同Addr string       // 地址Channel chan string    // 消息管道conn net.Conn        // 连接server *Server     // 缓存Server的引用
}
3.2、全局方法,NewUser

我们定义一个NewUser全局方法,构造User对象,提供给外部调用。

// user.go 脚本func NewUser(conn net.Conn, server *Server) *User {userAddr := conn.RemoteAddr().String()user := &User{Name: userAddr,Addr: userAddr,Channel:    make(chan string),conn: conn,server: server,}return user
}
3.3、用户上线,Online

封装一个Online方法,用户上线时,广播一个上线消息,

// user.go 脚本func (this *User) Online() {// 用户上线,将用户加入到OnlineMap中,注意加锁操作this.server.mapLock.Lock()this.server.OnlineMap[this.Name] = thisthis.server.mapLock.Unlock()// 广播当前用户上线消息this.server.BroadCast(this, "上线啦O(∩_∩)O")
}
3.4、用户下线,Offline

封装一个Offline方法,用户下线时,广播一个下线消息,

// user.go 脚本func (this *User) Offline() {// 用户下线,将用户从OnlineMap中删除,注意加锁this.server.mapLock.Lock()delete(this.server.OnlineMap, this.Name)this.server.mapLock.Unlock()// 广播当前用户下线消息this.server.BroadCast(this, "下线了o(╥﹏╥)o")
}
3.5、消息处理,DoMessage

消息的传输,实际项目中会使用到一些通信协议对消息进行加密和压缩,比如protobufsproto等。这里我就简单处理,直接以字符串的二进制流传输,做一个简单的消息广播。

// user.go 脚本func (this *User) DoMessage(buf []byte, len int) {//提取用户的消息(去除'\n')msg := string(buf[:len-1])// 调用Server的BroadCast方法this.server.BroadCast(this, msg)
}

上面Server类中的BroadCast方法,会把消息同步回每个User对象的Channel管道,所以我们需要在User中去监听Channel管道消息,封装个ListenMessage方法。我们先构造一个bytebuf,在头部两个字节写入消息长度,然后再写入消息内容,如下,

func (this *User) ListenMessage() {for {msg := <-this.Channelfmt.Println("Send msg to client: ", msg, ", len: ", int16(len(msg)))bytebuf := bytes.NewBuffer([]byte{})// 前两个字节写入消息长度binary.Write(bytebuf, binary.BigEndian, int16(len(msg)))// 写入消息数据binary.Write(bytebuf, binary.BigEndian, []byte(msg))// 发送消息给客户端this.conn.Write(bytebuf.Bytes())}
}

然后在NewUser方法中添加一个协程调用,如下

func NewUser(conn net.Conn, server *Server) *User {// ...// 启动协程,监听Channel管道消息go user.ListenMessage()return user
}

4、main.go脚本

main.go脚本是程序入口脚本,我们要定义一个main方法作为入口函数。
我们封装一个StartServer方法,通过NewServer全局方法构造一个Server对象,然后执行Start成员方法,如下,

// main.go 脚本func StartServer() {server := NewServer("127.0.0.1", 8888)server.Start()
}

然后在main方法中启动一个协程去执行StartServer,如下,

// main.go 脚本func main() {// 启动Servergo StartServer()// TODO 你可以写其他逻辑fmt.Println("这是一个Go服务端,实现了Socket消息广播功能")// 防止主线程退出for {time.Sleep(1 * time.Second)}
}

5、编译运行

VSCode的终端中,进入GoSocketServer目录,然后执行go mod init GoSocketServer,生成go.mod文件,如下,

执行go build命令,将go脚本编译为.exe可执行程序(Windows平台),如下,

运行GoSocketServer.exe,如下,可以看到,服务端启动起来了,

下面,我们用Unity实现客户端部分的功能吧~

六、Unity客户端

1、创建工程,UnitySocketClient

创建Unity工程,项目名称叫UnitySocketClient吧,如下,

2、UGUI制作界面

使用UGUI制作一个界面,如下,

节点层级结构如下,

3、C#脚本

C#脚本只有两个,一个ClientNet.cs,一个Main.cs

3.1、ClientNet.cs脚本

ClientNet.cs脚本封装三个接口出来供外部调用,如下,

代码如下,代码比较简单,我写了注释,相信大家能看懂,

using System;
using UnityEngine;using System.Net.Sockets;public class ClientNet : MonoBehaviour
{private void Awake(){m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);m_readOffset = 0;m_recvOffset = 0;// 16KBm_recvBuf = new byte[0x4000];}private void Update(){if (null == m_socket) return;if (m_connectState == ConnectState.Ing && m_connectAsync.IsCompleted){// 连接服务器失败if (!m_socket.Connected){m_connectState = ConnectState.None;if (null != m_connectCb)m_connectCb(false);}}if (m_connectState == ConnectState.Ok){TryRecvMsg();}}private void TryRecvMsg(){// 开始接收消息m_socket.BeginReceive(m_recvBuf, m_recvOffset, m_recvBuf.Length - m_recvOffset, SocketFlags.None, (result) =>{// 如果有消息,会进入这个回调// 这个len是读取到的长度,它不一定是一个完整的消息的长度,我们下面需要解析头部两个字节作为真实的消息长度var len = m_socket.EndReceive(result);if (len > 0){m_recvOffset += len;m_readOffset = 0;if (m_recvOffset - m_readOffset >= 2){// 头两个字节是真实消息长度,注意字节顺序是大端int msgLen = m_recvBuf[m_readOffset + 1] | (m_recvBuf[m_readOffset] << 8);if (m_recvOffset >= (m_readOffset + 2 + msgLen)){// 解析消息string msg = System.Text.Encoding.UTF8.GetString(m_recvBuf, m_readOffset + 2, msgLen);Debug.Log("Recv msgLen: " + msgLen + ", msg: " + msg);if (null != m_recvMsgCb)m_recvMsgCb(msg);m_readOffset += 2 + msgLen;}}// buf移位if (m_readOffset > 0){for (int i = m_readOffset; i < m_recvOffset; ++i){m_recvBuf[i - m_readOffset] = m_recvBuf[i];}m_recvOffset -= m_readOffset;}}}, this);}/// <summary>/// 连接服务端/// </summary>/// <param name="host">IP地址</param>/// <param name="port">端口</param>/// <param name="cb">回调</param>public void Connect(string host, int port, Action<bool> cb){m_connectCb = cb;m_connectState = ConnectState.Ing;m_socket.SendTimeout = 100;m_connectAsync = m_socket.BeginConnect(host, port, (IAsyncResult result) =>{// 连接成功会进入这里,连接失败不会进入这里var socket = result.AsyncState as Socket;socket.EndConnect(result);m_connectState = ConnectState.Ok;m_networkStream = new NetworkStream(m_socket);Debug.Log("Connect Ok");if (null != m_connectCb) m_connectCb(true);}, m_socket);Debug.Log("BeginConnect, Host: " + host + ", Port: " + port);}/// <summary>/// 注册消息接收回调函数/// </summary>/// <param name="cb">回调函数</param>public void RegistRecvMsgCb(Action<string> cb){m_recvMsgCb = cb;}/// <summary>/// 发送消息/// </summary>/// <param name="bytes">消息的字节流</param>public void SendData(byte[] bytes){m_networkStream.Write(bytes, 0, bytes.Length);}/// <summary>/// 关闭Sockete/// </summary>public void CloseSocket(){m_socket.Shutdown(SocketShutdown.Both);m_socket.Close();}/// <summary>/// 判断Socket是否连接状态/// </summary>/// <returns></returns>public bool IsConnected(){return m_socket.Connected;}private enum ConnectState{None,Ing,Ok,}private Action<bool> m_connectCb;private Action<string> m_recvMsgCb;private ConnectState m_connectState = ConnectState.None;private IAsyncResult m_connectAsync;private byte[] m_recvBuf;private int m_readOffset;private int m_recvOffset;private Socket m_socket;private NetworkStream m_networkStream;private static ClientNet s_instance;public static ClientNet instance{get{if (null == s_instance){var go = new GameObject("ClientNet");s_instance = go.AddComponent<ClientNet>();}return s_instance;}}
}
3.2、Main.cs脚本

Main.cs脚本作为入口脚本,同时作为UI交互的脚本。
代码如下,

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class Main : MonoBehaviour
{public Button sendBtn;public InputField inputField;public Text chatText;private Queue<string> m_msgQueue;private void Awake(){m_msgQueue = new Queue<string>();}void Start(){// 注册消息回调ClientNet.instance.RegistRecvMsgCb((msg) =>{// 把消息缓存到队列中,注意不要在这里直接操作UI对象m_msgQueue.Enqueue(msg);});// 连接服务端ClientNet.instance.Connect("127.0.0.1", 8888, (ok) =>{Debug.Log("连接服务器, ok: " + ok);});sendBtn.onClick.AddListener(SendMsg);}/// <summary>/// 发送消息/// </summary>private void SendMsg(){if (ClientNet.instance.IsConnected()){// 把字符串转成字节流byte[] data = System.Text.Encoding.UTF8.GetBytes(inputField.text + "\n");// 发送给服务端ClientNet.instance.SendData(data);// 清空输入框文本inputField.text = "";}else{Debug.LogError("你还没连接服务器");}}private void Update(){if (m_msgQueue.Count > 0){// 从消息队列中取消息,并更新到聊天文本中chatText.text += m_msgQueue.Dequeue() + "\n";}// 按回车键,发送消息if(Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter)){SendMsg();}}private void OnDestroy() {ClientNet.instance.CloseSocket();}
}

Main.cs脚本挂到Canvas节点上,并赋值脚本的UI成员对象,如下,

4、打包客户端

点击菜单File / Build Settings...,然后添加场景到Scenes in Build列表中,选择平台为PC平台,如下,

接着点击Player Settings,设置Fullscreen ModeWindowed模式,即窗口模式,设置一下窗口大小,如下,

最终,点击Build,执行打包,

打包完毕,生成了exe文件,如下,

七、运行测试

1、启动Go服务端

回到我们的Go服务端工程,在终端执行我们上面生成的GoSocketServer.exe,如下,

2、启动Unity客户端

启动多个客户端,测试聊天,如下,

功能正常,VeryGood,收拾吃饭去~

八、工程源码

本文服务端+客户端工程源码已上传到CODE CHINA,感兴趣的同学可自行下载学习。
地址:https://codechina.csdn.net/linxinfa/golangserverandunityclientdemo
注:我使用的go版本是1.17.2,使用的Unity版本是2021.1.9f1c1

九、完毕

好啦,就先到这里吧~
我是林新发:https://blog.csdn.net/linxinfa
原创不易,若转载请注明出处,感谢大家~
喜欢我的可以点赞、关注、收藏,如果有什么技术上的疑问,欢迎留言或私信~

【游戏开发实战】用Go语言写一个服务器,实现与Unity客户端通信(Golang | Unity | Socket | 通信 | 教程 | 附工程源码)相关推荐

  1. 一个简单的c 游戏编程语言,编程达人 c语言写一个简单的小游戏-推箱子

    在学习C语言之后,写了一个简单的小游戏来锻炼自己的代码以及C语言知识的掌握能力. 推箱子作为手机上最常见的简单游戏,其代码也相对简单,想法也比较简单,下面为其代码和运行图. /************ ...

  2. 如何用C语言写一个服务器和客户端(TCP)

    如果想要自己写一个服务器和客户端,我们需要掌握一定的网络编程技术,个人认为,网络编程中最关键的就是这个东西--socket(套接字).socket(套接字):socket 的原意是"插座&q ...

  3. 微信开发获取地理位置实例(java,非常详细,附工程源码)

    在本篇博客之前,博主已经写了4篇关于微信相关文章,其中三篇是本文基础: 1.微信开发之入门教程,该文章详细讲解了企业号体验号免费申请与一些必要的配置,以及如何调用微信接口. 2.微信开发之通过代理调试 ...

  4. 【游戏开发实战】教你在Unity中实现模型消融化为灰烬飘散的效果(ShaderGraph | 消融 | 粒子系统 | 特效)

    文章目录 一.前言 二.ShaderGraph环境准备 三.模型准备:原神角色模型 四.实现思路 1.效果一的实现思路 2.效果二的实现思路 五.ShaderGraph具体实现 1.效果一 1.1.创 ...

  5. FPGA找工作写简历,你离高薪offer只差一个高端项目,提供工程源码和技术支持

    这里写目录标题 1.前言 2.你或许很菜 3.工程源码 4.技术支持 5.工程源码和技术支持获取方式 1.前言 如果你是即将毕业的学生或是想转行做FPGA的工程师,你都会面临一个问题,那就是找工作,找 ...

  6. 《Unity 5.x游戏开发实战》一1.9 添加一个水平面

    本节书摘来异步社区<Unity 5.x游戏开发实战>一书中的第1章,第1.9节,作者: Alan Thorn 译者: 李华峰 责编: 胡俊英,更多章节内容可以访问云栖社区"异步社 ...

  7. 【牛客刷题】上手用C语言写一个三子棋小游戏超详解哦(电脑优化)

    作者:[南航科院小张 南航科院小张的博客 专栏:从c语言的入门到进阶 学习知识不只是要懂,还要会用:想要找到好的工作,这里给大家介绍一件可以斩获诸多大厂offer的利器–牛客网 点击免费注册和我一起开 ...

  8. c语言写一个简单的小游戏-推箱子

    在学习C语言之后,写了一个简单的小游戏来锻炼自己的代码以及C语言知识的掌握能力. 推箱子作为手机上最常见的简单游戏,其代码也相对简单,想法也比较简单,下面为其代码和运行图. /************ ...

  9. ChatGPT实现用C语言写一个扫雷小游戏

    前几天我们利用 ChatGPT实现用C语言写一个学生成绩管理系统 其过程用时不到30秒,速度惊人 今天又让ChatGPT用C语言写了一个扫雷小游戏,它的回答是:抱歉,我是AI语言模型,无法编写程序. ...

最新文章

  1. Java 理论与实践: JDK 5.0 中更灵活、更具可伸缩性的锁定机制--转载
  2. php curl获取响应,php – cUrl – 获取html响应正文
  3. ButterKnife 8.4.0 @BindView 失败,nullpointerexception
  4. Oracle 把触发器说透
  5. 参赛方案-主机托管、中小企业虚拟化应用
  6. jzoj3833-平坦的折线【模型转换,LIS】
  7. 团队作业-第二周-测试计划
  8. 【线性模型引论】王松桂 课后习题2.1参考答案
  9. R语言实战应用精讲50篇(三十一)-R语言实现决策树(附R语言代码)
  10. VBS实现添加网络打印机
  11. Google Adsense(Google网站联盟)广告申请指南
  12. 吹气球--记忆化搜索
  13. 18年第十二届东北四省赛
  14. 逆転裁判5android,逆转裁判5安卓-phoenix wright: ace attorney dual destinies官方app2021免费...
  15. 微信灰度上线“群接龙”功能,据说只有1%的人能看到!
  16. python爬取pexels网站图片
  17. 用C语言实现扫雷游戏(详解)
  18. 60个可爱的云图案设计,激发你的灵感
  19. 国产下的netcore
  20. ReSpeaker 4-Mic 声源定位

热门文章

  1. Laravel的created_at与deleted_at 类型
  2. Android百度地图雷达效果,地图导航实测:百度地图路线雷达圈粉“老司机”
  3. 史上最简单的mybatis-plus快速入门
  4. 别把爱变成伤害,不要在朋友圈里乱晒娃,朋友圈晒娃注意四点
  5. 基于单片机的环境监测系统设计(#0473)
  6. Android百度地图 - 在地图上标注已知GPS纬度经度值的一个或一组覆盖物 - OPEN 开发经验库 - 360安全浏览器 8.1...
  7. java中 enum什么意思_Java中“enum”的用途是什么?
  8. 已知字符串str1 = tomorrow is sunny day,下列表达式能正确查找到子字符串is的是()(选两项)
  9. oppo手机html文件管理,oppo手机文件管理里面的文件可不可以删除
  10. Windows远程访问服务器jupyter notebook