目录

使用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/60swhile (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之间的随机整数,不包含3Point 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;}}#endregionRectangle 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 updatevoid Start(){}// Update is called once per framevoid 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 updatevoid Start(){}// Update is called once per framevoid 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 updatevoid Start(){}// Update is called once per framevoid 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 updatevoid Start(){}// Update is called once per framevoid 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 framevoid 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 updatevoid Start(){}// Update is called once per framevoid 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 framevoid 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 framevoid 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 updatevoid Start(){sr= GetComponent<SpriteRenderer>();}// Update is called once per framevoid 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 updatevoid Start(){}// Update is called once per framevoid 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 updatevoid Start(){Destroy(gameObject, 0.2f);}// Update is called once per framevoid 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 updatevoid Start(){Invoke("BornTank", 1f);//延时调用Destroy(gameObject, 1f);//延时销毁}// Update is called once per framevoid 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;//发射子弹CDprivate 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 updatevoid Start(){}// Update is called once per framevoid 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 updatevoid Start(){}// Update is called once per framevoid 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 updatevoid Start(){Invoke("BornTank", 1f);//延时调用Destroy(gameObject, 1f);//延时销毁}// Update is called once per framevoid 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(){//在界面下边界中间位置,创建BossCreateItem(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(){//在界面下边界中间位置,创建BossCreateItem(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;}
}

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

  1. Unity使用MVC架构制作牧师与魔鬼小游戏

    Unity使用MVC架构制作牧师与魔鬼小游戏 MVC架构简介 MVC概述 MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller) ...

  2. unity 开发射击打靶vr_【好玩微信小游戏大全】香肠派对:点开既玩的射击小游戏!正版授权...

    沐沐带你发现好游戏! 沐沐今天推荐这款游戏叫<香肠派对>, 你以为是这款吃鸡的APP游戏吗? 大错特错! 沐沐我怎么会这么俗呢? 要推荐就推荐你没玩过的! 今天推荐这款<香肠派对&g ...

  3. 【游戏开发实战】使用Unity 2019制作仿微信小游戏飞机大战(七):主角飞机碰撞与爆炸

    文章目录 零.教程目录 一.前言 二.本篇目标 三.飞机机碰撞组件:BoxCollider2D.Rigidbody2D 四.添加Tag:Enemy 五.主角飞机碰撞处理:OnTriggerEnter2 ...

  4. matlab火星漫游车转向控制,OSG开发笔记(二十四):OSG漫游之平移与转向

    若该文为原创文章,未经允许不得转载 原博主博客地址:https://blog.csdn.net/qq21497936 本文章博客地址:https://blog.csdn.net/qq21497936/ ...

  5. unity课设小游戏_Unity制作20个迷你小游戏实例训练视频教程

    本教程是关于Unity制作20个迷你小游戏实例训练视频教程,时长:20小时,大小:3.8 GB,MP4高清视频格式,教程使用软件:Unity,附源文件,作者:Raja Biswas,共97个章节,语言 ...

  6. Unity开发备忘录000028:Bolt无代码做游戏——控制角色行走

    1. 添加角色 (1)添加地面碰撞器 在添加角色之前,先添加一个地面碰撞器,否则添加UnityChan角色后,由于重力的作用,她将坠入深渊. 创建一个空对象,更名为CollierOnGround,为其 ...

  7. Appgamekit制作消消乐小游戏(附代码)# 1

    Appgamekit制作消消乐小游戏(附代码)# 1 其实作者我也是刚刚才接触的Appgamekit,而且以前我是学C/C++的,所以我学的东西拿来这里就只有代码的结构思路会清晰一点了.(但是思路其实 ...

  8. 利用switch语句制作的抽卡小游戏。

    标题:利用switch语句制作的抽卡小游戏. 输出效果: 代码部分: 基本只用了switch的嵌套来完成 //该游戏是使用switch语句开发的数字小游戏,作者:initial.M //抽卡游戏规则介 ...

  9. 小白学习Unity 3D做经典游戏坦克大战日常

    老师 | Trigger 学习者 |小白 出品 | Siki 学院 Hello,小伙伴们.接下来小白跟Trigger老师做一款2D游戏坦克大战.从素材.代码到场景和UI的游戏开发.小白把日常遇到的问题 ...

最新文章

  1. C语言——把结构体数据封装成TLV格式的数据
  2. 超全面Python基础入门教程【十天课程】博客笔记汇总表
  3. 详解 Flink 容器化环境下的 OOM Killed
  4. 专业软件 —— 硬件评测
  5. 【洛谷 - P1772 】[ZJOI2006]物流运输(dp)
  6. ISACA:网络安全人员短缺仍是老大难问题
  7. setUserVisibleHint-- fragment真正的onResume和onPause方法
  8. MySQL——优化ORDER BY语句
  9. 2019windows上安装Mac OS 10.14过程详细截图
  10. Kubernetes详解(十一)——标签与标签选择器
  11. DOM技术对xml增删改查后更新源文件异常报错
  12. 太阳能电池最大功率点跟踪MPPT(Maximum Power Point Tracking)技术
  13. java实现飞机大战小游戏(源码+注释)
  14. lg android tv 手机助手,手机如何投屏到LG电视的教程来了!
  15. DSP与FPGA的SRIO通信实现
  16. csv文件超过104万数据怎么办
  17. 为什么很多人不喜欢甚至排斥用中文编程?
  18. 考研高等数学公式总结(一)
  19. Andorid APK反逆向解决方案---梆梆加固原理探寻
  20. Flutter 使用自定义 fluro 路由转场动画实现个性化页面切换

热门文章

  1. Jzoj5426 摘Galo
  2. 基于FPGA的数据采集—信号产生篇
  3. TSC打印机驱动问题
  4. 接口调用正常却返回404
  5. Xilinx Zynq-7000 PL端Kintex-7架构可编程逻辑资源,PS端主频可高达1GHz晶振、电源接口和拨码开关
  6. 微信扫码登陆在chrome浏览器被拦截
  7. 拷贝pdf中的表格数据
  8. 第二届『Citric杯』NOIP提高组模拟赛
  9. xp win7 绿色chrome
  10. 9个非常有意思的HTML5动画推荐