Unity开发笔记(五)—— 制作第四个小游戏《坦克大战》

目录

使用VS传统方法制作

使用Unity制作


使用VS传统方法制作

写在前面的话

C#可以干什么?

  • 桌面应用开发(用的少,现在市面上的桌面应用大部分是C++开发的)
  • Unity游戏开发
  • Web开发(用的少,现在市面上的网站是Java/PHP开发的)

开发工具:Unity、VS

注意:杀毒软件可能会把开发完成阶段生成的exe文件误当成病毒删除,所以使用时注意关闭

一、准备

 进入项目后可以看到Form1.cs的设计模型框 

鼠标右键选择查看代码,可查看Form1.cs的具体代码 

选择视图->工具箱,在工具箱中有一些系统自带组件鼠标拖动到Form1.cs进行UI布局的设计

控制窗体显示的位置 

居中显示

自定义位置显示 

 查看窗体事件有哪些

我们找到Paint(这个事件是用于更新画布的),然后在其后面的空格处双击,然后我们就会得到一个Form1_Paint方法

 

下面我们在此方法中编写代码去画一条线段

注意这里的坐标原点是表头以下部分的左上角 

 查看本机有哪些字体?新建一个txt文件打开,然后找到字体即可查看

 

绘制文字 

绘制图片,双击打开Resources文件,选择图像,选择添加现有文件,选择导入即可

 

我们可以在Resources类下发现有自动生成的代码

同理,添加音频

 

编写代码 

 绘制图片成功

 

控制代码收缩,使用region和endregion 

也可用Bitmap来获取图片对象且使用它可以对颜色进行透明处理

二、正式开始

1.创建画布窗口

创建窗体应用项目,设置窗口居中显示,设置标题(长宽均为15*30像素,为了对此取奇数)和游戏标题

新建一个线程 

新建一个类(项目右键添加->类)

创建Start和Update方法,Start方法用于游戏启动时的初始化,Update用于游戏每帧画面的更新逻辑操作 

为了优化性能,限制1s执行60次update方法

我们在调试时可以发现,当关闭窗口后主线程没有关闭,这是因为子线程没有关闭的情况下子线程是不会关闭的,我们添加一个FormClosed事件(方法见一)

修改Thread作用域,然后在FormClosed方法中调用中断线程的方法

创建画布并赋值 

将画布置为黑色 (为什么要把置为黑色代码放到每一帧里面重复执行?我直接执行一次不就好了吗?答案是:因为我们在游戏中还有动态的坦克,如果只执行一次则在创建坦克时会有重复)

 

2.绘制地图

创建GameObject类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TankWar
{
    class GameObject
    {
        public int X { get; set; }
        public int Y { get; set; }

        //上述等价于
        //public int y;
        //public int Y {
        //    get {
        //        return y;
        //    }
        //    set {
        //        value = y;
        //    }
        //}
    }
}

 创建NotMovingThing类,继承GameObject,新建Image对象

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TankWar
{
    class NotMoveThing:GameObject
    {
        public Image img { get; set; }
    }
}

 创建MoveThing类

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TankWar
{
    enum Direction{ 
        UP,
        DOWN,
        LEFT,
        RIGHT
    }
    class MoveThing:GameObject
    {
        public Bitmap BitmapUp { get; set; }
        public Bitmap BitmapDown { get; set; }
        public Bitmap BitmapLeft { get; set; }
        public Bitmap BitmapRight { get; set; }
        public int Speed { get; set; }
        public Direction direction { get; set; }
    }
}

创建MyTank、EnemyTank、Bullet,分别继承MoveThing

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TankWar
{
    class MyTank:MoveThing
    {
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TankWar
{
    class EnemyTank:MoveThing
    {
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TankWar
{
    class Bullet:MoveThing
    {
    }
}

为GameObject添加抽象方法以获取图片对象和在画布上画图片的公共方法 

子类实现抽象方法,红线部分按下alt+enter选择实现抽象类,就会自动补充好实现方法,然后根据自己的逻辑需要修改即可 

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TankWar
{
    class NotMoveThing:GameObject
    {
        public Image img { get; set; }

        protected override Image GetImage()
        {
            return img;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TankWar
{
    enum Direction{ 
        UP,
        DOWN,
        LEFT,
        RIGHT
    }
    class MoveThing:GameObject
    {
        public Bitmap BitmapUp { get; set; }
        public Bitmap BitmapDown { get; set; }
        public Bitmap BitmapLeft { get; set; }
        public Bitmap BitmapRight { get; set; }
        public int Speed { get; set; }
        public Direction direction { get; set; }

        protected override Image GetImage()
        {
            switch (direction) {
                case Direction.UP:
                    return BitmapUp;
                case Direction.DOWN:
                    return BitmapDown;
                case Direction.LEFT:
                    return BitmapLeft;
                case Direction.RIGHT:
                    return BitmapRight;
                default:
                    return BitmapUp;
            }
        }
    }
}

将黑底图片设为透明

绘制墙,导入图片和音频资源(同上),创建GameObjectManager

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TankWar.Properties;

namespace TankWar
{
    class GamebjectManager
    {
        private static List<NotMoveThing> wallList = new List<NotMoveThing>();//保存所有墙对象

        public static void DrawMap() {
            foreach (NotMoveThing  wall in wallList) {
                wall.DrawSelf();//绘制墙
            }
        }
        public static void CreateMap() {
            CreateWall(1, 1, 5,wallList);//创建墙对象
        }

        /**
         * x,y代表一个30*30的方格的位置,如第一格是0,0,我们在绘画时从1,1位置开始画
         * count代表要创建的强对象个数
         */
        private static void CreateWall(int x,int y,int count,List<NotMoveThing> wallList) {
            int xPosition = x * 30;
            int yPosition = y * 30;
            for (int i=yPosition;i<yPosition+count*30; i+=15) {
                NotMoveThing wall1 = new NotMoveThing(xPosition,i,Resources.wall);
                NotMoveThing wall2 = new NotMoveThing(xPosition+15, i, Resources.wall);
                wallList.Add(wall1);
                wallList.Add(wall2);
            }
        }
    }
}

新增NotMoveThing构造f方法

​
新增NotMoveThing构造方法

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TankWar
{
    class NotMoveThing:GameObject
    {
        public Image img { get; set; }

        protected override Image GetImage()
        {
            return img;
        }

        public NotMoveThing(int x,int y,Image img) {
            this.X = x;
            this.Y = y;
            this.img = img;
        }
    }
}

在GameFrameWork中调用

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TankWar
{
    class GameFramework
    {
        public static Graphics g;
        public static void Start() {
            GamebjectManager.CreateMap();
        }

        public static void Update() {
            GamebjectManager.DrawMap();
        }
    }
}

效果如下,但是会出现闪烁问题(这是因为每一帧都需要重新绘制) 

 为了解决闪烁问题,我们可以采用把所有的图像绘制在一张图片上,然后再把图片绘制到画布上

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TankWar
{
    public partial class Form1 : Form
    {
        private Thread thread;
        private static Graphics windowG;//窗口画布对象
        private static Bitmap tempBmp;//临时图片对象

        public Form1()
        {
            InitializeComponent();
            this.StartPosition = FormStartPosition.CenterScreen;//使窗口在屏幕居中显示

            windowG = this.CreateGraphics();//创建窗体画布

            tempBmp = new Bitmap(450,450);//创建临时图片
            Graphics bmpG = Graphics.FromImage(tempBmp);//根据图片对象创建临时画布对象
            GameFramework.g = bmpG;//赋值,以便在GameFramework中拿到此对象


            thread = new Thread(new ThreadStart(GameMainThread));
            thread.Start();
        }

        private static void GameMainThread() {
            GameFramework.Start();
            int sleepTime = 1000 / 60; //值为:1/60s
            while (true)
            {
                GameFramework.g.Clear(Color.Black);//将画布内容清空,并置为黑色
                GameFramework.Update();//画画
                windowG.DrawImage(tempBmp, 0, 0);
                Thread.Sleep(sleepTime);//每执行一次休息一段时间,保证1s执行60次
            }
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            thread.Abort();
        }
    }
}

这样图像就不再闪动了 

继续创建其他墙,并添加图片参数

继续添加墙和boss,最终代码和最终效果

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TankWar.Properties;

namespace TankWar
{
    class GamebjectManager
    {
        private static List<NotMoveThing> wallList = new List<NotMoveThing>();//保存所有普通墙对象
        private static List<NotMoveThing> steelList = new List<NotMoveThing>();//保存所有钢铁墙对象
        private static NotMoveThing boss;//保存boss对象
        public static void DrawMap() {
            foreach (NotMoveThing  wall in wallList) {
                wall.DrawSelf();//绘制墙
            }
            foreach (NotMoveThing wall in steelList) {
                wall.DrawSelf();//绘制墙
            }
            boss.DrawSelf();
        }
        public static void CreateMap() {
            CreateWall(1, 1, 5,Resources.wall,wallList);//创建墙对象
            CreateWall(3, 1, 5, Resources.wall, wallList);//创建墙对象
            CreateWall(5, 1, 4, Resources.wall, wallList);//创建墙对象
            CreateWall(7, 1, 3, Resources.wall, wallList);//创建墙对象
            CreateWall(9, 1, 4, Resources.wall, wallList);//创建墙对象
            CreateWall(11, 1, 5, Resources.wall, wallList);//创建墙对象
            CreateWall(13, 1, 5, Resources.wall, wallList);//创建墙对象

            CreateWall(7, 5, 1, Resources.steel, steelList);//创建钢铁墙对象
            CreateWall(0, 7, 1, Resources.steel, steelList);//创建钢铁墙对象
            CreateWall(14, 7, 1, Resources.steel, steelList);//创建钢铁墙对象

            CreateWall(2, 7, 1, Resources.wall, wallList);
            CreateWall(3, 7, 1, Resources.wall, wallList);
            CreateWall(4, 7, 1, Resources.wall, wallList);
            CreateWall(6, 7, 1, Resources.wall, wallList);
            CreateWall(7, 6, 2, Resources.wall, wallList);
            CreateWall(8, 7, 1, Resources.wall, wallList);
            CreateWall(10, 7, 1, Resources.wall, wallList);
            CreateWall(11, 7, 1, Resources.wall, wallList);
            CreateWall(12, 7, 1, Resources.wall, wallList);

            CreateWall(1, 9, 5, Resources.wall, wallList);//创建墙对象
            CreateWall(3, 9, 5, Resources.wall, wallList);//创建墙对象
            CreateWall(5, 9, 3, Resources.wall, wallList);//创建墙对象
            CreateWall(6, 10, 1, Resources.wall, wallList);//创建墙对象
            CreateWall(7, 10, 1, Resources.wall, wallList);//创建墙对象
            CreateWall(8, 10, 1, Resources.wall, wallList);//创建墙对象
            CreateWall(9, 9, 3, Resources.wall, wallList);//创建墙对象

            CreateWall(11, 9, 5, Resources.wall, wallList);//创建墙对象
            CreateWall(13, 9, 5, Resources.wall, wallList);//创建墙对象

            CreateWall(6, 13, 2, Resources.wall, wallList);//创建墙对象
            CreateWall(7, 13, 1, Resources.wall, wallList);//创建墙对象
            CreateWall(8, 13, 2, Resources.wall, wallList);//创建墙对象

            CreateBoss(7, 14,Resources.Boss);
        }

        /**
         * x,y代表一个30*30的方格的位置,如第一格是0,0,我们在绘画时从1,1位置开始画
         * count代表要创建的强对象个数
         */
        private static void CreateWall(int x,int y,int count,Image img,List<NotMoveThing> wallList) {
            int xPosition = x * 30;
            int yPosition = y * 30;
            for (int i=yPosition;i<yPosition+count*30; i+=15) {//例如从30到180(150+30),需执行10次
                NotMoveThing wall1 = new NotMoveThing(xPosition,i,img);
                NotMoveThing wall2 = new NotMoveThing(xPosition+15, i, img);
                wallList.Add(wall1);
                wallList.Add(wall2);
            }
        }

        private static void CreateBoss(int x,int y,Image img) {
            int xPosition = x * 30;
            int yPosition = y * 30;
            boss = new NotMoveThing(xPosition, yPosition,img);
        }
    }
}

修改窗体属性,使其不能用鼠标拖动改变窗体大小,但可以最大化和最小化

3.绘制主角(即自己的坦克)

GameObjectManager类新增如下两个方法,并在GameFramework中添加调用

 添加对应的构造方法

最终效果图:

4.控制坦克的移动

添加键盘监听事件函数(同上,再Form的属性->事件下找到KeyDown和KeyUp)

调用鼠标按下和起来的方法

这样控制坦克移动随可以,但会有一个一开始的卡顿(按下后移动一下然后停顿一s再往前走)

因此我们这样做,定义一个isMoving变量,当键盘按下后设置为true,当键盘起来时设置为false。然后根据isMoving来控制移动

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using TankWar.Properties;

namespace TankWar
{
    class MyTank:MoveThing
    {
        public bool IsMoving { get; set; }
        public MyTank(int x,int y,int speed) {
            this.IsMoving = false;
            this.X = x;
            this.Y = y;
            this.Speed = speed;
            this.direction = Direction.UP;
            BitmapDown = Resources.MyTankDown;
            BitmapUp = Resources.MyTankUp;
            BitmapRight = Resources.MyTankRight;
            BitmapLeft = Resources.MyTankLeft;
        }

        public void KeyDown(KeyEventArgs args) {
            switch (args.KeyCode) {
                case Keys.W:
                    direction = Direction.UP;
                    IsMoving = true;
                    break;
                case Keys.S:
                    direction = Direction.DOWN;
                    IsMoving = true;
                    break;
                case Keys.A:
                    direction = Direction.LEFT;
                    IsMoving = true;
                    break;
                case Keys.D:
                    direction = Direction.RIGHT;
                    IsMoving = true;
                    break;
            }
        }

        public void KeyUp(KeyEventArgs args) {
            switch (args.KeyCode)
            {
                case Keys.W:
                    IsMoving = false;
                    break;
                case Keys.S:
                    IsMoving = false;
                    break;
                case Keys.A:
                    IsMoving = false;
                    break;
                case Keys.D:
                    IsMoving = false;
                    break;
            }
        }

        private void Move() {
            if (IsMoving==false) {
                return;
            }
            switch (direction) {
                case Direction.UP:
                    Y -= Speed;
                    break;
                case Direction.DOWN:
                    Y += Speed;
                    break;
                case Direction.LEFT:
                    X -= Speed;
                    break;
                case Direction.RIGHT:
                    X += Speed;
                    break;
            }
        } 

        public override void Update()
        {
            Move();
            base.Update();
        }
    }
}

现在我们的坦克可以移动了,但是会穿过墙体和外边界 

添加墙体检测,在MoveCheck方法添加墙体检测

 处理资源冲突异常:

在MoveThing中重写此方法 

5.添加敌人坦克

添加EnemyTank生成方法

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using TankWar.Properties;

namespace TankWar
{
    class GamebjectManager
    {
        ...
        private static List<EnemyTank> tankList = new List<EnemyTank>();//保存敌人坦克对象
        private static int enemyBornSpeed = 60;//敌人坦克的生成速度
        private static int enemyBornCount = 60;//敌人坦克的生成数量
        private static Point[] points=new Point[3];

        public static void Start() {
            points[0].X = 0;
            points[0].Y = 0;
            points[1].X = 7*30;
            points[1].Y = 0;
            points[2].X = 14*30;
            points[2].Y = 0;
        }

        public static void Update() {
            ...
            foreach (EnemyTank tank in tankList) {
                tank.Update();
            }
            ...
            EnemyBorn();
        }

        public static void EnemyBorn() {
            enemyBornCount++;
            if (enemyBornCount<enemyBornSpeed) {
                return;
            }
            Random rd = new Random();
            int index=rd.Next(0, 3);//生成0-3之间的随机整数,不包含3
            Point positon = points[index];
            int enemyType = rd.Next(1, 5);
            switch (enemyType) {
                case 1:
                    CreateEnemyTank1(positon.X,positon.Y);
                    break;
                case 2:
                    CreateEnemyTank2(positon.X, positon.Y);
                    break;
                case 3:
                    CreateEnemyTank3(positon.X, positon.Y);
                    break;
                case 4:
                    CreateEnemyTank4(positon.X, positon.Y);
                    break;
            }
            enemyBornCount = 0;
        }

        private static void CreateEnemyTank1(int x,int y) {
            EnemyTank tank = new EnemyTank(x, y, 2, Resources.GrayDown, Resources.GrayUp, Resources.GrayLeft, Resources.GrayRight);
            tankList.Add(tank);
        }
        private static void CreateEnemyTank2(int x, int y)
        {
            EnemyTank tank = new EnemyTank(x, y, 2, Resources.GreenDown, Resources.GreenUp, Resources.GreenLeft, Resources.GreenRight);
            tankList.Add(tank);
        }
        private static void CreateEnemyTank3(int x, int y)
        {
            EnemyTank tank = new EnemyTank(x, y, 4, Resources.QuickDown, Resources.QuickUp, Resources.QuickLeft, Resources.QuickRight);
            tankList.Add(tank);
        }
        private static void CreateEnemyTank4(int x, int y)
        {
            EnemyTank tank = new EnemyTank(x, y, 1, Resources.SlowDown, Resources.SlowUp, Resources.SlowLeft, Resources.SlowRight);
            tankList.Add(tank);
        }
    }
}

创建EnemyTank构造函数及移动

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TankWar
{
    class EnemyTank:MoveThing
    {
        private Random r = new Random();
        public EnemyTank(int x, int y, int speed,Bitmap bmpDown,Bitmap bmpUp,Bitmap bmpLeft,Bitmap bmpRight)
        {
            //this.IsMoving = true;
            this.X = x;
            this.Y = y;
            this.Speed = speed;
            BitmapDown = bmpDown;
            BitmapUp = bmpUp;
            BitmapRight = bmpRight;
            BitmapLeft = bmpLeft;
            this.Direction = Direction.DOWN;
        }

        public override void Update()
        {
            MoveCheck();//移动前检查
            Move();
            base.Update();
        }

        private void ChangeDirection() {
            while (true) {
                Direction dir = (Direction)r.Next(0, 4);
                if (Direction == dir)
                {
                    continue;
                } else {
                    Direction = dir;
                    break;
                }
            }
            MoveCheck();
        }

        private void Move()
        {
            switch (Direction)
            {
                case Direction.UP:
                    Y -= Speed;
                    break;
                case Direction.DOWN:
                    Y += Speed;
                    break;
                case Direction.LEFT:
                    X -= Speed;
                    break;
                case Direction.RIGHT:
                    X += Speed;
                    break;
            }
        }

        private void MoveCheck()
        {
            #region 检查是否超过窗体边界
            if (Direction == Direction.UP)
            {
                if (Y - Speed < 0)
                {
                    ChangeDirection();
                    return;
                }
            }
            else if (Direction == Direction.DOWN)
            {
                if (Y + Speed + Height > 450)
                {
                    ChangeDirection();
                    return;
                }
            }
            else if (Direction == Direction.LEFT)
            {
                if (X - Speed < 0)
                {
                    ChangeDirection();
                    return;
                }
            }
            else if (Direction == Direction.RIGHT)
            {
                if (X + Speed + Width > 450)
                {
                    ChangeDirection();
                    return;
                }
            }
            #endregion

            Rectangle rect = GetRectangle();
            switch (Direction)
            {
                case Direction.UP:
                    rect.Y -= Speed;
                    break;
                case Direction.DOWN:
                    rect.Y += Speed;
                    break;
                case Direction.LEFT:
                    rect.X -= Speed;
                    break;
                case Direction.RIGHT:
                    rect.X += Speed;
                    break;
            }
            if (GamebjectManager.isCollidedWall(rect) != null)
            {
                ChangeDirection();
                return;
            }
            if (GamebjectManager.isCollidedSteel(rect) != null)
            {
                ChangeDirection();
                return;
            }
            if (GamebjectManager.isCollidedBoss(rect))
            {
                ChangeDirection();
                return;
            }
        }
    }
}

6.发射子弹

在MyTank中添加空格监听,当按下空格后发射子弹

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TankWar.Properties;

namespace TankWar
{
    enum Tag { 
        MyTank,
        EnemyTank
    }
    class Bullet:MoveThing
    {
        public Tag Tag { get; set; } //用于判断是自己的坦克发射的子弹还是敌人的坦克发射的子弹
        public bool isDestroy { get; set; }
        public Bullet(int x, int y, int speed,Direction dir,Tag tag)
        {
            isDestroy = false;
            this.X = x;
            this.Y = y;
            this.Speed = speed;
            BitmapDown = Resources.BulletDown;
            BitmapUp = Resources.BulletUp;
            BitmapRight = Resources.BulletRight;
            BitmapLeft = Resources.BulletLeft;
            this.Direction = dir;
            this.Tag = tag;
            this.X -= Width / 2;
            this.Y -= Height / 2;
        }

        public override void DrawSelf()
        {
            base.DrawSelf();
        }

        public override void Update()
        {
            MoveCheck();//移动前检查
            Move();
            base.Update();
        }

        private void Move()
        {
            switch (Direction)
            {
                case Direction.UP:
                    Y -= Speed;
                    break;
                case Direction.DOWN:
                    Y += Speed;
                    break;
                case Direction.LEFT:
                    X -= Speed;
                    break;
                case Direction.RIGHT:
                    X += Speed;
                    break;
            }
        }

        private void MoveCheck()
        {
            #region 检查是否超过窗体边界
            if (Direction == Direction.UP)
            {
                if (Y +Height/2+3< 0)//子弹图片自身的高度/2和子弹本身的高度的一半(大约为3)
                {
                    isDestroy=true;
                    return;
                }
            }
            else if (Direction == Direction.DOWN)
            {
                if (Y + Height/2 -3 > 450)
                {
                    isDestroy = true;
                    return;
                }
            }
            else if (Direction == Direction.LEFT)
            {
                if (X+Width/2-3 < 0)
                {
                    isDestroy = true;
                    return;
                }
            }
            else if (Direction == Direction.RIGHT)
            {
                if (X+Width/2+3 > 450)
                {
                    isDestroy = true;
                    return;
                }
            }
            #endregion

            ...
        }
    }
}

当子弹超出边界时,判断isDestroy并销毁

当子弹遇到墙或者敌人后销毁它们

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TankWar.Properties;

namespace TankWar
{
    enum Tag { 
        MyTank,
        EnemyTank
    }
    class Bullet:MoveThing
    {
        ...

        private void MoveCheck()
        {
            ...
            
            Rectangle rect = GetRectangle();
            rect.X = X + Width / 2 - 3;//取到子弹实际位置的左上角横坐标
            rect.Y = Y + Height / 2 - 3;
            rect.Height = 3;
            rect.Width = 3;
            NotMoveThing wall = null;
            if ((wall=GamebjectManager.isCollidedWall(rect)) != null)
            {
                isDestroy = true;
                GamebjectManager.DestoryWall(wall);
                return;
            }
            if (GamebjectManager.isCollidedSteel(rect) != null)
            {
                isDestroy = true;
                return;
            }
            if (GamebjectManager.isCollidedBoss(rect))
            {
                //ChangeDirection();
                return;
            }
            if (Tag==Tag.MyTank) {
                EnemyTank tank = null;
                if ((tank = GamebjectManager.isCollidedEnemyTank(rect))!=null)
                {
                    isDestroy = true;
                    GamebjectManager.DestoryTank(tank);
                    return;
                }
            }
            
        }
    }
}

效果 

7.添加爆炸效果

创建爆炸效果类

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TankWar.Properties;

namespace TankWar
{
    class Explosion : GameObject
    {
        public bool IsNeedDestory { get; set;}
        private int playSpeed = 1;
        private int playCount = 0;//0/2=0 1/2=0 2/2=1 3/2=1..每张图片停留2帧 
        private int index = 0;
        private Bitmap[] bmpArray = new Bitmap[] {
            Resources.EXP1,
            Resources.EXP2,
            Resources.EXP3,
            Resources.EXP4,
            Resources.EXP5
        };
        public Explosion(int x,int y) {
            foreach (Bitmap bmp in bmpArray) {
                bmp.MakeTransparent(Color.Black);
            }
            this.X = x - bmpArray[0].Width / 2;//得到左上角坐标
            this.Y = y - bmpArray[0].Height / 2;
            IsNeedDestory = false;
        }

        protected override Image GetImage()
        {
            if (index>4) {
                return bmpArray[4];
            }
            return bmpArray[index];
        }

        public override void Update()
        {
            playCount++;
            index = (playCount - 1) / playSpeed;//获取播放图片的索引
            if (index>4) {
                IsNeedDestory = true;
            }
            base.Update();
        }
    }
}

添加生成爆炸效果的代码 

8.优化敌人坦克

敌人坦克可发射子弹 

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TankWar
{
    class EnemyTank:MoveThing
    {
        public int AttackSpeed { get; set; }
        private int attackCount = 0;
        ...
        public EnemyTank(int x, int y, int speed,Bitmap bmpDown,Bitmap bmpUp,Bitmap bmpLeft,Bitmap bmpRight)
        {
            ...
            AttackSpeed = 60;
        }

        public override void Update()
        {
            ...
            AttackCheck();//是否需要攻击
            ...
        }

        ...
        private void AttackCheck() {
            attackCount++;
            if (attackCount < AttackSpeed) return;
            Attack();
            attackCount = 0;
        }
        private void Attack()
        {
            int x = this.X;
            int y = this.Y;
            switch (Direction)
            {
                case Direction.UP:
                    x = x + Width / 2;
                    break;
                case Direction.DOWN:
                    x = x + Width / 2;
                    y += Height;
                    break;
                case Direction.LEFT:
                    y = y + Height / 2;
                    break;
                case Direction.RIGHT:
                    x += Width;
                    y = y + Height / 2;
                    break;
            }
            GamebjectManager.CreateBullet(x, y, Tag.EnemyTank, Direction);
        }
    }
}

 敌人可随机转向,而不是遇到障碍我才转向

 

 

 子弹可以攻击主角坦克,主角坦克可以被攻击4次,第4次主角坦克消失并回归起点

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using TankWar.Properties;

namespace TankWar
{
    class MyTank:MoveThing
    {
        public int HP { get; set; }//血量
        ...
        private int originalX, originalY;
        public MyTank(int x,int y,int speed) {
            ...
            originalX = x;
            originalY = y;
            ...
            HP = 4;
        }

        ...

        public void TakeDamage() {
            HP--;
            if (HP<=0) {
                X = originalX;
                Y = originalY;
                HP = 4;
            }
        }
    }
}

 子弹攻击boss时游戏结束

9.添加音效

using System;
using System.Collections.Generic;
using System.Linq;
using System.Media;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using TankWar.Properties;

namespace TankWar
{
    class SoundManager
    {

        private static SoundPlayer startPlayer = new SoundPlayer();
        private static SoundPlayer addPlayer = new SoundPlayer();
        private static SoundPlayer blastPlayer = new SoundPlayer();
        private static SoundPlayer firePlayer = new SoundPlayer();
        private static SoundPlayer hitPlayer = new SoundPlayer();

        public static void InitSound() {
            startPlayer.Stream = Resources.start;
            addPlayer.Stream = Resources.add;
            blastPlayer.Stream = Resources.blast;
            firePlayer.Stream = Resources.fire;
            hitPlayer.Stream = Resources.hit;
        }
        public static void PlayStart() {
            startPlayer.Play();
        }
        public static void PlayAdd()
        {
            addPlayer.Play();
        }
        public static void PlayBlast() {
            blastPlayer.Play();
        }
        public static void PlayFire()
        {
            firePlayer.Play();
        }
        public static void PlayHit()
        {
            hitPlayer.Play();
        }

    }
}

使用Unity制作

1.创建工程

修改布局模式为2 by 3

Project面板切换单行模式 

导入资源(将unitypackage文件拖到Project面板,在弹出的弹窗点击Import即可)

确保单张图片的SpriteMode选择为Single,多张小图片组成的图片选择为Multiple,TextureType选择为Sprite2D 

切割图片(点击Sprite Editor->Slice->Gride By Cell Size,输入最小图片大小点击Slice即可,之后就可以展开看到切割后图片)

创建Player(将Player1拖到Hierarchy面板即可),创建其对应预制体,同理可创建Wall、Barrier、Grass、Heart

创建动画效果(出生动画、爆炸动画、护盾动画、河流动画),选择动画然后拖到Hierarchy面板,然后命名即可,然后创建其对于预制体,然后会自动产生俩个文件,将其放到新建文件夹Animator和AnimatorController中(适当改名以清晰结构)

2.控制Player移动

新建脚本Player放在新建文件夹Scripts下,与Player对象关联,然后编辑内容

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

public class Player : MonoBehaviour
{
    public float moveSpeed=3;
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        float h = Input.GetAxisRaw("Horizontal");
        transform.Translate(Vector3.right * h * moveSpeed * Time.deltaTime,Space.World);//乘以delTatime表示按每一秒移动,以世界坐标轴移动
        float v = Input.GetAxisRaw("Vertical");
        transform.Translate(Vector3.up * v * moveSpeed * Time.deltaTime, Space.World);
    }
}

新建一个数组,用以存放4个方向的图片

获取SpriteRender对象控制图片的显示

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

public class Player : MonoBehaviour
{
    public float moveSpeed=3;
    private SpriteRenderer sr;
    public Sprite[] tankSprite;
    private void Awake()
    {
        sr = GetComponent<SpriteRenderer>();//得到图片渲染组件
    }
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        float h = Input.GetAxisRaw("Horizontal");
        transform.Translate(Vector3.right * h * moveSpeed * Time.deltaTime,Space.World);//乘以delTatime表示按每一秒移动,以世界坐标轴移动
        if (h < 0) {
            sr.sprite = tankSprite[3];//左
        } else if (h>0) {
            sr.sprite = tankSprite[1];//右
        }
        float v = Input.GetAxisRaw("Vertical");
        transform.Translate(Vector3.up * v * moveSpeed * Time.deltaTime, Space.World);
        if (v < 0)
        {
            sr.sprite = tankSprite[2];
        }
        else if (v > 0)
        {
            sr.sprite = tankSprite[0];
        }
    }
}

3.为Player添加碰撞效果

添加碰撞器(点击Add Component后如图)

添加刚体组件

然后将Player的所有新加属性应用到其预制体上 

然后为剩余的Map预制体添加碰撞器(这里把River换到了Map文件中,Player放在了新文件夹下)

然后在运行后发现坦克下落了(这是因为重力的原因,我们将其重力设为0即可) 

去掉Grass的碰撞器,因为逻辑上不需要碰撞

当我们在运行时会发现坦克会在碰撞体边角处发生z轴的旋转,所以我们在这里勾选如图选项即可

处理坦克遇到墙面时抖动滚动的情形(将所有代码放大FixedUpdate方法中并改Time.DeltaTime为Time.fixedDeltaTime,即固定每一帧执行的时间从而保证物理碰撞时相同的)

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

public class Player : MonoBehaviour
{
    public float moveSpeed=3;
    private SpriteRenderer sr;
    public Sprite[] tankSprite;
    private void Awake()
    {
        sr = GetComponent<SpriteRenderer>();//得到图片渲染组件
    }
    // Start is called before the first frame update
    void Start()
    {
        
    }

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

    private void FixedUpdate()
    {
        float h = Input.GetAxisRaw("Horizontal");
        transform.Translate(Vector3.right * h * moveSpeed * Time.deltaTime, Space.World);//乘以delTatime表示按每一秒移动,以世界坐标轴移动
        if (h < 0)
        {
            sr.sprite = tankSprite[3];//左
        }
        else if (h > 0)
        {
            sr.sprite = tankSprite[1];//右
        }
        float v = Input.GetAxisRaw("Vertical");
        transform.Translate(Vector3.up * v * moveSpeed * Time.deltaTime, Space.World);
        if (v < 0)
        {
            sr.sprite = tankSprite[2];
        }
        else if (v > 0)
        {
            sr.sprite = tankSprite[0];
        }
    }
}

在测试过程种,我们发现同时按下两个键(如左上、左下等)坦克会歇着走,这样是不好的用户体验,所以我们添加如下内容,同时为了代码简洁我们把它放到一个Move方法中调用

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

public class Player : MonoBehaviour
{
    ...
    private void FixedUpdate()
    {
        Move();   
    }

    private void Move()
    {
        float h = Input.GetAxisRaw("Horizontal");
        transform.Translate(Vector3.right * h * moveSpeed * Time.fixedDeltaTime, Space.World);//乘以delTatime表示按每一秒移动,以世界坐标轴移动
        if (h < 0)
        {
            sr.sprite = tankSprite[3];//左
        }
        else if (h > 0)
        {
            sr.sprite = tankSprite[1];//右
        }

        if (h != 0)
        {
            return;//处理两键同时按下导致坦克斜着走问题
        }


        float v = Input.GetAxisRaw("Vertical");
        transform.Translate(Vector3.up * v * moveSpeed * Time.fixedDeltaTime, Space.World);
        if (v < 0)
        {
            sr.sprite = tankSprite[2];
        }
        else if (v > 0)
        {
            sr.sprite = tankSprite[0];
        }
    }
}

设置层级显示(即多张图片叠在一起后优先显示那张图片),下图将出生动画层级设为1(默认为0)则坦克图片经过时就会被遮盖了,同理可设置Grass、Explosion等

然后我们添加Bullet(子弹),将图片拖入左侧面板即可,然后再创建其预制体(放在Tank下)

 添加发射子弹的函数Attack,并绑定预制体

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

public class Player : MonoBehaviour
{
    ...
    public GameObject bulletPrefab;//子弹预制体
    private void Awake()
    {
        sr = GetComponent<SpriteRenderer>();//得到图片渲染组件
    }
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        Attack(); //发射子弹,一定要放在Update中,如果放在FixedUpdate会偶尔发出不出子弹
    }

    private void FixedUpdate()
    {
        Move(); //坦克移动  
        
    }

    private void Attack() {
        if (Input.GetKeyDown(KeyCode.Space)) {
            Instantiate(bulletPrefab, transform.position, transform.rotation);
        }
    }
    ...
}

控制子弹的角度(这里需要把欧拉角转换为四元数表示传入),然后再每次坦克变向时设置bulletAngle

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

public class Player : MonoBehaviour
{
    ...
    private Vector3 bulletAngle;//子弹的发射角度
    ...

    // Update is called once per frame
    void Update()
    {
        Attack(); //发射子弹
    }

    private void FixedUpdate()
    {
        Move(); //坦克移动  
        
    }

    private void Attack() {
        if (Input.GetKeyDown(KeyCode.Space)) {
            Instantiate(bulletPrefab, transform.position, Quaternion.Euler(transform.eulerAngles+bulletAngle));
        }
    }

    private void Move()
    {
        float v = Input.GetAxisRaw("Vertical");
        transform.Translate(Vector3.up * v * moveSpeed * Time.fixedDeltaTime, Space.World);
        if (v < 0)
        {
            sr.sprite = tankSprite[2];
            bulletAngle = new Vector3(0, 0, -180);
        }
        else if (v > 0)
        {
            sr.sprite = tankSprite[0];
            bulletAngle = new Vector3(0, 0, 0);
        }

        if (v != 0)
        {
            return;//处理两键同时按下导致坦克斜着走问题
        }

        float h = Input.GetAxisRaw("Horizontal");
        transform.Translate(Vector3.right * h * moveSpeed * Time.fixedDeltaTime, Space.World);//乘以delTatime表示按每一秒移动,以世界坐标轴移动
        if (h < 0)
        {
            sr.sprite = tankSprite[3];//左
            bulletAngle = new Vector3(0, 0, 90);//这里记住坐标是反着的,
        }
        else if (h > 0)
        {
            sr.sprite = tankSprite[1];//右
            bulletAngle = new Vector3(0, 0, -90);
        }
    }
}

欧拉角:欧拉角包括3个旋转,根据这3个旋转来指定一个刚体的朝向。这3个旋转分别绕x轴,y轴和z轴,分别称为Pitch,Yaw和Roll

绕X轴(红线旋转)

绕Y轴(绿线)旋转 

绕Z轴(蓝线)旋转 

与我们在Unity的坐标一一对应 

四元数

这个四元数真的很难理解,我们先来看一个我们好理解的二元数(也即我们学过的复数),如图我们画一条线段AB(用3+4i表示从A点到B点的路程变化,而B的坐标为(3,4)),当我们想把这条线段旋转90度时,我们得到-6+4i,从而达到旋转后B点的坐标为(-6,4),这并非巧合,而是可通过计算得到:(4+6i)*i=4i-6,即乘以i代表旋转90度。如果想旋转45度呢?乘以 1/\sqrt{2}+1/\sqrt{2}i 就可以得到(即旋转度数为\theta,乘以cos\theta+sin\thetai)  

然后我们推广到三维空间,同样的几何意义,一个空间线段旋转指定度数得到新的坐标,虚四元数表示为:q=q0+q1i+q2j+q3k,其中i^{2}+j^{2}+k^{2}=ijk=-1,则旋转后的四元数记为p=(q0+q1i+q2j+q3k)*(cos \theta+sin \thetai+sin \thetaj+sin \thetak)即可得到了

控制子弹的移动,创建Bullet脚本,并放在Scripts下,并于Bullet预制体绑定

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

public class Bullet : MonoBehaviour
{
    public float moveSpeed = 10;
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        transform.Translate(transform.up * moveSpeed * Time.deltaTime, Space.World);//世界坐标轴
    }
}

为子弹添加CD(不添加会随这快速按键发射太快)

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

public class Player : MonoBehaviour
{
    ...
    private float timeVal;//发射子弹CD


   
    // Update is called once per frame
    void Update()
    {
        Attack();
        if (timeVal >= 0.4){
            Attack(); //发射子弹
        }else {
            timeVal += Time.deltaTime;
        }
    }

    private void FixedUpdate()
    {
        Move(); //坦克移动  
        
    }

    private void Attack() {
        if (Input.GetKeyDown(KeyCode.Space)) {
            Instantiate(bulletPrefab, transform.position, Quaternion.Euler(transform.eulerAngles+bulletAngle));
            timeVal = 0;
        }
    }
    ...
}

为子弹添加触发器(记得勾选Is Trigger)和钢体组件(重力设为0)

 

为预制体添加Tag,并修改对应Player、Barrier、Wall的Tag

创建空气墙(为了给四周做一个边界,从而判断子弹何时被销毁)复制一个Barrier取名为AirBarrier,删除其SpriteRenderer组件(这样它就变透明不会渲染样式了,即看不见的墙)然后创建其对应预制体 

 添加坦克开始时的护盾效果,将护盾效果放在Player下面(这样就会随着坦克移动了),然后在Player脚本添加护盾效果预制体(记得绑定)、护盾效果时间和是否保护标志等逻辑代码(当护盾时间不大于0时隐藏护盾效果)最后将Player的属性Apply到预制体上

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

public class Player : MonoBehaviour
{
    ...
    private float defendTimeVal=3;//保护时间
    private bool isDefended=true;//是否保护
    ...
    public GameObject defendEffectPrefab;//护盾特效预制体
    ...

    // Update is called once per frame
    void Update()
    {
        //是否处于无敌状态
        if (isDefended) {
            defendEffectPrefab.SetActive(true);
            defendTimeVal -= Time.deltaTime;
            if (defendTimeVal<=0) {
                isDefended = false;
                defendEffectPrefab.SetActive(false);
            }
        }
        if (timeVal >= 0.4)
        {
            Attack(); //发射子弹
        }
        else
        {
            timeVal += Time.deltaTime;
        }

    }
}

添加Boos的破坏效果,新建Heart脚本,并于Heart预制体绑定,编码,然后绑定图片

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

public class Heart : MonoBehaviour
{
    private SpriteRenderer sr;
    public Sprite BrokenSprite;
    // Start is called before the first frame update
    void Start()
    {
        sr= GetComponent<SpriteRenderer>();
    }

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

    public void Die() {
        sr.sprite = BrokenSprite;
    }
}

为子弹创建触发方法(针对Wall、Heart、Barrier)(Barrier的IsPlayerBullet要勾选)

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

public class Bullet : MonoBehaviour
{
    public float moveSpeed = 10;
    public bool isPlayerBullet;//是否为玩家子弹
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        transform.Translate(transform.up * moveSpeed * Time.deltaTime, Space.World);//世界坐标轴
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        switch (collision.tag) {
            case "Tank":
                if (!isPlayerBullet) {
                    collision.SendMessage("Die");//执行碰撞到的物体的Die方法
                }
                break;
            case "Heart":
                collision.SendMessage("Die");//执行碰撞到的物体的Die方法
                Destroy(gameObject);//销毁子弹自身
                break;
            case "Enemy":
                break;
            case "Wall":
                Destroy(collision.gameObject);//销毁碰撞到的物体
                Destroy(gameObject);//销毁子弹自身
                break;
            case "Barrier":
                Destroy(gameObject);//销毁子弹自身
                break;
            default:
                break;
        }
    }
}

重命名子弹名称,并新建一个EnemyBullet,然后其IsPlayerBullet取消勾选,PlayerBullet则勾选 

创建爆炸特效脚本并与预制体绑定

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

public class Explosion : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        Destroy(gameObject, 0.2f);
    }

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

创建出生效果,创建Born脚本(与Born预制体绑定),然后将Player预制体与脚本绑定

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

public class Born : MonoBehaviour
{
    public GameObject playerPrefab;
    // Start is called before the first frame update
    void Start()
    {
        Invoke("BornTank", 1f);//延时调用
        Destroy(gameObject, 1f);//延时销毁
    }

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

    private void BornTank() {
        Instantiate(playerPrefab, transform.position, Quaternion.identity);
    }
}

创建敌人(设置钢体、碰撞器、重力为0,Z轴定向,设置4各方向图片、创建对应预制体,绑定爆炸效果预制体和子弹预制体)添加移动AI(每3秒进行一次攻击,每4秒进行一次转向),并添加敌人子弹的判定效果

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

public class Enemy : MonoBehaviour
{
    public float moveSpeed = 3;
    private Vector3 bulletAngle;//子弹的发射角度
    private float v, h;
    private float timeVal;//发射子弹CD
    private float timeValChangeDirection=4;//改变方向的时间


    private SpriteRenderer sr;
    public Sprite[] tankSprite;
    public GameObject bulletPrefab;//子弹预制体
    public GameObject explosionPrefab;//爆炸效果预制体
    private void Awake()
    {
        sr = GetComponent<SpriteRenderer>();//得到图片渲染组件
    }
    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        if (timeVal >= 3)
        {
            Attack(); //发射子弹
        }
        else
        {
            timeVal += Time.deltaTime;
        }

    }

    private void FixedUpdate()
    {
        Move(); //坦克移动  

    }

    private void Attack()
    {
            Instantiate(bulletPrefab, transform.position, Quaternion.Euler(transform.eulerAngles + bulletAngle));
            timeVal = 0;
    }

    private void Move()
    {
        if (timeValChangeDirection >= 4)
        {
            int num = Random.Range(0, 8);
            if (num > 5)
            {
                v = -1;//向下走
                h = 0;
            }
            else if (num == 0)
            {
                v = 1;//向后走
                h = 0;
            }
            else if (num > 0 && num <= 2)
            {
                h = -1;//向左走
                v = 0;
            }
            else if (num > 2 && num <= 4)
            {
                h = 1;//向右走
                v = 0;
            }
            timeValChangeDirection = 0;
        } else {
            timeValChangeDirection += Time.fixedDeltaTime;
        }
        transform.Translate(Vector3.right * h * moveSpeed * Time.fixedDeltaTime, Space.World);//乘以delTatime表示按每一秒移动,以世界坐标轴移动
        if (h < 0)
        {
            sr.sprite = tankSprite[3];//左
            bulletAngle = new Vector3(0, 0, 90);//这里记住坐标是反着的,
        }
        else if (h > 0)
        {
            sr.sprite = tankSprite[1];//右
            bulletAngle = new Vector3(0, 0, -90);
        }

        if (h != 0)
        {
            return;//处理两键同时按下导致坦克斜着走问题
        }

        transform.Translate(Vector3.up * v * moveSpeed * Time.fixedDeltaTime, Space.World);
        if (v < 0)
        {
            sr.sprite = tankSprite[2];
            bulletAngle = new Vector3(0, 0, -180);
        }
        else if (v > 0)
        {
            sr.sprite = tankSprite[0];
            bulletAngle = new Vector3(0, 0, 0);
        }
    }

    private void Die()
    {
        Instantiate(explosionPrefab, transform.position, transform.rotation);//产生爆炸效果
        Destroy(gameObject);
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{
    public float moveSpeed = 10;
    public bool isPlayerBullet;//是否为玩家子弹
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        transform.Translate(transform.up * moveSpeed * Time.deltaTime, Space.World);//世界坐标轴
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        switch (collision.tag) {
            case "Tank":
                if (!isPlayerBullet) {
                    collision.SendMessage("Die");//执行碰撞到的物体的Die方法
                    Destroy(gameObject);//销毁子弹自身                
                }
                break;
            case "Heart":
                collision.SendMessage("Die");//执行碰撞到的物体的Die方法
                Destroy(gameObject);//销毁子弹自身
                break;
            case "Enemy":
                if (isPlayerBullet) {
                    collision.SendMessage("Die");
                    Destroy(gameObject);//销毁子弹自身
                }
                break;
            case "Wall":
                Destroy(collision.gameObject);//销毁碰撞到的物体
                Destroy(gameObject);//销毁子弹自身
                break;
            case "Barrier":
                Destroy(gameObject);//销毁子弹自身
                break;
            default:
                break;
        }
    }
}

创建多个Born(绑定敌人坦克预制体,多个Born中有一个是用来生成Player的,所以需要勾选CreatePlayer)编写代码控制显示和销毁以及产生的坦克类型

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

public class Born : MonoBehaviour
{
    public GameObject playerPrefab;
    public GameObject[] enemyPrefabList;
    public bool createPlayer;
    // Start is called before the first frame update
    void Start()
    {
        Invoke("BornTank", 1f);//延时调用
        Destroy(gameObject, 1f);//延时销毁
    }

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

    private void BornTank() {
        if (createPlayer) {
            Instantiate(playerPrefab, transform.position, Quaternion.identity);
        }else {
            int num = Random.Range(0, 2);
            Instantiate(enemyPrefabList[num], transform.position, Quaternion.identity);
        }
    }
}

创建空对象命名为MapCreation,创建MapCreation脚本,声明一个GameObject数组,将下图种相关预制体拖入(右上角的锁可以锁定该页面方便拖放预制体) 

 创建BOSS和BOSS周围的墙

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

public class MapCreation : MonoBehaviour
{
    public GameObject[] item;//保存Boss、墙、障碍、出生效果、河流、草、空气墙
    private void Awake()
    {
        //在界面下边界中间位置,创建Boss
        CreateItem(item[0], new Vector3(0, -8, 0), Quaternion.identity);
        //Boss周围的墙
        CreateItem(item[1], new Vector3(-1,-8,0), Quaternion.identity);
        CreateItem(item[1], new Vector3(1, -8, 0), Quaternion.identity);
        for (int i=-1;i<2;i++) {
            CreateItem(item[1], new Vector3(i, -7, 0), Quaternion.identity);
        }
    }

    private void CreateItem(GameObject createObject,Vector3 position,Quaternion rotation) {
        GameObject item = Instantiate(createObject, position, rotation);
        item.transform.SetParent(gameObject.transform);//将新建的对象放在MapCreation下使目录简洁
    }
}

创建外围空气墙,随机位置创建其他对象(墙、障碍、海、草等),拖入一个主角坦克生成点Born

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

public class MapCreation : MonoBehaviour
{
    public GameObject[] item;//保存Boss、墙、障碍、出生效果、河流、草、空气墙
    private List<Vector3> itemPostionList=new List<Vector3>();//保存每个对象的位置,用于判断随机生成的位置是否已有对象
    private void Awake()
    {
        //在界面下边界中间位置,创建Boss
        CreateItem(item[0], new Vector3(0, -8, 0), Quaternion.identity);
        //Boss周围的墙
        CreateItem(item[1], new Vector3(-1,-8,0), Quaternion.identity);
        CreateItem(item[1], new Vector3(1, -8, 0), Quaternion.identity);
        for (int i=-1;i<2;i++) {
            CreateItem(item[1], new Vector3(i, -7, 0), Quaternion.identity);
        }
        //创建外围空气墙
        for (int i=-11;i<12;i++) {//上边界
            CreateItem(item[6], new Vector3(i, 9, 0), Quaternion.identity);
        }
        for (int i = -11; i < 12; i++)//下边界
        {
            CreateItem(item[6], new Vector3(i, -9, 0), Quaternion.identity);
        }
        for (int i = -8; i < 9; i++)//左边界
        {
            CreateItem(item[6], new Vector3(-11, i, 0), Quaternion.identity);
        }
        for (int i = -8; i < 9; i++)//右边界
        {
            CreateItem(item[6], new Vector3(11, i, 0), Quaternion.identity);
        }

        //创建其他对象(墙、障碍、河流、草)
        for (int i=0;i<20;i++) {
            CreateItem(item[1], createRandomPosition(), Quaternion.identity);
        }
        for (int i = 0; i < 20; i++)
        {
            CreateItem(item[2], createRandomPosition(), Quaternion.identity);
        }
        for (int i = 0; i < 20; i++)
        {
            CreateItem(item[4], createRandomPosition(), Quaternion.identity);
        }
        for (int i = 0; i < 20; i++)
        {
            CreateItem(item[5], createRandomPosition(), Quaternion.identity);
        }


    }

    private void CreateItem(GameObject createObject,Vector3 position,Quaternion rotation) {
        GameObject item = Instantiate(createObject, position, rotation);
        item.transform.SetParent(gameObject.transform);//将新建的对象放在MapCreation、下
        itemPostionList.Add(position);//将新建的对象位置放入列表
    }

    private Vector3 createRandomPosition() {
        while (true) {
            Vector3 position = new Vector3(Random.Range(-9, 10), Random.Range(-7, 8), 0);//不在四个边界产生游戏物体
            if (!IsUsedPosition(position)) {
                return position;
            }
        }
    }

    private bool IsUsedPosition(Vector3 position) {
        for (int i=0;i<itemPostionList.Count;i++) {
            if (position==itemPostionList[i]) {
                return true;
            }
        }
        return false;
    }
}

  • 3
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值