You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何检测同一数组中PictureBox的碰撞并实现碰撞反弹

问题描述

我正在开发一款游戏,需要实现敌人(太空垃圾)相互碰撞时向相反方向反弹的功能。我尝试使用两层For循环,结合IntersectsWith方法检测碰撞,但未能成功。当前已通过Timer实现飞船、基地、太空垃圾等对象的移动及部分碰撞逻辑,现需解决数组中太空垃圾之间的碰撞检测及反弹问题。

以下是我的Form1类完整代码:

Public Class Form1
Private pic_Nave As Nave
Private pic_Base As Base
Private vDesecho(10) As Desecho
Private nivel As Integer
Private vAsteroide(10) As Asteroide
Private desechoEliminated As Integer
'Private cont(10) As Double
Private spawntime As Integer

Private Sub tmr_Mover_Tick(sender As Object, e As EventArgs) Handles tmr_Mover.Tick
    pic_Nave.Mover(Me)
    pic_Base.Mover(Me)
    spawntime -= 1
    lblitems.Text = spawntime


    For i = 0 To 9
        vDesecho(i).Mover(Me)
        If (pic_Nave.imagen.Bounds.IntersectsWith(vDesecho(i).imagen.Bounds)) Then

            Controls.Remove(vDesecho(i).imagen)
        End If
        If (pic_Base.imagen.Bounds.IntersectsWith(vDesecho(i).imagen.Bounds)) Then
            vDesecho(i).diry = -vDesecho(i).diry
        End If
    Next
    'For i = 0 To 9
    'For j = 9 To 0
    'If (vDesecho(i).imagen.Bounds.IntersectsWith(vDesecho(j).imagen.Bounds)) Then
    'spawntime = 5
    'vDesecho(i).dirx = -vDesecho(i).dirx
    'vDesecho(j).diry = -vDesecho(j).diry
    'vDesecho(j).Mover(Me)
    'End If
    'Next
    'Next

    For i = 0 To nivel - 1
        vAsteroide(i).Mover(Me)
        If (pic_Base.imagen.Bounds.IntersectsWith(vAsteroide(i).imagen.Bounds)) Then
            vAsteroide(i).diry = -vAsteroide(i).diry
        End If
    Next
End Sub

Private Sub makedesecho()
    For i = 1 To 1
        vDesecho(i) = New Desecho()
        Controls.Add(vDesecho(i).imagen)

        vDesecho(i).Mover(Me)
        If (pic_Nave.imagen.Bounds.IntersectsWith(vDesecho(i).imagen.Bounds)) Then

            Controls.Remove(vDesecho(i).imagen)
            desechoEliminated = desechoEliminated + 1
        End If
    Next
End Sub

Public Sub New()

    ' Esta llamada es exigida por el diseñador.
    InitializeComponent()

    ' Agregue cualquier inicialización después de la llamada a InitializeComponent().
    nivel = 1
    desechoEliminated = 0
    spawntime = 5
    For i = 0 To 9
        vDesecho(i) = New Desecho()
        Controls.Add(vDesecho(i).imagen)
    Next

    For i = 0 To nivel - 1
        vAsteroide(i) = New Asteroide()
        Controls.Add(vAsteroide(i).imagen)
    Next

    pic_Nave = New Nave()
    Controls.Add(pic_Nave.imagen)

    pic_Base = New Base()
    Controls.Add(pic_Base.imagen)

    tmr_Mover.Start()

End Sub
Protected Overrides Function ProcessCmdKey(ByRef msg As Message, keyData As Keys) As Boolean
    Select Case keyData
        Case Keys.Up
            pic_Nave.diry -= 1
        Case Keys.Down
            pic_Nave.diry += 1
        Case Keys.Left
            pic_Nave.dirx -= 1
        Case Keys.Right
            pic_Nave.dirx += 1

    End Select
    Return MyBase.ProcessCmdKey(msg, keyData)
End Function
End Class
解决方案

核心问题分析

你之前的碰撞检测代码存在两个关键问题:

  • 重复检测:循环中i和j遍历全范围,会重复检测同一对太空垃圾(如i=0,j=1和i=1,j=0),还会触发自身碰撞(i=j)。
  • 错误的反弹逻辑:只反转其中一个对象的x方向和另一个的y方向,不符合物理反弹的预期。

修正后的实现

1. 优化碰撞检测循环

让j从i+1开始遍历,确保每对垃圾只检测一次,且跳过自身:

For i = 0 To 9
    ' 跳过已被移除的太空垃圾
    If Not Me.Controls.Contains(vDesecho(i).imagen) Then
        Continue For
    End If

    For j = i + 1 To 9
        ' 跳过已被移除的太空垃圾
        If Not Me.Controls.Contains(vDesecho(j).imagen) Then
            Continue For
        End If

        ' 检测碰撞
        If vDesecho(i).imagen.Bounds.IntersectsWith(vDesecho(j).imagen.Bounds) Then
            ' 方案1:简单反转方向(模拟反弹)
            vDesecho(i).dirx = -vDesecho(i).dirx
            vDesecho(i).diry = -vDesecho(i).diry
            vDesecho(j).dirx = -vDesecho(j).dirx
            vDesecho(j).diry = -vDesecho(j).diry

            ' 方案2:交换速度(更真实的弹性碰撞,假设质量相同)
            ' Dim tempDirX As Integer = vDesecho(i).dirx
            ' Dim tempDirY As Integer = vDesecho(i).diry
            ' vDesecho(i).dirx = vDesecho(j).dirx
            ' vDesecho(i).diry = vDesecho(j).diry
            ' vDesecho(j).dirx = tempDirX
            ' vDesecho(j).diry = tempDirY

            ' 移动一次避免对象卡在一起
            vDesecho(i).Mover(Me)
            vDesecho(j).Mover(Me)
        End If
    Next
Next

2. 替换到Timer事件中

把这段代码替换掉你原来注释的碰撞循环,放在太空垃圾移动循环之后即可:

Private Sub tmr_Mover_Tick(sender As Object, e As EventArgs) Handles tmr_Mover.Tick
    pic_Nave.Mover(Me)
    pic_Base.Mover(Me)
    spawntime -= 1
    lblitems.Text = spawntime


    For i = 0 To 9
        vDesecho(i).Mover(Me)
        If (pic_Nave.imagen.Bounds.IntersectsWith(vDesecho(i).imagen.Bounds)) Then
            Controls.Remove(vDesecho(i).imagen)
        End If
        If (pic_Base.imagen.Bounds.IntersectsWith(vDesecho(i).imagen.Bounds)) Then
            vDesecho(i).diry = -vDesecho(i).diry
        End If
    Next

    ' 新增/修正的太空垃圾碰撞检测代码
    For i = 0 To 9
        If Not Me.Controls.Contains(vDesecho(i).imagen) Then
            Continue For
        End If
        For j = i + 1 To 9
            If Not Me.Controls.Contains(vDesecho(j).imagen) Then
                Continue For
            End If
            If vDesecho(i).imagen.Bounds.IntersectsWith(vDesecho(j).imagen.Bounds) Then
                vDesecho(i).dirx = -vDesecho(i).dirx
                vDesecho(i).diry = -vDesecho(i).diry
                vDesecho(j).dirx = -vDesecho(j).dirx
                vDesecho(j).diry = -vDesecho(j).diry
                vDesecho(i).Mover(Me)
                vDesecho(j).Mover(Me)
            End If
        Next
    Next

    For i = 0 To nivel - 1
        vAsteroide(i).Mover(Me)
        If (pic_Base.imagen.Bounds.IntersectsWith(vAsteroide(i).imagen.Bounds)) Then
            vAsteroide(i).diry = -vAsteroide(i).diry
        End If
    Next
End Sub

3. 额外注意事项

  • 空引用防护:检测前先判断控件是否存在,避免已被移除的垃圾对象引发错误。
  • 碰撞后位移:碰撞后立即调用一次Mover,避免两个对象重叠卡住。
  • 进阶优化:如果需要更精确的碰撞,可以使用矩形的交集区域判断碰撞方向,只反转对应轴的方向(比如左右碰撞反转x,上下碰撞反转y)。

内容的提问来源于stack exchange,提问作者Lizmarie Feriz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 04:12:03