草庐IT

Unity独立开发游戏之路

库卡の巨价 2023-10-15 原文

简单的介绍

        本人本科是上海师范大学教育类的专业,由于对于计算机领域的兴趣考研考到了本校的计算机专业。对于跨考计算机的心路历程也许会专门写一篇文章。之所以会尝试自学unity独立开发自己的游戏,部分原因是因为自己对于计算机的领域的兴趣,也有自己喜欢自由自在的创造,还有部分原因是一位多年好友对开发游戏的执着。期间遇到的困难和压力也是非常具有挑战性,我们通过观看各种unity大佬的视频,搜集项目源码进行学习。实现了一个又一个功能,还是挺有成就感。我会挑选几个让我眼前一亮地代码和思路进行记录分享,也是对于自己小结。

        我之后所提到的思路和代码并不适合unity初学者,尽管我也只是个初学者。因为我确实没有做游戏软件有个宏观的设计,之前也没接触过任何类似的项目。我属于是想到哪写到哪的那种情况,因此我需要这篇文档来对我自己之前的工作有个回顾。

        如果你能给予我任何意见,我一定会认真听取,我也会尽我所能完善这篇文章。

前言

        该项目是一款unity2D回合制战略卡牌游戏,简单来说就是每名玩家操控一枚棋子移动,并且利用各个棋子的技能以及卡牌与其他玩家战斗。使用的是Photon网络框架进行联机的设置。

登录界面

 实现的功能

玩家输入自己的昵称,然后选择加入的房间。实现起来难度不大。

部分代码

using Photon.Pun;//联网
using UnityEngine.UI;//操作UI,好像暂时用不到
using Photon.Realtime;//使用RoomOptions类设置房间相关信息时需要用用到
using TMPro;//操作TextMeshPro文本需要用到

public class NetworkLaunch : MonoBehaviourPunCallbacks{}
//unity中的脚本默认继承MonoBehaviour,这里继承PunCallbacks是因为我们需要重写Photon中的回调函数

private void Start()
{
    PhotonNetwork.ConnectUsingSettings();//连接到服务器
}
public override void OnConnectedToMaster()//回调函数,设置UI可见性
{
    base.OnConnectedToMaster();
    nameUI.SetActive(true);
}
public void OnClickPlayBtn()//输入完昵称,按下按钮调用
{
    nameUI.SetActive(false);
    PhotonNetwork.NickName = playerName.GetComponent<TMP_InputField>().text;
    loginUI.SetActive(true);
}
public void OnClickJoinBtn()//输入完房间名,按下按钮调用
{
    if (roomName.GetComponent<TMP_InputField>().text.Length < 2)
    {
        return;
    }
    RoomOptions roomoptions = new RoomOptions();
    roomoptions.MaxPlayers = 8;
    PhotonNetwork.JoinOrCreateRoom(roomName.GetComponent<TMP_InputField>().text, roomoptions, TypedLobby.Default);
}
public override void OnJoinedRoom()//回调函数,玩家加入房间时调用
{
    base.OnJoinedRoom();
    PhotonNetwork.LoadLevel(1);
}

房间等待界面 

房主视角:

 其他成员视角:

 其他成员准备后

 实现的功能

        左上角显示房间名,每个玩家上方显示自己的昵称。房主只有开始游戏按钮,其他成员只有准备按钮,其他玩家点击准备,所有房间中的玩家均显示该玩家准备状态,再次点击准备按钮,准备会取消显示。后进入的玩家按顺序入座。当所有玩家均准备时,房主点击开始游戏才会生效,所有玩家进入下一个游戏场景。

        这个房间的设计简直是Photon网络框架实现网络同步的入门考试!下面我将详细说明。

部分代码

        我会按照我当时完成功能的先后顺序来进行描述,可能会缺少一定的逻辑

生成房间成员到指定位置

PhotonNetwork.Instantiate("Roomer", Roomerloc[i], Quaternion.identity, 0);

        这里使用的是Photon的网络生成预制件,需要将预制件存储指定的Resource文件夹中,这样Photon会根据第一参数的字符串找到指定预制件。第二个参数是世界坐标的位置(区别于Canvas中的坐标),后面几个参数没具体了解,好像默认的就行。

        使用Photon自带的网络生成的好处是同步方便,一旦生成所有玩家都能看到这个游戏对象。但也有一定的缺点,canvas中利用Grid layout Group可以方便地对生成在其中地游戏对象进行排列,但是Photon网络生成的游戏对象只能在世界坐标下,如何使得Roomer排列整齐成为了第一个困难。

    public List<Vector3> Roomerloc = new List<Vector3>();    
    public void InitRoomloc()
    {
        Vector3 v3 = new Vector3(-6, 2, 0);
        for (int i = 0; i < 2; i++)
        {
            for (int j = 0; j < 4; j++)
            {
                Roomerloc.Add(v3);
                v3 += new Vector3(4, 0, 0);
            }
            v3 = new Vector3(-6, -2, 0);
        }
    }

        解决这个问题其实不难,我只需要利用简单的循环计算出适合Roomer放置的位置,存储在Roomerloc当中。接下来需要解决新的问题,如何将Roomer生成到我们想要的位置,这里我想到了一个比较容易地方法:设置一个bool型的数组is_sit来标记哪些位置已经被占用了,后加入房间的玩家只需要寻找一个没有被占座的座位坐下来即可。

        这个问题说实话困扰了我几天的时间,原来“欠下的还是要换的”。之前使用网络生成轻而易举地解决了生成同步的问题,但是这个入座问题还是回到了同步问题上去。最初的尝试的结果都是is_sit中数据未能同步,导致几名Roomer坐到同一个位置的情况。

如何实现同步

RPC方式

gameobject.GetComponent<PhotonView>().RPC("UpdateRoomInfo", RpcTarget.MasterClient,传递参数);

        需要传递参数的脚本需要挂载Photonview脚本,使用RPC(Remote Procedure Call)进行数据传输,第一参数是函数名称,这些函数之前需要[PunRPC]进行标记,RPC才能找到他们。第二参数是发送数据的对象,RpcTarget.All是房间中所有对象(包括自己),RpcTarget.MasterClient只发送给主机。第三个参数传递参数可以是基本数据类型(int,int[]等)但不能是Gameobject对象。传递参数与调用的函数中的形参需要对应。

SetCustomProperty方式

    //设置玩家的属性
    ExitGames.Client.Photon.Hashtable table = new ExitGames.Client.Photon.Hashtable();
    table.Add("IsReady", false);
    PhotonNetwork.LocalPlayer.SetCustomProperties(table);

    //获取玩家的属性
    object isready;
    if (player.CustomProperties.TryGetValue("IsReady", out isready)){}
    //使用isready需要强制转换

        我们可以来设置玩家属性,Photon自动会帮我们同步到其他主机之上(只不过速度真的很慢),根据一些经验,一个客户端只能设置自己的property,设置其他玩家的property是不会同步的。

        

        知道了同步的工具还不够,因为一个新玩家加入到房间当中并不是将自己数据告诉其他人,而是请求其他人把数据(is_sit)同步给自己。所以在游戏对象的Start方法中,用RPC向主机请求is_sit数据,主机向所有人发送is_sit数据。对于is_sit的修改只需要用RPC修改主机中的数据即可。简而言之主机来确保is_sit的数据一致。

初始进入房间的同步

        现在玩家生成到指定的位置,刚加入的玩家确实能看到前面有几个和他自己一样的“Roomer”预制件,他们是谁,他们的准备状态如何?这些又该如何显示呢?

        //显示名字
        if (photonView.IsMine)
        {
            SetRoomerName(PhotonNetwork.NickName);
        }
        else
        {
            SetRoomerName(photonView.Owner.NickName);
        }

        //显示准备状态
        if (photonView.Owner.IsMasterClient)
        {
            roomhostimage.SetActive(true);
        }
        object isready;
        if (photonView.Owner.CustomProperties.TryGetValue("IsReady", out isready))
        {
            SetReadyImage((bool)isready);
        }

        这是写在每个Roomer在唤醒时所调用的代码,虽然“生成“在一个终端只有一行代码,但是他的生成在所有终端都会调用,也就是说所有终端都会调用每个Roomer生成代码。显示名字的这几行if/else言简意赅但表达的意思很多。如果一个客户机生成自己创建的Roomer,就会执行if中的代码,获取全局变量PhotonView中的NickName,因为这个变量就是当前操作的玩家。如果一个客户机生成其他的Roomer就会执行else中的代码。其中photonview是每个roomer各自的,也就是获取创建Roomer中的玩家的NickName。准备状态的显示也是同理,当然前提需要我们在点击准备时,时刻修改property玩家的准备信息。

游戏主界面

        这是我们游戏中最为核心的部分,也会是篇幅最长的一个部分。我将游戏中的内容大致分为四个板块:

1,初始化

2,玩家移动

3,卡牌制作

4,玩家对战

        可以简单看一下这个游戏设计得界面,类似于飞行棋,玩家在移动阶段时可以在棋盘上自由移动,之后可以按照一定规则对棋盘上的其他玩家使用牌或者释放技能

 

初始化

        初始化工作我在写代码之初并没有仔细考虑过,但是随着功能的增加,我发现必须要用初始化来做一些游戏进行必要的准备工作,尤其在联机的情况下。并且这些初始化工作也是必须按照一定的先后顺序执行,我将逐一介绍初始化我做了哪些准备工作。

Gamer的生成

//Networkmanager中的start中生成
gamer = PhotonNetwork.Instantiate("Gamer", Vector3.zero, Quaternion.identity, 0);


//Gamer中的Start中执行
if (photonView.IsMine)
{
    ownid = PhotonNetwork.LocalPlayer.ActorNumber;
}
else
{
    ownid = photonView.Owner.ActorNumber;
}

        Networkmanager网络管理每个玩家都拥有有且仅有一个,用于管理客户端通信之间的代码。Gamer是网络生成的“玩家”,是在世界坐标系下的一个游戏对象。我知道photon为每个房间中的玩家创建了Player类的实例化对象 ,而我最终生成的“棋子”是在canvas当中。为了能使得他们Player和棋子产生联系,我考虑使用Gamer这个中间量。通过用Gamer中的变量指向操作的棋子,来建立操作者和他们操作的棋子之间的联系。

        你如果能看到这里,关于ownid想必应该不陌生,我看大佬用这个变量记录玩家的唯一标识符,也就是可以通过Gamer.ownid简单判断是否属于某个Player。我也就依葫芦画瓢,也确实对于后面的代码非常有帮助。

有关Unity独立开发游戏之路的更多相关文章

  1. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  2. ruby-on-rails - 独立 ruby​​ 脚本的配置文件 - 2

    我有一个在Linux服务器上运行的ruby​​脚本。它不使用rails或任何东西。它基本上是一个命令行ruby​​脚本,可以像这样传递参数:./ruby_script.rbarg1arg2如何将参数抽象到配置文件(例如yaml文件或其他文件)中?您能否举例说明如何做到这一点?提前谢谢你。 最佳答案 首先,您可以运行一个写入YAML配置文件的独立脚本:require"yaml"File.write("path_to_yaml_file",[arg1,arg2].to_yaml)然后,在您的应用中阅读它:require"yaml"arg

  3. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  4. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  5. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  6. ruby-on-rails - 在 Rails 开发环境中为 .ogv 文件设置 Mime 类型 - 2

    我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain

  7. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  8. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  9. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  10. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

随机推荐