14309人加入学习
(31人评价)
C#编程-第三季-坦克大战-宇宙最简单2021最新版

使用VS2019 制作完成于2021年11月12日

价格 免费

1 添加soundmanager类

2 增加静态playstart方法

使用soundPlayer方法

stream 对于游戏音效

play

3 游戏开始播放游戏音效

4 但是,需要多次调用某个音效,需要另外的方法

改写代码:

1  静态变量startplayer(游戏开始)

2 公开方法InitSound

传入音效资源stream

3 公开的播放方法里面,调用这个对象的play方法

(对象创建之后,就不需要重复构造对象,也不需要重复读取资源)

4 gameframework里面initSound

 

创建其他声音对象

addplayer (添加坦克)

blastplayer(爆炸)

fire发射

hit击中

 

问题,目前无法多个音轨同时播放

[展开全文]

1 放置GAMEOVER的图片

2 bullet增加检测是否和BOSS碰撞的事件

3 Gameframework内增加枚举,Running Gameover

4 增加静态变量 ,记录当前状态

5 增加判断,如果运行中,则游戏持续更新

如果游戏结束,则进入游戏结束状态

6 增加方法,切换游戏状态

7 增加游戏结束状态方法

绘制游戏结束的图片

8 BOSS碰撞的事件内增加进入游戏结束状态的事件

 

[展开全文]

1 设置判断,标签是敌人还是自己
2 如果是敌人的子弹
3 增加静态布尔,是否碰到自己的坦克
4 检测是否碰撞到了我的坦克,返回我的坦克,否则返回null
5 回到子弹,如果子弹标签是敌人坦克,则判断碰撞我的坦克是否不为空,继续后续流程,增加takeDamage方法
6 增加我的坦克血量,int =4
7 我的坦克增加takeDamage方法,减少血量
8 我的坦克死亡重生相关:
1)我的坦克初始位置X、Y
2)构造函数内,将初始位置X、Y,传入坐标值
9 受伤内增加HP小于0时,将初始X、Y传入坐标,HP=4

[展开全文]

1 添加墙壁、坦克、钢铁的爆炸效果

2 添加爆炸效果销毁
判断index是否大于4
增加isNeedDestory,构造方法里面默认为false

3 GameobjectManager
增加检测爆炸效果是否销毁(复用子弹的销毁方法)

4 updata里面调用销毁方法

将敌人坦克的攻击加入逻辑,用计数器来控制坦克的攻击欲望
1 将原来的attack的逻辑移到敌人坦克中
2 设置一个计数器,计数器到了之后坦克开始攻击

设置敌人坦克随机转向,而不是撞墙才会转向
1 添加自动转向方法 私有 AutoChangeDirection
2  AutoChangeDirection,增加计数器,到时间了进行转向

[展开全文]

1 添加墙壁、坦克、钢铁的爆炸效果

2 添加爆炸效果销毁
判断index是否大于4
增加isNeedDestory,构造方法里面默认为false

3 GameobjectManager
增加检测爆炸效果是否销毁(复用子弹的销毁方法)

4 updata里面调用销毁方法

[展开全文]

1 爆炸效果资源
2 爆炸效果类explosion,继承gameobject
3 重写getImage()
4 重写drawSelf()
5 构造方法:传入x y 坐标 
6 设置一个数组bmpArray,放置爆炸动画图,构造函数内遍历所有图片并设置透明度
7 构造方法内,设置中心坐标x y
8 私有变量,播放速度int playSpeed =2  每张图片停留2帧
9 计数器 playCount = -1 帧数
10 getImage()内写入,返回当前索引的图片bmpArray[index]
11 index设成1个私有的变量,-1
12 重写update,放入计数器 playCount自增,索引变量index=(playCount -1)/ playSpeed

回到gameobjectManage
1 创建爆炸方法,传入x,y,实例化一个爆炸类,加入爆炸列表
2 创建一个爆炸专用列表
3 遍历每一个爆炸,updata

子弹类里面对应的地方加入爆炸方法
1 int 爆炸中心位置, xExplosion = this.x +Width /2 (子弹的位置)
2 碰撞墙壁时候,添加爆炸效果方法,传入x,y

bmpArray数组越界了
1 需要在getImage()内增加判断,如果索引大于4则变成4
2 把playSpeed改成1之后,索引变成-1,出现数组越界(暂时不改成2)

遗留问题:爆炸效果不会自己销毁

[展开全文]

一 处理超出左边界和右边界的情况

 

二 和其他元素碰撞

获得子弹图片中间的位置(子弹的左上角)

检测和墙 钢 坦克的碰撞

 

获得墙,如果发生碰撞,销毁墙壁方法

 

GameObjectManager添加销毁墙壁方法

 

处理坦克的碰撞

GameObjectManager增加坦克碰撞的方法

增加销毁坦克方法

bullet增加碰撞检测

 

 

 

 

[展开全文]

子弹移动,移植敌人坦克的move和movecheck方法

1 当撞墙时候,子弹销毁

2 碰到墙(敌人 自己),墙消失,子弹销毁

 

子弹类中,检查子弹是否超出边界

检测上边界

 

检测下边界

 

bulletList出现资源共享问题引起的错误

解决方案:稍后移除Bullet

1 给Bullet增加一个移除标记

2 出墙后,设置出墙标记即可

3、在GameObjectManager内增加一个移除销毁的子弹方法

(处于性能考虑,可以不用每一帧都进行垃圾回收)

4、GameObjectManager内的Updata每次都调用销毁程序

 

 

 

[展开全文]

1 myTank: keydown中增加按下空格键的事件

 

2 创建bullet类

按下空格键发射子弹,独立使用attack方法

攻击方法

发射子弹,朝向对应的坐标

问题:由于子弹自身图片过大,导致计算差距,所以需要绘制的时候去除子弹空白像素的尺寸

调用游戏物体控制内的方法创建子弹,传入参数

 

3 GameObjectManager

创建生成子弹方法

创建子弹集合

子弹自己更新

创建子弹的方法

 

 

 

[展开全文]

解决资源冲突

原因:狂按某个按键,同时调用两次keyDown方法

主线程GameMain和KeyDown线程冲突,都去调用bmp

方法1:减少keyDown调用次数
方法2:设置线程锁

 

方法1:

 

方法2:多线程

两个线程同时调用bitmap的方法,引起资源访问冲突问题

1) GetImage(但还是加在父类DrawSelf里面)

2) Directon Dir

3) GameObject DrawSelf

 

1 生成锁object

2 Directon Dir

获取图片尺寸时加锁

3 父类DrawSelf 设为virtual

enemyTank重写DrawSelf,将锁放入

 

理解:C#--lock线程锁 

 

 

[展开全文]

思路:让敌坦克持续移动,撞墙则转向

 

由于EnemyTank是一直移动的,所以不需要isMoving的判断

将isMoving和相应的构造函数的放入MyTank

 

EnemyTank改变朝向方法

私有方法ChangeDirection

1、避开当前的朝向

2、Direction,枚举里面加上对应的序号

3、随机0~4

4、不要将生成随机数的放入函数,而是放入enemyTank的成员内,可以保证唯一的种子

(可以让随机程度更高一些)

如果放在函数内,就是不同的种子,可能会随到一样的结果

5、随机数方向

6、将对应方法放入方向检测

7、转向函数内也要加一个方向检测

 

出现bmp调用的报错

 

 

[展开全文]

GameObjectManager

创建EnemyBorn()方法

 

设置一个start方法

(刷敌的三个位置放在start里面)

 

新建刷敌人的点位

private static Point[] enemyPoint = new Point[3];

 

刷敌方法

选择随机点位

 

将通用变量转入父类

 

创建敌人类

 

创建坦克集合,方便后面统一管理

 

写4个创建坦克的方法,传入坐标值、速度,四方向图片

 

刷敌方法

随机刷出敌人类型

 

update持续生成敌人

 

 

 

[展开全文]

GameObject
1、p v GetRectangle
Rectangle rectangle = new X Y Width Height

GameObjectManager
1、bool isCollidedWall(rectangle rt)
for each nomovething wall 
if wall.GetRectangle().IntersectsWith(rt) return true
2、gai
static nomovething 
return wall

MyTank
1、if (GameObjectManager.isCollidedWall =/ null
ismoving false
2、rectangle rect = getRectangle()
switch case
rect.Y -= speed
向上:rect.Y += speed
向下:rect.X -= speed
向左:rect.X += speed
向右:
3 isCollidedWall(rect)
 

操作步骤总结

  1. 理解碰撞检测原理

    • 通过判断两个矩形(Rectangle)是否相交来确定碰撞。

    • 系统提供的 Rectangle 类(位于 System.Drawing)用于表示游戏物体的位置和大小。

  2. 为游戏物体添加矩形获取方法

    • 在每个游戏物体类中实现 GetRectangle() 方法,返回其对应的矩形:

      csharp

      public Rectangle GetRectangle() {
          return new Rectangle(X, Y, Width, Height);
      }
  3. 在游戏管理类中实现碰撞检测方法

    • 方法名:IsCollideWithWall(Rectangle rect)

    • 功能:检测传入的矩形是否与任意墙体的矩形相交。

    • 关键函数:Rectangle.IntersectsWith()

      csharp

      public Wall IsCollideWithWall(Rectangle rect) {
          foreach (Wall wall in wallList) {
              if (wall.GetRectangle().IntersectsWith(rect)) {
                  return wall; // 返回碰撞的墙体
              }
          }
          return null; // 无碰撞
      }
  4. 预判未来位置避免穿透

    • 在移动前,根据移动方向(Direction)计算下一步的矩形位置:

      csharp

      Rectangle futureRect = currentRect;
      switch (direction) {
          case Direction.Up:
              futureRect.Y -= speed;
              break;
          case Direction.Down:
              futureRect.Y += speed;
              break;
          // 处理 Left/Right 方向...
      }
    • 用 futureRect 调用 IsCollideWithWall() 检测碰撞。

  5. 处理碰撞结果

    • 如果返回非 null,则禁止移动:

      csharp

      if (GameObjectManager.IsCollideWithWall(futureRect) != null) {
          isMoving = false;
          return;
      }

关键函数与类

  • Rectangle 类

    • 命名空间:System.Drawing

    • 构造方法:new Rectangle(x, y, width, height)

    • 碰撞检测方法:IntersectsWith(Rectangle other)

  • 自定义方法

    • GetRectangle():返回游戏物体的矩形区域。

    • IsCollideWithWall(Rectangle rect):检测与墙体的碰撞,返回碰撞的墙体对象或 null

  • 注意事项

    • 碰撞检测需基于“未来位置”而非当前位置,避免穿透。

    • 方向枚举(如 Direction.Up)需提前定义。

[展开全文]

MoveCheck 移动碰撞检测,私有

1、超出墙体边界

2、和其他元素发生碰撞

 

GameObject

1、增加公用变量int 高宽{get,set}

通过图片获取宽高——

notMovething

1、私有image的变量img

2、Img的get方法保持不变{return img;}

3、img的set方法{img=value,高=img.高,宽=img.宽}

Movething

有四张图片,但只有一张“当前”的图片,四张图片高宽不一致

1、重写Dir,设置私有变量dir(类似img)

2、设置dir的set方法:

       dir = value;

      Bitmap bmp = null;

       switch dir

             case  方向朝上

                  把bmp = BitmapUp;  break

             同理,朝下、朝左、朝右 

       最后,将bmp的宽赋值给Width,同理赋值Height

 

超出墙体边界

myTank

0、在构造函数中,把this.Dir的默认放在最后(需要完成上下左右的设置之后再进行默认值设置)

1、如果向上移动,如果Y-Speed<0,则无法移动,return

(00点在左上角)

2、elseif,下,Y+Speed>是否大于450

3、左,x-Speed<0

4、右边 X+Speed+Width>450

 

收纳代码

#region  注释的说明文字

#endregion 

 

[展开全文]

GameObject 

1、添加虚方法 Update

2、将Drawself放入Update

 

GameObjectManger

1、思考:不需要drawself方法,因为update方法自带了

2、创建Update方法,公开,静态

3、将原来的drawMap、drawTank里面的方法内容都迁入update

4、把原来的drawself全都改成update

5、把原来的drawMap、drawTankd都注释掉

 

gameframework

1、去除原来的drawMap、drawTank

2、调用gameobjectManager里面的update方法

 

完成重构

 

可选优化:将wallList、steelList等都合并成同一个集合,然后统一调用update方法

 

myTank

1、重写update,公开

2、添加base.Update()

3、新增Move方法,私有

4、判断如果isMoving是false

5、switch case 

        上 -Y、下+Y、左-X、右+X

[展开全文]

使用键盘来控制

 

如何检测按键

1、窗体内找到KeyDown\KeyUp

2、监听按键,两个按键方法

3、sender 发送者(暂时不需要设置)

4、KeyEventArgs e哪个键值

      .KeyCode = Key.W

 

用GameOjectManager作为中转传递键值

1、增加两个方法KeyUp、KeyDown

用于调用myTank的KeyUp、KeyDown

2、窗口From1里面,直接调用GameOjectManager的KeyUp和KeyDown

 

窗口传给GameObjectManager,再传给myTank(不过有一些繁琐,所以改成直接让Form获取gameObjectManager,进而控制tank)

 

移动事件放在myTank里

0、tank里加入按下和抬起的方法(并不需要静态了)

Form 传递给GameObjectManage,再传给MyTank

1、处理键值,switch来判断是哪个键值按下了

2、W按下了,Direction设为Up,break,其他依次类推

3、设置移动:W按下了,Y -=Speed 根据速度减坐标(但需要优化)

设置移动方法优化:

1、public 布尔值:isMoving

2、按下WASD任意值时,isMoving 打开

3、松开WASD任意值时,isMoving 关闭

[展开全文]

创建坦克方法

1、固定坐标

2、new一个MyTank,设置坐标,方向速度

3、设置静态对象MyTank

4、绘制坦克方法

       drawself

 

创建MyTank

1、构造方法:坐标、速度、方向

       设置默认值

       四个朝向的对应图片

 

update里面设置DrawMyTank

[展开全文]

复制CreateWall进行持续绘制(重复操作*N次)

 

选择创建墙的类型

1、修改CreateWall的参数,增加Image img

2、指定绘制的类型Resources.wall

3、新增钢墙Resources.steel

 

创建钢墙的集合(用于不同类型)

1、创建钢墙steelList

2、DrawMap内增加钢墙集合的绘制

 

用同样的方法绘制其他的墙壁

 

新增BOSS的集合并绘制

1、创建新的BOSS的方法

2、static void GreateBoss

3、创建静态BOSS对象(默认为空)

 

[展开全文]

GameFrameWork.myGG.Clear(Color.Black)刷黑和绘制是交替运行,所以就会出现闪烁

但是,刷黑必须在每帧都要运行,避免运行后的残影

 

解决方法:将单帧上所有的元素(刷黑、墙体、坦克等)先画完,再贴到屏幕上

1、创建图片(和窗体一样大)

2、通过图片创建一个画布(FromImage静态方法)

3、删除原有的窗体画布,将图片画布画入窗体

4、声明一个静态的变量,作为图片画布

5、清空改为清空图片画布

6、把窗体画布也声明成静态的

7、窗体循环内,增加窗体画布的每帧绘制(位置为0,0点)

x

[展开全文]

添加游戏对象管理类GameObjectManager

1、公共的创建初始化地图(静态方法)CreateMap

2、私有的创建墙的方法(静态)

CreateWall

       传入坐标(从哪个坐标开始,绘制几个格子)、图片

       将坐标转化为30一个格子

 3、for循环,绘制砖块

 4、新建对象wall1、wall2(左墙、右墙)

5、新建类型为不可以动物体的list并实例化

6、将左墙、右墙放入list

7、将创建墙的返回值为list

 

不可移动物体类中

1、构造一个方法,用于传入坐标值、图片

 

CreateMap方法

1、将list放在类中(而不是创建墙的方法中)

 

创建静态方法DrawMap,将列表中的对象都绘制出来

1、遍历每一个墙,调用Drawself方法进行绘制

2、每一帧都要进全局刷新

3、start 里面加入CreateMap

4、update里面加入DrawMap

[展开全文]

授课教师

问问题加入A计划,有专门负责答疑的老师哦!!!

课程特色

图文(1)
下载资料(1)
视频(35)

学员动态

危险 开始学习 17-绘制地图
林zdhs 完成了 素材.zip
林zdhs 开始学习 素材.zip
林zdhs 加入学习