Unity3D 初级案例 推箱子 完整项目 带详细注释

素材来源于 Unity Assets 商店

完整项目github链接

https://github.com/1520386112/Sokoban

运行效果

在这里插入图片描述

动画状态机

因为该项目对应的动画逻辑是按一下键播放一次移动动画,所以实际代码中并没有通过设置参数来控制动画,而是通过 anim.Player("")方法直接播放移动动画,然后自动过渡到对应的待机状态。
在这里插入图片描述

代码

MapContainer

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 存放地图信息
/// </summary>
public class MapContainer : MonoBehaviour
{
    public static int[,] snapshoot = {
    { 1, 1, 1, 1, 1, 1, 0, 0, 0},
    { 1, 0, 0, 0, 5, 1, 1, 0, 0},
    { 1, 2, 0, 0, 0, 0, 1, 0, 0},
    { 1, 0, 1, 0, 1, 1, 1, 1, 1},
    { 1, 0, 1, 0, 1, 0, 0, 5, 1},
    { 1, 0, 1, 0, 0, 3, 0, 1, 1},
    { 1, 0, 0, 0, 1, 3, 0, 1, 0},
    { 1, 0, 0, 0, 0, 0, 0, 1, 0},
    { 1, 1, 1, 1, 1, 1, 1, 1, 0}
    };
}

Player

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static GameController;

/// <summary>
/// 挂载在 player 上,主要用于控制动画的播放
/// </summary>
public class Player : MonoBehaviour
{
    private Animator animator;

    public string anim_down = "WalkDown";
    public string anim_left = "WalkLeft";
    public string anim_right = "WalkRight";
    public string anim_up = "WalkUp";

    private void Start()
    {
        animator = GetComponent<Animator>();
    }

    /// <summary>
    /// 根据 GameController 传入的输入方向来播放对应动画
    /// </summary>
    public void PlayPlayerAnimation(Direction dir)
    {
        switch (dir)
        {
            case Direction.DOWN:
                animator.Play(anim_down);
                break;
            case Direction.UP:
                animator.Play(anim_up);
                break;
            case Direction.LEFT:
                animator.Play(anim_left);
                break;
            case Direction.RIGHT:
                animator.Play(anim_right);
                break;
        }
    }
}

Box

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Box : MonoBehaviour
{
    public Sprite normalSprite;
    public Sprite onTargetSprite;

    private SpriteRenderer spriteRenderer;

    private void Start()
    {
        spriteRenderer = GetComponent<SpriteRenderer>();
    }

    public void SetSpriteToNormal()
    {
        spriteRenderer.sprite = normalSprite;
    }

    public void SetSpriteToOnTargetSprite()
    {
        spriteRenderer.sprite = onTargetSprite;
    }
}

GameController

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static MapContainer;

/// <summary>
/// 游戏逻辑核心类
/// </summary>
public class GameController : MonoBehaviour
{
    //初始化地图时根据 prefab 生成物体
    public GameObject playerPrefab;
    public GameObject boxPrefab;
    public GameObject targetPrefab;
    public GameObject wallPrefab;

    public GameObject gameWinText;

    /// <summary>
    /// 记录游戏是否胜利,胜利后取消监测玩家输入
    /// </summary>
    private bool gameWin;

    // 记录 Player 当前在 snapshoot 数组中的下标
    private int playerIndX;
    private int playerIndY;

    private Player player;

    /// <summary>
    /// 存储某个位置对应的箱子,方便根据位置拿到该处箱子
    /// </summary>
    private Box[,] boxes;

    /// <summary>
    /// 目标点数目,用于判断游戏是否结束
    /// </summary>
    private int targetNum;
    /// <summary>
    /// 目前已经完成的目标点数目
    /// </summary>
    private int currentCompleteNum;

    private enum TileType { NULL = 0, WALL = 1, PLAYER = 2, BOX = 3, TARGET = 5, PLAYER_TARGET = 7, BOX_TARGET = 8}
    
    /// <summary>
    /// direction 数组四个方向对应的下标
    /// </summary>
    public enum Direction {DOWN, UP, LEFT, RIGHT}

    private int[,] direction = { {1, 0}, {-1, 0}, {0, -1},{0, 1} };

    private void Start()
    {
        boxes = new Box[9, 9];

        InitMap();
    }

    private void Update()
    {
        if (!gameWin)
        {
            DetectInput();
        }
    }

    void InitMap()
    {
        for(int i = 0; i < 9; ++i)
        {
            for(int j = 0; j < 9; ++j)
            {
                GameObject go = null;
                switch (snapshoot[i,j])
                {
                    case (int)TileType.WALL:
                        go = CreateGameObject(wallPrefab, i, j);
                        go.name = "Wall" + i + "_" + j;
                        break;
                    case (int)TileType.PLAYER:
                        go = CreateGameObject(playerPrefab, i, j);
                        go.name = "Player";
                        player = go.GetComponent<Player>();
                        playerIndX = i;
                        playerIndY = j;
                        break;
                    case (int)TileType.BOX:
                        go = CreateGameObject(boxPrefab, i, j);
                        go.name = "Block";
                        boxes[i, j] = go.GetComponent<Box>();
                        break;
                    case (int)TileType.TARGET:
                        go = CreateGameObject(targetPrefab, i, j);
                        go.name = "Target";
                        targetNum++;
                        break;
                }
            }
        }
    }

    /// <summary>
    /// 监测玩家输入
    /// </summary>
    private void DetectInput()
    {
        if (Input.GetKeyDown(KeyCode.DownArrow))
        {
            Move(Direction.DOWN);
        }
        else if (Input.GetKeyDown(KeyCode.UpArrow))
        {
            Move(Direction.UP);
        }
        else if (Input.GetKeyDown(KeyCode.LeftArrow))
        {
            Move(Direction.LEFT);
        }
        else if (Input.GetKeyDown(KeyCode.RightArrow))
        {
            Move(Direction.RIGHT);
        }
    }

    private void Move(Direction dir)
    {
        //播放角色动画
        player.PlayPlayerAnimation(dir);

        int aimPosX = playerIndX + direction[(int)dir, 0];
        int aimPosY = playerIndY + direction[(int)dir, 1];
        
        switch (snapshoot[aimPosX, aimPosY])
        {
            //目标点为空或者是目标点,直接将玩家移动过去即可
            case (int)TileType.NULL:
            case (int)TileType.TARGET:
                MoveTile(playerIndX, playerIndY, aimPosX, aimPosY, TileType.PLAYER);
                break;
            
            //目标点是箱子
            case (int)TileType.BOX:
            case (int)TileType.BOX_TARGET:
                int nextNextX = aimPosX + direction[(int)dir, 0];
                int nextNextY = aimPosY + direction[(int)dir, 1];             

                switch (snapshoot[nextNextX, nextNextY])
                {
                    //目标方向的下下块没有阻挡物时,方可移动
                    case (int)TileType.NULL:
                    case (int)TileType.TARGET:
                        MoveTile(playerIndX, playerIndY, aimPosX, aimPosY, TileType.PLAYER);
                        MoveTile(aimPosX, aimPosY, nextNextX, nextNextY, TileType.BOX);
                        break;
                }
                break;
        }
    }

    /// <summary>
    /// 移动某个Tile,更新地图快照以及物体位置
    /// <param name="tileType">需要移动的块类型</param>
    /// </summary>
    private void MoveTile(int originX, int originY, int aimX, int aimY, TileType tileType)
    {
        //在目标点的箱子被推动,当前完成数减一
        if(snapshoot[originX, originY] == (int)TileType.BOX_TARGET)
        {
            currentCompleteNum--;
        }

        /* 直接将目标点处的信息数字加上移动的块对应的数即可
         * TileType 枚举的特殊数字就是为了如此操作而设计的
         */
        snapshoot[aimX, aimY] += (int)tileType;
        snapshoot[originX, originY] -= (int)tileType;

        //推动一个箱子到目标点,当前完成数加一
        if(snapshoot[aimX, aimY] == (int)TileType.BOX_TARGET)
        {
            currentCompleteNum++;
            if(currentCompleteNum == targetNum)
            {
                GameWin();
            }
        }

        switch (tileType)
        {
            case TileType.PLAYER:
            case TileType.PLAYER_TARGET:
                player.transform.position = new Vector2(aimY, -aimX);
                playerIndX = aimX;
                playerIndY = aimY;
                break;

            case TileType.BOX:
            case TileType.BOX_TARGET:
                boxes[aimX, aimY] = boxes[originX, originY];
                boxes[originX, originY] = null;

                boxes[aimX, aimY].transform.position = new Vector2(aimY, -aimX);

                //移动后根据箱子是否在目标点上修改箱子的Sprite
                if(snapshoot[aimX, aimY] == (int)TileType.BOX_TARGET)
                    boxes[aimX, aimY].SetSpriteToOnTargetSprite();
                else
                    boxes[aimX, aimY].SetSpriteToNormal();

                break;
        }
    }

    GameObject CreateGameObject(GameObject go, int row, int col)
    {
        return Instantiate(go, new Vector3(col, -row), Quaternion.identity);
    }

    private void GameWin()
    {
        gameWin = true;
        gameWinText.SetActive(true);
        Debug.Log("游戏胜利");
    }
}

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值