|
|
|
|
|
موضوع : پخش افکتهاي صوتی در برنامه هاي مالتي مديا ساخت بافر و play کردن آن : تاکنون ما توانستيم DirectSound را initial کنيم . همانطور که می دانيد در تمام component های DirectX داده ها در يکسری بافر ذخيره می شوند . در مورد DirectSound نيز ما يک بافر با نام DirectSoundSecondaryBuffer8 می سازيم و داده های صوتی را در آن قرار می دهيم . برخی پارامتر ها هستند که بايد برای بافر تنظيم شوند مثل : stereo يا mono بودن بافر ، ۸ بيتی يا ۱۶ بيتی بودن بافر ، فرکانس صوتی ( 22khz ، 44khz و غيره ) . اگر اين پارامترها را مشخص نکنيم DirectSound از اطلاعات فايل صوتی استفاده می کند . در يک کاربرد ساده ، ما تنها يک بافر صوتی از يک فايل ايجاد می کنيم اما امکان ايجاد چندين بافر بطور همزمان و نيز پخش چندين صدا بطور همزمان نيز وجود دارد : DSBDesc.lFlags = DSBCAPS_CTRLFREQUENCY Or DSBCAPS_CTRLPAN Or DSBCAPS_CTRLVOLUME Set DSBuffer = DS.CreateSoundBufferFromFile(App.Path & "\Sample.wav", DSBDesc)x MsgBox "SOUND BUFFER CREATED:"x MsgBox "Buffer Size: " & DSBDesc.lBufferBytes & "bytes (" & Round(DSBDesc.lBufferBytes / 1024, 3) & "kb)"x MsgBox "Buffer Channel Count:" & DSBDesc.fxFormat.nChannelsIIf(DSBDesc.fxFormat.nChannels = 1, " (Mono)", " (Stereo)")x MsgBox "Buffer Bits per channel: " & DSBDesc.fxFormat.nBitsPerSample & " bits"x در بالا يک بافر صوتی ايجاد شده و اطلاعات صدا از فايل به بافر load شده است . حال بايستی داده صوتی موجود در بافر را play کنيم : دستور لازم برای Play کردن بافر بصورت loop : DSBuffer.Play DSBPLAY_LOOPING دستور لازم برای Play کردن بافر بدون loop : DSBuffer.Play DSBPLAY_DEFAULT دستورات لازم برای Stop کردن بافر : DSBuffer.Stop DSBuffer.SetCurrentPosition 0 دستور لازم برای Pause کردن بافر : DSBuffer.Stop تنظيم خصوصيات بافر : سه خصوصيت وجود دارد که در مورد بافر تنظيم می شود pannig ، volume و frequency محدوده مقادير pannig بين اعداد زير است : DSBPAN_LEFT = -10,000 DSBPAN_CENTER = 0 DSBPAN_RIGHT = 10,000 توسط متد SetPan می توان pannig بافر را تنظيم کرد : DSBuffer.SetPan yourValue DirectSound صدا را تقويت نمی کند بلکه آنرا تضعيف می نمايد بنابراين ماکزيمم volume عبارت است از volume ای که فايل صوتی با آن ضبط شده است . بعبارت ديگر محدود مقادير volume بين اعداد زير است : DSBVOLUME_MAX = 0 DSBVOLUME_MIN = -10000 توسط متد SetVolume می توان volume بافر را تنظيم کرد : DSBuffer.SetVolume yourValue محدود فرکانسی DirectSound عبارت است از : DSBFREQUENCY_MIN = 100 (hz)x DSBFREQUENCY_MAX = 100000 (hz) = 100khz x توسط متد SetFrequency می توان فرکانس بافر را تنظيم کرد : DSBuffer.SetFrequency yourValue موضوع : پخش موزيک توسط DirectMusic مقدمه : در اولين درس از آموزش DirectXAudio با چگونگي پخش افکتهاي صوتي آشنا شديد . اکنون اين توانايي را داريد که يک engine ساده صوتي بنويسيد . در اين بخش مباني پخش موزيک را فرا خواهيد گرفت . پس از اين درس شما مي توانيد يک ماژوال براي پخش موزيکهاي پس زمينه و افکتهاي صوتي براي برنامه هايتان ايجاد کنيد . Initil کردن DirectMusic8 : قبل از هر کار بايستي ماژول DirectMusic8 را مقداردهي اوليه کنيد . اينکار بصورت زير انجام مي شود : Option ExplicitImplements DirectXEvent8 Private oDX As DirectX8 Private oDMPerf As DirectMusicPerformance8 Private oDMLoader As DirectMusicLoader8 Private oDMSeg As DirectMusicSegment8 Dim dmParams As DMUS_AUDIOPARAMS Set oDX = New DirectX8 Set oDMPerf = oDX.DirectMusicPerformanceCreate Set oDMLoader = oDX.DirectMusicLoaderCreate oDMPerf.InitAudio frmMain.hWnd, DMUS_AUDIOF_ALL, dmParams, Nothing, DMUS_APATH_DYNAMIC_STEREO, 128 oDMPerf.SetMasterAutoDownload True شي DirectMusicLoader8 کمک مي کند تا موزيک درون بافر load شود . شي DirectMusicSegment8 مموزيکي را که بايد پخش شود ذخيره مي کند . کد فوق کافي است يکبار زمانيکه برنامه آغاز مي شود ، اجرا گردد . اکنون ما يک واسط مقدار دهي شده از DirectMusic داريم اما قبل از اينکه موزيک را Load کرده و پخش کنيم چگونگي terminate کردن DirectMusic را در زير مي بينيد : If ObjPtr(oDMSeg)Then Set oDMSeg = Nothing If ObjPtr(oDMLoader)Then Set oDMLoader = Nothing If Not (oDMPerf Is Nothing) Then oDMPerf.CloseDown Set oDMPerf = Nothing End If If ObjPtr(oDX) Then Set oDX = Nothing پيغامها : در برخي از component هاي DirectX8 مثل Input , Sound , Music و Play برنامه شما بايستي يک سيستم messaging را برپا کند تا DirectX زمان وقوع برخي رخدادهاي خاص را بشما گزارش دهد . اين مطلب بخصوص زمانيکه يک موزيک را پخش مي کنيد مفيد است براي مثال مي تواند زمان خاتمه يافتن موزيک را به شما اطلاع دهد و آنگاه شما مي توانيد قطعه موزيک بعدي را پخش کنيد . پيغامها توسط يک سيستم callback انجام مي شوند . کد زير را در تابع InitDMusic تان پس از initial کردن DirectMusic8 قرار دهيد : oDMPerf.AddNotificationType DMUS_NOTIFY_ON_SEGMENT hEvent = oDX.CreateEvent(Me)x oDMPerf.SetNotificationHandle hEvent اولين سطر به DirectMusic مي گويد چه نوع پيغامهايي را مي خواهيد به برنامه تان بفرستد . چندين نوع پيغام وجود دارد : DMUS_NOTIFY_ON_SEGMENT = اطلاعات موزيک فعلي ( شروع پخش ، پايان پخش و غيره ) DMUS_NOTIFY_ON_CHORD = اطلاعات تغيير chord موزيک DMUS_NOTIFY_ON_COMMAND = زمانيکه يک event فرماني صدا زده شود . DMUS_NOTIFY_ON_MEASUREANDBEAT = اطلاعات beat/measure مربوط به موزيک فعلي DMUS_NOTIFY_ON_PERFORMANCE = که event مربوط به سطح performance می باشد . DMUS_NOTIFY_ON_RECOMPOSE = که recomposition event می باشد . آخرين بخش از پيغام دهي ، تابع اصلي آن مي باشد . همانطور که در بخش Initial کردن DirectMusic ديديد يک توصيف بصورت Implements DirectXEvent8 داشتيم . بخش اصلي تابع callback مربوط به DirectXEvent8 ، شامل يک select case است که بين پيغامهاي مختلف سوئيچ می کند : Private Sub DirectXEvent8_DXCallback(ByVal eventid As Long)x If eventid = hEvent Then Dim dmMSG As DMUS_NOTIFICATION_PMSG If Not oDMPerf.GetNotificationPMSG(dmMSG) Then Else Select Case dmMSG.lNotificationOption Case DMUS_NOTIFICATION_SEGABORT Case DMUS_NOTIFICATION_SEGALMOSTEND Case DMUS_NOTIFICATION_SEGEND Case DMUS_NOTIFICATION_SEGLOOP Case DMUS_NOTIFICATION_SEGSTART Case Else End Select End If End If End Sub پخش موزيک / متوقف کردن موزيک : براي پخش يک موزيک ابتدا بايستي آنرا load کنيد . اينکار توسط کد زير انجام مي شود : oDMLoader.SetSearchDirectory App.Path & "\"x Set oDMSeg = oDMLoader.LoadSegment(App.Path & FILENAME)oDMSeg.SetStandardMidiFile DirectMusic تنها چهار نوع فرمت صوتي را مي پذيرد : WAV ، MID ، RMI و SEG . براي پخش فايلهاي MP3 بايستي از DirectXShow استفاده کنيد که آنرا در درسهاي بعدي خواهيد ديد . اکنون که داده هاي فايل صوتي درون بافر load شد مي توانيد آنرا پخش کنيد : oDMSeg.SetRepeats 0 oDMPerf.PlaySegmentEx oDMSeg, DMUS_SEGF_DEFAULT, 0 تعداد پخش شدن فايل را با متد SetRepets تنظيم کنيد . اگر اين مقدار صفر باشد ، آهنگ تنها يکبار پخش مي شود و اگر 1- باشد بطور ممتد پخش خواهد شد . براي متوقف کردن موزيک از کد زير استفاده کنيد : oDMPerf.StopEx oDMSeg, 0, DMUS_SEGF_DEFAULT براي تنظيم ميزان صدا از متد SetMasterVolume استقاده کنيد : oDMPerf.SetMasterVolume yourvalue رنج صدا بين 20+ دسی بل تا 200- دسي بل است . براي تنظيم Tempo از متد SetMasterTempo استفاده کنيد : oDMPerf.SetMasterTempo yourvalue/ 100 بطور نرمال tempo برابر 1 مي باشد . عدد 2 سرعت را دو برابر مي کند و عدد 0 موزيک را قطع مي کند . موضوع : ايجاد صدای سه بعدی توسط DirectSound3D مقدمه تاکنون با چگونگي پخش افکتهاي صوتي و موسيقي پس زمينه توسط DirectXAudiuo آشنا شديد . اين مطالب براي کاربردهاي ساده مناسبند اما اينکه فقط ما صداي استريو داشته باشيم کافي نيست و در کاربردهاب حرفه اي بايستي از صداهاي کاملاً سه بعدي استفاده کنيم . با استفاده از افکتهاي صوتي سه بعدي مي توانيم صدا را در تمام جهتها براي کاربر شبيه سازي کنيم اما با همه مزاياي صداي سه بعدي ، دو اشکال براي آن وجود دارد : اول اينکه پخش صداي سه بعدي پيچيده تر از پخش صداي عادي است و تنها کارت هاي سخت افزاري جديد بطور کاملاً واقعي از آن پشتيباني مي کنند و دوم اينکه صداي سه بعدي با 4 بلندگو يا بيشتر حاصل مي شود – کيفيت حالت 2 بلندگو بد نيست اما در مقايسه با حالت 4 بلندگو ، بسيار کيفيت صداي سه بعدي پايين است . برپاسازي DirectSound3D برپاسازي صداي سه بعدي چندان پيچيده نيست اما هر بافر صوتي که براي يک صداي سه بعدي مي سازيد ، يک overhead را به سيستم تان اضافه مي کند . همچنين برخي درايورها هستند که تنها اجازه ايجاد تعداد محدودي بافر سه بعدي را در يک لحظه مي دهند و نيز اغلب درايورها تعداد بافرهاي سه بعدي که مي توان در يک لحظه پخش کرد را محدود مي کنند ( معمولاً 8 تا 16 بافر ) . اولين قدم در استفاده از صداي سه بعدي تعريف متغيرها و اشيا زير است : Dim DSBuffer As DirectSoundSecondaryBuffer8 Dim DSBuffer3D As DirectSound3DBuffer8 Dim DSBListener As DirectSound3DListener8 تنها دو شي آخر براي شما جديد هستند . شي DirectSound3dBuffer8 يک ارائه سه بعدي از بافرهاي عادي است . ما همچنان از DirectSoundSecondaryBuffer8 براي نگهداري داده صوتي استفاده مي کنيم و از DirectSound3Dbuffer8 براي نگهداري پارامترهاي سه بعدي و تنظيمات سه بعدي استفاده مي کنيم . شي DirectSound3Dlistener8 نيز يک listener است و براي تنظيم کردن سرعت و جهت صدا و برخي پارامترهاي ديگر استفاده مي شود . مرحله دوم ، ساخت بافر صوتي است . اين کار در دو بخش انجام مي شود . اول ما يک بافر صوتي نرمال مي سازيم و سپس يک واسط بافر صوتي سه بعدي را از آن بدست مي آوريم : If Not (DSBuffer Is Nothing) Then DSBuffer.Stop Set DSBuffer = Nothing DSBDesc.lFlags = DSBCAPS_CTRL3D Or DSBCAPS_CTRLVOLUME Set DSBuffer = DS.CreateSoundBufferFromFile(App.Path & "\blip.wav", DSBDesc)x If DSBDesc.fxFormat.nChannels > 1 Then MsgBox "You can only use mono (1 channel) sounds with DirectSound3D"x End If If optLow.Value Then DSBDesc.guid3DAlgorithm = GUID_DS3DALG_NO_VIRTUALIZATION If optMedium.Value Then DSBDesc.guid3DAlgorithm = GUID_DS3DALG_HRTF_LIGHT If optHigh.Value Then DSBDesc.guid3DAlgorithm = GUID_DS3DALG_HRTF_FULL Set DSBuffer = DS.CreateSoundBufferFromFile(App.Path & "\blip.wav", DSBDesc)x Set DSBuffer3D = DSBuffer.GetDirectSound3DBuffer()x سه نکته است که بايد به آن دقت شود : 1 – اضافه کردن DSBCAPS_CTRL3D بسيار مهم است . شما اگر اين پارامتر را بکار نبريد ، قادر نخواهيد بود که واسط سه بعدي را بدست آوريد . 2 – ما بايستي تنها از افکتهاي صوتي Mono ( تک کاناله ) استفاده کنيم زيرا افکت صوتي استريو در صداي سه بعدي معنا ندارد زيرا صدا از يک نقطه در فضاي سه بعدي مي آيد . 3 – سطح الگوريتم سه بعدي – که در پارامتر DSBDesc.guid3Dalgorhthm آمده . حالت NO VIRTULIZATION تنها از CPU استفاده مي کند و روي تمام سيستم ها کار مي کند اما افکتها مينيمم هستند . حالت HRTF LIGHT هم از CPU و هم سخت افزار کارت صوتي استفاده مي کند و کيفيت بهتري را نسبت به خالت اول ارائه مي دهد . حالت HRTF FULL بهترين حالت است اما در صورتي درست کار مي کند که يک سخت افزار سه بعدي داشته باشيد . آخرين پارامتري که بايد تنظيم کنيم شي listener است : DSBDesc_2.lFlags = DSBCAPS_CTRL3D Or DSBCAPS_PRIMARYBUFFER Set DSBPrimary = DS.CreatePrimarySoundBuffer(DSBDesc_2) x Set DSBListener = DSBPrimary.GetDirectSound3Dlistener DSBListener.SetOrientation 0#, 0#, 1#, 0#, 1#, 0#, DS3D_IMMEDIATE تا اينجا صداي سه بعدي ما آماده است و مي توانيم برخي پخش بافر را مشابه درسهاي قبلي شروع کنيد . پارامترهاي اختياري : چند پارامتر وجود دارد که مي توان آنها را تغيير داد : 1 – Volume : عدد 0 بيشترين ميزان صدا و عدد 3000 - کمترين ميزان صدا را دارد : If DSBuffer Is Nothing Then Exit Sub DSBuffer.SetVolume scrlVolume.Value 2 – Position : تنظيم محل listener : DSBuffer3D.SetPosition Src_X, 0, Src_Y, DS3D_IMMEDIATE DSBListener.SetPosition Src_X, 0, Src_Y, DS3D_IMMEDIATE 3 – Velocity : تنظيم سرعت و جهت منبع صدا : DSBuffer3D.SetVelocity X, Y, Z, DS3D_IMMEDIATE DSBListener.SetVelocity X, Y, Z, DS3D_IMMEDIATE 4 – Dppler Effect : انحراف صدا از مسيري که مي پيمايد انحراف سرعت حرکت صدا : DSBListener.SetDopplerFactor CSng(scrlDoppler.Value), DS3D_IMMEDIATE 5 – Rolloff Effect : rolloff چگونگي تضعيف صدا با تغيير فاصله است DSBListener.SetRolloffFactor CSng(scrlRolloff.Value), DS3D_IMMEDIATE 6 – Distance : ماکزيمم فاصله اي که يک صدا مي تواند شنيده شود : DSBuffer3D.SetMaxDistance 250, DS3D_IMMEDIATE DSBuffer3D.SetMinDistance 0.01, DS3D_IMMEDIATE رجيستري چيست ؟ سيستم عامل ويندوز تنظيمات سخت افزاري و نرم افزاري خود را بطور مرکزي در يک بانک اطلاعاتي با ساختار سلسله مراتبي ذخيره مي کند که رجيستري نام دارد . رجيستري جايگزيني براي بسياري از فايلهاي پيکربندي INI ، SYS و COM است که در نسخه هاي اوليه ويندوز موجود بود . رجيستري ، سيستم عامل را با مهيا کردن اطلاعات موردنيز براي اجراي برنامه ها و load شدن component ها ، کنترل مي کند . رجيستري شامل انواع مختلفي از اطلاعات مي باشد مثل : - اطلاعات سخت افزارهاي نصب شده روي سيستم - اطلاعات درايورهاي نصب شده روي سيستم - اطلاعات برنامه هاي نصب شده روي سيستم - اطلاعات پروتکلهاي شبکه اي مورد استفاده در سيستم ساختار رجيستري شامل چندين مجموعه رکورد است که داده هاي اين رکوردها توسط بسياري از برنامه ها و اجزاي سيستم عامل خوانده و يا نوشته مي شود . اجزاي رجيستري اجزاي تشکيل دهنده رجيستري عبارتند از : 1 – subtree : Subtree ها همانند folder هاي موجود در ريشه يک درايو هارد هستند . رجستری ويندوز داراي پنج subtree مي باشد : - HKEY_LOCAL_MACHINE : شامل تمام داده هاي پيکربندي براي کامپيوتر مي باشد و شامل 5 key است :Hardware ، SAM ، Security ، Software و System - HKEY_USERS : شامل داده هاي مربوط به تنظيمات سيستم عامل براي هر user است مثل تنظيمات desktop و محيط ويندوز - HKEY_CURRENT_USER : شامل داده هاي کاربر فعلي سيستم - HKEY_CLASSES_ROOT : شامل اطلاعات پيکربندي نرم افزار است مثل داده هاي OLE و داده هاي کلاسهاي متناظر با فايل - HKEY_CURRENT_CONFIG : شامل اطلاعات مورد نياز براي تنظيمات داريورهاي سخت افزاري و غيره 2 – Key : key ها همانند folder ها و subfolder هاي روي هارد هستند . هر key متناظر با object هاي نرم افزاري يا سخت افزاري مي باشد . subkey ها key هايي هستند که درون يکسري key قراردارند . 3 – Entry : هر key داراي يک يا چند entry است . هر entry داراي سه بخش مي باشد : - نام Name - نوع داده اي Data Type : مقدار هر entry يکي از انواع داده هاي زير است : REG_DWORD ، REG_SZ ، REG_EXPAND_SZ ، REG_BINARY ، REG_MULTI_SZ ، REG_FULL_RESOURCE_DESCRIPTOT - مقدار Value نکته 1 : براي مشاهده رجيستري و اعمال تغييرات در آن ( لطفاً اگر هيچ تجربه اي در تنظيم کردن رجيستري نداريد اطلاعات آنرا تغيير ندهيد ) ، مي توانيد از برنامه regedit.exe و يا regedt32.exe موجود در ويندوز استفاده کنيد . براي اينکار کافيست نام برنامه را در کادر Run وارد کنيد . براي کار با رجيستري در ويژوال بيسيک کلاس Registery.bas را مطابق مطالب زير ايجاد کرده و در پروژه هاي خود از آن استفاده کنيد : 1 - تعريف ثابتهاي مورد نياز : براي نوشتن اين کلاس نياز به تعريف چهار دسته ثابت داريم : - ثابتهاي مربوط به تعريف data type هاي entry هاي رجيستري : Global Const REG_SZ As Long = 1 Global Const REG_DWORD As Long = 4 - ثابتهاي مربوط به تعريف key هاي رجيستري Global Const HKEY_CLASSES_ROOT = &H80000000 Global Const HKEY_CURRENT_USER = &H80000001 Global Const HKEY_LOCAL_MACHINE = &H80000002 Global Const HKEY_USERS = &H80000003 - ثابتهاي مربوط به خطاهاي کار با رجيستري Global Const ERROR_NONE = 0 Global Const ERROR_BADDB = 1 Global Const ERROR_BADKEY = 2 Global Const ERROR_CANTOPEN = 3 Global Const ERROR_CANTREAD = 4 Global Const ERROR_CANTWRITE = 5 Global Const ERROR_OUTOFMEMORY = 6 Global Const ERROR_INVALID_PARAMETER = 7 Global Const ERROR_ACCESS_DENIED = 8 Global Const ERROR_INVALID_PARAMETERS = 87 Global Const ERROR_NO_MORE_ITEMS = 259 - ثابتهاي متفرقه Global Const KEY_ALL_ACCESS = &H3F Global Const REG_OPTION_NON_VOLATILE = 0 2 - Declare کردن Api هاي مورد نياز : براي کار با رجيستري از توابع کتابخانه Advapi32.dll استفاده مي کنيم . اين توابع عبارتند از : - تابع RegCloseKey : آزاد کردن handle مربوط به يک key Declare Function RegCloseKey Lib "advapi32.dll" (ByVal hKey As Long) As Long - تابع RegCreateKeyEx : ساخت يک key در رجيستري ( اگر key قبلاً وجود داشته باشد ، اين تابع آنرا باز مي کند ) : Declare Function RegCreateKeyEx Lib "advapi32.dll" Alias "RegCreateKeyExA" (ByVal hKey As Long, ByVal lpSubKey As String, ByVal Reserved As Long, ByVal lpClass As String, ByVal dwOptions As Long, ByVal samDesired As Long, ByVal lpSecurityAttributes As Long, phkResult As Long, lpdwDisposition As Long) As Long - تابع RegOpenKeyEx : باز کردن يک key Declare Function RegOpenKeyEx Lib "advapi32.dll" Alias "RegOpenKeyExA" (ByVal hKey As Long, ByVal lpSubKey As String, ByVal ulOptions As Long, ByVal samDesired As Long, phkResult As Long) As Long - تابع RegQueryValueExLong : استخراج type و data ي يک نام متناظر با يک key باز شده Declare Function RegQueryValueExString Lib "advapi32.dll" Alias "RegQueryValueExA" (ByVal hKey As Long, ByVal lpValueName As String, ByVal lpReserved As Long, lpType As Long, ByVal lpData As String, lpcbData As Long) As Long Declare Function RegQueryValueExLong Lib "advapi32.dll" Alias "RegQueryValueExA" (ByVal hKey As Long, ByVal lpValueName As String, ByVal lpReserved As Long, lpType As Long, lpData As Long, lpcbData As Long) As Long Declare Function RegQueryValueExNULL Lib "advapi32.dll" Alias "RegQueryValueExA" (ByVal hKey As Long, ByVal lpValueName As String, ByVal lpReserved As Long, lpType As Long, ByVal lpData As Long, lpcbData As Long) As Long - تابع RegSetValueEx : ذخيره يک مقدار در فيلد value يک کليد باز Declare Function RegSetValueExString Lib "advapi32.dll" Alias "RegSetValueExA" (ByVal hKey As Long, ByVal lpValueName As String, ByVal Reserved As Long, ByVal dwType As Long, ByVal lpValue As String, ByVal cbData As Long) As Long Declare Function RegSetValueExLong Lib "advapi32.dll" Alias "RegSetValueExA" (ByVal hKey As Long, ByVal lpValueName As String, ByVal Reserved As Long, ByVal dwType As Long, lpValue As Long, ByVal cbData As Long) As Long - تابع RegDeleteKey : پاک کردن يک کليد و کليه اطلاعات مرتبط با آن Private Declare Function RegDeleteKey& Lib "advapi32.dll" Alias "RegDeleteKeyA" (ByVal hKey As Long, ByVal lpSubKey As String) - تابع RegDeleteValue : حذف مقدار يک key Private Declare Function RegDeleteValue& Lib "advapi32.dll" Alias "RegDeleteValueA" (ByVal hKey As Long, ByVal lpValueName As String) 3 - توابع کمکي : براي نوشتن توابع اصلي کار با رجيستري نياز به نوشتن توابع کمکي زير است : - تابع SetValueEx : با توجه به نوع داده يک کليد ، مقدار موجود در آنرا در يک متغير ذخيره مي کند : Public Function SetValueEx(ByVal hKey As Long, sValueName As String, lType As Long, vValue As Variant) As Long Dim lValue As Long Dim sValue As String Select Case lType Case REG_SZ ' type of value is string sValue = vValue SetValueEx = RegSetValueExString(hKey, sValueName, 0&, lType, sValue, Len(sValue))x Case REG_DWORD ' type of value is Double word lValue = vValue SetValueEx = RegSetValueExLong(hKey, sValueName, 0&, lType, lValue, 4)x End Select End Function - تابع QueryValueEx : سايز و نوع داده اي يک داده را که بايد خوانده شود مشخص مي کند . Function QueryValueEx(ByVal lhKey As Long, ByVal szValueName As String, vValue As Variant) As Long Dim cch As Long Dim lrc As Long Dim lType As Long Dim lValue As Long Dim sValue As String lrc = RegQueryValueExNULL(lhKey, szValueName, 0&, lType, 0&, cch)x Select Case lType ' For strings Case REG_SZ: sValue = String(cch, 0)x lrc = RegQueryValueExString(lhKey, szValueName, 0&, lType, sValue, cch)x If lrc = ERROR_NONE Then vValue = Left$(sValue, cch)x Else vValue = Empty End If ' For DWORDS Case REG_DWORD: lrc = RegQueryValueExLong(lhKey, szValueName, 0&, lType, lValue, cch)x If lrc = ERROR_NONE Then vValue = lValue Case Else 'all other data types not supported lrc = -1 End Select QueryValueExExit: QueryValueEx = lrc Exit Function QueryValueExError: Resume QueryValueExExit End Function 4 - توابع اصلي : توابع مربوط به پاک کردن يک کليد از رجيستري ، ساخت يک کليد جديد در رجيستري و مقداردهي به يک کليد : - تابع DeleteKey : اين تابع يک کليد از رجيستري را حذف مي کند . داراي دو پارامتر ورودي است : Location که يکي از مقادير HKEY_CLASSES_ROOT ، HKEY_CURRENT_USER ، HKEY_LOCAL_MACHINE و يا HKEY_USERS است . KeyName که نام کليدي است که بايد از رجيستري حذف شود . اين کليد ممکنست شامل subkey هايي نيز باشد مثلاً Key1\SubKey1 Public Function DeleteKey(lPredefinedKey As Long, sKeyName As String)x Dim lRetVal As Long lRetVal = RegDeleteKey(lPredefinedKey, sKeyName)x DeleteKey = lRetVal ' return function value End Function - تابع DeleteValue : اين تابع يک entry را از کليد حذف مي کند . داراي سه پارامتر ورودي است : Location ، KeyName و ValueName که نام آن value را مشخص مي کند . Public Function DeleteValue(lPredefinedKey As Long, sKeyName As String, sValueName As String)x Dim lRetVal As Long Dim hKey As Long lRetVal = RegOpenKeyEx(lPredefinedKey, sKeyName, 0, KEY_ALL_ACCESS, hKey)x lRetVal = RegDeleteValue(hKey, sValueName)x RegCloseKey (hKey)x DeleteValue = lRetVal End Function - تابع CreateNewKey : اين تابع يک کليد جديد ايجاد مي کند . داراي دو پارامتر ورودي است : Location و KeyName Public Function CreateNewKey(lPredefinedKey As Long, sNewKeyName As String)x Dim hNewKey As Long Dim lRetVal As Long lRetVal = RegCreateKeyEx(lPredefinedKey, sNewKeyName, 0&, vbNullString, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, 0&, hNewKey, lRetVal)x RegCloseKey (hNewKey)x CreateNewKey = lRetVal End Function - تابع SetKeyValue : اين تابع پارامتر data يک entry را تنظيم مي کند . داراي 5 پارامتر ورودي است : Location ، KeyName ، ValueName ، ValueSetting و ValueType Public Function SetKeyValue(lPredefinedKey As Long, sKeyName As String, sValueName As String, vValueSetting As Variant, lValueType As Long)x Dim lRetVal As Long Dim hKey As Long lRetVal = RegOpenKeyEx(lPredefinedKey, sKeyName, 0, KEY_ALL_ACCESS, hKey)x lRetVal = SetValueEx(hKey, sValueName, lValueType, vValueSetting)x RegCloseKey (hKey)x SetKeyValue = lRetVal End Function - تابع QueryValue : اين تابع فيلد داده يک entry را برمي گرداند . داراي سه پارامتر ورودي است : Location ، KeyName و ValueName Public Function QueryValue(lPredefinedKey As Long, sKeyName As String, sValueName As String)x Dim lRetVal As Long Dim hKey As Long Dim vValue As Variant lRetVal = RegOpenKeyEx(lPredefinedKey, sKeyName, 0, KEY_ALL_ACCESS, hKey)x lRetVal = QueryValueEx(hKey, sValueName, vValue)x QueryValue = vValue RegCloseKey (hKey)x End Function ساخت يک انتصاب فايل يا File Association به يک برنامه در اين درس می خواهم با استفاده از کلاسی که در درس قبل معرفی شد تابعی بسازيم که توسط آن بتوانيم فايلهای با پسوندی مشخص را به يک برنامه اختصاص دهيم . بعبارت ديگر تابعی بنويسيم که اطلاعات لازم برای باز شدن فايلهايی با پسوند xxx را توسط برنامه MyApp.exe در رجيستری ثبت کند . Public Sub CreateAssociation(sExtension As String, sApplication As String, sAppPath As String)x Dim sPath, sAppExe As String CreateNewKey "." & sExtension, HKEY_CLASSES_ROOT SetKeyValue HKEY_CLASSES_ROOT, "." & sExtension, "", sApplication & ".Document", REG_SZ CreateNewKey sApplication & ".Document\shell\open\command", HKEY_CLASSES_ROOT SetKeyValue HKEY_CLASSES_ROOT, sApplication & ".Document", "", sApplication & " Document", REG_SZ sPath = sAppPath & " %1"x sAppExe = sApplication & ".exe"x SetKeyValue HKEY_CLASSES_ROOT, sApplication& ".Document\shell\open\command", "", sPath, REG_SZ CreateNewKey "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\." & sExtension, HKEY_CURRENT_USER SetKeyValue HKEY_CURRENT_USER, "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\." & sExtension, "Application", sAppExe, REG_SZ CreateNewKey "Applications\" & sAppExe & "\shell\open\command", HKEY_CLASSES_ROOT SetKeyValue HKEY_CLASSES_ROOT, "Applications\" & sAppExe & "\shell\open\command", "", sPath, REG_SZ End Sub کاربرد اين تابع بصورت زير است : CreateAssociation("xxx","MyApp","c:\MyApp.exe")x اجرا شدن يک برنامه در هنگام راه اندازی سيستم فرض کنيد می خواهيم برنامه ای بنويسيم که هر بار در هنگام راه اندازي سيستم بطور خودكار اجرا شود. البته نمي خواهم در startup ويندوز ديده شود . براي اين كار بايد برنامه موردنظر را در StartUp رجيستري قرار دهيم . به اين ترتيب كه در يكي از كليدهاي زير يك مقدار رشته اي جديد(String Value) ايجاد کنيم و آدرس برنامه را در آن وارد كنيم : HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Run HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run براي مثال اگه اسم برنامه مورد نظر MyApp و مسيرش C:\Windows\MyApp.exe است بايد بصورت زير عمل کرد : SetKeyValue HKEY_LOCAL_MACHINE, "SOFTWARE\Microsoft\Windows\CurrentVersion\Run", "MyApp", "C:\MyApp.exe", REG_SZ نکته : البته دو تا راه ديگر برای اينکار وجود دارد که برخی تروجان ها هم از اين روشها استفاده می کنند تا روی سيستم باقی بمانند : يكي استفاده از win.ini و نوشتن نام فايل جلوي = run و ديگري استفاده از system.ini و نوشتن نام برنامه جلوي خط explorer.exe . آشنايي با Windows API قصد دارم در مورد API هاي ويندوز و چگونگي استفاده از آنها در ويژوال بيسيک بطور خلاصه توضيح دهم و همچنين دو مثال پراستفاده را نيز در اين زمينه بيان کنم که عبارتند از چگونگي پخش فايلهاي Wav و ساخت يک تايمر با دقت بالا : ۱ - آشنايي با Windows API : واژه API مخفف Application Programming Interface مي باشد . API هاي ويندوز مجموعه اي از توابع از پيش آماده موجود در سيستم عامل هستند که شما مي توانيد آنها را در برنامه هاي خود فراخواني کنيد . اين توابع در چندين کتابخانه DLL ويندوز ذخيره شده اند . براي دسترسي به اين توابع در ويژوال بيسيک ابتدا بايد آنها را برنامه خود declare کنيد . براي مثال : Declare Function sndPlaySound Lib "winmm.dll" Alias "sndPlaySoundA" (ByVal lpszSoundName As String, ByVal uFlags As Long) As Long همانطور که مي بينيد مثال فوق يک Declare از تابع sndPlaySound مي باشد که اين تابع در کتابخانه Winmm.dll موجود است . کلمه Alias نشان مي دهد که اين تابع نام ديگري در dll دارد . ساير بخشها مربوط به تعريف پارامترهاي تابع مي باشند که در مورد مثال فوق ، اين تابع دو پارامتر ورودي و يک خروجي از نوع Long دارد . پس از Delare کردن API در برنامه مي توانيد از آن استفاده نمائيد . ۲ - پخش فايلهاي Wav : تابعي که براي پخش فايلهاي Wav استفاده مي شود تابع sndPlaySound است که در بالا با آن آشنا شديد . پارامتر lpzSoundName نام و مسير فايل Wavو پارامتر uFlags چگونگي پخش فايل را مشخص مي کند . مقادير ممکن اين پارامتر عبارتند از : - SND_ASYNC : اجازه مي دهد طوري فايل Wav پخش شود که آنرا بتوان وقفه داد . بعبارت ديگر قادر خواهيد بود فايل Wav تان را هر زمان که بخواهيد پخش کنيد و مطمئن باشيد که حتماً شنيده مي شود . - SND_LOOP : فايل Wav را بطور ممتد پخش مي کند . - SND_NODEFAULT : اگر فايل Wav پيدا نشود صداي ديگري پخش نخواهد شد ( مثلاً برخي صداهاي default ويندوز ) - SND_SYNC : در طول پخش فايل Wav کنترل به برنامه داده نمي شود . اين پارامتر در زمانيکه مي خواهيد فايل Wav اي را در پس زمينه برنامه تان پخش کنيد مناسب نمي باشد . - SND_NOSTOP : اگر فايل Wav اي قبلاً در حال پخش باشد ، فايل Wav شما آنرا دچار وقفه نمي کند . از اين پارامتر زماني استفاده مي شود که بخواهيم فايل Wav مان هيچوقت در وسط کار قطع نشود . اگر بخواهيد از بيش از يکي از اين پارامترها استفاده کنيد توسط Or آنها را ترکيب نمائيد مثال : sndPlaySound App.path & "\ding.wav", SND_ASYNC or SND_LOOP نکته : براي استفاده از توابع صوتي پيچيده تر بايستي از DirectSound که يکي از اجزاي DirectX مي باشد استفاده کنيد . در مورد DirectSound بعداً صحبت خواهم کرد . ۳ - ساخت يک تايمر با دقت بالا : شايد تا بحال از کنترل تايمر موجود در نوار ابزار ويژوال بيسيک استفاده کرده باشيد . اين تايمر داراي دقت حدود ۵۵ ميلي ثانيه است . براي دستيابي به زمانهاي با دقت بالاتر اين کنترل مفيد نخواهد بود . تابع GetTickCount يک API موجود در کتابخانه Kernel32.dll است . اين تابع طول زماني را که سيستم شروع به کار کرده است را برحسب ميلي ثانيه برمي گرداند : Private Declare Function GetTickCount Lib "kernel32" () As Long براي بررسي طي شدن يک مدت زماني خاص شما ابتدا بايد مقدار اين تابع را در يک متغير کمکي مثل TempTime قرار دهيد سپس در يک حلقه Do-Loop بايد اختلاف زمان GetTickCount جديد و زمان TempTime را با مقدار زماني که مي خواهيد سپري شود مقايسه کنيد : TempTime = GetTickCount()x Do While DesiredTime < GetTickCount() - TempTime Do some things' Loop توسط کد بالا مي توان يک عمليات خاص را براي يک مدت زماني مشخص اجرا کرد . کد زير نشان مي دهد که چگونه مي توان دستورات خاصي را در فواصل زماني خاص اجرار کرد : ExitFunction = False TempTime = GetTickCount()x Do While not(ExitFunction)x If DesiredTime < GetTickCount() - TempTime then Reset the temporary variable' TempTime = GetTickCount()x Do some things' End If Loop همچنين از تابع GetTickCount مي توان براي benchmark برنامه ها استفاده کرد . بعبارت ديگر مي توان زمان اجراي يکسري دستورات خاص را بدست آورد مباحث پيشرفته Direct3D موضوع : ساخت يک موتور گرافيکي سه بعدي قبل از شروع مباحث جديد برنامه نويسي Direct3D ، با هم مروري بر مباحث قبلي خواهيم داشت .( مباحث قبلي در آرشيو موجود مي باشند در اين درس با استفاده از مطالب قبلي يک Engine سه بعدي ساخته و از امکانات آن در يک برنامه نمونه استفاده خواهيم کرد . اين engine داراي دو کلاس است : 1 – کلاس MainD3D 2 – کلاس D3Dobject در کلاس MainD3D متغيرها و توابع لازم براي ساخت يک device سه بعدي ، تنظيمات ماتريسي ، تابع رندر و غيره موجود مي باشد . متغيرهاي عمومي اين کلاس عبارتند از : Public g_DX As New DirectX8 Public g_D3D As Direct3D8 Public g_D3DX As New D3DX8 Public g_D3DDevice As Direct3DDevice8 Public NTextures As Long روتين ها و توابع اين کلاس عبارتند از : 1 - InitD3D : اين روتين ، اشيا D3D و D3Ddevice را مي سازد و پارامترهاي آنها را تنظيم مي کند . 2 – ApplyCameraChanges : روتين ايجاد ماتريس View 3 – SetupMatrices : روتين ايجاد ماتريس Projection 4 – StartRender : در اين روتين عمليات لازم براي شروع عمل رندر صورت مي گيرد . 5 – RenderObject : اين تابع ، يک شي سه بعدي از نوع کلاس D3Dobject را مي گيرد و بردارهاي مورد نياز و نيز بافت شي را تنظيم مي کند و در پايان شي را ترسيم مي کند . 6 – FinishRender : در اين روتين به عمليات رندر پايان داده مي شود . 7 – Cleanup: روتين از بين بردن اشيا Direct3D 8 – CreateVector : تابع ساخت يک بردار سه بعدي 9 – CreateTextures : روتين ساخت يک بافت جديد 10 – InitTexture: تابع مقداردهي به يک بافت در کلاس D3Dobject متغيرها و توابع لازم براي ايجاد يک شي سه بعدي و اختصاص بافت به آن موجود مي باشد . در اين کلاس دو type عمومي تعريف شده است : 1 - NormalVERTEX 2 - TeturedVERTEX همچنين روتين ها و توابع اين کلاس عبارتند از : 1 – InitObject : تابعي که تنظيمات اوليه vertex ها و بافت شي را انجام مي دهد . 2 – Vertex : روتين ايجاد vertex هاي مورد نياز 3 – GetRenderingMode: تابعي که مد رندر را مشخص مي کند . و نيز يکسري تابع ساخت vertex نرمال و ساخت vertex داراي بافت و غيره اين دو کلاس در يک پروژه ويژوال بيسيک قرارداده شده و پروژه با نام D3Dengine.dll کامپايل شده است . حال با استفاده از اين engine مي خواهيم يک منظره سه بعدي را ايجاد کنيم : اين منظره شامل سه object است : ديوار ، آسمان و زمين. ابتدا بايد يک شي از کلاس MainD3D تعريف کنيم : Dim D3D8Main As MainD3D8 در متد Form Load نيز سه شي Floor ، Sky و Wall را بصورت زير تعريف مي کنيم : Dim Floor As D3DObject Dim Sky As D3DObject Dim Walls As D3Dobject سپس اين سه شي را به اضافه شي D3D8Main ، ايجاد مي کنيم : Set D3D8Main = New D3DEngine.MainD3D8 Set Floor = New D3DEngine.D3DObject Set Sky = New D3DEngine.D3DObject Set Walls = New D3DEngine.D3Dobject در ابتدا شي MainD3D را Initial مي کنيم و سپس بافتهاي مورد نيز خود را مي سازيم : D3D8Main.InitD3D True, Me.hWnd D3D8Main.CreateTextures 3 D3D8Main.InitTexture 1, App.Path + "\floor.jpg" D3D8Main.InitTexture 2, App.Path + "\sky.bmp" D3D8Main.InitTexture 3, App.Path + "\wall.bmp" حال به سراغ ايجاد و مقداردهي vertex هاي floor مي رويم . floor شامل شش vertex مي باشد و بنابراين دو face مثلثي دارد : Floor.InitObject 6, 2, TriangleList, True, 1 Floor.Vertex 0, -55, -2, -55, vbWhite, 0, 10 Floor.Vertex 1, 55, -2, -55, vbWhite, 10, 10 Floor.Vertex 2, 55, -2, 55, vbWhite, 10, 0 Floor.Vertex 3, -55, -2, -55, vbWhite, 0, 10 Floor.Vertex 4, 55, -2, 55, vbWhite, 10, 0 Floor.Vertex 5, -55, -2, 55, vbWhite, 0, 0 سپس به سراغ ايجاد و مقداردهي vertex هاي wall مي رويم . wall شامل بيست و چهار vertex مي باشد و بنابراين هشت face مثلثي دارد : Walls.InitObject 24, 8, TriangleList, True, 3 Walls.Vertex 0, -55, -2, -55, &HBCE8FC, 0, 1 Walls.Vertex 1, 55, -2, -55, &HBCE8FC, 5, 1 Walls.Vertex 2, 55, 8, -55, &HBCE8FC, 5, 0 Walls.Vertex 3, -55, -2, -55, &HBCE8FC, 0, 1 Walls.Vertex 4, 55, 8, -55, &HBCE8FC, 5, 0 Walls.Vertex 5, -55, 8, -55, &HBCE8FC, 0, 0 Walls.Vertex 6, -55, -2, 55, &HBCE8FC, 0, 1 Walls.Vertex 7, 55, -2, 55, &HBCE8FC, 5, 1 Walls.Vertex 8, 55, 8, 55, &HBCE8FC, 5, 0 Walls.Vertex 9, -55, -2, 55, &HBCE8FC, 0, 1 Walls.Vertex 10, 55, 8, 55, &HBCE8FC, 5, 0 Walls.Vertex 11, -55, 8, 55, &HBCE8FC, 0, 0 Walls.Vertex 12, -55, -2, 55, &HBCE8FC, 0, 1 Walls.Vertex 13, -55, -2, -55, &HBCE8FC, 5, 1 Walls.Vertex 14, -55, 8, -55, &HBCE8FC, 5, 0 Walls.Vertex 15, -55, -2, 55, &HBCE8FC, 0, 1 Walls.Vertex 16, -55, 8, -55, &HBCE8FC, 5, 0 Walls.Vertex 17, -55, 8, 55, &HBCE8FC, 0, 0 Walls.Vertex 18, 55, -2, 55, &HBCE8FC, 0, 1 Walls.Vertex 19, 55, -2, -55, &HBCE8FC, 5, 1 Walls.Vertex 20, 55, 8, -55, &HBCE8FC, 5, 0 Walls.Vertex 21, 55, -2, 55, &HBCE8FC, 0, 1 Walls.Vertex 22, 55, 8, -55, &HBCE8FC, 5, 0 Walls.Vertex 23, 55, 8, 55, &HBCE8FC, 0, 0 حال به سراغ ايجاد و مقداردهي vertex هاي sky مي رويم . sky شامل شش vertex مي باشد و بنابراين دو face مثلثي دارد : Sky.InitObject 6, 2, TriangleList, True, 2 Sky.Vertex 0, -55, 8, -55, &HBCE8FC, 0, 1 Sky.Vertex 1, 55, 8, -55, &HBCE8FC, 0, 1 Sky.Vertex 2, 55, 8, 55, &HBCE8FC, 0, 1 Sky.Vertex 3, -55, 8, -55, &HBCE8FC, 0, 1 Sky.Vertex 4, 55, 8, 55, &HBCE8FC, 0, 1 Sky.Vertex 5, -55, 8, 55, &HBCE8FC, 0, 1 در پايان تابع رندر را صدا مي کنيم . البته در هر بار عمل رندر کردن ، دوربين يک درجه در صفحه X-Z دوران مي کند تا کل ديوار قابل مشاهده باشد : Dim Angle As Double PI = 3.1415 Angle = 0 Do DoEvents D3D8Main.StartRender vbBlack D3D8Main.RenderObject Sky D3D8Main.RenderObject Floor D3D8Main.RenderObject Walls D3D8Main.FinishRender If Sqr(Angle ^ 2) = 360 Then Angle = 0 Angle = Angle + 1 D3D8Main.CamLookAtX = Sin((Angle * 2 * PI) / 360) D3D8Main.CamLookAtZ = Cos((Angle * 2 * PI) / 360) D3D8Main.ApplyCameraChanges Loop موضوع : استفاده از object هاي 3D Studio Max در Direct3D تا بحال ما هر شيي را که مي خواستيم در Direct3D بسازيم خودمان بوسيله کد نويسي آنرا توصيف کرده ايم . ممکنست اين سوال برايتان پيش آمده باشد که بازيهاي تجاري براي توليد کاراکترهاي و اشيا پيچيده سه بعدي چگونه عمل مي کنند ؟ منطقي بنظر نمي رسد که اينگونه مدلهاي پيچيده بصورت کد وارد برنامه شده اند زيرا نياز به هزاران خط برنامه براي هر فريم خواهد بود . بجاي اينکار ما object هاي خود را توسط برنامه هاي ديگري مي سازيم و آنها را در برنامه خودمان load مي کنيم سپس بافتها و material هاي مورد نظر را به آنها اختصاص داده و در پايان آنها را رندر مي کنيم . مزيت ديگر اينکار اينست که شما مي توانيد براحتي فايل object خود را تغيير دهيد و مدلهايي با جزئيات متفاوت براي برنامه خود قرار دهيد . مراحل ساخت چنين برنامه هايي بصورت زير است : ۱ - ساخت object سه بعدي : اولين چيزي که بايستي بدانيد داشتن دانش پايه اي از چگونگي مدلسازي سه بعدي است . همچنين نياز به يک نرم افزار مدلسازي مثل 3D Studio Max داريد . بعد از ساخت مدل خود در Max نياز به يک Convertor داريد تا فايلهاي Max را به فايلهاي Direct3D که با فرمت "X." هستند تبديل کنيد . Convertor هاي زيادي براي تبديل فايلهاي نرم افزارهاي مدلسازي به فايلهاي "X." وجود دارند که برخي از آنها عبارتند از : - برنامه PolyTrans3D System Translation - برنامه Deep Exploration 2.0 - برنامه Quick3D - برنامه 3DWin - DirectX Explorer Plugin - ابزارهاي موجود در DirectX 8.0 SDK که عبارتند از : برنامه Conv3DS براي تبديل فايلهاي 3DS به فايلهاي X DX SDK Exporter Plugin براي تبديل فايلهاي 3DS و Max به فايلهاي X از بين اين برنامه ها و plugin ها من برنامه Deep Exploration را به شما پيشنهاد مي کنم . 2 - Load کردن يک Object ساخته شده : زمانيکه فايل X شي مورد نظر را ساختيد ، load کردن آن در direct3D ساده است . براي اينکار نياز به يک مش داريم که اطلاعات شي ما را نگهداري کند : Dim Mesh As D3DXMesh همچنين براي اختصاص material و texture به شي ، نياز به تعريف متغيرهاي زير داريم : Dim MeshMaterial As D3DMATERIAL8 Dim MeshTexture As Direct3DTexture8 حال به سراغ بازنويسي روتين InitGeometry مي رويم : - تعريف متغيرهاي مورد نياز : Dim mtrlBuffer as D3DXBuffer Dim TextureFile as String Dim n as Long - گرفتن داده هاي شي از فايل X : Set Mesh=D3DX.LoadMeshFromX app.path&"\"&"yourfilename",D3DMESH_MANAGED,D3DDevice,Nothing,mtrlBuffer,n - استخراج اطلاعات materiasl شي و تنظيم پارامتر Ambient : D3DX.BufferGetMaterial mtrlBuffer,0,MeshMaterial MeshMaterial.Ambient=MeshMaterial.Diffuse - استخراج نام بافت بکار رفته براي شي : TextureFile=D3DX.BufferGetTextureName(mtrlBuffer,0)x - ساخت بافت : If TextureFile<>"" Then Set MeshTexture=D3DX.CreateTextureFromFile D3DDevice,app.path&"\"&TextureFile,128,128,D3DX_DEFAULT,0, D3DFMT_UNKNOWN,D3DPOOL_MANAGED,D3DX_FILTER_LINEAR,D3DX_FILTER_LINEAR,0,Byval 0,Byval 0 End If ۳ - رندر نمودن شي : رندر نمودن شي چندان مشکل نيست اما همچنان بايد ماتريسها و تبديلاتي را که مي خواهيد ، خودتان مديريت کنيد . D3DDevice.SetMaterial MeshMaterial D3DDevice.SetTexture 0,MeshTexture Mesh.DrawSubset 0 موضوع : مباحث تکميلي نورپردازي در Direct3D در بخش اول آموزش Direct3D با مباني نورپردازي آشنا شديد . در اين درس قصد دارم آن مباحث را کاملتر برايتان مطرح کنم . نورپردازي يکي از بخشهاي مهم طراحي يک بازي و يا يک انيميشن سه بعدي است . بمنظور پياده سازي نورپردازي يک صحنه ابتدا بايد با تئوري آن آشنا شويد . تئوري نورپردازي : نورپردازي در Direct3D تخميني از چگونگي عملکرد نور در دنياي واقعي مي باشد . چهار نوع اصلي نور در Direct3D قابل استفاده است ( همچنين شما مي توانيد خودتان انواع جديدي از نور ايجاد کنيد که موضوع ما نيست ) : ۱ - Point Light : توسط يک نقطه در فضاي سه بعدي ايجاد مي شود و داراي سه پارامتر رنگ ، دامنه و تضعيف مي باشد . دامنه يک نور مسافتي است که نور مي تواند طي کند . تضعيف ، مقدار کاهش نور در اثر افزايش مسافت مي باشد . نور نقطه اي در تمام جهات تششع مي کند - شبيه يک لامپ حبابي و يا يک شمع ۲ - Spot Light : داراي يک موقعيت و يک جهت است و تنها نور را در يک جهت خاص مي تاباند - شبيه يک چراغ قوه . اين نور داراي يک زاويه مخروطي و يک دامنه است . ۳ - Directional Light : داراي موقعيت نيست و براي پياده سازي نورهايي که از فاصله بسيار دور مي آيند - مثل خورشيد - مناسب است . ۴ - Ambient Light : اين نور تضمين مي کند که تمام vertex هاي يک صحنه تاريکتر از يک رنگ خاص نباشند . عملي کردن نورپردازي : ضمن اينکه اغلب کارت هاي گرافيک سه بعدي از نورپردازي پشتيباني مي کنند اما اين نکته بايد مورد توجه قرار گيرد که با افزايش تعداد نور در يک صحنه محاسبات Direct3D بيشتر مي شود و اين باعث کند شدن رندر صحنه خواهد شد و بنابراين کارت هاي گرافيکي سه بعدي نيز داراي يک ماکزيمم تعداد نور هستند - مثلاً ۱۶ نور در GeForce 2 - همچنين توجه داشته باشيد که نورهاي مختلف داراي زمان پردازشي متفاوتي هستند . نور ambient سريعترين زمان پردازشي را دارد ، سپس نورdirectional ، سپس نور point و کندترين آنها Spot Light است . همچنين نکته ديگري که بايد توجه کنيد دامنه نور است . اگر نور ، يک منطقه بزرگي را پوشش دهد بر تعداد زيادي از vertex ها تاثير مي گذارد و اين باعث افزايش محاسبات مي شود . نورپردازي Specular - که در درسهاي بعدي در مورد آن صحبت مي کنم و براي ايجاد اشيا درخشان استفاده مي شود - نيز زمان پردازشي زيادي دارد و بهتر است کمتر از آن استفاده شود . پارامتر ديگري که بايد در نظر بگيريد جزئيات هندسه شما مي باشد . هر چه پيچيدگي صحنه بيشتر باشد ، نورپردازي نيز زمان بيشتري را مصرف مي کند . سايه زني نيز يک بخش بسيار پيچيده در مدل سازي نور است و محاسبات آن بسيار زمان گير خواهد بود بنابراين Direct3D مستقيماً محاسبات سايه زني را انجام نمي دهد بلکه رنگ نور را بر مبناي جهت هر مثلث scale مي کند بنابراين قسمت پشتي يک شي که رو به نور نيست ، هيچ نوري را دريافت نمي کند . بردار نرمال : Direct3D هر vertex را بر مبناي بک بردار نرمال نورپردازي مي کند و نوري که يک vertex دريافت مي کند به زاويه بين نور و بردار نرمال آن vertex بستگي دارد . بردار نرمال توسط سه vertex يک face مثلثي ايجاد مي شود و اين بردار نرمال ساخته شده به vertex ها اختصاص مي يابد . بردار نرمال در واقع سمت يک مثلث را مشخص مي کند بنابراين اگر نور پشت مثلث باشد ، مثلث هيچ نوري را دريافت نميکند . بردار نرمال بايستي داراي طول ۱ باشد . مراحل توليد بردار نرمال يک face مثلثي : ۱ - مطمئن شويد که face در جهت عقربه هاي ساعت ساخته شده است . ۲ - يک بردار از vertex شماره صفر به vertex شماره يک بسازيد . ۳ - يک بردار از vertex شماره صفر به vertex شماره دو بسازيد . ۴ - حاصلضرب برداري ( cross droduct ) اين دو بردار را بدست آوريد . ۵ - نتيجه حاصلضرب را نرمال کنيد . Private Function GenerateTriangleNormals(p0 As UnlitVertex, p1 As UnlitVertex, p2 As UnlitVertex) As D3DVECTOR Dim v01 As D3DVECTOR Dim v02 As D3DVECTOR Dim vNorm As D3DVECTOR D3DXVec3Subtract v01, MakeVector(p1.X, p1.Y, p1.Z), MakeVector(p0.X, p0.Y, p0.Z)x D3DXVec3Subtract v02, MakeVector(p2.X, p2.Y, p2.Z), MakeVector(p0.X, p0.Y, p0.Z)x D3DXVec3Cross vNorm, v01, v02 D3DXVec3Normalize vNorm, vNorm GenerateTriangleNormals.X = vNorm.X GenerateTriangleNormals.Y = vNorm.Y GenerateTriangleNormals.Z = vNorm.Z End Function اگر دو face در يک vertex مشترک باشند ( مثل گوشه دو ديوار ) براي توليد نرمال اين vertex ابتدا نرمال دو face را با روش فوق بدست آوريد سپس دو بردار نرمال را با هم جمع کنيد و در پايان بردار حاصلجمع را نرمال کنيد . برپاسازي نورپردازي : اولين چيزي که قبل از برپاسازي نورپردازي بايستي اعمال کنيم تغيير ساختار vertex است . براي اينکار بايد پارامتر color را از ساختار vertex حذف و سه پارامتر را براي نگهداري نرمال اضافه کنيم : Private Type UnlitVertex X As Single Y As Single Z As Single nx As Single ny As Single nz As Single tu As Single tv As Single End Type Const Unlit_FVF = (D3DFVF_XYZ Or D3DFVF_NORMAL Or D3DFVF_TEX1)x همچنين بايد براي تمام vertex هاي شي خود بردار نرمال را محاسبه کنيد براي مثال اگر شي شما يک مکعب است براي هر ۱۲ face آن بردار نرمال را بدست آوريد . در زير من کد لازم براي ساخت نرمال يکي از اين face ها را نوشته ام : Cube2(0) = CreateVertex(-1, -1, 1, 0, 0, 0, 0, 0)x Cube2(1) = CreateVertex(1, 1, 1, 0, 0, 0, 1, 1)x Cube2(2) = CreateVertex(-1, 1, 1, 0, 0, 0, 0, 1)x vN = GenerateTriangleNormals(Cube2(0), Cube2(1), Cube2(2))x Cube2(0).nx = vN.X: Cube2(0).ny = vN.Y: Cube2(0).nz = vN.Z Cube2(1).nx = vN.X: Cube2(1).ny = vN.Y: Cube2(1).nz = vN.Z Cube2(2).nx = vN.X: Cube2(2).ny = vN.Y: Cube2(2).nz = vN.Z براي برپا سازي نور ابتدا بايستي يک material به device خود اضافه کنيد : Dim Mtrl As D3DMATERIAL8, Col As D3DCOLORVALUE Col.a = 1: Col.r = 1: Col.g = 1: Col.b = 1 Mtrl.Ambient = Col Mtrl.diffuse = Col D3DDevice.SetMaterial Mtrl سپس بايستي طوري device خود را تنظيم کنيد که نور شما را بشناسد - lights يک شي از نوع D3DLight8 است - يکبار که اين خط را بنويسيد مي توانيد از نور استفاده کنيد اما اگر خصوصيات نور را تغيير دهيد بايستي دوباره اين دستور را فراخواني کنيد : D3DDevice.SetLight 0, Lights حال بايد نور را روشن کنيد : D3DDevice.LightEnable 0, 1 و در پايان بايد به Direct3D بگوئيد که نورپردازي را براي شما انجام دهد : D3DDevice.SetRenderState D3DRS_LIGHTING, 1 چگونگي ايجاد يک نور : براي ايجاد هر يک از ۴ نوع اصلي نور بايد به روشي خاص عمل کنيد : ۱ - نورپردازي Ambient : اين نوع نورپردازي بسيار ساده است و تنها با فراخواني تابع SetRenderState ايجاد مي شود . رنگ ambient يک عدد هگزادسيمال بصورت RRGGBB است : D3DDevice.SetRenderState D3DRS_AMBIENT, &H202020 ۲ - نورپردازي Directional : داراي دو پارامتر رنگ و جهت مي باشد : Lights.Type = D3DLIGHT_DIRECTIONAL Lights.diffuse.r = 1 Lights.diffuse.g = 1 Lights.diffuse.b = 1 Lights.Direction = MakeVector(0, -1, 0)x 3 - نورپردازي Point : داراي سه پارامتر موقعيت ، رنگ و تضعيف مي باشد : Lights.Type = D3DLIGHT_POINT Lights.position = MakeVector(5, 0, 2)x Lights.diffuse.b = 1 Lights.Range = 100 Lights.Attenuation1 = 0.05 ۴ - نورپردازي Spot : اين نور داراي دو مخروط است که نقاط خارج مخروط اول روشنتر از نقاط داخل آن هستند . دو زاويه براي مخروط وجود دارد - زاويه داخلي theta و زاويه خارجي phi - که برحسب راديان هستند : Lights.Type = D3DLIGHT_SPOT Lights.position = MakeVector(-4, 0, 0)x Lights.Range = 100 Lights.Direction = MakeVector(1, 0, 0)x Lights.Theta = 30 * (Pi / 180)x Lights.Phi = 50 * (Pi / 180)x Lights.diffuse.g = 1 Lights.Attenuation1 = 0.05 موضوع : استفاده از Index Buffer براي ذخيره سازي اشکال سه بعدي مقدمه : مکعبي که در درسهاي قبلي ساختيم را درنظر بگيريد . با دانشي که اکنون داريد ، دو راه براي ساخت يک مکعب داريم : ۱ - استفاده از 36 عدد vertex براي تعريف face هاي مکعب ۲ - ساخت مکعب با استفاده از يک مدلساز و ذخيره آن با فرمت X روش اول غيرکارامد است زيرا شما بايستي از تعداد زيادي vertex براي يک شکل بسيار ساده استفاده کنيد . روش دوم مناسب است اما زمانيکه بخواهيم رنگها و بافتها را تغيير دهيم دچار مشکل خواهيم شد . روش جديدي که امروز در مورد آن صحبت مي کنم استفاده ار Index Buffer است . Index Buffer شامل يکسري عدد integer است که اين اعداد مرجعي براي vertex هاي ذخيره شده در يک Vertex Buffer هستند . براي مثال فرض کنيد يک Vertex Buffer شامل 8 عدد vertex داريم که يک مکعب را براي ما توصيف مي کند . ما مي توانيم يک Index Buffer با ۳۶ عضو بسازيم بطوريکه ترتيب اتصال vertex ها را براي ما مشخص کنند . مثلاً Index هاي ۰ و ۱و ۳ براي مشخص کردن face شماره ۱ مکعب بکار مي روند . بنابراين بجاي استفاده از ۳۶ عدد vertex مي توانيم مکعب را با ۸ عدد vertex و يک Index Buffer بسازيم . گرچه استفاده از Index Buffer بسيار کارامد است اما چندين محدوديت در استفاده از آن وجود دارد . مهمترين آنها اينست که تمام انديسهايي که يک vertex مشابه را share مي کنند بايستي خصوصيات مشابهي داشته باشند - موقعيت ، رنگ ، بافت و نرمال يکسان - براي مثال نمي توانيد مکعبي بسازيد که هر face آن يک رنگ داشته باشد . ساخت Index Buffer : ابتدا به متغيرهاي زير نياز داريم : Dim VBuffer as Direct3DVertexBuffer8 Dim IBuffer as Direct3DIndexBuffer8 Dim Vlist(0 to 7) as LITVERTEX Dim Ilist(0 to 35) as Integer تابع InitGeometry بصورت زير بازنويسي مي شود: ۱- توليد هشت vertex براي مکعب : Vlist(0) = CreateLitVertex(-1, -1, -1, &HFF0000, 0, 0, 0)x Vlist(1) = CreateLitVertex(-1, 1, -1, &HFF00&, 0, 0, 0)x Vlist(2) = CreateLitVertex(1, -1, -1, &HFF&, 0, 0, 0)x Vlist(3) = CreateLitVertex(1, 1, -1, &HFF00FF, 0, 0, 0)x Vlist(4) = CreateLitVertex(-1, -1, 1, &HFFFF00, 0, 0, 0)x Vlist(5) = CreateLitVertex(-1, 1, 1, &HFFFF, 0, 0, 0)x Vlist(6) = CreateLitVertex(1, -1, 1, &HFFCC00, 0, 0, 0)x Vlist(7) = CreateLitVertex(1, 1, 1, &HFFFFFF, 0, 0, 0)x ۲ - ايجاد Vertex Buffer توسط تابع CreateVertexBuffer : Set VBuffer = D3DDevice.CreateVertexBuffer(Len(Vlist(0)) * 8, 0, Lit_FVF, D3DPOOL_DEFAULT)x D3DVertexBuffer8SetData VBuffer, 0, Len(Vlist(0)) * 8, 0, Vlist(0)x ۳ - توليد index ها : front ' Ilist(0) = 0: Ilist(1) = 1: Ilist(2) = 2 Ilist(3) = 1: Ilist(4) = 3: Ilist(5) = 2 Right ' Ilist(6) = 2: Ilist(7) = 3: Ilist(8) = 6 Ilist(9) = 3: Ilist(10) = 7: Ilist(11) = 6 Back ' Ilist(12) = 6: Ilist(13) = 7: Ilist(14) = 4 Ilist(15) = 7: Ilist(16) = 5: Ilist(17) = 4 Left ' Ilist(18) = 4: Ilist(19) = 5: Ilist(20) = 0 Ilist(21) = 5: Ilist(22) = 1: Ilist(23) = 0 Top ' Ilist(24) = 1: Ilist(25) = 5: Ilist(26) = 3 Ilist(27) = 5: Ilist(28) = 7: Ilist(29) = 3 Bottom ' Ilist(30) = 2: Ilist(31) = 6: Ilist(32) = 0 Ilist(33) = 6: Ilist(34) = 4: Ilist(35) = 0 ۴ - ايجاد Index Buffer توسط تابع CreateIndexBuffer : Set IBuffer = D3DDevice.CreateIndexBuffer(Len(Ilist(0)) * 36, 0, D3DFMT_INDEX16, D3DPOOL_DEFAULT)x D3DIndexBuffer8SetData IBuffer, 0, Len(Ilist(0)) * 36, 0, Ilist(0)x تابع Render : براي رندر کردن اين مکعب دو روش وجود دارد : ۱ - استفاده از تابع DrawIndexedPrimitive : در اين روش از VBuffer و IBUffer و آرايه vertex ها استفاده مي شود : Public Sub Render()x D3DDevice.Clear 0, ByVal 0, D3DCLEAR_TARGET Or D3DCLEAR_ZBUFFER, 0, 1#, 0 D3DDevice.BeginScene D3DDevice.SetStreamSource 0, VBuffer, Len(Vlist(0))x D3DDevice.SetIndices IBuffer, 0 D3DDevice.DrawIndexedPrimitive D3DPT_TRIANGLELIST, 0, 36, 0, 12 D3DDevice.EndScene D3DDevice.Present ByVal 0, ByVal 0, 0, ByVal 0 End Sub ۲ - استفاده از تابع DrawIndexedPrimitiveUP : در اين روش از آرايه هاي vertex و index استفاده مي شود : Public Sub Render()x D3DDevice.Clear 0, ByVal 0, D3DCLEAR_TARGET Or D3DCLEAR_ZBUFFER, 0, 1#, 0 D3DDevice.BeginScene D3DDevice.DrawIndexedPrimitiveUP D3DPT_TRIANGLELIST, 0, 8, 12, Ilist(0), D3DFMT_INDEX16, Vlist(0), Len(Vlist(0))x D3DDevice.EndScene D3DDevice.Present ByVal 0, ByVal 0, 0, ByVal 0 End Sub موضوع : Vertex/Mesh Animation در اين درس در مورد روشهاي ساخت انيميشن در Direct3D صحبت خواهيم کرد . انيميشن در فضاي سه بعدي در حالتهاي مختلفي مي تواند ايجاد شود که بسته به engine گرافيکي شما و ابزارهايي که ايجاد کرده ايد ، دارد . سه روش اصلي ساخت انيميشن وجود دارد که عبارتند از : - Tween سازي دستي / درون يابي خطي ( manual tweening/linear interpolation ) - درون بابي برداري ( vector interpolation ) - درون يابي بر اساس فريم کليدي ( keyframe interpolation ) 1 – روش اول يکي از ساده ترين راههاي ساخت انيميشن است . اين روش در زمانيکه با مدلهاي پيچيده سر و کار داريد مناسب نيست – و يا مدلهايي با تعداد زيادي vertex – اين روش نوعي tween کردن است که از مزيت index buffer ها استفاده مي کند . درون يابي ، چگونگي تغييرات شيي در طول يک زمان مشخص مي باشد . در درسهاي قبلي شما درون يابي رنگ را روي يک شي ديديد که در آن يک رنگ بطور ملايم به رنگ ديگري تبديل مي شد ( fadeشدن ( . درون يابي خطي نيز مشابه آن است . براي درون يابي خطي از موقعيت A به موقعيت B از فرمول زير استفاده مي شود : (B*V)+A*(1-V) که A و B مختصاتهاي مبدا و مقصد هستند و V ضريب درون يابي است که عددي بين صفر و يک مي باشد . اين فرمول مختصات نقطه tween را در هر لحظه مشخص مي کند . همانطور که مي بينيد بکار بردن اين فرمول براي يک شي با تعداد زيادي vertex بسيار وقت گير بوده و fram rate را پايين مي آورد . تابع زير دو vertex و يک مقدار ضريب درون يابي را مي گيرد تا نقطه tween را محاسبه کند : Private Function TweenVertices(Source As LITVERTEX, Dest As LITVERTEX, TweenAmount As Single) As LITVERTEX TweenVertices.X = (Dest.X * TweenAmount) + Source.X * (1# - TweenAmount)x TweenVertices.Y = (Dest.Y * TweenAmount) + Source.Y * (1# - TweenAmount)x TweenVertices.Z = (Dest.Z * TweenAmount) + Source.Z * (1# - TweenAmount)x TweenVertices.color = Source.color End Function اگر شما از vertex هاي UNLIT استفاده کنيد – vertex هايي با بردار نرمال – در اينصورت بايد کد فوق را تغيير دهيد و بايد tween را از نرمال مبدا به نرمال مقصد نيز انجام دهيد . همانطور که مي بينيد رنگ tween vertex نيز تنظيم شده است . در يک تابع tweening مناسبتر مي توانيد رنگها ، مختصات بافت و مقادير specular را نيز tween کنيد . محدوديتي که اين روش دارد اينست که خطي است و براي مدل کردن حرکتهاي غير خطي درست کار نمي کند . حال مي خواهيم از تابع tween استفاده کنيم تا يک مکعب را در يک انيميشن به يک هرم تبديل کنيم . ابتدا سه شي را بصورت زير تعريف مي کنيم : در ابتداي انيميشن ، شي current cube همان source cube است’ CubeVertices(0) = CreateLitVertex(-1, -1, -1, &HFF0000, 0, 0, 0)x CubeVertices(1) = CreateLitVertex(-1, 1, -1, &HFF00&, 0, 0, 0)x CubeVertices(2) = CreateLitVertex(1, -1, -1, &HFF&, 0, 0, 0)x CubeVertices(3) = CreateLitVertex(1, 1, -1, &HFF00FF, 0, 0, 0)x CubeVertices(4) = CreateLitVertex(-1, -1, 1, &HFFFF00, 0, 0, 0)x CubeVertices(5) = CreateLitVertex(-1, 1, 1, &HFFFF, 0, 0, 0)x CubeVertices(6) = CreateLitVertex(1, -1, 1, &HFFCC00, 0, 0, 0)x CubeVertices(7) = CreateLitVertex(1, 1, 1, &HFFFFFF, 0, 0, 0)x مکعب اوليه’ CubeVerticesSource(0) = CreateLitVertex(-1, -1, -1, &HFF0000, 0, 0, 0)x CubeVerticesSource(1) = CreateLitVertex(-1, 1, -1, &HFF00&, 0, 0, 0)x CubeVerticesSource(2) = CreateLitVertex(1, -1, -1, &HFF&, 0, 0, 0)x CubeVerticesSource(3) = CreateLitVertex(1, 1, -1, &HFF00FF, 0, 0, 0)x CubeVerticesSource(4) = CreateLitVertex(-1, -1, 1, &HFFFF00, 0, 0, 0)x CubeVerticesSource(5) = CreateLitVertex(-1, 1, 1, &HFFFF, 0, 0, 0)x CubeVerticesSource(6) = CreateLitVertex(1, -1, 1, &HFFCC00, 0, 0, 0)x CubeVerticesSource(7) = CreateLitVertex(1, 1, 1, &HFFFFFF, 0, 0, 0)x هرم مقصد’ CubeVerticesDest(0) = CreateLitVertex(-1, -1, -1, &HFF0000, 0, 0, 0)x CubeVerticesDest(1) = CreateLitVertex(-0.1, 1, -0.1, &HFF00&, 0, 0, 0)x CubeVerticesDest(2) = CreateLitVertex(1, -1, -1, &HFF&, 0, 0, 0)x CubeVerticesDest(3) = CreateLitVertex(0.1, 1, -0.1, &HFF00FF, 0, 0, 0)x CubeVerticesDest(4) = CreateLitVertex(-1, -1, 1, &HFFFF00, 0, 0, 0)x CubeVerticesDest(5) = CreateLitVertex(-0.1, 1, 0.1, &HFFFF, 0, 0, 0)x CubeVerticesDest(6) = CreateLitVertex(1, -1, 1, &HFFCC00, 0, 0, 0)x CubeVerticesDest(7) = CreateLitVertex(0.1, 1, 0.1, &HFFFFFF, 0, 0, 0)x حال بايد در يک حلقه با استفاده از تابع twen پيکسلهاي CubeVertices را update کنيم : Private Sub UpdateAnimation()x Dim I As Integer به روز کردن پارامترهاي زمان و جهت' If AnimTweenDir = True Then AnimTweenFactor = AnimTweenFactor + (((GetTickCount() - LastTimeTweened) / 1000)*1#) LastTimeTweened = GetTickCount If AnimTweenFactor >= 1# Then AnimTweenFactor = 1# AnimTweenDir = False End If Else AnimTweenFactor = AnimTweenFactor - (((GetTickCount() - LastTimeTweened) / 1000)*1#) LastTimeTweened = GetTickCount If AnimTweenFactor <= 0# Then AnimTweenFactor = 0# AnimTweenDir = True End If End If به روز کردن اطلاعات vertex ها ' For I = 0 To 7 CubeVertices(I) = TweenVertices(CubeVerticesSource(I), CubeVerticesDest(I), AnimTweenFactor)x Next I به روز کردن بافر vertex’ If D3DVertexBuffer8SetData(VBuffer, 0, Len(CubeVertices(0)) * 8, 0, CubeVertices(0)) = D3DERR_INVALIDCALL Then GoTo Error: Exit Sub Error: Debug.Print “Error occured whilst updating the animation…”x End Sub زمان پايه انيميشن توسط عبارت زير تنظيم مي شود : (((GetTickCount() - LastTimeTweened) / 1000) * 1#) همانطور که مي دانيد دو نوع انيميشن وجود دارد : انيميشن بر مبناي frame و انيميشن بر مبناي زمان . در انيميشن بر مبناي frame شماره فريم با يک مقدار ثابت در زمان افزايش مي يابد اما اگر اينکار باعث مي شود کيفيت انيميشن در کامپيوترهاي با سرعت متفاوت تغيير کند . بنابراين انيميشن را بر مبناي زمان توليد کرده ايم . انيميشن هاي بر مبناي زمان بجاي " 1 فريم در هر سيکل " ، " 30 فريم در هر ثانيه " هستند 2 – روش دوم از توابع کتابخانه D3DX براي انجام عمل tweening استفاده مي کند و بنابراين بهبودي در سرعت انيميشن نسبت به روش بالا حاصل مي شود . با استفاده از کتابخانه D3DX مي توانيم عمل درون يابي خطي را براي تمام اجزا اصلي يک vertex انجام دهيم . ليست زير توابعي را براي اينکار نشان مي دهد : - تابع D3DXVec3Lerp : انجام درون يابي براي موقعيت و نرمال : D3DXVec3Lerp( VOut as D3DVECTOR, V1 as D3DVECTOR, V2 as D3DVECTOR, S as Single)x - VOut = The result of the interpolation - V1 = The source coordinates - V2 = The destination coordinates - S = The interpolation amount - between, but not limited to, 0.0 - 1.0 scale; where 0 is the source and 1 is the destination - تابع D3DXColorLerp : انجام درون يابي براي رنگهاي vertex : D3DXColorLerp( COut as D3DCOLORVALUE, C1 as D3DCOLORVALUE, C2 as D3DCOLORVALUE, S as Single)x - COut = The resulting colour - C1 = The source colour - C2 = The destination colour - S = The interpolant, on a 0.0 to 1.0 scale - تابع D3DXVec2Lerp : انجام درون يابي براي مختصاتهاي دوبعدي : - VOut = The result of this interpolation - V1 = The source coordinates - V2 = The destination coordinates - S = The interpolant on a 0.0 to 1.0 scale - تابع D3DXVec3Hermite : توليد يک مسير منحني که از دو نقطه کنترل عبور مي کند : D3DXVec3Hermite( VOut as D3DVECTOR, V1 as D3DVECTOR, T1 as D3DVECTOR, V2 as D3DVECTOR, T2 as D3DVECTOR, S as Single)x - VOut = The Result - V1 = The Source Coordinate - T1 = The Tangent at the Source coordinate, this is the direction and speed the line will leave the source point. - V2 = The Destination Coordinate - T2 = The Tangent at the Destination coordinate, this is the direction and speed the line will enter the destination point. - S = The Interpolant Value براي اينکه بتوانيم از کتابخانه D3DX استفاده کنيم بايد توصيف vertex هايمان را تغيير دهيم و بايستي يکسري مقادير ARGB اضافي را به ساختار vertex اضافه کنيم : Private Type LITVERTEX X As Single Y As Single Z As Single color As Long specular As Long tu As Single tv As Single ColorEx As D3DCOLORVALUE End Type حال تابع tween را بصورت زير مي نويسيم : Private Function TweenVertices(Source As LITVERTEX, Dest As LITVERTEX, TweenAmount As Single) As LITVERTEX Dim vResult As D3DVECTOR Dim vResult2 As D3DVECTOR2 Tween کردن موقعيت vertex ها ‘ D3DXVec3Lerp vResult, MakeVector(Source.X, Source.Y, Source.Z), MakeVector(Dest.X, Dest.Y, Dest.Z), TweenAmount TweenVertices.X = vResult.X TweenVertices.Y = vResult.Y TweenVertices.Z = vResult.Z Tween کردن اطلاعات texture ’ D3DXVec2Lerp vResult2, MakeVector2D(Source.tu, Source.tv), MakeVector2D(Dest.tu, Dest.tv), TweenAmount TweenVertices.tu = vResult2.X TweenVertices.tv = vResult2.Y Tween کردن اطلاعات رنگ ‘ D3DXColorLerp TweenVertices.ColorEx, Source.ColorEx, Dest.ColorEx, TweenAmount With TweenVertices.ColorEx TweenVertices.color = RGB(.B * 255, .G * 255, .R * 255)x End With End Function نکته اي که بايد به آن توجه کنيد اينست که در تابع فوق براي اشاره به vertex ، يک بردار ساخته شده است ( توسط تابع MakeVector ) . 3 – روش سوم پر استفاده ترين روش انيميشن سازي است . اگر شما انيميشن هاي پيچيده با تعداد زيادي شي در آن داشته باشيد و اگر بخواهيد تغييرات اشيا را در هر فريم ذخيره کنيد ، به حجم بالايي از منابع ذخيره سازي نياز است . بجاي آن ما با استفاده از يکسري فريم کليدي ، فريمهاي مياني را پيش بيني مي کنيم . براي انجام درون يابي فريم کليدي ، بايستي مقدار vertex را در هر فريم کليدي بدانيم و نيز بدانيم هر فريم کليدي در چه زماني ظاهر مي شود . بنابراين بايد براي هر انيميشن چند فايل را بعنوان فريم کليدي ذخيره کنيم . در اين درس ما داده هاي کليدي انيميشن را از يکسري فايل load مي کنيم بنابراين تمام ثابتهاي زمان keyframe درون برنامه قرار داده مي شود ( شما مي توانيد خودتان يک ماژول بنويسيد که انيميشن هاي عمومي تر را نيز مديريت کند . اين ماژول بايد قادر باشد که يک فرمت استاندارد فايل را import کند ، اشيا و texture هاي مربوطه را load نمايد و سپس خودش ساخت انيميشن را بطور اتوماتيک انجام دهد و برنامه اصلي فقط روتين render و يا update را فراخواني کند ) . پس از جمع آوري اطلاعات فريم هاي کليدي ، بايد در هر زمان محاسبه کنيم که چه مدتي از شروع انيميشن گذشته است و بنابراين انيميشن در چه موقعيتي قرار دارد . سپس محاسبه مي کنيم که فريم کليدي قبلي و فريم کليدي بعدي چيست همچنين حساب مي کنيم در چه فاصله زماني از ايندو قرار داريم . سرانجام يک درون يابي نرمال را انجام مي دهيم تا اطلاعات فريم جاري بدست آيد و اين اطلاعات را درون يک شي Mesh مي گذاريم و آنرا رندر مي کنيم . در درسهاي قبلي در مورد load کردن اشيا از يک فايل X صحبت کردم اما در مورد چگونگي گرفتن اطلاعات vertex از يک شي Mesh صحبت نشد . کتابخانه D3DX براي اينکار دو تابع دارد : - تابع D3DXMeshVertexBuffer8GetData : اطلاعات يک شي D3DXMesh را گرفته و در يک آرايه از D3DVERTEX ذخيره مي کند : D3DXMeshVertexBuffer8GetData( D3DXMeshobj As Unknown, Offset As Long, Size As Long, Flags As Long, Data As Any) As Long - D3DXMeshobj As Unknown = A D3DXMESH object that you want to extract the data from. - Offset As Long = How far into the vertex buffer we want to start reading, 0 is the beginning - Size As Long = Size of the vertex buffer, this will be Len(D3DVERTEX) * Mesh.GetNumVertices - Flags As Long = A combination of the CONST_D3DLOCKFLAGS, leave as 0. - Data As Any = The first element in the array that you want the data to be read into, should be an array of D3DVERTEX vertices - Return Code As Long = Returns D3D_OK for success, or either of D3DERR_INVALIDCALL or E_INVALIDARG for an error - تابع D3DXMeshVertexBuffer8SetData : اطلاعات يک بافر vertex را در يک شي D3DXMesh قرار مي دهد : D3DXMeshVertexBuffer8SetData( D3DXMeshobj As Unknown, Offset As Long, Size As Long, Flags As Long, Data As Any) As Long - D3DXMeshobj As Unknown = The D3DXMESH object that defines where you want the data to be placed - Offset As Long = How far into the Destination vertex buffer you want to place the data - Size As Long = The Size of the buffer in bytes, this will be Len(D3DVERTEX) * Mesh.GetNumVertices - Flags As Long = A Combination of the CONST_D3DLOCKFLAGS, leave as 0 - Data As Any = The first element in the array of data you want placed in the mesh's vertex buffer - Return Code As Long = D3D_OK for success or D3DERR_INVALIDCALL or E_INVALIDARG for failure عمليات انجام انيميشن فريم کليدي بصورت زير است : - load کردن اشيا از فايلهاي X به درون شي D3DXMesh - استخراج اطلاعات vertex از اين شي - انجام درون يابي بين فريمهاي کليدي - قرار دادن اطلاعات vertex هاي درون يابي در يک شي D3DXMesh فرض مي کنيم که انيميشن ما هميشه از زمان صفر تا زمان n باشد – برحيب ميلي ثانيه – بنابراين مي توانيم از GetTickCount براي توابع زماني خود استفاده کنيم . همچنين يک ساختار را براي هر فريم کليدي بصورت زير تعريف مي کنيم : Private Type KeyFrame شي load شده از يک فايل’ Mesh As D3DXMesh آرايه material براي هر شي’ MatList() As D3DMATERIAL8 آرايه Texture’ TexList() As Direct3DTexture8 تعداد material ها و texture هايي که استفاده مي کنيم’ nMaterials As Long داده هاي vertex براي اين فريم کليدي’ VertexList() As D3DVERTEX موقعيت اين فريم کليدي در انيميشن’ TimeIndex As Long End Type حال بايد تابعي بنويسيم که اطلاعات را از يک فايل X استخراج کرده و درون فريم کليدي قرار دهد : Private Function CreateKeyFrameFromFile(Filename As String, TexturePrefix As String, Time As Long) As KeyFrame نام فايل X براي شي سه بعدي: Filename ’ پوشه اي که اطلاعات texture اين شي در آن قرار دارد : TexturePrefix ’ انديس زمان براي اين فريم کليدي : Time ' Dim I As Long Dim XBuffer As D3DXBuffer Dim TextureFile As String Dim hResult As Long 'خواندن اطلاعات از فايل ورودي به حافظه Set CreateKeyFrameFromFile.Mesh = D3DX.LoadMeshFromX(Filename, D3DXMESH_MANAGED, D3DDevice, Nothing, XBuffer, CreateKeyFrameFromFile.nMaterials)x توليد material ها و texture ها ‘ ReDim CreateKeyFrameFromFile.MatList(CreateKeyFrameFromFile.nMaterials) As D3DMATERIAL8 ReDim CreateKeyFrameFromFile.TexList(CreateKeyFrameFromFile.nMaterials) As Direct3DTexture8 For I = 0 To CreateKeyFrameFromFile.nMaterials - 1 D3DX.BufferGetMaterial XBuffer, I, CreateKeyFrameFromFile.MatList(I)x CreateKeyFrameFromFile.MatList(I).Ambient = CreateKeyFrameFromFile.MatList (I).diffuse TextureFile = D3DX.BufferGetTextureName(XBuffer, I)x If TextureFile <> "" Then Set CreateKeyFrameFromFile.TexList(I) = D3DX.CreateTextureFromFileEx(D3DDevice, TexturePrefix & TextureFile, D3DX_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT, 0, D3DFMT_UNKNOWN, D3DPOOL_MANAGED, D3DX_FILTER_LINEAR, D3DX_FILTER_LINEAR, 0, ByVal 0, ByVal 0)x End If Next I استخراج داده هاي vertex’ ReDim CreateKeyFrameFromFile.VertexList(CreateKeyFrameFromFile.Mesh.GetNumVertices) As D3DVERTEX hResult = D3DXMeshVertexBuffer8GetData(CreateKeyFrameFromFile.Mesh, 0, Len(CreateKeyFrameFromFile.VertexList(0)) * reateKeyFrameFromFile.Mesh.GetNumVertices, 0, CreateKeyFrameFromFile.VertexList(0)) CreateKeyFrameFromFile.TimeIndex = Time End Function در تابع Initialize خطوط زير را براي ساخت فريم هاي کليدي اضافه مي کنيم : nKeyFrames = 4 kfAnimLength = 2500 AnimLastStartAt = GetTickCount()x ReDim kfAnim(nKeyFrames - 1) As KeyFrame kfAnim(0) = CreateKeyFrameFromFile(App.Path & "\frame0.x", App.Path & "\", 0)x kfAnim(1) = CreateKeyFrameFromFile(App.Path & "\frame1.x", App.Path & "\", kfAnimLength * (1 / 3))x kfAnim(2) = CreateKeyFrameFromFile(App.Path & "\frame2.x", App.Path & "\", kfAnimLength * (2 / 3))x kfAnim(3) = CreateKeyFrameFromFile(App.Path & "\frame3.x", App.Path & "\", kfAnimLength)x kfCurrent = CreateKeyFrameFromFile(App.Path & "\frame0.x", App.Path & "\", 0) دقت کنيد که از يک انديس زمان براي ساخت فريم هاي کليدي استفاده شده است . حال بايد کدي براي نمايش دادن انيميشن بنويسيم . ابتدا بايد به روشي تغييرات فريمها را کنترل کنيم : For I = 0 To nKeyFrames - 2 If CurrentTimeIndex >= kfAnim(I).TimeIndex Then PrevFrame = I NextFrame = I + 1 End If Next I سپس بايد با توجه به زمان index دو فريم کليدي و زمان جاري ، پارامتر درون يابي را محاسبه کنيم : sTime = kfAnim(PrevFrame).TimeIndex eTime = kfAnim(NextFrame).TimeIndex cTime = CurrentTimeIndex eTime = eTime - sTime cTime = cTime - sTime sTime = sTime - sTime InterpolateAmount = cTime / eTime سپس بايد بر اساس اين پارامتر عمل درون يابي را روي داده هاي vertex انجام دهيم : For I = 0 To kfCurrent.Mesh.GetNumVertices 'درون يابي مختصاتها D3DXVec3Lerp vTemp3D, MakeVector(kfAnim(PrevFrame).VertexList(I).X, kfAnim(PrevFrame).VertexList(I).Y, _ kfAnim(PrevFrame).VertexList(I).Z), MakeVector(kfAnim(NextFrame).VertexList(I).X, kfAnim(NextFrame).VertexList(I).Y, _ kfAnim(NextFrame).VertexList(I).Z), InterpolateAmount kfCurrent.VertexList(I).X = vTemp3D.X kfCurrent.VertexList(I).Y = vTemp3D.Y kfCurrent.VertexList(I).Z = vTemp3D.Z 'درون يابي نرمالها D3DXVec3Lerp vTemp3D, MakeVector(kfAnim(PrevFrame).VertexList(I).nx, kfAnim(PrevFrame).VertexList(I).ny, _ kfAnim(PrevFrame).VertexList(I).nz), MakeVector(kfAnim(NextFrame).VertexList(I).nx, kfAnim(NextFrame).VertexList(I).ny, _ kfAnim(NextFrame).VertexList(I).nz), InterpolateAmount kfCurrent.VertexList(I).nx = vTemp3D.X kfCurrent.VertexList(I).ny = vTemp3D.Y kfCurrent.VertexList(I).nz = vTemp3D.Z 'درون يابي اطلاعات بافت D3DXVec2Lerp vTemp2D, MakeVector2D(kfAnim(PrevFrame).VertexList(I).tu, kfAnim(PrevFrame).VertexList(I).tv), _ MakeVector2D(kfAnim(NextFrame).VertexList(I).tu, kfAnim(NextFrame).VertexList(I).tv), InterpolateAmount kfCurrent.VertexList(I).tu = vTemp2D.X kfCurrent.VertexList(I).tv = vTemp2D.Y Next I حال بايد داده توليد شده را به فرمت Mesh برگردانيم : hResult = D3DXMeshVertexBuffer8SetData(kfCurrent.Mesh, 0, Len(kfCurrent.VertexList(0)) * kfCurrent.Mesh.GetNumVertices, 0, kfCurrent.VertexList(0))x با استفاده از روش فوق مي توانيد هر تعداد فريم کليدي را به انيميشنتان اضافه کنيد . اشکالي که روش فوق دارد اينست که اطلاعات texture براي تمام فريمهاي کليدي جداگانه ذخيره شده است در حاليکه texture در تمام فريمها ثابت است . در درسهاي بعدي از روشي بنام texture pooling استفاده مي کنيم تا تنها يک کپي از texture ها نگهداري کنيم مقدمه : کنترل WinSock نسبت به تمام کنترلهاي اينترنت در سطح پايينتري قرار دارد . اين کنترل امکان ايجاد سرويسهاي شبکه اي مبتني بر پروتکلهاي TCP و UDP را مهيا مي کند . بعبارت ديگر توسط اين کنترل مي توان برنامه هاي کاربردي Client/Server ( سرويس گيرنده / سرويس دهنده ) ايجاد و با استفاده از پروتکل TCP و يا UDP بين آنها ارتباط برقرار نمود . با تنظيم خصوصيات و فراخواني متدهاي اين کنترل مي توانيد به راحتي به يک کامپيوتر راه دور متصل شويد و داده ها را در هر دو جهت جابجا نمائيد . نمونه کاربرهايي که مي توان با اين کنترل ايجاد نمود : Client-server chat ، Mail client ، Mail server ، Proxy Server ، Network Game ، Port Scanner ، پياده سازي الگوريتم هاي موازي و … مباني TCP : پروتکل کنترل اينترنت ( Transfer Control Protocol ) اجازه مي دهد يک اتصال ( Connection ) را از طريق سوکت ( socket ) به يک کامپيوتر راه دور ( Remote Computer ) ساخته و استفاده کنيد . با استفاده از اين اتصال ، هر دو کامپيوتر مي توانند داده ها را بين خودشان انتقال دهند . برقراري ارتباط از طريق TCP همانند صحبت کردن با تلفن است که بايد حتماً اتصالي بين دو کامپيوتر صورت گيرد تا بتوانند با هم ارتباط برقرار کنند . اگر يک برنامه Client مي سازيد بايستي بدانيد که نام يا آدرس IP کامپيوتر Server چيست ( Remote Host IP ) و همچنين از طريق چه پورتي مي توانيد به آن متصل شويد ( Remote Port ) . حال بايستي به آن پورت Connect کنيد . همچنين اگر يک برنامه Server مي سازيد بايستي پورتي را که روي آن به درخواستها گوش مي دهيد مشخص کنيد ( LocalPort ) و سپس به پورت گوش دهيد ( Listen ) . زمانيکه يک کامپيوتر Client تقاضاي يک اتصال را مي دهد Server اين درخواست را Accept مي کند . زمانيکه يک اتصال ساخته مي شود ، هر دو کامپيوتر مي توانند داده را فرستاده و دريافت کنند . مباني UDP : پروتکل ديتاگرام کاربر ( User Datagram Protocol ) پروتکلي بدون اتصال ( Connectionless ) است . برخلاف TCP ، کامپيوترها نياز به برپا کردن يک اتصال ندارند بنابراين يک برنامه مي تواند يک client و يا يک server باشد . برقراري ارتباط در UDP شبيه ارسال نامه از طريق پست است . براي انتقال داده توسط UDP ابتدا بايد Local Port کامپيوتر Client تنظيم گردد . کامپيوتر Server تنها بايستي RemoteHost را برابر آدرس کامپيوتر Client قرار دهد و همچنين Remote Port را همان Local Port کامپيوتر Client قرار دهد . سپس دو کامپيوتر مي توانند داده ها را بين خود جابجا کنند . استفاده از کنترل WinSock : 1 – انتخاب پروتکل: در زمان استفاده از کنترل WinSock اولين کاري که بايد انجام دهيد انتخاب يکي از پروتکلهاي TCP يا UDP است . طبيعت برنامه اي که شما مي سازيد نوع پروتکلي را که بايد استفاده کنيد مشخص مي کند . چند سوال زير به شما کمک مي کند که پروتکل مورد نيازتان را انتخاب کنيد : - آيا برنامه شما در زمانيکه داده فرستاده مي شود يا دريافت مي شود نياز به اطلاعاتي از طرف Server يا Client دارد ؟ اگر چنين است بايستي يک اتصال TCP قبل از ارسال يا دريافت داده ايجاد شود . - آيا داده بسيار بزرگ است ( مثل تصوير يا فايلهاي صوتي ) ؟ زمانيکه يک اتصال TCP ساخته مي شود پروتکل TCP اتصال را باقي نگه مي دارد و درستي ارسال داده تضمين شده است . اين اتصال در هر حال به منابع محاسباتي بيشتري نياز دارد و بنابراين پرهزينه تر است . - آيا داده متناوب ارسال مي شود يا در يک نشست ( Session ) ارسال خواهد شد ؟ براي مثال اگر شما يک برنامه مي سازيد که کامپترهاي مشخصي را در يک زمان خاص از انجام شدن عملياتي مطلع مي کند پروتکل UDP مناسب تر است . پروتکل UDP همچنين براي ارسال مقادير کوچک داده اي مناست تر مي باشد . 2 – تنظيم پروتکل : براي تنظيم پروتکلي که مي خواهيد در برنامه تان از آن استفاده کنيد در زمان طراحي برنامه خاصيت Protocol کنترل WinSock را برابر sckTCPProtocol و يا sckUDPProtocol قرار دهيد . همچنين مي توانيد پروتکل خود را توسط کد زير تنظيم کنيد : WinSock.Protocol=sckTCPProtocol 3 – مشخص کردن نام کامپيوتان : براي اتصال به کامپيوتر راه دور بايستي آدرس IP و يا نام کامپوتر را بدانيد . نام کامپيوتر در Control Panel/Network/Identification موجود است . در صورتيکه مي خواهيد دو برنامه Client و Server خود را روي يک کامپيوتر تست کنيد از آدرس IP 127.0.0.1 براي هر دو استفاده کنيد اما اگر دو برنامه را روي دو کامپيوتر مجزا در شبکه قرار داده ايد با اجراي دستور ipconfig در DOS Prompt مي توانيد آدرس IP کامپيوتر ها را بدست آوريد . 4 – ايجاد اتصال TCP : در زمان ساخت برنامه اي که از پروتکل TCP استفاده مي کند ابتدا بايد تصميم بگيريد که اين برنامه Client است يا Server . براي ساخت يک برنامه Server بايستي روي يک پورت خاص Listen کنيد . زمانيکه Client تقاضاي يک اتصال را مي دهد ، برنامه Server مي تواند آنرا Accept کند و بنابراين اتصال کامل شده است . حال Client و Server مي توانند با هم ارتباط داشته باشند . مراحل زير ساخت يک سرور چت ساده بر مبناي TCP را نشان مي دهد : - از منوي Project گزينه Components را انتخاب کنيد و در ليست Component ها مورد Microsoft WinSock 6.0 را انتخاب کنيد . - يک کنترل WinSock در فرم خود قرار دهيد و نام آنرا tcpserver بگذاريد - دو textbox با نامهاي txtSendData و txtReceiveData و نيز يک دکمه در فرم قرار دهيد . - کد زير را در رويداد Form_Load بنويسيد : Tcpserver.LocalPort=1000 tcpserver.Listen - زمانيکه درخواستي از طرف Client مي آيد رويداد ConnectionRequest اجرا مي شود . در اين رويداد ابتدا بايد چک کنيد که حالت کنترل بسته باشد . اگر چنين نيست اتصال را قبل از پذيرفتن اتصال جديد ببنديد . سپس تقاضا را بر اساس پارامتر requestID مي پذيريم : Private Sub tcpserver_ConnectionRequest(ByVal requestID As Long) If tcpserver.State <> sckClosed Then tcpserver.Close tcpserver.Accept requestID End Sub - حال اتصال بين Client و Server برقرار شده است . کد زير را براي event مربوط به کليک دکمه Send بنويسيد : Tcpserver.SendData txtSendData.text - اگر داده اي از طرف Client بيايد رويداد DataArrival اجرا مي شود . کد زير را براي اين رويداد بنويسيد : Private Sub tcpserver_DataArrival(ByVal bytesTotal As Long) Dim strData As String tcpserver.GetData strData txtReceiveData.Text = strData End Sub - کد زير را براي رويداد Form_Unload بنويسيد : Tcpserver.Close مراحل ساخت يک TCP Client بصورت زير است : - يک کنترل WinSock در فرم قرار دهيد و نام آنرا tcpclient بگذاريد . - دو textbox با نامهاي txtsend و txtreceive و نيز يک دکمه با نام sendدر فرم قرار دهيد . - يک دکمه با نام connect در فرم قرار دهيد . - کد زير را براي متد Form_Load بنويسيد : tcpclient.RemoteHost=”yourservername”x tcpclient.RemotePort=1000 - کد زير را براي رويداد کليک شدن دکمه connect بنويسيد : tcpclient.Connect - کد زير را براي رويداد کليک شدن دکمه send بنويسيد : tctclient.SendData txtsend.Text - کد زير را براي رويداد DataArrival بنويسيد : Private Sub tcpclient_DataArrival(ByVal bytesTotal As Long) Dim strData As String tcpclient.GetData strData txtreceive.Text = strData End Sub - کد زير را باري رويداد Form_Unload بنويسيد : Tcpclient.Close کدهاي فوق يک سيستم Client-Server ساده را نشان مي دهد . فايل exe هر دو برنامه را بسازيد و آنها را اجرا کنيد تا بتوانيد سيستم خود را تست کنيد . 5 – پذيرفتن بيش از يک تقاضاي اتصال : Server اي که در بالا ساخته شد تنها مي تواند تقاضاي يک اتصال را بپذيرد . با استفاده از ايجاد يک آرايه از کنترل WinSock مي توان چندين تقاضاي اتصال را پذيرفت . براي اينکار کافي است يک کپي ( instance ) از کنترل بسازيم ( با تنظيم خاصيت Index ) و متد Accept را براي instance جديد بکار ببريم . فرض کنيد يک کنترل WinSock با نام sckServer در فرم داريم که خاصيت Index آنرا صفر قرار داده ايم . همچنين يک متغير intMax از نوع Long تعريف مي کنيم که تعداد اتصالات همزمان به Server را نگه مي دارد . در event مربوط به Form_Load کد زير را بنويسيد : intMax=0 sckServer(0).LocalPort=1000 sckServer(0).Listen هر بار که تقاضاي يک اتصال مي رسد کد ابتدا تست مي کند که مقدار Index چقدر است . اگر مقدار Index صفر باشد متغير intMax يکي افزايش مي يابد و از intMax براي ساخت يک instance جديد از کنترل استفاده مي شود . حال از اين instance براي پذيرفتن تقاضاي اتصال استفاده مي گردد . براي اينکار کد زير را براي رويداد ConnectionRequest بنويسيد : Private Sub sckServer_ConnectionRequest(Index As Integer, ByVal requestID As Long) If Index = 0 Then intmax = intmax + 1 Load sckServer(intmax)x sckServer(intmax).LocalPort = 0 sckServer(Index).Accept requestID End If End Sub 6 – ايجاد اتصال UDP : ساخت يک برنامه UDP ساده تر از برنامه هاي TCP است زيرا پروتکل UDP به اتصال نياز ندارد . در برنامه TCP بالا يک کنترل WinSock بايستي حتماً Listen مي کرد و يک کنترل ديگر يک اتصال را توسط متد Connect ايجاد نمود . در عوض پروتکل UDP نيازي به اتصال ندارد . براي ارسال داده بين دو کنترل WinSock سه مرحله بايستي انجام شود : - پارامتر RemoteHost برابر نام کامپيوتر مقابل است . - پارامتر RemotePort برابر پارامتر LocalPort کامپيوتر مقابل - استفاده از متد Bind براي مشخص کردن LocalPort چون هر دو کامپيوتر از نظر ارتباط مساوي هستند ، اين نوع برنامه ها را Peer-to-Peer گويند . براي نمونه از کد زير براي ساخت يک برنامه chat استفاده مي کنيم : - يک کنترل WinSock در فرم قرار دهيد و نام آنرا udppeerA بگذاريد . - خاصيت Protocol آنرا UDPProtocol قرار دهيد . - دو textbox با نامهاي txtsend و txtreceive و نيز يک دکمه در فرم قرار دهيد . - کد زير را براي متد Form_Load بنويسيد : udppeerA.RemoteHost=”nameofpeerB”x udppeerA.RemotePort=1001 udppeerA.Bind 1002 - کد زير را براي event مربوط به کليک دکمه بنويسيد : udppeerA.SendData txtsend.text - کد زير را براي رويداد DataArrival بنويسيد : Dim strData as String udppeerA.GetData strData txtreceive.Text=strData براي ساخت UDP peerB مشابه مراحل بالا عمل کنيد فقط خاصيت RemoteHost آنرا نام کامپيوتر PeerA و خاصيت RemotePort آنرا 1002 و خاصيت Bind آنرا 1001 قرار دهيد . بررسی خواص کنترل WinSock : ByteReceived : مقدار داده دريافت شده ( موجود در بافر receive ) را نشان مي دهد . توسط متد GetData مي توان اين داده را دريافت نمود . LocalHostName : نام ماشين محلي را نشان مي دهد . اين پارامتر فقط خواندني است . LocalIP : آدرس IP ماشين محلي را بصورت يک string برمي گرداند . اين پارامتر فقط خواندني است . LocalPort : براي خواندن و يا تنظيم شماره پورت محلي بکار مي رود . Protocol : براي خواندن و يا تنظيم پروتوکل مورد استفاده توسط کنترل WinSock بکار مي رود . RemoteHost : براي خواندن و يا تنظيم نام يا آدرس IP ماشين راه دور بکار مي رود . RemoteHostIP : آدرس IP ماشين راه دور را برمي گرداند : ۱- براي برنامه هاي Client بعد از زمانيکه يک اتصال توسط متد Connect پذيرفته شد ، اين خاصيت حاوي آدرس IP ماشين راه دور است . ۲ - براي برنامه Server ، بعد از آمدن يک Connection Request اين خاصيت شامل آدرس IP ماشين راه دور است . ۳ - در زمان استفاده از پروتکل UDP بعد از اينکه رويداد Data Arrival رخ داد اين خاصيت حاوي آدرس IP ماشيني است که داده را فرستاده . RemotePort : براي خواندن و يا تنظيم شماره پورت ماشين راه دوري که مي خواهيد به آن متصل شويد بکار مي رود . SocketHandle : مقداري را برمي گرداند که مرتبط با سوکتي است که کنترل WinSock را مديريت مي کند و براي ارتباط با لايه WinSock بکار مي رود . اين پارامتر فقط خواندني است و تنها براي ارسال به API هاي WinSock طراحي شده است . State : وضعيت کنترل WinSock را نشان مي دهد . وضعيتهاي ممکن براي State عبارتند از : ۱ - sckClosed : اتصال بسته است . ۲ - sckOpen : اتصال باز است . ۳ - sckListening : حالت گوش دادن به پورت 4 - sckConnectionPending : معلق شدن اتصال ۵ - sckResolvingHost : تصميم گيري در مورد ميزبان ۶ - sckHostResolved : در مورد ميزبان تصميم گيري شد . ۷ - sckConnecting : حالت برقراري ارتباط ۸ - sckConnected : ارتباط برقرار شد . ۹ - sckClosing : حالت قطع اتصال ۱۰ - sckError : حالت خطا بررسی متدهای کنترل WinSock : متد Accept : تنها براي برنامه هاي TCP Server بکار مي رود . اين متد براي پذيرفتن يک اتصال در زمان مديريت رويداد ConnectionRequest استفاده مي شود . متد Bind : اين پارامتر LocalPort و LocalIP يک اتصال را مشخص مي کند . متد Close : براي بستن يک اتصال TCP و يا بستن يک listening socket بکار مي رود . متد GetData : بلوک جاري داده دريافت شده را گرفته و آنرا در متغيري از نوع Variant ذخيره مي کند . شکل کلي اين متد بصورت زير است : WinSock.GetData data[,type][,maxlen]x که data داده دريافتي است . اگر داده کافي موجود نباشد data برابر empty خواهد بود . type نوع داده دريافتي است که مي تواند مقادير زير باشد : vbByte - vbInteger - vbLong - vbSingle - vbDouble - vbDate - vbBoolean - vbError - vbString - vbArray+vbByte maxlen حداکثر سايز را در زمان دريافت يک byte Array و يا يک string مشخص مي کند . متد Getdata در رويداد Data Arrival استفاده مي شود که اين رويداد يک پارامتر با نام TotalBytes دارد . اگر maxlen اي که شما تعيين کرده ايد کمتر از TotalBytes باشد پيغام هشدار شماره ۱۰۰۴۰ دريافت مي کنيد بدين معني که بايتهاي باقيمانده گم خواهند شد . متد Listen : يک سوکت مي سازد و آنرا در حالت Listen قرار مي دهد . اين متد تنها در اتصالات TCP بکار ميرود . متد PeekData : مشابه GetData است با اين تفاوت که داده را از صف ورودي حذف نمي کند . اين متد تنها براي اتصالات TCP بکار مي رود . متد SendData : براي ارسال داده به کامپيوتر راه دور بکار مي رود . بررسي event هاي کنترل WinSock : رويداد Close : زماني رخ مي دهد که کامپيوتر راه دور اتصال را ببندد . رويداد Connect : بعد از اينکه يک اتصال به Server ايجاد شد روي مي دهد . شکل کلي آن بصورت زير است : Private Sub WinSock_Connect(ErrorOccurred As Boolean)x که پارامتر ErrorOccurred دو مقدار دارد : اگر True باشد يعني اتصال Fail شده است و اگر False باشد يعني اتصال با موفقيت انجام شده است . با رويداد Connect مي توانيد error هايي که در زمان فرايند باز کردن اتصال برگردانده شده را چک کنيد . رويداد ConnectionRequest : زماني رخ مي دهد که يک کامپيوتر راه دور تقاضاي يک اتصال را بدهد . اين رويداد فقط براي برنامه هاي TCP Server بکار مي رود . رويداد DataArrival : زماني رخ مي دهد که داده جديدي بيايد . رويداد Error : زماني رخ مي دهد که يک خطا در فرايند ارتباط رخ دهد ( مثلاً Failed to Connect و يا Failed to Send ) . شکل کلي آن بصورت زير است : Private WinSock_Error(number as Integer,description as String,scode as Long,source as String,helpfile as String,helpcontext as Long,canceldisplay as Boolean)x number شماره کد خطا است . description توضيحي در مورد خطا است . source توصيف منبع خطا canceldisplay : مشخص مي کند آيا پيغام خطاي پيش فرض نشان داده شود يا نه رويداد SendComplete : زماني رخ مي دهد که يک عمل Send تکميل شده باشد . رويداد SendProgress : زماني رخ مي دهد که کنترل شروع به ارسال داده نمايد . شکل کلي آن بصورت زير است : WinSock_SendProgress (bytesSent As Long, bytesRemaining As Long)x که bytesSent تعداد بايتهاي ارسال شده و bytesRemaining تعداد بايتهاي باقيمانده است . نکته ۱ : براي دريافت جدول خطاهاي WinSock با من تماس بگيريد . نکته ۲ : موضوع بعدي : آشنايي با الگوريتم Collision Detection در ساخت انيميشن هاي دوبعدي |
||