の記事に触発されて、適当に円が跳ねるものを作ってみた。
それっぽく動いているだけで、法則とか適当なのはご勘弁を。
接触判定
今回接触判定に使用しているのはMSOffice 2010以降から搭載されている「図形の結合」機能である(2010ではリボンに表示されていないが、機能自体は存在している)。
Office 2013以降なら
(図形を選択) 描画ツール > 書式 > 図形の挿入
に存在する。
より詳細な部分は先頭の記事を参照。
コード
PowerPoint2010以降で動作するはずです(2013 32bit/2016 64bitで動作確認)。
PowerPointで、標準モジュールとクラスモジュールを挿入し、以下のコードを貼り付ける。
クラスモジュールの名前はBall
とすること。
BallBoundSample
プロシージャを起動すると上記の画像のような動作を行うはずである。
標準モジュール
標準モジュール
Option Explicit
Private Declare PtrSafe Sub _
Sleep Lib "kernel32" ( _
Optional ByVal dwMilliseconds As Long = 1)
Public Type OfficeXY
X As Single
Y As Single
End Type
Sub BallBoundSample()
'作業場所の指定
Dim TgtPres As PowerPoint.Presentation
Set TgtPres = PowerPoint.ActivePresentation
TgtPres.SlideShowSettings.ShowType = ppShowTypeWindow2
Dim sldShowWin As PowerPoint.SlideShowWindow
If TgtPres.Windows.Count = 0 Then
Set sldShowWin = TgtPres.SlideShowWindow
sldShowWin.Activate
Else
Set sldShowWin = TgtPres.SlideShowSettings.Run
End If
Dim tgtSld As Slide
Set tgtSld = sldShowWin.View.Slide
'形状の準備
Dim tgtGround As PowerPoint.Shape
Set tgtGround = MakeGround(tgtSld)
Dim myBall As Ball
Set myBall = MakeBall(tgtSld)
'動かしてみる
Do
'スライド外になったら抜ける
If Not InSlideArea(myBall.BaseShape, TgtPres) Then Exit Do
Call myBall.NextStep(0.2)
Call myBall.Clash(tgtGround)
Call Sleep
DoEvents '描写・途中で止めたい場合用
Loop
'後始末
'Stop
tgtSld.Shapes.Range.Delete
End Sub
'適当な形状を作るだけ
Private Function MakeGround(ByVal iTgtSld As Slide) As Shape
Dim shps As PowerPoint.Shapes
Set shps = iTgtSld.Shapes
Dim oShp As Shape
'位置や角度の設定は適当
With iTgtSld.Parent.SlideMaster
Set oShp = shps.AddShape(msoShapeRectangle, -.Width, .Height / 2, .Width * 3, 1)
End With 'iTgtSld.Parent.SlideMaster
Call oShp.IncrementRotation(15)
Set MakeGround = oShp
End Function
Private Function MakeBall(ByVal iTgtSld As Slide) As Ball
Const Size! = 50!
Dim shpTop As Single: shpTop = 0!
Dim shpLft As Single: shpLft = 0!
Dim shpWid As Single: shpWid = Size
Dim shpHit As Single: shpHit = Size
Dim shps As PowerPoint.Shapes
Set shps = iTgtSld.Shapes
Dim tmpBall As PowerPoint.Shape
Set tmpBall = shps.AddShape(msoShapeOval, shpLft, shpTop, shpWid, shpHit)
Set MakeBall = GetBallClass(tmpBall)
End Function
Private Function GetBallClass(ByVal BaseShape As Shape) As Ball
Dim tmpCls As Ball
Set tmpCls = New Ball
Call tmpCls.Init(BaseShape)
Set GetBallClass = tmpCls
End Function
Private Function InSlideArea(ByVal ChkShp As Shape, ByVal TgtPres As Presentation) As Boolean
With TgtPres.SlideMaster
If ChkShp.Top + ChkShp.Height < 0 Then GoTo NotInSlideArea
If ChkShp.Left > .Width Then GoTo NotInSlideArea
If ChkShp.Left + ChkShp.Width < 0 Then GoTo NotInSlideArea
If ChkShp.Top > .Height Then GoTo NotInSlideArea
End With
Let InSlideArea = True
Exit Function
NotInSlideArea:
Let InSlideArea = False
Exit Function
End Function
クラスモジュール:Ball
Ball.cls
Option Explicit
Private clsShp As PowerPoint.Shape
Private clsVelocity As OfficeXY
Private clsAcceleration As OfficeXY
'反発率を再現しようとしたもの
Private Const Ratio! = 0.9!
Public Property Get BaseShape() As PowerPoint.Shape
Set BaseShape = clsShp
End Property
Public Property Get Velocity() As OfficeXY
Let Velocity = clsVelocity
End Property
Public Property Get Acceleration() As OfficeXY
Let Acceleration = clsAcceleration
End Property
Private Sub Class_Initialize()
clsVelocity.X = 0!
clsVelocity.Y = 0!
clsAcceleration.X = 0!
clsAcceleration.Y = 9.81!
End Sub
Friend Sub Init(ByVal BaseShape As PowerPoint.Shape)
Set clsShp = BaseShape
End Sub
'時間を進める
'現在位置の更新と速度の再定義
Public Sub NextStep(Optional ByVal Step As Single = 1)
With clsShp
.Left = VBA.CSng(.Left + (clsVelocity.X * Step))
.Top = VBA.CSng(.Top + (clsVelocity.Y * Step))
End With 'clsShp
With clsVelocity
.X = VBA.CSng((.X + clsAcceleration.X * Step))
.Y = VBA.CSng((.Y + clsAcceleration.Y * Step))
End With 'clsVelocity
'画面描写
clsShp.TextFrame2.TextRange.Text = " "
End Sub
'不完全なロジック
'大きくめり込んだとき、貫通したとき復帰不可
'分解能を上げる(NextStepの引数を小さくする)ことで仮対応
Public Function Clash(ByVal iTgtShp As PowerPoint.Shape) As Boolean
Dim sldShps As PowerPoint.Shapes
Set sldShps = clsShp.Parent.Shapes
Dim currentShpCnt As Long
currentShpCnt = sldShps.Count
Dim shpIndxs(1) As Long
shpIndxs(0) = clsShp.ZOrderPosition
shpIndxs(1) = iTgtShp.ZOrderPosition
Dim dupShpRng As PowerPoint.ShapeRange
Set dupShpRng = sldShps.Range(shpIndxs).Duplicate
Dim dupOvalIndex As Long
dupOvalIndex = IIf(shpIndxs(0) < shpIndxs(1), 1, 2)
Dim currentCoords As OfficeXY
With dupShpRng.Item(dupOvalIndex)
currentCoords.X = .Left + (.Width / 2)
currentCoords.Y = .Top + (.Height / 2)
End With 'dupShpRng.Item(1)
'形状が交差していれば、形状作成。交差していなければ形状消失。
Call dupShpRng.MergeShapes(msoMergeIntersect)
If currentShpCnt + 1 <> sldShps.Count Then Exit Function
Clash = True
Dim intersectShp As PowerPoint.Shape
Set intersectShp = sldShps.Item(currentShpCnt + 1)
Dim normalVector As OfficeXY
normalVector.X = currentCoords.X - (intersectShp.Left + (intersectShp.Width / 2))
normalVector.Y = currentCoords.Y - (intersectShp.Top + (intersectShp.Height / 2))
Dim normalVec As Double
normalVec = VectorNorm(normalVector)
Dim basVector As Double
basVector = VectorNorm(clsVelocity)
With clsVelocity
.X = VBA.CSng(basVector * (normalVector.X / normalVec) * Ratio)
.Y = VBA.CSng(basVector * (normalVector.Y / normalVec) * Ratio)
End With
Call intersectShp.Delete
End Function
Private Function VectorNorm(BaseVector As OfficeXY) As Double
Let VectorNorm = VBA.Sqr(BaseVector.X * BaseVector.X + BaseVector.Y * BaseVector.Y)
End Function
その他
2017/01/15追記
先頭の記事の人の記事が追加されていました。