我不知道该怎么表达,明明清楚的知道自己的心意,
我不知道该怎么表达,明明清楚的知道自己的心意,
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)
理解碰撞检测原理
通过判断两个矩形(Rectangle)是否相交来确定碰撞。
系统提供的 Rectangle 类(位于 System.Drawing)用于表示游戏物体的位置和大小。
为游戏物体添加矩形获取方法
在每个游戏物体类中实现 GetRectangle() 方法,返回其对应的矩形:
csharp
public Rectangle GetRectangle() {
return new Rectangle(X, Y, Width, Height);
}
在游戏管理类中实现碰撞检测方法
方法名: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; // 无碰撞
}
预判未来位置避免穿透
在移动前,根据移动方向(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() 检测碰撞。
处理碰撞结果
如果返回非 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