《Unity3D网络游戏实战》学习与实践--制作一款大乱斗游戏

news2024/10/2 12:21:39

角色类

基类Base Human是基础的角色类,它处理“操控角色”和“同步角色”的一些共有功能;CtrlHuman类代表“操控角色”​,它在BaseHuman类的基础上处理鼠标操控功能;SyncHuman类是“同步角色”类,它也继承自BaseHuman,并处理网络同步(如果有必要)​。

BaseHuman

        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;

        public class BaseHuman : MonoBehaviour {
            //是否正在移动
            protected bool isMoving = false;
            //移动目标点
            private Vector3 targetPosition;
            //移动速度
            public float speed = 1.2f;
            //动画组件
            private Animator animator;
            //描述
            public string desc = "";

            //移动到某处
            public void MoveTo(Vector3 pos){
                targetPosition = pos;
                isMoving = true;
                animator.SetBool("isMoving", true);
            }

            //移动Update
            public void MoveUpdate(){
                if(isMoving == false) {
                    return;
                }

                Vector3 pos = transform.position;
                transform.position = Vector3.MoveTowards(pos, targetPosition, speed*Time.
                                      deltaTime);
                transform.LookAt(targetPosition);
                if(Vector3.Distance(pos, targetPosition) < 0.05f){
                    isMoving = false;
                    animator.SetBool("isMoving", false);
                }
            }

            // Use this for initialization
            protected void Start () {
                animator = GetComponent<Animator>();
            }

            // Update is called once per frame
            protected void Update () {
                MoveUpdate();
            }
        }

CtrlHuman

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CtrlHuman : BaseHuman
{
    new void Start()
    {
        base.Start();
    }

    // Update is called once per frame
    new void Update()
    {
        base.Update();
        if(Input.GetMouseButtonDown(0)) {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            Physics.Raycast(ray,out hit);
            if(hit.collider.tag == "Terrain") {
                MoveTo(hit.point);
            }
        }
    }
}

如何使用网络模块

在实际的网络游戏开发中,网络模块往往是作为一个底层模块用的,它应该和具体的游戏逻辑分开,而不应该把处理逻辑的代码写到 ReceiveCallback 里面去。因为ReceiveCallback应当只处理网络数据,不应该去处理游戏功能

一个可行的做法是,给网络管理类添加回调方法,当收到某种消息时就自动调用某个函数,这样便能够将游戏逻辑和底层模块分开。制作网络管理类前,需要先了解委托、协议和消息队列这三个概念。

通信协议

通信协议是通信双方对数据传送控制的一种约定,通信双方必须共同遵守,方能“知道对方在说什么”和“让对方听懂我的话”​。

使用一种最简单的字符串协议来实现。协议格式如下所示,消息名和消息体用“|”隔开,消息体中各个参数用“, ”隔开。

消息名|参数1, 参数2, 参数3, ...

Move|127.0.0.1:1234, 10, 0, 8,

处理数据:

        string str = "Move|127.0.0.1:1234, 10, 0,8, ";

        string[] args = str.Split('|');
        string msgName = args[0]; //协议名:Move
        string msgBody = args[1]; //协议体:127.0.0.1:1234, 10, 0,8,

        string[] bodyArgs = msgBody.Split(', ');
        string desc = bodyArgs [0];               //玩家描述:127.0.0.1:1234
        float x = float.Parse(bodyArgs [1]);     //x坐标:10
        float y = float.Parse(bodyArgs [2]);     //y坐标:0
        float z = float.Parse(bodyArgs [3]);     //z坐标:8

消息队列

多线程消息处理虽然效率较高,但非主线程不能设置Unity3D组件,而且容易造成各种莫名其妙的混乱。由于单线程消息处理足以满足游戏客户端的需要,因此大部分游戏会使用消息队列让主线程去处理异步Socket接收到的消息。

C#的异步通信由线程池实现,不同的BeginReceive不一定在同一线程中执行。创建一个消息列表,每当收到消息便在列表末端添加数据,这个列表由主线程读取,它可以作为主线程和异步接收线程之间的桥梁。由于MonoBehaviour的Update方法在主线程中执行,可让Update方法每次从消息列表中读取几条信息并处理,处理后便在消息列表中删除它们

NetManager类

网络模块中最核心的地方是一个称为NetManager的静态类,这个类对外提供了三个最主要的接口。

  • Connect方法,调用后发起连接;
  • AddListener方法,消息监听。其他模块可以通过AddListener设置某个消息名对应的处理方法,当网络模块接收到这类消息时,就会回调处理方法;
  • Send方法,发送消息给服务端。

无论内部实现有多么复杂,网络模块对外的接口只有图片展示的这几个:

对内部而言,NetManager使用了异步Socket接收消息,每次接收到一条消息后,NetManager会把消息存入消息队列中​。NetManager有一个供外部调用的Update方法,每当调用它时就会处理消息队列里的第一条消息,然后根据协议名将消息分发给对应的回调函数

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using System.Net.Sockets;
    using UnityEngine.UI;
    using System;

    public static class NetManager {
        //定义套接字
        static Socket socket;
        //接收缓冲区
        static byte[] readBuff = new byte[1024];
        //委托类型
        public delegate void MsgListener(String str);
        //监听列表
        private static Dictionary<string, MsgListener> listeners =
            new Dictionary<string, MsgListener>();
        //消息列表
        static List<String> msgList = new List<string>();

        //添加监听
        public static void AddListener(string msgName, MsgListener listener){
            listeners[msgName] = listener;
        }

        //获取描述
        public static string GetDesc(){
            if(socket == null) return "";
            if(! socket.Connected) return "";
            return socket.LocalEndPoint.ToString();
        }

        //连接
        public static void Connect(string ip, int port)
        {
            //Socket
            socket = new Socket(AddressFamily.InterNetwork,
                SocketType.Stream, ProtocolType.Tcp);
            //Connect(用同步方式简化代码)
            socket.Connect(ip, port);
            //BeginReceive
            socket.BeginReceive( readBuff, 0, 1024, 0,
                ReceiveCallback, socket);
        }
        //Receive回调
        private static void ReceiveCallback(IAsyncResult ar){
            try {
                Socket socket = (Socket) ar.AsyncState;
                int count = socket.EndReceive(ar);
                string recvStr =
                    System.Text.Encoding.Default.GetString(readBuff, 0, count);
                msgList.Add(recvStr);
                socket.BeginReceive( readBuff, 0, 1024, 0,
                    ReceiveCallback, socket);
            }
            catch (SocketException ex){
                Debug.Log("Socket Receive fail" + ex.ToString());
            }
        }

        //发送
        public static void Send(string sendStr)
        {
            if(socket == null) return;
            if(! socket.Connected)return;

            byte[] sendBytes = System.Text.Encoding.Default.GetBytes(sendStr);
            socket.Send(sendBytes);
        }

        //Update
        public static void Update(){
            if(msgList.Count <= 0)
                return;
            String msgStr = msgList[0];
            msgList.RemoveAt(0);
            string[] split = msgStr.Split('|');
            string msgName = split[0];
            string msgArgs = split[1];
            //监听回调;
            if(listeners.ContainsKey(msgName)){
                listeners[msgName](msgArgs);
            }
        }
    }

参考书籍:《Unity3D网络游戏实战(第2版)》 (豆瓣) (douban.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1979248.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL的数据结构B+tree以及SQL优化

首先呢&#xff0c;我们知道MySQL的数据结构为Btree,那么其结构究竟是什么样的&#xff0c;为什么选择Btree&#xff0c;而不选择Btree。下面我们从其结构分析 1.Btree平衡多路查找树 B-tree结构的数据可以让系统高效的找到数据所在的磁盘块。为了描述B-Tree,首先定义一条记录…

入门mem0.NET

入门mem0.NET 安装包 如果你的项目使用了EntityFrameworkCore,那么你可以跟随这个教程走 <ItemGroup><PackageReference Include"mem0.NET" Version"0.1.7" /><PackageReference Include"mem0.NET.Qdrant" Version"0.1.7…

云动态摘要 2024-08-04

给您带来云厂商的最新动态&#xff0c;最新产品资讯和最新优惠更新。 最新优惠与活动 数据库上云优选 阿里云 2024-07-04 RDS、PolarDB、Redis、MongoDB 全系产品新用户低至首年6折起&#xff01; [免费体验]智能助手ChatBI上线 腾讯云 2024-07-02 基于混元大模型打造&…

java之IO篇——File、字节流、字符流

前言 IO流是用于读写文件中的数据&#xff0c;要读写文件之前可以创建文件获取文件对象再创建IO流&#xff0c;正文会先介绍File类&#xff0c;通过File类的构造方法获取文件的对象&#xff0c;创建文件或目录以及File类的一些方法获取文件对象的属性。后面还介绍了相关的IO流体…

Radxa ROCK 3C开发板编译Opencv,支持调用树莓派摄像头模块V2

目录 1、ROCK 3C和树莓派摄像头模块V2介绍2、ROCK 3C在rsetup开启支持3、测试指令4、编译Opencv4.1 增加swap&#xff0c;确保内存够用4.2 安装依赖和下载opencv4.3 编译参考链接 5、使用opencv调用树莓派摄像头模块V2 1、ROCK 3C和树莓派摄像头模块V2介绍 ROCK 3C 是一款基于…

刷题篇 - 01

目录 题目一&#xff1a; 题目二&#xff1a; 题目三&#xff1a; 题目四&#xff1a; 题目五&#xff1a; 题目六&#xff1a; 题目七&#xff1a; 题目一&#xff1a; 387. 字符串中的第一个唯一字符 - 力扣&#xff08;LeetCode&#xff09; public int firstUniqC…

订单定时状态处理业务(SpringTask)

文章目录 概要整体架构流程技术细节小结 概要 订单定时状态处理通常涉及到对订单状态进行定期检查&#xff0c;并根据订单的状态自动执行某些操作&#xff0c;比如关闭未支付的订单、自动确认收货等. 需求分析以及接口设计 需求分析 用户下单后可能存在的情况&#xff1a; …

鸿蒙(API 12 Beta2版)NDK开发【内存管理purgeable内存开发指导】

场景介绍 HarmonyOS提供Purgeable Memory内存管理机制&#xff0c;开发者可以使用相关接口创建PurgeableMemory对象&#xff0c;从而管理purgeable内存。 开发者可以通过本指导了解在HarmonyOS应用中&#xff0c;如何使用Native层相关接口操作purgeable内存。功能包括purgeab…

Jupyter-Notebook常用操作看这一篇就够啦

来源&#xff1a; “码农不会写诗”公众号 链接&#xff1a;Jupyter-Notebook常用操作看这一篇就够啦 文章目录 01 概括02 快捷键总结03 运行外部python文件04 魔法命令4.1 运行计时4.2 查看变量与函数4.3 其它常用指令 书接上文 Jupyter-Notebook是一个基于 Web 的交互式开发环…

第十四节、受伤、死亡的逻辑和动画

一、受伤的动画效果 1齿轮控制当前动画图层的权重 2、层级 当前动画层为add&#xff0c;所以不会覆盖之前的动画层&#xff0c;而是添加一个动画层 3、受伤闪烁 调用颜色的值&#xff0c;实现受伤闪烁 4、录制动画 点击时间轴&#xff0c;插入关键帧 伤害图层选择add&…

2024华数杯C题解题思路、参考论文已出(无偿分享)~

C题&#xff1a;老外游中国 “数模加油站”团队出品~ 问题1&#xff1a; 解题思路&#xff1a; 1、数据准备&#xff1a; 导入352个城市的csv文件&#xff0c;提取每个城市中的100个景点的信息。 将每个景点的评分数据提取出来&#xff0c;形成一个包含35200个景点评分的列…

centos虚拟机restart网络后隔一会断联

1. 前言 不知道各位有没有遇到过虚拟机网络设置的坑&#xff0c;往往前一段时间用的好好的&#xff0c;突然网络又不行了无法连接外部网络&#xff0c;而且使用 service network restart 一瞬间可以&#xff0c;但是过一会就断连了… 2. 解决方案 根据对虚拟机网络的学习了解…

五、一个quad同时支持pcie和sfp两种高速接口的ref时钟配置

项目描述 上位机将截图数据通过 XDMA 写入到 FPGA 侧的 DDR 内存区域 1 中通过 axi_lite 接口给 axi_read_start 信号&#xff0c;通知 AXI_read 模块启动读取数据&#xff0c;然后通过 GTP TX 模块发送出去。经过光纤回环&#xff0c;GTP RX 端接收到数据&#xff0c;送给 AX…

今天的一件小事,亲身感受:付费是提高效率的重要途径

今天需要修改一个单页网站源码&#xff0c;有一个小问题困住我3个小时了。 毕竟我也不是专业的&#xff0c;没有系统学习过这些&#xff0c;搜答案都不知道怎么搜哈哈 吃过午饭&#xff0c;想着不能这么耗下去了&#xff0c;于是及时去了某宝&#xff0c;找到一个修改代码的 …

搭建nexus上传jar包,并结合jenkins运行项目

一、搭建nexus 1、docker拉取镜像 需要将docker原更新一下 https://blog.csdn.net/qx020814/article/details/140908006?spm1001.2014.3001.5502 docker pull sonatype/nexus3 创建nexus挂载文件、增加权限&#xff1a; mkdir docker_nexus3 mkdir docker_nexus3/nexus-data c…

TreeSize:免费的磁盘清理与管理神器,解决C盘爆满的燃眉之急

目录 TreeSize&#xff1a;免费的磁盘清理与管理神器&#xff0c;解决C盘爆满的燃眉之急 一、TreeSize介绍 二、下载安装TreeSize 2.1、下载地址 2.2、下载步骤 ​2.3、安装步骤 三、professional版的TreeSize试用 3.1、分析磁盘空间 3.2、显示拓展名统计信息 3.3、显…

将本地的业务写成成可供RPC远程调用的方法

第一步&#xff1a;首先我们先定义proto文件&#xff0c;这些proto文件将会为远程调用者提供调用的方法&#xff0c;为login方法。 2.重写UserServiceRpc类中的Login方法。 在Login中做的操作主要是&#xff0c;得到requst里面的参数&#xff0c;然后调用本地的Login方法&#…

可解释性终极追问,什么才是第一性解释?20篇CCF-A+ICLR论文给你答案

一、前言 长期以来&#xff0c;我们团队一直在思考可解释性领域的一个终极问题&#xff0c;即什么才是解释性领域的第一性原理&#xff1f;所谓第一性原理&#xff0c;目前没有一个被广泛接受的框架&#xff0c;世上本无路&#xff0c;我们需要逐渐去定义这样一个路。我们需要在…

Linux系统中的高级内核模块调试技术

引言 在Linux系统中进行高级内核模块开发时&#xff0c;调试是不可或缺的重要环节。调试技术能够帮助开发人员发现和解决代码中的错误和问题&#xff0c;提高开发效率和代码质量。本文将深入探讨Linux系统中高级内核模块调试的技术和方法&#xff0c;包括常用的调试工具、调试…

JAVA—面向对象编程高级

学习了一定基础后&#xff0c;开始更加深入的学习面向对象&#xff0c;包含static,final两个关键字&#xff0c;面向对象编程三大特征之继承和多态。以及对于抽象类&#xff0c;内部类&#xff0c;接口&#xff0c;枚举&#xff0c;泛型的学习。 目录 1.static &#xff08;…