Schmidt
9/20/2011 2:48:00 AM
In an attempt, to further cleanup our Form
(and our already existing cBall-Class),
we will introduce an additional Class now:
cVect
This class will be responsible mainly for the
Collision-math - but of course also to store
the x, y Coords of the different Vectors, which
we will use "all over the place" when the demo
evolves.
As a short (math-related) introducion - there's
two kind of "vector-types" (both will host
their x,y Coords in the same cVect-Class) -
but to understand (from a users point of view)
the behaviour a bit better:
The one kind (in our cBall-example represented
by the C-MemberVariable, our Ball-Centerpoint)
is a Position-Vector ... think about it as
"host of absolute x,y-coords" - and then there's
the other kind of vector: a "host of relative coords" -
aka Direction-Vector (represented in our cBall-Class
over the V-Membervariable, our Ball-Velocity).
So, in other words: 'C' is a Point (vector) -
and 'V' is a vector used for "compositing -
or calculating new Points in our Coord-System.
For example, to get the new (center-)Point, after
moving our simulation one step further, we would
apply the current (relative) Velocity (stored in 'V')
in a Vector-Addition to our Ball-CenterPoint 'C':
Set C = C.Plus(V)
To create the new Center-Point for the Ball-Placement
(and rendering) in each simulation-step anew.
Ok - that much for the moment with regards to
vector-math - let's move on to the real topic
of this Tutorial-Step: Constructors.
VB6 doesn't have them. <g>
Well not entirely true, because one can make
good use of the often hated (and left untouched)
Default-Method, each VB-Class is able to offer,
when setting a special attribute on a given "Public
Method of your choice" on the Class in question
(per <Extras><ProcedureAttributes> in the IDE-menu).
What are constructors for?
Well, if you look at the code in our latest Form
(in Tutorial-Step 2):
Set Ball2 = New cBall 'on Ball2 we change the default-velocity
Ball2.Vx = -2
Ball2.Vy = -2
We see the instantiation line, to create the Ball2-
Object from our cBall-Class - followed by:
"changing two public-members at construction-time,
because we simply want a different behaviour of Ball2,
compared to Ball1".
Now the lines above don't look all that horrible,
but when hacking a lot of similar code in, and
there's not two members to set, but (on average)
maybe 3-5, then constructors, which allow to supply
these "class-settings, to start with" in a single
line (with intellisense-support), would be worthwhile.
Ok, there's different approaches in VB6, to work around
that - the simplest one is perhaps, to just define a
constructor-function for a given class-type in a *.bas
module (globally).
The used method here achieves a better encapsulation,
because the Constructor-Function is defined in the
Class itself - as is the standard in other languages
as well.
example in C++:
class cVect {
public:
float x;
float y;
cVect () {};
cVect (float, float);
};
cVector::cVector (float a, float b) {
x = a;
y = b;
}
Directly above is the Constructor-Method...
All in all not differing that much from our cVect VB6-Class:
Public x As Double
Public y As Double
'constructor, set as class-DefaultMethod in Extras->ProcedureAttributes
Public Function cVect(ByVal x As Double, ByVal y As Double) As cVect
Set cVect = New cVect
With cVect
.x = x
.y = y
End With
End Function
The above class-defined Constructor-method allows us,
to write the following "math-methods" in cVect this way:
Public Function Neg()
Set Neg = cVect(-x, -y)
End Function
Public Function FlipX()
Set FlipX = cVect(-x, y)
End Function
Public Function FlipY()
Set FlipY = cVect(x, -y)
End Function
Public Function Plus(V As cVect)
Set Plus = cVect(x + V.x, y + V.y)
End Function
--------------------------------
Instead doing it this way ...
(example only for the Neg()-method):
Public Function Neg()
Set Neg = New cVect
Neg.x = -x
Neg.y = -y
End Function
So, constructors can ensure less typing-efforts.
There's no need, to over-do this (and introduce
constructors in each and every Class) - but
for cVect (and also cBall) this is very handy.
Note, that the (other) internal methods in cVect
will directly "jump to" (and use) the internal
definition of the constructor-method, which
is BTW named the same way as the Class-Type,
but VB does not have any problem with that -
and this allows for easier "search-replace
stuff" (e.g. when you search with: "check for
whole word" - in case you want to rename a Class
later on).
Ok, so now our Class 'cVect' can internally use its
similarly named cVect-constructor... fine.
But what about usage of this predefined
cVect-Constructor-method from the outside
(globally within your Application)?
Also possible, even over the same constructor-
name 'cVect'.
There's three things, which are prerequisites to
achieve that:
1. the cVect-Constructor-Method needs to be defined
Public in your Class: done
2. the cVect-Method needs the (hidden) "Class-DefaultMethod
Attribute" set (achievable in your IDE over
<Extras><Procedure-Attributes> -> Set as Default
3. You will need an additional Public Variable in a
*.bas Module, defined this way:
Public cVect As New cVect
The last point (3.) is using a declaration with VBs
'New' Keyword before the ClassType in the Var-Definition.
This ensures an automatic IsNothing-check, meaning:
You can use the Variable (in this case 'cVect')
"all over the place" in your App (or Module)
without taking care of proper instancing yourself.
What VB does under the hood for you, in case such a
"specially defined" Variable is about to be used, is:
If IsNothing(cVect) Then
Set cVect = New cVect
End If
....hand out the Variable to the caller...
An IsNothing check does not take up that much
time - it only needs a few CPU-cycles, to
perform an IsZero-check on the 4Byte-ObjectPointer.
So, whilst "overboarding usage" of:
Dim SomeObjVar As New cSomeClass
is not recommended, this VB6-feature has its
use-cases nevertheless.
Ok, not much to explain anymore (I think) in this
tutorial-step.
Todo in the next step:
- further cleanup of the Form, introducing an
"ucCanvas" UserControl and a cBalls-Collection-Class.
Olaf
'***Into a Class, named: cVect
Option Explicit
Public x As Double
Public y As Double
'constructor->set as class-defaultmethod in Extras->ProcedureAttributes
Public Function cVect(ByVal x As Double, ByVal y As Double) As cVect
Set cVect = New cVect
With cVect
.x = x
.y = y
End With
End Function
Public Function Neg()
Set Neg = cVect(-x, -y)
End Function
Public Function FlipX()
Set FlipX = cVect(-x, y)
End Function
Public Function FlipY()
Set FlipY = cVect(x, -y)
End Function
Public Function Plus(V As cVect)
Set Plus = cVect(x + V.x, y + V.y)
End Function
'***Into a Class, named: cBall
Option Explicit
Public R As Double 'Ball-Radius
Public C As cVect 'Ball-CenterPoint (a Position-Vector)
Public V As cVect 'Ball-Velocity (a Direction-Vector)
Public FillColor As Long
Public BorderColor As Long
Private Sub Class_Initialize()
FillColor = vbYellow
BorderColor = &HD0D0D0
End Sub
'constructor->set as class-defaultmethod in Extras->ProcedureAttributes
Public Function cBall(ByVal R As Double, C As cVect, V As cVect, _
Optional FillColor, Optional BorderColor) As cBall
Set cBall = New cBall
With cBall
.R = R
Set .C = C
Set .V = V
If Not IsMissing(FillColor) Then .FillColor = FillColor
If Not IsMissing(BorderColor) Then .BorderColor = BorderColor
End With
End Function
Public Sub Move()
Set C = C.Plus(V)
End Sub
Public Sub CheckForCollision(CanvasWidth, CanvasHeight)
If C.x < R Or C.x > CanvasWidth - R Then Set V = V.FlipX
If C.y < R Or C.y > CanvasHeight - R Then Set V = V.FlipY
Move 'move out of the penetration-zone
End Sub
'***Into a *.bas-Module, named: modGlobals
Option Explicit
Public cBall As New cBall 'global cBall-Constructor (auto-instancing)
Public cVect As New cVect 'global cVect-Constructor (auto-instancing)
'***Into a Form, named: fDemo
Option Explicit
Private WithEvents TRender As VB.Timer 'for dynamic creation
Private fW, fH '<- just to store the current FormDimensions
Private Ball1 As cBall, Ball2 As cBall
Private Sub Form_Load() 'let's load (and explicitely init) our Balls
Set Ball1 = cBall(20, cVect(50, 50), cVect(1, 2))
Set Ball2 = cBall(16, cVect(150, 150), cVect(-2, -1), vbCyan)
End Sub
Private Sub Form_Resize() 'save the cur. Width/Height in Vars
fW = ScaleWidth
fH = ScaleHeight
End Sub
Private Sub TRender_Timer()
Cls
Draw
Refresh
End Sub
Sub Draw()
Ball1.Move
Ball1.CheckForCollision fW, fH
Ball2.Move
Ball2.CheckForCollision fW, fH
'drawing remains in our current Form-Canvas
FillColor = Ball1.FillColor
Circle (Ball1.C.x, Ball1.C.y), Ball1.R, Ball1.BorderColor
FillColor = Ball2.FillColor
Circle (Ball2.C.x, Ball2.C.y), Ball2.R, Ball1.BorderColor
End Sub
'-----------------------------------------------------
'inits, which don't really belong to the problem above
Private Sub Form_Initialize()
'Inits on our Form-Canvas
AutoRedraw = True
ScaleMode = vbPixels
FillColor = vbYellow
FillStyle = vbFSSolid
'we create the RenderLoop-Timer dynamically...
Set TRender = Controls.Add("VB.Timer", "TRender")
TRender.Interval = 10 '... set a small interval
TRender.Enabled = True '... and start it
End Sub