位置: 编程技术 - 正文

手游开发攻防——二、基础篇(手游开发流程具体)

编辑:rootadmin

推荐整理分享手游开发攻防——二、基础篇(手游开发流程具体),希望有所帮助,仅作参考,欢迎阅读内容。

文章相关热门搜索词:手游开发游戏开发,手游怎么攻击,手机攻防类游戏,手游怎么攻击,手游开发引擎有哪些,手游开发技术,手游开发游戏开发,手游开发工具,内容如对您有帮助,希望把文章链接给更多的朋友!

Store上面的例子“Unity Projects Stealth”来讲解Unity的一些知识。所以可能你要对Unity一些概念有个了解。另外,这个例子"Unity Projects Stealth.unitypackage"我已经分享到 :Window 7 X

Unity:4.3.3f1

1、项目大致了解:

我们打开unity,新建项目,然后导入上面下载的"Unity Projects Stealth.unitypackage"。等导入完毕后就可以看到“Project”视图(如果你没打开,可以从菜单栏“Window”-“Project”打开)里面的结构如下:

里面每个目录大家都可以随便打开看看:

Animations 一些角色动画Animator 动画控制器,实际里面啥都没有-_-Audio音频文件Fonts字体Gizmos在scene视图用于调试的一些图标Materials材质Models模型Prefabs预设Scenes场景文件,实际里面啥都没有-_-Scripts脚本,实际里面啥都没有-_-Shaders着色器Textures一些贴图纹理Done这里面才包含了上面缺少的动画控制器、场景文件和脚本〜·

大家平常做项目也可以参考上面的做法,对不同的资源放不同目录进行归类整理,而且大家一看到这些文件夹就都知道里面有什么资源。可以减少很多沟通成本,也为自己查找资源带来便利。

好了,如果没什么问题,点击播放按钮,应该就能运行项目了。

使用键盘的WASD或上下左右箭头就可以控制人物走动了。屏幕左下方也有文本给出其它操作的按键。比如"Z"可以打开开关等。

2、碰撞器基础

一般情况下,你想让物体直接有体积,能产生碰撞,那么你需要给这个物体增加一个碰撞器“Collider”。在Unity中,你只需要选择一个对象,然后点击菜单栏的”Component“-“Physics”,里面就有各种各样的碰撞器,你根据自己模型选择一个比较接近的就OK了。Unity里面的碰撞器区分为两种,一种为静态的,另一种动态的。

静态的意思是这个碰撞器在游戏过程中不会发生位移、旋转、缩放。

动态的意思则是说这个碰撞器可能会在游戏过程中发生位移、旋转、缩放。动态碰撞器有两种:

一种是CharacterController。

另一种是普通的碰撞器&#;刚体组件。

第二种一定要增加刚体组件。不然可能会导致碰撞失效、性能开销增加。(比如Unity一个UI组件NGUI,它新版本的Panel会检测当前对象是否带了Rigibody,如果没有则自动增加一个。就是为了防止开发者做一些界面动画,忘记修改添加Rigibody,导致UI按钮点击失效。)

3、控制角色

控制一个角色在场景中运动,最简单的做法是把一个对象拖到场景中,然后根据按键。设置这个对象transform的position&#;。这样对象就可以在场景中运动起来。

获取按键可以用Input.GetAxis方法。获取X和Z轴的按键分别可以使用:Input.GetAxis("Horizontal") 和 Input.GetAxis("Vertical")方法。“Horizontal”和“Vertical”其实是在“Edit”-“Project Settings”-“Input”里面配置的。

那么我们这个例子是使用什么方法来让玩家角色运动的呢?

先在“Hierarchy”视图(如果没有这个视图,可以在“Window”-“Hierarchy”打开它)中找到“char_ethan”对象,选中它。

我们先看看它的“Inspector”视图(如果没有可以在“Window”-“Inspector”打开它)。

注意这里除了有基本的Transform组件外,还有“Animator”,“Capsule Collider”,“Rigidbody”组件。这三个组件其实用于做可移动物体其实是黄金组合(当然也有用“Character Controller”的)。

还有“Audio Source”、“Audio Listener”两个分别是播放声音和监听声音的组件。

然后"Done Player Health (Script)","Done Player Inventory (Script)","Done Player Movement (Script)"分别是对角色做控制的脚本。下面我们简单说说每个组件的用途:

A、Animator

其实是Unity内置的一个动画控制器,原理是状态机。比如双击上面的“Animator”面板里面的“Controller”属性

就会出现状态机的编辑窗口

B、Capsule Collider

胶囊体碰撞器。比较简单,没什么好说的。

C、Rigidbody

刚刚上面也说了,动态碰撞器的需要。

关于A、B、C这几个组件上面就只简单介绍到这里,这里不展开讨论。如果有需要,我后面会再单独写文章介绍。

下面开始看看控制代码部分。

D、DonePlayerMovement

这里有个“DonePlayerMovement(Script)”组件,从名字上看就知道应该是控制移动的(所以说命名很重要)。我们打开这个组件的源码,看看。

[csharp] view plaincopyusing UnityEngine; using System.Collections; public class DonePlayerMovement : MonoBehaviour { public AudioClip shoutingClip; // 玩家大喊的声音 public float turnSmoothing = f; // 用于玩家平滑转向的&#; public float speedDampTime = 0.1f; // 用于控制从一个&#;变化到另一个的时间限制 private Animator anim; private DoneHashIDs hash; // 保存各种动画状态的hash void Awake () { anim = GetComponent<Animator>(); hash = GameObject.FindGameObjectWithTag(DoneTags.gameController).GetComponent<DoneHashIDs>(); // Set the weight of the shouting layer to 1. anim.SetLayerWeight(1, 1f); } void FixedUpdate () { // Cache the inputs. float h = Input.GetAxis("Horizontal"); float v = Input.GetAxis("Vertical"); bool sneak = Input.GetButton("Sneak"); MovementManagement(h, v, sneak); } void Update () { // Cache the attention attracting input. bool shout = Input.GetButtonDown("Attract"); // Set the animator shouting parameter. anim.SetBool(hash.shoutingBool, shout); AudioManagement(shout); } void MovementManagement (float horizontal, float vertical, bool sneaking) { // Set the sneaking parameter to the sneak input. anim.SetBool(hash.sneakingBool, sneaking); // If there is some axis input... if(horizontal != 0f || vertical != 0f) { // ... set the players rotation and set the speed parameter to 5.5f. Rotating(horizontal, vertical); anim.SetFloat(hash.speedFloat, 5.5f, speedDampTime, Time.deltaTime); } else // Otherwise set the speed parameter to 0. anim.SetFloat(hash.speedFloat, 0); } void Rotating (float horizontal, float vertical) { // Create a new vector of the horizontal and vertical inputs. Vector3 targetDirection = new Vector3(horizontal, 0f, vertical); // Create a rotation based on this new vector assuming that up is the global y axis. Quaternion targetRotation = Quaternion.LookRotation(targetDirection, Vector3.up); // Create a rotation that is an increment closer to the target rotation from the player's rotation. Quaternion newRotation = Quaternion.Lerp(rigidbody.rotation, targetRotation, turnSmoothing * Time.deltaTime); // Change the players rotation to this new rotation. rigidbody.MoveRotation(newRotation); } void AudioManagement (bool shout) { // If the player is currently in the run state... if(anim.GetCurrentAnimatorStateInfo(0).nameHash == hash.locomotionState) { // ... and if the footsteps are not playing... if(!audio.isPlaying) // ... play them. audio.Play(); } else // Otherwise stop the footsteps. audio.Stop(); // If the shout input has been pressed... if(shout) // ... play the shouting clip where we are. AudioSource.PlayClipAtPoint(shoutingClip, transform.position); } } 这个脚本整体来说:

在Awake的时候,先找到脚本需要用到的Animator组件和DoneHashIds组件,然后缓存它们(在后面用到就不需要频繁查找了,节省CPU)。

在FixedUpdate的时候,获取玩家按下的移动键,并处理移动、角色朝向问题。

在Update的时候,获取玩家是否按下“Attract”键,并确定是否播放动画以及声音。

OK,我们主要来看看MovementManagement这个方法:

[csharp] view plaincopyvoid MovementManagement (float horizontal, float vertical, bool sneaking) { // Set the sneaking parameter to the sneak input. anim.SetBool(hash.sneakingBool, sneaking); // If there is some axis input... if(horizontal != 0f || vertical != 0f) { // ... set the players rotation and set the speed parameter to 5.5f. Rotating(horizontal, vertical); anim.SetFloat(hash.speedFloat, 5.5f, speedDampTime, Time.deltaTime); } else // Otherwise set the speed parameter to 0. anim.SetFloat(hash.speedFloat, 0); } 我们可以发现,除了Rotating方法,基本都是对anim做一些状态设置的方法。那么Rotating方法里面是什么呢?[csharp] view plaincopyvoid Rotating (float horizontal, float vertical) { // 创建一个Vector3用于保存输入的水平位移方向 Vector3 targetDirection = new Vector3(horizontal, 0f, vertical); // 根据上面的方向计算这个方向指向的角度 Quaternion targetRotation = Quaternion.LookRotation(targetDirection, Vector3.up); // 创建一个从玩家当前方向旋转到目标方向的旋转增量 Quaternion newRotation = Quaternion.Lerp(rigidbody.rotation, targetRotation, turnSmoothing * Time.deltaTime); // 修改玩家的方向 rigidbody.MoveRotation(newRotation); } 手游开发攻防——二、基础篇(手游开发流程具体)

上面代码其实看出,他也没有实现玩家的位置移动。那是在其它组件里面实现的还是什么其它方式处理的呢?且听下回分解。

---------------------------------------------------------------------------------------------------------------------

好的,这次距离上次时间有点久,本来说一周更新一次,但实在没办法。在公司客户端、服务端都要搞,而且战斗系统重新重构优化,也花了不少时间。望见谅。

我们接上次的话题:代码没有实现玩家的位置移动的代码,但是实际运行的时候却可以看到角色会移动。这是为什么?

我们一起看到“Hierarchy”视图中的“char_ethan”。注意里面的Animator组件(它勾选了“Apply Root Motion”):

我们看到官方文档对于这个选项是这么介绍的:

Root motion is the effect where an object's entire mesh moves away from its starting point but that motion is created by the animation itself rather than by changing the Transform position.

这里大致的意思其实就是:这个选项会在由动画产生移动时,使得物体的mesh会偏离起点。

那也就是物体的移动是做在动画中的,然后勾选Animator的ApplyRootMotion选项来实现的。

关于角色控制中anim.SetFloat的方法的。我们在这里暂时不讲。这个涉及到动画混合,以后会专门讲。

那么角色的移动、旋转就都讲了。接下来我们看看碰撞器。

4、碰撞器及其使用

前面我们说了碰撞器分静态和动态两种。

那么角色“char_ethan”身上的肯定就是动态碰撞器了,注意到“char_ethan”对象有个“Capsule Collider”组件。这就是一个胶囊体碰撞器组件。然后角色还有个“rigidbody”组件,这个是刚体组件。也就是采用前面说的普通碰撞器&#;刚体组件。

关于各种内置碰撞器的知识。大家不了解的可以看看官网 Trigger”

只要两个相互碰撞的碰撞器,有任何一个勾选了IsTrigger,那么就会触发这两个碰撞器上面的OnTriggerEnter方法。

如果两个相互碰撞的碰撞器,都没勾选IsTrigger,那么会触发这两个碰撞器上面的OnCollisionEnter,OnCollisionStay,OnCollisionExit方法。

只有在不勾选IsTrigger的时候,才能使用Physics.Raycast进行射线检测。

所以当你的碰撞方法没有调用的时候,请确保IsTrigger和方法是符合的。

注意:这个游戏中玩家走路的时候不会穿墙,因为墙的碰撞器和玩家身上的碰撞器都没有勾选IsTrigger。

5、怪物AI

在“Hierarchy”视图中找到三个怪物“char_robotGuard_”、“char_robotGuard_”、“char_robotGuard_”,这三个怪物上面的组件其实都是一样的。我们来看看“char_robotGuard_”就可以了。看看怪物是怎么巡逻、然后发现敌人进行追踪的。

怪物身上有如下组件:

A、Animator这里不再赘述。B、碰撞器

两个碰撞器?为什么这里要用两个碰撞器?我们先看看两个碰撞器的属性:

一个是胶囊体,没有勾选IsTrigger。这个应该是配合走路,为了不让角色穿过建筑物的。

第二个是球体碰撞器,勾选了IsTrigger。干嘛用的,别急,我们还是先来看那4个脚本吧。

C、DoneEnemySight

二话不说我们先上代码

[csharp] view plaincopyusing UnityEngine; using System.Collections; public class DoneEnemySight : MonoBehaviour { public float fieldOfViewAngle = f; // Number of degrees, centred on forward, for the enemy see. public bool playerInSight; // Whether or not the player is currently sighted. public Vector3 personalLastSighting; // Last place this enemy spotted the player. private NavMeshAgent nav; // Reference to the NavMeshAgent component. private SphereCollider col; // Reference to the sphere collider trigger component. private Animator anim; // Reference to the Animator. private DoneLastPlayerSighting lastPlayerSighting; // Reference to last global sighting of the player. private GameObject player; // Reference to the player. private Animator playerAnim; // Reference to the player's animator component. private DonePlayerHealth playerHealth; // Reference to the player's health script. private DoneHashIDs hash; // Reference to the HashIDs. private Vector3 previousSighting; // Where the player was sighted last frame. void Awake () { // Setting up the references. nav = GetComponent<NavMeshAgent>(); col = GetComponent<SphereCollider>(); anim = GetComponent<Animator>(); lastPlayerSighting = GameObject.FindGameObjectWithTag(DoneTags.gameController).GetComponent<DoneLastPlayerSighting>(); player = GameObject.FindGameObjectWithTag(DoneTags.player); playerAnim = player.GetComponent<Animator>(); playerHealth = player.GetComponent<DonePlayerHealth>(); hash = GameObject.FindGameObjectWithTag(DoneTags.gameController).GetComponent<DoneHashIDs>(); // Set the personal sighting and the previous sighting to the reset position. personalLastSighting = lastPlayerSighting.resetPosition; previousSighting = lastPlayerSighting.resetPosition; } void Update () { // If the last global sighting of the player has changed... if(lastPlayerSighting.position != previousSighting) // ... then update the personal sighting to be the same as the global sighting. personalLastSighting = lastPlayerSighting.position; // Set the previous sighting to the be the sighting from this frame. previousSighting = lastPlayerSighting.position; // If the player is alive... if(playerHealth.health > 0f) // ... set the animator parameter to whether the player is in sight or not. anim.SetBool(hash.playerInSightBool, playerInSight); else // ... set the animator parameter to false. anim.SetBool(hash.playerInSightBool, false); } void OnTriggerStay (Collider other) { // If the player has entered the trigger sphere... if(other.gameObject == player) { // By default the player is not in sight. playerInSight = false; // Create a vector from the enemy to the player and store the angle between it and forward. Vector3 direction = other.transform.position - transform.position; float angle = Vector3.Angle(direction, transform.forward); // If the angle between forward and where the player is, is less than half the angle of view... if(angle < fieldOfViewAngle * 0.5f) { RaycastHit hit; // ... and if a raycast towards the player hits something... if(Physics.Raycast(transform.position &#; transform.up, direction.normalized, out hit, col.radius)) { // ... and if the raycast hits the player... if(hit.collider.gameObject == player) { // ... the player is in sight. playerInSight = true; // Set the last global sighting is the players current position. lastPlayerSighting.position = player.transform.position; } } } // Store the name hashes of the current states. int playerLayerZeroStateHash = playerAnim.GetCurrentAnimatorStateInfo(0).nameHash; int playerLayerOneStateHash = playerAnim.GetCurrentAnimatorStateInfo(1).nameHash; // If the player is running or is attracting attention... if(playerLayerZeroStateHash == hash.locomotionState || playerLayerOneStateHash == hash.shoutState) { // ... and if the player is within hearing range... if(CalculatePathLength(player.transform.position) <= col.radius) // ... set the last personal sighting of the player to the player's current position. personalLastSighting = player.transform.position; } } } void OnTriggerExit (Collider other) { // If the player leaves the trigger zone... if(other.gameObject == player) // ... the player is not in sight. playerInSight = false; } float CalculatePathLength (Vector3 targetPosition) { // Create a path and set it based on a target position. NavMeshPath path = new NavMeshPath(); if(nav.enabled) nav.CalculatePath(targetPosition, path); // Create an array of points which is the length of the number of corners in the path &#; 2. Vector3 [] allWayPoints = new Vector3[path.corners.Length &#; 2]; // The first point is the enemy's position. allWayPoints[0] = transform.position; // The last point is the target position. allWayPoints[allWayPoints.Length - 1] = targetPosition; // The points inbetween are the corners of the path. for(int i = 0; i < path.corners.Length; i&#;&#;) { allWayPoints[i &#; 1] = path.corners[i]; } // Create a float to store the path length that is by default 0. float pathLength = 0; // Increment the path length by an amount equal to the distance between each waypoint and the next. for(int i = 0; i < allWayPoints.Length - 1; i&#;&#;) { pathLength &#;= Vector3.Distance(allWayPoints[i], allWayPoints[i &#; 1]); } return pathLength; } } Awake主要是获取一些组件,在后面的逻辑中可以使用。Update检查玩家最后一次被发现的位置是否一致,不是则同步位置。把playerInSight(是否发现玩家)这个状态设置到Animator的状态机里面。OnTriggerStay没错,前面说到的第二个碰撞器正是这里要用到的。第二个碰撞器勾选了IsTrigger,就是为了触发这个方法。当玩家进入这个碰撞器(也就是怪物的视野)触发。检查玩家是否在怪物前方,而且玩家和怪物中间没有遮挡物。那么设置playerInSight为true。OnTriggerExit当玩家离开怪物的视野(碰撞器)。那么设置playerInSight为false。CalculatePathLength计算怪物和玩家当前的距离。基本这个组件就是用于检测玩家是否在怪物视野内。然后设置玩家的一些位置信息,以及是否被发现的信息。

我们接着看DoneEnemyAI这个组件

D、DoneEnemyAI

还是上代码:

[csharp] view plaincopyusing UnityEngine; using System.Collections; public class DoneEnemyAI : MonoBehaviour { public float patrolSpeed = 2f; // The nav mesh agent's speed when patrolling. public float chaseSpeed = 5f; // The nav mesh agent's speed when chasing. public float chaseWaitTime = 5f; // The amount of time to wait when the last sighting is reached. public float patrolWaitTime = 1f; // The amount of time to wait when the patrol way point is reached. public Transform[] patrolWayPoints; // An array of transforms for the patrol route. private DoneEnemySight enemySight; // Reference to the EnemySight script. private NavMeshAgent nav; // Reference to the nav mesh agent. private Transform player; // Reference to the player's transform. private DonePlayerHealth playerHealth; // Reference to the PlayerHealth script. private DoneLastPlayerSighting lastPlayerSighting; // Reference to the last global sighting of the player. private float chaseTimer; // A timer for the chaseWaitTime. private float patrolTimer; // A timer for the patrolWaitTime. private int wayPointIndex; // A counter for the way point array. void Awake () { // Setting up the references. enemySight = GetComponent<DoneEnemySight>(); nav = GetComponent<NavMeshAgent>(); player = GameObject.FindGameObjectWithTag(DoneTags.player).transform; playerHealth = player.GetComponent<DonePlayerHealth>(); lastPlayerSighting = GameObject.FindGameObjectWithTag(DoneTags.gameController).GetComponent<DoneLastPlayerSighting>(); } void Update () { // If the player is in sight and is alive... if(enemySight.playerInSight && playerHealth.health > 0f) // ... shoot. Shooting(); // If the player has been sighted and isn't dead... else if(enemySight.personalLastSighting != lastPlayerSighting.resetPosition && playerHealth.health > 0f) // ... chase. Chasing(); // Otherwise... else // ... patrol. Patrolling(); } void Shooting () { // Stop the enemy where it is. nav.Stop(); } void Chasing () { // Create a vector from the enemy to the last sighting of the player. Vector3 sightingDeltaPos = enemySight.personalLastSighting - transform.position; // If the the last personal sighting of the player is not close... if(sightingDeltaPos.sqrMagnitude > 4f) // ... set the destination for the NavMeshAgent to the last personal sighting of the player. nav.destination = enemySight.personalLastSighting; // Set the appropriate speed for the NavMeshAgent. nav.speed = chaseSpeed; // If near the last personal sighting... if(nav.remainingDistance < nav.stoppingDistance) { // ... increment the timer. chaseTimer &#;= Time.deltaTime; // If the timer exceeds the wait time... if(chaseTimer >= chaseWaitTime) { // ... reset last global sighting, the last personal sighting and the timer. lastPlayerSighting.position = lastPlayerSighting.resetPosition; enemySight.personalLastSighting = lastPlayerSighting.resetPosition; chaseTimer = 0f; } } else // If not near the last sighting personal sighting of the player, reset the timer. chaseTimer = 0f; } void Patrolling () { // Set an appropriate speed for the NavMeshAgent. nav.speed = patrolSpeed; // If near the next waypoint or there is no destination... if(nav.destination == lastPlayerSighting.resetPosition || nav.remainingDistance < nav.stoppingDistance) { // ... increment the timer. patrolTimer &#;= Time.deltaTime; // If the timer exceeds the wait time... if(patrolTimer >= patrolWaitTime) { // ... increment the wayPointIndex. if(wayPointIndex == patrolWayPoints.Length - 1) wayPointIndex = 0; else wayPointIndex&#;&#;; // Reset the timer. patrolTimer = 0; } } else // If not near a destination, reset the timer. patrolTimer = 0; // Set the destination to the patrolWayPoint. nav.destination = patrolWayPoints[wayPointIndex].position; } } 这个组件主要是:如果判断玩家在视野中,则对玩家进行射击。如果玩家最后一次被发现的点不是默认点,则进行追击。否则就进行巡逻。

追击、巡逻,主要都是对NavMeshAgent进行设置来实现控制怪物的移动。关于NavMeshAgent我们以后再专门来讲解。

而射击调用的Shooting方法里面只有一行代码:nav.Stop。那么射击具体在哪里实现的?接着看DoneEnemyShooting组件。

E、DoneEnemyShooting

你知道我肯定会说,看代码。。。没错:

[csharp] view plaincopyusing UnityEngine; using System.Collections; public class DoneEnemyShooting : MonoBehaviour { public float maximumDamage = f; // The maximum potential damage per shot. public float minimumDamage = f; // The minimum potential damage per shot. public AudioClip shotClip; // An audio clip to play when a shot happens. public float flashIntensity = 3f; // The intensity of the light when the shot happens. public float fadeSpeed = f; // How fast the light will fade after the shot. private Animator anim; // Reference to the animator. private DoneHashIDs hash; // Reference to the HashIDs script. private LineRenderer laserShotLine; // Reference to the laser shot line renderer. private Light laserShotLight; // Reference to the laser shot light. private SphereCollider col; // Reference to the sphere collider. private Transform player; // Reference to the player's transform. private DonePlayerHealth playerHealth; // Reference to the player's health. private bool shooting; // A bool to say whether or not the enemy is currently shooting. private float scaledDamage; // Amount of damage that is scaled by the distance from the player. void Awake () { // Setting up the references. anim = GetComponent<Animator>(); laserShotLine = GetComponentInChildren<LineRenderer>(); laserShotLight = laserShotLine.gameObject.light; col = GetComponent<SphereCollider>(); player = GameObject.FindGameObjectWithTag(DoneTags.player).transform; playerHealth = player.gameObject.GetComponent<DonePlayerHealth>(); hash = GameObject.FindGameObjectWithTag(DoneTags.gameController).GetComponent<DoneHashIDs>(); // The line renderer and light are off to start. laserShotLine.enabled = false; laserShotLight.intensity = 0f; // The scaledDamage is the difference between the maximum and the minimum damage. scaledDamage = maximumDamage - minimumDamage; } void Update () { // Cache the current value of the shot curve. float shot = anim.GetFloat(hash.shotFloat); // If the shot curve is peaking and the enemy is not currently shooting... if(shot > 0.5f && !shooting) // ... shoot Shoot(); // If the shot curve is no longer peaking... if(shot < 0.5f) { // ... the enemy is no longer shooting and disable the line renderer. shooting = false; laserShotLine.enabled = false; } // Fade the light out. laserShotLight.intensity = Mathf.Lerp(laserShotLight.intensity, 0f, fadeSpeed * Time.deltaTime); } void OnAnimatorIK (int layerIndex) { // Cache the current value of the AimWeight curve. float aimWeight = anim.GetFloat(hash.aimWeightFloat); // Set the IK position of the right hand to the player's centre. anim.SetIKPosition(AvatarIKGoal.RightHand, player.position &#; Vector3.up * 1.5f); // Set the weight of the IK compared to animation to that of the curve. anim.SetIKPositionWeight(AvatarIKGoal.RightHand, aimWeight); } void Shoot () { // The enemy is shooting. shooting = true; // The fractional distance from the player, 1 is next to the player, 0 is the player is at the extent of the sphere collider. float fractionalDistance = (col.radius - Vector3.Distance(transform.position, player.position)) / col.radius; // The damage is the scaled damage, scaled by the fractional distance, plus the minimum damage. float damage = scaledDamage * fractionalDistance &#; minimumDamage; // The player takes damage. playerHealth.TakeDamage(damage); // Display the shot effects. ShotEffects(); } void ShotEffects () { // Set the initial position of the line renderer to the position of the muzzle. laserShotLine.SetPosition(0, laserShotLine.transform.position); // Set the end position of the player's centre of mass. laserShotLine.SetPosition(1, player.position &#; Vector3.up * 1.5f); // Turn on the line renderer. laserShotLine.enabled = true; // Make the light flash. laserShotLight.intensity = flashIntensity; // Play the gun shot clip at the position of the muzzle flare. AudioSource.PlayClipAtPoint(shotClip, laserShotLight.transform.position); } } Update的时候可以发现它检查动画的shotFloat属性。然后判断是否应该射击或取消射击。

而射击的激光,这里可以看到是采用LineRenderer实现的。

6、事件通知

以上关于怪物的AI我们就讲完了。但这只是单个怪物的AI。实际这个游戏里面还有用于监控玩家的摄像头、还有红外线墙检测玩家是否通过。但是原理都一样。都是通过监听碰撞器的OnTriggerStay来设置玩家被发现的全局位置。也就是DoneLastPlayerSighting。

个人关于这个demo代码设计的看法:

我个人觉得这种做法不好。大家都自己去获取DoneLastPlayerSighting,然后自己检查里面的状态。然后还可以设置里面的状态。这样会导致程序变量跟踪困难。

如果是我的话,我会把DoneLastPlayerSighting做成是一个消息中心。然后每个敌人订阅这个消息中心的消息(即玩家被发现的位置发生变化的消息),然后由消息中心通知所有订阅的对象。然后订阅的对象也可以提交自己发现的目标给消息中心,如果消息中心确认通过,再通知已经订阅的其它对象。这样做起来代码会更清晰,更容易维护。

下一篇我会专门来讲关于这部分代码的一些优化。

7、渲染特效A、雾效

你可以通过点击Unity的菜单栏“Edit”-“Render Settings”。这时候在右边的Inspector面板可以通过勾选“fog”选项来开启雾效。“Fog Color”可以调整雾的颜色。

B、游戏中监视器投射到地上的亮点

在“Hiererchy”中找到监视器物体“prop_cctvCam_”,你会发现它下面有个子物体“cam_frustum_collision”,它上面有个Light组件,Light上的Cookie属性就是产生亮点的贴图。

C、激光墙的激光

其实就是用了一个"Self-Illumin/Diffuse"的shader,这是一个自发光shader,你可以尝试一下,在相同的灯光条件下,采用自发光shader的物体总会比Diffuse的亮。

Unity读取点云数据 在Unity商店找到一个免费插件PointCloudFreeViewer可以加载xyz、xyzrgba式的点云数据。收费版PointCloudViewerandTools

Unity之2D游戏界面相机适配 之前写的关于2D游戏界面相机的水平、竖直、以人为中心三种模式的相机控制,我写着写着,总感觉相机适配有点繁琐,而且适配得不怎么好,多平台的

Unity3D 4.6.3之浅拷贝与深拷贝 今天在做一个项目时,遇到了不小的麻烦,将存于数组中的一个游戏对象从数组中删除时,游戏物体仍显示在场景中,要么用destroy销毁时报错,最后想

标签: 手游开发流程具体

本文链接地址:https://www.jiuchutong.com/biancheng/381487.html 转载请保留说明!

上一篇:Unity5.0更新列表(unity5.4.0)

下一篇:Unity读取点云数据(unity获取鼠标点击的位置)

  • 独资企业赚的钱全是老板的吗
  • 哪些资产损失应向税务机关申报扣除?
  • 个人出租商用房税率
  • 政府补助是否可以加计抵扣
  • 股权变更涉及哪些税种
  • 电子发票有使用期限吗
  • 工资中事假扣款执行依据
  • 房地产企业预缴土地增值税
  • 分支机构需要填报企业所得税纳税申报表吗
  • 商品周转天数和周转率
  • 房屋租赁发票需要备注吗
  • 小规模纳税人开具增值税专用发票
  • 开票资料地址填营业执照
  • 软件企业超税负率怎么算
  • mac鼠标移动到角落立刻显示桌面
  • 2020 php 薪资
  • 社保挂靠会计处理
  • 预付下年度广告费会计分录
  • 国地税合并有什么角度写论文
  • 其他应付款余额在借方表示什么意思
  • 金融企业贷款损失准备金计提比例
  • 免费镜像翻转软件
  • 小规模纳税人减半征收的六税两费
  • 制造费用的归集
  • 微信小程序完整授权
  • nginx解决跨域问题原理
  • ps去水印的三种方法
  • java替换集合的元素
  • 将织梦dedecms转换到wordpress
  • 保险公司工伤怎么赔付
  • 固定资产投资额在财务报表中怎么体现
  • 数据库双亲结点
  • sql server 2012安装无网络可以OK?
  • 采用公允价值模式计量的投资性房地产处置
  • 如何开具污水处理证明
  • 运输费计入什么科目分录
  • 明细与发票
  • 住宿发票 抵扣
  • 房地产企业账务处理所用科目
  • 营业税金及附加怎么计算
  • 一般情况下银行加权平均成本的变化主要取决于什么因素
  • 未开票收入账务处理分录
  • 原材料的采购成本怎么计算
  • 合并报表编制方法有几种
  • 软件开发费属于研发和技术服务还是信息技术服务
  • 红字发票填开说明是?
  • 中央空调的维护费一年要多少钱?
  • 从会计角度看会计刺客
  • 建账有哪些步骤,每个步骤有哪些注意事项
  • mysql服务1053
  • 查找星期几
  • sql server数据库使用
  • mysql的随机函数
  • 该程序无法正常启动
  • win7系统安装完后启动不了
  • solaris命令大全
  • 昂达主板插线安装图解
  • 如何在苹果电脑上下载软件
  • windows10使用说明
  • win8如何使用
  • bootstrap下拉框设置默认值
  • BootStrap glyphicons 字体图标实现方法
  • jquery.js
  • android加载dex
  • 安卓图片缓存太占空间
  • ugy与ngy
  • 安卓全局替换
  • js设计模型
  • 怎么用python画图具体步骤
  • 国家税务总局会议管理办法
  • 欠税款要坐牢吗
  • 国家税务局查验发票显示网络异常
  • 小规模纳税人增值税优惠政策2024
  • 土地增值税清算退税涉及企业所得税补缴
  • 广东省国家税务局电子发票系统,网络设置
  • 车辆购置税查询不到
  • 车辆营运证去哪里审
  • 欠税多久法院立案
  • 2022年广州社保基数
  • 福建是高原还是平原
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号

    网站地图: 企业信息 工商信息 财税知识 网络常识 编程技术

    友情链接: 武汉网站建设