研究一下Unity3d自带的AngryBots项目,了解基本的游戏运行机制:
1. 人物的动作控制逻辑
***Player对象***
[外形]
Player对象里有一个对象具有Skinned Mesh Renderer组件,该组件使用的Mesh名为main_player_lorez。
类似的还有表达武器的,名为main_weapon001的GameObject。
***Player对象***
[外形]
Player对象里有一个对象具有Skinned Mesh Renderer组件,该组件使用的Mesh名为main_player_lorez。
类似的还有表达武器的,名为main_weapon001的GameObject。
[操作]: (InputManager)
**移动**
定义:
移动在InputManager里添加了2种操作方式:
水平移动,名为 Horizontal
垂直移动,名为 Vertical
并设置了一些属性,比如对应的按键,加速度,类型等等。
**移动**
定义:
移动在InputManager里添加了2种操作方式:
水平移动,名为 Horizontal
垂直移动,名为 Vertical
并设置了一些属性,比如对应的按键,加速度,类型等等。
在脚本(PlayerMoveController.js)里,通过Input.GetAxis(“Horizontal”) 和 Input.GetAxis(“Vertical”)获得玩家的按键状态转化成的运动方向。
并储存在MovementMotor.js脚本定义的movementDirection变量里。
并储存在MovementMotor.js脚本定义的movementDirection变量里。
实现:
Player添加了RigidBody组件,该组件提供了按物理规律改变GameObject的Transform的能力。
在FreeMovementMotor.js脚本里,定义了一些参数,用于和movementDirection一起,计算出作用于RigidBody对象上的力(Force)。角就开始向指定方向移动了。
Player添加了RigidBody组件,该组件提供了按物理规律改变GameObject的Transform的能力。
在FreeMovementMotor.js脚本里,定义了一些参数,用于和movementDirection一起,计算出作用于RigidBody对象上的力(Force)。角就开始向指定方向移动了。
**面向(facingDirection)**
直接使用usePosition作为屏幕坐标,用角所在位置定义一个平面,求得射线焦点,将该角所在位置到该点的方向作为面向。
并储存在MovementMotor.js脚本定义的facingDirection变量里。
直接使用usePosition作为屏幕坐标,用角所在位置定义一个平面,求得射线焦点,将该角所在位置到该点的方向作为面向。
并储存在MovementMotor.js脚本定义的facingDirection变量里。
[动作播放]:Player Animation(Script)(PlayerAnimation.js)
var moveAnimations : MoveAnimation[]; 因为是public变量,所以可以在inspector中直接修
var moveAnimations : MoveAnimation[]; 因为是public变量,所以可以在inspector中直接修
改,
例子中定义了6个动作 run_forward/run_backward/run_right/run_left 和 idle/turn 。
由于这个例子里角的动作定义了6个clip,和上述6个动作名称一一对应。
例子中定义了6个动作 run_forward/run_backward/run_right/run_left 和 idle/turn 。
由于这个例子里角的动作定义了6个clip,和上述6个动作名称一一对应。
动作的播放不是在转向发生,或是ASWD按下时发生的。
该脚本对比Player的Transform在2帧内的变化,根据面向、移动方向,计算出具体播放哪个动作。
同时,有动作混合逻辑,使得动作的切换是有过程并且平滑的。
上半身转动到一定角度,下半身也会调整,这个也是逻辑做的功能。
该脚本对比Player的Transform在2帧内的变化,根据面向、移动方向,计算出具体播放哪个动作。
同时,有动作混合逻辑,使得动作的切换是有过程并且平滑的。
上半身转动到一定角度,下半身也会调整,这个也是逻辑做的功能。
2. 从射击到命中的整个处理流程,射击特效的制作原理
[创建子弹]
Cache对象
ObjectCache类
var prefab : GameObject;
var cacheSize : int = 10;
[创建子弹]
Cache对象
ObjectCache类
var prefab : GameObject;
var cacheSize : int = 10;
Spawner.jsunity3d animation
var caches : ObjectCache[];
function Awake () {
caches[i].Initialize ();
}
static function Spawn(…);
static function Destroy(…);
var caches : ObjectCache[];
function Awake () {
caches[i].Initialize ();
}
static function Spawn(…);
static function Destroy(…);
有一个对象cache池,即为objectCache对象的实例,
该对象初始化固定数量的对象实例,并顺序的提供对象实例。
Spawner对象按Prefab类型将多个ObjectCache对象组织起来,
并通过Spawn 和 Destroy 函数提供统一的接口来创建和销毁各种对象实例–例如子弹,导弹。
该对象初始化固定数量的对象实例,并顺序的提供对象实例。
Spawner对象按Prefab类型将多个ObjectCache对象组织起来,
并通过Spawn 和 Destroy 函数提供统一的接口来创建和销毁各种对象实例–例如子弹,导弹。
[发射子弹的时机]
WeaponSlot
WeaponSlot
TriggerOnMouseOrJoystick.js
public var mouseDownSignals : SignalSender;
public var mouseUpSignals : SignalSender;
SignalSender.js
public function SendSignals (sender : MonoBehaviour)
public var receivers : ReceiverItem[];
AutoFire.js
function Update ()
if (firing) {
if (Time.time > lastFireTime + 1 / frequency) {
var go : GameObject = Spawner.Spawn (bulletPrefab, spawnPoint.position, ation * coneRandomRotation) as GameObject;
WeaponSlot(GameObject)对象有一个脚本组件 , 名为TriggerOnMouseOrJoystick
public var mouseDownSignals : SignalSender;
public var mouseUpSignals : SignalSender;
SignalSender.js
public function SendSignals (sender : MonoBehaviour)
public var receivers : ReceiverItem[];
AutoFire.js
function Update ()
if (firing) {
if (Time.time > lastFireTime + 1 / frequency) {
var go : GameObject = Spawner.Spawn (bulletPrefab, spawnPoint.position, ation * coneRandomRotation) as GameObject;
WeaponSlot(GameObject)对象有一个脚本组件 , 名为TriggerOnMouseOrJoystick
该脚本的update方法通过Input.GetMouseButtonDown (0) 来监测鼠标左键的按下状态,同时使用SignalSender对象将事件Fire出去。
SignalSender本质上来说是一个发布订阅模式,EventSource通过声明SignalSender变量,
来声明会发起的事件(event name),并在必要的时机,调用SignalSender.SendSignals(this)来fire事件。
事件的接收方由SignalSender的receivers变量给出。
因为它是个全局变量,所以可以在inspector里设置。
客户方的处理逻辑和事件源就是通过这样的方式关联起来的。
事件的名称也是通过inspector来设置的。
SignalSender本质上来说是一个发布订阅模式,EventSource通过声明SignalSender变量,
来声明会发起的事件(event name),并在必要的时机,调用SignalSender.SendSignals(this)来fire事件。
事件的接收方由SignalSender的receivers变量给出。
因为它是个全局变量,所以可以在inspector里设置。
客户方的处理逻辑和事件源就是通过这样的方式关联起来的。
事件的名称也是通过inspector来设置的。
SendSignals方法接受的参数为MonoBehaviour类型,因此可以通过这个事件机制,在不同的脚本中调用不同的功能。
由于GameObject的SendMessage的实现原理,只需要保证事件的接收方包含与事件名称相同的函数,即会被自动调用。
(疑惑:这种方式是不带参数的,如果需要对Event做额外的参数传递怎么办呢?能想到的是在一个公共的地方做数据交换)
由于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脚本中触发。
*人物的射击动作–通过另一组监听实现的,不在AutoFire脚本中触发。
[命中时的事情]
PerFrameRaycast.js
private var hitInfo : RaycastHit;
AutoFire.js(命中判定)
var hitInfo : RaycastHit = raycast.GetHitInfo ();
AutoFire.js(击退敌人)
var force : Vector3 = transform.forward * (forcePerSecond / frequency);
hitInfo.rigidbody.AddForceAtPosition (force, hitInfo.point, ForceMode.Impulse);
AutoFire.js(播放击中的音效)
var sound : AudioClip = MaterialImpactManager.GetBulletHitSound (llider.sharedMaterial);
AudioSource.PlayClipAtPoint (sound, hitInfo.point, hitSoundVolume);
PerFrameRaycast.js
private var hitInfo : RaycastHit;
AutoFire.js(命中判定)
var hitInfo : RaycastHit = raycast.GetHitInfo ();
AutoFire.js(击退敌人)
var force : Vector3 = transform.forward * (forcePerSecond / frequency);
hitInfo.rigidbody.AddForceAtPosition (force, hitInfo.point, ForceMode.Impulse);
AutoFire.js(播放击中的音效)
var sound : AudioClip = MaterialImpactManager.GetBulletHitSound (llider.sharedMaterial);
AudioSource.PlayClipAtPoint (sound, hitInfo.point, hitSoundVolume);
游戏中实现的命中,和子弹飞行无关,是通过PerFrameRaycast.js脚本提供的射线查询结果来做的命中判定。
PerFrameRaycast每帧做一次射线查询,将得到的结果保存在hitInfo中。
AutoFire在update的时候,检查是否命中了对象。
如果命中了对象,计算各种伤害,并调用Health脚本组件的相关方法。(Health相关的事情稍后详细描述)
[子弹的飞行]
子弹是一个名为InstanceBullet的GameObject,
它由名为InstanceBullet的Prefab对象来描述,
主要包含了一个表达子弹轨迹的长条形的mesh,和一个用于控制其飞行的脚本SimpleBullet.js。
子弹是一个名为InstanceBullet的GameObject,
它由名为InstanceBullet的Prefab对象来描述,
主要包含了一个表达子弹轨迹的长条形的mesh,和一个用于控制其飞行的脚本SimpleBullet.js。
SimpleBullet.js
function Update () {
function Update () {
tr.position += tr.forward * speed * Time.deltaTime;
function Update () {
if (Time.time > spawnTime + lifeTime || dist < 0) {
Spawner.Destroy (gameObject);
function Update () {
if (Time.time > spawnTime + lifeTime || dist < 0) {
Spawner.Destroy (gameObject);
SimpleBullet.js包含了一些参数,保证子弹有以下行为:
沿创建的方向飞行
有时限,时间到了会被休眠(Spawner.Destroy)
有距离上限,超过距离会休眠(Spawner.Destroy)
沿创建的方向飞行
有时限,时间到了会被休眠(Spawner.Destroy)
有距离上限,超过距离会休眠(Spawner.Destroy)
AutoFire.js
bullet.dist = hitInfo.distance;
除了上述2种方式消隐子弹实例外,子弹可以穿过场景里的石头,但是无法穿越箱子,也无法穿越将石头移开后露出的场景边界。
这是因为在AutoFire做命中判定的同时,根据射线查询的结果调整了子弹的距离上限参数。
bullet.dist = hitInfo.distance;
除了上述2种方式消隐子弹实例外,子弹可以穿过场景里的石头,但是无法穿越箱子,也无法穿越将石头移开后露出的场景边界。
这是因为在AutoFire做命中判定的同时,根据射线查询的结果调整了子弹的距离上限参数。
3. 怪物的激活、攻击、动作控制原理,你所遇到的第一个怪物KamikazeBuzzer的攻击特效的实现原理
[第1个KamikazeBuzzer]
SimpleBuzzers7
EnemyArea.js
Box Collider
KamikazeBuzzer
KamikazeMovementMotor.js
BuzzerKamikazeControllerAndAi.js
DestroyObject.js
Health.js
AudioSource
[第1个KamikazeBuzzer]
SimpleBuzzers7
EnemyArea.js
Box Collider
KamikazeBuzzer
KamikazeMovementMotor.js
BuzzerKamikazeControllerAndAi.js
DestroyObject.js
Health.js
AudioSource
[外形]
buzzer_bot
buzzer_bot
[动作]
这个怪物的mesh没动作。
这个怪物的mesh没动作。
[激活]
EnemyArea.js
function OnTriggerEnter (other : Collider) {
if (other.tag == “Player”)
ActivateAffected (true);
角进入 Box Collider 的范围时,会触发OnTriggerEnter,
这时会将SimpleBuzzers7的子对象KamikazeBuzzer设置为激活的。挂载到KamikazeBuzzer对象上的脚本组件也就可以开始执行了。
EnemyArea.js
function OnTriggerEnter (other : Collider) {
if (other.tag == “Player”)
ActivateAffected (true);
角进入 Box Collider 的范围时,会触发OnTriggerEnter,
这时会将SimpleBuzzers7的子对象KamikazeBuzzer设置为激活的。挂载到KamikazeBuzzer对象上的脚本组件也就可以开始执行了。
[移动]
KamikazeMovementMotor.js
该脚本控制KamikazeBuzzer的刚体属性,根据参数和一定的计算规则计算出力,作用于刚体,让KamikazeBuzzer动起来,类似于Player的移动原理。
KamikazeMovementMotor.js
该脚本控制KamikazeBuzzer的刚体属性,根据参数和一定的计算规则计算出力,作用于刚体,让KamikazeBuzzer动起来,类似于Player的移动原理。
BuzzerKamikazeControllerAndAi.js
该脚本根据怪物和Player之间的位置关系,按一定计算规则算出KamikazeMovementMotor需要的参数,从而达到控制其移动的目的。
该脚本根据怪物和Player之间的位置关系,按一定计算规则算出KamikazeMovementMotor需要的参数,从而达到控制其移动的目的。
direction = (player.position – character.position);
因为方向总是朝着player,所以看起来就有个“追击”的效果。
因为方向总是朝着player,所以看起来就有个“追击”的效果。
rechargeTimer < 0.0f && threatRange && Vector3.Dot (character.forward, direction) > 0.8f
这个判断达到了“追过头”的效果。
这个判断达到了“追过头”的效果。
[攻击特效]
当移动流程里“追到了”条件达成后,主要调用DoElectricArc函数来表达攻击方式。
当移动流程里“追到了”条件达成后,主要调用DoElectricArc函数来表达攻击方式。
zapNoise = Vector3 (Random.Range (-1.0f, 1.0f), 0.0f, Random.Range(-1.0f, 1.0f)) * 0.5f;
zapNoise = ation * zapNoise;
这里有些小随机,是为了让每次电到Player的位置不一样。
zapNoise = ation * zapNoise;
这里有些小随机,是为了让每次电到Player的位置不一样。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论