关闭

Unity 4.3 制作一个2D横版射击游戏

标签: unity2d
53588人阅读 评论(31) 收藏 举报
分类:
       这是一个横版射击游戏。教程来自:http://pixelnest.io/tutorials/2d-game-unity/ 中文版教程:http://www.litpo.com/category/易学的教程/
教程文章已经详细地介绍实现过程,这里就不再复述,只做一些简单的笔记记录,以供备忘。

1.创建第一个场景

新建工程。接着在"Project"面板上创建如下文件夹:

接着在"Hierarchy"面板上创建多个空对象,形成以下结构:

这些空对象的Position保持为(0, 0, 0)即可。保存场景,名称为"Stage1"。

2.添加显示背景

将背景图片放入"Textures"文件夹,确认这张图片的纹理类型,即"Texture Type"属性为"Sprite",如下图所示:

在场景中添加一个Sprite游戏对象,设置其"Sprite"属性为刚才导入的背景图片。重命名这个对象为"Background1",将其移动到"Level -> 0 - Background"里面,设置其"Position"为(0, 0, 0)。接着添加背景元素。导入平台图片到"Textures"文件夹,选中"platforms"图片,设置其"Sprite Mode"为"Multiple",然后点击"Sprite Editor",如下图所示:

在弹出的"Sprite Editor"窗口中,进行绘制每个平台的包围矩形,以便将纹理分割成更小的部分。然后分别命名为"platform1"和"platform2",如下图所示:

创建一个新的Sprite游戏对象,设置其"Sprite"属性为"platform1"。再在创建新Sprite对象,设置其"Sprite"属性为"platform2"。将它们放置到"1 - Middleground"对象里,并且确认它们的Z坐标为0。将这两个对象从"Hierarchy"面板拖动到"Project"面板下的"Prefabs"文件夹,保存成预制对象。接着,为了避免显示顺序问题,修改下游戏对象的Z坐标,如下所示:
Layer Z Position
0 - Background 10
1 - Middleground 5
2 - Foreground 0
此时,点击"Scene"面板上的2D到3D视图切换,可以清楚看到层次:

3.创建主角和敌人

导入主角图片"Textures"文件夹,创建一个Sprite游戏对象,命名为"Player",设置其"Sprite"属性为刚才导入的主角图片。将此对象放入"2 - Foreground"层里,设置其Scale(0.2, 0.2, 1)。接着,为主角添加盒碰撞体组件,点击"Add Component"按钮,选择"Box Collider 2D",设置其Size(10, 10),虽然这还是大于实际的区域,但已比图片小多了,如下图所示:

(可以通过按住Shift键,来调整绿色碰撞范围)
接着,再为主角对象添加"Rigidbody 2D"刚体组件,现在运行可以看到如下结果:

可以看到主角往下落了,这是因为刚体带有重力,但在这个游戏不需要用到,将"Gravity Scale"属性设为0即可。另外,不想因为物理而引起的主角旋转,则将"Fixed Angles"属性勾选上。完整的设置如下图所示:

开始准备让主角移动。在"Scripts"文件夹里,创建一个C#脚本,名称为"PlayerScript",实现让方向键移动主角,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using UnityEngine;

/// <summary>
/// Player controller and behavior
/// </summary>
public class PlayerScript : MonoBehaviour
{
  /// <summary>
  /// 1 - The speed of the ship
  /// </summary>
  public Vector2 speed = new Vector2(5050);

  // 2 - Store the movement
  private Vector2 movement;

  void Update()
  {
    // 3 - Retrieve axis information
    float inputX = Input.GetAxis("Horizontal");
    float inputY = Input.GetAxis("Vertical");

    // 4 - Movement per direction
    movement = new Vector2(
      speed.x * inputX,
      speed.y * inputY);

  }

  void FixedUpdate()
  {
    // 5 - Move the game object
    rigidbody2D.velocity = movement;
  }
}
这里以改变刚体的速率来达到主角的移动,而不是通过直接改变"transform.Translate",因为那样的话,可能会不产生碰撞。将此脚本附加到主角对象上,现在运行,方向键来控制移动,如下图所示:


接下来,添加第一个敌人。导入章鱼敌人图片到"Textures"文件夹,创建一个Sprite游戏对象,命名为"Poulpi",设置其"Sprite"属性为刚才导入的图片,设置"Scale"属性为(0.4, 0.4, 1),添加"Box Collider 2D"组件,其Size为(4, 4),添加"Rigidbody 2D"组件,其"Gravity Scale"属性为0,并且勾选"Fixed Angles"属性框。将对象保存为预制。在这里只让章鱼简单地往前行,创建一个脚本,命名为"MoveScript",代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using UnityEngine;

/// <summary>
/// Simply moves the current game object
/// </summary>
public class MoveScript : MonoBehaviour
{
  // 1 - Designer variables

  /// <summary>
  /// Object speed
  /// </summary>
  public Vector2 speed = new Vector2(1010);

  /// <summary>
  /// Moving direction
  /// </summary>
  public Vector2 direction = new Vector2(-10);

  private Vector2 movement;

  void Update()
  {
    // 2 - Movement
    movement = new Vector2(
      speed.x * direction.x,
      speed.y * direction.y);
  }

  void FixedUpdate()
  {
    // Apply movement to the rigidbody
    rigidbody2D.velocity = movement;
  }
}
将此脚本附加到章鱼对象上,现在运行,可以看到章鱼往前移动,如下图所示:


此时如果将主角放在章鱼前面,两个对象将会产生碰撞,互相阻塞对方的移动。

4.射击

导入子弹图片"Textures"文件夹,创建一个Sprite游戏对象,命名为"PlayerShot",设置其"Sprite"属性为刚才导入的图片,设置"Scale"属性为(0.75, 0.75, 1),添加"Rigidbody 2D"组件,其"Gravity Scale"属性为0,并且勾选"Fixed Angles"属性框,添加"Box Collider 2D"组件,其Size为(1, 1),并且勾选"IsTrigger"属性。勾选"IsTrigger"属性表示该碰撞体用于触发事件,并将被物理引擎所忽略。意味着,子弹将穿过触碰到的对象,而不会阻碍对象的移动,触碰的时候将会引发"OnTriggerEnter2D"事件。创建一个脚本,命名为"ShotScript",代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using UnityEngine;

/// <summary>
/// Projectile behavior
/// </summary>
public class ShotScript : MonoBehaviour
{
  // 1 - Designer variables

  /// <summary>
  /// Damage inflicted
  /// </summary>
  public int damage = 1;

  /// <summary>
  /// Projectile damage player or enemies?
  /// </summary>
  public bool isEnemyShot = false;

  void Start()
  {
    // 2 - Limited time to live to avoid any leak
    Destroy(gameObject, 20); // 20sec
  }
}
将此脚本附加到子弹对象上,然后将"MoveScript"脚本也附加到子弹对象上以便可以移动。保存此对象为预制。接着,让碰撞产生伤害效果。创建一个脚本,命名为"HealthScript",代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
using UnityEngine;

/// <summary>
/// Handle hitpoints and damages
/// </summary>
public class HealthScript : MonoBehaviour
{
  /// <summary>
  /// Total hitpoints
  /// </summary>
  public int hp = 1;

  /// <summary>
  /// Enemy or player?
  /// </summary>
  public bool isEnemy = true;

  /// <summary>
  /// Inflicts damage and check if the object should be destroyed
  /// </summary>
  /// <param name="damageCount"></param>
  public void Damage(int damageCount)
  {
    hp -= damageCount;

    if (hp <= 0)
    {
      // Dead!
      Destroy(gameObject);
    }
  }

  void OnTriggerEnter2D(Collider2D otherCollider)
  {
    // Is this a shot?
    ShotScript shot = otherCollider.gameObject.GetComponent<ShotScript>();
    if (shot != null)
    {
      // Avoid friendly fire
      if (shot.isEnemyShot != isEnemy)
      {
        Damage(shot.damage);

        // Destroy the shot
        Destroy(shot.gameObject); // Remember to always target the game object, otherwise you will just remove the script
      }
    }
  }
}
将此脚本附加到Poulpi预制体上。现在运行,让子弹和章鱼碰撞,可以看到如下结果:


如果章鱼的生命值大于子弹的伤害值,那么章鱼就不会被消灭,可以试着通过改变章鱼对象的"HealthScript"的hp值。接着,准备可以射击。创建一个脚本,命名为"WeaponScript",代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
using UnityEngine;

/// <summary>
/// Launch projectile
/// </summary>
public class WeaponScript : MonoBehaviour
{
  //--------------------------------
  // 1 - Designer variables
  //--------------------------------

  /// <summary>
  /// Projectile prefab for shooting
  /// </summary>
  public Transform shotPrefab;

  /// <summary>
  /// Cooldown in seconds between two shots
  /// </summary>
  public float shootingRate = 0.25f;

  //--------------------------------
  // 2 - Cooldown
  //--------------------------------

  private float shootCooldown;

  void Start()
  {
    shootCooldown = 0f;
  }

  void Update()
  {
    if (shootCooldown > 0)
    {
      shootCooldown -= Time.deltaTime;
    }
  }

  //--------------------------------
  // 3 - Shooting from another script
  //--------------------------------

  /// <summary>
  /// Create a new projectile if possible
  /// </summary>
  public void Attack(bool isEnemy)
  {
    if (CanAttack)
    {
      shootCooldown = shootingRate;

      // Create a new shot
      var shotTransform = Instantiate(shotPrefab) as Transform;

      // Assign position
      shotTransform.position = transform.position;

      // The is enemy property
      ShotScript shot = shotTransform.gameObject.GetComponent<ShotScript>();
      if (shot != null)
      {
        shot.isEnemyShot = isEnemy;
      }

      // Make the weapon shot always towards it
      MoveScript move = shotTransform.gameObject.GetComponent<MoveScript>();
      if (move != null)
      {
        move.direction = this.transform.right; // towards in 2D space is the right of the sprite
      }
    }
  }

  /// <summary>
  /// Is the weapon ready to create a new projectile?
  /// </summary>
  public bool CanAttack
  {
    get
    {
      return shootCooldown <= 0f;
    }
  }
}
将这个脚本附加到主角对象上,设置其"Shot Prefab"属性为"PlayerShot"预制体。打开"PlayerScript"脚本,在Update()方法里面,加入以下片段:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void Update()
  {
    // ...

    // 5 - Shooting
    bool shoot = Input.GetButtonDown("Fire1");
    shoot |= Input.GetButtonDown("Fire2");
    // Careful: For Mac users, ctrl + arrow is a bad idea

    if (shoot)
    {
      WeaponScript weapon = GetComponent<WeaponScript>();
      if (weapon != null)
      {
        // false because the player is not an enemy
        weapon.Attack(false);
      }
    }

    // ...
  }
当收到射击的按钮状态,调用Attack(false)方法。现在运行,可以看到如下结果:


接下来,准备创建敌人的子弹。导入敌人子弹图片"Textures"文件夹,选中"PlayerShot"预制体,按下Ctrl+D进行复制,命名为"EnemyShot1",然后改变其Sprite为刚才导入的图片,设置其Scale(0.35, 0.35, 1)。接着,让章鱼可以射击。将"WeaponScript"脚本附加到章鱼对象上,拖动"EnemyShot1"预制体到其"Shot Prefab"属性,创建一个脚本,命名为"EnemyScript",用来简单地每一帧进行自动射击,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using UnityEngine;

/// <summary>
/// Enemy generic behavior
/// </summary>
public class EnemyScript : MonoBehaviour
{
  private WeaponScript weapon;

  void Awake()
  {
    // Retrieve the weapon only once
    weapon = GetComponent<WeaponScript>();
  }

  void Update()
  {
    // Auto-fire
    if (weapon != null && weapon.CanAttack)
    {
      weapon.Attack(true);
    }
  }
}
将此脚本附加到章鱼对象上,如下图所示:

现在运行,可以看到如下结果:


可以看到章鱼向右射击了子弹,这是因为代码就是让它那么做的。实际上,应该做到可以朝向任何方向射击子弹。在这里,创建一个空对象,命名为"WeaponObject",删除章鱼预制体身上的"WeaponScript"脚本,将"WeaponScript"脚本附加到"WeaponObject"对象上,并且拖动"EnemyShot1"预制体到其"Shot Prefab"属性,设置"WeaponObject"对象的"Rotation"属性为(0, 0, 180),然后保存为预制,如下所示:
接着,修改"EnemyScript",代码为如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// Enemy generic behavior
/// </summary>
public class EnemyScript : MonoBehaviour
{
  private WeaponScript[] weapons;

  void Awake()
  {
    // Retrieve the weapon only once
    weapons = GetComponentsInChildren<WeaponScript>();
  }

  void Update()
  {
    foreach (WeaponScript weapon in weapons)
    {
      // Auto-fire
      if (weapon != null && weapon.CanAttack)
      {
        weapon.Attack(true);
      }
    }
  }
}
可以适当调整子弹的移动速度,它应该快于章鱼的移动速度。现在运行,如下图所示:


按这种方式,可以轻易实现多个方向的射击。复制添加一个"WeaponObject"对象,让这两个"WeaponObject"对象的旋转不同即可。现在主角被章鱼攻击到,但是没有任何伤害。将"HealthScript"脚本附加到主角对象上,确保"Is Enemy"属性不被勾选。现在运行,可以看到如下结果:


目前,当主角和章鱼碰撞时,仅仅只是阻碍对方的移动而已,在这里改成互相受到伤害。打开"PlayerScript"文件,添加如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//PlayerScript.cs
//....

void OnCollisionEnter2D(Collision2D collision)
  {
    bool damagePlayer = false;

    // Collision with enemy
    EnemyScript enemy = collision.gameObject.GetComponent<EnemyScript>();
    if (enemy != null)
    {
      // Kill the enemy
      HealthScript enemyHealth = enemy.GetComponent<HealthScript>();
      if (enemyHealth != null) enemyHealth.Damage(enemyHealth.hp);

      damagePlayer = true;
    }

    // Damage the player
    if (damagePlayer)
    {
      HealthScript playerHealth = this.GetComponent<HealthScript>();
      if (playerHealth != null) playerHealth.Damage(1);
    }
  }
至此,一个基本的射击游戏完成。下一篇,将进行增强表现,完善游戏。
28
1

猜你在找
【直播】机器学习&深度学习系统实战(唐宇迪)
【直播】Kaggle 神器:XGBoost 从基础到实战(冒教授)
【直播回放】深度学习基础与TensorFlow实践(王琛)
【直播】计算机视觉原理及实战(屈教授)
【直播】机器学习之凸优化(马博士)
【直播】机器学习之矩阵(黄博士)
【直播】机器学习之概率与统计推断(冒教授)
【直播】机器学习之数学基础
【直播】TensorFlow实战进阶(智亮)
【直播】深度学习30天系统实训(唐宇迪)
查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:3275858次
    • 积分:33783
    • 等级:
    • 排名:第135名
    • 原创:394篇
    • 转载:88篇
    • 译文:5篇
    • 评论:3370条
    个人说明
    联系方式:
    最新评论