版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
本科毕业设计(论文)基于Unity3D的狩猎模拟器的设计与实现DesignanddevelopmentoftankcombatsurvivalgamebasedonUnity3D院(系)计算机学院专业软件工程班级7班学号16210120727学生姓名杨天天指导教师王千秋提交日期
内容摘要因为国内能够打猎的狩猎基地大多不对外开放,再加上国家对于野生动物的保护,人们极少能够见到野生的动物,更别说与野生动物互动和打猎了。因此,为了人们能够体会到在远离人群的森林中,体验真实的狩猎快感和体会自然森林的原始韵味,特此制作了这款模拟类型的游戏《狩猎模拟器》。模拟类型的游戏重点在于现实的还原,但是一味照搬现实是枯燥无味的,所以在玩法上应该以玩家能够更容易体验到狩猎的乐趣为主。以狩猎为核心的游戏目前出现的也不在少数,比如《荒野的召唤》可以说是这个类型游戏的标杆,但是即使这样的大作也不可能让所有玩家喜欢,其中更是有些许的不足之处,其中引用b站一部分玩家的评论“这个游戏过于枯燥“、”大部分时间都在走路“和”看风景模拟器“,这些是部分玩家的观点,我认为出现这种重心从狩猎偏移到看风景的情况,不是因为说这个游戏的狩猎玩法很硬核,而是这个游戏节奏太慢,不适合现在国内部分喜欢快节奏游戏人群的玩家,当我自己去体验了这个游戏之后感觉也确实如此。所以我开发的这个狩猎游戏将尝试解决这个问题:在尽量保留狩猎的真实性的情况下,将一个以狩猎为核心的游戏从跑图看风景中解放出来,回归到畅快淋漓的狩猎之中。
AbstractBecausemosthuntingbasesthatcanbehuntedinChinaarenotopentotheoutsideworld,coupledwiththecountry'sprotectionofwildanimals,peoplerarelyseewildanimals,letaloneinteractwithandhuntwildanimals.Therefore,inordertoallowpeopletoexperiencetherealhuntingpleasureintheforestfarfromthecrowdandtheoriginalcharmofthenaturalforest,thissimulation-typegame"HuntingSimulator"wasproduced.Simulation-typegamesfocusonclosenessandreality,butcopyingrealityblindlyisdull,sothegameplayshouldbebasedontheplayer'sabilitytoexperiencethefunofhuntingmoreeasily.Hunting-basedgamesarenotuncommonatpresent,suchas"CalloftheWild"canbesaidtobethebenchmarkforthistypeofgame,butevensuchamasterpieceisnotlikelytobelikedbyallplayers,amongwhichtherearesomeshortcomings,Whichquotesthecommentsofsomeplayersinstationb,"Thisgameistooboring","Ispendmostofmytimewalking,"and"seeingthescenerysimulator."Thesearesomeoftheplayers’views.IthinkthisfocusshiftsfromhuntingtoLookingatthescenery,itisnotbecausethehuntinggameplayofthisgameisveryhard-core,butbecausethepaceofthegameistooslow,anditisnotsuitableforsomedomesticplayerswholikefast-pacedgamers.ItfeelstruewhenIexperiencethegameinthisway.SothishuntinggameIdevelopedwilltrytosolvethisproblem:whileretainingtheauthenticityofhuntingasmuchaspossible,liberateagamewithhuntingasthecorefromrunningpicturestoseethelandscape,andreturntothesmoothandvigoroushunting.
本科毕业设计(论文) 绪论选题的目的和意义前几年最火的游戏类型无疑是竞技类型的游戏,比如《英雄联盟》和《王者荣耀》等,但是近两年玩家的喜好又偏向模拟真实类型的游戏,比如《模拟飞行》、《模拟修仙》等,其中有大厂也有新人的制作,未来模拟类型的游戏可能成为比较火的游戏类型。模拟类型的游戏非常注重玩家的体验,这是该类型游戏的核心,不管模拟什么,最终的目的就是要玩家感受到该游戏所模拟的东西。游戏中设计模拟的东西可以是生活中十分平常的东西也可以是普通人触及不到又想体验的东西,更可以是设计者幻想架空的梦幻之物,但是最终一定要玩家体验到这个游戏所模拟的东西,并真正融入进去。《狩猎模拟器》这个游戏就和名字一样,这是一款模拟狩猎的游戏,狩猎这个词对于任何人都不陌生,但是狩猎在中国几乎是不可能的事情,偷猎更是会将牢低坐穿。因为狩猎这个早在几十万年前就作为我们祖先获得食物的方式,后面的封建时代更是发展为娱乐,可以说这是刻在我们骨子里的行为,所以大部分人应该对狩猎有一种热衷。为了能让这部分人可以体验到原汁原味到丛林狩猎而不会犯法,这款模拟狩猎的游戏就此诞生。模拟类游戏的诞生和发展模拟器类游戏是诞生自一些军用的模拟器,例如防空3D模拟器等,也正因为这一特性,最早的模拟器类游戏就是以军事为题材的,据说第一款模拟器类游戏内容非常简单,模拟发射一枚导弹来命中目标,玩家可以通过旋钮来控制导弹的速度和弧度。大家公认的“世界上第一款电子游戏”双人网球就是世界所知的第二款模拟器类游戏,两个玩家通过游戏手柄控制游戏,这其实就是一个模拟网球游戏,但值得可喜的是示波器上能显示游戏画面了。到了1970和80年代,计算机图像技术虽然逐渐成熟,但是游戏作品少有模拟器类的拟真元素存在。到了80和90年代,随着计算机科学的不断发展,慢慢的带有模拟类的游戏就开始泛行了,其中模拟人生、模拟城市和命令与征服系列,直到今天仍然受到众多玩家的喜爱。由于受到计算机的限制,那个时候的人们都是用在科研等领域,因为那时候计算机的计算能力还不足以支撑实时的模拟验算在上个世纪计算机模拟技术主要用于科学、电影特效等领域。这其中就包含了一些古老的模拟游戏,在经过一番画面升级和游戏内容丰富而焕然一新,模拟城市游戏里甚至每一个市民都变得有血有肉,而不再仅仅是游戏中的一个数字。在2012年前后全球范围内曾掀起了一阵模拟类游戏热潮,除了模拟城市、都市天际线等一系列的城建模拟游戏,还有一些模拟驾驶游戏,能让玩家体验到日常生活中很难体验到的驾驶体验。随着计算机技术的发展,模拟这一元素几乎成为了每一款游戏中不可缺少的部分,模拟元素在游戏中的存在让玩家有了更好的沉浸感。有一句话就叫做学习知识最好的方法就是实践,游戏就提供了一个很好的平台,随着Unity3D、UE等游戏引擎的出现,制作游戏难度日益降低,有越来越多的人出于个人兴趣,去尝试着做一款属于独属于自己的游戏,有些玩家能在游戏中进行实践和学习;有的在各种千奇百怪的模拟器类游戏,甚至还有什么做饭模拟器等,享受更多的是一种娱乐;有的则可以在游戏中体验不一样的人生,更多是实现了在现实生活中实现不了的东西!论文组织结构游戏以学校所教的unity3d引擎为基础,利用自己和老师教的知识进行游戏开发。论文记录游戏构思、设计和制作的过程,并且完美展示自己的所见所学,证明自己的独到观点。第一部分:绪论,主要介绍设计该游戏的背景和目的,并且讨论模拟类游戏的过去,从而希望别人理解为什么会设计这款游戏第二部分:介绍开发这款游戏所做的准备、所需要的开发环境和开发工具第三部分:详细介绍游戏各个模块的设计和实现,根据设计这款游戏程序的模式,将这部分的说明分成不同小块来进行说明第四部分:游戏界面设计、声效设计第五部分:白盒测试、黑盒测试,打包后运行测试第六部分:从新论述一遍自己原先设计该游戏的目的和背景,回应开题
开发工具Unity3D引擎关于Unity3D软件功能的介绍Unity3D是一款3D跨平台次时代游戏引擎。它提供了非常完美的跨平台系统。平台之间的差别常常会影响到产品的开发速度和进度,因此大部分开发者们要在这上面花费大量的时间,而Unity3D则能够在近10种主流平台之间完美移植。同时Unity3D也提供了一个交互良好的操作界面,能够完美地在Windows和MacOS下部署。该软件自带的五个工程视图框架能够很好地帮助设计者分类游戏中的对象及观察制作游戏的进程。其中包括project视图,该视图主要存放资源文件,hierarchy视图主要放置场景中具体的游戏对象,inspector视图主要用来描述游戏资源的信息,scene视图主要存放游戏中模型资源,game视图是用来观察已经完成的游戏的运行成果。Unity3D还为我们提供了多种脚本语言编译的常用环境,用户可以根据需求将默认的编译环境做出调整。Unity3D支持C#、javascript、boo三种脚本语言。API接口Unity3D丰富的API能够帮你完成各种想要完成的需求。API是一些先定义好的函数,为开发人员基于某软件或硬件无需访问源码就能提供访问一组例程的能力,亦或理解内部工作机制的细节。Unity3D就有一套完整的API函数库,通过这些API函数,开发人员可以很方便地实现项目的初始化,功能模块的每帧调用,如何进行触发检测并进行触发事件的响应,如何判断是否进入触发区,是否在触发区,是否离开触发区,如何实现拖动事件的响应等。物理引擎任何一个游戏的设计都需要其物体特性的合理性,这样才显得真实可信。U3D为开发人员提供了大量组件,可以对对象进行渲染,颜色的诱明度,平行光,点光源,法线贴图,图片,文字,动画,声音,材质等一系列组件可以带给我们很逼真的视觉效果,而在物理特性上添加的刚体,碰撞器等组件,可以实现使对象受到重力,摩擦力,空气阻力等自然物理特性的影响,而爬坡的坡度设定,碰撞后的一切物理变化,逼真的体验让你觉得这就是一个现实中的物体。Unity3D脚本生命周期Unity3D游戏引擎不像常规的程序直接在Main函数入口运行,而是在内部实现了自己的生命周期事件。通过对这些生命周期的事件进行写入,Unity内部就会不断地迭代这些生命周期函数。下面按照脚本的执行顺序介绍游戏中比较常用的Unity事件函数。Awake():当游戏对象被初始化的时候调用,无论该对象是否已被激活。Start():在Awake事件后调用,但只有被激活的时候才能够执行。Update():游戏中的帧事件,因为游戏大部分都是按帧率来执行逻辑的。FixedUpdate():游戏的固定帧事件,基本同Update(),但该事件可由开发者去控制执行频率。LateUpdate():这个函数是在Update()函数在每帧执行结束后才会执行的函数,和Update类似,每帧都会执行一次,一般用来处理跟随逻辑,比如OnGUI():一般写UI逻辑,不过NGUI出来后一般不使用,因为优化很差OnEnable():在Start事件后调用,只有被激活的时候才会执行。OnDisable():当游戏对象被禁止激活的时候会调用。OnDestroy():当游戏对象被销毁的时候执行。Unity3D的脚本基本上都会继承自于MonoBehavior基类。一般不继承于MonoBehavior的类会用来写一些工具类。通常来说,它在整个程序运行的过程中都是存在的,除非自己手动GC释放内存。图形用户界面本文主要使用NGUI和UGUI进行UI界面的开发,GUI是图形用户界面的英文简写。Unity的GUI系统被称作UnityGUI,UnityGUI能使你非常快捷简便的添加功能齐备且种类繁多的界面元素,通过在GUI控件的创建操作中同时包括实例化,定位和功能定义。使你只需要一次性写出很少量的代码就能同时完成创建一个GUI界面元素实例,并定位实例在屏幕的位置和描述界面元素被激活时所要执行的脚本三种工作。UGUI是Unity内置的,NGUI则是一款为Unity游戏引擎开发的工具功能扩展的UI插件,它也能够为开发者提供方便快捷的UI设计方法,加快设计游戏的速度。BehaviorDesignerBehaviorDesigner提供了强大的API,可以让你根据自己游戏内部的AI需求编写自己的AI脚本,而且还提供了一个直观的可视化编辑器,该可视化编辑器具有广泛的第三方集成,可以创建复杂的AI,而无需编写任何代码。AssetStoreUnityTechnologies和其他社区成员会不断地创作免费的和商用的资源,而Unity资源商店则是这些资源收录的宝库。里面有各种各样的资源可供使用,包含了从纹理,模型和动画到整个示例项目,教程和编辑器插件等内容。在Unity编辑器的内置接口中可以访问到这些资源,它们可供下载并直接导入到你的项目中去。这里我们游戏中大部分使用的模型都会从资源商店中下载,而不是使用建模软件自己动手做,那样花费大量的时间。C#语言C#是微软公司发布的一种运行于.NETFramework之上,面向对象的高级程序设计语言。C#与Java类似,是一种语言,一种工具。但这里需要知道Unity是跨平台的,而C#并不是一种跨平台的语言。只是由于Mono的的重新实现,使得Unity能够使用C#来开发,因此,我们可以在Unity上写C#的代码。语法明了,类库使用方便,是C#的优点,也是我们在Unity开发中首先考虑的语言。VisualStudio2017VisualStudio2017是微软公司推出的开发环境。它支持最新的集成环境开发。VisualStudio也带来了NETFramework4.6、MicrosoftVisualStudio2017CTP,同时也支持开发者开发Windows的应用程序。并且支持SQLSever,IBM,Oracle数据库等。
游戏玩法设计场景设计主要分为场景1和场景2,场景1主要是游戏开始界面,主要是给带来玩家一个引导作用,当玩家点击开始游戏跳转进入场景2,场景2是游戏的主体玩法部分。场景1主要组成部分:开始游戏按钮、退出游戏按钮、加载界面场景2主要组成部分:平地、高山、花、草、树木、竹林、动物场景的制作Unity中自带地形创建工具,我们可以在Hierarchy面板中Create->3DObject->Terrain这样来创建一个平面的地形,然后在检查器界面对地形进行改造、改变地面材质和添加花草树木。图3-1地形绘制改造界面狩猎模拟器游戏地图,为了经量模拟真实世界环境,使用了9块地形来拼成一个大地图,这里只有中间的地形作为玩家可以进入的地形,而游戏也围绕这里开展,其他8块地形只作为远景地形,也就是玩家只能看到而不能走进去,中间那一块四周将会使用空气墙把玩家隔离,防止玩家走进其他只是作为景色的地形。图3-2创建好的地形
为了让玩家更加身临其境,我们可以为地形添加一些树木和花草,让森林变的更加真实,这里我们直接使用unity自带的地形编辑器,为我们的地形进行批量的添加花草树木图3-3环境绘制Player制作物理抖动为了模拟现实中人物在移动和瞄准的时候明显的感觉自己在抖动,这个抖动会分别使用相机和手的左右抖动来模拟,也就是当Player在游戏里进行移动的时候,我们的镜头和手也会跟着抖动,而进入瞄准状态的时候玩家会更明显的感觉到瞄准镜的抖动。 上面说明了物理抖动的实现原理,接下来我们来编写脚本以实现我们镜头抖动。首先create->script创建脚本FPSController,接下来实现如下:localCameraRotationOffset
=
Vector3.Lerp(localCameraRotationOffset,
Vector3.zero,
Time.deltaTime
*
3);
//相机本地旋转值还原
float
swayY
=
(Mathf.Cos(Time.fixedTime
*
10
*
swaySpeed)
*
0.3f)
*
sizeY;
float
swayX
=
(Mathf.Sin(Time.fixedTime
*
5
*
swaySpeed)
*
0.2f)
*
sizeX;
FPSCamera.gameObject.transform.localPosition
=
Vector3.Lerp(FPSCamera.gameObject.transform.localPosition,
localCameraPositionTemp
+
new
Vector3(swayX,
swayY,
0),
Time.deltaTime
*
3);
//瞄准镜头位置设置
FPSCamera.gameObject.transform.localRotation
=
Quaternion.Lerp(FPSCamera.gameObject.transform.localRotation,
Quaternion.Euler(localCameraRotationTemp.eulerAngles
+
localCameraRotationOffset),
Time.deltaTime
*
3);//瞄准镜头旋转角度设置
接下来是手的抖动,因为该游戏里面手是连着枪的,所以我们直接在枪的脚本里写手的物理抖动,同样create->script先创建脚本Gun,然后实现如下:float
swayY
=
(Mathf.Cos(Time.time
*
10
*
swaySpeed)
*
0.3f)
*
sizeY;
float
swayX
=
(Mathf.Sin(Time.time
*
5
*
swaySpeed)
*
0.2f)
*
sizeX;
this.transform.localPosition
=
Vector3.Lerp(this.transform.localPosition,
positionTemp
+
new
Vector3(swayX,
swayY,
0),
Time.fixedDeltaTime
*
4);
this.transform.localRotation
=
Quaternion.Lerp(this.transform.localRotation,
Quaternion.Euler((rotationTemp.eulerAngles.x
+
(FPSmotor.rotationDif.x)),
(rotationTemp.eulerAngles.y
+
(FPSmotor.rotationDif.y)),
(rotationTemp.eulerAngles.z
+
(FPSmotor.direction.x
*
7))),
Time.fixedDeltaTime
*
3);
FPS功能FPS功能就是指开镜功能,当我们使用FPS功能时,就会举起枪打开单筒望远镜,而镜头会切换到瞄准镜头,玩家也就可以看的更远。这里我们创建两个相机create->camera,一个是正常的相机,深度为1,可以看到自己和枪,也就是模拟正常人观看视角的相机,另一个相机将深度设置为2,设置为只能看到枪,当我们点击鼠标右键时就会渲染第二个相机用来覆盖第一个相机,再次点击鼠标右键则可以关闭渲染,变回原来的镜头。图3-4正常相机镜头图3-5FPS相机镜头功能我们在脚本FPSController里面实现,下面实现开镜时人物动画的播放判断处理,代码如下:if
(HideGunWhileZooming
&&
FPSmotor
&&
NormalCamera.GetComponent<Camera>().enabled)
{
FPSmotor.HideGun(!Zooming);
}
if
(!GetComponent<Animation>()
||
!Active)
return;
switch
(gunState)
{
case
0:
if
(AmmoIn
<=
0)
{
Zooming
=
false;
if
(Clip
>
0)
{
GetComponent<Animation>().clip
=
GetComponent<Animation>()[BoltPose].clip;
GetComponent<Animation>().CrossFade(BoltPose,
0.5f,
PlayMode.StopAll);
gunState
=
2;
if
(FPSmotor
&&
Zooming)
{
FPSmotor.CameraForceRotation(new
Vector3(0,
0,
20));
FPSmotor.Stun(0.2f);
}
}
}
break;
case
1:
if
(Time.time
>=
cooldowntime
+
CooldownTime)
{
gunState
=
0;
}
break;
case
2:
GetComponent<Animation>().Play();
if
(GetComponent<Animation>()[BoltPose].normalizedTime
>
BoltTime)
{
if
(Shell
&&
ShellSpawn)
{
if
(!boltout)
{
if
(FPSmotor
&&
Zooming)
{
FPSmotor.CameraForceRotation(new
Vector3(0,
0,
-5));
FPSmotor.Stun(0.1f);
}
}
}
}
break;
}
这里对镜头的切换进行平缓处理,开镜的时候可以明显的看到镜头向前平移,然后再覆盖掉正常镜头,代码实现如下:if
(FPSmotor)
{
if
(Zooming)
{
FPSmotor.sensitivityXMult
=
MouseSensitiveZoom;
FPSmotor.sensitivityYMult
=
MouseSensitiveZoom;
FPSmotor.Noise
=
true;
}
else
{
FPSmotor.sensitivityXMult
=
MouseSensitive;
FPSmotor.sensitivityYMult
=
MouseSensitive;
FPSmotor.Noise
=
false;
}
}
if
(Zooming)
{
if
(ZoomFOVLists.Length
>
0)
{
MouseSensitiveZoom
=
((MouseSensitive
*
0.16f)
/
10)
*
ZoomFOVLists[IndexZoom];
NormalCamera.GetComponent<Camera>().fieldOfView
+=
(ZoomFOVLists[IndexZoom]
-
NormalCamera.GetComponent<Camera>().fieldOfView)
/
10;
}
}
else
{
NormalCamera.GetComponent<Camera>().fieldOfView
+=
(fovTemp
-
NormalCamera.GetComponent<Camera>().fieldOfView)
/
10;
}
开镜的UI处理,这里我们直接在OnGUI函数里面编写实现,代码如下:void
OnGUI()
{
if
(!Active)
return;
if
(NormalCamera.GetComponent<Camera>().enabled)
{
if
(!Zooming)
{
if
(CrosshairImg)
{
GUI.color
=
new
Color(1,
1,
1,
0.8f);
GUI.DrawTexture(new
Rect((Screen.width
*
0.5f)
-
(CrosshairImg.width
*
0.5f),
(Screen.height
*
0.5f)
-
(CrosshairImg.height
*
0.5f),
CrosshairImg.width,
CrosshairImg.height),
CrosshairImg);
GUI.color
=
Color.white;
}
}
else
{
scale.x
=
Screen.width
/
originalWidth;
//
calculate
hor
scale
scale.y
=
Screen.height
/
originalHeight;
//
calculate
vert
scale
scale.z
=
1.0f;
var
svMat
=
GUI.matrix;
GUI.matrix
=
Matrix4x4.TRS(Vector3.zero,
Quaternion.identity,
scale);
if
(CrosshairZoom)
{
float
scopeSize
=
(Screen.height
*
1.1f);
GUI.DrawTexture(new
Rect(0,
0,
originalWidth,
originalHeight),
CrosshairZoom);
}
GUI.matrix
=
svMat;
}
}
}
射击功能本游戏将只使用一把枪进行游戏,这把枪可以设置射出的子弹速度、发射间隔、攻击力等。图3-6玩家使用的武器枪主要的功能就是发射子弹,接下来我们就来实现这个功能,我们直接打开Gun脚本来进行编写,因为发射子弹是单独的一个功能,我们可以写一个发射子弹的方法,命名为Shoot(),代码实现如下:public
void
Shoot()
{
if
(!Active)
return;
if
(timefire
+
FireRate
<
Time.time)
{
if
(gunState
==
0)
{
if
(AmmoIn
>
0)
{
if
(FPSmotor)
FPSmotor.Stun(KickPower);
if
(SoundGunFire
&&
audiosource
!=
null)
{
if
(PlayerPrefs.GetInt("FX")
==
1)
audiosource.PlayOneShot(SoundGunFire);
}
for
(int
i
=
0;
i
<
BulletNum;
i++)
{
if
(Bullets)
{
Vector3
point
=
NormalCamera.GetComponent<Camera>().ScreenToWorldPoint(new
Vector3(0,
0,
NormalCamera.GetComponent<Camera>().nearClipPlane));
GameObject
bullet
=
(GameObject)Instantiate(Bullets,
point,
NormalCamera.gameObject.transform.rotation);
//生成子弹
bullet.transform.forward
=
NormalCamera.transform.forward;
//设置子弹的z轴
Destroy(bullet,
LifeTimeBullet);
//删除子弹
}
}
boltout
=
false;
GetComponent<Animation>().Stop();
GetComponent<Animation>().Play(ShootPose,
PlayMode.StopAll);
timefire
=
Time.time;
cooldowntime
=
Time.time;
if
(!SemiAuto)
{
gunState
=
1;
AmmoIn
-=
1;
}
else
{
if
(Shell
&&
ShellSpawn)
{
GameObject
shell
=
(GameObject)Instantiate(Shell,
ShellSpawn.position,
ShellSpawn.rotation);
shell.GetComponent<Rigidbody>().AddForce(ShellSpawn.transform.right
*
2);
shell.GetComponent<Rigidbody>().AddTorque(Random.rotation.eulerAngles
*
10);
GameObject.Destroy(shell,
5);
}
if
(Clip
>
0)
{
AmmoIn
=
1;
Clip
-=
1;
}
else
{
gunState
=
3;
}
}
}
}
}
}
子弹单列在写子弹前,我们必须清楚怎么处理子弹的和其他物体的碰撞,在这个问题上,我是先上百度搜索来一下,看看网友们的意见,最后我找了一些有意思的讨论,如下图:图3-7评论1图3-8评论2这里我们可以看到直接做碰撞会浪费资源造成卡顿,而且发生碰撞的时候还会有问题,并给出了解决方法,那就是使用射线来检测。在这个游戏里我们会在子弹上面发射一条射线来获取子弹即将击中的目标,并根据子弹和即将碰撞的物体的距离和子弹速度,求出在多久后会击中目标,create->script创建一个bullet_AS的脚本,然后代码实现如下:public
bool
RayShoot(bool
first)
{
bool
res
=
false;
RaycastHit[]
hits;
//射线集合
float
ray
=
BulletRaylength
*
Time.timeScale;
if
(ray
<
0.5f)
{
ray
=
0.5f;
}
hits
=
Physics.RaycastAll(transform.position,
transform.forward,
ray,
ignoreWalkThru);
for
(var
i
=
0;
i
<
hits.Length;
i++)
{
RaycastHit
hit
=
hits[i];
if
(hit.collider)
{
if
(hit.collider.tag
!=
"Player"
&&
hit.collider.tag
!=
this.gameObject.tag)
//射线的碰撞器不是player和子弹
{
targetLookat
=
null;
TargetLocked
=
false;
if
(!hittedObjectCheck(hit.collider.gameObject))
//当objectHittedList集合里面没有射线拿到的gameobject时,将其添加进来
{
res
=
true;
addHitedObject(hit.collider.gameObject);
GameObject
hitparticle
=
null;
this.transform.position
=
hit.point;
if
(hit.rigidbody)
{
hit.rigidbody.AddForceAtPosition(this.transform.forward
*
HitForce,
hit.point);
//子弹打到物体给于物体一个力
}
if
(hitparticle
!=
null)
{
if
(hit.collider.GetComponent<Terrain>())
{
hitparticle.transform.forward
=
hit.normal;
}
else
{
hitparticle.transform.forward
=
this.transform.forward;
}
GameObject.Destroy(hitparticle,
5);
//
Debug.Log("second
called");
}
if
(DestroyWhenHit
||
hitcount
>=
HitCountMax
||
hit.collider.GetComponent<Terrain>())
{
//Debug.Log(hit.collider.tag);
hitArea
=
hit.collider.tag;
Debug.Log(hitArea);
hited
=
true;
//删除子弹开关
}
HitTarget(hit.collider);
}
}
}
}
if
(hited)
//删除子弹判断
{
GameObject.Destroy(this.gameObject,0.04f);
}
return
res;
}
上里我们说bullet_AS写成一个单列,是因为在击中目标后,我们要知道到底击中了哪个部位,所以这里要返回击中目标部位的名字,这样动物的伤害判定就可以使用,代码实现如下:public
static
Bullet_AS
Instance()
{
return
_instance;
}
public
void
Awake()
{
_instance
=
this;
}
public
string
HitArea()
{
if
(hitArea
!=
null)
{
return
hitArea;
}
return
null;
}
子弹的飞行是物理运算,为了防止掉帧的时候子弹的运动出现异常,如一开始我们说的那样,这里不使用Update函数而是使用FixedUpdate函数,代码实现如下:void
FixedUpdate()
{
if
(TargetLocked
&&
GetComponent<Rigidbody>())
{
if
(targetLookat
!=
null)
{
this.transform.LookAt((targetLookat.transform.position
+
targetLookatOffset));
float
lateralSpeed
=
GetComponent<Rigidbody>().velocity.magnitude;
this.GetComponent<Rigidbody>().velocity
=
new
Vector3(transform.forward.x,
transform.forward.y,
transform.forward.z)
*
lateralSpeed;
}
}
}
人物移动创建一个空的物体,给空的物体添加碰撞器和Rigidbody,我们将在这个空的物体里面实现Player的移动逻辑,到时候将原本的Player作为这个空物体的子物体就可以实现Player的移动了。 这部分的功能分为移动、快速跑和下蹲,移动功能是指玩家可以根据WSAD键来控制Player进行上下左右移动,快速跑的是指点击左Shift键就可以加快速度奔跑,而下蹲键是玩家可以根据点击左Ctrl键降低身位。这些功能我们直接调用unity里面的函数来实现,而不用自己手动写。首先create->script创建脚本FirstPersonCharacter,物理运动逻辑自然还是在FixedUpdate函数里面实现,代码如下:float
speed
=
runSpeed;
float
h
=
Input.GetAxis("Horizontal");
float
v
=
Input.GetAxis("Vertical");
input
=
new
Vector2(
h,
v
);
bool
walkOrRun
=
Input.GetKey(KeyCode.LeftShift);
speed
=
walkByDefault
?
(walkOrRun
?
runSpeed
:
walkSpeed)
:
(walkOrRun
?
walkSpeed
:
runSpeed);
if
(input.sqrMagnitude
>
1)
input.Normalize();
Vector3
desiredMove
=
transform.forward
*
input.y
*
speed
+
transform.right
*
input.x
*
strafeSpeed;
因为我们是第一人称射击的玩法,只有移动、快速跑动还是不够的,还要有camera跟随鼠标转动,接下来我们来实现,继续创建脚本SimpleMouseRotator,代码实现如下: transform.localRotation
=
originalRotation;
float
inputH
=
0;
float
inputV
=
0;
if
(relative)
{
inputH
=
Input.GetAxis("Mouse
X");
inputV
=
Input.GetAxis("Mouse
Y");
if
(targetAngles.y
>
180)
{
targetAngles.y
-=
360;
followAngles.y
-=
360;
}
if
(targetAngles.x
>
180)
{
targetAngles.x
-=
360;
followAngles.x-=
360;
}
if
(targetAngles.y
<
-180)
{
targetAngles.y
+=
360;
followAngles.y
+=
360;
}
if
(targetAngles.x
<
-180)
{
targetAngles.x
+=
360;
followAngles.x
+=
360;
}
targetAngles.y
+=
inputH
*
rotationSpeed;
targetAngles.x
+=
inputV
*
rotationSpeed;
targetAngles.y
=
Mathf.Clamp
(
targetAngles.y,
-rotationRange.y
*
0.5f,
rotationRange.y
*
0.5f
);
targetAngles.x
=
Mathf.Clamp
(
targetAngles.x,
-rotationRange.x
*
0.5f,
rotationRange.x
*
0.5f
);
}
else
{
inputH
=
Input.mousePosition.x;
inputV
=
Input.mousePosition.y;
targetAngles.y
=
Mathf.Lerp
(
-rotationRange.y
*
0.5f,
rotationRange.y
*
0.5f,
inputH/Screen.width
);
targetAngles.x
=
Mathf.Lerp
(
-rotationRange.x
*
0.5f,
rotationRange.x
*
0.5f,
inputV/Screen.height
);
}
followAngles
=
Vector3.SmoothDamp(
followAngles,
targetAngles,
ref
followVelocity,
dampingTime
);
transform.localRotation
=
originalRotation
*
Quaternion.Euler(
-followAngles.x,
followAngles.y,
0
);
动物的AI动物AI比较明显的分为两大板块,一种是食草动物,一种是食肉动物,食草动物AI之间只有一些细节上的区别,食肉动物AI也是这样,只会对一些特殊点的动物AI进行改进。鹿的AI我们先来分析大自然中的鹿有哪些行为,比如:行走、奔跑、吃草、休息、被攻击会逃跑和死亡,我们将这些行为设计为一个个状态,鹿会根据特定的条件在这些状态之间切换。首先我们将行走、奔跑、吃草、休息、逃跑和死亡等行为设计为行走状态、奔跑状态、吃草状态、休息状态、逃跑状态和死亡状态,鹿正常情况下会随机休息、行走和吃草,而被攻击和看到敌人后就会逃跑,当血量降低为0的时候,鹿就会进入死亡状态,这里我们可以将鹿的行为归类为两个比较大的行为,也就是没有被攻击的状态和被攻击的状态,鹿没有被攻击的状态设计为行为树1可以进行自由行走、休息、吃草、看敌人逃跑和死亡等状态,被攻击的状态设计为行为树2可以逃跑和逃跑,然后根据这些状态和跳转条件绘制行为树图,下面是行为树1图:图3-9行为树1图3-10行为树2首先我们先拿到鹿的模型,导入包->自定义包,导入鹿的模型和动画,这里鹿的动画是帧动画,也就是每帧对模型进行微调整,制作出来的动画,这里我们根据行为树的状态制作了6个动画,代表鹿的6个行为,分别是死亡、被攻击、吃草、跑动、走动和休息。图3-11鹿的帧动画和模型接下来我们根据设计行为树图来制作鹿的行为树,这里我们会使用到行为树插件。首先AddBehaviorTree创建一个名为notHitBehaviorTree的行为树,然后根据行为树1设计图构建行为树,先看完成后的行为树,如图:图3-12行为树1完成图这里我们将鹿的探查范围设计为两个,分别上视觉和听觉,视觉只能看到前方90度位置的敌人,不过比听觉的范围更大,听觉范围是一个圆圈距离较小。图3-13鹿的探查范围AddBehaviorTree创建一个名为hitBehaviorTree的行为树,根据行为树2设计图构建行为树,看完成后的行为树,如图:图3-14行为树2完成图接下来我们更加详细的了解鹿各个行为的实现,首先是自由行走行为,鹿会随机挑选一个自己巡逻的地点,向那个地点前进,行走一段时间后,自由行走状态会自行打断,跳转到休息状态或者吃草状态。Create->script创建脚本DeerPatrol,代码实现如下:if(startTime
+
restDuration
<
Time.time)
{
isIdle
=
true;
startTime
=
Time.time;
restDuration
=
restTime.Value;
return
TaskStatus.Inactive;
}
if
(!navMeshAgent.pathPending)
{
var
thisPosition
=
transform.position;
thisPosition.y
=
navMeshAgent.destination.y;
if
(Vector3.SqrMagnitude(thisPosition
-
navMeshAgent.destination)
<
arriveDistance.Value)
{
if
(randomPatrol.Value)
{
waypointIndex
=
Random.Range(0,
waypoints.Value.Count);
}
else
{
waypointIndex
=
(waypointIndex
+
1)
%
waypoints.Value.Count;
}
navMeshAgent.destination
=
Target();
}
}
transform.GetComponent<Animation>().Play("walk");
return
TaskStatus.Running;
接下来实现休息状态和吃草状态,休息状态和吃草状态都是禁止的,所以我们可以将他们写在一个状态里,根据停留的时间来判断执行哪个状态,这里停留的时间是随机的。create->script创建脚本DeerWait,代码实现如下:public
override
void
OnStart()
{
startTime
=
Time.time;
if
(randomWait.Value)
{
waitDuration
=
Random.Range(randomWaitMin.Value,
randomWaitMax.Value);
}
else
{
waitDuration
=
waitTime.Value;
}
}
public
override
TaskStatus
OnUpdate()
{
if
(startTime
+
waitDuration
<
Time.time)
{
return
TaskStatus.Inactive;
}
if
(randomWait.Value)
{
if
(waitDuration
<
randomWaitMax.Value
/
2)
{
transform.GetComponent<Animation>().Play("idle2");
}
else
{
transform.GetComponent<Animation>().Play("idle1");
}
}
return
TaskStatus.Running;
}
public
override
void
OnPause(bool
paused)
{
if
(paused)
{
pauseTime
=
Time.time;
}
else
{
startTime
+=
(Time.time
-
pauseTime);
}
}
这里我们来实现逃跑状态,逃跑是在看到敌人或者听到敌人在附近的时候就会切换进入的状态,所以这里我们要拿到看到的物体或者听到的物体,然后逃离这个物体,接下来我们先创建脚本DeerFlee,然后代码实现如下:public
override
void
OnAwake()
{
navMeshAgent
=
gameObject.GetComponent<UnityEngine.AI.NavMeshAgent>();
}
public
override
void
OnStart()
{
dynamicTarget
=
(targetTransform
!=
null
&&
targetTransform.Value
!=
null);
navMeshAgent.speed
=
speed.Value;
navMeshAgent.angularSpeed
=
angularSpeed.Value;
navMeshAgent.enabled
=
true;
navMeshAgent.destination
=
Target();
}
public
override
TaskStatus
OnUpdate()
{
if
(!navMeshAgent.pathPending
&&
Vector3.SqrMagnitude(transform.position
-
Target())
>
fleedDistance.Value)
{
return
TaskStatus.Success;
}
navMeshAgent.destination
=
FleePosition();
transform.GetComponent<Animation>().Play("run");
return
TaskStatus.Running;
}
public
override
void
OnEnd()
{
navMeshAgent.enabled
=
false;
}
private
Vector3
Target()
{
if
(dynamicTarget)
{
return
targetTransform.Value.position;
}
return
targetPosition.Value;
}
private
Vector3
FleePosition()
{
return
transform.position
+
(transform.position
-
Target()).normalized
*
lookAheadDistance.Value;
}
鹿的AI已经制作完成,接下来鹿还需要一个管理器,管理行为树间的切换、HP、被攻击扣除的血量、被攻击的部位和死亡状态所要处理的事项,Create->script创建脚本DeerController,实现鹿被攻击之后关闭行为树1,打开行为树2,并完成扣血进入流血状态,当鹿逃跑一段时间后,流血状态取消,关闭行为树2打开行为树1。HP为0的时候行为树1和行为树2全部关闭,执行鹿死亡的动画。这里子弹单列返回的击中部位可以使用到,我们可以根据这个来判断对鹿造成多少伤害,会进入什么状态。下面是代码:void
Update()
{
if
(isDeath
==
false)
{
if
(Bullet_AS.Instance()
!=
null)
{
if
(Bullet_AS.Instance().HitArea()
!=
null)
{
Debug.Log(Bullet_AS.Instance().HitArea());
if
(Bullet_AS.Instance().HitArea()
==
"DeerHead")
{
hitHead();
}
if
(Bullet_AS.Instance().HitArea()
==
"DeerNeck")
{
hitNeck();
}
if
(Bullet_AS.Instance().HitArea()
==
"DeerBody")
{
hitBody();
}
}
}
if
(HP
>=
0
&&
bleedTime
>=
0)
{
if
(bleedInterval
<=
0)
{
HP
-=
1;
bleedInterval
=
bleedIntervale
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 山东省聊城市东昌教育集团2025-2026学年上学期九年级期末数学模拟检测试题(含答案)
- 安徽省蚌埠市部分学校2026届九年级上学期期末考试英语试卷(含答案、无听力原文及音频)
- 飞行区技术标准培训课件
- 钢结构连接设计技术要领
- 飞机简单介绍
- 飞机知识科普儿童
- 飞机的基础知识课件
- 2026山东事业单位统考省煤田地质局第五勘探队招聘初级综合类岗位3人考试参考试题及答案解析
- 2026年唐山市丰南区新合供销合作社管理有限公司招聘审计人员1名备考考试试题及答案解析
- 工业厂房水电维修管理制度(3篇)
- ICU护士长2025年度述职报告
- 2026云南保山电力股份有限公司校园招聘50人笔试参考题库及答案解析
- 引水压力钢管制造及安装工程监理实施细则
- 钢结构除锈后油漆施工方案
- 骨科患者围手术期静脉血栓栓塞症预防指南(2025年)
- 辅助生殖项目五年发展计划
- 仓库安全消防管理制度
- 2025年信息化运行维护工作年度总结报告
- 肠梗阻的课件
- 广西对口升专职业技能测试答案
- 冶炼烟气制酸工艺解析
评论
0/150
提交评论