Unity3D自带例子AngryBots的分析.docx

上传人:b****1 文档编号:11099901 上传时间:2023-05-29 格式:DOCX 页数:12 大小:21.08KB
下载 相关 举报
Unity3D自带例子AngryBots的分析.docx_第1页
第1页 / 共12页
Unity3D自带例子AngryBots的分析.docx_第2页
第2页 / 共12页
Unity3D自带例子AngryBots的分析.docx_第3页
第3页 / 共12页
Unity3D自带例子AngryBots的分析.docx_第4页
第4页 / 共12页
Unity3D自带例子AngryBots的分析.docx_第5页
第5页 / 共12页
Unity3D自带例子AngryBots的分析.docx_第6页
第6页 / 共12页
Unity3D自带例子AngryBots的分析.docx_第7页
第7页 / 共12页
Unity3D自带例子AngryBots的分析.docx_第8页
第8页 / 共12页
Unity3D自带例子AngryBots的分析.docx_第9页
第9页 / 共12页
Unity3D自带例子AngryBots的分析.docx_第10页
第10页 / 共12页
Unity3D自带例子AngryBots的分析.docx_第11页
第11页 / 共12页
Unity3D自带例子AngryBots的分析.docx_第12页
第12页 / 共12页
亲,该文档总共12页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

Unity3D自带例子AngryBots的分析.docx

《Unity3D自带例子AngryBots的分析.docx》由会员分享,可在线阅读,更多相关《Unity3D自带例子AngryBots的分析.docx(12页珍藏版)》请在冰点文库上搜索。

Unity3D自带例子AngryBots的分析.docx

Unity3D自带例子AngryBots的分析

研究一下Unity3d自带的AngryBots项目,了解基本的游戏运行机制:

1.人物的动作控制逻辑

***Player对象***

[外形]

Player对象里有一个对象具有SkinnedMeshRenderer组件,该组件使用的Mesh名为main_player_lorez。

类似的还有表达武器的,名为main_weapon001的GameObject。

[操作]:

(InputManager)

**移动**

定义:

移动在InputManager里添加了2种操作方式:

水平移动,名为Horizontal

垂直移动,名为Vertical

并设置了一些属性,比如对应的按键,加速度,类型等等。

在脚本(PlayerMoveController.js)里,通过Input.GetAxis("Horizontal")和Input.GetAxis("Vertical")获得玩家的按键状态转化成的运动方向。

并储存在MovementMotor.js脚本定义的movementDirection变量里。

实现:

Player添加了RigidBody组件,该组件提供了按物理规律改变GameObject的Transform的能力。

在FreeMovementMotor.js脚本里,定义了一些参数,用于和movementDirection一起,计算出作用于RigidBody对象上的力(Force)。

角色就开始向指定方向移动了。

**面向(facingDirection)**

直接使用Input.mousePosition作为屏幕坐标,用角色所在位置定义一个平面,求得射线焦点,将该角色所在位置到该点的方向作为面向。

并储存在MovementMotor.js脚本定义的facingDirection变量里。

[动作播放]:

PlayerAnimation(Script)(PlayerAnimation.js)

varmoveAnimations:

MoveAnimation[];因为是public变量,所以可以在inspector中直接修改,

例子中定义了6个动作run_forward/run_backward/run_right/run_left和idle/turn。

由于这个例子里角色的动作定义了6个clip,和上述6个动作名称一一对应。

动作的播放不是在转向发生,或是ASWD按下时发生的。

该脚本对比Player的Transform在2帧内的变化,根据面向、移动方向,计算出具体播放哪个动作。

同时,有动作混合逻辑,使得动作的切换是有过程并且平滑的。

上半身转动到一定角度,下半身也会调整,这个也是逻辑做的功能。

2.从射击到命中的整个处理流程,射击特效的制作原理

[创建子弹]

Cache对象

ObjectCache类

varprefab:

GameObject;

varcacheSize:

int=10;

Spawner.js

varcaches:

ObjectCache[];

functionAwake(){

caches[i].Initialize();

}

staticfunctionSpawn(...);

staticfunctionDestroy(...);

有一个对象cache池,即为objectCache对象的实例,

该对象初始化固定数量的对象实例,并顺序的提供对象实例。

Spawner对象按Prefab类型将多个ObjectCache对象组织起来,

并通过Spawn和Destroy函数提供统一的接口来创建和销毁各种对象实例--例如子弹,导弹。

[发射子弹的时机]

WeaponSlot

TriggerOnMouseOrJoystick.js

publicvarmouseDownSignals:

SignalSender;

publicvarmouseUpSignals:

SignalSender;

SignalSender.js

publicfunctionSendSignals(sender:

MonoBehaviour)

publicvarreceivers:

ReceiverItem[];

AutoFire.js

functionUpdate()

if(firing){

if(Time.time>lastFireTime+1/frequency){

vargo:

GameObject=Spawner.Spawn(bulletPrefab,spawnPoint.position,spawnPoint.rotation*coneRandomRotation)asGameObject;

WeaponSlot(GameObject)对象有一个脚本组件,名为TriggerOnMouseOrJoystick

该脚本的update方法通过Input.GetMouseButtonDown(0)来监测鼠标左键的按下状态,同时使用SignalSender对象将事件Fire出去。

SignalSender本质上来说是一个发布订阅模式,EventSource通过声明SignalSender变量,

来声明会发起的事件(eventname),并在必要的时机,调用SignalSender.SendSignals(this)来fire事件。

事件的接收方由SignalSender的receivers变量给出。

因为它是个全局变量,所以可以在inspector里设置。

客户方的处理逻辑和事件源就是通过这样的方式关联起来的。

事件的名称也是通过inspector来设置的。

SendSignals方法接受的参数为MonoBehaviour类型,因此可以通过这个事件机制,在不同的脚本中调用不同的功能。

由于GameObject的SendMessage的实现原理,只需要保证事件的接收方包含与事件名称相同的函数,即会被自动调用。

(疑惑:

这种方式是不带参数的,如果需要对Event做额外的参数传递怎么办呢?

能想到的是在一个公共的地方做数据交换)

通过SignalSender,武器的逻辑状态--"开火"--已经被逻辑识别了,例子将结果保存在AutoFire脚本的firing变量中。

开火后,在AutoFire里,激活了子弹的实例对象。

[开火的特效]

WeaponSlot

AutoFire.js

muzzleFlashFront.active=true;

audio.Play();

通过SignalSender,武器的逻辑状态--"开火"--已经被逻辑识别了,

例子将结果保存在AutoFire脚本的firing变量中。

同一时刻,也播放了一些开火的特效:

*武器开火的音效,这只是调用AudioSource组件。

*武器枪口的火花,muzzleFlashFront对象,在inspector中指定为一个GameObject。

其中包含一些Mesh和一个Light,以及一个将Mesh旋转和缩放以达到比较酷的喷射火光的脚本。

*人物的射击动作--通过另一组监听实现的,不在AutoFire脚本中触发。

[命中时的事情]

PerFrameRaycast.js

privatevarhitInfo:

RaycastHit;

AutoFire.js(命中判定)

varhitInfo:

RaycastHit=raycast.GetHitInfo();

AutoFire.js(击退敌人)

varforce:

Vector3=transform.forward*(forcePerSecond/frequency);

hitInfo.rigidbody.AddForceAtPosition(force,hitInfo.point,ForceMode.Impulse);

AutoFire.js(播放击中的音效)

varsound:

AudioClip=MaterialImpactManager.GetBulletHitSound(hitInfo.collider.sharedMaterial);

AudioSource.PlayClipAtPoint(sound,hitInfo.point,hitSoundVolume);

游戏中实现的命中,和子弹飞行无关,是通过PerFrameRaycast.js脚本提供的射线查询结果来做的命中判定。

PerFrameRaycast每帧做一次射线查询,将得到的结果保存在hitInfo中。

AutoFire在update的时候,检查是否命中了对象。

如果命中了对象,计算各种伤害,并调用Health脚本组件的相关方法。

(Health相关的事情稍后详细描述)

[子弹的飞行]

子弹是一个名为InstanceBullet的GameObject,

它由名为InstanceBullet的Prefab对象来描述,

主要包含了一个表达子弹轨迹的长条形的mesh,和一个用于控制其飞行的脚本SimpleBullet.js。

SimpleBullet.js

functionUpdate(){

tr.position+=tr.forward*speed*Time.deltaTime;

functionUpdate(){

if(Time.time>spawnTime+lifeTime||dist<0){

Spawner.Destroy(gameObject);

SimpleBullet.js包含了一些参数,保证子弹有以下行为:

沿创建的方向飞行

有时限,时间到了会被休眠(Spawner.Destroy)

有距离上限,超过距离会休眠(Spawner.Destroy)

AutoFire.js

bullet.dist=hitInfo.distance;

除了上述2种方式消隐子弹实例外,子弹可以穿过场景里的石头,但是无法穿越箱子,也无法穿越将石头移开后露出的场景边界。

这是因为在AutoFire做命中判定的同时,根据射线查询的结果调整了子弹的距离上限参数。

3.怪物的激活、攻击、动作控制原理,你所遇到的第一个怪物KamikazeBuzzer的攻击特效的实现原理

[第1个KamikazeBuzzer]

SimpleBuzzers7

EnemyArea.js

BoxCollider

KamikazeBuzzer

KamikazeMovementMotor.js

BuzzerKamikazeControllerAndAi.js

DestroyObject.js

Health.js

AudioSource

[外形]

buzzer_bot

[动作]

这个怪物的mesh没动作。

[激活]

EnemyArea.js

functionOnTriggerEnter(other:

Collider){

if(other.tag=="Player")

ActivateAffected(true);

角色进入BoxCollider的范围时,会触发OnTriggerEnter,

这时会将SimpleBuzzers7的子对象KamikazeBuzzer设置为激活的。

挂载到KamikazeBuzzer对象上的脚本组件也就可以开始执行了。

[移动]

KamikazeMovementMotor.js

该脚本控制KamikazeBuzzer的刚体属性,根据参数和一定的计算规则计算出力,作用于刚体,让KamikazeBuzzer动起来,类似于Player的移动原理。

BuzzerKamikazeControllerAndAi.js

该脚本根据怪物和Player之间的位置关系,按一定计算规则算出KamikazeMovementMotor需要的参数,从而达到控制其移动的目的。

direction=(player.position-character.position);

因为方向总是朝着player,所以看起来就有个“追击”的效果。

rechargeTimer<0.0f&&threatRange&&Vector3.Dot(character.forward,direction)>0.8f

这个判断达到了“追过头”的效果。

[攻击特效]

当移动流程里“追到了”条件达成后,主要调用DoElectricArc函数来表达攻击方式。

zapNoise=Vector3(Random.Range(-1.0f,1.0f),0.0f,Random.Range(-1.0f,1.0f))*0.5f;

zapNoise=transform.rotation*zapNoise;

这里有些小随机,是为了让每次电到Player的位置不一样。

publicvarelectricArc:

LineRenderer;

electricArc.SetPosition(0,electricArc.transform.position);

electricArc.SetPosition(1,player.position+zapNoise);

主要靠LineRenderer来描述闪电弧。

LineRenderer用来构造若干条连续的线段,可以设置起始的宽度和结束的宽度。

[被击]

DamagePos(GameObject)

Transform(Component)

KamikazeBuzzer(GameObject)

Health.js

Health.js

privatevardamageEffect:

ParticleEmitter;

functionOnDamage(amount:

float,fromDirection:

Vector3){

damageEffect.Emit();

DamagePos对象聚合了一个Transform组件,该组件为被击效果提供坐标信息。

KamikazeBuzzer聚合了一个Health.js,其中的damageEffect指定为ElectricSparksHitA(prefab)。

在之前子弹的命中流程中,被击中的target,会调用其Health组件的OnDamage函数。

KamikazeBuzzer的Health的OnDamage,就是创建ElectricSparksHitA(Clone)对象,从而达到播放被击特效。

[死亡和爆炸]

Health.js

publicvardieSignals:

SignalSender;

functionOnDamage(amount:

float,fromDirection:

Vector3){

if(health<=0)

{

dieSignals.SendSignals(this);

SpawnObject.js

functionOnSignal(){

spawned=Spawner.Spawn(objectToSpawn,transform.position,transform.rotation);

DestroyObject.js

functionOnSignal(){

Spawner.Destroy(objectToDestroy);

当health值减少到0及0以下,对象就被判定为死亡了。

DamagePos对象聚合了一个SpawnObject.js脚本。

在其OnSignal函数里创建一个ExplosionSequenceBuzzer(prefab);

ExplosionSequenceBuzzer是用来表达爆炸效果的。

在其EffectSequencer.js脚本中,控制了一些粒子的变化。

KamikazeBuzzer对象聚合了一个DestroyObject.js脚本。

在其OnSignal函数里销毁了KamikazeBuzzer对象实例。

4.人物与怪相关的health处理相关流程

Player和怪物的血量,都是通过聚合一个Health.js脚本组件来完成。

伤害计算则是在各自的组件里独立编写计算的。

Player是AutoFire,KamikazeBuzzer是在其AI脚本里。

Health组件主要定义了

血量

被击特效

受伤的痕迹

被击事件

死亡事件

协作方式已经在分析Player和KamikazeBuzzer的行为方式时有所表述。

5.摄像机跟随与控制

PlayerMoveController.js

里面根据角色位置计算摄像机位置。

根据鼠标位置,微调摄像机位置。

6.雨滴相关效果的实现原理,包括雨滴掉落、落到地面产生的波纹、地表水面的实现与反射效果等

【雨滴】

[相关GameObject]

Rain表达雨声

RainBox表达雨滴

RainEffect将各种东西组织起来的Root对象

RainDrops雨滴掉落的Root对象

RainslpashesBig表达雨滴的大涟漪的Root对象

RainslpashesSmall表达雨滴的小涟漪的Root对象

splashbox表达涟漪

[Mesh&Material]

RainDrops_LQ0/1/2

RainsplashesBig_LQ0/1/2

RainsplashesSmall_LQ0/1/2

[Shader]

Rain

RainSplash

[组织关系]

Environment(dynamic)

RainEffects(位置000)

RainDrops(RainManager.js)

RainBox*N

RainBox.js

Rain(Shader)

RainDrops_LQ0(Mesh)

RainslpashesBig(RainsplashManager.js)

splashbox

RainsplashBox.js

RainSplash(Shader)

RainsplashesBig_LQ0(Mesh)

RainslpashesSmall

splashbox

RainsplashBox.js

RainSplash(Shader)

RainsplashesSmall_LQ0(Mesh)

[落雨]

RainManager.js

functionCreateMesh():

Mesh{

publicfunctionGetPreGennedMesh():

Mesh{

RainManager在运行期创建了雨幕的Mesh和Material,思路为在固定大小的长方体里,随机生成只有4个顶点的小片。

生成的对象和名字有关,即为GameObject.name+_LQ0/1/2,一共3种类型的Mesh,只是生成的片的位置,uv坐标等不一致。

RainBox.js

functionUpdate(){

functionOnDrawGizmos(){

Update里的逻辑让雨幕Mesh在Y方向上从上自下的循环运动,从而达到雨滴落下的效果。

OnDrawGizmos函数是为了在编辑期绘制雨幕的外形。

[涟漪的创建]

RainsplashManager.js

RainsplashBox.js

涟漪的创建方式和雨幕原理一样,只是在小片生成时的坐标,法线方向略有不同。

大涟漪和小涟漪只是创建的片的数量和区域大小不同而已。

[涟漪的扩散]

RainSplash(Shader)里,对传进来的定点上的uv坐标和颜色做了一定的变换,从而做出涟漪从小变大和逐渐消隐。

(疑问)材质从哪里指定的?

inspector手工指定?

(疑问)RainBox.js里的enable,禁止和允许了哪些调用?

【地表水面与反射】

[相关GameObject]

polySurface5097地表

RealtimeReflectionInWaterFlow.shader处理水面模拟和反射的shader

MainCameraReflectionMainCamera反射摄像机

RealtimeReflectionReplacement.shader备用的shader方案

MainCamera主摄像机

ReflectionFx.cs生成反射贴图的脚本

publicSystem.StringreflectionSampler="_ReflectionTex";反射贴图

reflectionMask

[关键代码]

ReflectionFx.cs

publicTransform[]reflectiveObjects;

privateCamerareflectionCamera;

publicLayerMaskreflectionMask;

计算反射摄像机的位置和朝向,将变换合并为反射矩阵

渲染到反射贴图

RealtimeReflectionInWaterFlow.shader

_ReflectionTex("_ReflectionTex",2D)="black"{}

v2f_fullvert(appdata_fullv)

o.fakeRefl=EthansFakeReflection(v.vertex);

fixed4frag(v2f_fulli):

COLOR0

fixed4rtRefl=tex2D(_ReflectionTex,(i.screen.xy/i.screen.w)+nrml.xy);

rtRefl+=tex2D(_FakeReflect,i.fakeRefl+nrml.xy*2.0);

RealtimeReflectionReplacement.shader

[LateUpdate]

在LateUpdate里处理反射

因为反射需要等所有对象的运动都结束了。

[reflectiveObjects]

reflectiveObjects是可以产生反射的对象,手工添加的,例子里有3个,分别是:

polySurface5097

polySurface425

polySurface5095

[helperCameras为什么要Clear]

helperCameras从设计意图上来看,是为了支持游戏里任意数量的摄像机的反射,也就是ReflectionFx.cs的通用性。

为了确保一帧之内只渲染一次反射贴图,所以就clear了。

[被反射对象的筛选]

reflectionMask

可以被反射的对象必须是以下layer之一

Reflection

Player

Enemies

这个通过Inspector设置GameObject的Layer属性即可。

7.其它你觉着重要的主题

Coroutine.协程,协程不是多线程,是将代码的执行控制权转移出去的一种机制。

通过这种机制,可以让代码的执行流程不那么顺序化,达到各种模块协作的目的。

AudioSource音源对象,需要AudioClip载入声音资源,用AudioListener(ears)一起计算音频的声音大小。

Animation动画对象,控制骨骼动画相关,支持混合和IK。

WWW封装URL操作的类,可以支持http,https,file,

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 人文社科 > 设计艺术

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2