今天来了解一下地图系统,这个相对简单

​ image-20260326112434254

首先我们在Hierarchy(层级)窗口里右键

然后2D object(二维对象)->tilemap(瓦片地图)->rectangular(矩形瓦片)

这里我们管2D游戏里的地图统称tilemap即瓦片,这个在其他游戏引擎里也是相同的定义,瓦片就是附着在游戏地图上的“贴图”

创建完成之后就会看到层级窗口出现了这个东西,不管你选择什么类型的瓦片都是以Grid(网格) 作为父对象的

​ image-20260326112939546

但是有什么用呢?这时候就需要一个东西了—–那就是瓦片面板 tile palette

​ image-20260326113100791

众所周知,在游戏引擎中,如果场景中充满了不同资源,那么将会占用非常巨大的内存(很多大厂的游戏动辄100G 200G就是因为里面充满了美术和建模资源),然而如果场景中都是相同的资源,比如NPC小怪都是一样的,采用的都是相同的贴图资源,那么其实所有小怪贴图占用的存储空间其实只有一张贴图的存储空间,这里就体现地图系统的优点了—–在场景中轻而易举的复用贴图资源而非一张一张的手动调整

首先第一步,选中一张贴图

​ image-20260326113922955

然后左键长按拖到右边面板中

​ image-20260326114008462

然后你就可以看到右边的面板出现了这个贴图

然后我们就可以像使用油画盘蘸颜料一样在Scene场景中使用这些贴图

当我们选中一张贴图时,就可以看到上面自动切换到这个画笔模式

​ image-20260326114400352

然后在scene场景里按住左键画几笔就会发现,贴图就像颜料一样附着在游戏场景中

​ image-20260326114552239

这里还可以批量框选填充

就是选择画笔形状旁边的那个fill填充模式

​ image-20260326114702852

以此类推,我们就画好地图了

​ image-20260326115222320

然而现实没有这么“轻而易举”,这种确实很适合制作传说之下星露谷物语这种类型的游戏,但是玩家更喜欢地图是变化的,可破坏的,或者卷轴滚动的,就先不说这个了

哦对了,忘记说怎么制作瓦片地图了,这个也很简单,不过在此之前我们学一下怎么删除不小心放进入的多余的瓦片

首先在tile palette中选择这个多余的瓦片,然后查看右侧的inspector检查器窗口

​ image-20260326120846757

可以看到已经删掉了

​ image-20260326120701704

瓦片的制作分为两种,第一种就是不切割整个放进去适合小瓦片,例如我们刚才放的人物

​ 这种直接把png图片拖入unity的asset文件夹里,然后拖到tile palette即可

​ 但是这种不适合大图片,不然就会创造出巨大无比的瓦片

​ image-20260326121501537

所以我们需要切割瓦片

首先选中图片,然后在inspector里注意选择这两个选项—–sprite mode选择多张图mulyiple,为我们切割图片做准备

filter mode 选择 point(no filter)邻点差值采样,不然放进入的图片糊的一批

​ image-20260326121844173

然后点击apply 应用设置,并点击sprite editor精灵图编辑器

​ image-20260326122451764

好的,我们刚才的瓦片地图的大小是16×16的,所以切割时可以选择16×16

​ image-20260326122756124

这里我们选择64×64吧

​ image-20260326122941128

然后就会看到蓝发娘被我们“碎尸”了

​ image-20260326123120700

使用的话可以按住shift加选(而按住ctrl是减选)

​ image-20260326123406515

然后就可以看到

​ image-20260326131854826

当然为了避免素材之间出现鱼龙混杂的情况,我们可以对于每一个场景都创建一个瓦片面板

​ image-20260326132808554

好地图讲完了,其实还有一个东西就是地图碰撞体组件,也就是地图空气墙,这里由于用不上就不说了

然后就是完善我们的人物,上次我说了这是跟着教程做的半成品,这次来完善角色的枪械发射功能\

角色枪械发射功能

上次我们的角色的脚本是这个

C#已复制
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player_Controller : MonoBehaviour
{
    // 玩家移动相关变量
    public float playerSpeed = 5f;//玩家速度
    private Rigidbody2D playerRigidbody;//玩家刚体
    private Animator playerAnimator;//玩家动画
    private Vector2 movementInput;//玩家输入,用于移动

    //武器相关变量
    public GameObject[] weapons;//武器数组
    private int currentWeaponIndex;//当前武器索引
    private Vector2 mousePosition;//鼠标位置


    void Start()
    {
        GetComponents();//获取组件引用

        weapons[0].SetActive(true);//默认激活第一把武器
    }

    void Update()
    {
        MovementLogic();//玩家控制行为
        SwitchWeapon();//切换武器
    }

    //获取组件引用
    void GetComponents()
    {
        playerRigidbody = GetComponent<Rigidbody2D>();
        playerAnimator = GetComponent<Animator>();
    }

    //玩家控制行为
    void MovementLogic()
    {
        //获取玩家输入
        movementInput.x = Input.GetAxisRaw("Horizontal");
        movementInput.y = Input.GetAxisRaw("Vertical");

        //移动玩家的刚体而非直接移动Transform
        playerRigidbody.velocity = movementInput.normalized * playerSpeed;

        //获取鼠标在世界坐标中的位置
        mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);

        //根据鼠标位置调整玩家朝向
        if (mousePosition.x > transform.position.x)
        {
            transform.rotation = Quaternion.Euler(new Vector3(0, 0, 0));
        }
        else
        {
            transform.rotation = Quaternion.Euler(new Vector3(0, 180, 0));
        }

        //根据角色移动状态切换动画
        if (movementInput != Vector2.zero)
        {
            playerAnimator.SetBool("isMoving", true);
        }
        else
        {
            playerAnimator.SetBool("isMoving", false);
        }
    }

    //切换武器
    void SwitchWeapon()
    {
        if (Input.GetKeyDown(KeyCode.Q))
        {
            weapons[currentWeaponIndex].SetActive(false);
            if (--currentWeaponIndex < 0)
            {
                currentWeaponIndex = weapons.Length - 1;
            }
            weapons[currentWeaponIndex].SetActive(true);
        }
        if (Input.GetKeyDown(KeyCode.E))
        {
            weapons[currentWeaponIndex].SetActive(false);
            if (++currentWeaponIndex > weapons.Length - 1)
            {
                currentWeaponIndex = 0;
            }
            weapons[currentWeaponIndex].SetActive(true);
        }
    }
}

已经是实现了武器的切换功能

但是发射功能的话就需要给武器写一个脚本,

这里你可能会疑问为什么武器切换逻辑放到玩家脚本中,因为切换武器这个行为属于玩家,不是武器自己切换

因为是面向对象,则与对象相关的所有内容需要放到对象相关的类中

后面会涉及面向对象的继承,不过我们先把精力全部放到单个武器上(为什么需要继承? 因为狙击枪,霰弹枪,手枪都属于枪,如果给每一个枪都写一个独立脚本浪费资源,但让所有枪都继承“枪”这个脚本就会节省代码量还便于管理)

​ image-20260326135108071

对了,你可能不知道这个蓝色的条是什么,这个其实就是当预制体配置发生改变时就会出现

只需要在inspector检查器窗口里覆盖实现更改就行

​ image-20260326135311665

不过也可以斩断这个对象与文件夹中的预制体的联系,通过unpack解除联系,这样对这个对象的修改就不会影响到预制体了

​ image-20260326135416682

这里是pistol手枪的脚本

C#已复制