跪求一个2d横版射击游戏戏 游戏名字是英文

7811人阅读
Unity3D(6)
这个教程是参考一个YouTube上面的教程做的,原作者的教程做得比较简单,我先参考着做一遍,毕竟我也只是个初学者,还没办法完全自制哈哈。不过我之前也看过一个2D平台游戏的系列教程了,以后会整合起来,做出一个类似冒险岛那样的游戏。
原视频链接:& &这是个YouTube视频链接,如果可以“友情访问外网”的朋友可以自己看看。视频全英无字幕,如果看起来有压力的话那其实也可以不看。反正我已经将里面的代码都整理在这里了。
原本我是打算把这篇东西整理到自己的qq空间的,不过发空间很麻烦,就算了,新浪博客也不方便,那就用CSDN吧,以后我的Unity学习笔记都会整理到这个博客里面。谈不上是教程,只能说是自己研究过程的一些整理。欢迎大家一起交流,一起进步,如果这篇笔记可以帮到更多的朋友学习Unity,那就最好不过了。
好了,废话不多说,马上开始。这次是我尝试着制作一个类似魂斗罗和超级玛丽那样的横版游戏,以后学得比较多了会整理更复杂的教程。
一、角色移动
打开,我用的是的版本,选择创建游戏或者游戏其实都可以,我喜欢创建的,这样方便一些。
修改摄像机的视图,改成正交视图。(才需要改,不用改,可以在两种视图模式下进行切换,比好些。)
(projection里面,即是正交视图,在情况下默认是,即透视视图,则默认为)
改完orthographic之后,可以修改,在正交视图里面决定画面的大小,越大视野的范围越大,每件物体相应的可见面积就会缩小。我把设为。如果觉得画面视野还不够大的话可以把改得更小,比如、这样。
刚开始的时候,画面会比较暗,有两种方法可以处理,第一种方法是在游戏场景中放置光源。
如上图,有好几种light可以选择。
另一种方法是在的里面进行设置
如图,修改Ambient&Light(环境光)的颜色,默认是深灰色,可以调成浅灰色或者白色(白色太亮,不推荐,浅灰色和浅黄色都可以选择,环境光相当于全局光线,如果选择其他颜色,比如紫色或者绿色的话,则会让整个画面变得相当诡异。)
设置完成之后我们就可以开始制作啦:
首先,需要创建一个基本的地形和角色,由于是的平面游戏,所以都使用→里面的即可。(原视频教程里面是用quad的,不过我想以后可能会换成其他的)
我们先创建三个,第一个改名为,第二个就叫做,第三个叫做,然后将拉长,将其的变为,想调得更大也可以。
(这里还多了一个,因为是事后截的图,所以多了一个,后面会用到,这个和前三个物体不同,这个是个空物体。)
接着我们新建几个文件夹,分别是(预设)、(材质)、(脚本)、(场景)、(纹理)。
在菜单栏→里保存当前场景,我命名为。
然后在Materials文件夹里面创建三个,分别命名为、、,将它们调整成自己喜欢的颜色,我将弄成天蓝色,将弄成红色,将弄成土黄色。这个只是个人爱好啦,用于区分这些物体的,可以随便弄。(Floor和Enemy都去掉Mesh
Collider,加上Box Collider,另外我们还要在Enemy身上添加一个Rigidbody组件。)将Player上面的Mesh Collider去掉,加上这两个:
然后创建第一个脚本。这次我全部使用脚本,所以后面有提到的脚本,除非有特别提到是,否则都是。脚本是学习笔记中的重点内容,其他的倒不是特别重要。
(这次用CharacterController组件来控制角色的移动,所以在角色身上去掉的组件,然后添加和组件。)
这个脚本命名为。下面贴出内容:
&&&&(脚本里面的中文都是我自己加进去的注释,可以全部删掉)
using UnityE
  using System.C
  public class Controller2D : MonoBehaviour {
   //引用CharacterController
   CharacterController characterC
   //重力
   public float gravity = 10;
   //水平移动的速度
   public float walkSpeed = 5;
   //弹跳高度
   public float jumpHeight = 5;
   //显示角色当前正受到攻击
   float takenDamage = 0.2f;
   // 控制角色的移动方向
   Vector3 moveDirection = Vector3.
   float horizontal = 0;
   // Use this for initialization
   void Start () {
characterController = GetComponent&CharacterController&();
   // Update is called once per frame
   void Update () {
//控制角色的移动
characterController.Move (moveDirection * Time.deltaTime);
horizontal = Input.GetAxis(&Horizontal&);
//控制角色的重力
moveDirection.y -= gravity * Time.deltaT
//控制角色右移(按d键和右键时)
在这里不直接使用0而是用0.01f是因为使用0之后会持续移动,无法静止
if (horizontal & 0.01f) {
moveDirection.x = horizontal * walkS
//控制角色左移(按a键和左键时)
if (horizontal & 0.01f) {
moveDirection.x = horizontal * walkS
// 弹跳控制
if (characterController.isGrounded) {
if(Input.GetKeyDown(KeyCode.Space)){
moveDirection.y = jumpH
   public IEnumerator TakenDamage(){
renderer.enabled =
yield return new WaitForSeconds(takenDamage);
renderer.enabled =
yield return new WaitForSeconds(takenDamage);
renderer.enabled =
yield return new WaitForSeconds(takenDamage);
renderer.enabled =
yield return new WaitForSeconds(takenDamage);
renderer.enabled =
yield return new WaitForSeconds(takenDamage);
renderer.enabled =
yield return new WaitForSeconds(takenDamage);
这个脚本比较简单,而且都已经做了注释,就不解释了。做完这个脚本之后保存,然后拖拽到身上,角色就可以活动了。后面的这个IEnumerator&TakenDamage是用来控制角色被攻击时进行闪烁的,这个本来是后面的内容,脚本先放这里了,就先提及一下。
二、相机跟随
接下来解决的是角色跳跃和相机跟随的问题。这个问题比较容易。而且由于笔记是我跟着一些视频教程做了几节之后再整理的,所以稍微有点“先进”。上面的脚本里面已经解决了角色的跳跃问题。
// 弹跳控制
if (characterController.isGrounded) {
if(Input.GetKeyDown(KeyCode.Space)){
moveDirection.y = jumpH
很简单,就是上面的这一小段代码。用空格键来进行反重力,当然,是可以随便设置的。为了方便,我又改成了这样:
if (characterController.isGrounded) {
if(Input.GetKeyDown(KeyCode.Space)||Input.GetKeyDown(KeyCode.K)){
moveDirection.y = jumpH
这样就可以用空格键或者键进行跳跃了,平时我自己的习惯是利用键位进行上下左右的移动,用键进行攻击和跳跃。这种按键布局和游戏机的手柄最为相似。
Unity也可以用手柄的,不过这个我还不太会,现在也暂时不用研究这个。
接下来新建一个的脚本。(脚本名字是可以随你喜欢的,不过有时候脚本的名字会影响到调用,所以必须尽量规范。)
using UnityE
  using System.C
  public class Camera2D : MonoBehaviour {
   public T
   public float smoothRate = 0.5f;
   private Transform thisT
   private Vector2
   // Use this for initialization
   void Start () {
thisTransform =
velocity = new Vector2 (0.5f, 0.5f);
   // Update is called once per frame
   void Update () {
Vector2 newPos2D = Vector2.
//Mathf.SmoothDamp平滑阻尼,这个函数用于描述随着时间的推移逐渐改变一个值到期望值,这里用于随着时间的推移(0.5秒)让摄像机跟着角色的移动而移动
newPos2D.x = Mathf.SmoothDamp (thisTransform.position.x, player.position.x, ref velocity.x, smoothRate);
newPos2D.y = Mathf.SmoothDamp (thisTransform.position.y, player.position.y, ref velocity.y, smoothRate);
Vector3 newPos = new Vector3 (newPos2D.x, newPos2D.y, transform.position.z);
//Vector3.Slerp 球形插值,通过t数值在from和to之间插值。返回的向量的长度将被插值到from到to的长度之间。time.time此帧开始的时间(只读)。这是以秒计算到游戏开始的时间。也就是说,从游戏开始到到现在所用的时间。
transform.position = Vector3.Slerp (transform.position, newPos, Time.time);
好了,这个就是摄像机的脚本,这个脚本不采用死死咬着角色不放的方法,而是采取缓慢跟随的效果。摄像机跟随的脚本可以千变万化,有空再研究其他的,这个不要太纠结。
三、游戏控制器和纹理
现在角色会动了,摄像机也可以跟随了。(不要问我为什么场景这么丑,脚本才是关键,场景只需要更换贴图什么的,这个其实在现在来说并不是重点。)接着就是制作一个简单的游戏控制器,也就是前面的GameManager。这个东西这次是用来显示一些纹理内容的,比如在画面上显示角色的生命值,以及当角色死掉之后重新开始等等……好了,现在创建一个空物体,然后命名为GameManager,创建一个同名脚本扔上去,然后打开这个脚本,我们要进行编辑:
  using UnityE
  using System.C
  public class GameManager : MonoBehaviour {
   //Controller2D脚本的参量
   public Controller2D controller2D;
   //角色生命值
   public Texture playersHealthT
   //控制上面那个Teture的屏幕所在位置
   public float screenPositionX;
   public float screenPositionY;
   //控制桌面图标的大小
   public int iconSizeX = 25;
   public int iconSizeY = 25;
   //初始生命值
   public int playersHealth = 3;
   GameO
   //这个地方定义了私有变量player作为一个GameObject,然后用下面的FindGameObjectWithTag获取它,这样的话,在下面的伤害判断时,就可以用player.renderer了。
   void Start(){
player = GameObject.FindGameObjectWithTag(&Player&);
   //OnGUI函数最好不要出现多次,容易造成混乱,所以我把要展示的东西都整合在这个里面
   void OnGUI(){
//控制角色生命值的心的显示
for (int h =0; h & playersH h++) {
GUI.DrawTexture(new Rect(screenPositionX + (h*iconSizeX),screenPositionY,iconSizeX,iconSizeY),playersHealthTexture,ScaleMode.ScaleToFit,true,0);
   void PlayerDamaged(int damage){
//此处使用player.renderer.enabled来进行判断,如果角色没有在闪烁,也就是存在的状态为真,那么才会受到伤害,这样可以避免角色连续受伤,还有另外一种方法是采用计时,这里没有采用那种方法。
if (player.renderer.enabled) {
if (playersHealth & 0) {
playersHealth -=
if (playersHealth &= 0) {
RestartScene ();
   void RestartScene(){
Application.LoadLevel (Application.loadedLevel);
好了,内容已经粘贴出来。有三个地方我认为有必要稍微解释一下,作为备忘的。第一个地方是在函数里面,循环用来画出的个数,这个东西在这里就是我们想要展示出来的角色的生命值(我用爱心表示)
接着用或者画一个透明的爱心。不用很大,我画的是乘以像素的,当然,要画的很大也可以,不过长宽比必须等于一,而且最好保持的次方的大小尺寸,比如乘以,乘以这样的尺寸。记得是弄成透明的,不要保存成白色。的很简单就不说了,在里面保存成透明背景的有点特殊。所以我顺便截一下图:(首先,我们要画完一个爱心……)
然后再点击左下角
的左上角的那个
然后就可以打开当前这幅画的属性:
在透明度那里打勾(默认是没有打勾的),然后用下边的那只滴管笔点击你自己画面中的背景颜色。我这里是白色,所以点击之后就是白色的,然后你的作品的背景颜色是其他颜色的,那么在这里出现的就是其他颜色的。
然后就可以确定了。然后在菜单栏选择“文件”→“另存为”就可以保存透明背景的PNG图片出来了。这个功能还是蛮有意思的,没办法这样直接保存成,需要对背景稍微处理一下。个人觉得,如果只是画像素画的话,确实比有一定的便利性。当然,我看的视频教程里面,那位兄台用的是,其实和差不多,也是可以的,直接创建一张透明背景的图片来画画。
画完我们的爱心之后就导入到Unity的项目里面去,这个很简单,就不多说了。直接拖拽放到文件夹就可以了。重命名为(名字可以随便,只要尽量不使用中文名就行)
在面板修改如下,然后点击。
然后拖拽到GameManager的面板的脚本里面的位置:
运行测试:
成功!现在我们的角色有生命值了哈哈!
(在Game视窗中,我将画面大小调成了是因为我用的是笔记本电脑,对于大部分的笔记本电脑来说,都是这个长宽比的尺寸,如果不是的话可以在那里点击之后选择其他的,比如等等,甚至可以进行自定义,这个就不多说了。)
现在如果我们修改的值,就可以改变运行时在画面上出现的爱心(生命值)的个数,当然,也可以在脚本里面对变量进行直接修改。
由于OnGUI函数是每一帧都进行实时渲染的,所以一旦被敌人攻击造成的伤害,那么函数就会自动减少当前的数量。同理,如果角色因为某些原因增加了,也是一样的。
四、敌人与伤害计算
好了,设置好了,自然就要设置简单的敌人以及伤害计算了。
此处用到的是这个函数:
  void PlayerDamaged(int damage){
//此处使用player.renderer.enabled来进行判断,如果角色没有在闪烁,也就是存在的状态为真,那么才会受到伤害,这样可以避免角色连续受伤,还有另外一种方法是采用计时,这里没有采用那种方法。
if (player.renderer.enabled) {
if (playersHealth & 0) {
playersHealth -=
if (playersHealth &= 0) {
RestartScene ();
这个在上面已经出现过了。
我参考的一个视频教程中并没有if&(player.renderer.enabled)的判断条件,我增加了这个是因为这样可以控制在闪烁的过程中不会再被重复扣血,否则的话,如果我们的撞到了敌人,如果敌人和我们一直在做着相向运动的话,那么我们会不明不白地被扣光然后挂掉的
(这里补充一个小常识:是的缩写,很多人可能误以为是吧,毕竟这个游戏术语就是用来表示生命的,似乎表示健康点也没什么不对。我一开始也是这么想的,后来看了一些教程和资料之后才发现,其实指的是“可以承受的打击点数或次数”,这样的话,用来解释当然是解释不通的。而这个是,就没什么问题,就不解释了。)
接下来我们要来对敌人进行编辑啦。上面已经做好了敌人的物体,我的这个敌人去掉了自带的组件,增添了和(刚体)组件。在里去掉的勾选,在选项里进行这样的设置:
也就是冻结了敌人物体可能造成的不必要的三向旋转(XYZ轴)和Z轴的位移。
接着在Box&Collider里面,我们将打勾,这样的话就变成了一个触发器,可以用于进行碰触判断。然后我们将的值扩大到,如图:
我们这样做的目的是让这个物体的实际碰触区域比它的实际体积大。为什么要这样做呢?其实这个是对Player物体身上的组件的不足的一个补充。
由于Character&Controller组件的碰触范围不是一个方形,而是一个圆形,为了避免物体插入,就必须缩小圆圈,这样的话,如果不放大敌人的碰触区间,就会使得物体在于敌人进行碰撞时,需要和敌人的身体相嵌入近一半才会产生碰触的效果,那样就实在是太核突了……
接着新建一个脚本,然后弄进以下内容:
using UnityE
  using System.C
  public class Enemy2D : MonoBehaviour {
   //GameManager脚本的参量
   public GameManager gameM
   //敌人移动的初始和停止位置,用于控制敌人在一定范围内移动
   float startP
   float endP
   //控制敌人向右移动的一个增量
   public int unitsToMove = 5;
   //敌人移动的速度
   public int moveSpeed = 2;
   //左右移动的布尔值
   bool moveRight =
   void Awake(){
startPos = transform.position.x;
endPos = startPos + unitsToM
   //此处这个Update函数用于控制敌人的左右移动,当向右移动到一定距离后就会反向移动,同理,左移一定距离之后也是。
   void Update(){
if (moveRight) {
rigidbody.position += Vector3.right * moveSpeed * Time.deltaT
if (rigidbody.position.x &= endPos) {
moveRight =
if (moveRight==false) {
rigidbody.position -= Vector3.right * moveSpeed * Time.deltaT
if (rigidbody.position.x &= startPos) {
moveRight =
   int damageValue = 1;
   //这里利用sendmessage函数使得角色与敌人自己碰撞时,发送一个扣血的message到gamemanager函数之中,然后就会在每次碰撞时减掉一滴血。
   void OnTriggerEnter(Collider col){
if (col.gameObject.tag == &Player&) {
gameManager.SendMessage(&PlayerDamaged&,damageValue,SendMessageOptions.DontRequireReceiver);
gameManager.controller2D.SendMessage(&TakenDamage&,SendMessageOptions.DontRequireReceiver);
保存,弄到我们的身上。
现在,利用函数,可以向物体(上面的脚本)发送消息了,第一个发送的消息是“”,这个可以用来使的造成伤害,角色的其实并没有在角色自己身上,而是在物体上。第二个发送的消息是给函数的,这个虽然是在身上,但是上面的已经捆绑了这个,所以同样发送给即可。这个是用来使角色受伤时进行闪烁的,使用的是接口。我们同样用里面的判断角色是否处于闪烁状态。
五、AI移动
上面那个比较复杂的脚本其实已经包含了这一节的内容了。我就只需要把那部分内容简单地复制一下:
void Update(){
if (moveRight) {
rigidbody.position += Vector3.right * moveSpeed * Time.deltaT
if (rigidbody.position.x &= endPos) {
moveRight =
if (moveRight==false) {
rigidbody.position -= Vector3.right * moveSpeed * Time.deltaT
if (rigidbody.position.x &= startPos) {
moveRight =
因为内容比较简单,就不多做解释了。这个教程里面提到的移动其实是非常简单的,以后还会继续深入设计一些比较复杂的。
六、角色攻击
接下来这一讲是关于的。我们创建一个叫做的脚本,扔到我们的身上。然后输入以下内容:
using UnityE
  using System.C
  public class PlayerAttack : MonoBehaviour {
   public Rigidbody bulletP
   // Update is called once per frame
   void Update () {
if (Input.GetKeyDown (KeyCode.J)) {
BulletAttack();
   //按下攻击按键时创建子弹的prefab,也就是bulletPrefab。
   void BulletAttack(){
//下面的这句话非常经典,利用as Rigidbody将Instantiate的GameObject强制转换为Rigidbody类型。
Rigidbody bPrefab = Instantiate (bulletPrefab, transform.position, Quaternion.identity)as R
bPrefab.rigidbody.AddForce (Vector3.right * 500);
保存,现在我们的就可以利用键发射子弹了。不过这个子弹是一个,我们需要自己设计一个出来才行。
首先我们新建一个,命名为。然后将的设置为(肯定要,至于,对于一个平面物体来说,没所谓。)然后添加组件和组件,在里面去掉,在的里面三项全打勾,在里面的打勾。在里面的打勾,其他的不用设置。然后创建一个叫做的脚本,扔到物体上。
然后我们把这个物体拖拽到文件夹位置,在文件夹中就会自动生成一个叫做的预设。以后如果需要对预设进行修改的话,只需要重新造一个克隆物体出来修改,然后修改完点击,就会影响到所有的预设体。
最后,我们把的拉到的上。然后将和的改为和他们的名字一样的。现在我们的主人公会发射子弹了。
在脚本中进行以下添加:
using UnityE
  using System.C
  public class Bullet : MonoBehaviour {
   //用于碰撞时摧毁两个物体
   void OnTriggerEnter(Collider other){
if (other.gameObject.tag == &Enemy&) {
Destroy(gameObject);
Destroy(other.gameObject);
保存,现在我们的子弹可以破坏敌人了。不过敌人会被一击消灭,而且子弹飞出画面不会自行摧毁。这些问题就留着下一节解决。
七、角色攻击2
接下来到第七讲。现在我们需要让我们的子弹变得会自动消失。也就是给发射过程设置一个自动摧毁的时间。避免出现子弹越来越多,而且永不消失的情况。首先我们需要在脚本里面增加一个函数,这个函数和函数有什么不同呢?这里顺便引用一段网上的话:
从字面上理解,它们都是在更新时会被调用,并且会循环的调用。
但是会在每次渲染新的一帧时,被调用。
而会在每个固定的时间间隔被调用,那么要是和的时间间隔一样,是不是就一样呢?答案是不一定,因为受当前渲染的物体,更确切的说是三角形的数量影响,有时快有时慢,帧率会变化,被调用的时间间隔就发生变化。
但是则不受帧率的变化,它是以固定的时间间隔来被调用,那么这个时间间隔怎么设置呢?
Edit-&Project&Setting-&time下面的。
(原链接:&)
现在这样我们就理解这两个的不同了,下面是添加的内容:
void FixedUpdate(){
Destroy (gameObject, 1.25f);
利用函数给出的时间,让物体在秒之后自行摧毁。
接下来我们修改一下脚本如下:
using UnityE
  using System.C
  public class PlayerAttack : MonoBehaviour {
   public Rigidbody bulletP
   float attackRate = 0.5f;
   float coolD
   // Update is called once per frame
   void Update () {
if (Time.time &= coolDown) {
if (Input.GetKeyDown (KeyCode.J)){
BulletAttack ();
   //按下攻击按键时创建子弹的prefab,也就是bulletPrefab。
   void BulletAttack(){
//下面的这句话非常经典,利用as Rigidbody将Instantiate的GameObject强制转换为Rigidbody类型。
Rigidbody bPrefab = Instantiate (bulletPrefab, transform.position, Quaternion.identity)as R
bPrefab.rigidbody.AddForce (Vector3.right * 500);
coolDown = Time.time + attackR
现在增加了两个变量,第一个是,第二个是,有了这两个变量之后,在每次使用函数时,会自动等于当前时间加上(秒),而每一帧里面,都需要进行判断是否大于,如果小于的话就无法射击,这样就实现了冷却的效果。
接下来需要考虑一个问题,那就是子弹的发射方向的问题,因为现在我们的子弹只能向右边发射,很不科学。所以为了解决这个问题,我参考的原视频教程里面将脚本整个整合到里面了。然后在外面的位置需要重新连接一次的。
接下来对Controller2D脚本进行一些加工,使我们的子弹可以向左边发射:
using UnityE
  using System.C
  public class Controller2D : MonoBehaviour {
   //引用CharacterController
   CharacterController characterC
   //重力
   public float gravity = 10;
   //水平移动的速度
   public float walkSpeed = 5;
   //弹跳高度
   public float jumpHeight = 5;
   //显示角色当前正受到攻击
   float takenDamage = 0.2f;
   // 控制角色的移动方向
   Vector3 moveDirection = Vector3.
   float horizontal = 0;
   //原PlayerAttack脚本里面的变量,把那个脚本和当前脚本合并,PlayerAttack脚本已经删除。
   public Rigidbody bulletP
   float attackRate = 0.5f;
   float coolD
   bool lookRight =
   // Use this for initialization
   void Start () {
characterController = GetComponent&CharacterController&();
   // Update is called once per frame
   void Update () {
//控制角色的移动
characterController.Move (moveDirection * Time.deltaTime);
horizontal = Input.GetAxis(&Horizontal&);
//控制角色的重力
moveDirection.y -= gravity * Time.deltaT
if (horizontal == 0) {
moveDirection.x =
//控制角色右移(按d键和右键时)
在这里不直接使用0而是用0.01f是因为使用0之后会持续移动,无法静止
if (horizontal & 0.01f) {
lookRight =
moveDirection.x = horizontal * walkS
//控制角色左移(按a键和左键时)
if (horizontal & -0.01f) {
lookRight =
moveDirection.x = horizontal * walkS
// 弹跳控制
if (characterController.isGrounded) {
if(Input.GetKeyDown(KeyCode.Space)||Input.GetKeyDown(KeyCode.K)){
moveDirection.y = jumpH
//原PlayerAttack的函数
if (Time.time &= coolDown) {
if (Input.GetKeyDown (KeyCode.J)){
BulletAttack ();
   //原PlayerAttack的函数,现在和当前脚本合并了。
   //按下攻击按键时创建子弹的prefab,也就是bulletPrefab。
   void BulletAttack(){
if (lookRight) {
//下面的这句话非常经典,利用as Rigidbody将Instantiate的GameObject强制转换为Rigidbody类型。
Rigidbody bPrefab = Instantiate (bulletPrefab, transform.position, Quaternion.identity)as R
bPrefab.rigidbody.AddForce (Vector3.right * 500);
coolDown = Time.time + attackR
//下面的这句话非常经典,利用as Rigidbody将Instantiate的GameObject强制转换为Rigidbody类型。
Rigidbody bPrefab = Instantiate (bulletPrefab, transform.position, Quaternion.identity)as R
bPrefab.rigidbody.AddForce (-Vector3.right * 500);
coolDown = Time.time + attackR
   public IEnumerator TakenDamage(){
renderer.enabled =
yield return new WaitForSeconds(takenDamage);
renderer.enabled =
yield return new WaitForSeconds(takenDamage);
renderer.enabled =
yield return new WaitForSeconds(takenDamage);
renderer.enabled =
yield return new WaitForSeconds(takenDamage);
renderer.enabled =
yield return new WaitForSeconds(takenDamage);
renderer.enabled =
yield return new WaitForSeconds(takenDamage);
这个脚本里面主要增加了的布尔值以及在发射子弹时的一些判定。另外用
if&(horizontal&==&0)&{
moveDirection.x&=&
这个函数使角色在不移动的情况下保持静止。
八、AI血值
接下来处理另一个问题,现在我们的敌人都是打一下就死掉的,但是很多横版游戏的敌人是非常彪悍的,并不是打一下就马上挂掉。所以我们开始来给设置简单的血值。
首先打开脚本,修改成下面这个样纸:
using UnityE
  using System.C
  public class Enemy2D : MonoBehaviour {
   //GameManager脚本的参量
   public GameManager gameM
   //敌人移动的初始和停止位置,用于控制敌人在一定范围内移动
   float startP
   float endP
   //控制敌人向右移动的一个增量
   public int unitsToMove = 5;
   //敌人移动的速度
   public int moveSpeed = 2;
   //左右移动的布尔值
   bool moveRight =
   //敌人的HP
   int enemyHealth = 1;
   //敌人的种类
   public bool basicE
   public bool advancedE
   void Awake(){
startPos = transform.position.x;
endPos = startPos + unitsToM
if (basicEnemy) {
enemyHealth = 3;
if (advancedEnemy) {
enemyHealth = 6;
   //此处这个Update函数用于控制敌人的左右移动,当向右移动到一定距离后就会反向移动,同理,左移一定距离之后也是。
   void Update(){
if (moveRight) {
rigidbody.position += Vector3.right * moveSpeed * Time.deltaT
if (rigidbody.position.x &= endPos) {
moveRight =
if (moveRight==false) {
rigidbody.position -= Vector3.right * moveSpeed * Time.deltaT
if (rigidbody.position.x &= startPos) {
moveRight =
   int damageValue = 1;
   //这里利用sendmessage函数使得角色与敌人自己碰撞时,发送一个扣血的message到gamemanager函数之中,然后就会在每次碰撞时减掉一滴血。
   void OnTriggerEnter(Collider col){
if (col.gameObject.tag == &Player&) {
gameManager.SendMessage(&PlayerDamaged&,damageValue,SendMessageOptions.DontRequireReceiver);
gameManager.controller2D.SendMessage(&TakenDamage&,SendMessageOptions.DontRequireReceiver);
   //下面这个函数用于判定敌人自己被攻击时的扣血。
   void EnemyDamaged(int damage){
if (enemyHealth & 0) {
enemyHealth -=
if (enemyHealth &= 0) {
enemyHealth = 0;
Destroy(gameObject);
然后我们同样要修改Bullet的脚本,让子弹与敌人发送碰撞时不是瞬间摧毁敌人,而是对敌人身上的Enemy2D脚本发送一个扣血的数量,这个数量一旦为零,Enemy2D脚本就会将敌人自行摧毁。
  using UnityE
  using System.C
  public class Bullet : MonoBehaviour {
   int damageValue = 1;
   //用于碰撞时摧毁两个物体
   void OnTriggerEnter(Collider other){
if (other.gameObject.tag == &Enemy&) {
Destroy(gameObject);
other.gameObject.SendMessage(&EnemyDamaged&,damageValue,SendMessageOptions.DontRequireReceiver);
   void FixedUpdate(){
Destroy (gameObject, 1.25f);
另外,在刚刚的脚本里面,我们还设置了和两个布尔值,分别设定我们当前敌人的为和,这样我们就可以通过这些布尔值来创建不同的敌人。(暂时只设定两种,当然可以随你喜欢,要多少种就做多少种。)
然后我们将物体做成一个,在场景中创建两个,一个打勾为,一个打勾为,这样我们的场景中就出现了一个滴血和一个滴血的敌人啦。
(这个是basic的)
(这个是advanced的)
&&&&为了区分敌人,我做了另外一个紫色的material,命名为,然后拖拽到我们的那个设定为的预设体身上。
运行,我们看到的情况就是这样:
左边的敌人三下攻击就挂掉,右边的则需要攻击六下。
九、经验系统
敌人的设置暂时可以了。接下来我们来设置经验值系统。在传统的中,打怪升级是永恒不变的主题。所以我们也想要设置一个类似的系统,哈哈。
将脚本修改成这个样纸:
using UnityE
  using System.C
  public class GameManager : MonoBehaviour {
   //Controller2D脚本的参量
   public Controller2D controller2D;
   //角色生命值
   public Texture playersHealthT
   //控制上面那个Teture的屏幕所在位置
   public float screenPositionX;
   public float screenPositionY;
   //控制桌面图标的大小
   public int iconSizeX = 25;
   public int iconSizeY = 25;
   //初始生命值
   public int playersHealth = 3;
   GameO
   int curEXP = 0;
   int maxEXP = 50;
   int level = 1;
   //这个布尔值用于判定是否显示角色的状态
   bool playerS
   //下面这个用于显示角色的状态
   public GUIText statsD
   //这个地方定义了私有变量player作为一个GameObject,然后用下面的FindGameObjectWithTag获取它,这样的话,在下面的伤害判断时,就可以用player.renderer了。
   void Start(){
player = GameObject.FindGameObjectWithTag(&Player&);
   void Update(){
if (curEXP &= maxEXP) {
LevelUp();
if (Input.GetKeyDown (KeyCode.C)) {
playerStats = !playerS
if (playerStats) {
statsDisplay.text = &等级:& + level + &\n经验:& + curEXP + &/& + maxEXP;
statsDisplay.text = &&;
   //这个函数用于角色升级
   void LevelUp(){
curEXP = 0;
maxEXP = maxEXP + 50;
//升级的功效
playersHealth++;
   //OnGUI函数最好不要出现多次,容易造成混乱,所以我把要展示的东西都整合在这个里面
   void OnGUI(){
//控制角色生命值的心的显示
for (int h =0; h & playersH h++) {
GUI.DrawTexture(new Rect(screenPositionX + (h*iconSizeX),screenPositionY,iconSizeX,iconSizeY),playersHealthTexture,ScaleMode.ScaleToFit,true,0);
   void PlayerDamaged(int damage){
//此处使用player.renderer.enabled来进行判断,如果角色没有在闪烁,也就是存在的状态为真,那么才会受到伤害,这样可以避免角色连续受伤,还有另外一种方法是采用计时,这里没有采用那种方法。
if (player.renderer.enabled) {
if (playersHealth & 0) {
playersHealth -=
if (playersHealth &= 0) {
RestartScene ();
   void RestartScene(){
Application.LoadLevel (Application.loadedLevel);
然后保存。然后我们在场景中创建一个物体。命名为。接下来我们需要导入一个字体文件。(当然,你想用默认字体也是可以的,做游戏的时候一般会用到或者其他类似的插件来做字体或者其他的一些效果。因为我现在还没怎么接触那些插件,所以暂时不涉及插件方面的内容,直接制作就好了……虽然在画面上可能简陋和丑了一些,不过这些要慢慢来的,没办法一口吃成胖子。)
我在字体大宝库网下载了一个字体,
&迷你简卡通,当然你也可以随便找自己喜欢的字体。不过记住要是格式的。因为我们的目前只支持这种格式的字体,常见的其他格式还有,还有一些其他类型的格式,需要转换的话,可以用一款叫做的工具,这款工具除了可以用来将转成我们喜欢的之外,还可以编辑、自己设计自己喜欢的字体,不过这个不是现在的内容,以后再进行整理吧。
在我们的里面创建一个叫做的文件夹,然后把我们的迷你简卡通字体扔进去。注意这个字体的名字尽量改成英文,不然容易出问题。在里面,所有文件夹和文件的名称尽量不要有中文,另外,保存的游戏、项目的路径里面也最好不要有中文。
完成之后我们看的栏,在里面进行选择,默认是字体,我们现在可以改成我们导入的了(我是这样命名的……)
将GUIStats的位置修改一下,这个会影响到我们运行游戏时字体出现的位置,当然你还可以修改字体颜色、大小等等,我就不想改这些了。
然后我们运行游戏,并且按C键,也就是我们刚刚设置的那个用于显示的键位。(你要是设置成其他按键也是一样的。)
这个就是运行之后的效果,哈哈,虽然很简陋,不过已经大功告成了!
现在我们需要对两个脚本进行一些修改:
首先是将GameManager脚本里面的变量改成的,这样的话才可以被外边调用到。然后我们打开脚本,修改下面部分的内容:
void EnemyDamaged(int damage){
if (enemyHealth & 0) {
enemyHealth -=
if (enemyHealth &= 0) {
enemyHealth = 0;
Destroy(gameObject);
gameManager.curEXP += 10;
然后保存。现在在摧毁敌人的同时,我们的就会接收一个加十的信号,这样就可以为我们的角色增加了。不过有个地方需要特别注意一下,不然的话会报错:由于我们的敌人是预设体而不是物体,所以每个敌人预设体的脚本上的脚本都要自己进行关联,不然的话就会在消灭敌人的时候因为找不到而无法发送增加经验值的信号,导致报错。
报错的内容为:NullReferenceException:&Object&reference&not&set&to&an&instance&of&an&object(需要注意一下)
现在刷刷刷干掉两个敌人,看,我们有20点经验值啦嘻嘻
接下来我们来做点对的测试,因为要打五只怪才能升一级,比较麻烦,我们直接增加一个按键加经验的功能好了(节操何在……)。非常简单,我们只需要在脚本的函数里面增加这么一个判断:
if&(Input.GetKeyDown&(KeyCode.E))&{
curEXP&+=&10;
然后我们每次按就会增加点经验值。当然,这个是测试的,真的设计游戏的时候不要这么玩,否则游戏的可玩性和平衡性就全完蛋了。
我们可以看到,当我们的经验增加到超过50时,爱心的个数就会变成,而且经验清零,之后如果想从第二级升到第三级,就需要点经验值了,以后每个等级需要多点经验值,以此类推。
十、主菜单和等级设置
开始这一讲之前需要做一些补充。(我看的视频教程里面有提到说,如果对这块还不太清楚的话需要看一下他前面的几个教程,所以我稍微花了点时间把他之前的几个教程给看完。)
里面是教你怎么设置背景图片以及做一些简单的按钮的。这个教程看完之后觉得比较简单,就不再赘述了,直接贴出里面用到的代码:
using UnityE
  using System.C
  public class MainMenu : MonoBehaviour {
   public Texture backgroundT
   public GUIStyle random1;
   public float guiPlacementX1;
   public float guiPlacementX2;
   public float guiPlacementY1;
   public float guiPlacementY2;
   public bool showGUIOutline =
   void OnGUI(){
  //显示背景
GUI.DrawTexture (new Rect (0, 0, Screen.width, Screen.height), backgroundTexture);
  //显示按钮
if (showGUIOutline) {
if (GUI.Button (new Rect (Screen.width * guiPlacementX1, Screen.height * guiPlacementY1, Screen.width * .5f, Screen.height * .1f), &开始游戏&)) {
print (&Clicked Play Game&);
if (GUI.Button (new Rect (Screen.width * guiPlacementX2, Screen.height * guiPlacementY2, Screen.width * .5f, Screen.height * .1f), &设置&)) {
print (&Clicked Options&);
if (GUI.Button (new Rect (Screen.width * guiPlacementX1, Screen.height * guiPlacementY1, Screen.width * .5f, Screen.height * .1f), &&, random1)) {
print (&Clicked Play Game&);
if (GUI.Button (new Rect (Screen.width * guiPlacementX2, Screen.height * guiPlacementY2, Screen.width * .5f, Screen.height * .1f), &&, random1)) {
print (&Clicked Options&);
另外呢,还有一个教程是设置场景的的。里面用到了一个链接,如下:
使用这个链接里面的方法创建的的面数会比较少,如果是开发游戏的话,那这个还是挺有必要了解了解的,不过我现在暂时不需要在这个东西上面研究和花费时间。
稍微研究完我们的之后,接下来继续我们的研究。
接下来我们可以重新创建一个场景,命名为的,然后我们把上面那个脚本保存之后放到新创建的场景的摄像机物体上面去。接着我们需要进行一点点简单的修改。使我们能够在场景点击“开始游戏”时进入我们前面设计的第一关。
这个很简单。
首先我们在上面的脚本里面添加一个公共的字符串变量,名字随便,我命名为,即:public&string&Level;&然后我们在按键的功能的那个地方添加
if (showGUIOutline) {
if (GUI.Button (new Rect (Screen.width * guiPlacementX1, Screen.height * guiPlacementY1, Screen.width * .5f, Screen.height * .1f), &开始游戏&)) {
print (&Clicked Play Game&);
Application.LoadLevel(Level);
其实使用可以在括号里面直接添加场景的序号,比如我们的场景序号就是,(因为场景的序号为),或者是直接打双引号调用场景名称。比如Application.LoadLevel(“Scene1”);&但是这样做有个问题,那就是如果你的场景改名字了,你就需要修改脚本里面这个地方的名字,或者是如果这一部分的脚本内容复制到其他地方去用的话,那就需要修改同样的内容。为了避免这种情况,我采用了,这样的话可以在物体上面直接打出我们的场景的名字,就可以加载了,是不是方便了很多呢?
Level里的是我自己打进去的,可以随时修改成别的内容。)
当然,不要忘了在菜单栏的File→里面将我们目前的两个场景拖拽进去。
以后如果添加了新的场景的话,也要进行同样的处理,否则的话是没办法加载和切换其他场景的。
在我们刚刚的脚本里面,我们还需要为之添加一张用来做背景的图片,我随便弄了一张,就是我现在的电脑壁纸。我的壁纸是我自己做的,分辨率是,所以需要进行以下的设置:
然后我们来试运行一下:
很好,嘻嘻,我们现在有了一个比较简单的开始菜单了至于换图片,美工处理和优化什么的,暂时先不用急,等编码部分解决了就马上给我们的整个场景和角色进行打扮和处理。
接下来需要考虑的就是防止角色冲出游戏画面的问题了,我们的角色如果飞出画面的话是会直接坠落身亡的……(不对,应该是会无限坠落,并且坠落速度大于相机的跟随速度,所以相机永远跟不上,画面里也就永远不会显示角色……汗……)所以呢,我们来稍微做点处理,很简单,类似地板那样,弄两个墙壁,然后呢,现在的场景很单调,我们再弄几块石头放到我们的场景上面去。
注意由于我们现在所有的东西都是平面物体,去掉之后换成,而对于平面物体来说,如果要使其发生碰撞的话,轴上的值必须相等,所以暂时需要把所有物体的轴上的值设置为零。(如果你的物体不是平面,而是个立方体的话就另当别论了,不过这个以后再说,现在画面丑陋就丑陋吧……)
还有一个问题需要修复一下,那就是我们的子弹是不可以穿透墙壁的,怎么解决这个问题呢?我们可以给脚本增加一个内容,使其在碰到带有某类的物体是自行摧毁,即可解决这个问题。
现在我们在场景中创建一个空物体,命名为(在英语里面,一词除了可以用来表示等级之外,还可以用来表示关卡,这里的表示的意思就是该关卡里面的全部自然物体),然后我们把所有的、还有都放到这个物体里面。这样做是为了使我们的面板看起来比较干净。
然后我们将里面的全部物体全选,加上一个LevelObjects的标签。然后我们打开脚本,在函数里面加入:
if&(other.gameObject.tag&==&&LevelObjects&)&{
Destroy(gameObject);
这样的话,我们的子弹碰到墙壁什么的,就可以自行摧毁啦。
现在,一个丑的要死的场景就搞定啦,哈哈还是有点小激动啊
十一、平台制作
这一讲比较简单,因为我们之前已经让我们的敌人可以左右晃动了,那么,那一部分的代码稍微修改一下,就可以做出一个上下升腾的平台效果出来。但是直接操作的话会出现一个很奇怪的问题,那就是在我们的角色跳上平台的那一瞬间,角色就会穿过平台掉落到地面上。这个问题在我以前看过的一个教程里面并没有出现,因为上次看的那个教程里面用到的平台是的,而本次用到的是的,可能在这方面会有一定的影响吧。
算了,暂时不管了,我是打算先把这个部分的教程完整地走完一遍,然后再去进行其他的一些补充的。这个问题就暂时按照原教程里面的方法修复好就算了。
那么原教程里面的方法是怎样做的呢?
首先,我们先创建一个的脚本,弄进去以下内容:
using UnityE
  using System.C
  public class MovingPlatform : MonoBehaviour {
   float startP
   float endP
   public int unitsToMove = 5;
   public int moveSpeed = 2;
   bool moveRight =
   void Awake(){
startPos = transform.position.y;
endPos = startPos + unitsToM
   void Update(){
if (moveRight) {
transform.position += Vector3.up * moveSpeed * Time.deltaT
if (transform.position.y &= endPos) {
moveRight =
if (moveRight==false) {
transform.position -= Vector3.up * moveSpeed * Time.deltaT
if (transform.position.y &= startPos) {
moveRight =
这部分内容全部都是在里面搬来的,然后我去掉了里面的中文注释,并且将改为,因为我们的平台物体并不需要一个在配合。(你要的话也是可以的)
接下来我们在物体(这个物体是刚刚创建的,类似墙壁物体一样,要去掉,然后加上)下面创建一个空物体作为它的子物体,命名为,然后再创建一个叫做的脚本,并且把这个脚本扔到我们的角色上面去,编辑脚本:
using UnityE
  using System.C
  public class StickToPlatform : MonoBehaviour {
   void OnTriggerStay(Collider other){
if (other.tag == &Platform&) {
this.transform.parent = other.
   void OnTriggerExit(Collider other){
if (other.tag == &Platform&) {
this.transform.parent =
这个地方稍微需要解释一下,这里的意思是,当我们的和这个踏板接触时,就会变成踏板的一个子物体,然后跟着踏板一起移动,当我们的和踏板不再接触时,就会取消掉它身上的父类,这样的话我们的角色物体又变成了独立物体。
现在,一个会自动上升和下降,并且不会对角色产生干扰的平台就做好了!(请注意,上面的脚本是拖拽到我们的角色身上,而不是平台上,不要弄错,否则无法运行的。)
好啦,我的第一篇CSDN博客就整理到这里。个人水平有限,如果里面有什么说错写错的地方,欢迎大家指正和补充。如果这篇笔记可以帮到大家,那自然是最好不过啦!希望能和大家多多交流~
工程文件打包下载:
第二篇继续整理和学习研究中……
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:136954次
积分:2067
积分:2067
排名:第14717名
原创:40篇
转载:192篇
评论:25条
(1)(3)(13)(3)(14)(6)(38)(93)(31)(25)(4)(1)

我要回帖

更多关于 2d横版射击游戏 的文章

 

随机推荐