基于Unity3d的军事模拟演练游戏开发与实现_第1页
基于Unity3d的军事模拟演练游戏开发与实现_第2页
基于Unity3d的军事模拟演练游戏开发与实现_第3页
基于Unity3d的军事模拟演练游戏开发与实现_第4页
基于Unity3d的军事模拟演练游戏开发与实现_第5页
已阅读5页,还剩48页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

摘要随互联网信息时代改变人们生活密切相关的方方面面,而游戏和虚拟现实事业凭借着和互联网的高契合性受到许多人对其的看好和关注。其中,射击类游戏可以给玩家们最为酣畅淋漓的快感,此类游戏不仅能消除人们在生活中的压力,在游戏中更能锻炼到反应速度和心态,同时还能提高对紧急情况的应变能力,有着极广的受众群体。Unity3D游戏引擎凭借其较低的入门难度,开发速度迅速的特点在近几年来在市场中逐渐为大众所知所用。本论文探究基于Unity3d的军事模拟演练游戏开发与实现,跟随的游戏的趋势,是一款兼容了第一人称和第三人称的射击游戏,该游戏可以自由的切换人称来符合不同玩家的需求,同时添加了大逃杀元素,以添加大地图以及搜集物资的模式使得该游戏充满了各种随机性要素,变成了一款玩法更加丰富的射击游戏,需要玩家时刻保持警觉,众多的藏身地和遮挡物使得该游戏更具策略性,与枪法成为游戏的双核心。而这种结合随着2017年3月《绝地求生》的发布引发热潮,众多游戏厂商为了迎合市场开发了一系列的大逃杀游戏,至今火热,可见拥有着客观的发展前景。本文主要描述该游戏结合引擎的开发原理,游戏的设计与代码实现,同时对于游戏画面的处理以及最终游戏功能测试,希望对后续开发同类型游戏的开发者提供思路以及参考。关键词:Unity3D;动作;射击;大逃杀

AbstractAstheInternetinformationagechangesallaspectsofpeople’slivesthatarecloselyrelated,gamesandvirtualrealityarehighlyvaluedandconcernedbymanypeoplefortheirhighcompatibilitywiththeInternet.Amongthem,shootinggamescangiveplayersthemostheartypleasure.Suchgamescannotonlyeliminatethepressureofpeopleinlife,butalsoexercisethereactionspeedandmentalityinthegame,whilealsoimprovingtheabilitytorespondtoemergencies.,Withaverywideaudience.TheUnity3Dgameengine,withitslowentrydifficultyandrapiddevelopmentspeed,hasgraduallybecomeknowntothepublicinthemarketinrecentyears.ThispaperexploresthedevelopmentandimplementationofamilitarysimulationexercisegamebasedonUnity3d.Thetrendofthegamethatfollowsisashootinggamethatiscompatiblewiththefirst-personandthird-personshootinggames.Thegamecanfreelyswitchthepersontomeettheneedsofdifferentplayers.TheBattleRoyaleelements,themodeofaddinglargemapsandcollectingmaterialsmakethegamefullofvariousrandomelements,andithasbecomeashootinggamewithrichergameplay.Playersneedtobealertatalltimes,andtherearemanyhidingplacesandocclusions.Thingsmakethegamemorestrategic,andthemarksmanshipbecomesthedualcoreofthegame.Withthereleaseof"JediSurvival"inMarch2017,alotofgamemanufacturershavedevelopedaseriesofbattleroyalegamestocatertothemarket.Sofar,ithasshownanobjectivedevelopmentprospect.Thisarticlemainlydescribesthedevelopmentprincipleofthegamecombiningengine,gamedesignandcodeimplementation.Atthesametime,fortheprocessingofthegamescreenandthefinalgamefunctiontest,Ihopetoprovideideasandreferenceforsubsequentdeveloperswhodevelopthesametypeofgame.Keywords:Unity3D;Action;Shooting;BattleRoyale广东东软学院本科毕业设计(论文)目录TOC\o"1-3"\h\u第一章绪论 绪论选题的目的和意义随着时代的变迁,人类的娱乐活动逐渐从实体转移到了虚拟物体上。计算机网络的普及,使人与人之间的联系变得更为接近,即便是千里之外,亦能共享娱乐带来的乐趣。毫无疑问的说,虚拟娱乐方式的出世,极大的丰富了人们的娱乐方式,给人提供了释放生活压力的新方式,随着人们对游戏的探究以及设备硬件的提高,人们越来越追求更加真实的感官,更深的浸入感,而能使玩家更能融入其中的3D游戏已经为市场主流。而论最能释放压力的游戏方式,则是为紧张刺激的射击类游戏,由其衍生出来的大逃杀模式更是能让人体会到肾上腺素飙升的快感。本次论文选择的游戏引擎为时下流行的Unity3d引擎,Unity3D是让开发者玩家轻松创建三维模型可视化与渲染,现代化用户交互界面程序或时下火热的高性能3A游戏大作等多样化互动类型的多平台兼容的综合型游戏开发工具,最为显著的特点是集成度高,开发门槛低,很适合第一次接触游戏制作的开发者的一款引擎软件。Unity3D引擎(简称U3D)在众多游戏引擎中,算是比较年轻的一个引擎,由于近年来需求大量高科技人才,而U3D以他不高的入门门槛以及高便利性迅速的火了起来,以至于各大公司对于能够熟练使用U3D开发软件的人求贤若渴。作为一个主打游戏的引擎,在其他比如展厅项目、渲染效果图等领域也开拓了一大块市场。而凭借U3D相比其他引擎较为短暂的入门周期,客观理想的市场前景,使得如今各大高校也将U3D引擎的学习列入了一门专门课程,从侧面也可以看出社会上能够熟练的使用U3D制作游戏的人才可以得到薪资丰厚的工作,结合以U3D如今的发展趋势,毫无疑问学习该引擎是一个不会令人后悔的选择。本文正是想要通过自行制作游戏去理解游戏的整个流程,熟悉游戏制作上的相关技术。本次选择的为时下大热的“绝地求生”游戏,由于日常也有玩过这个游戏,对流程比较熟悉,所以具体阐述游戏的框架以及制作过程。而游戏本身的趣味性是毋庸置疑的,射击类游戏本身就有着极高的乐趣,每次命中敌人都有着一种独特的快感,而且在现实生活中由于体力和体能的限制,并不是每个人都可以持枪射击和奔跑,而在虚拟模拟演练中,可以尽情的奔跑和射击,进可持枪攻敌,退可伏地阴人。可以和大家一起争夺空投,游戏的多样化带了的娱乐性是十分丰富的。研究现状自1981年,任天堂的拿手好作品《DonkeyKong》登场拉开帷幕以来,至今游戏行业的发展已历经几轮巨变。遥想当年,游戏界尚处于萌芽阶段,主人公马里奥第一次出现在大众视野中,何人曾猜到这个普通的水管工能担任后来任天堂的首席形象代表,那时正是街机游戏百花齐放的12年,游戏行业的开始了蓬勃发展,其中日本更是为此行业的霸主。3D游戏的起源来自于90年代的美国,自3D游戏引擎的问世后瞬间扩展全球。从那时候开始,游戏产业的丰厚收入甚至超过了传统工业,巨大的市场使世界各大经济强国重新开始审视游戏行业,游戏产业的地位也逐渐水涨船高,。中韩国政府对于游戏的大力支持使得韩国能够后来居上,如今的游戏发展规模已经和当初的霸主日本平分秋色,而韩国发展的最为优秀的网络游戏方面更是夺得了亚洲的王冠,而如今韩国的经济支柱之一已经妥妥的由游戏产业带来的效益支撑。结合我国之前的发展需求以及传统观点的影响,游戏行业在我国发展处于起步晚、经验不足的情况。并且由于经济实力以及开发周期等原因,除了大型公司,国内多数公司在权衡利弊之后,还是更加稳妥的选择了国外更为成熟的游戏引擎工具,众多引擎之中,Unity3D和虚幻在我国最为青睐,两大巨头平分秋色。但今年来我国游戏的收入也有着显著的提升,伽马数据REF_Ref25256\w\h[1]REF_Ref19843\w\h[12]发布《2019中国游戏产业年度报告》显示,2019年我国游戏市场实际销售收入2330.2亿元,同比增长8.7%,用户规模、自主研发、海外市场等主要指标均明显好于去年同期。目REF_Ref19758\w\h[13]前中国自主研发的移动游戏在美、日、韩、英、德等国家的流水同比增长率均高于该国移动游戏市场的增速,表明国产移动游戏在海外市场已经建立一定的创新优势。将游戏与文化结合是打造精品的重要方向。2019年上半年,游戏厂商游族网络海外地区营业收入达到10.09亿元,占总营业收入的58.36%。得到海外市场认可,很重要一点就是文化融入。可以看出随着移动硬件设备的提升,移动游戏也开始了惊人的增长,同时移动电竞成为了产业发展的主要驱动力。很多游戏产品正突破技术壁垒提高体验,VR、AR游戏逐渐普及。REF_Ref19879\w\h[14]随着5G商用正式启动、基站不断完善扩展,高速便捷的网络基础推动了游戏产业诸多创新。其中,云游戏以云计算等技术基础,将游戏在服务器端运行,通过网络传送给用户,破解游戏对高端电脑配置等的依赖,成为当前创新的主要方向之一。一旦普遍,移动端市场将会进一步扩大,而Unity3D的跨平台特性将会具备巨大优势,熟练使用该引擎有着不可忽视的战略意义。论文组织结构 论文从构思设计,参考着相关的博客以及技术书籍,整合了前人的经验,完成了一个模拟军事演练的枪战游戏制作。下文将通过游戏的制作展示U3D的相关组件和操作,同时也分享一下有关于游戏的制作流程和个人制作该游戏的心得,其中着重分享游戏的功能设计及其代码的编写实现。现将论文根据游戏制作过程分解成主要的六大章,各章节的如下安排:第一部分:绪论,主要阐述了该毕业设计制作的背景、国内外对相关课题的研究现状以及本设计的大概介绍; 

第二部分:对U3D的部分功能进行介绍,并且推荐以下本游戏制作过程中使用到的各类开发工具以及引用的插件。第三部分:讲述游戏的场景以及用户交互界面的设计使用到的组件及实现。 第四部分:详细介绍游戏的各个模块的制作,清晰明确游戏内部关系,采用由下到上的顺序层层拆析步骤直观介绍下整个游戏流程以及游戏框架的设计和搭建,并且展示游戏中各模块比较核心的脚本源代码,采用由主及枝详细解析各步骤。

第五部分:阐述一些屏幕后处理以及着色器的实现。第六部分:展示一下游戏运行数据以及测试结果。第七部分:制作该游戏的心得,针对游戏中一些不足和缺陷进行总结,以及如何改良。 

Unity游戏引擎及插件软件介绍Unity3D游戏引擎Unity3D引擎介绍Unity3D引擎(以下简称U3D)是UnityTechnologies研发的让玩家轻松创建丰富的互动内容软件的多平台的综合型游戏开发工具,完美的跨平台系统大大提高了不同平台移植的工作效率。同时U3D还具备高度集成的系统,以及优秀的用户交互操作界面,使得用户能够轻易入手该软件创建自己的工程。其中五个视图框架Project(存放资源文件)、Hierarchy(放置游戏对象)、Scene(预览编辑界面)、Game(游戏运行结果)、inspector(查看对象的信息)合理放置并且多利用编辑器的快捷键可以使工作效率得到显著的提高。以上所说的都是U3D比较表面的东西,现如今U3D在不断的优化,引入了灯光、地形、动画、渲染、粒子系统、可编程shader、物理系统等等。而引擎支持的语言为C#、javascript、boo三种脚本语言,其中C#由于优秀的性能使得官方对其优化力度也较高,所以大多数使用者选择了C#。Unity3D常用函数由于U3D的高度集成性,在实现功能时,常常可以借用U3D提供的函数来完成,可以通过继承和引用命名空间的方式来获取函数,同时也可以通过继承重载函数,我们可以通过进入函数内部结构查看内置函数的说明。而U3D不像使用Vs编写程序一样,需要从Main函数入口进入运行,只需要引用UnityEngine命名空间并继承MonoBehavior脚本并挂载到场景物体上运行时内部就会不断迭代以下的生命函数REF_Ref25684\w[4]:Awake():物体初始化时调用,无论是否被激活。Start():晚于Awake()执行,同时需要激活物体。OnEnable():每次激活该物体都会执行一次,适用于每次打开的物体的初始化。Update();每帧都会调用一次,帧率是不稳定的,对标的为FixedUpdate(),区别在于FixedUpdate是固定帧,同时可以通过Application.targetFrameRate对游戏帧数进行锁定。LateUpdate():在Update(),后执行,用于比如相机渲染等需要最后刷新的函数执行。DisEnable():每次关闭物体使用一次,通过使用gameobject.Setive来控制物体的开关触发。另外还还有些常用的函数:OnTriggerEnter():需要碰撞体组件并且开启Trigger模式,当有刚体(Rigidbody)组件的物体进入该物体使则会一直调用里面的函数。OnCollisionEnter():和OnTriggerEnter():不同,两个都需要碰撞体和刚体,碰撞则触发一次OnCollisionEnter2D():同OnCollisionEnter(),只是为二维对象。OnTriggerEnter()、OnTriggerStay()、OnTriggerExit()为一组,分别代表进入、保持进入、离开,只有Stay会一直触发,其他的均自触发一次。每一个在引擎内新建的脚本范本都会继承和引用基类,在制作一些类似节点的脚本,或者结构体等不需要再周期内调用的函数,可以自行去掉节省资源,后期可通过GC内存管理清理不必要的内存。2.1.3GUI(Unity3D自带用户图形界面)本次游戏的制作使用引擎自带的UGUI进行UI界面的开发,GUI是图形用户界面的简称,又叫图形用户接口。由于UI界面的存在,用户不需要太高的专业性,做到了将技术性隐藏起来,同时增加了美观性。通过使用UGUI可以简单快捷的添加封装好的各类界面元素,在创建完毕时候,引擎会实例化功能定义,而脚本中只需要获取并且引用即可修改其中的资源信息,相对于远古版本的OnGUI(U3D自带的脚本实现UI界面),显著的提高了编辑速度以及客观性,同时后期由于控件封装的功能,可以使后期的修改位置角度或图片等元素十分快捷,后期跨平台或改变分辨率后对于空间的调整也不至于那么麻烦,极大的减少了开发时间以及设计的代码量。2.1.4物理引擎及其他系统物理引擎是通过刚性物体赋予真实的物理属性的方式来计算运动、旋转和碰撞反映。如今的游戏以3d游戏为主流趋势,而物理系统也成为了游戏的不可或缺要素,可以说一个开发者能将物理引擎完美融入到游戏性,那么这个游戏必将成为游戏中的佼佼者。虽说物理系统可以通过自己编程来实现,而对于初窥门径的开发者来说,无疑是举步为艰。而U3D为开发者提供了一系列物理组件,让开发者不需要深入了解原理即可初步使用组件构架出物理法则世界。无独有偶,单单有物理系统是无法让玩家沉浸其中,U3D提供了譬如地形、粒子、着色器、音效、动画机、材质球、音效、贴图、智能导航等等组件,使得初学乍练的开发者能满怀热情去探索游戏世界。2.1.5DoTween插件DOTween是一款针对于Unity开发的快速,高效兼顾类型安全的面向对象的免费动画引擎,针对C#用户进行了开源优化,具有大量提高效率的功能。因为一切都被缓存并且重用,避免了无用的GC分配,实现同时兼顾速度与效率。当前开发团队更新了更为专业和高性能的进阶收费版本,但本次游戏我们仅需在Unity商店下载免费版本足以使用。通过本插件可以方便快捷的制作补间动画。不论是颜色、位置或者大小,在引入插件命名空间后直接调用物体的tranform组件即可轻易的调用函数实现动画。由于其应用广、逻辑易用、自由度高等特点,已经成为了每个使用Unity的新手最为热门的入门插件之一。2.2C#开发语言C#是微软公司发布的一种面向对象的、运行于.NET

Framework和.NET

Core(完全开源,跨平台)之上的高级程序设计语言。C#在不损失C/C++原有强大的功能和编写的丰富类库的前提下,能够让使得C++程序员可以高效的开发程序。C#是C的衍生,因而与C/C++具有着极大相似性,意味着了解类似语言的开发者可以快速的转向C#。C#并不同于LUA、PYTHON具备跨平台的特性,所以Unity殷勤通过Mono虚拟机将代码实时编译或者预先编译到原生代码实现移植到非Windows平台的。选择该语言,是因为C#在网络开发方面成绩很优秀、同时相对于动态语言js更加稳定,具备优秀的类库,同时U3D开发团队也更倾向对于C#进行优化。2.3VisualStudioCode软件Visual

Studio

Code软件是微软公司于2015年4月30日在Build开发者大会上正式推出的一款轻量级的跨平台源代码编辑器。使用该软件进行代码的编写可以使系统资源占用大幅度减少,同时最为人称道的是他的极其强大的拓展性,,使得每个用户都能够找到自己想要的大部分功能。我最终选择该软件进行编程的原因是因为其内嵌的小地图,信息预览和使用详情,同时支持全局替换,这对于后期的优化以及调试有着巨大的便利性。美术资源设计3.1场景设计3.1.1地形绘制正如前文提到的Unity自带了地形(Terrian)系统,本次游戏地形的制作就是通过该系统创建而成。该组件该组件类似于Maya中使用置换贴图实现变形高段数的多边形平面,原理是通过中等多边形密度的多边形平面,再指定一张灰阶图作为高插图,根据mesh各顶点对应的灰度数值改变Y轴的高度形成高低起伏的地形。通过Create->3DObject->Terrain在Hierarchy面板创建对象(如图4.1-1),该组件的Terrian属性栏最上排有七个主要按钮,除了最后一个设置按钮,其他的都是以笔刷的形式绘制地形,简单介绍一下功能REF_Ref26938\w\h[5]。图3-1创建地形Raise

Lower

Terrain、Paint

Height、Smooth

Height

这三个选项

点击后都会出现笔刷面板,通过自定义笔刷大小(BrushSize)和明度(Opacity)和笔刷类型可自定义绘制的地形,通过鼠标在地形上拖动即可绘制,而同时按住Shift键时绘制会变成使地形下限,前提是在TerrainSettings中设置地形的高度。PaintHight可以直接刷出特定高度,按住shift键获取某一点的高度,即可直接刷出一个平滑的水平面高度的平整地形SmoothHeight点击该选项后使用笔刷可以平滑地形,使坡度不会显得突兀。Paint

Texture、Place

Trees、Place

Details这三个选项都需要自定义贴图或者物体,根据顺序起到的作用分别为在地形表面绘制贴图、添加大型物体(通常用来做树)、添加细节物体(如草丛、花),自定义添加物体需要需要在Textures/Trees/details栏中点击编辑按钮,再选择对应的资源即可直接在地图中刷出物体,十分便捷,同时可以设置透明度改变刷出物的密度(如图3-2)。

图3-2添加编辑物体3.1.2添加风尘效果完成了地形的绘制后,我们在场景中添加风尘的效果,提高游戏的沉浸度和浸入感。首先制作一个风尘粒子效果15,Unity中同样集成了粒子系统,在Hierarchy面板创建一个ParticelSystem,打开Inspector栏可以看到许多的选项,简单介绍一下我使用到的属性REF_Ref26990\w\h[6]。(如图3-3)图3-3粒子效果基本属性:其中包括粒子的动画时间、是否循环、发射速度和角度,颜色和最大粒子数等属性,由于风尘的凌乱我将发射角度和速度以及颜色各属性设置在了一个区间内随机。Emission:控制发送速率,会影响粒子数量。Shape:控制发射场的形状,这里设置为Box。Coloroverlifetime:控制生命周期内颜色变化,可多个颜色混合或取随机值。Rotationoverlifetime:控制生命周期内的角度变化,可多个角度混合或取随机值。Renderer:可以改变粒子的渲染材质或者切换网格体等一系列渲染参数。为了场景中的物体受到风的影响,我们在地形下添加风场(WindZone),配置了风场效果后,场景内的树会根据风场的方向和速度摇曳。(如图3-4)图3-4添加风场后的场景效果3.1.3添加建筑物地形制作和风尘效果实现后,我们将房子模型预设体放置于场景中,为不可通过的物体(如墙、栅栏)添加对应的碰撞体(不规则的模型可用MeshCollider启动convex属性,网格便会完美贴合于模型上),同时为所有门添加之前便写好的交互脚本。在场景中选择合适的位置添加敌人预设范围和玩家出生点,最终为所有的房子添加生存资源自动生成器(创建一个Cube关掉渲染效果和碰撞器同时添加ItemSpawner脚本,如图3-5)。图3-5配置完毕的场景3.1.4添加水面动效岛屿四周的水面以及岛中的一些水池如果只是用单纯的面片的话会显得十分的死板,而让水面有动态的效果有两种做法,一是通过修改面片的法线起到高低起伏的效果,二是通过着色器修改。而第二种方法由于节省大量的资源被大众所提倡使用,这次我也通过这种方式实现水面的动效。首先,我们要先明白着色器的渲染流水线有两个比较重要的阶段为顶点处理和像素处理,而并不改变定点的信息,所以主要针对的是像素程序的编写。主要的思路就是主贴图的uv坐标随时间改变,而在着色器中实现随机的效果一般通过导入一张噪点图,根据获取噪点图的差值信息和原图进行叠加,最终输出的结果就是为随机波动的图像动画了,核心代码和效果图如下。REF_Ref25668\w\h[11]fixed4

f_deal

(v2f

i)

:

SV_Target

{

fixed4

bias

=

tex2D(_Noise,

i.uv+_Time.xy*_distortFactorTime);

half4

color

=

tex2D(_MainTex,

i.uv+bias.xy*_distortFactor);

return

fixed4(color.rgb,0.5f);

}

图3-6着色器处理后的水面3.2UI(用户交互界面)设计UI面板分为游戏面板:背包面板、小地图大图面板、玩家游戏信息面板(指针、击杀信息、准心瞄准镜、血量能量条、子弹情况、倒计时、存活数量、公告信息)以及游戏系统面板:胜负界面、游戏菜单界面。(如图3-6)图3-6游戏UI的层级分布所有需要开关的UI面板都可以通过挂载PanelInstance这个单例脚本,同时在该脚本中引用了Dotween插件,在OnEnbale中使用了插值动画,使每次打开面板时能够实现变大逐渐呈现的插值动画效果,再在游戏初始化时获取所有的交互面板到面板管理器中PlayerHUDCanvas(PanelsManager)。//利用DG实现过场动画效果

public

void

OnEnable(){

this.transform.localScale

=

Vector3.zero;

this.GetComponent<Image>().color

=

new

Color(255,255,255,0);

this.transform.DoScale(new

Vector3(1,1,1),0.5f);

this.GetComponent<Image>().DoColor(new

Color(255,255,255,0),0.5f);

}

为了方便各种情况的调用,脚本中设计了多种情形下打开和关闭脚本的方式,但总归都是通过数组和集合再利用循环进行面板的切换。在PlayerHUDCanvas中才是具体的控制面板的参数改变,主要为血量的变化、通知信息的刷新、背包面板信息的改变给控制器脚本和玩家控制脚本实现了外界的接口。根据图4.2-1的Inspector属性栏可以清楚的看到引用的物体信息。public

PanelInstance[]

Pages;

public

PanelInstance

currentPanel;

//

关闭所有脚本

public

void

CloseAllPanels

()

{

if

(Pages.Length

<=

0)

return;

for

(int

i=0;

i<Pages.Length;

i++)

{

Pages[i].OpenPanel

(false);

if(Pages

[i].isActiveAndEnabled){

StartCoroutine

(DisablePanelDeleyed

(Pages

[i]));

}

}

}

//根据名字打开相应的脚本

public

void

OpenPanelByName

(string

name)

{

PanelInstance

page

=

null;

for

(int

i=0;

i<Pages.Length;

i++)

{

if

(Pages

[i].name

==

name)

{

page

=

Pages

[i];

break;

}

}

if

(page

==

null)

return;

page.PanelBefore

=

currentPanel;

currentPanel

=

page;

CloseAllPanels

();

page.OpenPanel

(true);

}

UI的框架分为PanelInstance->PanelsManager(个人常用的ui框架)->PlayerHUDCanvas作为一个主题,同时将单例各面板分派给独立的脚本控制。例如游戏结束界面的数据显示以及击杀信息显示而PlayerHUDCanvas在游戏初始化时就进行实例,绑定到游戏总控制器FgameManager中集中控制,在需要执行ui事件时只需要调用FgameManager即可

这样的调用方式调用入口统一,同时也减少了所有的代码聚集在一个脚本中防止后期调试和修复bug的困难。

第四章游戏功能设计以及实现4.1游戏基本规则及功能设计本次开发的游戏,玩家通过鼠标和键盘进行角色的操控。在游玩的过程中,玩家可以进行人称视角的切换、通场景的物体进行交互、行走跳跃、同时还可以改变进行的状态,可以走跑蹲趴,令玩拥有丰富的进攻方式和隐蔽自己的方式。在游戏的一开始玩家会在出生岛等待,继而会到飞机上跳伞到达军事演练地点,通过搜集散落在场景中的军事物资,通过灵活多变的方式淘汰对手,在过程中,会逐渐缩减安全范围,当最后一个人存活时,游戏方能结束。每当一定的时间间隔,都会生成具有肥沃物资的空投予于玩家拾取REF_Ref27111\w[3]REF_Ref27118\w[2]。总结规则后可以将游戏的主要功能分为三部分:实现玩家对游戏角色的控制、在玩家系统的基础上添加敌人AI、实现控制整个游戏流程的总控制器。如下图4-1所示。图4-1游戏框架图4.2玩家系统4.2.1玩家的输入图4-2玩家输入按键示意图如上图所示,PlayerInputController脚本负责接收玩家所有的输入值,并且调用相应的函数去实现对应输入的功能,这种归纳到一个脚本控制的做法对于后期的调试有着极大的帮助。如下图所示,数字1-5代表着对应类型武器的切换,而WSAD键用以控制玩家角色的移动,Tab键控制背包的开关,C键用于改变玩家的行走姿态,V键用以控制玩家视野的改变,M键控制大地图的打开,而F键控制的则是与场景中各种物体的交互,譬如打开门、拾取物体、离开飞机等事件。而鼠标左键控制玩家进行攻击事件,右键打开瞄准功能(需武器本身拥有此功能接口),同时鼠标的移动控制视角的旋转。代码中可以通过Input.Get..系列脚本可以监听相应的按键事件,GetAxis()以及GetButton()函数对应的键值可以在编辑器Edit->ProjectSetting->Input中修改相应的监听键值,而GetKeyDown(KeyCode)可以直接监听传入的键是否被按下。4.2.2玩家的生成无论是玩家、敌人亦或是随机刷新的军事物资生成的原理都是相同的,获取已经配置好的预设体,然后使用Instantiate()传入需要生成的物体、位置以及角度即可生成。而为了保持相对的随机,我们选择将物体的生成控制在一个范围内随机获取生成位置。#if

UNITY_EDITOR

using

UnityEditor;

#endif

void

OnDrawGizmos()

{

#if

UNITY_EDITOR

Gizmos.color

=

Color.Green;

Gizmos.DrawSphere(transform.position,

2);

Gizmos.DrawWireCube(transform.position,

this.transform.localScale);

Handles.Label(transform.position,

"玩家生成区域");

#endif

}

为了方便观察区域的位置和移动,可以引入UnityEditor,调用OnDrawGizmos()并编写Gizmos.DrawWireCube()即可在编辑器Scene视图绘制出相应范围的正方体范围。(此处使用预处理器命令判断防止项目导出时出现错误)如下图4-3所示,绿、红、蓝分别表示玩家、敌人以及物资的生成区域。图4-3角色生成区域public

Vector3

SpawnPoint

()

{

return

CheckGroundHeight(this.transform.position

+new

Vector3(

Random.Range

(-(int)(this.transform.localScale.x

/

2.0f),(int)(this.transform.localScale.x

/

2.0f)),

0,

Random.Range

(-(int)(this.transform.localScale.z

/

2.0f),

(int)(this.transform.localScale.z

/

2.0f)))

);}

//检测脚下的地面高度

Vector3

CheckGroundHeight(Vector3

srcPos)

{

RaycastHit

hit;

if

(Physics.Raycast

(position,

-Vector3.up,

out

hit,

1000.0f))

{

return

hit.point

+

Offset;

}

return

srcPos;

}

配置完之后,在需要生成物体时,使用Random.Range()返回x/z轴平面一个随机的点,由于Unity默认的游戏对象的localScale为1,而当我们在设置区域大小的时候正是修改的此值,因此这里localScale实际上就等于一个长度的获取。同时为了物体能正确的显示在地面(由于山体坡度的不统一需要实时获取),需要获取一个正确的高度点,将获得的随机点向下发射一次射线,当检测到碰撞信息再加上一个向上的单位向量Vector3.Up即可保证该高度恰巧处于地面之上。首先需要定义一个RaycastHit射线,然后调用Physics.Raycast()初入发射起点和方向以及长度,即可将射线碰撞的信息存入Hit中,而Hit.Point就是射线检测到的地面坐标点。此时只需要调用Instantiate()传入物体、位置。角度即可生成相应的物体。Instantiate(gameObject,SpawnPoint(PlayerSpawner.tranform,position),Quaternion.identity)

;

4.2.3动画系统在第三人称游戏中需要十分多的动画过度,在Unity中过度动画需要先对动画机进行配置,如图,首先将所有的人物动画放置到动画机中,并按照相应的动画转化条件将动画连接起来并设置相应的变量值,然后在Conditions设置触发动画间的触发条件,如下图4-4所示。public

override

void

PlayAttackAnimation(bool

attacking,

int

attacktype)

{

if

(animator)

{

if

(attacking)

{

animator.SetTrigger("Shoot");

}

animator.SetInteger("Shoot_Type",

attacktype);

}

base.PlayAttackAnimation(attacking,

attacktype);

}图4-4动画机系统配置接下来只需要在代码中获取动画机Animator,并且相应的变量类型调用设置函数,传入对应的变量名和数值即可对动画机的状态进行改变。如上述代码则是确认存在动画机的情况下,设置Trigger类型的变量Shoot为触发状态,设置Shoot_Typed的int变量为对应的状态。4.2.4场景交互的实现游戏中需要许多需要交互的东西,如图所示,当玩家屏幕对准物体时,会显示物体的相关信息,其他处于可交互状态的情况下也会出现可交互的示意文字。这意味着在游戏中会按照一定频率对屏幕中心物体进行检测,当为可交互的物体时而对玩家进行反馈,而按下交互键使则执行对应的操作REF_Ref27193\w[7]。public

void

Yzw_Checking()

{

float

raySize

=

2.5f;

Vector3

origin

=

PlayerView.FPSCamera.RayPointer.position;

Vector3

direction

=PlayerView.FPSCamera.RayPointer.forward;

RaycastHit[]

casterhits

=

Physics.RaycastAll(origin,direction

,

raySize);

for

(int

i

=

0;

i

<

casterhits.Length;

i++)

{

if

(casterhits[i].collider)

{

RaycastHit

hit

=

casterhits[i];

hit.collider.SendMessage("GetInfo",

SendMessageOptions.DontRequireReceiver);

}

}

}

这里又再一次运用到了射线,调用的方式相同,此时的射线发射点为相机正中心的位置,而方向也为相机的正前方,即玩家的屏幕中心。在Update()中不断刷新的射线一旦检测到有可交互的物体,就会通过获取的碰撞体发送调用GetInfo()函数的信息,这里发送信息的方法为SendMessage(),首先需要传入调用的函数,后面为设置接受者是否需要为监听状态,而SendMessageOptions.DontRequireReceiver表示不需要为接受者状态。(如图4-5)public

override

void

GetInfo()

{

YZW.Hud.ShowInfo("按下“F”开门",

this.transform.position);

}

public

void

ShowInfo(string

info,

Vector3

position)

{

if

(Info

!=

null)

{

isShowinfo

=

true;

Info.gameObject.SetActive(true);

RectTransform

inforec

=

Info.GetComponent<RectTransform>();

inforec.anchoredPosition

=

GetWorldSpaceUIposition(position);

Info.text

=

info;

timeInfo

=

Time.time;

}

}

图4-5场景交互以门的反馈信息为例,如上图4-5所示,当可交互物体被检测到时反馈本身的信息到玩家的交互界面控制器HudManager中,而HudManager中的ShowInfo()会将场景中的提示文本的游戏对象打开,在通过GetWorldSpaceUIposition()将物体的世界坐标转化为屏幕坐标以确保文本在物体中心位置,最后在替换文本。同理,当玩家按下交互键时可以同样发射一条射线确认当前的物体可交互,并且请求调用该物体脚本中的交互函数,则可以进行玩家角色和场景的交互。4.2.5背包系统人物需要和场景内的物体交互,主要行为是将场景内的演习资源放入背包中,将物品丢弃,武器装弹消耗背包内物体。由于只有当玩家距离交互物体到一定距离并且对准物体才能进行交互,所以需要一直检测玩家是否检测到了交互物体,通过发射射线对当前视角中心的物体进行检测,当玩家有物体时,将物体信息传入总控制器中的ui部分并且显示出来提示玩家可以进行交互。当玩家按下交互键时,则发送信息,如果是物体的话则将物体存入背包中。当存入物品过多超过了背包的容量想要丢弃部分不需要的物资时,只需点击UI面板中背包面板中想要丢弃的物品即可丢出。当武器进行换弹时,会读取当前枪支的最大容量和当前容量计算出需要消耗的子弹数量进行填充。(如图4-6)图4-6背包系统在游戏中搜集物资需要对物体进行存储、丢弃以及使用。而物品的存储需要将物品以及数量一一对标,同时由于物体的特性不同,需要定义类型来区分。public

void

AddSmToBag(ItemData

sm,int

count)

{

for

(int

i

=

0;

i

<

ItemsList.Length;

i++)

{

if

(sm.ItemID

==

ItemsList[i].ItemID)

{

ItemsList[i].index+=count;

}

}

ItemsList.Add(sm);

}

由于物品的特性不同,通过枚举将所有的类型列举出来。每个物体具备多个特性,所以这里将物体的各项属性编写成一个ItemCollector类,方便我们将其存入列表中。public

class

ItemCollector

{

public

ItemData

Item;

public

int

count;

public

int

NumTag

=

-1;

public

bool

isOpen;

public

int

Shortcut

=

-1;

public

int

EquipIndex

=

-1;

public

int

EquipMod

=

-1;

public

bool

InUse

=

false;

public

int

InventoryIndex;

public

int

ItemIndex;

}

public

void

LoadSmData(ItemData

sm,int

count)

{

for

(int

o

=

0;

o

<

ItemsList.Length;

o++)

{

if

(sm.ItemID

==

ItemsList[o].ItemID)

{

ItemsList[o].index+=count;

}

}

ItemsList.Add(sm);

}

在对物体进行存储时,通过For循环确认列表是否以及有相同物体,如果列表中已经存在该物体,则直接将加入的物体数量叠加到列表中物体,而如若没有该物体,则添加该物体到列表中。丢弃物品时则相反,由于丢弃的数量不一定为全部,所以需要对物体的数量进行判断,如果丢弃后数量为0时则移除该物体。4.2.6音效控制在Unity中使用音频需要用到AudioSource和AudioClip两个组件,AudioSource类似于一个播放器,而AudioClip则是用来挂载音源。通过调用AudioSource的PlayOneShot()方法即可播放音源,与Play()方法不同,playOneShot就是为了解决播放多种和多次音效的问度题而生的(个人观点),这函数不管你目前有没有正在播放音效,它都会另起炉灶播放指定的音效,并且不会打断当前正在播放的音效。if

(delay

>=

Delay)

{

audios.PlayOneShot(FoodSteps[Random.Range(0,

FoodSteps.Length)]);

delay

=

0;

}

if

(delay

<

Delay)

{

//根据移动速度改变音轨时间

delay

+=

character.MoveVelocity.magnitude

*

Time.deltaTime;

}

通过获取当前角色的移动速度来控制音频的播放速率,速度越快,则音频结束的速度越快,为了脚步看起来自然,这里我导入了三个音频随机播放,如下图4-7所示。图4-7音频控制4.2.7人物的基本移动移动和跳跃可以说是3d游戏中最基础的移动,在Unity中使用物理系统进行最基础的移动方式主要通过给予玩家物体刚体组件,通过施加力来控制玩家的移动和跳跃,而前文我们提到的加速则是通过增大施加力的数值达到更快的速度。角色控制器是官方基于刚体设计的专门用于角色的基本控制的组件,如图4-8所示,具有角色移动主要的几项属性,本次我们在此组件的基础上编写玩家的移动和跳跃时间。图4-8角色控制器组件角色控制器中有编写好的移动函数,只需要传入移动的方向和数值即可进行移动。跳跃的本质是向上人体对地面施加一个力,反作用力将人体推向天空,而在地心引力的作用下人体速度逐渐衰减然后下降到地面,因此我们需要创建一个模拟的重力场,并给予物体一个向上的加速度,并实时计算在重力的情况下速度的衰减变化,通过isGrounded检测是否回到地面来控制跳跃状态的开关。

public

CharacterController

_myBody;

public

float

_runSpeed=

1;

public

bool

_isJump

=

false;

public

float

_jumpSpped

=

3;

public

float

_gravity

=

1;

public

float

_Vspeed

=

0;

void

Update

()

{

_myBody.Move(new

Vector3(Input.GetAxis("Vertical"),_Vspeed,Input.GetAxis("Horizontal"))*_runSpeed);

if

(Input.GetButton("Jump")

&&

!_isJump)

{

_isJump

=

true;

_Vspeed

=

_jumpSpped;

}

if

(_isJump)

{

_Vspeed

-=

_gravity

*

Time.deltaTime;

//人物碰撞地面了

if

(_myBody.isGrounded)

_isJump

=

false;

}

}

4.2.8人物战斗系统人物从拾取到枪支后进行攻击时主要是沿着摄像头的正前方创建一个子弹物体,当物体生成后机会开始沿着子弹的正前方不断的刷新自身的位置。public

void

Update(){

this.transform.position

+=

this.transform.forward

*

Speed

*

Time.deltaTime;

}

当子弹命中敌人或地面以及到达销毁时间时就会删除该物体,这里直接用Destory函数销毁物体,第二个参数代表延迟的时间。public

void

Start(){

GameObject.Destroy(this.gameObject,

lifeTime);

}

当玩家进行射击时,为了还原枪械后坐力的效果,这里采用了Dotween插件中的Punching函数对相机的角度进行上移,该函数可以实现从角度到设定角度的移动并且回弹,这个效果十分迫近现实的后坐力,同时Dotween优秀的插值计算使得我们动态修改摄像机的位置与角度也不会影响到回弹操作的进行,代码如下。transform.DOPunchRotation(new

Vector3(-1,

0,

0),0.3f,

2,

0.1f);

//传入的参数分别表示:移动方向(向上)、动画时间、震动次数。最后参数不为0时,会把你赋的值乘上一个参数,作为你运动方向反方向的点,物体在这个点和目标点之间运动

而玩家受到攻击时,会调用函数对自身的血量进行修改,并且反馈到屏幕。主要的流程和前文一样,都是通过调用交互界面管理器进行反馈。(如图4-9)图4-9受击效果4.3敌人AIAI敌人需要模拟真实玩家的效果,由于敌人和玩家之间的代码可以大量的复用,所以只需要对敌人的状态能够起到根据情况能自主的进行转换即可,根据功能的需求,机器人具备的状态有,在原地站立、寻找敌人的状态、移动到安全区的状态。促使敌人行为状态的改变元素有内外之分,外因为发现了敌人(玩家或其他AI)时则会进入战斗状态或者安全区缩小则AI会往安全区前进,内在为设置的刷新时间到了,随机获取新的状态REF_Ref27193\w[7]REF_Ref27271\w[8]。首先先设定AI的发现距离和获取武器攻击距离,三个状态直接会互相转换,譬如当敌人处于原地待机状态时,发现了敌人或者定期刷新状态的时间到了,那么敌人就会转为攻击或巡逻状态,而当机器人处于无战斗状态时,安全区缩小时机器人会往安全区的方向撤离。当敌人处于战斗状态时,当敌人消失在视野中或者逃出了追击距离,那么丧失目标的机器人将会切换状态进入漫游或待机状态,如下图4-10所示。图4-10敌人行为逻辑在现如今游戏绝地求生中,机器人会进入房子中搜集物资,看起来和旁人无疑,但早期的机器人是自动生成生存物资的,为了降低游戏的复杂度,这里我将沿用其早期版本的用法。在机器人诞生之后一段时间后(给玩家收集物资的时间),则开始进行对机器人物资的生成,包括防具以及武器,而这里敌人的子弹是无限的,因此敌人不需要重复再生成物资。TimeWait

=

ItemSets[i].NextTime

+

Random.Range(0,

ItemSets[i].NextTime

/

2)

if

(ItemSets[i].RandomEquipOne)

{

//随机装备一些物体

character.inventory.AddItemByItemData(ItemSets[i].Items[Random.Range(0,

ItemSets[i].Items.Length)],

1,

-1,

-1);

character.inventory.UpdateInventoryToAll();

}

else

{

for

(int

v

=

0;

v

<

ItemSets[i].Items.Length;

v++)

{

character.inventory.AddItemByItemData(ItemSets[i].Items[v],

1,

-1,

-1);

}

character.inventory.UpdateInventoryToAll();

}

}4.4游戏总控实现除开玩家和敌人,最为重要的就是整个游戏的自控系统。整个系统负责了游戏的进程,以及除开人物的所有的元素控制。譬如游戏玩家进入飞机、空投的丢落、安全区的缩进,游戏的胜负条件以及我们之前所说的机器人生成。4.4.1切换场景将战斗场景和开始场景分割开,可以防止游戏一开始加载的资源过多导致的卡顿。首先需要将所有的场景添加到BuildingSetting中,不然在跳转的时候会因找不到索引导致出错。UnityEngine.SceneManagement.SceneManager.LoadScene("battleground");

void

OnEnable()

{

UnityEngine.SceneManagement.SceneManager.sceneLoaded

+=

OnLevelFinishedLoading;

}

void

Awake

()

{

DontDestroyOnLoad

(this.gameObject);

}

上述代码调用SceneManager.LoadScene(),将目标场景的名字传入函数中调用即可切换场景,此脚本一般还需要在Awake函数中声明加载新场景时不销毁,可以避免多个场景重复挂载,同时还可以通过对SceneManager.sceneLoad事件添加委托,即可在每次加载新场景的时候刷新设置为不销毁物体的某种属性。4.4.2生成物体场景控制器也负责各角色和物资的生成,生成敌人的方法类型,但为了保证敌人的随机性,敌人的生成区域有多个,因此生成时需要获取场景中所有的敌人区域随机生成。public

void

SpawnAI()

{

EnemySpawner

spawner

=

(EnemySpawner)GameObject.FindObjectOfType<EnemySpawner>();

if

(spawner)

{

spawner.MaxObject

=

YZW.gameManager.BotNumber;

AIs

=

spawner.Spawn(YZW.gameManager.BotNumber);

for

(int

i

=

0;

i

<

AIs.Length;

i++)

{

if

(AIs[i]

!=

null)

{

AICharacterShooterNav

ai

=

AIs[i].GetComponent<AICharacterShooterNav>();

if

(ai

!=

null)

{

ai.Fighting

=

false;

}

}

}

}

}

以上代码通过FindObjectOfType获取所有拥有EnemySpawner的游戏物体,并且根据之前设置的机器人数量BotNumber,生成对应数量的敌人。在生成结束后将敌人的战斗状态打开,开始智能搜集物资的状态,物资的生成同理,参考有关玩家的生成。还有一种比较特殊的物体为飞机。用到飞机的场景是按时刷新的空投,由于第一次空降不需要空投所以设置了一个布尔进行区分。

public

void

SpawnPlane(bool

drop,

Vector3

passingTo)

{

AirplaneSpawner

spawner

=

GameObject.FindObjectOfType<AirplaneSpawner>();

if

(spawner

!=

null)

{

Vector2

pos

=

Random.insideUnitCircle;

pos.Normalize();

pos

*=

spawner.transform.localScale.x;

pos

=

this.transform.position

+

new

Vector3(pos.x,

0,

pos.y)

+

Vector3.up;

GameObject

planeobj

=

Instantiate(AirplaneObject,

pos,

Quaternion.identity);

if

(planeobj

!=

null)

{

Debug.Log("生成飞机");

planeobj.transform.LookAt(passingTo

+

(Vector3.up

*

planeobj.transform.position.y));

Airplane

plane

=

planeobj.GetComponent<Airplane>();

if

(plane

!=

null)

{

plane.DropSupply

=

drop;

plane.SetDrop(passingTo);

}

}

}

}这里类似于生成敌人,也是获取场景中的飞机生成点的游戏对象,然后通过SpawnPoint()方法Random.insideUnitCircle返回一个单位圆的随机向量,将向量标准化后乘于对应的区域范围即可获取一个单元原边缘的一个随机点。在生成飞机物体后,根据生成的情况设置是否需要进行空投的投放设置。4.4.3安全区控制 if

(DeadAreas.Length

>

currentArea)

{

LerpValue

=

0;

DeadArea

NextArea

=

DeadAreas[currentArea];

currentAreaDamage

=

NextArea.Damage;

Radius

=

NextArea.Radius;

if

(Time.time

>=

timeAreaTmp

+

NextArea.Time)

{

if

(!isEscape)

{

timeEscapeTmp

=

Time.time;

isEscape

=

true;

}

float

timing

=

1

-

(((NextArea.EscapeTime

+

timeEscapeTmp)

-

Time.time)

/

NextArea.EscapeTime);

if

(currentArea

>

0)

LerpValue

=

timing;

if

(Time.time

>

NextArea.EscapeTime

+

timeEscapeTmp)

{

if

(lastDeadArea)

{

positionTmp

=

CentreArea;

scaleTmp

=

new

Vector3(Radius,

SafeArea.transform.localScale.y,

Radius)

*

2;

}

if

(currentArea

>=

DeadAreas.Length

-

1)

return;

currentArea

+=

1;

isEscape

=

false;

timeAreaTmp

=

Time.time;

CentreArea

=

GetArea(CentreArea,

Radius

-

(DeadAreas[currentArea].Radius));

CmdStartNewArea(positionTmp,

scaleTmp,

currentArea);

if

(DeadAreas[currentArea].AirDrop)

SpawnPlane(true,

CentreArea);

}

}

else

{

float

timeLeft

=

(timeAreaTmp

+

NextArea.Time)

-

Time.time;

if

(timeLeft

<

60)

{

if

(Time.time

>=

broadcastTimpTmp

+

10)

{

{

Broadcast("距离安全区缩小还有

"

+

Mathf.Ceil(timeLeft)

+

"

秒",

3);

broadcastTimpTmp

=

Time.time;

}

}

}

}

damageUpdate();

}

if

(safeArea

&&

lastDeadArea)

{

safeArea.transform.position

=

CentreArea;

safeArea.transform.localScale

=

new

Vector3(Radius,

SafeArea.transform.localScale.y,

Radius)

*

2;

lastDeadArea.transform.position

=

Vector3.Lerp(positionTmp,

safeArea.transform.position,

LerpValue);

lastDeadArea.transform.localScale

=

Vector3.Lerp(scaleTmp,

safeArea.transform.localScale,

LerpValue);

}

安全区的缩小实际上是通过NextArea.Time(下个圈到达时间)结合timeAreaTmp(当前区域时间)和Time.time(实际时间)已经倒计时的判断的,而每次时间达到之后会刷新所有有关该圈的数值,如下图所示,当前毒圈的伤害、下一个圈的距离、毒圈的缩小范围。同时在即将到达缩圈时间前,会通过调用玩家的用户交互界面提示圈即将缩小的事件。

第五章屏幕后效5.1选中效果由于游戏场景中物体繁多,有可能会出现部分叠加在一起的情况,为了能够更加直观明确的看出是否选中了当前物体,加入一个物体描边的效果提示。物体选中以轮廓提示,一般是通过修改材质来实现,但是由于物体过多,逐个修改材质未免有些繁琐,所以这里选择用屏幕后处理的方式实现。这种做法需要一个额外的摄像机专门用来渲染轮廓,同时需要设定对象为对应的Layer,在该层级的问题进入描边摄像机时,即会对物体进行处理,其本质是对物体进行模糊处理(两次模糊、横向纵向以此进行高斯模糊),然后通过原始图以及模糊图计算出轮廓图,最终混合采样轮廓图和原始图进行叠加再次输出到视图中。

//高斯模糊,两次模糊,横向纵向,使用pass0进行高斯模糊

_mat.SetVector("_offsets",

new

Vector4(0,

samplerScale,

0,

0));

Graphics.Blit(renderTexture,

temp1,

_mat,

0);

_mat.SetVector("_offsets",

new

Vector4(samplerScale,

0,

0,

0));

Graphics.Blit(temp1,

temp2,

_mat,

0);

//如果有叠加再进行迭代模糊处理

for(int

i

=

0;

i

<

iteration;

i++)

{

_mat.SetVector("_offsets",

new

Vect

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论