U3D学习笔记(5)



1.不能试图让场景里的按钮去回调预设体里的函数,因为不能保证预设体是否存在,也就无法保证预设体函数是否可用,但可以在预设体里加入按钮组件,这样确保了有按钮必有预设体。
2.U3d 网络研究(Network )
 0.客户端只负责发送各种信息(移动,开启动画等),服务端接收到这些信息后真正移对玩家进行操作
 1.服务器端必须先连接,然后客户端在通过那个唯一端口连接到这个服务器
 2.network .initializeserve 用来连接服务器。可以赋值给networkconnectionerror 并打印来测试是否连接失败
 3.想作为服务器的设备使用Network .player .ipaddress 先知道该设备的ip地址,然后所有客户端再连接这个ip 地址进行通信。
 4.刚从网上接受到的消息会被保存在Network message info类型 中,可以在rpc 的接收方里添加这一个参数类型用来修改。
 5.Network .institution 和Network .destroy 会在参与游戏的所有对象删除和创建对象,达到同步,
 所以联网游戏不用institution 和destroy 并且用这种方式创建的物体如果不关闭Networkview组件 的同步和关注,
 则可以同步关注对象的transform 和它创建的实例对象(如爆炸实例)
 6.onconnecttoserver 在成功连接到客户端时被客户端调
 7.有时需要网络传递Gameobject 对象(不属于基本类型不能直接传递),这时需要使用该对象的Networkview .viewid 来传递
 8.断开连接后不要再用Network .institution 创建,否则报错
 9.用Networkview .ismine 来判断是不是本地客户端创造的对象,一个作用是可以用来判断能不能对它进行操控,如果不是本地的就把Enable =false .就无法操作了
 10.清除的时候先清除所有rpcs ,再销毁对象,要是不removerpcs ,每次客户端重新连接都会比前一次多一个预设体创建出来。
 11.当客户端和服务端所拥有的初始角色不一样多(比如你服务器里运行前就加了个人而客户端没有)就会由于rpc 数量不匹配而报错
 12.ismine 是否通过自己实例化了网络对象,即拥有者是不是自己
 Owner 表示Networkview 的拥有者
 可以这样用:if (!ismine ),log (Owner ).即如果这个网络视图组件不是我的,那就打印出它的所有者
 13.Network .player 获取本地游戏对象实例(和小写的transform ,gameobject一个道理 )
3.收发网络消息方式1 Rpc 函数
 -2sender 如果发送给自己是值为-1,否则为0
 -1.在rpc 只能调用该游戏对象所绑定的脚本组件里的rpc 函数,而不能调用别的对象上的。
 0.经常用来做聊天室
 此时可以关闭Network view 组件的实时同步功能
 1.Network .rpc (“接受消息的函数”,接收对象,发送的值(可选)),作用是把发送的值传入接受消息的函数,理论上所有东西都可以发过去,比如改变动画状态,如果服务器动画没变可能是条件不符。
 !!但是注意其实调用的是你写在发送方脚本里的rpc 函数,你接收方就算不写rpc 函数也能接收到,因为它本来就和接收方毫无关系!(所以才叫远程调用!)
 所以如果你在rpc 里写上input 这种移动函数,就会导致远程可以移动你本地人物的位置,所以一般rpc 里只写修改的状态而不是Input 这种带修改的
 其中接收消息的函数前面加上[rpc],并且函数的参数类型必须和上面发送的值类型匹配。
 2. 只需要将包含RPCs函数的脚本绑定在一个包含NetworkView组件的游戏对象上就可以使用RPC。
 3.接收对象如果选择缓存,那么缓存后,以后登录的玩家都会接收到这个信息。比如登录信息是要缓存的,这样你或别人一出来就能知道你的好友或者其他人有没有登录,而你离线这段事件频道里的发言是可以不缓存的。
 4.rpc 就是用来状态同步的,所以传入的数值(rpc 里的参数)也应该是发送方自己产生的,然后再别的player 里同步这些数值!所以参数里面的值都只会是发送方的。
 5.用info .Networkview .observed ,(组件里observe 里选择预设体)
 或者info .Networkview .getcomponent ()直接得出来了!
 6.如果一个组件绑定了多个脚本包含同名rpc函数 ,则会根据在组件里的顺序优先调用前面的rpc 函数,后面的不执行。所以一般不要定义同名rpc .
4.Socket 研究
 -2.(Socket )iar .asyncstate 获取的总是本地Socket 接口。客户端获取的是联通服务端的那条,而客户端获取的是单向的那个(插座)
 -1.Socket 可以想象成一个有很多插口的插座,所有口共用一个端口,服务器和客户端通过连接它们两个插件的接口形成连接,然后通过接口传输信息。
 这条线就是socket ,remoteendpoint 表示线的那端的端口信息,localendpoint 表示线这端的端口信息。
 对于服务器初始化时的endpoint ,它(和它的复制)只有本地端口,因为它是一个插座。
 0.Socket 创建步骤
 服务器端:
 1.New 一个Socket a
 2.给a 绑定ip 和port ,用bind 函数(ipendpoint (注意ip 地址要Parse ,port ))
 3.a.listen (x )开始监听是否有客户端连接,x 表示最大连接数(超过就无法连接)
 4.a. accept (),当没有达到上面x 的上限时,就可以接收客户端的连接请求(客户端的connect函数),然后新建一个socket b 和客户端连接(a和b 占有同一个端口和ipaddress ,b 是a 的复制,但是系统会自动辨别到底和a 还是b 通信,所以不算是一个端口接两个东西,a,b 是union 的关系,同一时间只存在一个,如果还有c,d,e 则同理),然后a 继续监听别的连接者,有则继续创建Socket c,...而b 则用来处理和它连接的客户端的通信。
 一般把b 写成toclientSocket 来表示b 是连接客户端b 的socket ,可以通过b .send 向对应客户端发信息
 客户端:创建Socket 和服务端一样,但是不需要bind ,而是使用connect 来连接服务端的ip 和端口,其它一样
 1.必须写成单例类,并且不继承monobehaviour 否则场景一释放链接就没了(一个场景就没必要了)
 2.endpoint: 端点,即开启socket 的那个连接点。是ip 地址和端口的一个集合
 3.句柄类似于指针或者索引
 4.ipaddress .Parse 把字符串转化成IP 地址
 5.Addressfamily 寻址方案
 6.传输的信息需要转成byte ,即需要转码才行。发送方通过encode.utf 8.getbyte(s ) 获取s 的utf8码byte ,接收方再通过 encode.utf 8.getbyte(s )转化成字符串
 9.len =receive (x )可以获得x 的长度
 10.Socket 服务器必须重新开一个线程(thread t =new thread ,并且t.srart 开启),否则会阻塞在listen 那里。或者用begin accept 异步开线程
 11.如果重开一个线程,记得把这个的thread .is background 设为true ,这样它就能够随着主线程的退出而退出,否则会独立于主线程运行
 如果用的是beginreceive 和begin accept 则要在回调函数(是一个异步加载函数)里的参数类型为iasyncresult 类型来传递值,再通过as 转化成原类型(async 异步的意思)
 12.endreceive 用来获取异步加载获取的字节数,receive 则只能获取本地字节数
5.Socket 异步加载(多线程)
 -1.异步法接收信息必须递归调用beginreceive 或者加在endreceive 函数第一行 (推荐后者,只有回调了才再开一个线程比较科学效率),但是一定要快,否则可能由于线程还没开出来但是此时又有消息发出了导致接收不到信息。注意是用和外界通信的那个Socket 而不是服务器本身的Socket 来调用这两个函数,其它也是!
 -1.5同样的,在endaccept 里也要立刻再开一个beginaccept 来准备接入别的客户端
 0.异步和多线程是有区别的,前者是靠后者实现的,但是它不需要你管理,可以自动使程序不阻塞(阻塞时去做别的事),而后者则需要自己管理
 1.beginconnect(x ),开始连接,当连接成功开启一个线程并且回调方法x . X 里必须包含endconnect (),表示连接完成而不是连接结束。其它的begin 开头的,在它的回调函数参数里也都必须end结尾
 (但注意调用这个end 的是传入的Socket 参数而不是原来调用begin 的那个Socket !!),用来完成传输并且返回一个值(如end send ,receive 表示返回发送的字节数,而end accept,accept 返回的是服务器创建的专门和该客户端通信的Socket c ,c 里包含了连接的客户端的相关信息,如c. remoteendpoint 表示远程端口即该客户端端口)。
 最后一个参数object state 是让自己(该脚本)知道传出的回调函数里的iasynresult 可以强制转化成什么类型 )调用了前面的那个回调函数。
 2.当一个线程必须阻塞(如等待connect ,accept 时),可以做别的事,如继续点击按钮显示人物信息等是多线程核心
 3.异步中一般都用传入的iasyncresult x 参数再创建一个Socket (如Socket k =x. Acyncstate as Socket )表示原Socket(即调用该异步的Socket ) 的复制(和原Socket 共用一个端口)来处理而不是原Socket 。(这和赋值是一个道理,原操作数尽量不要动,用另一个相等的数让它去处理)
 4.begin accept 后调用的函数x 里要做的事,
 1.获取连接的客户端Socket
 2.开启另一个beginaccept 来等待别的客户端连接(参数里的异步回调函数还是x )
 3.while 死循环来实时接收客户端发送的消息
 4.死循环的出口为客户端断开
 总结:结构形状就像许多瀑布并流
6.U3d弹窗
 1.设置Active 来控制弹窗的显示
 2.用工厂模式控制弹窗显示内容
 3.为了防止多个弹窗,可以使用队列来一一处理
7.射线相关
 1. 射线:射线是3D世界中一个点向一个方向发射的一条无终点的线,在发射轨迹中与其他物体发生碰撞时,它将停止发射 。
 2. layerMask: 只选定Layermask层内的碰撞器,其它层内碰撞器忽略。 选择性的碰撞。
 LayerMask mask = 1 << LayerMask.NameToLayer("Player");表示开启play er 层来和射线碰撞。
 其中 NameToLayer 是返回的该名字所定义的层的层索引,注意是从0开始。
 3. 1 <<10 打开第10的层。
 ~(1 << 10)打开除了第10之外的层。
 ~(1 << 0)打开所有的层。
 (1 << 10) |(1 << 8) 打开第10和第8的层。
 解释: LayerMask实际上是一个位码操作,在Unity3d中Layers一共有32层,这个是不能增加或者减少的,LayerMask实际上是用Int32的32个位来表示每个层级,当这个位为1时表示使用这个层,为0时表示不用这个层。
 所以1<<表示把1左移几位(下标从0开始的),是1的那一位就开启了
 4.hit info .point 记录的是射线碰撞点的坐标,而hitinfo .transform .Position 记录的是碰撞到的物体的Position 位置属性(如2,1,3这样一个定死了的数)而不是点击处的坐标
 5.drawray (a,b,c )其中第二个参数b 表示的是方向而不是点,如果想要表示从射线始x 到尾y 的射线,那么第二个参数就写成y 的坐标减去x 的坐标得到一个方向向量
 6.发出射线时用到的mouseposition 只是用来两点发出一条射线用的,以后都用不到了,注意不要和碰撞点混淆
 7.一定要分层,为了点击地面和点击怪物响应不同事件
8.A. Sendmessage 调用a 里的这个方法
 A. Sendmessageupward 寻找a 和a 的所有父节点中调用该方法
 A .broadcast在A 和A 的子节点中寻找调用该方法
 所以可以绑定一个空对象,里面管理某类动作(如播放动画),然后绑定到人物上,用upward 那个让父类播放
9.!Tostring 不会改转化原来的变量的属性,只适合打印的时候使用,平时转化都要用别的,要获取某个组件名字用name 方法。如s =a .Tostring ,其实s 还是a 类型的,但是会以字符串打印出来
10.Onawake 阶段组件还没有设置到对象上,此时若企图getcomponent 则会报错没找到对象,所以组件必须在start 里get





  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值