赞
踩
目录
写在前面的话
C#可以干什么?
开发工具: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();
- }
-
- }
- }
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度呢?乘以 就可以得到(即旋转度数为,乘以cos+sini)
然后我们推广到三维空间,同样的几何意义,一个空间线段旋转指定度数得到新的坐标,虚四元数表示为:q=q0+q1i+q2j+q3k,其中,则旋转后的四元数记为p=(q0+q1i+q2j+q3k)*(cos +sin i+sin j+sin k)即可得到了
控制子弹的移动,创建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;
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。