当前位置:   article > 正文

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

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类

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. namespace TankWar
  7. {
  8. class GameObject
  9. {
  10. public int X { get; set; }
  11. public int Y { get; set; }
  12. //上述等价于
  13. //public int y;
  14. //public int Y {
  15. // get {
  16. // return y;
  17. // }
  18. // set {
  19. // value = y;
  20. // }
  21. //}
  22. }
  23. }

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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Drawing;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. namespace TankWar
  8. {
  9. class NotMoveThing:GameObject
  10. {
  11. public Image img { get; set; }
  12. }
  13. }

 创建MoveThing类

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Drawing;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. namespace TankWar
  8. {
  9. enum Direction{
  10. UP,
  11. DOWN,
  12. LEFT,
  13. RIGHT
  14. }
  15. class MoveThing:GameObject
  16. {
  17. public Bitmap BitmapUp { get; set; }
  18. public Bitmap BitmapDown { get; set; }
  19. public Bitmap BitmapLeft { get; set; }
  20. public Bitmap BitmapRight { get; set; }
  21. public int Speed { get; set; }
  22. public Direction direction { get; set; }
  23. }
  24. }

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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. namespace TankWar
  7. {
  8. class MyTank:MoveThing
  9. {
  10. }
  11. }
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. namespace TankWar
  7. {
  8. class EnemyTank:MoveThing
  9. {
  10. }
  11. }
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. namespace TankWar
  7. {
  8. class Bullet:MoveThing
  9. {
  10. }
  11. }

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

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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Drawing;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. namespace TankWar
  8. {
  9. class NotMoveThing:GameObject
  10. {
  11. public Image img { get; set; }
  12. protected override Image GetImage()
  13. {
  14. return img;
  15. }
  16. }
  17. }
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Drawing;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. namespace TankWar
  8. {
  9. enum Direction{
  10. UP,
  11. DOWN,
  12. LEFT,
  13. RIGHT
  14. }
  15. class MoveThing:GameObject
  16. {
  17. public Bitmap BitmapUp { get; set; }
  18. public Bitmap BitmapDown { get; set; }
  19. public Bitmap BitmapLeft { get; set; }
  20. public Bitmap BitmapRight { get; set; }
  21. public int Speed { get; set; }
  22. public Direction direction { get; set; }
  23. protected override Image GetImage()
  24. {
  25. switch (direction) {
  26. case Direction.UP:
  27. return BitmapUp;
  28. case Direction.DOWN:
  29. return BitmapDown;
  30. case Direction.LEFT:
  31. return BitmapLeft;
  32. case Direction.RIGHT:
  33. return BitmapRight;
  34. default:
  35. return BitmapUp;
  36. }
  37. }
  38. }
  39. }

将黑底图片设为透明

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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using TankWar.Properties;
  7. namespace TankWar
  8. {
  9. class GamebjectManager
  10. {
  11. private static List<NotMoveThing> wallList = new List<NotMoveThing>();//保存所有墙对象
  12. public static void DrawMap() {
  13. foreach (NotMoveThing wall in wallList) {
  14. wall.DrawSelf();//绘制墙
  15. }
  16. }
  17. public static void CreateMap() {
  18. CreateWall(1, 1, 5,wallList);//创建墙对象
  19. }
  20. /**
  21. * x,y代表一个30*30的方格的位置,如第一格是0,0,我们在绘画时从1,1位置开始画
  22. * count代表要创建的强对象个数
  23. */
  24. private static void CreateWall(int x,int y,int count,List<NotMoveThing> wallList) {
  25. int xPosition = x * 30;
  26. int yPosition = y * 30;
  27. for (int i=yPosition;i<yPosition+count*30; i+=15) {
  28. NotMoveThing wall1 = new NotMoveThing(xPosition,i,Resources.wall);
  29. NotMoveThing wall2 = new NotMoveThing(xPosition+15, i, Resources.wall);
  30. wallList.Add(wall1);
  31. wallList.Add(wall2);
  32. }
  33. }
  34. }
  35. }

新增NotMoveThing构造f方法

  1. 新增NotMoveThing构造方法
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Drawing;
  5. using System.Linq;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. namespace TankWar
  9. {
  10. class NotMoveThing:GameObject
  11. {
  12. public Image img { get; set; }
  13. protected override Image GetImage()
  14. {
  15. return img;
  16. }
  17. public NotMoveThing(int x,int y,Image img) {
  18. this.X = x;
  19. this.Y = y;
  20. this.img = img;
  21. }
  22. }
  23. }

在GameFrameWork中调用

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Drawing;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. namespace TankWar
  8. {
  9. class GameFramework
  10. {
  11. public static Graphics g;
  12. public static void Start() {
  13. GamebjectManager.CreateMap();
  14. }
  15. public static void Update() {
  16. GamebjectManager.DrawMap();
  17. }
  18. }
  19. }

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

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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Drawing;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Threading;
  9. using System.Threading.Tasks;
  10. using System.Windows.Forms;
  11. namespace TankWar
  12. {
  13. public partial class Form1 : Form
  14. {
  15. private Thread thread;
  16. private static Graphics windowG;//窗口画布对象
  17. private static Bitmap tempBmp;//临时图片对象
  18. public Form1()
  19. {
  20. InitializeComponent();
  21. this.StartPosition = FormStartPosition.CenterScreen;//使窗口在屏幕居中显示
  22. windowG = this.CreateGraphics();//创建窗体画布
  23. tempBmp = new Bitmap(450,450);//创建临时图片
  24. Graphics bmpG = Graphics.FromImage(tempBmp);//根据图片对象创建临时画布对象
  25. GameFramework.g = bmpG;//赋值,以便在GameFramework中拿到此对象
  26. thread = new Thread(new ThreadStart(GameMainThread));
  27. thread.Start();
  28. }
  29. private static void GameMainThread() {
  30. GameFramework.Start();
  31. int sleepTime = 1000 / 60; //值为:1/60s
  32. while (true)
  33. {
  34. GameFramework.g.Clear(Color.Black);//将画布内容清空,并置为黑色
  35. GameFramework.Update();//画画
  36. windowG.DrawImage(tempBmp, 0, 0);
  37. Thread.Sleep(sleepTime);//每执行一次休息一段时间,保证1s执行60次
  38. }
  39. }
  40. private void Form1_FormClosed(object sender, FormClosedEventArgs e)
  41. {
  42. thread.Abort();
  43. }
  44. }
  45. }

这样图像就不再闪动了 

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

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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Drawing;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. using TankWar.Properties;
  8. namespace TankWar
  9. {
  10. class GamebjectManager
  11. {
  12. private static List<NotMoveThing> wallList = new List<NotMoveThing>();//保存所有普通墙对象
  13. private static List<NotMoveThing> steelList = new List<NotMoveThing>();//保存所有钢铁墙对象
  14. private static NotMoveThing boss;//保存boss对象
  15. public static void DrawMap() {
  16. foreach (NotMoveThing wall in wallList) {
  17. wall.DrawSelf();//绘制墙
  18. }
  19. foreach (NotMoveThing wall in steelList) {
  20. wall.DrawSelf();//绘制墙
  21. }
  22. boss.DrawSelf();
  23. }
  24. public static void CreateMap() {
  25. CreateWall(1, 1, 5,Resources.wall,wallList);//创建墙对象
  26. CreateWall(3, 1, 5, Resources.wall, wallList);//创建墙对象
  27. CreateWall(5, 1, 4, Resources.wall, wallList);//创建墙对象
  28. CreateWall(7, 1, 3, Resources.wall, wallList);//创建墙对象
  29. CreateWall(9, 1, 4, Resources.wall, wallList);//创建墙对象
  30. CreateWall(11, 1, 5, Resources.wall, wallList);//创建墙对象
  31. CreateWall(13, 1, 5, Resources.wall, wallList);//创建墙对象
  32. CreateWall(7, 5, 1, Resources.steel, steelList);//创建钢铁墙对象
  33. CreateWall(0, 7, 1, Resources.steel, steelList);//创建钢铁墙对象
  34. CreateWall(14, 7, 1, Resources.steel, steelList);//创建钢铁墙对象
  35. CreateWall(2, 7, 1, Resources.wall, wallList);
  36. CreateWall(3, 7, 1, Resources.wall, wallList);
  37. CreateWall(4, 7, 1, Resources.wall, wallList);
  38. CreateWall(6, 7, 1, Resources.wall, wallList);
  39. CreateWall(7, 6, 2, Resources.wall, wallList);
  40. CreateWall(8, 7, 1, Resources.wall, wallList);
  41. CreateWall(10, 7, 1, Resources.wall, wallList);
  42. CreateWall(11, 7, 1, Resources.wall, wallList);
  43. CreateWall(12, 7, 1, Resources.wall, wallList);
  44. CreateWall(1, 9, 5, Resources.wall, wallList);//创建墙对象
  45. CreateWall(3, 9, 5, Resources.wall, wallList);//创建墙对象
  46. CreateWall(5, 9, 3, Resources.wall, wallList);//创建墙对象
  47. CreateWall(6, 10, 1, Resources.wall, wallList);//创建墙对象
  48. CreateWall(7, 10, 1, Resources.wall, wallList);//创建墙对象
  49. CreateWall(8, 10, 1, Resources.wall, wallList);//创建墙对象
  50. CreateWall(9, 9, 3, Resources.wall, wallList);//创建墙对象
  51. CreateWall(11, 9, 5, Resources.wall, wallList);//创建墙对象
  52. CreateWall(13, 9, 5, Resources.wall, wallList);//创建墙对象
  53. CreateWall(6, 13, 2, Resources.wall, wallList);//创建墙对象
  54. CreateWall(7, 13, 1, Resources.wall, wallList);//创建墙对象
  55. CreateWall(8, 13, 2, Resources.wall, wallList);//创建墙对象
  56. CreateBoss(7, 14,Resources.Boss);
  57. }
  58. /**
  59. * x,y代表一个30*30的方格的位置,如第一格是0,0,我们在绘画时从1,1位置开始画
  60. * count代表要创建的强对象个数
  61. */
  62. private static void CreateWall(int x,int y,int count,Image img,List<NotMoveThing> wallList) {
  63. int xPosition = x * 30;
  64. int yPosition = y * 30;
  65. for (int i=yPosition;i<yPosition+count*30; i+=15) {//例如从30到180(150+30),需执行10次
  66. NotMoveThing wall1 = new NotMoveThing(xPosition,i,img);
  67. NotMoveThing wall2 = new NotMoveThing(xPosition+15, i, img);
  68. wallList.Add(wall1);
  69. wallList.Add(wall2);
  70. }
  71. }
  72. private static void CreateBoss(int x,int y,Image img) {
  73. int xPosition = x * 30;
  74. int yPosition = y * 30;
  75. boss = new NotMoveThing(xPosition, yPosition,img);
  76. }
  77. }
  78. }

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

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

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

 添加对应的构造方法

最终效果图:

4.控制坦克的移动

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

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

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

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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using System.Windows.Forms;
  7. using TankWar.Properties;
  8. namespace TankWar
  9. {
  10. class MyTank:MoveThing
  11. {
  12. public bool IsMoving { get; set; }
  13. public MyTank(int x,int y,int speed) {
  14. this.IsMoving = false;
  15. this.X = x;
  16. this.Y = y;
  17. this.Speed = speed;
  18. this.direction = Direction.UP;
  19. BitmapDown = Resources.MyTankDown;
  20. BitmapUp = Resources.MyTankUp;
  21. BitmapRight = Resources.MyTankRight;
  22. BitmapLeft = Resources.MyTankLeft;
  23. }
  24. public void KeyDown(KeyEventArgs args) {
  25. switch (args.KeyCode) {
  26. case Keys.W:
  27. direction = Direction.UP;
  28. IsMoving = true;
  29. break;
  30. case Keys.S:
  31. direction = Direction.DOWN;
  32. IsMoving = true;
  33. break;
  34. case Keys.A:
  35. direction = Direction.LEFT;
  36. IsMoving = true;
  37. break;
  38. case Keys.D:
  39. direction = Direction.RIGHT;
  40. IsMoving = true;
  41. break;
  42. }
  43. }
  44. public void KeyUp(KeyEventArgs args) {
  45. switch (args.KeyCode)
  46. {
  47. case Keys.W:
  48. IsMoving = false;
  49. break;
  50. case Keys.S:
  51. IsMoving = false;
  52. break;
  53. case Keys.A:
  54. IsMoving = false;
  55. break;
  56. case Keys.D:
  57. IsMoving = false;
  58. break;
  59. }
  60. }
  61. private void Move() {
  62. if (IsMoving==false) {
  63. return;
  64. }
  65. switch (direction) {
  66. case Direction.UP:
  67. Y -= Speed;
  68. break;
  69. case Direction.DOWN:
  70. Y += Speed;
  71. break;
  72. case Direction.LEFT:
  73. X -= Speed;
  74. break;
  75. case Direction.RIGHT:
  76. X += Speed;
  77. break;
  78. }
  79. }
  80. public override void Update()
  81. {
  82. Move();
  83. base.Update();
  84. }
  85. }
  86. }

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

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

 处理资源冲突异常:

在MoveThing中重写此方法 

5.添加敌人坦克

添加EnemyTank生成方法

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Drawing;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. using System.Windows.Forms;
  8. using TankWar.Properties;
  9. namespace TankWar
  10. {
  11. class GamebjectManager
  12. {
  13. ...
  14. private static List<EnemyTank> tankList = new List<EnemyTank>();//保存敌人坦克对象
  15. private static int enemyBornSpeed = 60;//敌人坦克的生成速度
  16. private static int enemyBornCount = 60;//敌人坦克的生成数量
  17. private static Point[] points=new Point[3];
  18. public static void Start() {
  19. points[0].X = 0;
  20. points[0].Y = 0;
  21. points[1].X = 7*30;
  22. points[1].Y = 0;
  23. points[2].X = 14*30;
  24. points[2].Y = 0;
  25. }
  26. public static void Update() {
  27. ...
  28. foreach (EnemyTank tank in tankList) {
  29. tank.Update();
  30. }
  31. ...
  32. EnemyBorn();
  33. }
  34. public static void EnemyBorn() {
  35. enemyBornCount++;
  36. if (enemyBornCount<enemyBornSpeed) {
  37. return;
  38. }
  39. Random rd = new Random();
  40. int index=rd.Next(0, 3);//生成0-3之间的随机整数,不包含3
  41. Point positon = points[index];
  42. int enemyType = rd.Next(1, 5);
  43. switch (enemyType) {
  44. case 1:
  45. CreateEnemyTank1(positon.X,positon.Y);
  46. break;
  47. case 2:
  48. CreateEnemyTank2(positon.X, positon.Y);
  49. break;
  50. case 3:
  51. CreateEnemyTank3(positon.X, positon.Y);
  52. break;
  53. case 4:
  54. CreateEnemyTank4(positon.X, positon.Y);
  55. break;
  56. }
  57. enemyBornCount = 0;
  58. }
  59. private static void CreateEnemyTank1(int x,int y) {
  60. EnemyTank tank = new EnemyTank(x, y, 2, Resources.GrayDown, Resources.GrayUp, Resources.GrayLeft, Resources.GrayRight);
  61. tankList.Add(tank);
  62. }
  63. private static void CreateEnemyTank2(int x, int y)
  64. {
  65. EnemyTank tank = new EnemyTank(x, y, 2, Resources.GreenDown, Resources.GreenUp, Resources.GreenLeft, Resources.GreenRight);
  66. tankList.Add(tank);
  67. }
  68. private static void CreateEnemyTank3(int x, int y)
  69. {
  70. EnemyTank tank = new EnemyTank(x, y, 4, Resources.QuickDown, Resources.QuickUp, Resources.QuickLeft, Resources.QuickRight);
  71. tankList.Add(tank);
  72. }
  73. private static void CreateEnemyTank4(int x, int y)
  74. {
  75. EnemyTank tank = new EnemyTank(x, y, 1, Resources.SlowDown, Resources.SlowUp, Resources.SlowLeft, Resources.SlowRight);
  76. tankList.Add(tank);
  77. }
  78. }
  79. }

创建EnemyTank构造函数及移动

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Drawing;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. namespace TankWar
  8. {
  9. class EnemyTank:MoveThing
  10. {
  11. private Random r = new Random();
  12. public EnemyTank(int x, int y, int speed,Bitmap bmpDown,Bitmap bmpUp,Bitmap bmpLeft,Bitmap bmpRight)
  13. {
  14. //this.IsMoving = true;
  15. this.X = x;
  16. this.Y = y;
  17. this.Speed = speed;
  18. BitmapDown = bmpDown;
  19. BitmapUp = bmpUp;
  20. BitmapRight = bmpRight;
  21. BitmapLeft = bmpLeft;
  22. this.Direction = Direction.DOWN;
  23. }
  24. public override void Update()
  25. {
  26. MoveCheck();//移动前检查
  27. Move();
  28. base.Update();
  29. }
  30. private void ChangeDirection() {
  31. while (true) {
  32. Direction dir = (Direction)r.Next(0, 4);
  33. if (Direction == dir)
  34. {
  35. continue;
  36. } else {
  37. Direction = dir;
  38. break;
  39. }
  40. }
  41. MoveCheck();
  42. }
  43. private void Move()
  44. {
  45. switch (Direction)
  46. {
  47. case Direction.UP:
  48. Y -= Speed;
  49. break;
  50. case Direction.DOWN:
  51. Y += Speed;
  52. break;
  53. case Direction.LEFT:
  54. X -= Speed;
  55. break;
  56. case Direction.RIGHT:
  57. X += Speed;
  58. break;
  59. }
  60. }
  61. private void MoveCheck()
  62. {
  63. #region 检查是否超过窗体边界
  64. if (Direction == Direction.UP)
  65. {
  66. if (Y - Speed < 0)
  67. {
  68. ChangeDirection();
  69. return;
  70. }
  71. }
  72. else if (Direction == Direction.DOWN)
  73. {
  74. if (Y + Speed + Height > 450)
  75. {
  76. ChangeDirection();
  77. return;
  78. }
  79. }
  80. else if (Direction == Direction.LEFT)
  81. {
  82. if (X - Speed < 0)
  83. {
  84. ChangeDirection();
  85. return;
  86. }
  87. }
  88. else if (Direction == Direction.RIGHT)
  89. {
  90. if (X + Speed + Width > 450)
  91. {
  92. ChangeDirection();
  93. return;
  94. }
  95. }
  96. #endregion
  97. Rectangle rect = GetRectangle();
  98. switch (Direction)
  99. {
  100. case Direction.UP:
  101. rect.Y -= Speed;
  102. break;
  103. case Direction.DOWN:
  104. rect.Y += Speed;
  105. break;
  106. case Direction.LEFT:
  107. rect.X -= Speed;
  108. break;
  109. case Direction.RIGHT:
  110. rect.X += Speed;
  111. break;
  112. }
  113. if (GamebjectManager.isCollidedWall(rect) != null)
  114. {
  115. ChangeDirection();
  116. return;
  117. }
  118. if (GamebjectManager.isCollidedSteel(rect) != null)
  119. {
  120. ChangeDirection();
  121. return;
  122. }
  123. if (GamebjectManager.isCollidedBoss(rect))
  124. {
  125. ChangeDirection();
  126. return;
  127. }
  128. }
  129. }
  130. }

6.发射子弹

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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Drawing;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. using TankWar.Properties;
  8. namespace TankWar
  9. {
  10. enum Tag {
  11. MyTank,
  12. EnemyTank
  13. }
  14. class Bullet:MoveThing
  15. {
  16. public Tag Tag { get; set; } //用于判断是自己的坦克发射的子弹还是敌人的坦克发射的子弹
  17. public bool isDestroy { get; set; }
  18. public Bullet(int x, int y, int speed,Direction dir,Tag tag)
  19. {
  20. isDestroy = false;
  21. this.X = x;
  22. this.Y = y;
  23. this.Speed = speed;
  24. BitmapDown = Resources.BulletDown;
  25. BitmapUp = Resources.BulletUp;
  26. BitmapRight = Resources.BulletRight;
  27. BitmapLeft = Resources.BulletLeft;
  28. this.Direction = dir;
  29. this.Tag = tag;
  30. this.X -= Width / 2;
  31. this.Y -= Height / 2;
  32. }
  33. public override void DrawSelf()
  34. {
  35. base.DrawSelf();
  36. }
  37. public override void Update()
  38. {
  39. MoveCheck();//移动前检查
  40. Move();
  41. base.Update();
  42. }
  43. private void Move()
  44. {
  45. switch (Direction)
  46. {
  47. case Direction.UP:
  48. Y -= Speed;
  49. break;
  50. case Direction.DOWN:
  51. Y += Speed;
  52. break;
  53. case Direction.LEFT:
  54. X -= Speed;
  55. break;
  56. case Direction.RIGHT:
  57. X += Speed;
  58. break;
  59. }
  60. }
  61. private void MoveCheck()
  62. {
  63. #region 检查是否超过窗体边界
  64. if (Direction == Direction.UP)
  65. {
  66. if (Y +Height/2+3< 0)//子弹图片自身的高度/2和子弹本身的高度的一半(大约为3)
  67. {
  68. isDestroy=true;
  69. return;
  70. }
  71. }
  72. else if (Direction == Direction.DOWN)
  73. {
  74. if (Y + Height/2 -3 > 450)
  75. {
  76. isDestroy = true;
  77. return;
  78. }
  79. }
  80. else if (Direction == Direction.LEFT)
  81. {
  82. if (X+Width/2-3 < 0)
  83. {
  84. isDestroy = true;
  85. return;
  86. }
  87. }
  88. else if (Direction == Direction.RIGHT)
  89. {
  90. if (X+Width/2+3 > 450)
  91. {
  92. isDestroy = true;
  93. return;
  94. }
  95. }
  96. #endregion
  97. ...
  98. }
  99. }
  100. }

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

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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Drawing;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. using TankWar.Properties;
  8. namespace TankWar
  9. {
  10. enum Tag {
  11. MyTank,
  12. EnemyTank
  13. }
  14. class Bullet:MoveThing
  15. {
  16. ...
  17. private void MoveCheck()
  18. {
  19. ...
  20. Rectangle rect = GetRectangle();
  21. rect.X = X + Width / 2 - 3;//取到子弹实际位置的左上角横坐标
  22. rect.Y = Y + Height / 2 - 3;
  23. rect.Height = 3;
  24. rect.Width = 3;
  25. NotMoveThing wall = null;
  26. if ((wall=GamebjectManager.isCollidedWall(rect)) != null)
  27. {
  28. isDestroy = true;
  29. GamebjectManager.DestoryWall(wall);
  30. return;
  31. }
  32. if (GamebjectManager.isCollidedSteel(rect) != null)
  33. {
  34. isDestroy = true;
  35. return;
  36. }
  37. if (GamebjectManager.isCollidedBoss(rect))
  38. {
  39. //ChangeDirection();
  40. return;
  41. }
  42. if (Tag==Tag.MyTank) {
  43. EnemyTank tank = null;
  44. if ((tank = GamebjectManager.isCollidedEnemyTank(rect))!=null)
  45. {
  46. isDestroy = true;
  47. GamebjectManager.DestoryTank(tank);
  48. return;
  49. }
  50. }
  51. }
  52. }
  53. }

效果 

7.添加爆炸效果

创建爆炸效果类

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Drawing;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. using TankWar.Properties;
  8. namespace TankWar
  9. {
  10. class Explosion : GameObject
  11. {
  12. public bool IsNeedDestory { get; set;}
  13. private int playSpeed = 1;
  14. private int playCount = 0;//0/2=0 1/2=0 2/2=1 3/2=1..每张图片停留2帧
  15. private int index = 0;
  16. private Bitmap[] bmpArray = new Bitmap[] {
  17. Resources.EXP1,
  18. Resources.EXP2,
  19. Resources.EXP3,
  20. Resources.EXP4,
  21. Resources.EXP5
  22. };
  23. public Explosion(int x,int y) {
  24. foreach (Bitmap bmp in bmpArray) {
  25. bmp.MakeTransparent(Color.Black);
  26. }
  27. this.X = x - bmpArray[0].Width / 2;//得到左上角坐标
  28. this.Y = y - bmpArray[0].Height / 2;
  29. IsNeedDestory = false;
  30. }
  31. protected override Image GetImage()
  32. {
  33. if (index>4) {
  34. return bmpArray[4];
  35. }
  36. return bmpArray[index];
  37. }
  38. public override void Update()
  39. {
  40. playCount++;
  41. index = (playCount - 1) / playSpeed;//获取播放图片的索引
  42. if (index>4) {
  43. IsNeedDestory = true;
  44. }
  45. base.Update();
  46. }
  47. }
  48. }

添加生成爆炸效果的代码 

8.优化敌人坦克

敌人坦克可发射子弹 

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Drawing;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. namespace TankWar
  8. {
  9. class EnemyTank:MoveThing
  10. {
  11. public int AttackSpeed { get; set; }
  12. private int attackCount = 0;
  13. ...
  14. public EnemyTank(int x, int y, int speed,Bitmap bmpDown,Bitmap bmpUp,Bitmap bmpLeft,Bitmap bmpRight)
  15. {
  16. ...
  17. AttackSpeed = 60;
  18. }
  19. public override void Update()
  20. {
  21. ...
  22. AttackCheck();//是否需要攻击
  23. ...
  24. }
  25. ...
  26. private void AttackCheck() {
  27. attackCount++;
  28. if (attackCount < AttackSpeed) return;
  29. Attack();
  30. attackCount = 0;
  31. }
  32. private void Attack()
  33. {
  34. int x = this.X;
  35. int y = this.Y;
  36. switch (Direction)
  37. {
  38. case Direction.UP:
  39. x = x + Width / 2;
  40. break;
  41. case Direction.DOWN:
  42. x = x + Width / 2;
  43. y += Height;
  44. break;
  45. case Direction.LEFT:
  46. y = y + Height / 2;
  47. break;
  48. case Direction.RIGHT:
  49. x += Width;
  50. y = y + Height / 2;
  51. break;
  52. }
  53. GamebjectManager.CreateBullet(x, y, Tag.EnemyTank, Direction);
  54. }
  55. }
  56. }

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

 

 

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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Drawing;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. using System.Windows.Forms;
  8. using TankWar.Properties;
  9. namespace TankWar
  10. {
  11. class MyTank:MoveThing
  12. {
  13. public int HP { get; set; }//血量
  14. ...
  15. private int originalX, originalY;
  16. public MyTank(int x,int y,int speed) {
  17. ...
  18. originalX = x;
  19. originalY = y;
  20. ...
  21. HP = 4;
  22. }
  23. ...
  24. public void TakeDamage() {
  25. HP--;
  26. if (HP<=0) {
  27. X = originalX;
  28. Y = originalY;
  29. HP = 4;
  30. }
  31. }
  32. }
  33. }

 子弹攻击boss时游戏结束

9.添加音效

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Media;
  5. using System.Text;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. using TankWar.Properties;
  9. namespace TankWar
  10. {
  11. class SoundManager
  12. {
  13. private static SoundPlayer startPlayer = new SoundPlayer();
  14. private static SoundPlayer addPlayer = new SoundPlayer();
  15. private static SoundPlayer blastPlayer = new SoundPlayer();
  16. private static SoundPlayer firePlayer = new SoundPlayer();
  17. private static SoundPlayer hitPlayer = new SoundPlayer();
  18. public static void InitSound() {
  19. startPlayer.Stream = Resources.start;
  20. addPlayer.Stream = Resources.add;
  21. blastPlayer.Stream = Resources.blast;
  22. firePlayer.Stream = Resources.fire;
  23. hitPlayer.Stream = Resources.hit;
  24. }
  25. public static void PlayStart() {
  26. startPlayer.Play();
  27. }
  28. public static void PlayAdd()
  29. {
  30. addPlayer.Play();
  31. }
  32. public static void PlayBlast() {
  33. blastPlayer.Play();
  34. }
  35. public static void PlayFire()
  36. {
  37. firePlayer.Play();
  38. }
  39. public static void PlayHit()
  40. {
  41. hitPlayer.Play();
  42. }
  43. }
  44. }

使用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对象关联,然后编辑内容

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class Player : MonoBehaviour
  5. {
  6. public float moveSpeed=3;
  7. // Start is called before the first frame update
  8. void Start()
  9. {
  10. }
  11. // Update is called once per frame
  12. void Update()
  13. {
  14. float h = Input.GetAxisRaw("Horizontal");
  15. transform.Translate(Vector3.right * h * moveSpeed * Time.deltaTime,Space.World);//乘以delTatime表示按每一秒移动,以世界坐标轴移动
  16. float v = Input.GetAxisRaw("Vertical");
  17. transform.Translate(Vector3.up * v * moveSpeed * Time.deltaTime, Space.World);
  18. }
  19. }

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

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

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class Player : MonoBehaviour
  5. {
  6. public float moveSpeed=3;
  7. private SpriteRenderer sr;
  8. public Sprite[] tankSprite;
  9. private void Awake()
  10. {
  11. sr = GetComponent<SpriteRenderer>();//得到图片渲染组件
  12. }
  13. // Start is called before the first frame update
  14. void Start()
  15. {
  16. }
  17. // Update is called once per frame
  18. void Update()
  19. {
  20. float h = Input.GetAxisRaw("Horizontal");
  21. transform.Translate(Vector3.right * h * moveSpeed * Time.deltaTime,Space.World);//乘以delTatime表示按每一秒移动,以世界坐标轴移动
  22. if (h < 0) {
  23. sr.sprite = tankSprite[3];//左
  24. } else if (h>0) {
  25. sr.sprite = tankSprite[1];//右
  26. }
  27. float v = Input.GetAxisRaw("Vertical");
  28. transform.Translate(Vector3.up * v * moveSpeed * Time.deltaTime, Space.World);
  29. if (v < 0)
  30. {
  31. sr.sprite = tankSprite[2];
  32. }
  33. else if (v > 0)
  34. {
  35. sr.sprite = tankSprite[0];
  36. }
  37. }
  38. }

3.为Player添加碰撞效果

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

添加刚体组件

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

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

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

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

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

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

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class Player : MonoBehaviour
  5. {
  6. public float moveSpeed=3;
  7. private SpriteRenderer sr;
  8. public Sprite[] tankSprite;
  9. private void Awake()
  10. {
  11. sr = GetComponent<SpriteRenderer>();//得到图片渲染组件
  12. }
  13. // Start is called before the first frame update
  14. void Start()
  15. {
  16. }
  17. // Update is called once per frame
  18. void Update()
  19. {
  20. }
  21. private void FixedUpdate()
  22. {
  23. float h = Input.GetAxisRaw("Horizontal");
  24. transform.Translate(Vector3.right * h * moveSpeed * Time.deltaTime, Space.World);//乘以delTatime表示按每一秒移动,以世界坐标轴移动
  25. if (h < 0)
  26. {
  27. sr.sprite = tankSprite[3];//左
  28. }
  29. else if (h > 0)
  30. {
  31. sr.sprite = tankSprite[1];//右
  32. }
  33. float v = Input.GetAxisRaw("Vertical");
  34. transform.Translate(Vector3.up * v * moveSpeed * Time.deltaTime, Space.World);
  35. if (v < 0)
  36. {
  37. sr.sprite = tankSprite[2];
  38. }
  39. else if (v > 0)
  40. {
  41. sr.sprite = tankSprite[0];
  42. }
  43. }
  44. }

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

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class Player : MonoBehaviour
  5. {
  6. ...
  7. private void FixedUpdate()
  8. {
  9. Move();
  10. }
  11. private void Move()
  12. {
  13. float h = Input.GetAxisRaw("Horizontal");
  14. transform.Translate(Vector3.right * h * moveSpeed * Time.fixedDeltaTime, Space.World);//乘以delTatime表示按每一秒移动,以世界坐标轴移动
  15. if (h < 0)
  16. {
  17. sr.sprite = tankSprite[3];//左
  18. }
  19. else if (h > 0)
  20. {
  21. sr.sprite = tankSprite[1];//右
  22. }
  23. if (h != 0)
  24. {
  25. return;//处理两键同时按下导致坦克斜着走问题
  26. }
  27. float v = Input.GetAxisRaw("Vertical");
  28. transform.Translate(Vector3.up * v * moveSpeed * Time.fixedDeltaTime, Space.World);
  29. if (v < 0)
  30. {
  31. sr.sprite = tankSprite[2];
  32. }
  33. else if (v > 0)
  34. {
  35. sr.sprite = tankSprite[0];
  36. }
  37. }
  38. }

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

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

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

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class Player : MonoBehaviour
  5. {
  6. ...
  7. public GameObject bulletPrefab;//子弹预制体
  8. private void Awake()
  9. {
  10. sr = GetComponent<SpriteRenderer>();//得到图片渲染组件
  11. }
  12. // Start is called before the first frame update
  13. void Start()
  14. {
  15. }
  16. // Update is called once per frame
  17. void Update()
  18. {
  19. Attack(); //发射子弹,一定要放在Update中,如果放在FixedUpdate会偶尔发出不出子弹
  20. }
  21. private void FixedUpdate()
  22. {
  23. Move(); //坦克移动
  24. }
  25. private void Attack() {
  26. if (Input.GetKeyDown(KeyCode.Space)) {
  27. Instantiate(bulletPrefab, transform.position, transform.rotation);
  28. }
  29. }
  30. ...
  31. }

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

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class Player : MonoBehaviour
  5. {
  6. ...
  7. private Vector3 bulletAngle;//子弹的发射角度
  8. ...
  9. // Update is called once per frame
  10. void Update()
  11. {
  12. Attack(); //发射子弹
  13. }
  14. private void FixedUpdate()
  15. {
  16. Move(); //坦克移动
  17. }
  18. private void Attack() {
  19. if (Input.GetKeyDown(KeyCode.Space)) {
  20. Instantiate(bulletPrefab, transform.position, Quaternion.Euler(transform.eulerAngles+bulletAngle));
  21. }
  22. }
  23. private void Move()
  24. {
  25. float v = Input.GetAxisRaw("Vertical");
  26. transform.Translate(Vector3.up * v * moveSpeed * Time.fixedDeltaTime, Space.World);
  27. if (v < 0)
  28. {
  29. sr.sprite = tankSprite[2];
  30. bulletAngle = new Vector3(0, 0, -180);
  31. }
  32. else if (v > 0)
  33. {
  34. sr.sprite = tankSprite[0];
  35. bulletAngle = new Vector3(0, 0, 0);
  36. }
  37. if (v != 0)
  38. {
  39. return;//处理两键同时按下导致坦克斜着走问题
  40. }
  41. float h = Input.GetAxisRaw("Horizontal");
  42. transform.Translate(Vector3.right * h * moveSpeed * Time.fixedDeltaTime, Space.World);//乘以delTatime表示按每一秒移动,以世界坐标轴移动
  43. if (h < 0)
  44. {
  45. sr.sprite = tankSprite[3];//左
  46. bulletAngle = new Vector3(0, 0, 90);//这里记住坐标是反着的,
  47. }
  48. else if (h > 0)
  49. {
  50. sr.sprite = tankSprite[1];//右
  51. bulletAngle = new Vector3(0, 0, -90);
  52. }
  53. }
  54. }

欧拉角:欧拉角包括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预制体绑定

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class Bullet : MonoBehaviour
  5. {
  6. public float moveSpeed = 10;
  7. // Start is called before the first frame update
  8. void Start()
  9. {
  10. }
  11. // Update is called once per frame
  12. void Update()
  13. {
  14. transform.Translate(transform.up * moveSpeed * Time.deltaTime, Space.World);//世界坐标轴
  15. }
  16. }

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

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class Player : MonoBehaviour
  5. {
  6. ...
  7. private float timeVal;//发射子弹CD
  8. // Update is called once per frame
  9. void Update()
  10. {
  11. Attack();
  12. if (timeVal >= 0.4){
  13. Attack(); //发射子弹
  14. }else {
  15. timeVal += Time.deltaTime;
  16. }
  17. }
  18. private void FixedUpdate()
  19. {
  20. Move(); //坦克移动
  21. }
  22. private void Attack() {
  23. if (Input.GetKeyDown(KeyCode.Space)) {
  24. Instantiate(bulletPrefab, transform.position, Quaternion.Euler(transform.eulerAngles+bulletAngle));
  25. timeVal = 0;
  26. }
  27. }
  28. ...
  29. }

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

 

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

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

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

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class Player : MonoBehaviour
  5. {
  6. ...
  7. private float defendTimeVal=3;//保护时间
  8. private bool isDefended=true;//是否保护
  9. ...
  10. public GameObject defendEffectPrefab;//护盾特效预制体
  11. ...
  12. // Update is called once per frame
  13. void Update()
  14. {
  15. //是否处于无敌状态
  16. if (isDefended) {
  17. defendEffectPrefab.SetActive(true);
  18. defendTimeVal -= Time.deltaTime;
  19. if (defendTimeVal<=0) {
  20. isDefended = false;
  21. defendEffectPrefab.SetActive(false);
  22. }
  23. }
  24. if (timeVal >= 0.4)
  25. {
  26. Attack(); //发射子弹
  27. }
  28. else
  29. {
  30. timeVal += Time.deltaTime;
  31. }
  32. }
  33. }

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

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class Heart : MonoBehaviour
  5. {
  6. private SpriteRenderer sr;
  7. public Sprite BrokenSprite;
  8. // Start is called before the first frame update
  9. void Start()
  10. {
  11. sr= GetComponent<SpriteRenderer>();
  12. }
  13. // Update is called once per frame
  14. void Update()
  15. {
  16. }
  17. public void Die() {
  18. sr.sprite = BrokenSprite;
  19. }
  20. }

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

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class Bullet : MonoBehaviour
  5. {
  6. public float moveSpeed = 10;
  7. public bool isPlayerBullet;//是否为玩家子弹
  8. // Start is called before the first frame update
  9. void Start()
  10. {
  11. }
  12. // Update is called once per frame
  13. void Update()
  14. {
  15. transform.Translate(transform.up * moveSpeed * Time.deltaTime, Space.World);//世界坐标轴
  16. }
  17. private void OnTriggerEnter2D(Collider2D collision)
  18. {
  19. switch (collision.tag) {
  20. case "Tank":
  21. if (!isPlayerBullet) {
  22. collision.SendMessage("Die");//执行碰撞到的物体的Die方法
  23. }
  24. break;
  25. case "Heart":
  26. collision.SendMessage("Die");//执行碰撞到的物体的Die方法
  27. Destroy(gameObject);//销毁子弹自身
  28. break;
  29. case "Enemy":
  30. break;
  31. case "Wall":
  32. Destroy(collision.gameObject);//销毁碰撞到的物体
  33. Destroy(gameObject);//销毁子弹自身
  34. break;
  35. case "Barrier":
  36. Destroy(gameObject);//销毁子弹自身
  37. break;
  38. default:
  39. break;
  40. }
  41. }
  42. }

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

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

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class Explosion : MonoBehaviour
  5. {
  6. // Start is called before the first frame update
  7. void Start()
  8. {
  9. Destroy(gameObject, 0.2f);
  10. }
  11. // Update is called once per frame
  12. void Update()
  13. {
  14. }
  15. }

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

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class Born : MonoBehaviour
  5. {
  6. public GameObject playerPrefab;
  7. // Start is called before the first frame update
  8. void Start()
  9. {
  10. Invoke("BornTank", 1f);//延时调用
  11. Destroy(gameObject, 1f);//延时销毁
  12. }
  13. // Update is called once per frame
  14. void Update()
  15. {
  16. }
  17. private void BornTank() {
  18. Instantiate(playerPrefab, transform.position, Quaternion.identity);
  19. }
  20. }

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

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class Enemy : MonoBehaviour
  5. {
  6. public float moveSpeed = 3;
  7. private Vector3 bulletAngle;//子弹的发射角度
  8. private float v, h;
  9. private float timeVal;//发射子弹CD
  10. private float timeValChangeDirection=4;//改变方向的时间
  11. private SpriteRenderer sr;
  12. public Sprite[] tankSprite;
  13. public GameObject bulletPrefab;//子弹预制体
  14. public GameObject explosionPrefab;//爆炸效果预制体
  15. private void Awake()
  16. {
  17. sr = GetComponent<SpriteRenderer>();//得到图片渲染组件
  18. }
  19. // Start is called before the first frame update
  20. void Start()
  21. {
  22. }
  23. // Update is called once per frame
  24. void Update()
  25. {
  26. if (timeVal >= 3)
  27. {
  28. Attack(); //发射子弹
  29. }
  30. else
  31. {
  32. timeVal += Time.deltaTime;
  33. }
  34. }
  35. private void FixedUpdate()
  36. {
  37. Move(); //坦克移动
  38. }
  39. private void Attack()
  40. {
  41. Instantiate(bulletPrefab, transform.position, Quaternion.Euler(transform.eulerAngles + bulletAngle));
  42. timeVal = 0;
  43. }
  44. private void Move()
  45. {
  46. if (timeValChangeDirection >= 4)
  47. {
  48. int num = Random.Range(0, 8);
  49. if (num > 5)
  50. {
  51. v = -1;//向下走
  52. h = 0;
  53. }
  54. else if (num == 0)
  55. {
  56. v = 1;//向后走
  57. h = 0;
  58. }
  59. else if (num > 0 && num <= 2)
  60. {
  61. h = -1;//向左走
  62. v = 0;
  63. }
  64. else if (num > 2 && num <= 4)
  65. {
  66. h = 1;//向右走
  67. v = 0;
  68. }
  69. timeValChangeDirection = 0;
  70. } else {
  71. timeValChangeDirection += Time.fixedDeltaTime;
  72. }
  73. transform.Translate(Vector3.right * h * moveSpeed * Time.fixedDeltaTime, Space.World);//乘以delTatime表示按每一秒移动,以世界坐标轴移动
  74. if (h < 0)
  75. {
  76. sr.sprite = tankSprite[3];//左
  77. bulletAngle = new Vector3(0, 0, 90);//这里记住坐标是反着的,
  78. }
  79. else if (h > 0)
  80. {
  81. sr.sprite = tankSprite[1];//右
  82. bulletAngle = new Vector3(0, 0, -90);
  83. }
  84. if (h != 0)
  85. {
  86. return;//处理两键同时按下导致坦克斜着走问题
  87. }
  88. transform.Translate(Vector3.up * v * moveSpeed * Time.fixedDeltaTime, Space.World);
  89. if (v < 0)
  90. {
  91. sr.sprite = tankSprite[2];
  92. bulletAngle = new Vector3(0, 0, -180);
  93. }
  94. else if (v > 0)
  95. {
  96. sr.sprite = tankSprite[0];
  97. bulletAngle = new Vector3(0, 0, 0);
  98. }
  99. }
  100. private void Die()
  101. {
  102. Instantiate(explosionPrefab, transform.position, transform.rotation);//产生爆炸效果
  103. Destroy(gameObject);
  104. }
  105. }
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class Bullet : MonoBehaviour
  5. {
  6. public float moveSpeed = 10;
  7. public bool isPlayerBullet;//是否为玩家子弹
  8. // Start is called before the first frame update
  9. void Start()
  10. {
  11. }
  12. // Update is called once per frame
  13. void Update()
  14. {
  15. transform.Translate(transform.up * moveSpeed * Time.deltaTime, Space.World);//世界坐标轴
  16. }
  17. private void OnTriggerEnter2D(Collider2D collision)
  18. {
  19. switch (collision.tag) {
  20. case "Tank":
  21. if (!isPlayerBullet) {
  22. collision.SendMessage("Die");//执行碰撞到的物体的Die方法
  23. Destroy(gameObject);//销毁子弹自身
  24. }
  25. break;
  26. case "Heart":
  27. collision.SendMessage("Die");//执行碰撞到的物体的Die方法
  28. Destroy(gameObject);//销毁子弹自身
  29. break;
  30. case "Enemy":
  31. if (isPlayerBullet) {
  32. collision.SendMessage("Die");
  33. Destroy(gameObject);//销毁子弹自身
  34. }
  35. break;
  36. case "Wall":
  37. Destroy(collision.gameObject);//销毁碰撞到的物体
  38. Destroy(gameObject);//销毁子弹自身
  39. break;
  40. case "Barrier":
  41. Destroy(gameObject);//销毁子弹自身
  42. break;
  43. default:
  44. break;
  45. }
  46. }
  47. }

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

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class Born : MonoBehaviour
  5. {
  6. public GameObject playerPrefab;
  7. public GameObject[] enemyPrefabList;
  8. public bool createPlayer;
  9. // Start is called before the first frame update
  10. void Start()
  11. {
  12. Invoke("BornTank", 1f);//延时调用
  13. Destroy(gameObject, 1f);//延时销毁
  14. }
  15. // Update is called once per frame
  16. void Update()
  17. {
  18. }
  19. private void BornTank() {
  20. if (createPlayer) {
  21. Instantiate(playerPrefab, transform.position, Quaternion.identity);
  22. }else {
  23. int num = Random.Range(0, 2);
  24. Instantiate(enemyPrefabList[num], transform.position, Quaternion.identity);
  25. }
  26. }
  27. }

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

 创建BOSS和BOSS周围的墙

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class MapCreation : MonoBehaviour
  5. {
  6. public GameObject[] item;//保存Boss、墙、障碍、出生效果、河流、草、空气墙
  7. private void Awake()
  8. {
  9. //在界面下边界中间位置,创建Boss
  10. CreateItem(item[0], new Vector3(0, -8, 0), Quaternion.identity);
  11. //Boss周围的墙
  12. CreateItem(item[1], new Vector3(-1,-8,0), Quaternion.identity);
  13. CreateItem(item[1], new Vector3(1, -8, 0), Quaternion.identity);
  14. for (int i=-1;i<2;i++) {
  15. CreateItem(item[1], new Vector3(i, -7, 0), Quaternion.identity);
  16. }
  17. }
  18. private void CreateItem(GameObject createObject,Vector3 position,Quaternion rotation) {
  19. GameObject item = Instantiate(createObject, position, rotation);
  20. item.transform.SetParent(gameObject.transform);//将新建的对象放在MapCreation下使目录简洁
  21. }
  22. }

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

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class MapCreation : MonoBehaviour
  5. {
  6. public GameObject[] item;//保存Boss、墙、障碍、出生效果、河流、草、空气墙
  7. private List<Vector3> itemPostionList=new List<Vector3>();//保存每个对象的位置,用于判断随机生成的位置是否已有对象
  8. private void Awake()
  9. {
  10. //在界面下边界中间位置,创建Boss
  11. CreateItem(item[0], new Vector3(0, -8, 0), Quaternion.identity);
  12. //Boss周围的墙
  13. CreateItem(item[1], new Vector3(-1,-8,0), Quaternion.identity);
  14. CreateItem(item[1], new Vector3(1, -8, 0), Quaternion.identity);
  15. for (int i=-1;i<2;i++) {
  16. CreateItem(item[1], new Vector3(i, -7, 0), Quaternion.identity);
  17. }
  18. //创建外围空气墙
  19. for (int i=-11;i<12;i++) {//上边界
  20. CreateItem(item[6], new Vector3(i, 9, 0), Quaternion.identity);
  21. }
  22. for (int i = -11; i < 12; i++)//下边界
  23. {
  24. CreateItem(item[6], new Vector3(i, -9, 0), Quaternion.identity);
  25. }
  26. for (int i = -8; i < 9; i++)//左边界
  27. {
  28. CreateItem(item[6], new Vector3(-11, i, 0), Quaternion.identity);
  29. }
  30. for (int i = -8; i < 9; i++)//右边界
  31. {
  32. CreateItem(item[6], new Vector3(11, i, 0), Quaternion.identity);
  33. }
  34. //创建其他对象(墙、障碍、河流、草)
  35. for (int i=0;i<20;i++) {
  36. CreateItem(item[1], createRandomPosition(), Quaternion.identity);
  37. }
  38. for (int i = 0; i < 20; i++)
  39. {
  40. CreateItem(item[2], createRandomPosition(), Quaternion.identity);
  41. }
  42. for (int i = 0; i < 20; i++)
  43. {
  44. CreateItem(item[4], createRandomPosition(), Quaternion.identity);
  45. }
  46. for (int i = 0; i < 20; i++)
  47. {
  48. CreateItem(item[5], createRandomPosition(), Quaternion.identity);
  49. }
  50. }
  51. private void CreateItem(GameObject createObject,Vector3 position,Quaternion rotation) {
  52. GameObject item = Instantiate(createObject, position, rotation);
  53. item.transform.SetParent(gameObject.transform);//将新建的对象放在MapCreation、下
  54. itemPostionList.Add(position);//将新建的对象位置放入列表
  55. }
  56. private Vector3 createRandomPosition() {
  57. while (true) {
  58. Vector3 position = new Vector3(Random.Range(-9, 10), Random.Range(-7, 8), 0);//不在四个边界产生游戏物体
  59. if (!IsUsedPosition(position)) {
  60. return position;
  61. }
  62. }
  63. }
  64. private bool IsUsedPosition(Vector3 position) {
  65. for (int i=0;i<itemPostionList.Count;i++) {
  66. if (position==itemPostionList[i]) {
  67. return true;
  68. }
  69. }
  70. return false;
  71. }
  72. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/116549
推荐阅读
相关标签
  

闽ICP备14008679号