Unity制作UFO小游戏

游戏截图


教学视频地址: https://www.bilibili.com/video/av21926233/index_1.html

场地建立

  1. 将地表以及玩家的资源文件拖动到Hierarchy窗口,并调整其大小。(每次添加新GameObj重置坐标到原点)
  2. 选择对应的Layer层,从低到高依次渲染,所以最高的Layer将覆盖低的显示。
  3. 相机调整,增加视野,2D游戏默认使用正交相机。
  4. 初步的对象建立完毕。

控制玩家

  1. 所有物体移动都是通过物理系统控制的,所以需要添加刚体通过外力控制玩家移动。
  2. 玩家游戏对象下通过C#脚本对物理添加外力,每个脚本初始都会有一些默认函数:
    1. Update:在渲染一帧前被调用,所以一般存放Unity需要每帧执行代码的容器 ;
    2. FixedUpdate:执行任何物理计算前被调用,所以一般存放物理操作代码;
  3. 查询API通过Input获取操作杆的输入,也就是键盘控制上下左右,然后对刚体使用AddForce增加外力。
    1. 获取刚体对象 GetComponent<Rigidbody2D>();
    2. 此时会发现移动的很慢,可以在脚本中添加公共变量speed来控制,此时会发现添加的公共变量会出现在Inspector中,可以直接改变数值生效,不用改动脚本代码了。(感觉很厉害)
  4. 调整到玩家正常移动。

加入碰撞

  1. 为了防止玩家出界,所以需要添加边界碰撞。

  2. 玩家以及地面添加Collider碰撞器组件。

    1. 碰撞器是一个形状或者容器,供物理引擎判断碰撞事件,一个游戏对象可以绑定多个碰撞器。

    2. 物理碰撞至少一方须要有Rigidbody刚体组件,玩家已经有,所以地面不需要添加。

    3. 因为玩家是球形,所以添加一个圆形的Collider,调整大小到刚好合适玩家。

    4. 地面是正方形所以添加Box Collider,如下图:
      这里写图片描述

    5. 此时运行时发现玩家直接飞出去了,为什么了?因为两个对象占据了同样的空间,玩家在地面的碰撞器盒子里面,物理引擎就会直接给所有相关的Rigidbody施力,所有玩家(有Rigidbody)被弹开飞出去了。

    6. 所有不应该直接放一个大的碰撞体,而是应该换成一系列小的碰撞器来代表墙体:

      这里写图片描述

      PS:虽然我们添加的碰撞器有重叠,但是我们没有Rigidbody组件,所以不会被弹开

  3. 测试可以正常的移动并且不会跑出地图边界。

相机跟随玩家移动

  1. 绑定相机和玩家,直接拖动相机到玩家的作为玩家的子对象,此时相机就会跟随玩家。
  2. 测试确实是跟随玩家移动,但是但玩家碰撞到墙壁发生旋转时,此时相机也会旋转,但这是我们不想要的结果也是不合理的,所以相机不能作为玩家子对象。
  3. 所以我们只能在相机上添加脚本,通过脚本控制。
    1. Start计算相机和玩家的位置偏移:相机位置-玩家位置,记录下来。
    2. LateUpdate(也是每帧运行,不过是运行Update之后才执行)更新相机的位置为:玩家位置+偏移。
  4. 测试移动玩家相机跟随移动。

创建收集物

  1. 同理拖动收集物到Hierarchy中,并重置坐标到原点。
  2. 为了能与玩家碰撞并且搜集,可以添加圆形Collider碰撞器组件。
  3. 添加一些动态变化吸引玩家注意达到提醒效果,比如收集物的旋转:
    1. transform.Rotate(new Vector3(0, 0, 45) * Time.deltaTime);
    2. Rotate:根据一个三维的欧拉角对应x,y,z轴进行旋转。
    3. Time.deltaTime:一帧对应的时间,所以乘上整体可以看做一秒按z轴旋转45度。
  4. 此时一个收集物创建完成,但是我们需要很多收集物,难道每个都要这样做么,这时候就需要Prefab(预制):
    1. 预制:一个包含模板的资源或者游戏对象组,可以通过现有的游戏对象或者游戏对象组来创建预制。
    2. 预制相当于克隆,一个预制对象的改变所有对象都会改变。
    3. 创建好预制对象后,直接复制对象即可生成很多收集物,改变每个的位置即可。

拾起收集物

  1. 想要玩家碰撞之后拾起收集物,就需要确定玩家何时碰撞到收集物,所以查阅Collider的API文档,知道使用OnTriggerEnter2D:当另一个物体进入与这个物体相连的触发碰撞器(只有2D物理)时发送 。

  2. 因为地面不会被搜集,所以需要判断是不是收集物,然后才收集隐藏。判断通过tag比较:

    if (collision.gameObject.CompareTag("PickUp"))
    collision.gameObject.SetActive(false);
  3. 完成后进行运行测试发现玩家与收集物接触时被弹开了,原因上面也提到过物理引擎在计算静态与动态对象发送碰撞时,会将动态对象(玩家)弹开。

    静态对象:任何带有2D碰撞器以及没有Rigidbody2D的游戏对象 ;

    动态对象:任何带有2D碰撞器和Rigidbody2D的游戏对象 ;

  4. 物理引擎允许穿透或者重叠的2D碰撞器区域。这时的2D引擎仍然会计算重叠区域并且会追踪碰撞器的重叠,但是不会有物理行为发生在重叠区域中也就是不会造成碰撞。

    实现方式:将收集物的2D碰撞器变为触发器

  5. 此时可以正常拾起收集物,但是有一个2D物理引擎的优化问题:当我们使用2D物理时,所有静态或者不移动的碰撞器都被计算成一个单体,这能提高性能。如果我们移动了一个引擎认为不会动的碰撞器,那么静态碰撞器或者所有的不移动的对象都将被重新计算,如果我们每一帧都这样做,游戏将会有损失性能的风险。

    这里我们旋转了收集物(移动了静态对象),所有每一帧都会重新计算。所以我们需要将收集物转为动态对象,也就是添加Rigidbody(注意添加时去掉重力)。

  6. 此时完成了收集物的拾起。

积分面板

  1. 通过增加UI的Text控件,显示当前分数,然后调整到自己喜爱的位置。

  2. 脚本中用一个变量来记录当前分数(因为不可人为改变,所以设为私有变量),每次收集金币时计数加1,并显示当前得分,当全部收集完毕显示你赢了!.

    count += 1;
    countText.text = "数量:" + count.ToString();
    if (count >= 9)
       WinText.text = "你赢了!";

此时一个完整的UFO收集金币游戏完成了。

完整的C#脚步

  • 玩家控制器脚本

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class PlayerController : MonoBehaviour
    {
    
    private Rigidbody2D rb2d;
    private int count = 0;
    public float speed;
    public Text countText, WinText;
    
    // Use this for initialization
    void Start()
    {
        rb2d = GetComponent<Rigidbody2D>();
        WinText.text = "";
    }
    
    private void FixedUpdate()
    {
        float moveHorizontal = Input.GetAxis("Horizontal");
        float moveVertical = Input.GetAxis("Vertical");
        Vector2 movement = new Vector2(moveHorizontal, moveVertical);
        rb2d.AddForce(movement * speed);
    }
    
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.CompareTag("PickUp"))
        {
            collision.gameObject.SetActive(false);
            SetCount();
        }
    }
    
    void SetCount()
    {
        count += 1;
        countText.text = "数量:" + count.ToString();
        if (count >= 9)
            WinText.text = "你赢了!";
    }
    }
  • 控制相机跟随玩家移动脚本

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class CameraController : MonoBehaviour {
    
    private Vector3 offset;
    public GameObject Player;
    
    // Use this for initialization
    void Start () {
        offset = transform.position - Player.transform.position;
    }
    
    // Update is called once per frame
    void LateUpdate () {
        transform.position = offset + Player.transform.position;
    }
    }
    
  • 控制金币旋转脚本

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class Pickup : MonoBehaviour 
    {
    // 旋转
    void Update () {
        transform.Rotate(new Vector3(0, 0, 45) * Time.deltaTime);
    }
    }
    

猜你喜欢

转载自blog.csdn.net/ilovexiaohao/article/details/80322170