今天开始来跟着SIKI学院学习Nodejs与Unity的交互,其实我以前学过一些nodejs,这篇文章我就将我学的不清晰的地方和以前不会的地方来做下笔记。
本质:不是语言,是谷歌的V8引擎来解释js代码环境
优点:
缺点:
像Java一样,nodejs也具有内存管理机制,可以用来自动算法将该销毁的内存销毁。
nodejs是运行在v8上的,v8将nodejs的变量全部都放在堆上。
process.memoryUsage()
方法返回 Node.js 进程的内存使用情况的对象,该对象每个属性值的单位为字节。
heapTotal
和 heapUsed
代表 V8 的内存使用情况(总共的和使用了的)。 external
代表 V8 管理的,绑定到 Javascript 的 C++ 对象的内存使用情况。 rss
是驻留集大小(常驻内存), 是给这个进程分配了多少物理内存(占总分配内存的一部分),这些物理内存中包含堆、代码段、以及栈。
对象、字符串、闭包等存于堆内存。 变量存于栈内存,实际的 JavaScript 源代码存于代码段内存。
v8采用的主要是分代式垃圾回收机制。
我们来看代码
var test0=null
global.test=function(){
var test1=null
console.log("test")
}
test()
代码中的test0变量的存活时间比较长,一直占用着资源,这样的变量就是老生代变量。test1变量的生命周期只有短短的一小点区域,像这样的变量,我们就叫它:“新生代变量”
可以参考这篇文章,写的详细
上面的都是在说堆内存,可实际上还存在堆外内存——buffer
因为我们的node语言是处理后端,所以为了安置大量的缓冲流存放到buffer中
js语言本身只有字符串数据类型,没有二进制数据类型,做后端就无可避免的要处理很多TCP流等二进制流,所以我们使用buffer来存放二进制数据的缓冲区,还能保证不会受堆内存的限制。
我们来做个试验
var buffer=new Buffer("我是ndy");
console.log(buffer)
输出:
官网奉上
buffer的构造方法中可以直接指定内存大小(否则可能造成浪费)
//10字节大小的buffer内存
var buffer=new Buffer(10);
buffer通过构造函数的实例来获得操作对象的话,对内存的操作权限是很大的,因为它本身是一个内存缓冲区,所以可能会获取到一些敏感信息,所以我们一般不会用 new 来构造buffer对象,官方提供了这样的形式:
buffer=Buffer.from("我是ndy")
或者
//alloc:内存大小,初始化,编码集
buffer=Buffer.alloc(10,1,"utf8")
console.log(buffer)
alloc的输出:
buffer中的值可以被看做是一个数组(即可以通过下标索引),且每一个值的十进制范围是 0-255(256个数),如果你输入一个负数,他会给你加256,直到你回到 0-255 的范围;如果你给了一个大于255的数字,他会减256,直到你回到 0-255 的范围;
buffer[0]=-100
console.log(buffer[0])
输出:
156
buffer类是由c++和js两部分构成
而在node中,使用了slab分配内存的机制
node当中申请的每一块slab,大小都是8k(一个对象有8k)
所以,所有小于8k的对象是一个小对象,而大于8k的对象则是大对象
小对象(小于8k):
buffer在分配内存的过程当中,会用到pool这样一个局部对象,实现代码大概如下
var pool
function allocPool(){
pool=new slowBuffer(Buffer.poolSize)
pool.used=0;
}
其中used是一个内存指针,一开始为零,比如说存进来了一个1024字节的对象,首先会判断一个slab的剩余空间够不够,如果够,就将对象放进内存(反之去找其他slab),那么used就会指向1024,即表示已经使用了1024字节。
这里有一个有趣的现象,比如我第一块slab占用了1k,现在又来了一个8k的对象,那么他就会存放于第二块slab中(因为第一块只剩7k了),如果再来一个对象,它只会和第二块slab进行判断,以此类推。第一块slab的剩余7k从此浪费了。
另外注意,一个slab中,只要存在还在使用的对象,那这个slab就不可以释放
对了,还有:每一个buffer对象有一个parent属性,这个属性指向的其实就是slowBuffer,而这个slowBuffer的对象实际上是在c++中定义的(这就不是v8引擎分配的了,是c++底层分配)
大对象(大于8k):
如果出现大对象,会单独申请一块匹配的对象进行保存,而不会使用共有的slab。
中文字符串的拼接经常会出现问题,我们来做个试验
首先创建一个txt文件,编码要UTF-8格式
txt文件改成UTF-8格式:
另存为,覆盖原文件,选择好编码方式
#Test.txt
你说啥,我看不见
我们先利用流来读取文件内容
var fs=require('fs')
var re=fs.createReadStream("./Test.txt")
var text=""
re.on("data",function(chunk){
text+=chunk
})
re.on("end",function(){
console.log(text)
})
输出:
你说啥,我看不见
没有问题
可是若我们限定了每次流读取的大小,那么就会出现问题(一个汉字三个字节,假设我们限定每次读取七个字节,第三个汉字就会有问题,造成歧义)
var fs=require('fs')
var re=fs.createReadStream("./Test.txt",{highWaterMark:7})
var text=""
re.on("data",function(chunk){
text+=chunk
})
re.on("end",function(){
console.log(text)
})
输出:
你���啥��我看不见
第一种解决方式很简单,直接规定好读入的编码格式,告诉它读取规则
var fs=require('fs')
var re=fs.createReadStream("./Test.txt",{highWaterMark:7})
re.setEncoding("utf8")
var text=""
re.on("data",function(chunk){
text+=chunk
})
re.on("end",function(){
console.log(text)
})
即恢复正常
但是这样只能解决部分格式,如果出现一些刻薄的格式,这个方法也会乱码。
我们出现的问题是因为限定了读入的大小长度所造成的说的,那么,将读入的东西先拼接起来就好了
所以,最好的方法就是第二种方法——拼接
var fs=require('fs')
var re=fs.createReadStream("./Test.txt",{highWaterMark:7})
var texts=[]
var size=0
re.on("data",function(chunk){
texts.push(chunk)
size+=chunk.length
})
re.on("end",function(){
//concat:将数组在size大小内进行拼接
var text=Buffer.concat(texts,size)
console.log(text.toString())
})
这样就可以根本上解决问题
我的那个nodejs的基本学习中聊过服务器这块,大家可以去先看看那个
字段 | 含义 |
---|---|
URG | 紧急指针是否有效。为1,表示某一位需要被优先处理 |
ACK | 确认号是否有效,一般置为1。 |
PSH | 提示接收端应用程序立即从TCP缓冲区把数据读走。 |
RST | 对方要求重新建立连接,复位。 |
SYN | 请求建立连接,并在其序列号的字段进行序列号的初始值设定。建立连接,设置为1 |
FIN | 希望断开连接。 |
TCP的三次握手是怎么进行的了:发送端发送一个SYN=1,ACK=0标志的数据包给接收端,请求进行连接,这是第一次握手;接收端收到请求并且允许连接的话,就会发送一个SYN=1,ACK=1标志的数据包给发送端,告诉它,可以通讯了,并且让发送端发送一个确认数据包,这是第二次握手;最后,发送端发送一个SYN=0,ACK=1的数据包给接收端,告诉它连接已被确认,这就是第三次握手。之后,一个TCP连接建立,开始通讯。
而四次挥手,关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。
跟多详情请看这里
建立TCP服务端很简单:
var net=require("net")
//当有客户端连接到服务器上,则会执行该回调
var server=net.createServer(function (socket){
socket.on("data",function(){
})
})
//绑定端口等信息
server.listen(1100,"127.0.0.1")
核心代码就是这样
UDP(用户数据包协议)不同于面对连接的TCP协议(因为面对连接,所以需要三次握手建立连接),UDP协议实际更像是一个广播,只管发包,不管有没有收到,所以速度较快,但是发包丢失率也会增加。
建立UDP核心代码:
var dgram=require("dgram")
var server=dgram.createSocket("udp4")
server.on("message",function(msg,rinfo){
console.log("msg:"+msg+" "+"rinfo:"+rinfo)
})
//创建一个新的udp连接的时候,会触发listening
server.on("listening",function(){
console.log("Start UDP server")
})
server.bind(1100)
我们再来写个客户端
var dgram=require("dgram")
var msg=Buffer.from("你好。")
var client=dgram.createSocket("udp4")
client.send(msg,1100,"127.0.0.1",function(err,data){
client.close()
})
好了,先启动服务端然后启动客户端,然后就可以看到服务端输出了“你好。”
通信成功。
NodeJS的JavaScript运行在单个进程的单个线程上,一个JavaScript执行进程只能利用一个CPU核心,而如今大多数CPU均为多核CPU,为了充分利用CPU资源,Node提供了child_process和cluster模块来实现多进程以及进程管理。本文将根据Master-Worker模式,搭建一个简单的服务器集群来充分利用多核CPU资源,探索进程间通信、负载均衡、进程重启等知识。
示例代码:
var chirdProccess=require("child_process")
//创造子进程,执行带参命令,保存进程对象
var worker=chirdProccess.spawn("node",["test2.js"])
worker.stdout.on("data",function(data){
console.log("子进程打印了"+data)
})
其中var worker=chirdProccess.spawn("node",["test2.js"])
就是一个子进程,参数是一个cmd命令——“node test2.js”,它会执行另一个文件test2.js
#test2.js
console.log("子进程")
worker.stdout.on("data",function(data){
这一句即得到子进程的标准输出,并打印在现在这个控制台中
console.log("子进程打印了"+data)
})
最后输出结果:
子进程打印了子进程
上面的方法还有一种更简单的写法就是利用exec函数
var chirdProccess=require("child_process")
var worker
worker=chirdProccess.exec("node test2",function(err,stdout,stderr){
console.log(stdout)
})
这样可以用自带的回调函数直接输出结果,不必再绑定事件
execFile方法:
var chirdProccess=require("child_process")
var worker=chirdProccess.execFile("node",["test2.js"],function(err,stdout,stdin){
console.log(stdout)
})
第一个参数是可执行文件的名称或者路径,第二个参数是给可执行文件传的参数,最后是一个回调函数
其实spawn和exec是将node作为命令执行了,node本质就是一个可执行程序。
child_process.fork()
方法是 child_process.spawn()
的一个特例,专门用于衍生新的 Node.js 进程。 与 child_process.spawn()
一样返回 ChildProcess
对象。 返回的 ChildProcess
将会内置一个额外的通信通道(IPC通信通道),允许消息在父进程和子进程之间来回传递。
var chirdProccess=require("child_process")
var worker=chirdProccess.fork("./test2.js",{
execArgv:['--inspect='+(process.debugPort+1)]
})
fork指定一个文件,node启动;但是要注意要配置好端口,否则端口会显示被占用(上例将端口号加了1)
fork不仅会创建子进程,还会创建一个IPC通信通道,这样父子进程就可以通信了
父进程:
var chirdProccess=require("child_process")
var worker=chirdProccess.fork("./test2.js",{
silent:true,
execArgv:['--inspect='+(process.debugPort+1)]
})
worker.stdout.on("data",function(data){
console.log(data.toString())
})
worker.send('来自于父进程的消息')
worker.on("message",function(data){
console.log(data)
})
子进程
console.log("子进程")
process.on("message",function(data){
console.log(data)
})
process.send("来自子进程的消息")
console.log(null==undefined)
console.log(null===undefined)
console.log(123=='123')
console.log(123==='123')
true
false
true
false
//数组
var array=[]
//从后面添加元素
array.push("aa")
//通过int下标添加元素
array[1]="bb"
//从前面添加元素
array.unshift("cc")
//插入元素,删除第零个元素
array.splice(1,0,"ddd")
//键值对
var array2=[]
array2['r']='sdf'
console.log(array)
console.log(array2)
[ ‘cc’, ‘ddd’, ‘aa’, ‘bb’ ]
[ r: ‘sdf’ ]
js中,对象的概念是最基本最重要的概念,几乎可以将所有的东西全都看成对象
js中没有类
通过构造函数创建
var map=new Object()
map["aa"]="ss"
console.log(map)
console.log(map.aa)
{ aa: ‘ss’ }
ss
通过字面量创建对象:
map={
aa:'abc',
bb:'tyu'
}
console.log(map)
{ aa: ‘abc’, bb: ‘tyu’ }
动态语言直接写形参即可
function Test(num){
console.log(num)
}
Test(345)
for(var i=0;i<10;i++){
console.log("123")
}
var array=["aa","bb","cc","dd"]
for (var i in array){
//注意,i不是array中的元素,而是下标
console.log(array[i])
}
array.forEach(Element=>{
console.log(Element)
})
var map={
aa:1,
bb:2
}
console.log(typeof map)
var test
console.log(typeof test)
//“空”是一个对象
var test2=null
console.log(typeof test2)
console.log(typeof (String(test2)))
console.log(typeof (Number(test2)))
在js中,允许先使用后定义:
console.log(i)
var i
输出:
undefined
js会将你的声明提升到最上面,但是也仅仅是提升变量的声明,你获取的变量会是一个默认值——undefined
console.log(i)
var i=1
输出:
undefined
var i=0
var i=2
console.log(i)
var a=10
{
var a=2
}
console.log(a)
输出:2
let a=10
{
let a=2
}
console.log(a)
输出:10
客户端使用Unity Asset可以下载的插件 socted io来进行socket通信,进行简单测试:
const io=require("socket.io")(3000)
//添加连接事件
io.on("connection",function(socket){
console.log("已经连接!")
})
一个空物体,挂载脚本,内容如下:
using SocketIO;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NetWorkMgr : MonoBehaviour
{
private SocketIOComponent _socket;
// Start is called before the first frame update
void Start()
{
_socket = gameObject.AddComponent();
_socket.url = "ws://127.0.0.1:3000/socket.io/?EIO=4&transport=websocket";
_socket.On(ev:"connection", callback:(data) =>
{
Debug.Log(data);
});
}
}
先启动服务端,然后启动Unity场景,服务端就会打印消息了
服务端:
const io=require("socket.io")(3000)
console.log("Start server")
//添加连接事件
io.on("connection",function(socket){
console.log("已经连接!")
//给客户端发送消息——connection
socket.emit("connection")
})
客户端:
void Start()
{
_socket = gameObject.AddComponent();
_socket.url = "ws://127.0.0.1:3000/socket.io/?EIO=4&transport=websocket";
_socket.On(ev: "connection", callback: (data) =>
{
Debug.Log(message: "收到名为connection的消息");
});
}
会打印出“收到名为connection的消息”
服务端:
const io=require("socket.io")(3000)
console.log("Start server")
//添加连接事件
io.on("connection",function(socket){
console.log("已经连接!")
//给客户端发送消息——connection
socket.emit("connection")
socket.on("Message",function(){
console.log("GetMessage")
})
})
客户端:
void Start()
{
_socket = gameObject.AddComponent();
_socket.url = "ws://127.0.0.1:3000/socket.io/?EIO=4&transport=websocket";
_socket.On(ev: "connection", callback: (data) =>
{
Debug.Log(message: "收到名为connection的消息");
});
emitEven.onClick.AddListener(this.emitEvent);
}
private void emitEvent() {
print("SendMessage");
_socket.Emit("Message");
}
我这里绑定了一个按钮,一点按钮,服务端就会输出“GetMessage”
客户端的基本事件监听接口模型:
public class NetWorkMgr : MonoBehaviour
{
private SocketIOComponent _socket;
//私有变量以_开头
//处理事务逻辑的接口
private Dictionary _eventsDic;
void Start()
{
_eventsDic = new Dictionary();
_socket = gameObject.AddComponent();
_socket.url = "ws://127.0.0.1:3000/socket.io/?EIO=4&transport=websocket";
}
//添加监听的事件接口
public void AddListener(string id,Action callback) {
//字典中没有这个id,则可以添加
if (!_eventsDic.ContainsKey(id)) {
_eventsDic.Add(id, new NetworkEvent(id, _socket));
//NetworkEvent类的对象就是对于某一个ID,它的事件的载体
}
_eventsDic[id].AddLisener(callback);
}
//一个ID可能会绑定多个回调函数,所以要指定清楚id和回调函数
public void RemoveListener(string id, Action callback) {
if (_eventsDic.ContainsKey(id)) {
_eventsDic[id].RemoveLisener(callback);
}
}
//发送消息
public void Emit(string id,JSONObject json=null) {
_socket.Emit(id, json);
}
}
我们将每一个消息体作为一个NetworkEvent类的对象,NetworkEvent类如下:
//此类用来作为NetWorkMgr的字典的值,用来处理一些事件逻辑
//用来处理网络部分事件的绑定还有移除
public class NetworkEvent
{
public string ID { get; private set; }
private Action _action;
public NetworkEvent(string id,SocketIOComponent socket) {
ID = id;
//构造这个对象就会开启这个名为id的事件的监听
socket.On(id, Excute);
}
public void AddLisener(Action callback) {
_action += callback;
}
public void RemoveLisener(Action callback)
{
_action -= callback;
}
private void Excute(SocketIOEvent data) {
if (_action != null) {
_action(data);
}
}
public void Clear() {
_action = null;
}
}
我们每给NetWorkMgr 类的字典里添加一个ID就会生成一个NetworkEvent类的对象,同时开启与ID同名的socket事件的监听,一旦事件被触发,就会调用ID里添加的方法。
为了让NetWorkMgr成为一个不随场景消失的东西,我们将NetWorkMgr做成一个单例。
public class NetWorkMgr : MonoBehaviour
{
//将这个类做成单例模式
public static NetWorkMgr Instance { get; private set; }
private SocketIOComponent _socket;
//私有变量以_开头
//处理事务逻辑的接口
private Dictionary _eventsDic;
void Awake()
{
InitInstance();
_eventsDic = new Dictionary();
_socket = gameObject.AddComponent();
_socket.url = "ws://127.0.0.1:3000/socket.io/?EIO=4&transport=websocket";
_socket.On(ev: "connection", callback: (data) =>
{
Debug.Log(message: "收到名为connection的消息");
});
}
private void InitInstance() {
//如果Instance变成了空(调换了场景)
//则初始化Instance,并不摧毁当前游戏物体
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else {
//不管NetWorkMrg的物体有多少个,静态的Instance只有一个
//进入else说明当前已经有了一个Instance
//所以摧毁我们这个后来者,避免两个单例物体
Destroy(gameObject);
}
}
//添加监听的事件接口
public void AddListener(string id,Action callback) {
//字典中没有这个id,则可以添加
if (!_eventsDic.ContainsKey(id)) {
_eventsDic.Add(id, new NetworkEvent(id, _socket));
//NetworkEvent类的对象就是对于某一个ID,它的事件的载体
}
//调用这个id对应的NetworkEvent对象的AddLisener
_eventsDic[id].AddLisener(callback);
}
//一个ID可能会绑定多个回调函数,所以要指定清楚id和回调函数
public void RemoveListener(string id, Action callback) {
if (_eventsDic.ContainsKey(id)) {
_eventsDic[id].RemoveLisener(callback);
}
}
//发送消息
public void Emit(string id,JSONObject json=null) {
_socket.Emit(id, json);
}
}
public class Keys
{
public readonly static string Connection = "connection";
public readonly static string DisConnection = "disconnection";
}
方便我们后面直接使用变量名进行处理
为相应的功能做相应的视图处理类:
比如连接事件
public class ConnectView : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
NetWorkMgr.Instance.AddListener(Keys.Connection, Connect);
NetWorkMgr.Instance.AddListener(Keys.DisConnection, DisConnect);
}
private void OnDestroy()
{
NetWorkMgr.Instance.AddListener(Keys.Connection, Connect);
NetWorkMgr.Instance.AddListener(Keys.DisConnection, DisConnect);
}
//建立连接的方法
private void Connect(SocketIOEvent data)
{
Debug.Log("connect server Success");
}
//断开链接的方法
private void DisConnect(SocketIOEvent data) {
Debug.Log("Disconnect server Success");
}
}
我们可以对这类视图事件做一个基类
public abstract class ViewBase : MonoBehaviour
{
//激活物件会调用
protected virtual void OnEnable() {
AddEventLintener();
}
//关闭物件会调用
protected virtual void OnDisable() {
RemoveEventLintener();
}
protected abstract void AddEventLintener();
protected abstract void RemoveEventLintener();
}
让后面的视图处理类都继承自这个基类,比如一个登陆视图类:
public class LoginView : ViewBase
{
protected override void AddEventLintener()
{
NetWorkMgr.Instance.AddListener(Keys.Login, LoginResult);
}
protected override void RemoveEventLintener()
{
NetWorkMgr.Instance.RemoveListener(Keys.Login, LoginResult);
}
private void LoginResult(SocketIOEvent data) {
}
}
这样就很方便处理了
将登陆视图与Unity场景的界面的东西绑定:
public class LoginView : ViewBase
{
private void Start()
{
string user = "";
string passwd = "";
//给user输入框注册事件监听器
transform.Find("LoginView/user").GetComponent()
.onEndEdit.AddListener((text)=> {
user = text;
});
//给passwd输入框注册事件监听器
transform.Find("LoginView/passwd").GetComponent()
.onEndEdit.AddListener((text) => {
passwd = text;
});
//给提交按钮注册发送信息效果
transform.Find("LoginView/Button").GetComponent
#index.js
const io=require("socket.io")(3000)
const login=require('./login')
require("./keys")
console.log("Start server")
//添加连接事件
io.on(keys.Connection,function(socket){
console.log("已经连接!")
//开启登录的监听
login.initLogin(socket)
//给客户端发送消息——connection
socket.emit("connection")
})
#login.js
var testAccount=[{user:"111",passwd:"111"},
{user:"222",passwd:"222"}]
module.exports.initLogin=function(socket){
socket.on(keys.Login,(data)=>{
var result={}
result.user=data.user
result.result=CheckAccount(data)
socket.emit(keys.Login,result)
if(result.result==true){
//登录成功,开始处理游戏代码
}
})
}
function CheckAccount(data){
var result=false;
testAccount.forEach(function(Item){
if(Item.user==data.user&&Item.passwd==data.passwd){
result=true
}
})
return result
}
#LoginView.cs
public class LoginView : ViewBase
{
private void Start()
{
string user = "";
string passwd = "";
//给user输入框注册事件监听器
transform.Find("LoginView/user").GetComponent()
.onEndEdit.AddListener((text)=> {
user = text;
});
//给passwd输入框注册事件监听器
transform.Find("LoginView/passwd").GetComponent()
.onEndEdit.AddListener((text) => {
passwd = text;
});
//给提交按钮注册发送信息效果
transform.Find("LoginView/Button").GetComponent
#launchGame.cs(这个脚本挂载在Unity的登录Canvas上)
public class launchGame : MonoBehaviour
{
void Start()
{
//网络管理组件的初始化
InitNetworkMgr();
InitLoginView();
}
private void InitNetworkMgr() {
GameObject mgr = new GameObject("NetWorkMgr");
mgr.AddComponent();
mgr.AddComponent();
}
private void InitLoginView() {
//添加组件,注册登录消息
transform.gameObject.AddComponent();
}
}
开启服务端,再开启客户端,输入登录信息点击登录,输入testAccount中的用户的话,客户端就会收到:“true”,反之false;
注意,客户端封装json时用的键名一定要和服务端取json元素用的键名对应,否则会取不出值
我们在客户端定义一个新的类——Util,作为用到的工具类
public class Util
{
public static bool GetBoolFromJson(JSONObject json) {
if (json.ToString() == "true")
{
return true;
}
else if (json.ToString() == "false")
{
return false;
}
else {
//记录一下:传入数据不是一个布尔型
Debug.LogError("the json is not a bool");
return false;
}
}
}
然后在 LoginView 中的 登录响应方法 中这样修改
private void LoginResult(SocketIOEvent data) {
//如果该方法被调用,说明node端向C#端发送了key为login的信息
//data是一个SocketIOEvent对象的json属性,通过下标得到值
if (Util.GetBoolFromJson(data.data["result"]))
{
Debug.Log("Login success");
}
else {
Debug.Log("Login lose");
}
}
这样我们就可以看到正确的响应登录的结果了
这里只是验证一下标识,接下来我们来真正做出效果
登录成功让它跳转场景
private void LoginResult(SocketIOEvent data) {
//如果该方法被调用,说明node端向C#端发送了key为login的信息
//data是一个SocketIOEvent对象的json属性,通过下标得到值
if (Util.GetBoolFromJson(data.data["result"]))
{
//同步加载
SceneManager.LoadScene("Game");
}
else {
Debug.Log("Login lose");
}
}
记得要将Game场景加入Build setting中
测试一下没有问题成功跳转,下面我们来丰富Game场景的初始化。
在Game场景中新建一个Canvas,挂载脚本 StartGameView.cs。
脚本中写上如下内容:
public class StartGameView : ViewBase
{
private void Start()
{
//发送消息告诉服务端初始化完成
NetWorkMgr.Instance.Emit(Keys.InitGameComplete);
}
protected override void AddEventLintener()
{
//注册事件监听
NetWorkMgr.Instance.AddListener(Keys.Spawn, spawnPlayer);
}
protected override void RemoveEventLintener()
{
NetWorkMgr.Instance.RemoveListener(Keys.Spawn, spawnPlayer);
}
private void spawnPlayer(SocketIOEvent data) {
}
}
上面就是StartGameView的基本内容
为了实现多人在线通信,不仅要给新登录的对象传递已经登录对象的数据信息,还要给所有已经登录的对象传递新登录的对象的信息
服务端:
#login.js
const startGame=require('./startGame')
var testAccount=[{user:"111",passwd:"111"},
{user:"222",passwd:"222"}]
module.exports.initLogin=function(socket){
socket.on(keys.Login,(data)=>{
var result={}
result.user=data.user
result.result=CheckAccount(data)
socket.emit(keys.Login,result)
if(result.result==true){
//登录成功,开始处理游戏代码
//调用startGame初始化游戏服务端
startGame.InintStartGame(socket,result.user)
}
})
}
function CheckAccount(data){
var result=false;
testAccount.forEach(function(Item){
if(Item.user==data.user&&Item.passwd==data.passwd){
result=true
}
})
return result
}
#startGame.js
let players={}
module.exports.InintStartGame=function(socket,playID){
socket.on(keys.InitGameComplete,function(data){
//获取当前的player对象
let player={
id:playID,
position:{x:0,y:0,z:0},
rotation:{x:0,y:0,z:0}
}
//将player对象保存到数组当中
players[playID]=player
//告诉老对象当前登录对象的信息
socket.broadcast.emit(keys.Spawn,player)
//告诉当前登录对象,已在线对象的信息
for(let player in players){
//不再发送自己了,避免重复
if(player.id!=playID)
socket.emit(keys.Spawn,players[player])
}
})
}
客户端:
我们再创建一个脚本PlayerSpawner(角色生成)用来处理所有的人物登录的情况
#PlayerSpawner.cs
public class PlayerSpawner
{
//这个类通过根据网路数据来生成用户
private static PlayerSpawner _instance;
public static PlayerSpawner Instance {
get {
if (_instance == null) {
_instance = new PlayerSpawner();
}
return _instance;
}
}
//字典用来保存已经登录的人物
private Dictionary _playerDic = new Dictionary();
public GameObject SpawnPlayer(string id)
{
GameObject player=GameObject.Instantiate(Resources.Load(Paths.Player));
//将这个角色加入字典
_playerDic.Add(id, player);
return player;
}
public void RemovePlayer(string id) {
//销毁物体
var player = _playerDic[id];
GameObject.Destroy(player);
//从字典中移除
_playerDic.Remove(id);
}
public GameObject GetPlayer(string id) {
return _playerDic[id];
}
}
#StartGameView.cs
public class StartGameView : ViewBase
{
private void Start()
{
//发送消息告诉服务端初始化完成
NetWorkMgr.Instance.Emit(Keys.InitGameComplete);
}
protected override void AddEventLintener()
{
//开启Spawn(生成)player的事件的监听
NetWorkMgr.Instance.AddListener(Keys.Spawn, spawnPlayer);
}
protected override void RemoveEventLintener()
{
NetWorkMgr.Instance.RemoveListener(Keys.Spawn, spawnPlayer);
}
private void spawnPlayer(SocketIOEvent data) {
//新建角色
var player = PlayerSpawner.Instance.SpawnPlayer(data.data["id"].ToString());
}
}
Resources.Load
这一句意思是在Resources目录下的Paths.Player(我在别的文件中定义了,定义的是Prefabs/Player)路径物体加载过来。一定要注意要有Resources目录!(Paths.Player)
我们先不考虑更多信息,先简单将人物可以正常生成
欢迎访问我的博客:is-hash.com
商业转载 请联系作者获得授权,非商业转载 请标明出处,谢谢