Unity2017 经典游戏开发教程 算法分析与实现 (张帆 著)

https://meta.box.lenovo.com/link/view/82c451b41ce34e81a4b34cb46747d3d5

 

第1章 熟悉Unity软件的操作

第2章 打地鼠 (已看)

第3章 俄罗斯方块

第4章 打砖块 (已看)

第5章 三消 (已看)

第6章 翻牌子 (已看)

第7章 连连看

第8章 拼图 (已看)

第9章 推箱子 (已看)

第10章 炸弹人  (已看)

第11章 华容道

第12章 横板跑酷

第13章 扫雷

第14章 贪吃蛇

第15章 五子棋

第16章 跳棋

第17章 吃豆人

第18章 斗地主

第19章 坦克大战

2111

第1章 熟悉Unity软件的操作

  1.1 可多平台发布的Unity游戏引擎

 

  1.2 Unity游戏引擎的下载和安装

 

    1.2.1 Unity的下载

 

    1.2.2 Unity的安装

 

    1.2.3 Unity的注册

 

    1.2.4 启动Unity

 

  1.3 认识Unity的编辑界面

 

    1.3.1 软件标题栏

 

    1.3.2 主菜单

 

    1.3.3 Project项目资源窗口

 

    1.3.4 Hierarchy层级窗口

 

    1.3.5 Scene场景窗口

 

    1.3.6 Inspector组件属性面板

 

    1.3.7 Game游戏预览窗口

 

    1.3.8 Console控制台

 

  1.4 自定义窗口布局

 

    1.4.1 使用Unity内置的窗口布局功能

 

    1.4.2 自定义窗口布局

 

  1.5 Unity中定义的重要概念

 

    1.5.1 资源(Assets)

 

    1.5.2 工程(Project)

 

    1.5.3 场景(Scenes)

 

    1.5.4 游戏对象(GameObject)

 

    1.5.5 组件(Component)

 

    1.5.6 脚本(Scripts)

 

    1.5.7 预置(Prefabs)

 

第2章 打地鼠

  2.1 游戏简介

《打地鼠》(原名: Whac-A-Mole)最早可以追溯到1976年创意工程股份有限公司(Creative Engineering,Inc.)推出的一款有偿街机游戏.这款游戏能够提高人体大脑和身体的协调能力,增强臂力, 锻炼身体,深受大众的喜爱. 在游戏中, 玩家要在规定时间内,敲打一只只从地洞力冒出头的傻地鼠,从而获得游戏胜利.但是作为掌机,如NDS或智能手机等是不可能直接用锤子去敲打屏幕的, 取而代之的便是NDS触控笔或者手指

  2.2 游戏规则

这个游戏的游戏规则是,在一定的时间内,地鼠随机出现在九个洞口中, 玩家要在它出现的时候击中它,击中加分,反之地鼠会自动消失,时间耗尽则游戏结束

随着游戏时间的减少, 老鼠出现的频率越来越高,存在的时间也越来越短,游戏难度逐步上升

  2.3 程序思路

    2.3.1 洞口的排列

利用一个3 x 3 的二维数组或者一个一维数组来表示地图中的9个洞口

    2.3.2 地鼠出现频率

public void start() {
    InvokeRepeating("canAppear", 0, 5);
}

public void canAppear() {
    InvokeRepeating("地鼠生成函数",0,1);
}
View Code

    2.3.3 单个地鼠设置

if (未被点击) {
    Destroy(this.gameObject, 3.0f);
} else {
    Destroy(this.gameObject);
}
View Code

    2.3.4 游戏时间和分数

    2.3.5 游戏流程图

Unity2017 经典游戏开发教程 算法分析与实现 (张帆 著)_第1张图片

  2.4 程序实现

    2.4.1 前期准备

    2.4.2 设置洞口

    2.4.3 单只地鼠的出现和消失

    2.4.4 地鼠的随机出现和出现频率

    2.4.5 时间, 分数和其他.

Unity2017 经典游戏开发教程 算法分析与实现 (张帆 著)_第2张图片

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

/// 
/// 地鼠被击打以后调用该函数
/// 
public class Done_Beaten : MonoBehaviour {

    public int id;
    /// 
    /// 在点击后3.5s销毁该地鼠,将洞口的isAppear值设置为false
    /// 
    void Update () {
        Destroy(gameObject, 0.35f);
        FindObjectOfType().holes[id].isAppear = false;
    }
}


using System.Collections;
using UnityEngine;

/// 
/// 鼠标追踪
/// 
public class Done_ChangeCursor : MonoBehaviour
{
    public Texture2D UserCursor;
    public Texture2D UserClickCursor;
    private bool isClicked = false;

    void Start ()
    {
        //在游戏界面内隐藏系统光标
        Cursor.visible = false;
    }

    /// 
    /// 时时判断鼠标点击状态,便于切换图像
    /// 
    void Update ()
    {
        if (Input.GetMouseButton (0))
        {
            isClicked = true;
        }
        else
        {
            isClicked = false;
        }
    }

    void OnGUI ()
    {
        //获取鼠标当前位置
        Vector2 mouse_pos = Input.mousePosition;

        //鼠标点击与否显示相应的图片
        if (isClicked == false)
        {
            GUI.DrawTexture(new Rect(mouse_pos.x -40f, Screen.height - mouse_pos.y -60f , 100, 100), UserCursor);
        }
        else
        {
            GUI.DrawTexture(new Rect(mouse_pos.x - 40f, Screen.height - mouse_pos.y -60f, 100, 100), UserClickCursor);
        }
    }

}


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

/// 
/// 游戏总控制脚本
/// 
public class Done_GameControl : MonoBehaviour {


    public GameObject Gophers;
    public int PosX, PosY;


    public TextMesh timeLabel;
    public float time = 30.0f;
    public int score = 0;
    

   
    /// 
    /// 设定一个地洞类,存储地洞的坐标以及是否出现的布尔值
    /// 
    public class Hole {
        
        public bool isAppear;
        public int HoleX;
        public int HoleY;
        
    }

    public Hole[] holes;


    /// 
    /// Awake函数实际上比Start函数调用的更早
    /// 在场景初始化的时候,将每个洞口的坐标值存入一维数组中,并将每一个洞口的isAppear值设定为false
    /// (-2,0) (0,0) (2,0)
    /// (-2,-1)(0,-1)(2,-1)
    /// (-2,-2)(0,-2)(2,-2)
    /// 
    void Awake() {
        
        PosX = -2;
        PosY = -2;
        holes = new Hole[9];
        for (int i = 0; i < 3; i++)
        {
            for (int j = 0; j < 3; j++)
            {
                holes[i * 3 + j] = new Hole();
                holes[i * 3 + j].HoleX = PosX;
                holes[i * 3 + j].HoleY = PosY;
                holes[i * 3 + j].isAppear = false;

                PosY++;
            }
            PosY = -2;
            PosX = PosX + 2;
        }
    }
    /// 
    /// 在游戏场景开始后延时调用canAppear函数,从第0秒开始,每隔十秒调用一次
    /// 
    void Start () {

        InvokeRepeating("CanAppear", 0, 10);

    }

    /// 
    /// 游戏时间控制
    /// 
    void Update () {

        //时间以秒的速度减少,并在timeLabel里显示当前剩余时间(一位小数)
        time -= Time.deltaTime;
        timeLabel.text = "Time: " + time.ToString("F1");

        //当时间耗尽,调用GameOver函数
        if (time < 0) {
            GameOver();
        }
        
    }

    /// 
    /// 从第0秒开始调用函数,每隔1秒调用一次
    /// 
    public void CanAppear() {

        InvokeRepeating("Appear", 0,1);

    }

    /// 
    /// 地鼠生成函数
    /// 
    public void Appear()
    {
        //当前地洞可以生成地鼠的条件: isAppear = false         
        //随机生成i值选择洞口,直到符合条件的洞口被选中
        int i = Random.Range(0, 9);        
        while (holes[i].isAppear == true){
            i = Random.Range(0, 9);
        }
        //debug只是用来打印当前的坐标,便于观察,并不会影响游戏运行(可写可不写)
        Debug.Log(holes[i].HoleX + "," + holes[i].HoleY);

        //选定洞口以后,在洞口的坐标上生成地鼠,传递洞口id,将当前洞口的isAppear值改为true
        Instantiate(Gophers, new Vector3(holes[i].HoleX, holes[i].HoleY + 0.4F, -0.1F), Quaternion.identity);
        Gophers.GetComponent().id = i;
        holes[i].isAppear = true;

       
    }
    
    /// 
    /// 游戏结束函数
    /// 
    void GameOver() {
        time = 0;
        timeLabel.text = "Time: 0";

        //将所有延时调用函数全部取消
        CancelInvoke();

        Debug.Log("game over");
    }
}


using UnityEngine;

/// 
/// 地鼠类
/// 
public class Done_Gophers : MonoBehaviour {
    
    public GameObject beaten;
    
    public int id;

    /// 
    /// 地鼠出现后,如果未被点击,将在三秒后自动销毁,将相应洞口的isAppear值设置为false
    /// 
    void Update () {
        
        Destroy(this.gameObject,3.0f);
        FindObjectOfType().holes[id].isAppear = false;
    }
   
    /// 
    /// 鼠标点击
    /// 
    void OnMouseDown() {
        Debug.Log("点击");

        //在相同的位置生成一个被击打图像的地鼠
        GameObject g;
        g = Instantiate(beaten, gameObject.transform.position, Quaternion.identity);
        g.GetComponent().id = id;

        //增加分数
        FindObjectOfType().score += 1;
        int scores = FindObjectOfType().score;
        GameObject.Find("Score").gameObject.GetComponent().text = "Score: " +scores.ToString();
        
        //FindObjectOfType<>().text = score.ToString();
        //GetComponentInChildren().text = score.ToString();
        //scoreLabel.text = "Score: " + score; 

        //在0.1s后摧毁当前生成的地鼠
        Destroy(this.gameObject, 0.1f);
        
    }

    

}


using UnityEngine;
using UnityEditor.SceneManagement;

/// 
/// 重新开始函数
/// 
public class Done_Restart : MonoBehaviour {
    /// 
    /// 按钮被点击以后,重新调用游戏场景
    /// 
    public void OnMouseDown() {
        Debug.Log("restart");
        EditorSceneManager.LoadScene("Done_mole");
    }
}
View Code

第3章 俄罗斯方块

  3.1 游戏简介

 

  3.2 游戏规则

 

  3.3 游戏实现思路

 

    3.3.1 随机生成方块

 

    3.3.2 地图的生成

 

    3.3.3 判断方块是否都在边界内

 

    3.3.4 判断是否碰到其他方块

 

    3.3.5 检查是否满行

 

    3.3.6 删除填满的行

 

    3.3.7 提示下一个方块组

 

    3.3.8 结束判定

 

    3.3.9 游戏流程图

 

  3.4 游戏程序实现

 

    3.4.1 前期准备

 

    3.4.2 制作场景

 

    3.4.3 生成方块组与方块组下落

 

    3.4.4 边界判断

 

    3.4.5 删除一行方块

 

    3.4.6 结束判定

 

    3.4.7 细节完善

 

第4章 打砖块

  4.1 游戏简介

打砖块是一款十分简单的小游戏, 只需要打掉所有的砖块即可获得胜利

《Breakout》,世界第一款打砖块游戏, 1976年由英宝格公司发行. 游戏设计者是后来创立苹果电脑公司的史蒂夫,乔布斯与斯蒂夫,沃兹尼亚克两人,程序设计是Brad Stewart

《Gee Bee》, 日本Namco公司在1978年推出的该公司第一款街机游戏, 合并了打砖块和弹珠台游戏的特色

快打砖块《Arkanoid》, 日本泰托(Taito)公司在1986年推出的街机游戏, 引入了电脑控制的敌机,还有后来打砖块中常见的加强道具(Powerup Item)等元素

  4.2 游戏规则

玩家操作在屏幕底端的横板, 通过反弹小球的方式, 使小球击打并消除砖块,, 只需要打掉所有的砖块即可获得胜利.小球掉落至横板下方即失败

  4.3 程序思路

    4.3.1 地图生成

for (x轴) {
    for (y轴) {
        生成砖块;
    }
}

XXXXXXXXXXXXX
RRRRRRRRRRRRR
YYYYYYYYYYYYY
BBBBBBBBBBBBB
GGGGGGGGGGGGG
PPPPPPPPPPPPP
XXXXXXXXXXXXX
XXXXXXXXXXXXX
XXXXXXXXXXXXX
RRRRRRRRRRRRR
YYYYYYYYYYYYY
BBBBBBBBBBBBB
GGGGGGGGGGGGG
PPPPPPPPPPPPP
XXXXXXXXXXXXX

XXXXRRRRRXXXX
XXXRRRRRRRXXX
XXRRRRRRRRRXX
XRRYRYRYRYRRX
RRRRRRRRRRRRR
XXRRRXRXRRRXX
XXXRXXXXXRXXX
XXXYXXXXXYXXX
XXXXYXXXYXXXX
XXXYYYYYYYXXX
XXYYXYYYXYYXX
XYYYYYYYYYYYX
XYXYYYYYYYXYX
XYXYXXXXXYXYX
XXXXYYXYYXXXX

XXXRRRRRXXXX
XXRRRRRRRRRX
XXGGGYYPYXXX
XGYGYYYPYYYX
XGYGGYYYPYYY
XXGYYYYPPPPX
XXXYYYYYYXXX
XXRRBRRBRRXX
XRRRBRRBRRRX
RRRRBBBBRRRR
YYRBYBBYBRYY
YYYBBBBBBYYY
YYBBBBBBBBYY
XXBBBXXBBBXX
XGGGXXXXGGGX
GGGGXXXXGGGG

GGGGGGGGGGGGG
GGGGGGGGGGGGG
GGXXXGGGXXXGG
GGXXXGGGXXXGG
GGXXXGGGXXXGG
GGGGGXXXGGGGG
GGGGGXXXGGGGG
GGGXXXXXXXGGG
GGGXXXXXXXGGG
GGGXXGGGXXGGG
GGGXXGGGXXGGG
GGGGGGGGGGGGG
GGGGGGGGGGGGG

XXXXXXRRRXXX
XXXXXRRRRRXX
XXXXXRGGGGXX
XXXRRRRRRRRR
XXXXRBBBBBBX
XXXXXBXBYXBX
XXXXXBBYYYBX
XYYXXBBYYYYY
YYYYXBYYYYYX
YYYYBBBBBBXX
XYYBYBBBBBBX
XXYBYBBBBBBB
XXXYBBBBBBXB
XXXXYBBBBBXX
XXXXBBBBBBBX
XXXXBBXXXBBX
XXXYYYXXXYYY
View Code

    4.3.2 砖块控制

    4.3.3 小球控制

float HitFactor {
    return (ballPos.x - racketPos.x) / racketWidth;
}

if (碰撞板) {
    小球的 xSpeed = HitFactor;
}
View Code

    4.3.4 游戏流程图

Unity2017 经典游戏开发教程 算法分析与实现 (张帆 著)_第3张图片

  4.4 程序实现

    4.4.1 前期准备

    4.4.2 游戏场景设定

    4.4.3 横板控制

    4.4.4 小球控制

    4.4.5 砖块的生成及控制

    4.4.6 道具的控制

Unity2017 经典游戏开发教程 算法分析与实现 (张帆 著)_第4张图片

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

public class Done_Ball : MonoBehaviour {
    public float BallSpeed = 18f;//如果速度过快,可以减小该值,但是可能受到重力影响,不能弹到上方,可以适当减小重力值,如:速度设置为12,Unity中Physics2D的重力值应为-4.45
    int num = 0;

    // Use this for initialization
    void Start()
    {



    }

    // Update is called once per frame
    void Update()
    {

        if (Input.anyKey && num == 0)//num控制小球是不是第一次离开横板
        {
            GetComponent().velocity = Vector2.up * BallSpeed;
            num++;
        }


        if (transform.position.y < -8)//小球掉落后重载场景
        {
            SceneManager.LoadScene("Done_Break Out");
        }
    }

    /// 
    /// 发球的碰撞触发器
    /// 
    /// 
    private void OnCollisionEnter2D(Collision2D col)
    {
        if (col.gameObject.name == "racket" && num == 1)
        {
            float x = HitFactor(transform.position,
                                col.transform.position, col.collider.bounds.size.x
                                );

            Vector2 dir = new Vector2(x, 1).normalized;
            GetComponent().velocity = dir * BallSpeed;
        }


    }

    /// 
    /// 球与板接触位置与反弹方向的公式
    /// 
    /// 
    /// 
    /// 
    /// 
    float HitFactor(Vector2 ballPos, Vector2 racketPos, float racketWidth)
    {
        return (ballPos.x - racketPos.x) / racketWidth;
    }
}


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

public class Done_Block : MonoBehaviour {

    public string color;
    public int hits_required;
}


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

public class Done_BlockController : MonoBehaviour
{
    public GameObject upgradePrefab;
    // Use this for initialization
    void Start()
    {

        string spriteFileName = "sprites/block_" + GetComponent().color;//获取颜色名称

        this.GetComponent().sprite = Resources.Load(spriteFileName);//贴图

    }

    /// 
    /// 球与砖块的碰撞检测
    /// 
    /// 
    void OnCollisionEnter2D(Collision2D col)
    {
        GameObject go = GameObject.Find("Main Camera");
        Done_LevelLoader levelLoader = go.GetComponent();
        gameObject.GetComponent().hits_required -= 1;

        if (gameObject.GetComponent().hits_required == 0)
        {
            Destroy(gameObject);
            levelLoader.block_count--;
            if (Random.value < 0.10)//生成概率
            {
                Instantiate(upgradePrefab,
                            new Vector3(
                                col.gameObject.transform.position.x,
                                col.gameObject.transform.position.y,
                                0),
                            Quaternion.identity);

            }
        }
        
    }
}


using UnityEngine;
using System.Collections;
using System.Text;
using System.IO;

public class Done_LevelLoader : MonoBehaviour
{

    public Done_Block block;
    public int block_count = 0;

    // Use this for initialization
    void Start()
    {
        string level = getRandomLevelName();
        //Debug.Log(level);
        LoadLevel(level);
    }

    public string getRandomLevelName()//随机获取地图名称
    {
        int level = Random.Range(1, 5);

        //通过地图名称读取文件夹中的txt
        return "Assets/_Complete-Game/Levels/level_" + level + ".txt";
    }

    /// 
    /// 载入地图
    /// 
    /// 
    public void LoadLevel(string levelName)
    {
        try
        {
            string line;
            StreamReader reader = new StreamReader(levelName, Encoding.Default);
            using (reader)
            {
                float pos_x = -5f;//初始克隆方块位置
                float pos_y = 5.8f;
                line = reader.ReadLine();
                while (line != null)
                {
                    char[] characters = line.ToCharArray();
                    foreach (char character in characters)
                    {
                        if (character == 'X')
                        {
                            pos_x += 0.87f;
                            continue;
                        }
                        Vector2 b_pos = new Vector2(pos_x, pos_y);
                        Done_Block b = Instantiate(block, b_pos, Quaternion.identity);
                        b.GetComponent().size = new Vector2(0.8f, 0.4f);//方块大小
                        switch (character)
                        {
                            case 'B':
                                b.GetComponent().color = "blue";
                                b.GetComponent().hits_required = 3;
                                block_count++;
                                break;
                            case 'G':
                                b.GetComponent().color = "green";
                                b.GetComponent().hits_required = 2;
                                block_count++;
                                break;
                            case 'P':
                                b.GetComponent().color = "pink";
                                b.GetComponent().hits_required = 1;
                                block_count++;
                                break;
                            case 'R':
                                b.GetComponent().color = "red";
                                b.GetComponent().hits_required = 5;
                                block_count++;
                                break;
                            case 'Y':
                                b.GetComponent().color = "yellow";
                                b.GetComponent().hits_required = 4;
                                block_count++;
                                break;
                            default:
                                Destroy(b);
                                break;
                        }
                        pos_x += 0.87f;//每块克隆方块间隔
                    }
                    pos_x = -5.5f;
                    pos_y -= 0.45f;
                    line = reader.ReadLine();
                }
                reader.Close();
            }
        }
        catch (IOException e)
        {
            Debug.Log(e.Message);
            // Update is called once per frame
        }
    }
}


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

public class Done_Racket : MonoBehaviour {
    public float speed = 10.0f;//横板移动速度
                              // Use this for initialization
    void Start () {
        
    }
    
    // Update is called once per frame
    void Update () {
        
            if (Input.GetKey(KeyCode.LeftArrow))
            {
                //当球拍未超过屏幕左侧时移动球拍,否则球拍不能再移动  
                if (transform.position.x > -5.2)
                {
                    transform.Translate(Vector3.left * Time.deltaTime * speed);
                }
                else
                {
                    return;
                }
            }
            else if (Input.GetKey(KeyCode.RightArrow))
            {
                if (transform.position.x < 5.2)
                {
                    transform.Translate(Vector3.right * Time.deltaTime * speed);
                }
                else
                {
                    return;
                }
            }
    }

    /// 
    /// 道具与板接触的触发器
    /// 
    /// 
    void OnTriggerEnter2D(Collider2D col)
    {
        if (col.gameObject.tag == "upgrade")
        {
            string name = col.gameObject.GetComponent().upgradeName;
            performUpgrade(name);
            Destroy(col.gameObject);
        }

    }

    /// 
    /// 道具生效
    /// 
    /// 
    void performUpgrade(string name)
    {
        // removing Unity-attached suffixed data to get original sprite name
        name = name.Remove(name.Length - 21);
        float x;
        Done_Ball ballController = GameObject.Find("ball").GetComponent();
        switch (name)
        {
            case "ball_speed_up":
                if (ballController.BallSpeed < 27)
                {
                    ballController.BallSpeed += 3;//当小球速度小于27,并且道具为ball_speed_up时,小球速度+3,以下类似。
                }
                break;
            case "ball_speed_down":
                if (ballController.BallSpeed > 18)
                {
                    ballController.BallSpeed -= 3;
                }
                break;

            case "paddle_size_up":
                x = this.gameObject.transform.localScale.x;
                if (x < 8.0f)
                    this.gameObject.transform.localScale = new Vector3(
                                                                    x += 0.25f,
                                                                       this.gameObject.transform.localScale.y,
                                                                       1.0f);
                break;
            case "paddle_size_down":
                x = this.gameObject.transform.localScale.x;
                if (x > 4.0f)
                    this.gameObject.transform.localScale = new Vector3(
                                                                    x -= 0.25f,
                                                                    this.gameObject.transform.localScale.y,
                                                                    1.0f);

                break;
            case "paddle_speed_up":
                speed += 3;
                break;
            case "paddle_speed_down":
                if (speed > 7)
                {
                    speed -= 3;
                }
                break;
            default:
                break;
        }
    }
}


using UnityEngine;
using System.Collections;

public class Done_UpGrade : MonoBehaviour
{

    public Sprite[] upgradeSprites;
    public string upgradeName = "";

    // Use this for initialization
    void Start()
    {
        Sprite icon = upgradeSprites[Random.Range(0, upgradeSprites.Length)];//随机选择图片
        upgradeName = icon.ToString();//与图片对应的道具名字
        this.gameObject.GetComponent().sprite = icon;//贴图
    }

    ///
    // Update is called once per frame
    void Update()
    {
        //道具位置刷新
        this.gameObject.transform.position = new Vector3(this.gameObject.transform.position.x,
                                                            this.gameObject.transform.position.y - 0.05f,
                                                            0);
        //如果道具低于横板,则销毁
        if (gameObject.transform.position.y <= -8.0f)
            Destroy(this.gameObject);
    }
}
View Code

第5章 三消

  5.1 游戏简介

三消类游戏的鼻祖是诞生于2000年的《宝石迷阵》.《宝石迷阵》是一款锻炼眼力的宝石交换消除游戏, 游戏的目标是将一颗宝石与邻近的宝石交换位置,形成一种水平或垂直的三颗或更多颗宝石的宝石链.当有超过3颗相同宝石的宝石链形成时,或两个链在一个交换中形成,就会得到额外的奖励.当链形成时,链上的宝石会消失,另有宝石从顶部掉落,以填补缺口. 有时, 连锁反应被称为瀑布效应,被落下的宝石所填充.连击将被奖励积分, 有两种不同的游戏模式可供选择.正常模式下, 玩家通过匹配宝石来填满屏幕底部的进度条

这款游戏2002年入选IGN主办的世界电脑游戏名人堂,成为继《俄罗斯方块》后第二款入选的同类游戏.迄今为止宝石迷阵已成长为拥有五部作品的系列作,拥有超过五亿玩家,登陆了当今几乎所有主流平台(PC, 手机, PS2, PS3, PSP, XBox, XBox360, NDS, NDSi, Wii等),成为同类游戏种的No.1 

  5.2 游戏规则

玩家选择两个宝石进行位置交换,互换后如果横排或竖排有3个以上相同的宝石,则消去这几个相同的宝石,如果互换后没有可以消去的宝石,则选中的两个宝石换回原来的位置.消去后的空格由上面的宝石掉下来补齐.每次消去宝石玩家都能得到一定的分数

连锁: 玩家消去宝石后, 上面的宝石会掉下来补充空格.如果这时游戏池中有连续摆放(横,竖)的3个或3个以上相同的宝石,则可以消去这些宝石,这就是一次连锁,空格被新的宝石填充,又可以进行下一次连锁. 每次连锁会有加分

重排: 玩家已经不能消去任何宝石时, 将清空游戏池, 用新的宝石填充

  5.3 程序思路

    5.3.1 地图生成

这里使用列表(动态数组),数组内每个坐标代表一颗宝石.每一条temp列表为一行,一行建立完后将此条temp列表存入List列表中.动态数组的优点: 可以实时改变数组长度, 并且可在指定位置插入新的元素

Unity2017 经典游戏开发教程 算法分析与实现 (张帆 著)_第5张图片

由图可知,我们可以先由行号[1]找到对应的temp列表链,之后再由列号[1]找到对应的元素,这个元素就是对应二维数组的[1][1]了

宝石的随机生成: 利用随机函数,随机填充不同的宝石

    5.3.2 消除检测

获取图案相同的对象, 一定要以一个对象为基准,这样才能够知道以谁为中心,以这个中心为核心横向及纵向的检测,检测到三个及以上的对象,那说明是可以消除的对象

从左上至左下, 横向遍历, 将符合消除条件的方块存储在临时数组里

Unity2017 经典游戏开发教程 算法分析与实现 (张帆 著)_第6张图片

从左上至右上, 竖向遍历, 将符合消除条件的方块存储在临时数组里

Unity2017 经典游戏开发教程 算法分析与实现 (张帆 著)_第7张图片

    5.3.3 消除算法

在地图遍历结束, 并且所有可消除宝石被存入消除列表后, 我们可以利用遍历消除列表来调用消除函数,在消除宝石并且生成新的宝石后, 再次调用地图遍历函数,这样就可以在新宝石生成后, 将地图上可消除的宝石消除了

void 消除函数 {
    消除宝石的代码
}

void 消除列表 {
    for (遍历列表) {
        调用 消除函数
    }
    调用 延迟调用函数;
}

延迟调用函数 {
    延迟0.5秒
    调用地图遍历检测;
    调用消除列表函数
}
View Code

优先级: 5连 > L/T形>4连>3连

Unity2017 经典游戏开发教程 算法分析与实现 (张帆 著)_第8张图片

    5.3.4 宝石掉落

在宝石A消除后, 存储并传递被消除宝石上方的宝石B数据. 在B下方生成一个新的B,并将原有B消除,循环遍历,最后在最上方生成新的宝石C即可有宝石掉落的效果

Unity2017 经典游戏开发教程 算法分析与实现 (张帆 著)_第9张图片

for (遍历) {
    temGemStone = 获取B的数据;
    改temGemStone的位置;
    生成新的B;
}

newGemStone = 获取新的宝石C;
更改C的位置;
生成新的C
View Code

    5.3.5 游戏流程图

Unity2017 经典游戏开发教程 算法分析与实现 (张帆 著)_第10张图片

  5.4 程序实现

    5.4.1 前期准备

    5.4.2 游戏场景决定

    5.4.3 地图生成

    5.4.4 点选响应及宝石交换

    5.4.5 宝石的消除判定及宝石的消除

Unity2017 经典游戏开发教程 算法分析与实现 (张帆 著)_第11张图片

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

public class Done_GameController : MonoBehaviour {

    public Done_Gemstone gemstone;
    public int rowNum = 7;//宝石列数  
    public int columNum = 10;//宝石行数  
    public ArrayList gemstoneList;//定义列表  
    private Done_Gemstone currentGemstone;
    private ArrayList matchesGemstone;

    // Use this for initialization
    void Start () {

        gemstoneList = new ArrayList();//新建列表  
        matchesGemstone = new ArrayList();
        for (int rowIndex = 0; rowIndex < rowNum; rowIndex++)
        {
            ArrayList temp = new ArrayList();
            for (int columIndex = 0; columIndex < columNum; columIndex++)
            {
                Done_Gemstone g = AddGemstone(rowIndex, columIndex);
                temp.Add(g);
            }
            gemstoneList.Add(temp);
        }
        if (CheckHorizontalMatches() || CheckVerticalMatches())
        {//开始检测匹配消除  
            RemoveMatches();
        }
    }
    public Done_Gemstone AddGemstone(int rowIndex, int columIndex)
    {
        Done_Gemstone g = Instantiate(gemstone) as Done_Gemstone;
        g.transform.parent = this.transform;//生成宝石为GameController子物体  
        g.GetComponent().RandomCreateGemstoneBg();
        g.GetComponent().UpdatePosition(rowIndex, columIndex);//传递宝石位置
        return g;
    }
    // Update is called once per frame
    void Update () {

    }


    /// 
    /// 鼠标点选判定
    /// 
    /// 
    public void Select(Done_Gemstone g)
    {
         
        if (currentGemstone == null)
        {
            currentGemstone = g;
            currentGemstone.isSelected = true;
            return;
        }
        else
        {
            if (Mathf.Abs(currentGemstone.rowIndex - g.rowIndex) + Mathf.Abs(currentGemstone.columIndex - g.columIndex) == 1)
            {
                StartCoroutine(ExangeAndMatches(currentGemstone, g));
            }
           
            currentGemstone.isSelected = false;
            currentGemstone = null;
        }
    }


    /// 
    /// 实现宝石交换并且检测匹配消除
    /// 
    /// 
    /// 
    /// 
    IEnumerator ExangeAndMatches(Done_Gemstone g1, Done_Gemstone g2)
    {
        Exchange(g1, g2);
        yield return new WaitForSeconds(0.5f);
        if (CheckHorizontalMatches() || CheckVerticalMatches())
        {
            RemoveMatches();
        }
        else
        {
            Exchange(g1, g2);//若不能消除,再次交换宝石
        }
    }
    

    /// 
    /// 生成所对应行号和列号的宝石
    /// 
    /// 
    /// 
    /// 
    public void SetGemstone(int rowIndex, int columIndex, Done_Gemstone g)
    {
        ArrayList temp = gemstoneList[rowIndex] as ArrayList;
        temp[columIndex] = g;
    }

    /// 
    /// 交换宝石数据
    /// 
    /// 
    /// 
    public void Exchange(Done_Gemstone g1, Done_Gemstone g2)
    {
        
        SetGemstone(g1.rowIndex, g1.columIndex, g2);
        SetGemstone(g2.rowIndex, g2.columIndex, g1);
        //交换g1,g2的行号  
        int tempRowIndex;
        tempRowIndex = g1.rowIndex;
        g1.rowIndex = g2.rowIndex;
        g2.rowIndex = tempRowIndex;
        //交换g1,g2的列号  
        int tempColumIndex;
        tempColumIndex = g1.columIndex;
        g1.columIndex = g2.columIndex;
        g2.columIndex = tempColumIndex;


        g1.TweenToPostion(g1.rowIndex, g1.columIndex);
        g2.TweenToPostion(g2.rowIndex, g2.columIndex);
    }

    /// 
    /// 通过行号和列号,获取对应位置的宝石
    /// 
    /// 
    /// 
    /// 
    public Done_Gemstone GetGemstone(int rowIndex, int columIndex)
    {
        ArrayList temp = gemstoneList[rowIndex] as ArrayList;
        Done_Gemstone g = temp[columIndex] as Done_Gemstone;
        return g;
    }

    /// 
    /// 实现检测水平方向的匹配  
    /// 
    /// 
    bool CheckHorizontalMatches()
    {
        bool isMatches = false;
        for (int rowIndex = 0; rowIndex < rowNum; rowIndex++)
        {
            for (int columIndex = 0; columIndex < columNum - 2; columIndex++)
            {
                if ((GetGemstone(rowIndex, columIndex).gemstoneType == GetGemstone(rowIndex, columIndex + 1).gemstoneType) && (GetGemstone(rowIndex, columIndex).gemstoneType == GetGemstone(rowIndex, columIndex + 2).gemstoneType))
                {
                    //Debug.Log ("发现行相同的宝石");  
                    AddMatches(GetGemstone(rowIndex, columIndex));
                    AddMatches(GetGemstone(rowIndex, columIndex + 1));
                    AddMatches(GetGemstone(rowIndex, columIndex + 2));
                    isMatches = true;
                }
            }
        }
        return isMatches;
    }

    /// 
    /// 实现检测垂直方向的匹配
    /// 
    /// 
    bool CheckVerticalMatches()
    {
        bool isMatches = false;
        for (int columIndex = 0; columIndex < columNum; columIndex++)
        {
            for (int rowIndex = 0; rowIndex < rowNum - 2; rowIndex++)
            {
                if ((GetGemstone(rowIndex, columIndex).gemstoneType == GetGemstone(rowIndex + 1, columIndex).gemstoneType) && (GetGemstone(rowIndex, columIndex).gemstoneType == GetGemstone(rowIndex + 2, columIndex).gemstoneType))
                {
                   //Debug.Log("发现列相同的宝石");  
                    AddMatches(GetGemstone(rowIndex, columIndex));
                    AddMatches(GetGemstone(rowIndex + 1, columIndex));
                    AddMatches(GetGemstone(rowIndex + 2, columIndex));
                    isMatches = true;
                }
            }
        }
        return isMatches;
    }
    /// 
    /// 储存符合消除条件的数组
    /// 
    /// 
    void AddMatches(Done_Gemstone g)
    {
        if (matchesGemstone == null)
            matchesGemstone = new ArrayList();
        int Index = matchesGemstone.IndexOf(g);//检测宝石是否已在数组当中  
        if (Index == -1)
        {
            matchesGemstone.Add(g);
        }
    }


    /// 
    /// 删除/生成宝石  
    /// 
    /// 
    void RemoveGemstone(Done_Gemstone g)
    {
        //Debug.Log("删除宝石");  
        g.Dispose();

        //删除宝石后在对应位置生成新的宝石
        for (int i = g.rowIndex + 1; i < rowNum; i++)
        {
            Done_Gemstone temGamestone = GetGemstone(i, g.columIndex);
            temGamestone.rowIndex--;
            SetGemstone(temGamestone.rowIndex, temGamestone.columIndex, temGamestone);

            temGamestone.TweenToPostion(temGamestone.rowIndex, temGamestone.columIndex);
        }
        Done_Gemstone newGemstone = AddGemstone(rowNum, g.columIndex);
        newGemstone.rowIndex--;
        SetGemstone(newGemstone.rowIndex, newGemstone.columIndex, newGemstone);

        newGemstone.TweenToPostion(newGemstone.rowIndex, newGemstone.columIndex);
    }

    /// 
    /// 删除匹配的宝石
    /// 
    void RemoveMatches()
    {
        for (int i = 0; i < matchesGemstone.Count; i++)
        {
            Done_Gemstone g = matchesGemstone[i] as Done_Gemstone;
            RemoveGemstone(g);
        }
        matchesGemstone = new ArrayList();
        StartCoroutine(WaitForCheckMatchesAgain());
    }

    /// 
    /// 连续检测匹配消除
    /// 
    /// 
    IEnumerator WaitForCheckMatchesAgain()
    {

        yield return new WaitForSeconds(0.5f);
        if (CheckHorizontalMatches() || CheckVerticalMatches())
        {
            RemoveMatches();
            GameObject.Find("Text").GetComponent().text = "连击";
            yield return new WaitForSeconds(3f);
            GameObject.Find("Text").GetComponent().text = "";
        }
    }
}

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

public class Done_Gemstone : MonoBehaviour {
    public float xOffset = -4.5f;//宝石的x间距
    public float yOffset = -2.0f;//宝石的y间距  
    public int rowIndex = 0;
    public int columIndex = 0;
    public GameObject[] gemstoneBgs;//宝石数组  
    public int gemstoneType;//宝石类型  
    private GameObject gemstoneBg;
    private SpriteRenderer spriteRenderer;
    private Done_GameController gameController;


    // Use this for initialization
    void Start ()
    {
        gameController = GameObject.Find("GameController").GetComponent();
        spriteRenderer = gemstoneBg.GetComponent();
    }
    
    // Update is called once per frame
    void Update () {
        
    }

  

    /// 
    /// 随机的宝石类型  
    /// 
    public void RandomCreateGemstoneBg()
    {
        if (gemstoneBg != null)
            return;
        gemstoneType = Random.Range(0, gemstoneBgs.Length);//从宝石数组中随机选择一种宝石
        gemstoneBg = Instantiate(gemstoneBgs[gemstoneType]) as GameObject;//实例化随机的宝石
        gemstoneBg.transform.parent = this.transform;
    }

    /// 
    /// 获取宝石的位置  
    /// 
    /// 
    /// 
    public void UpdatePosition(int _rowIndex, int _columIndex)
    {
        rowIndex = _rowIndex;
        columIndex = _columIndex;
        this.transform.position = new Vector3(columIndex + xOffset, rowIndex + yOffset, 0);//控制生成宝石的位置
    }

    public bool isSelected
    {
        set
        {
            if (value)
            {
                spriteRenderer.color = Color.red;
            }
            else
            {
                spriteRenderer.color = Color.white;
            }
        }
    }

    public void OnMouseDown()
    {
        gameController.Select(this);
    }

    /// 
    /// 调用iTween插件实现宝石滑动效果  
    /// 
    /// 
    /// 
    public void TweenToPostion(int _rowIndex, int _columIndex)
    {
        rowIndex = _rowIndex;
        columIndex = _columIndex;
        iTween.MoveTo(this.gameObject, iTween.Hash("x", columIndex + xOffset, "y", rowIndex + yOffset, "time", 0.5f));
    }

    public void Dispose()
    {
        Destroy(this.gameObject);
        Destroy(gemstoneBg.gameObject);
        gameController = null;
    }
}
View Code

第6章 翻牌子

  6.1 游戏简介

《翻牌子》是一个相当有趣的记忆力训练小游戏,操作简单, 极易上手, 深受广大玩家的喜爱.玩家只要找到两两对应的牌子即可得分,找到所有对应的卡牌即结束游戏

  6.2 游戏规则

游戏界面内共有12张卡片,两两成对,共6种图案

玩家每次可以翻开两张牌, 若一样, 则两张牌将始终处于正面,否则, 再次翻转为背面.当所有卡牌配对成功后,计时停止,游戏结束

游戏记录步数, 步数越少成绩越好

当不同的两张牌被翻出时, 需要等待一段时间后才能继续点击

  6.3 程序思路

    6.3.1 搭建卡片池

翻牌子这个游戏的卡片池一般由三行四列12张卡片组成,这里我们可以使用自动布局, 配置卡片列表,随机排列两组相同的卡片.我们可以通过二维数组为每一个卡片进行编号, 随机赋予它卡片的性质,便于后续每张卡片的状态追踪

[2][0] [2][1] [2][2] [2][3]
[1][0] [1][1] [1][2] [1][3]
[0][0] [0][1] [0][2] [0][3]
View Code

    6.3.2 卡片状态

翻牌子这个游戏中, 最为核心的内容就是卡片的配对, 我们在这里给每张卡片都定义了三种状态: 未被翻开状态, 翻开状态, 配对成功状态.我们可以建立一个卡片类, 给每个卡片一个定义初始状态即未被翻开状态

在场景设置中, 一张卡片其实是由两张图重叠组成的,在上面的是一张卡片的背面, 在下面的则是卡片的图案. 当玩家在点击卡片时, 第一张图取消显示,让下面的卡片图案显示出来

当玩家点击两张卡片以后, 卡片切换至翻开状态, 此时需要判断两张卡片是否相同, 以此来决定卡片应切换回未被翻开状态还是配对状态

用户点击函数 {
    卡片背面显示 = false;
    if (两张牌相同) {
        分数++
        步数++
        if (场景中所有卡片均点击完成) {
            游戏结束
        }
    } else {
        卡片背面显示 = true;
    }
}
View Code

    6.3.3 游戏计分

    6.3.4 游戏路程图

Unity2017 经典游戏开发教程 算法分析与实现 (张帆 著)_第12张图片

  6.4 程序实现

    6.4.1 前期准备

    6.4.2 游戏场景设定

    6.4.3 卡片池的生成

    6.4.4 卡片图案的随机生成

    6.4.5 卡片的配对

    6.4.6 步数,分数和重新开始

Unity2017 经典游戏开发教程 算法分析与实现 (张帆 著)_第13张图片

using UnityEngine;
using System.Collections;

//记忆卡片类
public class Done_MemoryCard : MonoBehaviour {
    
    //[SerializeField] private SceneController controller;
    //[SerializeField] private GameObject cardBack;

    //public Done_SceneController controller;
    public GameObject cardBack;

    private int _id;
    public int id {
        get { return _id; }
    }

    public void SetCard(int id, Sprite image) {
        _id = id;
        Debug.Log ("Setting card");
        Debug.Log (id);
        GetComponent().sprite = image;
    }

    //显示且可以被点击,将卡片背面的显示状态显示为false,将此时点击的卡片值传入
    public void OnMouseDown() {
        if (cardBack.activeSelf && FindObjectOfType().canReveal == true) {
            cardBack.SetActive (false);
            FindObjectOfType().CardRevealed(this);
        }
    }

    //未被点击状态,显示背面
    public void Unreveal() {
        cardBack.SetActive (true);
    }

}


using UnityEngine;
using System.Collections;
using UnityEditor.SceneManagement;

/// 
/// 游戏控制脚本
/// 
public class Done_SceneController : MonoBehaviour {
    
    public const int gridRows = 3;
    public const int gridCols = 4;
    public const float offsetX = 2f;
    public const float offsetY = 2.5f;

    public const float originalX = -3;
    public const float originalY = 0;

    //[SerializeField] private GameObject winning;
    //[SerializeField] private MemoryCard originalCard;
    //[SerializeField] private Sprite[] images;
    //[SerializeField] private TextMesh scoreLabel;
    //[SerializeField] private TextMesh stepLabel;

    public  GameObject winning;
    public  Done_MemoryCard originalCard;
    public  Sprite[] images;
    public  TextMesh scoreLabel;
    public  TextMesh stepLabel;


    //建立两个卡片对象,在点击判断时使用
    private Done_MemoryCard _firstRevealed;
    private Done_MemoryCard _secondRevealed;

    private int _score = 0;
    private int _step = 0;

    /// 
    /// 将图片打乱随机分给不同的卡片
    /// 
    void Start () {

        //设置win图像显示为false
        winning.SetActive(false);

        //Vector3 startPos = originalCard.transform.position;

        //设置数组并打乱
        int[] numbers = {0,0,1,1,2,2,3,3,4,4,5,5};
        numbers = ShuffleArray (numbers);


        for (int i = 0; i < gridCols; i++) {
            for (int j = 0; j < gridRows; j++) {
                Done_MemoryCard card;

                card = Instantiate(originalCard) as Done_MemoryCard;

                //按顺序给牌定义数字位置下标,赋予id,显示图片
                int index = j * gridCols + i;
                int id = numbers [index];
                card.SetCard (id, images [id]);

                float posX = (offsetX * i) + originalX;
                float posY = (offsetY * j) + originalY;

                card.transform.position = new Vector3(posX, posY, 1);
            }
        }
    }

    /// 
    /// 打乱数组函数
    /// 
    /// 
    /// 
    private int[] ShuffleArray(int[] numbers) {
        //复制数组
        int[] newArray = numbers.Clone () as int[];

        for (int i=0; i < newArray.Length; i++) {
            int tmp = newArray [i];
            int r = Random.Range(i, newArray.Length);
            newArray [i] = newArray [r];
            newArray [r] = tmp;
        }

        return newArray;
    }
    
    

    void Update () {
    
    }

    
    /// 
    /// 可以点击状态,即判断是否第二张卡片点击状态被改变
    /// 
    public bool canReveal {
        get {return _secondRevealed == null;}
    }

    /// 
    /// 点击卡片,如果第一次点击则翻开第一张卡片,反之翻开第二张,开启协程
    /// 
    public void CardRevealed(Done_MemoryCard card) {
        if (_firstRevealed == null) {
            _firstRevealed = card;

        } else {
            _secondRevealed = card;

            _step++;
            stepLabel.text = "Step: " + _step;

            StartCoroutine (CheckMatch ());
        }
    }


    /// 
    /// 配对函数
    /// 
    /// 
    
    private IEnumerator CheckMatch() {
        //如果两张卡片的id相同,分数增加,如果分数增加到了一定值,判断胜利
        //如果不相同,等待0.5秒将卡片翻转
        //清空两个点击的状态
        if (_firstRevealed.id == _secondRevealed.id) {

            _score++;
            scoreLabel.text = "Score: " + _score;

            if (_score == ((gridRows * gridCols) / 2)) {
                winning.SetActive(true);
            }
        } else {
            yield return new WaitForSeconds (1.5f);

            _firstRevealed.Unreveal ();
            _secondRevealed.Unreveal ();
        }

        _firstRevealed = null;
        _secondRevealed = null;
    }

    
    /// 
    /// 加载关卡
    /// 
    public void Restart() {
        //Application.LoadLevel ("Memory");
        EditorSceneManager.LoadScene("Done_Memory");
    }
}

using UnityEngine;
using System.Collections;


/// 
/// 重新开始函数
/// 
public class Done_UIButton : MonoBehaviour {

    public GameObject targetObject;
    public string targetMessage = "Restart";

    public void OnMouseUp() {

        if (targetObject != null) {
            targetObject.SendMessage (targetMessage);
        }

    }
    
}
View Code

第7章 连连看

  7.1 游戏简介

 

  7.2 游戏规则

 

  7.3 程序思路

 

    7.3.1 地图生成

 

    7.3.2 消除检测

 

    7.3.3 画线

 

    7.3.4 游戏流程图

 

  7.4 程序实现

 

    7.4.1 前期准备

 

    7.4.2 制作游戏场景

 

    7.4.3 地图创建

 

    7.4.4 点选判定

 

    7.4.5 消除判定

 

    7.4.6 画线

 

    7.4.7 道具实现

 

第8章 拼图

  8.1 游戏简介

拼图游戏是一款经典的益智类游戏, 它的历史可以追溯到1760年左右, 英国雕刻师和制图师约翰,施皮尔斯伯里(John Spilsbury)将地图贴到硬木板上,然后沿着国界线进行切割, 制作了最原始的拼图, 最初的拼图是具有教育意义的,拼图所用的图片有些附有适合青少年阅读的短文,有些则用于向更多的人传授历史或地理知识

  8.2 游戏规则

将一张图片分成若干份, 我们这里把这些分开的图片称为碎片, 玩家将这些碎片按照原图的样式拼接起来, 则视为游戏成功

  8.3 游戏思路

    8.3.1 原图与碎片的对应关系

以3 x 3九宫格为例, 可以将构成原图的9个碎片图片用不同的整数(如1,2,3,4,5,6,7,8,9)来表示, 并保存到一个3 x 3 的二维数组中

假设碎片的宽度w, 高度为h, 碎片的中心点位置即碎片的位置坐标,二维数组的第一个元素图片位于地图的左下角

当原图的左下角坐标为在世界坐标的原点, 即(0,0)位置, 假设当前图片在二维数组索引号为[i][j],那么当前碎片的具体位置公式如下

x = (i + 1/2) x w

y = (j + 1/2) x h

例如, 索引号为[2][2] 的坐标可以根据上述公式求出为(5 / 2 x w, 5 / 2 x h)

注: 如果原图的中心在世界坐标系的原点,即(0,0)位置上时, 索引号为[i][j]碎片的具体位置公式为

x = (j - 1) x w

y = (i - 1) x h

Unity2017 经典游戏开发教程 算法分析与实现 (张帆 著)_第14张图片

    8.3.2 鼠标拖拽移动碎片

鼠标拖拽移动碎片的功能包括以下几个功能

判断鼠标点击的位置是否在碎片内

实现被拖拽的碎片跟随鼠标移动

鼠标松开时, 获取碎片当前的坐标,与对应的正确的位置坐标进行距离判断比较, 我们可以设置一个临界值,如果它们之间的距离小于这个临界值,那么可认为碎片靠近了对应的正确位置,将碎片放置到正确的位置上, 否则, 碎片会回到碎片池里

    8.3.3 正确判断

if (|Tx - Sx| < 临界值 && |Ty -Sy| < 临界值) {
    碎片坐标 = 正确坐标
} else {
    碎片回到原来的位置
}
View Code

    8.3.4 获胜判断

当所有的碎片都被放置在正确的位置上, 则判定游戏结束. 那么, 要怎么判断所有的碎片都被放置到正确的位置呢? 我们可以设置一个用来计数的变量, 每当一个碎片被放置到正确的位置之后, 该变量就加1,当这个变量值等于碎片的总数时, 就说明, 所有的碎片都被放置到了正确的位置,这时, 就可以判定游戏结束

    8.3.5 游戏流程图

Unity2017 经典游戏开发教程 算法分析与实现 (张帆 著)_第15张图片

  8.4 游戏实现

    8.4.1 前期准备

    8.4.2 制作游戏场景

    8.4.3 碎片生成

    8.4.4 鼠标事件

    8.4.5 游戏结束判断

Unity2017 经典游戏开发教程 算法分析与实现 (张帆 著)_第16张图片

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

public class Done_CreatePic : MonoBehaviour {
    public string sprit_Path = "Sprites/Pictures";
    public Sprite[] sp_S;//所有的图片
    public int textureNum = -1;//图片序号
    public static GameObject[,] pic = new GameObject[3,3];
    public static bool isSetTruePosition = false;


    /// 
    /// 加载资源,并初始化界面
    /// 
    void Start () {

        sp_S = Resources.LoadAll(sprit_Path);
        
        for (int i = 0; i < 3; i++)
        {
            for (int j = 0; j < 3; j++)
            {
                textureNum++;
                pic[i,j] = new GameObject("picture" + i + j);
                //给物体一个贴图
                pic[i,j].AddComponent().sprite = sp_S[textureNum];              
                //将碎片放置到随机的位置
                pic[i,j].GetComponent().position = new Vector2(Random.Range(3.0f,5.5f),Random.Range(0.0f,2.5f));              
            }
        }

    }  
}

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

public class Done_GameOver : MonoBehaviour {

    public static int _trueNum;//到达正确位置的碎片的数量
    private static int _allPicNum = 9;//碎片的数量

    
    /// 
    /// 结束判定
    /// 
    public static void Judge()
    {
        if (_trueNum == _allPicNum)
        {
            Debug.Log("游戏结束");
        }
    }
}

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

public class Done_MouseDrag : MonoBehaviour
{

    private Vector2 _vec3Offset;                  // 偏移  
    public Vector2 _ini_pos;                      //初始位置
    private Transform targetTransform;            //目标物体
    public static int width = 3;                  
    public static int height = 3;
    public float threshold = 0.2f;                //临界值

    private bool isMouseDown = false;             //鼠标是按下
    private Vector3 lastMousePosition = Vector3.zero;

    float chipWidth = 1;                          //碎片宽度
    float chipHeight = 1;                         //碎片高度

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            isMouseDown = true;
            for(int i = 0;i)
            {
                for(int j = 0;j)
                {
                    if(Mathf.Abs(Camera.main.ScreenToWorldPoint(Input.mousePosition).x - Done_CreatePic.pic[i, j].transform.position.x) 2
                        && Mathf.Abs(Camera.main.ScreenToWorldPoint(Input.mousePosition).y - Done_CreatePic.pic[i, j].transform.position.y) < chipHeight/2)
                    {
                        targetTransform = Done_CreatePic.pic[i, j].transform;                      
                        _ini_pos = new Vector2(targetTransform.position.x, targetTransform.position.y);//记录碎片初始位置
                        break;
                      
                    }
                   
                }
            }        
            
        }
        if (Input.GetMouseButtonUp(0))
        {
            isMouseDown = false;
            lastMousePosition = Vector3.zero;
            OnMyMouseUp();         
        }

        if (isMouseDown)
        {
            if (lastMousePosition != Vector3.zero)
            {            
                //将目标物体置于最上层
                targetTransform.GetComponent().sortingOrder = 100;
                   
                Vector3 offset = Camera.main.ScreenToWorldPoint(Input.mousePosition) - lastMousePosition;//鼠标偏移量
               //碎片当前位置 = 碎片上一帧的位置 + 鼠标偏移量
                targetTransform.position += offset;
             
               
                
            }
            lastMousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);

        }
       
    }

   
    void OnMyMouseUp()
    {
        for(int j = 0;j)
        {
            for(int i= 0;i)
            {
                if(targetTransform.name == Done_CreatePic.pic[i,j].name)
                {
                    if (Mathf.Abs(targetTransform.position.x - j)  < threshold&& Mathf.Abs(targetTransform.position.y - i) < threshold)
                    {                      
                        Debug.Log("OnMyMouseUp");
                        targetTransform.position = new Vector2(j, i);  
                        Done_GameOver._trueNum++;
                        Debug.Log(Done_GameOver._trueNum);
                        Done_GameOver.Judge();
                        break;
                    }
                    else
                    {
                        targetTransform.position = _ini_pos;
                        
                    }
                }
                targetTransform.GetComponent().sortingOrder = 5;       
            }
        }    
    }
}
View Code

第9章 推箱子

  9.1 游戏简介

推箱子是一个古老的游戏, 其目的是在训练玩家的逻辑思考能力. 推箱子的场景设置在一个狭小的仓库中, 要求把木箱放到指定的位置, 这个游戏稍不小心就会出现游戏无解的情况,所以需要巧妙地利用有限的空间和通道,合理预测, 安排移动的次序和位置,才有可能顺利完成任务

  9.2 游戏规则

这个游戏是在一个正方形的棋盘上进行的, 每一个方块表示一个地板或一面墙.地板可以通过, 墙面不可以通过. 地板上放置了箱子, 一些地板被标记为存储位置

玩家被限制在棋盘上, 可以水平或垂直地移动到空的方块上(永远不会穿过墙或箱子).箱子不得被推入其他箱子或墙壁,也不能被拉出.箱子得数量等于存储位置的数量.当所有的箱子都安放在储藏地点时, 游戏胜利

  9.3 程序思路

    9.3.1 地图生成

我们可以使用二维数组来存储地图信息. 每个元素中用不同的数字来标记不同的对象. 0表示空白, 1表示墙, 2表示角色, 3表示箱子, 9表示终点(9表示终点是为了方便以后加入其他颜色的箱子)

1, 1, 1, 1, 1, 0, 0, 0, 0,
1, 2, 0, 0, 1, 0, 0, 0, 0,
1, 0, 3, 0, 1, 0, 1, 1, 1,
1, 0, 3, 0, 1, 0, 1, 9, 1,
1, 1, 1, 3, 1, 1, 1, 9, 1,
0, 1, 1, 0, 0, 0, 0, 9, 1,
0, 1, 0, 0, 0, 1, 0, 0, 1,
0, 1, 0, 0, 0, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1, 0, 0, 0
View Code

地图的生成: 通过遍历地图二维数组,读取每个元素的数值, 根据数值标记生成不同的地图对象. 由于一维数组在编译地图以及可以更简便地表示角色及箱子在数组内的移动,所以我们也可以利用一维数组来存储地图数组. 这里我们使用一维数组来表示, 其二维数组映射到一维数组的公式为: b [ i * n + j ] = a [ i ] [ j ], 其中,设数组有m行n列, i, j 分别表示数组内的行数与列数(0 < I < m, 0 < j < n). 每一个方格的具体位置都与其二维数组内的位置对应,假设方块的长宽均为1,例如, 脚本内设定一个 9x9 的数组,方块对应的二维数组内位置为 a[2][1],一维数组内位置为 b[2*9+1] == b[19], 那么方块实际坐标为(2, 1)

    9.3.2 角色移动

public enum Direction { Up = -9, Down = 9, Left = -1, Right =1 }
public Direction dir;
View Code

    9.3.3 箱子移动

    9.3.4 角色及箱子移动逻辑

if (玩家按键 上) {
    dir = Direction.Up
} else if (玩家按键 下) {
    dir = Direction.Down
} else if (玩家按键 左) {
    dir = Direction.Left
} else if (玩家按键 右) {
    dir = Direction.Right
}

if (下一位置为空) {
    temp_map[角色目前位置(i * n + j)] = 0
    temp_map[角色下一位置(i * n + j + dir)] = 2
}

if (下一位置为箱子) {
    temp_map[角色目前位置(i * n + j)] = 0;
    temp_map[角色下一位置(i * n +j + dir)] = 2;
    temp_map[角色下二位置(i * n + j + dir * 2)] = 3;
}

if (下一位置为墙壁) {
    return false;
}
View Code

地图刷新: 人物或箱子可以移动后, 将人物或箱子在数组内对应位置的值设为0,对应移动目标位置的数组的值设为人物或箱子的值,最后刷新人物或箱子即可

Unity2017 经典游戏开发教程 算法分析与实现 (张帆 著)_第17张图片

    9.3.5 游戏获胜判定

由程序遍历地图, 当地图中没有不与地图重合的箱子时, 玩家获胜.注: 初始箱子名为Box, 当箱子与终点重合时, 刷新为名为FinalBox的箱子, 两种箱子颜色不同

if (没有Box) {
    玩家获胜;
}
View Code

    9.3.6 游戏流程图

Unity2017 经典游戏开发教程 算法分析与实现 (张帆 著)_第18张图片

  9.4 程序实现

    9.4.1 前期准备

    9.4.2 制作游戏场景

    9.4.3 地图生成

    9.4.4 角色都移动

    9.4.5 箱子的移动

    9.4.6 游戏胜利判定

    9.4.7 动画的加入

Unity2017 经典游戏开发教程 算法分析与实现 (张帆 著)_第19张图片

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

public class Done_Build : MonoBehaviour
{
    public static int[] temp_map;
    public int[] final_map;
    public GameObject[] destoryObj;
    public Sprite[] mapSprites;
    public bool playerDestory = false;
    public bool boxDestory = false;


    GameObject g;

    public GameObject mapPrefab;//地图的墙壁
    public GameObject playerPrefab;//角色
    public GameObject boxPrefab;//箱子
    public GameObject finalBoxPrefab;//到终点后的箱子
    public GameObject finalPrefab;//游戏获胜后刷新一张图片,用于提示游戏获胜。

    /// 
    /// 数组初始化
    /// 
    private void Awake()
    {
        
        final_map = new int[9 * 9];//传值数组
        temp_map = new int[]
            {
                1, 1, 1, 1, 1, 0, 0, 0, 0,
                1, 2, 0, 0, 1, 0, 0, 0, 0,
                1, 0, 3, 0, 1, 0, 1, 1, 1,
                1, 0, 3, 0, 1, 0, 1, 9, 1,
                1, 1, 1, 3, 1, 1, 1, 9, 1,
                0, 1, 1, 0, 0, 0, 0, 9, 1,
                0, 1, 0, 0, 0, 1, 0, 0, 1,
                0, 1, 0, 0, 0, 1, 1, 1, 1,
                0, 1, 1, 1, 1, 1, 0, 0, 0

            };

        for (int i = 0; i < 9; i++)
        {
            for (int j = 0; j < 9; j++)
            {
                final_map[j * 9 + i] = temp_map[j * 9 + i];
            }
        }
    }



    // Use this for initialization
    void Start()
    {
        BuildMap();
    }

    /// 
    /// 地图初始化
    /// 
    void BuildMap()
    {

        destoryObj = new GameObject[9 * 9];//用于销毁GameObject

        int i = 0;
        for (int y = 0; y < 9; y++)
        {
            for (int x = 0; x < 9; x++)
            {
                
               
                switch (temp_map[i])
                {
                    
                    case 1: //生成墙壁
                    case 0://无图
                
                    g = Instantiate(mapPrefab) as GameObject;
                    g.transform.position = new Vector3(x, -y, 0);
                    g.name = x.ToString() + y;

                    destoryObj[y * 9 + x] = g;

                    Sprite icon = mapSprites[temp_map[i]];//使贴图与地图数组吻合
                    g.GetComponent().sprite = icon;
                        break;
                        
                    case 2://生成人物

                    g = Instantiate(playerPrefab) as GameObject;
                    g.transform.position = new Vector3(x, -y, 0);
                    g.name = "Player";

                    destoryObj[y * 9 + x] = g;
                        break;

                    
                    case 3://生成箱子

                    g = Instantiate(boxPrefab) as GameObject;
                    g.transform.position = new Vector3(x, -y, 0);
                    g.name = "Box";

                    destoryObj[y * 9 + x] = g;
                        break;
                        
                }
                if (i < 80)
                {
                    i++;
                }
            }
        }
    }
    
    /// 
    /// 角色移动
    /// 
    void PlayerMove()
    {//获取各项数值

        int x, y;
        int x_num = 0;
        int y_num = 0;

        //获取Player坐标
        Transform playerPos = GameObject.Find("Player").GetComponent();
        x = (int)playerPos.position.x;
        y = (int)playerPos.position.y;

        //获取界面移动坐标数值
        x_num = FindObjectOfType().x_num;
        y_num = FindObjectOfType().y_num;

        //获取动画 一定要放在销毁Player前
        string animator_name = FindObjectOfType().animator_name;

        //销毁原有Player
        Destroy(destoryObj[-y*9+x]);

       
        g = Instantiate(playerPrefab) as GameObject;

        g.GetComponent().Play(animator_name);
        g.transform.position = new Vector3(x + x_num, y + y_num, 0);
        g.name = "Player";

        //将新的Player存入销毁数组
        destoryObj[-((y + y_num) * 9) + x + x_num] = g;
        playerDestory = false;
    }
    /// 
    /// 箱子移动
    /// 
    void BoxMove()
    {//获取各项数值

        int x, y;
        int x_num = 0;
        int y_num = 0;


        //获取界面移动坐标数值及Box界面编号
        x_num = FindObjectOfType().x_num;
        y_num = FindObjectOfType().y_num;


        //获取相应Box坐标
        Transform playerPos = GameObject.Find("Player").GetComponent();
        x = (int)playerPos.position.x + x_num;//Player的X坐标+x_num为下一个目标点的X坐标,即为相应Box的X坐标,Y同理
        y = (int)playerPos.position.y + y_num;

        //销毁原有Box
        Destroy(destoryObj[-y * 9 + x]);
        
        if (final_map[-((y + y_num) * 9) + x + x_num]== 0 || final_map[-((y + y_num) * 9) + x + x_num] == 3)
        {
            g = Instantiate(boxPrefab) as GameObject;
            g.transform.position = new Vector3(x + x_num, y + y_num, 0);
            g.name = "Box";
        }

        else if (final_map[-((y + y_num) * 9) + x + x_num] == 9)
        {
            g = Instantiate(finalBoxPrefab) as GameObject;
            g.transform.position = new Vector3(x + x_num, y + y_num, 0);
            g.name = "FinalBox";
        }
        //将新的Box存入销毁数组
        destoryObj[-((y + y_num) * 9) + x + x_num] = g;
        playerDestory = false;
        boxDestory = false;
    }

    private void Update()
    {
        if (playerDestory)//如果Player移动并且原有Player需要销毁
        {
            if (boxDestory)//如果Box移动并且原有Box需要销毁
            {
                BoxMove();
            }
            PlayerMove();
        }
        //游戏结束判定
        if (GameObject.Find("Box") == null&&GameObject.Find("Final")==null)
        {
            g = Instantiate(finalPrefab) as GameObject;
            g.transform.position = new Vector3(4, -4, 0);
            g.name = "Final";
        }
    }
}

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

public class Done_GameController : MonoBehaviour {
    
    
    public int x_num = 0;
    public int y_num = 0;
    public bool IsChange = false;
    public int box_num;
    public string animator_name;
 


  
    
  

    // Use this for initialization
    void Start()
    {
      
    }

    public bool IsMove()
    {
        int x, y;
        
        //获取Player坐标
        Transform playerPos = GameObject.Find("Player").GetComponent();
        x = (int)playerPos.position.x;
        y = (int)playerPos.position.y;

        GameObject g;



        // 如果人物下一个运动目标点为空
        
        if ((Done_Build.temp_map[-y * 9 + x + (int)dir] == 0 || Done_Build.temp_map[-y * 9 + x + (int)dir] == 9) && IsChange == true)
        {
            //改变数组内人物位置
            Done_Build.temp_map[-y * 9 + x] = 0;
            Done_Build.temp_map[-y * 9 + x + (int)dir] = 2;

            // 回传数据
            //FindObjectOfType().temp_map = temp_map;
            IsChange = false;
            FindObjectOfType().playerDestory = true;
            return true;
        }

        //如果人物下一个运动目标点为墙壁
        else if (Done_Build.temp_map[-y * 9 + x + (int)dir] == 1 && IsChange == true)
        {
            IsChange = false;
            return false;
        }
        //   如果人物下一个运动目标点为箱子
        else if (Done_Build.temp_map[-y * 9 + x + (int)dir] == 3 && (Done_Build.temp_map[-y * 9 + x + (int)dir*2] == 0 || Done_Build.temp_map[-y * 9 + x + (int)dir*2] == 9) && IsChange == true)         //temp_map[-y * 9 + x + array_num]:下一个运动目标点的数组位置
        {


            //改变数组内人物及箱子位置
            Done_Build.temp_map[-y * 9 + x] = 0;
            Done_Build.temp_map[-y * 9 + x + (int)dir] = 2;
            Done_Build.temp_map[-y * 9 + x + (int)dir*2] = 3;

            //回传数据
            //FindObjectOfType().temp_map = temp_map;
            IsChange = false;
            FindObjectOfType().playerDestory = true;
            FindObjectOfType().boxDestory = true;
            return true;

        }

        return false;
    }

    public enum Direction { Up = -9,Down = 9,Left = -1,Right = 1}
    public Direction dir;
    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.UpArrow)) { dir = Direction.Up; }
        if (Input.GetKeyDown(KeyCode.DownArrow)) { dir = Direction.Down; }
        if (Input.GetKeyDown(KeyCode.LeftArrow)) { dir = Direction.Left; }
        if (Input.GetKeyDown(KeyCode.RightArrow)) { dir = Direction.Right; }

        switch (dir)
        {
            case Direction.Up:
                IsChange = true;
                x_num = 0;                  //生成新Gameboject时的实际位置坐标
                y_num = 1;
                animator_name = "Up";       //动画控制
                break;

            case Direction.Down:
                IsChange = true;
                x_num = 0;
                y_num = -1;
                animator_name = "Down";
                break;

            case Direction.Left:
                IsChange = true;
                x_num = -1;
                y_num = 0;
                animator_name = "Left";
                break;
            case Direction.Right:
                IsChange = true;
                x_num = 1;
                y_num = 0;
                animator_name = "Right";
                break;

        }
            IsMove();
    }
}
View Code

第10章 炸弹人

  10.1 游戏简介

《炸弹人》(Bomber Man) 是HUDSON出品的一款战略迷宫类型电子游戏. 游戏于1983年发行, 至今已经陆续发行了70余款系列游戏.设计精妙, 在操作上有一定的难度和要求,给很多红白机玩家带来了童年乐趣

炸弹人的形象完全采用了Broderbund公司制作的一款游戏《淘金者(Lode Runner)》中敌方机器人的设计.这使得《炸弹人》成了一款于《淘金者》息息相关的游戏

这里向大家介绍一下《炸弹人》与《淘金者》的故事。 其实炸弹人是一个想变成人类的机器人,他坚信只要到达地面就能变成人类,最后, 他终于努力变成了人,却以淘金者的身份回到了地下.游戏具体操作是游戏主人公放置炸弹杀死怪物,炸弹也可以炸死自己,在操作过程中不能碰到怪物,碰到怪物则死亡, 玩家需要利用地形,寻找怪物运动的规律来炸死所有怪物,找到传送之门进入下一关

  10.2 游戏规则

在每一个关卡的地图内, 主人公使用放置炸弹的方法来清理路障, 消灭怪物. 无论谁在炸弹爆炸的范围内都会死亡/消失

清理路障的时候会爆出一些道具: 增加威力, 增加个数或是引爆炸弹的能力

主人公一共有三条命, 每次死亡都回重置本关, 闯关成功则会奖励生命值

玩家可以利用地形和灵活的走位获取游戏的胜利, 在此过程中一旦死亡次数超过生命值, 游戏结束, 反之, 如果在规定时间内消灭所有的怪物并顺利找到传送之门.即可进入下一关

  10.3 程序思路

    10.3.1 地图生成

其实炸弹人较难的部分是要考虑地图的生成布局, 地图的整体大小是固定不变的,地图主要由三个部分组成, 即墙体, 道具和怪物

墙体生成: 用二维数组记录地图行列数, 在地图中有两种不同状态的墙体, 分别为可炸毁的墙体和不可炸毁的墙体

不可炸毁的墙体: 在地图中按规律分布, 我们可以利用该规律生成墙体. 用二维数组来表示整张地图的话, 记地图左下角为[0][0], 则不可炸毁的墙体所在位置的数组下标均为奇数.注意, 地图外围有一圈不可炸毁的墙体我们不记在数组内部

for (x = -1; x < 列数 + 1; x++){
    for (y = -1; y < 行数 + 1; y++) {
        if ((x % 2 ==1 & y % 2 == 1) | x == -1 | x == 列 | y == -1 | y == 行数) // 数组下标均为奇数|均在地图最上/下/左/右侧
            生成不可炸毁的墙体;
    }
}
View Code

可炸毁的墙体: 则随机分布在地图空闲的地方, 因而我们需要建立一个存有当前地图内空闲位置的列表, 数组内偶数行的偶数列即所选范围(添加判断语句if (x % 2 == 0 | y % 2 == 0)), 再利用随机数在空闲位置生成一定数量的可摧毁墙体即可

一般来说,炸弹人会在游戏开始时生成在地图的左上角, 因而我们要给主角留些生存的空间, 所以我们在制作空闲列表的时候需要将左上角这三个位置去除, 即这三个位置是不能生成可摧毁墙体的

道具/门的生成: 当我们随机生成可炸毁墙体后, 系统检测存有可炸毁前提的位置列表, 将道具随机赋予给其中1个2或2个墙体, 当墙体被炸毁时, 道具显示. 玩家拾取道具后可以改变玩家类的一些信息

    10.3.2 炸弹管理

炸弹人中另一个较为重要的部分是炸弹爆炸的运算

建立一个炸弹类, 在炸弹上设置一个定时器, 在生成炸弹前提前检测当前位置是否已经存在炸弹或墙体,有则炸弹无法生成

炸弹计时器结束后调用火焰类, 火焰类中包含定时器以及爆炸范围信息, 在火焰类定时器工作时间内, 存在于爆炸范围内的一切物体都将被摧毁(不可摧毁墙体除外),爆炸范围利用遍历算法,以炸弹位置为中心向上下左右四周进行遍历,中途如果遇到墙体时, 该方向向上的遍历结束

for (i = 0; i < 威力; i++) {
    if (没有墙体) {
        炸弹放置坐标a[x + i][y].火焰显示 = true
    } else if (有可摧毁墙体) {
        可摧毁墙体被销毁  
    }
    break;
}
else break;
}
View Code

    10.3.3 怪物管理

与道具生成恰恰相反, 怪物生成时需检测地图中没有墙体的位置, 利用随机数分布于这些位置中, 然后开始运动

怪物在运动过程中不断检测其运动方向上是否有障碍, 一遇到障碍就通过随机数指定一个新的方向进行运动

    10.3.4 游戏管理

游戏有一个整体的计时器, 在游戏初始化以后开始计时, 如果在倒计时结束前玩家没有消灭所有敌人并打开传送之门, 游戏结束

玩家拥有生命值, 用整型变量记录即可, 玩家死亡一次, 地图内所有的场景都将被重新加载, 如果在消灭所有敌人之前玩家生命值降为0, 则游戏提前结束, 反之游戏进入下一关

    10.3.5 游戏流程图

Unity2017 经典游戏开发教程 算法分析与实现 (张帆 著)_第20张图片

  10.4 程序实现

    10.4.1 前期准备

    10.4.2 地图制作

    10.4.3 开始制作

    10.4.4 玩家操控

    10.4.5 墙体摧毁

    10.4.6 怪物制作

Unity2017 经典游戏开发教程 算法分析与实现 (张帆 著)_第21张图片

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

/// 
/// 地图管理器,负责墙体的生成和销毁,道具的放置等
/// 
public class Done_BoardManager : MonoBehaviour
{

    /// 
    /// 用于管理可炸毁墙体数量
    /// 
    public class Count
    {
        public int minimum;
        public int maximum;

        public Count(int min, int max)
        {
            minimum = min;
            maximum = max;
        }
    }

    //声明各种地图内的预制体                               
    public GameObject wallTile;
    public GameObject metalTile;
    //敌人预制体
    public GameObject wormPrefab;
    //public GameObject doorTile;
    //public GameObject badgeTile;

    // 数量参数,地图的行列数以及墙体的数量范围,敌人的数量
    public int columns { get; private set; }
    public int rows { get; private set; }
    public Count wallCount = new Count(30, 40);
    public Count wormCount = new Count(3, 5);

    // 管理用参数
    public List wallList = new List();              // Wall类集合
    public List metalList = new List();           // Metal类集合
    private List gridPositions = new List();  // 可用的格子的集合
    //public Door door;                                           ////public Badge badge;                                         // 徽章

    private Transform boardHolder;                              // 墙体的父Transform

    void InitialiseList()
    {
        gridPositions.Clear();
        for (int x = 0; x < columns; x++)
        {
            for (int y = 0; y < rows; y++)
            {
                // 为主角留一个出生位置
                // □□■■■■
                // □■■■■■
                // ■■■■■■
                if ((x == 0 & y == rows - 1) | (x == 0 & y == rows - 2) | (x == 1 & y == rows - 1))
                {
                    continue;
                }
                // 找出所有的可用的格子存入gridPositions
                //在炸弹人游戏中,偶数行和偶数列的格子是可以用于可炸毁墙体的生成和怪物以及人的行走的。其与墙体均不可走。
                if (x % 2 == 0 || y % 2 == 0)
                {
                    gridPositions.Add(new Vector3(x, y, 0f));
                }
            }
        }
    }

    /// 
    /// 给游戏创建Metal外墙(边界)和内墙
    /// 在炸弹人游戏中,除整个地图被外墙包围以外,地图中x,y均为奇数的格子也会生成不可炸毁的墙体
    /// 
    void BoardSetup()
    {
        columns = 18;
        rows = 7;
        metalList.Clear();
        boardHolder = new GameObject("Board").transform;

        for (int x = -1; x < columns + 1; x++)
        {
            for (int y = -1; y < rows + 1; y++)
            {
                GameObject toInstantiate = null;
                if ((x % 2 == 1 & y % 2 == 1) | x == -1 | x == columns | y == -1 | y == rows)
                {
                    toInstantiate = metalTile;
                }
                if (toInstantiate)
                {
                    GameObject instance = Instantiate(toInstantiate, new Vector3(x, y, 0f), Quaternion.identity);
                    instance.transform.SetParent(boardHolder);
                    metalList.Add(instance.GetComponent());
                }
            }
        }
    }

    /// 
    /// 从我们的gridPositions集合中返回一个随机位置
    /// 
    /// 
    public Vector3 RandomPosition()
    {
        if (gridPositions.Count == 0)
        {
            Debug.Log("可用的格子不够了");
        }
        int randomIndex = Random.Range(0, gridPositions.Count);
        Vector3 randomPosition = gridPositions[randomIndex];
        gridPositions.RemoveAt(randomIndex);
        return randomPosition;
    }


    /// 
    ///  创建minimum到maximum数之间个数的可炸毁墙体wall
    ///  wall的生成范围在GridPositions列表中
    /// 
    /// 
    /// 
    void LayoutWallAtRandom(int minimum, int maximum)
    {

       int objectCount = Random.Range(minimum, maximum + 1);
        Debug.Log(objectCount);

        for (int i = 0; i < objectCount; i++)
        {
            Vector3 randomPosition = RandomPosition();

            GameObject obj = Instantiate(wallTile, randomPosition, Quaternion.identity);

            obj.transform.SetParent(boardHolder);

            wallList.Add(obj.GetComponent());
        }
    }

    /// 
    ///  创建minimum到maximum数之间个数的敌人
    ///  敌人的生成范围在GridPositions列表中
    /// 
    /// 
    /// 
    void LayoutWormAtRandom(int minimum, int maximum)
    {

        wallList.Clear();

        int objectCount = Random.Range(minimum, maximum + 1);
        Debug.Log(objectCount);

        for (int i = 0; i < objectCount; i++)
        {
            Vector3 randomPosition = RandomPosition();

            GameObject obj = Instantiate(wormPrefab, randomPosition, Quaternion.identity);
            
        }
    }

    // Use this for initialization
    /// 
    /// 建立场景
    /// 
    void Start()
    {
        BoardSetup();

        InitialiseList();

        LayoutWallAtRandom(wallCount.minimum, wallCount.maximum);
        
        //敌人生成
        LayoutWormAtRandom(wormCount.minimum, wormCount.maximum);
    }

    // Update is called once per frame
    void Update()
    {

    }
}

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

/// 
/// 火花生成
/// 
public class Done_Bomb : MonoBehaviour {

    //火花承载变量
    public GameObject ExplosionRrefab;

    
    void OnDestroy()
    {
        //在炸弹的位置上生成火花
        Instantiate(ExplosionRrefab,transform.position,Quaternion.identity);
    }
}

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

/// 
/// 炸弹生成脚本
/// 
public class Done_BombDrop : MonoBehaviour {

    //申明炸弹
    public GameObject BombPrefab;

    void Update()
    {
        //检测空格键,生成炸弹
        if (Input.GetKeyDown(KeyCode.Space))
        {
            //获取炸弹人当前坐标,将坐标整数化,在此位置上生成炸弹
            Vector2 pos = transform.position;
            //Vector2 pos = new Vector2(Mathf.Floor(transform.position.x), Mathf.Floor(transform.position.y));
            //Mathf.Round(pos.x);
            //Mathf.Round(pos.y);
            Instantiate(BombPrefab,pos, Quaternion.identity);
        }
    }
}

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

/// 
/// 炸弹消失脚本
/// 
public class Done_DestroyAfter : MonoBehaviour {


    //设置炸弹存在时间
    public float time = 3f;
    // Use this for initialization
    void Start () {
        //销毁炸弹
        Destroy(gameObject,time);
    }
    
    
}

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

/// 
/// 火花管理
/// 
public class Done_Explosion : MonoBehaviour {

    private void OnTriggerEnter2D(Collider2D coll)
    {
        //如果碰到的物体不是静态属性,则被消除
        if (!coll.gameObject.isStatic) {
            Destroy(coll.gameObject);
        }
    }
}

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

public class Done_Metal : MonoBehaviour {

    // Use this for initialization
    void Start () {
        
    }
    
    // Update is called once per frame
    void Update () {
        
    }
}

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

/// 
/// 玩家移动脚本
/// 
public class Done_Move : MonoBehaviour {


    //设置运动速度
    public float speed = 16f;
    /// 
    /// 实时获取键盘输入
    /// 
    private void FixedUpdate()
    {
        //getAxisRaw只能返回-1,0,1三个值
        //得到水平或垂直命令
        float h = Input.GetAxisRaw("Horizontal");
        float v = Input.GetAxisRaw("Vertical");

        //访问炸弹人2D刚体,通过获取的值进行运动
        GetComponent().velocity = new Vector2(h, v) * speed;

        //播放对应动画
        GetComponent().SetInteger("x", (int)h);
        GetComponent().SetInteger("y", (int)v);
    }

    
    /// 
    /// 玩家与敌人相撞消失
    /// 
    /// 
    private void OnCollisionEnter2D(Collision2D co)
    {
        
        if (co.gameObject.name == "Done_worm(Clone)")
        {
            Destroy(gameObject);

        }
    }
}

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

public class Done_Wall : MonoBehaviour {

    // Use this for initialization
    void Start () {
        
    }
    
    // Update is called once per frame
    void Update () {
        
    }
}

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

/// 
/// 怪物运动
/// 
public class Done_worm : MonoBehaviour {

    //怪物运动速度
    public float Speed = 2f;

    /// 
    /// 给怪物设定一个随机的运动方向
    /// 
    /// 
    Vector2 randir() {
        //设置随机数为-1,0,1
        int r = Random.Range(-1, 2);

        //三目运算符(条件?结果1:结果2),给怪物一个运动发方向
        return (Random.value < 0.5) ? new Vector2(r, 0) : new Vector2(0, r);
    }

    /// 
    /// 检测运动方向
    /// 
    /// 
    /// 
    bool isVaildDir(Vector2 dir) {
        //获取怪物此时的位置
        Vector2 pos = transform.position;
        //从怪物当前位置发射一条射线,如果碰到物体则怪物无法运动,反之可以
        RaycastHit2D hit = Physics2D.Linecast(pos + dir, pos);
        return (hit.collider.gameObject == gameObject);

    }

    /// 
    /// 怪物运动,调用动画
    /// 
    void ChangeDir() {

        //获取随机的二维向量
        Vector2 dir = randir();
        {
            //检测是否能运动
            if (isVaildDir(dir))
            {
                GetComponent().velocity = dir * Speed;
                GetComponent().SetInteger("x", (int)dir.x);
                GetComponent().SetInteger("y", (int)dir.y);
            }
        }
    }
    /// 
    /// 实时获取运动方向
    /// 
    void Start()
    {
        //每隔0.5秒重复调用怪物运动函数,让怪物可以实时获取新的运动方向
        InvokeRepeating("ChangeDir", 0, 0.5f);
           
    }
}
View Code

第11章 华容道

  11.1 游戏简介

 

  11.2 游戏规则

 

  11.3 游戏程序实现思路

 

    11.3.1 棋子

 

    11.3.2 棋盘

 

    11.3.3 移动棋子

 

    11.3.4 结束判定

 

    11.3.5 游戏流程图

 

  11.4 游戏实现

 

    11.4.1 前期准备

 

    11.4.2 制作游戏场景

 

    11.4.3 生成棋子

 

    11.4.4 棋子移动

 

    11.4.5 游戏结束判定

 

第12章 横板跑酷

  12.1 游戏简介

 

  12.2 游戏规则

 

  12.3 程序思路

 

    12.3.1 地图

 

    12.3.2 金币和道具

 

    12.3.3 障碍物

 

    12.3.4 玩家

 

    12.3.5 金币分数和已经前进距离的显示

 

    12.3.6 游戏流程图

 

  12.4 工程实现

 

    12.4.1 前期准备

 

    12.4.2 制作游戏场景

 

    12.4.3 玩家控制

 

    12.4.4 路段上金币, 道具和障碍物的生成

 

    12.4.5 显示前进距离和金币

 

第13章 扫雷

  13.1 游戏简介

 

  13.2 游戏规则

 

    13.2.1 扫雷的布局

 

    13.2.2 扫雷的基本操作

 

    13.2.3 游戏结束

 

  13.3 程序的思路

 

    13.3.1 雷区绘制

 

    13.3.2 左键单击

 

    13.3.3 右键单击

 

    13.3.4 左右键双击

 

    13.3.5 游戏结束

 

    13.3.6 游戏流程图

 

  13.4 程序实现

 

    13.4.1 前期准备

 

    13.4.2 制作游戏场景

 

    13.4.3 雷区的生成

 

    13.4.4 地雷随机分布

 

    13.4.5 方块关联

 

    13.4.6 鼠标点击

 

    13.4.7 游戏失败

 

    13.4.8 剩余地雷数, 时间和笑脸管理

 

第14章 贪吃蛇

  14.1 游戏简介

 

  14.2 游戏规则

 

  14.3 程序思路

 

    14.3.1 地图的生成

 

    14.3.2 食物出现

 

    14.3.3 蛇的数据结构

 

    14.3.4 贪吃蛇移动算法

 

    14.3.5 蛇的增长

 

    14.3.6 判断蛇头是否撞到了自身

 

    14.3.7 边界判断

 

    14.3.8 游戏流程图

 

  14.4 游戏程序实现

 

    14.4.1 前期准备

 

    14.4.2 制作场景

 

    14.4.3 生成食物

 

    14.4.4 蛇的移动

 

    14.4.5 蛇的长大及移动

 

    14.4.6 累计分数

 

    14.4.7 结束判定

 

第15章 五子棋

  15.1 游戏简介

 

  15.2 游戏规则

 

    15.2.1 五子棋棋盘和棋子

 

    15.2.2 五子棋基本规则

 

    15.2.3 落子顺序

 

    15.2.4 禁手

 

  15.3 游戏算法思路

 

    15.3.1 棋盘的绘制

 

    15.3.2 盘面棋子绘制

 

    15.3.3 落子

 

    15.3.4 获胜规则判定

 

    15.3.5 判定黑方禁手功能

 

    15.3.6 游戏流程图

 

  15.4 游戏程序实现

 

    15.4.1 前期准备

 

    15.4.2 创建场景

 

    15.4.3 落子

 

    15.4.4 切换落子权限

 

    15.4.5 更新棋盘状态

 

    15.4.6 获胜判断

 

    15.4.7 禁手规则

 

    15.4.8 重新开始

 

第16章 跳棋

  16.1 游戏简介

 

  16.2 游戏规则

 

  16.3 程序思路

 

    16.3.1 棋盘排列

 

    16.3.2 棋子生成

 

    16.3.3 棋子的位置和移动

 

    16.3.4 计算可移动位置

 

    16.3.5 回合限制

 

    16.3.6 游戏胜负判断

 

    16.3.7 游戏流程图

 

  16.4 程序实现

 

    16.4.1 前期准备

 

    16.4.2 创建棋盘

 

    16.4.3 创建棋子

 

    16.4.4 移动棋子

 

    16.4.5 限制可移动位置

 

    16.4.6 回合限制

 

    16.4.7 胜利判断

 

第17章 吃豆人

  17.1 游戏简介

 

  17.2 游戏规则

 

  17.3 程序思路

 

    17.3.1 地图生成

 

    17.3.2 幽灵状态

 

    17.3.3 小精灵管理

 

    17.3.4 游戏流程图

 

  17.4 程序实现

 

    17.4.1 前期准备

 

    17.4.2 制作游戏场景

 

    17.4.3 吃豆人的移动

 

    17.4.4 豆子的消失

 

    17.4.5 幽灵运动

 

第18章 斗地主

  18.1 游戏简介

 

  18.2 游戏规则

 

  18.3 程序思路

 

    18.3.1 扑克牌

 

    18.3.2 洗牌

 

    18.3.3 发牌

 

    18.3.4 出牌

 

    18.3.5 牌型

 

    18.3.6 大小

 

    18.3.7 玩家

 

    18.3.8 胜利

 

    18.3.9 游戏流程

 

  18.4 工程实现

 

    18.4.1 前期准备

 

    18.4.2 制作游戏场景

 

    18.4.3 定义一张牌

 

    18.4.4 洗牌

 

    18.4.5 发牌

 

    18.4.6 胜利判定

 

    18.4.7 叫地主

 

    18.4.8 出牌

 

    18.4.9 判断牌型

 

    18.4.10 比大小

 

    18.4.11 胜利

 

第19章 坦克大战

  19.1 游戏简介

 

  19.2 游戏规则

 

  19.3 程序思路

 

    19.3.1 地图生成

 

    19.3.2 敌人

 

    19.3.3 玩家

 

    19.3.4 障碍物

 

    19.3.5 道具

 

    19.3.6 基地

 

    19.3.7 游戏流程图

 

  19.4 工程实现

 

    19.4.1 前期准备

 

    19.4.2 制作游戏场景

 

    19.4.3 玩家控制

 

    19.4.4 子弹

 

    19.4.5 地图上各类障碍物及基地

 

    19.4.6 敌人

 

    19.4.7 敌人生成器

 

    19.4.8 道具

 

转载于:https://www.cnblogs.com/revoid/p/10799058.html

你可能感兴趣的:(Unity2017 经典游戏开发教程 算法分析与实现 (张帆 著))