Beginning DirectX9 (Phần 1)
Bạn đang xem 20 trang mẫu của tài liệu "Beginning DirectX9 (Phần 1)", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên
Tài liệu đính kèm:
beginning_directx9_phan_1.pdf
Nội dung text: Beginning DirectX9 (Phần 1)
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN © 2004 by Premier Press, a division of Course Technology. All rights Senior Vice President, reserved. Course PTR Group: No part of this book may be reproduced or transmitted in any form or by any Andy Shafran means, electronic or mechanical, including photocopying, recording, or by Publisher: any information storage or retrieval system without written permission from Stacy L. Hiquet Course PTR, except for the inclusion of brief quotations in a review. Senior Marketing Manager: The Premier Press logo and related trade dress are trademarks of Premier Sarah O’Donnell Press and may not be used without written permission. Marketing Manager: DirectX is a registered trademark of Microsoft Corporation in the U.S. and/or Heather Hurley other countries. Manager of Editorial © Microsoft Corporation, 2002. All rights reserved. Services: All other trademarks are the property of their respective owners. Heather Talbot Important: Course PTR cannot provide software support. Please contact the Senior Acquisitions Editor: appropriate software manufacturer’s technical support line or Web site for Emi Smith assistance. Associate Marketing Course PTR and the author have attempted throughout this book to Manager: distinguish proprietary trademarks from descriptive terms by following the Kristin Eisenzopf capitalization style used by the manufacturer. Project/Copy Editor: Information contained in this book has been obtained by Course PTR from Karen A. Gill sources believed to be reliable. However, because of the possibility of human Technical Reviewer: or mechanical error by our sources, Course PTR, or others, the Publisher Joseph Hall does not guarantee the accuracy, adequacy, or completeness of any Retail Market Coordinator: information and is not responsible for any errors or omissions or the results Sarah Dubois obtained from use of such information. Readers should be particularly aware Interior Layout: of the fact that the Internet is an ever-changing entity. Some facts may have Marian Hartsough changed since this book went to press. Cover Designer: Educational facilities, companies, and organizations interested in multiple Steve Deschene copies or licensing of this book should contact the publisher for quantity CD-ROM Producer: discount information. Training manuals, CD-ROMs, and portions of this book Brandon Penticuff are also available individually or can be tailored for specific needs. Indexer: ISBN: 1-59200-349-4 Sharon Shock Library of Congress Catalog Card Number: 2004090736 Proofreader: Printed in the United States of America Sean Medlock 04 05 06 07 08 BH 10 9 8 7 6 5 4 3 2 1 Course PTR, a division of Course Technology 25 Thomson Place Boston, MA 02210 2
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Thông tin về tác giả WENDY JONES là người đã dành trọn niềm đam mê của cô ấy cho máy tính ngay từ lần đầu tiên cô được nhìn thấy chiếc máy Apple IIe khi còn học ở trường phổ thông. Kể từ khi tiếp cận với nó, cô ấy đã dành mọi khoảng thời gian rảnh rỗi vào công việc học lập trình BASIC và kỹ thuật đồ hoạ, vẽ những ý tưởng của mình lên giấy vẽ và thực hiện nó trên máy tính. Ngoài BASIC thì cô cũng đã lần lượt tiếp cận với các ngôn ngữ khác như Pascal, C, Java và C++. Nghề nghiệp của Wendy là một chuỗi thay đổi như chính sự phát triển của máy tính vậy, từ bỏ môi trường lập trình DOS, cô tự trang bị cho mình những kiến thức về lập trình Window và sao đó rất nhanh chóng nhảy sang thế giới .NET. Mặc dù các công ty Internet đem lại rất nhiều tiền, nhưng đó không phải là tất cả, chính vì thế Wendy đã mở rộng kỹ năng lập trình của mình sang lĩnh vực lập trình games và dồn tất cả sức lực của mình cho niềm đam mê đó. Wendy đã hiện thực hoá được niềm đam mê này khi cô nhận được một đề nghị làm việc cho công ty Atari’s Humongous Entertainment với tư cách là một lập trình viên. Trong xuốt quá trình làm việc ở Atari, cô ấy đã làm việc trên cả hai môi trường PC và máy chơi game cá nhân, bị quấn theo hàng loạt những thách thức mà công việc đó đem đến. Hiện tại Wendy đang làm việc các phần mềm dành cho PocketPC và trên những thiết bị chơi game cầm tay. Nếu bạn có bất cứ góp ý hay những câu hỏi về quyển sách này, bạn có thể liên hệ với cô theo địa chỉ mail: [email protected] 3
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Bố cục của sách Lời giới thiệu PHẦN I. NHỮNG KIẾN THỨC NỀN TẢNG Chương 1. DirectX là gì, tại sao và làm thế nào để sử dụng nó Chương 2. Ví dụ đầu tiên sử dụng DirectX Chương 3. Phông nền, khung hình, hoạt cảnh. PHẦN II. NHỮNG THÀNH PHẦN CƠ BẢN CỦA MỘT THẾ GIỚI 3D Chương 4. Những kiến thức cơ bản về 3D Chương 5. Ma trận, các phép biến đổi và phép xoay trong không gian Chương 6. Bảng mầu, vật liệu phủ và ánh sáng trong không gian Chương 7. Chia nhỏ và làm mịn đối tượng Chương 8. Vật thể, điểm chèn và các hiệu ứng PHẦN III. NHỮNG KIẾN THỨC BỔ XUNG Chương 9. Sử dụng DirectInput Chương 10. Hiệu ứng âm thanh bằng DirectSound Chương 11. Xây dựng một dự án mẫu PHẦN IV. PHỤ LỤC Phụ lục A. Giải đáp các bài tập cuối các chương Phụ lục B. Cách sử dụng CD-ROM Giải thích các thuật ngữ Chỉ mục các từ khoá 4
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN LỜI GIỚI THIỆU ập trình game có thể nói là một công việc cực kỳ thú vị trong thế giới lập trình bởi nó đem lại cho bạn cơ hội được tạo ra một thế giới ảo nơi mà những sinh vật huyền bí, những vùng đất chưa hề được biết đến ngay cả trong những giấc mơ. LLi ệu bạn có thể tìm thấy điều này ở một nơi nào khác? Với nó, bạn có thể đem đến cho mọi người khả năng trở thành bất cứ một nhân vật nào, một con người nào mà họ mong muốn và hơn cả là một môi trường để họ có thể sống theo đúng sở thích. Cũng chính bởi thế mà ngành công nghiệp Game đã phát triển không ngừng, vượt qua mọi giới hạn và kéo theo nó là phát triển của công nghệ phục vụ cho nó. Chỉ vài năm trước đây, có lẽ ở cấp độ người sử dụng còn chưa hề biết đến các thiết bị thể hiện hình ảnh 3D ngoại trừ các máy trạm đắt tiền SGI dựa trên nền tảng OpenGL. Tại thời điểm đó có thể nói OpenGL mới chỉ đang ở thời kỳ phôi thai. Đến khi các máy tính cá nhân PC đã được phổ biến hơn, OpenGL đã mở rộng nền tảng của mình và điều đó thực sự đã đem đến một cú huých mạnh đầu tiên trong lịch sử phát triển game cũng như những ứng dụng mô phỏng không gian 3D. Tại thời điểm đó, Windows vẫn chưa phải là nền tảng tốt nhất để phát triển games, tuy nhiên họ cũng rất nhanh chóng nhận ra những lợi nhuận từ ngành công nghiệp này. Chính vì thế phiên bản DirectX đầu tiên cũng sớm được Microsoft tung ra. Tuy nhiên trong thời gian đầu này thì sự phổ biến của nền tảng này vẫn còn rất hạn chế do OpenGL đã được thừa nhận là phương thức chuẩn để phát triển đồ hoạ 3D trên môi trường Windows. Ngày nay, hầu hết các game phát triển trên PC bán trên thị trường đều được xây dựng trên nền tảng DirectX, nó đã giúp cho những game thủ có thể tận hưởng được một công nghệ đồ hoạ tiên tiến và hiện thực nhất về thế giới. Những cần phải có những gì để tiếp cận công nghệ này? Những kinh nghiệm và kiến thức về lập trình C++ cũng như lý thuyết lập trình hướng đối tượng (OOP) sẽ giúp bạn có thể hiểu được tất cả những bài giảng được trình bày trong quyển sách này. Ngoài ra bạn cũng nên trang bị thêm cho mình một vài kỹ năng về toán học và hình hoạ, tuy nhiên hầu hết những khái niệm toán học sử dụng cũng sẽ được giải thích chi tiết. Những hiểu biết về Visual Studio .NET hay bất kỳ sản phẩm nào trong bộ Visual Studio cũng sẽ rất là hữu ích. 5
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Làm thế nào để sử dụng quyển sách này hiệu quả? Quyển sách này được chia làm 3 phần chính. Phần đầu tiên sẽ mô tả cở bản về DirectX, làm thế nào để thiết lập và chạy một ứng dụng sử dụng nền tảng này. Phần thứ hai sẽ cung cấp cho bạn những khái niệm cơ bản phục vụ quá trình thiết kế và xây dựng một môi trường 3D, kèm theo đó là giới thiệu những khái niệm về 3D và Direct3D. Phần 3 và phần cuối sẽ xoay quanh các kiến thức khác của nền tảng DirectX như xử lý âm thanh thông qua DirectSound, lấy các thông tin đầu vào từ người dùng thông qua DirectInput. Quyển sách kết thúc bằng một dự án minh hoạ nhỏ với hi vọng đem đến cho bạn một cái nhìn toàn cảnh hơn về tất các các kiến thức chúng ta đã học và áp dụng cụ thể chúng như thế nào. Nếu bạn đã từng làm quen với môi trường DirectX và cũng đã từng viết một vài ứng dụng sử dụng nền tảng này, bạn có thể bỏ qua phần một. Tôi nghĩ rằng bất kỳ ai khi mới tiếp cận với môi trường lập trình game và DirectX nên đọc quyển sách này một cách tuần tự để có thể hiểu hết được những khả năng mà DirectX có thể đem lại. 6
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN PHẦN I KIẾN THỨC NỀN TẢNG Chương 1. DirectX là gì, tại sao và làm thế nào để sử dụng nó Chương 2. Ví dụ đầu tiên sử dụng DirectX Chương 3. Phông nền, khung hình, hoạt cảnh. 7
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN CHƯƠNG 1 DirectX là gì, Tại sao và Làm thế nào để sử dụng nó irectX về thực chất là một hệ thống các giao diện lập trình ứng dụng (API) phục vụ cho quá trình phát triển các game trên nền tảng hệ điều hành Windows. Nếu Dnh ư tại thời điểm một vài năm trước, những người phát triển game thường phải vật lộn với những vấn đề nảy sinh từ sự không tương thích của phần cứng - không thể phát triển những sản phẩm game để tất cả mọi người đều có thể tận hưởng được những tính năng của game đó. Thì ngày nay, Microsoft đã đem tới một nền tảng DirectX. Nó cung cấp cho những người phát triển game một phương thức đơn nhất, một nền tảng thư viện API “sạch” (đã được chuẩn hoá) để họ có thể viết và chạy trên hầu hết các nền tảng mà không cần phải lo ngại nhiều về phần cứng của PC. Chỉ một vài năm sau khi phiên bản DirectX được đề xuất, số lượng game được phát triển trên nền tảng Windows đã tăng lên một cách nhanh chóng. Trong chương này, chúng ta sẽ đề cập tới các vấn đề: DirectX là gì vậy Tại sao nó lại hữu dụng đến thế Thành phần nào đã xây dựng nên nền tảng DirectX API DirectX là gì? DirectX là một tập hợp thư viện các hàm API được Microsoft thiết kế để cung cấp cho những người phát triển game một giao diện lập trình cấp thấp để liên kết tới các phần cứng của PC chạy trên hệ điều hành Windows. Phiên bản hiện tại của nó là 9.0(c), mỗi một đối tượng API của DirectX cung cấp một khả năng truy cập khác nhau tới từng loại phần cứng của hệ thống, nó bao gồm hệ thống đồ hoạ, âm thanh và kết nối mạng, tất cả chúng đều được xây dựng trên một giao diện chuẩn. Giao diện này cho phép những người phát triển có thể viết những game của họ bằng cách sử dụng tập hợp cách hàm này lại với nhau mà không cần phải quan tâm nhiều tới phần cứng mà nó sẽ được sử dụng. 8
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Những thành phần tạo nên DirectX Hệ thống thư viện API của DirectX được phân chia ra làm nhiều thành phần nhỏ, mỗi thành phần này đảm nhiệm những nhiệm vụ khác nhau của hệ thống. Chúng có thể được sử dụng hoàn toàn độc lập với nhau, bạn có thể chỉ cần thêm những thành phần nào mà game của bạn cần tới mà thôi. Sau đây là danh sách các thành phần đó: DirectX Graphic. Thành phần này đảm nhiệm tất cả các chức năng kết xuất đồ hoạ của hệ thống. Nó cung cấp những hàm API để người dùng có thể quản lý quá trình vẽ 2D cũng như 3D, ngoài ra nó cũng hỗ trợ cả quá trình khởi tạo và xác lập độ phân giải cho game của bạn. DirectInput. Tất cả những gì người dùng nhập vào sẽ được quản lý bởi các hàm API trong thành phần này. Nó bao gồm khả năng hỗ trợ các thiết bị như bàn phím, chuột, gamepad, joystick. Với phiên bản mới nhất hiện nay cũng đã hỗ trợ cả các thiết bị tương tác thực (force-feedback) như cần lái, chân ga cho các game đua tốc độ. DirectPlay. Khả năng kết nối mạng được cung cấp cho những hệ thống game của bạn thông qua thành phần DirectPlay này. Những hàm phục vụ kết nối này đem đến cho bạn khả năng phát triển ứng dụng có thể giao tiếp với bất kỳ một máy nào khác, cho phép không chỉ một người có thể chơi game đó. DirectPlay đem đến cho bạn một giao diện lập trình cấp cao nhằm giúp bạn tránh khỏi những vấn đề khó khăn trong quá trình lập trình ứng dụng liên kết mạng. DirectSound. Nếu bạn muốn chèn thêm các hiệu ứng âm thanh hoặc nhạc nền, bạn sẽ cần phải sử dụng tới những hàm API do thành phần này cung cấp. Chức năng của DirectSound là cho phép bạn có thể tải và chơi một hoặc nhiều file nhạc dạng WAV cũng như toàn bộ khả năng điều khiển quá trình chơi nhạc đó. DirectMusic. Thành phần này mang đến cho bạn khả năng tạo các bản nhạc “động” (tại thời điểm chương trình chạy, không cần phải lưu trữ trong cơ sở dữ liệu của game). Nó có khả năng hỗ trợ hầu hết các chức năng chính của một phần mềm chơi nhạc midi như tự chạy lại theo một qui trình đã xác lập, biến đổi âm lượng để phù hợp với hoạt cảnh trong game, thay đổi nhịp của giai điệu và nhiều chức năng cao cấp khác. DirectShow. Bạn có thể cắt một hoạt cảnh phim hoặc lồng ghép âm thanh và game của mình thông qua thành phần này. AVI, MP3, MPEG và ANF chỉ là một trong số rất nhiều các loại định dạng file mà DirectShow hỗ trợ. Với DirectShow, bạn sẽ không phải tải tất cả các đối tượng file này lên bộ nhớ, thay vào đó là truy cập trực tiếp dữ liệu trên ổ cứng hoặc CD-ROM. DirectSetup. Sau khi game của bạn đã hoàn thành, bạn sẽ muốn phát hành. DirectSetup cung cấp cho bạn những chức năng giúp ứng dụng có thể tự động cài đặt những phiên bản DirectX mới nhất lên hệ thống của người sử dụng. Chú ý: Hệ thống đồ hoạ DirectX bao gồm toàn bộ các chức năng của cả DirectDraw và Direct3D. Phiên bản DirectX 7.0 là phiên bản cuối cùng phân tách hai thành phần này thành hai đối tượng độc lập. 9
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Tại sao DirectX lại cần thiết? Trước khi hệ điều hành Windows được phát hành, các lập trình viên chủ yếu phát triển các game trên nền tảng hệ điều hành DOS. Đấy là một hệ điều hành đơn nhiệm, không hỗ trợ giao diện đồ hoạ (non-GUI) và cách duy nhất để thực hiện các thao tác đồ hoạ này là truy cập trực tiếp vào phần cứng và thực thi chúng. Điều này đem đến cả những lợi ích cũng như những vấn đề khó khăn cần giải quyết. Ta có thể ví dụ ra, bởi vì sử dụng cách truy cập trực tiếp giữa mã game và phần cứng, lập trình viên có thể có được toàn bộ sức mạnh cũng như quyền điều khiển của hệ thống, điều nay đem đến khả năng hoạt động với hiệu suất và chất lượng cao của hệ thống game. Tuy nhiên, mặt trái của vấn đề là những nhà phát triển game này sẽ phải tự xây dựng cho mình những thư viện hỗ trợ cho từng loại thiết bị phần cứng nếu họ muốn game có khả năng hỗ trợ. Nó bao gồm ngay cả những thiết bị rất phổ thông như card âm thanh và đồ hoạ. Tại thời điểm đó, không phải tất cả các thiết bị đều tuân theo một chuẩn nhất định. Chính điều này đã làm cho quá trình thao tác trên màn hình đồ hoạ cực kỳ khó khăn đặc biệt khi bạn muốn phát triên các game có độ phân giải trên 320 x 240 pixel. Chỉ việc viết các game sử dụng độ phân giải 640 x 480 cũng đã khiến các lập trình viên phải truy cập trực tiếp vào bộ nhớ video và tự mình quản lý toàn bộ quá trình cập nhật các thanh ghi sử lý của card đồ hoạ đó. Và họ đã nghĩ rằng đã đến lúc cần phải tìm kiếm một cách đi khác đơn giản và hiệu quả hơn. Khi mà phiên bản Windows 3.1 chính thức phát hành, những vấn đề đó vẫn chưa hề được giải quyết. Hơn thế, bởi vì Windows chạy trên nền DOS nên chính nó vô tình đã làm giảm đi tài nguyên hệ thống giành cho các game và nó cũng đã loại bỏ toàn bộ khả năng truy cập trực tiếp mà những nhà phát triển game vẫn hay dùng trước đó từ rất lâu rồi. Vậy là hầu hết các game hỗ trợ Window tại thời điểm đó chủ yếu là những game đánh bài và các game đơn giản khác. Trong khi đó các game hành động vẫn tiếp tục được phát triển trên nền tảng DOS. Mặc dù trước đó, Microsoft đã cố gắng cung cấp cho người phát triển một cách cách truy cập nhanh hơn tới các thiết bị đồ hoạ thông qua hệ thống thư viện có tên WinG. Nó là tiền thân của DirectX và chỉ hỗ trợ rất ít chức năng. WinG thực sự là một ý tưởng hay, tuy nhiên nó vẫn chưa thể đem tới cho những người phát triển khả năng truy cập hệ thống tốt hơn so với những cách mà họ vẫn thích làm trên nền hệ điều hành DOS. Chính vì thế, Microsoft đã có những cải tiến và bổ xung thêm khá nhiều chức năng trong phiên bản đầu tiên DirectX, hay còn được biết tới với cái tên GameSDK (bộ công cụ phát triển game). DirectX đã cung cấp cho các lập trình viên một thư viện độc lập để phát triển, nó là lớp trung gian giữa game và các phần cứng của PC. Lúc này thì quá trình thao tác đồ hoạ đã trở nên dễ dàng hơn rất nhiều. Tuy nhiên phiên bản đầu tiên của DirectX vẫn chưa hỗ trợ được nhiều dạng phần cứng khác nhau, nhưng đó thực sự đã là một thay đổi rất có ý nghĩa và thực sự đáp ứng được mong muốn của những người phát triển game. Chỉ một vài năm sau, họ đã phát hành phiên bản DirectX 9, mỗi một phiên bản đều đã được bổ xung và tăng khả năng hỗ trợ những công nghệ mới như kết nối mạng, kết xuất âm thanh, hình ảnh và hỗ trợ rất nhiều dạng thiết bị đầu vào mới. 10
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Các thành phần của DirectX được kết hợp lại như thế nào DirectX đem đến cho những lập trình viên khả năng phát triển các game trên môi trường Window một cách đơn giản hơn. Đó là một hệ thống thư viện được tổ chức dễ hiểu hơn với sự hỗ trợ đầy đủ của Microsoft, những người cuối cùng cũng nhận ra sự quan trọng và những lợi nhuận có thể đem lại từ ngành công nghiệp giải trí game trên máy tính. Qua từng năm, các phiên bản của DirectX đã dần dần được bổ xung, phát triển thêm nhằm cung cấp sự hỗ trợ ngày càng nhanh hơn cũng như cho những phần cứng mới hơn. Microsoft muốn đảm bảo rằng mỗi một thành phần của DirectX có khả năng hỗ trợ các game đã được viết cho phiên bản trước sẽ không gặp bất kỳ vấn đề gì khi phiên bản mới được cài đặt vào hệ thống. Để thực hiện được điều này, Microsoft đã thiết kế DirectX trên nền tảng COM. Mô hình đối tượng thành phần (COM – Component Object Model) Các hàm DirectX API được thiết kế trên nền tảng Component Object Model (COM). Đối tượng COM bao gồm tập hợp các giao diện nhằm cung cấp các phương thức cho những người lập trình có thể truy cập DirectX. Đối tương COM có thể hiểu nôm na là các tệp tin DLL đã được đăng ký với hệ thống. Đối với những đối tượng DirectX COM, quá trình này sảy ra khi DirectX được cài đặt vào hệ thống. Mặc dù nó khá giống với các đối tượng C++, nhưng các đối tượng COM cần thiết phải có một giao diện để truy cập tới các phương thức bên trong nó. Đây thực sự là một thuận lợi so với cách sử dụng các đối tượng thông thường bởi vì ta có thể xây dựng rất nhiều kiểu giao diện cho một đối tượng COM, nó rất thuận lợi cho việc tương thích ngược (hỗ trợ các ứng dụng đã xây dựng trước đó). Điều này được thể hiện, mỗi một phiên bản của DirectX đều chứa những giao diện mới của DirectDraw và nó có thể truy cập thông qua các hàm API, ngoài ra nó vẫn chứa phiên bản trước đó nên nó không hề tác động tới những mã lệnh đã được viết trước đó. Những game được viết để sử dụng Direct7 hoàn toàn có thể hoạt động với Direct9 mà không gặp bất kỳ vấn đề gì. Thêm một lợi điểm nữa của kỹ thuật này là các đối tượng COM có khả năng hoạt động với rất nhiều ngôn ngữ khác chứ không phải chỉ bằng C++. Người lập trình có thể sử dụng Visual Basic, C++ hay C# mà vẫn có thể sử dụng cùng một thư viện DirectX. Kiến trúc của DirectX Nền tảng của DirectX bao gồm 2 lớp: lớp hàm API và lớp (giao diện) thiết bị (HAL – Hardware Abstraction Layer). Các hàm trên lớp API sẽ kết nối tới phần cứng thông qua lớp HAL. Lớp HAL cung cấp một giao diện đã được chuẩn hoá cho DirectX, ngoài ra nó có khả năng “nói chuyện” trực tiếp với phần cứng thông qua các xác lập đối với các loại thiết bị điều khiển của chính nó. Và bởi vì HAL cần phải biết cách hoạt động của phần cứng cũng như của các thiết bị điều khiển nên HAL được viết bởi chính các nhà phát triển phần cứng. Bạn không bao giờ phải kết nối tới HAL một cách trực tiếp trong quá trình viết game của mình. Thay vào đó chúng ta sẽ sử dụng kết nối gián tiếp thông qua các hàm mà DirectX cung cấp. Hình minh hoạ 1.1 thể hiện sơ đồ minh hoạ sự phân lớp giữa Direct3D và các thiết bị điều khiển phần cứng. 11
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Hình 1.1 – Mô hình phân lớp DirectX Trong các phiên bản trước, DirectX còn được phân chia lớp HAL ra thành một lớp khác nữa với tên gọi là Lớp mô phỏng phần cứng (HEL – Hardware Emulation Layer) hay thiết bị RGB. Lớp HEL sẽ mô phỏng một vài chức năng mà thiết bị phần cứng còn thiếu. Điều này cho phép cả những thiết bị rẻ tiền cũng như đắt tiền đều có khả năng cung cấp những chức năng giống nhau cho hàm DirectX API. Mặc dù các chức năng mà HEL cung cấp là giống nhau nhưng hiệu suất cũng như chất lượng sẽ không bằng. Bởi vì HEL thực hiện các chức năng này bằng phần mềm, thông thường thì tốc độ khung hình thể hiện sẽ thấp hơn so với chức năng được xây dựng sẵn trên phần cứng. Lớp HEL này đã được thay đổi thành kiểu thiết bị phần mềm có thể cắm và chạy (tương tự thiết bị phần cứng Plug&Play). Thiết bị này thực hiện quá trình hiển thị thông qua các hàm được người lập trình viết nên. Hầu hết những game được thương mại đều không còn sử dụng kỹ thuật này nữa mà thay vào đó là yêu cầu thiết bị hiển thị hỗ trợ 3D bởi vì gần như tất cả các máy tính hiện nay đều hỗ trợ nó. Chú ý: Bộ công cụ hỗ trợ phần cứng của DirectX (DDK – Device Driver Kit) cung cấp những thông tin cần thiết giúp bạn có khả năng tự phát triển một thiết bị cắm chạy được mô phỏng bằng phần mềm. Tổng kết chương Trong chương vừa rồi chúng ta đã tìm hiểu về những công nghệ ẩn chứa bên dưới những hình ảnh cũng như trả lời câu hỏi DirectX là gì, tại sao và sự cần thiết của nó như thế nào. Trong chương tiếp theo chúng ta sẽ đi sâu hơn vào những vấn đề kỹ thuật, giúp bạn có thể từng bước xây dựng được những ứng dụng đầu tiên sử dụng công nghệ DirectX. 12
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN CHƯƠNG 2 CHƯƠNG TRÌNH ĐẦU TIÊN rong phần này sẽ trình bày một ví dụ nhỏ sử dụng DirectX. Chúng ta sẽ từng bước đi qua tất cả những tiến trình cần thiết cho một chương trình sử dụng công nghệ TDirectX. Hầu hết các ví dụ đi kèm với bộ công cụ hỗ trợ phát triển ứng dụng DirectX (DirectX SDK) được đặt trong thư mục sample, thư mục này chứa tập hợp rất nhiều mã nguồn và làm bạn sẽ phải tốn rất nhiều thời gian công sức để tìm hiểu nó. Trong những ví dụ và hướng dẫn chúng ta sẽ làm theo dưới đây, mặc dù những flatform đó có thể nó đã được viết sẵn trong ví dụ đi kèm của bộ SDK nhưng chúng ta sẽ không sử dụng lại. Điều đó sẽ giúp bạn hình dung được một cách rõ ràng hơn những công việc bạn phải làm để biến những ý tưởng của mình thành những game thực sự. Dưới đây là nội dung những phần chúng ta sẽ xem xét trong chương này: Làm thế nào để tạo một dự án mới Xác lập các thông số cho ứng dụng Làm thế nào để khởi tạo ứng dụng với DirectX Làm thế nào để xoá màn hình ứng dụng Làm thế nào để thể hiện nền cho ứng dụng Làm thế nào để game của bạn phóng lên toàn màn hình Làm thế nào đề nhận biết khả năng hỗ trợ, cấu hình của hệ thống Xây dựng một dự án mới. Bước đầu tiên bạn cần làm cho bất kỳ một ứng dụng nào là tạo một project mới trong Visual Studio. Bạn hãy chạy Visual Studio .Net với chế độ không mở bất kỳ một project nào. 1. Lựa chọn New, Project từ thực đơn File, hộp thoại New Project sẽ xuất hiện có dạng tương tự hình minh hoạ 2.1 dưới đây. 13
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN 2. Thay đổi tên của project thành example1 và lựa chọn loại ứng dụng là Win32 Project từ danh sách các project mẫu (project templates). Kích chọn nút OK để hoàn tất, một hộp thoại có tên Application Wizard sẽ xuất hiện với 2 tabs cho bạn lựa chọn và xác lập là: Overview, Application Settings. (hình minh hoạ 2.2) 3. Lựa chọn Application Settings tab và chắc chắn rằng lựa chọn Empty Project (tạo một dự án rỗng) đã được tích chọn, hình minh hoạ 2.3. 14
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN 4. Kích chọn nút Finish để kết thúc. Chèn thêm mã lệnh cho chương trình Sau khi bạn thực hiện các bước trên, Visual Studio đã tạo ra một project mới cho bạn tuy nhiên nó vẫn chưa chứa bất kỳ thứ gì cả. Bước tiếp theo, chúng ta sẽ tiến hành thêm mã lệnh để khởi tạo cho một ứng dụng trong môi trường window. Bạn hãy bắt đầu bằng cách chèn thêm một tệp tin mới chứa mã nguồn và dự án. 1. Lựa chọn Add New Item từ thực đơn Project. Một hộp thoại tương tự hình minh hoạ 2.4 phía dưới đây sẽ xuất hiện. 2. Lựa chọn kiểu file C++ File (.cpp) từ danh sách kiểu tệp tin mẫu (Templates). 3. Thay đổi tên tệp tin thành winmain.cpp 4. Kích chuột vào nút Open để mở tệp tin đó và bắt đầu viết mã lệnh. 15
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN WinMain Phần đầu tiên của bất kỳ một ứng dụng nào bao giờ cũng là Entry point (điểm bắt đầu, khởi tạo). Trong các ứng dụng được viết dưới dạng Consolo, hàm bắt đầu có tên là main(), trong khi đó hàm bắt đầu của một ứng dụng Window bao giờ cũng là WinMain(). Hàm WinMain này được dùng để khởi tạo ứng dụng của bạn như tạo cửa sổ giao diện chương trình, thiết lập hàm quản lý sự kiện. Tại tời điểm này bạn có thể gõ lại theo đoạn code được liệt kê dưới đây hoặc mở file winmain.cpp trong thư mục chapter2\example1. // khai báo sử dụng thư viện mẫu của Windows - cần thiết cho tất cả các ứng dụng #include HINSTANCE hInst; // khai báo biến toàn cục chứa con trỏ instance (biến thể) của chương trình HWND wndHandle; // biến toàn cục chứa con trỏ quản lý cửa sổ ứng dụng // khai báo hàm bool initWindow( HINSTANCE hInstance ); LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ); // Đây là hàm winmain, hàm được gọi đầu tiền của mọi ứng dụng trong window int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow ) { // khởi tạo cửa sổ ứng dụng if ( !initWindow( hInstance ) ) return false; // vòng lặp chính dùng để quản lý thông điệp: MSG msg; ZeroMemory( &msg, sizeof( msg ) ); while( msg.message!=WM_QUIT ) { // Kiểm tra các sự kiện được gửi tới trong hàng đợi của ứng dụng while (GetMessage(&msg, wndHandle, 0, 0) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } } return (int) msg.wParam; } Phần quan trọng nhất của hàm trên là phần quản lý các thông điệp của chương trình. Phần này đảm nhiệm quá trình quản lý các thông điệp mà window gửi tới ứng dụng. Hàm GetMessage làm nhiệm vụ kiểm tra các thông điệp trong hàng đợi của chương trình, nhận dạng những thông điệp mà người dùng gửi tới và đang chờ chương trình xử lý. Khi có bất kỳ một thông điệp nào được gửi tới (hàm GetMessage() trả về giá trị True) thì hàm TranslateMessage and DispatchMessage sẽ được gọi để xử lý. Sau khi bạn đã tạo xong hàm WinMain, bây giờ là lúc chúng ta xây dựng hàm tạo cửa sổ ứng dụng. InitWindow Trong môi trường Window, bất kỳ một ứng dụng nào muốn tạo một cửa sổ trên màn hình desktop đều cần phải đăng ký 1 lớp đối tượng thuộc lớp window. Sau khi lớp đó được đăng ký ứng dụng của bạn sẽ có thể tạo những cửa sổ cần thiết. Đoạn mã dưới đây là một ví dụ quá trình đăng kí một cửa sổ thông thường với hệ thống và sau đó lớp này sẽ được dùng để tạo ra một cửa sổ thực sự trong môi trường Windows. 16
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN / * bool initWindow( HINSTANCE hInstance ) * initWindow registers the window class for the application, creates the window / bool initWindow( HINSTANCE hInstance ) { WNDCLASSEX wcex; // Xác lập thuộc tính đối tượng kiểu WNDCLASSEX structure. Các thuộc tính này sẽ tác // động tới cách thể hiện của cửa sổ chương trình wcex.cbSize = sizeof(WNDCLASSEX); // hàm sizeof() trả về kích thước của một đối //tượng kiểu dữ liệu đầu vào – đơn vị tính là byte wcex.style = CS_HREDRAW | CS_VREDRAW; // xác lập kiểu lớp wcex.lpfnWndProc = (WNDPROC)WndProc; // xác lập tên hàm gọi lại callback procedure wcex.cbClsExtra = 0; // xác lập số byte cấp phát thêm cho Class wcex.cbWndExtra = 0; // xác lập số byte cấp phát thêm cho mỗi instance của Class wcex.hInstance = hInstance; // con trỏ (handle) trỏ tới instance của ứng dụng wcex.hIcon = 0; //loại biểu tượng chương trình wcex.hCursor = LoadCursor(NULL, IDC_ARROW);// xác lập kiểu con trỏ chuột mặc định wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); // màu nền của cửa sổ wcex.lpszMenuName . = NULL; // con trỏ trỏ tới object dữ liệu thực đơn ứng dụng wcex.lpszClassName = “DirectXExample”; // tên lớp đăng ký với hệ thống wcex.hIconSm = 0; // con trỏ tới dữ liệu biểu tượng cửa sổ ứng dụng RegisterClassEx(&wcex); //gọi hàm đăng ký lớp với hệ thống // Tạo cửa sổ mới wndHandle = CreateWindow( “DirectXExample”, //tên lớp sử dụng đã khai báo và đăng ký ở trên “DirectXExample”, //tiêu đề của cửa sổ chương trình WS_OVERLAPPEDWINDOW, //loại của sổ chương trình CW_USEDEFAULT, // toạ độ X của của sổ khi xuất hiện CW_USEDEFAULT, // toạ độ Y của của sổ khi xuất hiện 640, // kích thước bề ngang của cửa sổ - đơn vị là pixel 480, // kích thước chiều cao của cửa sổ NULL, // con trỏ trỏ tới đối tượng cha ; //NULL = đối tượng quản lý là desktop của Windows NULL, // con trỏ đối tượng menu của chương trình; NULL = không sử dụng hInstance, // con trỏ instance của ứng dụng NULL); // không có giá trị gì được truyền cho cửa sổ // Kiểm tra lại xem quá trinh khởi tạo cửa sổ có thành công hay không if (!wndHandle) return false; // Thể hiện cửa sổ lên màn hình Window ShowWindow(wndHandle, SW_SHOW); UpdateWindow(wndHandle); return true; } Tất cả thông tin, ý nghĩa của các hàm sử dụng ở trên bạn có thể tham khảo thêm trong bất kỳ tài liệu hướng dẫn lập trình windows nào. Chính vì thế chúng ta sẽ đi nhanh qua các phần này, các mã lệnh sẽ được được viết một số chú thích để bạn dễ hình dung hơn tiến trình thực hiện của chương trình. Tất cả các ứng dụng muốn thể hiện một cửa sổ trên màn hình đều phải thực hiện quá trình đăng ký lớp cửa sổ đó với hệ thống. Lớp cửa sổ này chứa những đặc tính của cửa sổ như màu màn hình nền, loại biểu tượng chuột sử dụng, và biểu tượng thể hiện của chương trình. Lớp cửa sổ này đã được cung cấp mâu khai báo sẵn theo cấu chúc đối tượng WNDCLASSEX. Sau khi đối tượng có kiểu WNDCLASSEX đã được xác lập đầy đủ các 17
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN thông tin, nó sẽ được truyền làm tham số cho lời gọi hàm đăng ký với hệ thống RegisterClassEx. Hàm RegisterClassEx sẽ lấy thông tin được cung cấp trong đối tượng WNDCLASSEX truyền cho hàm và thực hiện quá trình đăng ký lớp cửa sổ ứng dụng với hệ thống. Sau khi bạn đã có một lớp cửa sổ được đăng ký, bạn sẽ có thể tạo ra các cửa sổ được sử dụng trong ứng dụng. Bước tiếp theo đó là tạo ra cửa sổ, nó được thực hiện thông qua hàm CreateWindow. Hàm CreateWindow này yêu cầu 11 đối số, mỗi một thông số được cung cấp sẽ được dùng để xác lập kiểu hiển thị của cửa sổ ứng dụng trên màn hình. Các thông số này đã được chú giải trong phần mã nguồn ở trên. WndProc Hàm WndProc (Window Procedure) là một hàm nữa bạn cần phải khai báo cho một cửa sổ ứng dụng. Mã nguồn của hàm này được minh hoạ ở dưới đây, hàm này sẽ làm nhiệm vụ đón nhận những sự kiện mà hệ thống gửi đến cho ứng dụng. Ví dụ như, khi sự kiện chuột được kích trên cửa sổ ứng dụng, hệ thống sẽ gửi sự kiện kích chuột này tới cho ứng dụng thông qua hàm WndProc này. Trong hàm này, bạn sẽ tiến hành khai báo sự những sự kiện nào sẽ được xử lý hay bỏ qua chúng. Đoạn mã ví dụ dưới đây sẽ minh hoạ những sự kiện đơn giản nhất mà một ứng dụng cần quản lý. / * LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, * LPARAM lParam) * The window procedure / LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { // Kiểm tra xem có thông điệp nào được gửi tới hàng đợi cửa ứng dụng không switch (message) //lọc các thông điệp { case WM_DESTROY: //bắt thông điệp yêu cầu kết thúc ứng dụng PostQuitMessage(0); //gọi hàm xử lý break; } // Chú ý, hàm này yêu cầu bạn phải cung cấp thông điệp trả về cho quá trình // xứ lý tiếp theo return DefWindowProc(hWnd, message, wParam, lParam); } Tại thời điểm này, bạn đã có thể biên dịch ứng dụng và chạy thử, sau khi chạy, ứng dụng sẽ có dạng một cửa sổ có nền trắng tương tự hình minh hoạ dưới đây. Bạn cũng có thể tìm thấy chương trình này trong thư viện Chapter2\example1 18
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Sử dụng DirectX Với phiên bản DirectX 8 trước đây, phần vẽ các đối tượng được chia ra làm hai hệ thống giao diện: DirectDraw và Direct3D. DirectDraw được sử dụng để thể hiện các đối tượng 2D, nó không còn được tiếp tục phát triển thêm nữa. Chính vì thế với phiên bản D9, nó đã được gộp lại toàn bộ trong một giao diện thống nhất thông qua các hàm API của Direct3D. Trong phần này chúng ta sẽ dần dần tiếp cận với cách để thực hiện tất cả quá trình vẽ thông qua Direct3D. Trước tiên là các bước sác lập hệ thống để có thể chạy được hệ thống giao diện lập trình Direct3D và tiếp đến là thực thi nó: - Tạo một đối tượng Direct3D. - Tạo một môi trường Direct3D (thiết bị - device) để thể hiện đối tượng. - Vẽ các đối tượng lên môi trường đó. Đối tượng Direct3D Đối tượng Direct3D là một đối tượng cung cấp một giao diện được sử dụng bởi các hàm để tập hợp và nhận biết khả năng tương thích của môi trường Direct3D. Có thể ví dụ, một đối tượng Direct3D đem đến cho bạn khả năng tìm và xác nhận xem hệ thống có bao nhiêu thiết bị hiển thị đồ hoạ đã được cài đặt trên hệ thống cũng như kiểm tra khả năng tương thích (hỗ trợ) của chúng. Một đối tượng Direct3D được tạo thông qua cách sau: IDirect3D9 *Direct3DCreate9( D3D_SDK_VERSION ); Chú ý: D3D_SDK_VERSION chỉ là một tham số mặc định có thể gửi cho hàm Direct3DCreate9 19
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Hàm này sẽ trả về một con trỏ trỏ tới giao diện IDirect3D9. Giá trị trả về của hàm sẽ là NULL nếu quá trình tạo mới một đối tượng thất bại. Bạn có nhớ là tôi đã đề cập tới khả năng truy suất số lượng thiết bị hiển thị video hay adapters trên máy? Đây là một chức năng đã được cung cấp trong Direct3D, để làm được điều này bạn chỉ cần làm những việc sau. UNIT IDirect3D9::GetAdapterCount(VOID); Hàm GetAdapterCound có khả năng cho phép bạn biết số lượng thiết bị hiển thị của hệ thống. Hàm này không yêu cầu bất kỳ một tham số nào phải truyền cho nó và thông thường nó sẽ có giá trị trả về là 1. Chú ý: Nếu hệ thống chỉ có một thiết bị hiển thị được cài đặt (card đồ hoạ), thì thiết bị đó sẽ được coi là thiết bị chính để hiển thị. Trong trường hợp có nhiều hơn 1 thì thiết bị đầu tiên sẽ là thiết bị chính. Tạo một thiết bị kết xuất (Rendering) Một Direct3D device, thông qua giao diện IDirect3DDevice9 sẽ cung cấp các phương thức cho các ứng dụng có thể sử dụng để kết xuất hình ảnh ra màn hình. Thông qua giao diện này tất cả các thao tác vẽ trong game của bạn sẽ được thực hiện. Một thiết bị Direct3D này có thể được tạo bằng cách gọi tới hàm CreateDevice. HRESULT CreateDevice( UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS *pPresentationParameters, IDirect3DDevice9 ppReturnedDeviceInterface ); Đối tượng thiết bị kết quả trả về này sẽ được sử dụng trong toàn bộ game của bạn để truy cập và kết xuất kết quả vẽ tới các thiết bị hiển thị phần cứng. Hàm CreateDevice này yêu cầu 6 tham số đầu vao và trả về một kết quả kiểu HRESULT. Nếu như quá trình thực hiện hàm này thanh công, kết quả trả về sẽ là D3D_OK; trong các trường hợp khác nó có thể là một trong các giá trị dưới đây: D3DERR_INVALIDCALL. Một trong những tham số cung cấp cho hàm không hợp lệ. D3DERR_NOTAVAILABLE. Thiết bị không hỗ trợ bạn gọi hàm này. D3DERR_OUTOFVIDEOMEMORY. Card đồ hoạ không đủ bộ nhớ để thực hiện hàm lời gọi hàm này. Chú ý: Một thói quen tốt mà bạn nên tạo lập đó là luôn luôn kiểm tra kết quả trả về của hàm để đảm bảo chắc chắn quá trình tạo đối tượng đã thực hiện chính xác mặc dù hầu hết các trường hợp hàm này sẽ trả về kết quả D3D_OK. Các tham số cần thiết của hàm CreateDevice: Adapter. Có kiểu UINT. Có giá trị là số hiệu của card đồ hoạ mà thiết bị dùng để hiển thị. Hầu hết các ứng dụng game sẽ gửi giá trị D3APAPTER_DEFAULT cho hàm để mặc định sử dụng thiết bị đồ hoạ chính của hệ thống. 20
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN DeviceType. Có kiểu D3DDEVTYPE. Có 3 kiểu thiết bị mà bạn có thể lựa chọn để khởi tạo: • D3DDEVTYPE_HAL. Thiết bị sử dụng chức năng hỗ trợ từ phần cứng card đồ hoạ tốc độ cao. • D3DDEVTYPE_REF. Các tính năng của Direct3D sẽ được cài đặt trong phần mềm, tuy nhiên sẽ sử dụng các chức năng đặc biệt mà CPU hỗ trợ nếu có thể. • D3DDEVTYPE_SW. Thiết bị cắm-chạy có chức mô phỏng bằng phần mềm sẽ được sử dụng. hFocusWindow. Có kiểu HWND. Đây là con trỏ của cửa sổ sử dụng thiết bị đó. BehaviorFlags. Có kiểu DWORD. Tham số này cho phép xác lập nhiều giá trị cờ trạng thái khác nhau cho quá trình khởi tạo thiết bị. Trong ví dụ minh hoạ ở đây chúng ta chỉ sử dụng giá trị cờ trạng thái xác lập tiến trình sử lý vertex sẽ được thực hiện bằng phần mềm: D3DCREATE_SOFTWARE_VERTEXPROCESSING. PresentationParamters. Có kiểu D3DPRESENT_PARMETERS. Đây là tham số điều khiển quá trình hiển thị của thiết bị như xác lập kiểu hiển thị cửa sổ ứng dụng/fullcreen hay có sử dụng hay không bộ nhớ đêm backbuffer. Cấu trúc của tham số này có dạng sau: typedef struct _D3DPRESENT_PARAMETERS_ { UINT BackBufferWidth, BackBufferHeight; D3DFORMAT BackBufferFormat; UINT BackBufferCount; D3DMULTISAMPLE_TYPE MultiSampleType; DWORD MultiSampleQuality; D3DSWAPEFFECT SwapEffect; HWND hDeviceWindow; BOOL Windowed; BOOL EnableAutoDepthStencil; D3DFORMAT AutoDepthStencilFormat; DWORD Flags; UINT FullScreen_RefreshRateInHz; UINT PresentationInterval; } D3DPRESENT_PARAMETERS; Bảng 2.1 sẽ mô tả chi tiết hơn các tham số trên. ppReturnedDeviceInterface. Có kiểu IDirect3Ddevice9 . Đây là biến chứa con trỏ thiết bị được tạo hàm tạo ra nếu quá trình khởi tạo thành công. Sau khi một thiết bị hiển thị đã được khởi tạo bạn sẽ có thể gọi tiếp các phương thức khác của Direct3D để lấy hoặc vẽ bất kỳ một đối tượng nào lên màn hình. 21
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Bảng 2.1 D3DPRESENT_PARAMETERS Thuộc tính Mô tả chi tiết BackBufferWidth Bề rộng của vùng đệm BackBuffer BackBufferHeight Chiều cao của vùng đệm BackBuffer BackBufferFormat Kiểu dữ liệu vùng đệm – nó có kiểu D3DFORMAT. Chú ý trong chế độ cửa sổ ứng dụng bạn phải truyền cho hàm này giá trị D3FMT_UNKNOW. BackBufferCount Số lượng vùng đệm BackBuffer được tạo MultiSampleType The levels of full-scene multisampling. Unless multisampling is being supported specifically, pass D3DMULTISAMPLE_NONE. MultiSampleQuality The quality level. Pass 0 to this parameter unless multisampling is enabled. SwapEffect Kiểu cháo đổi dữ liệu khi chuyễn đổi bộ đệm backbuffer. Trong ví dụ này ta sử dụng D3DSWAPEFFECT_DISCARD hDeviceWindow Cửa sổ ứng dụng chứa thiết bị được tạo. Windowed Có giá trị TRUE nếu ứng dụng kiểu cửa sổ và FALSE cho ứng dụng chiếm toàn bộ màn hình EnableAutoDepthStencil Giá trị điều khiển độ sâu bộ đệm (depth buffer) cho ứng dụng. Xác lập giá trị TRUE nếu muốn Direct3D quản lý bộ đệm này cho bạn. AutoDepthStencilFormat Định dạng cho bộ đệm – có kiểu D3DFORMAT Flags Trừ trường hợp bạn muốn tự mình xác lập giá trị cho nó, không thì bạn có thể truyền giá trị 0 theo mặc định. FullScreen_RefreshRateInHz Tốc độ làm tươi màn hình. Trong chế độ cửa sổ ứng dụng, tham số này phải được xác lập là 0. PresentationInterval Điều khiển này xác lập tốc độ tráo đổi của bộ nhớ đệm Xoá màn hình Sau khi một thiết bị Direct3D đã được tạo ra, bây giờ bạn có thể bắt đầu tô điểm lên màn hình, bạn có thể làm điều này với các bức ảnh hay thông qua một số đa giác. Tuy nhiên công việc đầu tiên bạn phải làm trong vòng lặp chính của ứng dụng game là xoá sạch màn hình. Việc xoá màn hình này sẽ cho bạn một khung hình hoàn toàn mới để thao tác và vẽ các đối tượng lên đó. Tệp tin winmain.cpp đã được cập nhật thêm hàm này bạn có thể tìm thấy trong thư mục chapter2\example2 trên CD-ROM. Bạn có thể xoá toàn bộ màn hình bằng cách gọi tới hàm Clear. HRESULT Clear( DWORD Count, const D3DRECT *pRects, DWORD Flags, D3DCOLOR Color, float Z, DWORD Stencil ); 22
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Bạn phải truyền đầy đủ 6 tham số cần thiết cho hàm. Tham số thứ nhất, Count – số lượng vùng hình chữ nhật được xoá. Nếu tham số này là 0 thì tham số thứ 2 pRects phải là giá trị NULL. Trong trường hợp đó, toàn bộ vùng nhien của màn hình sẽ được xoá, đây là các tham số hay được sử dụng nhất. Nếu giá trị Count lớn hơn 0, pRects phải trỏ tới mảng các đối tượng kiểu D3DRECT lưu giữ toạ độ các vùng chữ nhật được xoá. Tham số cờ trạng thái Flags xác lập bộ đệm sẽ được xoá. Có 3 trường hợp: D3DCLEAR_STENCIL D3DCLEAR_TARGET D3DCLEAR_ZBUFFER Giá trị mà bạn sẽ sử dụng tại thời điểm này là D3DCLEAR_TARGET, nó dùng đề xác lập đối tượng được xoá là vùng dữ liệu hiển thị hiện thời. Color – giá trị màu (có kiểu D3DCOLOR) sẽ được xác lập sau khi xoá. Có rất nhiều marcos sẽ trợ giúp bạn xác lập giá trị màu theo ý muốn tuỳ theo các chuẩn màu sắc khác nhau, ví dụ như D3DCOLOR_XRGB. Tham số Z xác lập giá trị chiều sâu bộ đệm. Giá trị này nằm trong khoảng từ 0.0f tới 1.0f. Chúng ta sẽ nói sâu hơn về nó trong các chương sau. Tham số Stencil lưu giá trị số hiệu bộ đệm mẫu tô. Nếu bạn không sử dụng bộ đệm này thì bạn phải truyền giá trị này cho hàm là 0. Thể hiện một phông cảnh Sau khi bạn đã xoá sạch một khung hình, bây giờ là lúc chúng ta thể hiện nó lên màn hình. Direct3D sử dụng hàm Present để làm điều này. Hàm Present này sẽ thực hiện việc chuyển đổi trang của bộ đệm nền. (có thể ví quá trình hiển thị hình ảnh như việc lật những bức vẽ (từ bộ đệm) lên màn hình). Thực chất thì tất cả quá trình bạn thao tác vẽ cho tới thời điểm này đều được thực hiện trên bộ nhớ đệm. Bộ nhớ đệm back buffer này là một vùng nhớ để lưu giữ toàn bộ kết quả vẽ trước khi nó được kết xuất lên màn hình. Page flipping (lật trang) là một tiến trình lấy các thông tin từ bộ nhớ đệm và hiển thị nó lên màn hình. Việc làm này nhằm giảm hiện tượng giật hình do tốc độ hiển thị của màn hình thường chậm hơn nhiều so với tốc độ sử lý của bộ nhớ trên card màn hình. Hàm này sẽ đảm bảo quá trình hiển thị sẽ mượt mà hơn và tất cả những gì bạn thực hiện trên bộ nhớ đệm sẽ được hiển thị lên màn hình. (!) Chú ý: Lật trang là quá trình tráo đổi dữ liệu từ bộ nhớ đệm lên bộ nhớ thực của màn hình, để thực hiện quá trình hiển thị thì bạn phải gọi tới hàm yêu cầu lật trang này mỗi khi thực hiện xong các thao tác vẽ trên bộ nhớ đệm. Cấu trúc của hàm Present: HRESULT Present( CONST RECT *pSourceRect, CONST RECT *pDestRect, HWND hDestWindowOverride, CONST RGNDATA *pDirtyRegion ); 23
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Hàm Present này yêu cầu 4 tham số đầu vào: pSourceRect là một con trỏ có kiểu RECT trỏ tới dữ liệu vùng chữ nhật thể hiện từ bộ nhớ đệm backbuffer. Giá trị này phải được gán là NULL nếu bạn muốn sử dụng toàn bộ dữ liệu vùng nhớ đệm backbuffer – đây cũng là trường hợp thường dùng. pDesRect là một con trỏ kiểu RECT khác chứa địa chỉ vùng chữ nhật đích. hDestWindowOverride là con trỏ trỏ tới của sổ đích được dùng để hiển thị. Giá trị này phải được xác lập là NULL nếu bạn sử dụng những xác lập cửa sổ trước đó trong phần khai báo tham số hiển thị ở trên. pDirtyRegion là con trỏ trỏ tới những vùng mà bộ đệm cần được cập nhật dữ liệu. Một lần nữa giá trị này là NULL nếu bạn muốn cập nhật toàn bộ dữ liệu bộ đệm. Dọn dẹp bộ nhớ (Cleaning Up) Công việc cuối bạn phải làm trong bất kỳ một ứng dụng DirectX nào là dọn dẹp vào giải phóng bộ nhớ cho các đối tượng bạn đã sử dụng. Ví dụ như, tại thời điểm bắt đầu của ứng dụng, bạo đã tạo ra cả một đối tượng Direct3D cũng như một thiết bị Direct3D. Khi ứng dụng được đóng lại, bạn cần thiết phải giải phóng những đối tượng đó để trả lại tài nguyên các đối tượng đó đã chiếm chỗ cho hệ thống thực hiện các công việc khác. Các đối tượng COM, nền tảng của DirectX, luôn được quản lý bởi hệ thống thông qua một biến đếm lưu giữ số lượng đối tượng mà ứng dụng đã tạo ra cũng như còn lại sau khi vừa được loại bỏ. Bằng việc sử dụng hàm Release, bạn vừa thực hiện việc giải phóng đối tượng và vừa tác động tới biến đếm (count) đó. Khi mà biến đếm đạt giá trị 0, hệ thống sẽ giải phóng toàn bộ bộ nhớ mà các đối tượng đã sử dụng. Lấy vị dụ, khi giải phóng một thiết bị hiển thị Direct3D, bạn sử dụng lệnh sau: If ( pd3dDevice != NULL ) pd3dDevice->Release( ); Nếu điều kiện trong câu lệnh If được thoả mãn (tức là biến pd3dDevice có chứa dữ liệu – nó được gán cho địa chỉ của một thiết bị được khởi tạo trước đó), thì thực hiện quá trình giải phóng biến đó. Chú ý: Bạn nên kiểm tra xem chắc chắn một đối tượng DirectX có khác rỗng (NULL) không trước khi gọi hàm Release đối với nó. Nếu bạn gọi hàm này trên một đối tượng rỗng thì chắc chắn sẽ xảy ra lỗi cho ứng dụng. Cập nhật mã nguồn chương trình. Sau khi đã tìm hiểu qua một vài lệnh và thông tin cơ bản về DirectX, bây giờ là lúc bạn sẽ xem xét làm thế nào để xác lập và chạy một ứng dụng DirectX, là lúc để chúng ta thêm các mã lệnh mới vào ứng dụng. Những mã lệnh này sẽ được chèn thêm vào trong tệp tin winmain.cpp mà chúng ta đã tạo trong phần trước. Bước đầu tiên trước khi viết bất kỳ một ứng dụng DirectX nào đó là chèn tệp tin header của Direct3D. Bạn thực hiện khai báo như sau trong ứng dụng: #include Tiếp theo bạn cần khai báo 2 biến toàn cục sẽ cần thiết trong quá trình lưu giữ con trò đối tượng để toàn bộ ứng dụng sử dụng. 24
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN LPDIRECT3D9 pD3D; //Đối tượng Direct3D LPDIRECT3DDEVICE9 pd3dDevice; //thiết bị hiển thị Direct3D Biến được khai báo kiểu LPDIRECT3D9 là một con trỏ sử dụng giao diện IDirect3D9, tương tự biến LPDIRECT3DDEVICE9 là con trỏ sử dụng giao diện IDirect3DDevice9. Tiếp theo, bạn sẽ cần phải gọi tới hàm initDirrect3D, bạn có thể thực hiện theo cách tôi sử dụng dưới đây. Chú ý là bạn phải đặt đoạn code gọi tới hàm này sau khi đã gọi hàm initWindow ở trong hàm WinMain. // hàm này được gọi sau khi quá trình khởi tạo cửa sổ ứng dụng kết thúc If ( !initDirect3D( ) ) return false; Thay đổi mã lệnh trong vòng lặp quản lý sự kiện Sau khi đã xác lập các thông số khởi tạo, bây giờ là lúc chúng ta thay thế vòng lặp quản lý sự kiện Windows bằng một mã lệnh khác hữu dụng hơn cho các ứng dụng game. Trong vòng lặp quản lý sự kiện ban đầu chúng ta sử dụng thì vòng lặp này sẽ liên tục gọi tới hàm GetMessage để kiểm tra sự kiện mỗi khi nó được gửi tới và chờ ứng dụng sử lý; Nếu thông điệp được gửi tới, hàm GetMassage sẽ phải đợi tới khi thông điệp sử lý xong mới tiếp tục. Chính vì thế trong phần này thay vì sử dụng hàm GetMassage thì chúng ta sẽ sử dụng hàm PeekMessage, hàm này cũng có chức năng kiểm tra thông điệp tuy nhiên nó trả điều khiển về ngay lập tức, cho phép ứng dụng game của bạn có thể gọi lại chính nó trong vòng lặp. Trong ví dụ này, chúng ta sẽ chèn thêm mệnh đề else sau lời gọi PeekMessage để gọi tới hàm render của game. Hàm render này sẽ thực hiện quá trình vẽ các đối tượng lên màn hình, hàm này sẽ được định nghĩa ngay ở phần sau. if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) ) { TranslateMessage ( &msg ); DispatchMessage ( &msg ); } else { render( ); } Hàm khởi tạo Direct3D Trong hàm initDirect3D này chúng ta sẽ thực hiện việc tạo một đối tượng Direct3D và thiết bị để sử dụng chúng. / * initDirect3D / bool initDirect3D(void) { pD3D = NULL; pd3dDevice = NULL; // Create the DirectX object if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) { return false; } // Fill the presentation parameters structure 25
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN D3DPRESENT_PARAMETERS d3dpp; ZeroMemory( &d3dpp, sizeof( d3dpp ) ); d3dpp.Windowed = TRUE; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; d3dpp.BackBufferCount = 1; d3dpp.BackBufferHeight = 480; d3dpp.BackBufferWidth = 640; d3dpp.hDeviceWindow = wndHandle; // Create a default DirectX device if( FAILED( pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_REF, wndHandle, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &pd3dDevice ) ) ) { return false; } return true; } Tại đoạn đầu của hàm trên, chúng ta đã tạo một lời gọi tới hàm Direct3DCreate9. Nó sẽ thực hiện lệnh tạo một đối tượng Direct3D, từ đó bạn có thể tạo một thiết bị hiển thị Direct3D. Tiếp đến là quá trinh xác lập các thông số hiển thị của ứng dụng. Chúng ta đã xác lập một cửa số có kích thước 640 x 480. Tiếp theo là hàm CreateDevice sẽ được gọi với tham số thứ 2 ở cuối cùng là biến cấu trúc với dữ liệu đã vừa được định nghĩa. Cũng tại đây chúng ta muốn hàm CreateDevice sẽ sử dụng thiết bị hiển thị đồ hoạ chính của hệ thống nên giá trị truyền vào là D3DAPAPTER_DEFAULT. Ngoài ra giá trị D3DDEVTYPE_REF cũng nhằm để xác lập rằng thiết bị sẽ sử dụng là thiết bị chuẩn Direct3D. Chúng ta cũng xác lập một tham số khác với giá trị D3DCREATE_SOFTWARE_VERTEXPROCESSING nhằm đảm bảo rằng ví dụ của chúng ta sẽ chạy được trên hầu hết tất cả các phần cứng. Khả năng truy cập chức năng Vertex trên phần cứng chỉ có được trên một vài card đồ hoạ mới. Và tham số cuối cùng &pd3dDevice là biến lưu giữ kết quả tạo đối tượng thiết bị Direct3D mà hàm trả về. Hàm kết xuất hình ảnh (Render) Hàm render là nơi bạn thực sự thao tác vẽ lên thiết bị. Như đã giới thiệu ở trên, thì hàm này sẽ được gọi bên trong vòng lặp chính và được gọi sau mỗi khung hình hiển thị. / * render / void render(void) { // Kiểm tra xem chắc chắn thiết bị Direct3D đã được tạo hay chưa if( NULL == pd3dDevice ) return;// Xoá toàn bộ bộ đệm màn hình về trạng thái mầu xanh da trời pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB( 0,0,255 ), 1.0f, 0 ); // Thể hiện dữ liệu bộ đệm lên màn hình pd3dDevice->Present( NULL, NULL, NULL, NULL ); } 26
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Trên đây chỉ là một ví dụ đơn giản của hàm render. Trước hết là chúng ta phải kiểm tra chắc chắn kết quả trả về sau khi gọi hàm CreateDevice, nếu nó có giá trị NULL tức là có lỗi khởi tạo thiết bị Direct3D. Tất nhiên đối với trường hợp này thì chúng ta sẽ chẳng thể thao tác gì trên đối tượng thiết bị này nữa ngoài việc thoát khỏi hàm. Tiếp đó, chúng ta sử dụng hàm Clear đã được giới thiệu ở trên. Bởi vì chúng ta muốn xoá toàn bộ bộ đệm, nên bạn cần truyền tham số 0 và NULL cho 2 tham số đầu của hàm. Xác lập D3DCLEAR_TARGET sẽ yêu cầu DirectX xoá toàn bộ bộ nhớ đệm. Thông số tiếp theo là một biến có kiểu D3DCOLOR. Trong ví dụ này chúng ta sử dụng macro D3DCOLOR_XRGB để lựa chọn màu cần xoá là màu xanh nước biển với các giá trị màu tương ứng trong hệ RGB là: R=0, G=0, B=255. Ngoài ra bạn cũng cần phải truyền một giá trị thực 1.0 để xác lập độ sâu của vùng đệm. Độ sâu của vùng đệm sẽ giúp Direct3D nhận biết khoảng nhìn giữa người chơi và cảnh vật xung quanh. Bạn có thể xác lập giá trị này trong khoảng 0.0 tới 1.0. Giá trị cao đồng nghĩa với phạm vi nhìn của người chơi sẽ xa hơn. Bộ đệm stencil (!) cho phép bạn đánh dấu vùng chính diện của bức ảnh mà chúng không được hiển thị. Bởi vì chúng ta không sử dụng nó nên tham số này sẽ được xác lập là 0. Công việc cuối cùng cần phải thực hiện trong hàm render là thể hiện đối tượng từ bộ đệm lên màn hình. Nó được thực hiện thông qua lời gọi tới hàm Present. Bởi vì bạn muốn thể hiện toàn bộ vùng đệm lên màn hình, giá trị NULL sẽ được truyền cho tất cả các tham số yêu cầu của hàm. Đây cũng là xác lập bạn thường dùng nhất. Khai báo hàm cleanUp Dĩ nhiên là sau khi ứng dụng kết thúc, bạn sẽ muốn giải phóng hết tất cả các đối tượng đã tạo ra. Nó được thực hiện trong đoạn code mô tả dưới đây. void cleanUp (void) { // Giải phóng đối tượng và thiết bị hiển thị DIrect3D if( pd3dDevice != NULL ) pd3dDevice->Release( ); if( pD3D != NULL ) pD3D->Release( ); } Trước khi giải phóng một đối tượng nào, bạn cần phải kiểm tra xem đối tượng đó có khác NULL không. Nếu đối tượng đó thực ra đã được tạo ra, phương thức Release sẽ được gọi để tiến hành giải phóng đối tượng đó khỏi bộ nhớ. Hàm này sẽ được đặt trước lời gọi xác lập giá trị trả về của hàm WinMain. Chèn thư viện DirectX vào chương trình Cuối cùng thì bạn cũng đã có tất cả những đoạn code cần thiết đều tạo một ứng dụng DirectX đâu tiên. Tuy nhiên trước khi bạn có thể biên dịch ứng dụng và chạy nó, bạn sẽ cần phải thực hiện một vài xác lập: liên kết thư viện DirectX. Trong ví dụ đơn giản này, bạn sẽ chỉ cần tạo liên kết tới tệp thư viện d3d9.lib. 1. Lựa chọn Properties option trong thực đơn Project. Cửa sổ hội thoại Property Pages sẽ xuất hiện. Hộp thoại này có hình dạng tương tự hình minh hoạ 2.6 27
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN 2. Lựa chọn Linker trong hộp panel bên trái. Đối tượng này sẽ được mở rộng và cho phép bạn xác lập các tuỳ chọn trong đó. 3. Tiếp đến, bạn lựa chọn thông số đầu vào Input. Hộp thoại sẽ thay đổi nội dung và sẽ có dạng tương tự hình minh hoạ 2.7 dưới đây. Hình 2.6 Cửa sổ hộp thoại Property Pages. Hình 2.7 Xác lập các thông số trong tuỳ chọn Linker 4. Gõ vào d3d9.lib trong mục Additional Dependencies và kích chọn OK để kết thúc. Biên dịch và chạy ứng dụng. Không giống như ví dụ ban đầu, cửa sổ ứng dụng này sẽ có một màu nền xanh nước biên. Mặc dù ứng dụng này không đi sâu vào những thứ mà DirectX có thể làm, nhưng nó đã cung cấp cho bạn những kiến thức nền tảng để bắt đầu với công nghệ này. 28
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Chú ý: Có rất nhiều tệp tin thư viện cần thiết cho những chức năng khác nhau của DirectX. Bạn chỉ cần liên kết tới những thư viện nào chứa những hàm cần thiết mà bạn muốn sử dụng. Xác lập ứng dụng chạy ở chế độ toàn màn hình Trong ví dụ mà chúng ta đã thực hiện ở trên, ứng dụng của chúng ta đều là dạng cửa sổ có kích thước 640x480 và được hiển thị trên màn hình desktop. Mặc dù kiểu giao diện này phù hợp cho các ứng dụng thông thường tuy nhiên đối với một ứng dụng game, bạn cần phải tạo các hiệu ứng ấn tượng người chơi bằng một không gian ảo nhưng phải thật nhất. Chính vì vậy bạn không thể không sử dụng chế độ chạy ứng dụng ở dạng toàn màn hình. Để tạo ứng dụng dạng này bạn chỉ cần phải thay đổi một đoạn code rất nhỏ trong ứng dụng mà chúng ta vừa tạo, thay đổi này được xác lập chủ yếu trong hàm CreateWindow. Đây là đoạn code gọi hàm CreateWindow mà chúng ta đã dùng: wndHandle = CreateWindow(“DirectXExample”, “DirectXExample”, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 640, 480, NULL, NULL, hInstance, NULL); Bạn có thể thấy tham số thứ ba, chúng ta đã xác lập kiểu ứng dụng là dạng cửa sổ với giá trị xác lập là WS_OVERLAPPEDWINDOW. Xác lập này bao gồm kiểu ứng dụng có thanh tiêu đề, có viền bao cửa sổ và các nút nhấn thu nhỏ (Minimize) và đóng cửa sổ. Trước khi chúng ta tiến hành xác lập cửa sổ dạng thể hiện toàn màn hình, kiểu dáng cửa sổ chúng ta cần xác lập các thông số sau: WS_EX_TOPMOST | WS_POPUP | WS_VISIBLE Phần xác lập đầu tiên, WS_EX_TOPMOST, có tác dụng đặt vị trí hiên thị của cửa sổ này luôn ở trạng thái bên trên tất cả các cửa sổ khác. WS_POPUP có tác dụng xác lập cửa sổ không có viền (border), không thanh tiêu đề và không có nút Minimize và Close. Phần xác lập cuối cùng, WS_VISIBLE yêu cầu với windows để ứng dụng tự cập nhật lại màn hình ứng dụng. Lời gọi tới hàm CreateWindow lúc này có dạng sau: wndHandle = CreateWindow(“DirectXExample”, “DirectXExample”, WS_EX_TOPMOST | WS_POPUP | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 640, 480, NULL, NULL, hInstance, NULL); 29
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Tiếp theo chúng ta sẽ tiến hành thay đổi một chút mã lệnh trong hàm initDirect3D. Trong biến có kiểu cấu trúc D3DPRESENT_PARAMETERS chúng ta đã truyền cho hàm CreateDevice, bạn cần thay đổi hai biến tham số: Windowed và BackBufferFormat. Hiện tại thì chúng ta đang sử dụng khai báo: d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; d3dpp.Windowed = TRUE; Chú ý: Chúng ta phải sử dụng tham số D3DFMT_UNKNOWN cho kiểu bộ đệm BackBufferFormat bởi vì chúng ta đang sử dụng kiểu hiển thị ứng dụng dạng cửa sổ. Để xác lập ứng dụng dạng toàn màn hình trong DirectX, tham số Windowed và BackBufferFormat chúng ta cần phải xác lập lại như sau: d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8; d3dpp.Windowed = FALSE; Thay đổi mà bạn có thể thấy ngay được đó là thuộc tính d3dpp.Windowed đã được đảo ngược giá trị. Đơn giản bởi vì nó là FALSE bởi vì bạn muốn CreateDevice biết rằng ứng dụng của bạn là dạng chiếm toàn màn hình. Xác lập thuộc tính khác d3dpp.BackBufferFormat có lẽ bạn cảm thấy khó nắm bắt hơn. Đối với ứng dụng kiểu cửa sổ mà chúng ta đã tạo lúc đầu, bạn không cần thiết phải quan tâm tới giá trị này bởi vì bạn sử dụng những xác lập mặc định của màn hình desktop bằng cách truyền tham số D3DFMT_UNKNOWN. Khi bạn muốn xác lập ứng dụng ở dạng chiếm toàn bộ màn hình, bạn cần thiết phải tự mình xác lập các thông số D3DFORMAT mà bạn muốn sử dụng. Tham số kiểu D3DFORMAT có giá trị thể hiện chất lượng mầu sắc hiển thị của màn hình. Ví dụ, trong trường hợp này chúng ta sẽ chọn D3DFMT_X8R8G8B8 là giá trị mặc định mà hầu hết các thiết bị card đồ hoạ đều hỗ trợ. D3DFMT_X8R8G8B8 thể hiện bạn sẽ sử dụng 32-bit để định dạng cho một điểm ảnh bao gồm 8 bit cho xác lập sắc mầu đỏ, 8 bít cho màu xanh lá cây, 8 bit cho màu xanh nước biển. Định dạng này cũng bao gồm 8 bit lưu trữ dữ liệu mở rộng. Tất cả các xác lập lại code trên bạn có thể tìm thấy trong mã nguồn chương trình tại thư mục chapter2\example3 trên đĩa CD-ROM đi kèm sách. Trong phần tiếp theo, chúng ta sẽ đi tìm hiểu cách để kiểm tra xem hệ thống có khả năng hỗ trợ những độ phân giải và kiểu thể hiện màu sắc ra sao. Chế độ hiển thị màn hình và các kiểu định dạng Nếu ứng dụng game của bạn chạy ở chế độ cửa sổ trên màn hình desktop thì việc kiểm tra xem hệ thống hỗ trợ các chế độ phân giải nào là không cần thiết. Bạn chỉ cần truyền giá trị D3DFMT_UNKNOWN cho hàm, chương trình sẽ tự động xác lập giá trị mặc định mà màn hình desktop đang lựa chọn. Video Modes and Formats Nếu game mà bạn viết chỉ chạy ở chế độ cửa sổ trên desktop, thì việc xác định chế độ đồ hoạ mà máy tính của bạn hỗ trợ không quan trọng lắm, tuy nhiên khi bạn muốn game của bạn chạy ở chế độ toàn bộ màn hình (fullscreen) , thì việc xác định những chế độ hiển thị được hỗ trợ là rất cần thiết. Tất cả các máy tính đều hỗ trợ chế độ phân giải màn hình 30
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN 640x480, nhưng còn chế độ 800x600 hay 1024x768 thì sao? Không phải tất cả các thiết bị đồ hoạ đều hỗ trợ những chế độ phân giải đó. Và nếu có thì liệu chúng có được chất lượng màu bạn mong muốn? Đó là lý do tại sao khi bạn viết một game ở chế độ toàn bộ màn hình, thì tốt nhất là bạn nên kiểm tra phần cứng để chắc rằng nó hỗ trợ những gì mà game của bạn cần. Để thực hiện điều đó, Direct3D cung cấp cho bạn một số hàm thông qua giao diện IDirect3D9. Hàm đầu tiên mà bạn cần đã được giới thiệu ở phần trên: UINT IDirect3D9::GetAdapterCount(VOID); Thông thường, hàm này sẽ trả về một giá trị kiểu integer không dấu (unsigned integer) chỉ ra số lượng thiết bị đồ hoạ của hệ thống. DirectX hỗ trợ cùng lúc nhiều thiết bị đồ hoạ, cho phép game chạy trên nhiều màn hình. Để mọi việc đơn giản, hãy nghĩ rằng, chỉ có một thiết bị đồ hoạ. Gathering Video Adapter and Driver Information Viêc nắm được thông tin chắc chắn về thiết bị đồ hoạ trên máy của bạn là rất hữu ích. Ví dụ, bạn có thể muốn biết độ phân giải mà thiết bị đồ hoạ hỗ trợ, hoặc thông tin hãng sản suất. Sử dụng hàm GetAdapterIdentifier, bạn có thể thu thập được rất nhiều thông tin. Nó được định nghĩa như sau: HRESULT GetAdapterIdentifier( UINT Adapter DWORD Flags, D3DADAPTER_IDENTIFIER9 *pIdentifier ); ■ Tham số đầu tiên có kiểu integer không dấu để xác định thiết bị đồ hoạ nào mà bạn muốn lấy thông tin. Bởi vì, chúng ta đang chấp nhận rằng hiện tại chỉ có một thiết bị đồ hoạ, nên giá trị đưa vào là D3DADAPTER_DEFAULT, nghĩa là thiết bị đồ hoạ chính sẽ được chọn. ■ Tham số thứ hai, là một cờ (flag), xác nhận WHQLLevel của trình điều khiển. ■ Tham số thứ ba là con trỏ tới vùng nhớ của cấu trúc D3DADAPTER_IDENTIFIER9. Cấu trúc này lưu thông tin về thiết bị đồ hoạ do hàm trả về. Cấu trúc D3DADAPTER_IDENTIFIER9 cung cấp những thông tin sau: typedef struct _D3DADAPTER_IDENTIFIER9 { // tên của trình điều khiển char Driver[MAX_DEVICE_IDENTIFIER_STRING]; // mô tả về thiết bị char Description[MAX_DEVICE_IDENTIFIER_STRING]; // version của thiết bị char DeviceName[32]; // version của trình điều khiển hiện được cài đặt LARGE_INTEGER DriverVersion; // This value holds the bottom 32 bits of the driver version DWORD DriverVersionLowPart; // This value holds the upper 32 bits of the driver version DWORD DriverVersionHighPart; // số ID sản xuất DWORD VendorId; // the ID of the particular device DWORD DeviceId; // the second part of the device ID 31
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN DWORD SubSysId; // the revision level of the device chipset DWORD Revision; // a unique identifier for the device GUID DeviceIdentifier; // the level of testing that this driver has gone through DWORD WHQLLevel; } D3DADAPTER_IDENTIFIER9; Cấu trúc này lưu giữ toàn bộ những đặc trưng có liên quan đến thiết bị và trình điều khiển thiết bị được cài đặt. Đầy đủ hơn về cấu trúc này xem ở tài liều của DirectX. Getting the Display Modes for an Adapter Bước tiếp theo là lấy thông tin chi tiết về các chế độ hiển thị mà thiết bị đồ hoạ hỗ trợ. Để làm được điều đó, trước tiên bạn phải kiểm tra xem có bao nhiêu chế độ hiển thị có thể. Việc này được thực hiện qua hàm GetAdapterModeCount, định nghĩa như sau: UINT GetAdapterModeCount( UINT Adapter, D3DFORMAT Format ); Tham số đầu tiên xác định thiết bị mà bạn muốn kiểm tra. Theo như đã nói ở trên, ta đưa vào giá trị D3DADAPTER_DEFAULT. Tham số thứ hai xác định loại định dạng đồ hoạ (D3DFORMAT) mà bạn muốn kiểm tra. Như ở trên ta dùng D3DFMT_X8R8G8B8, với 8 bit cho màu đỏ, 8 bit cho màu xanh lá cây, 8 bit cho xanh nước biển. 8 bit chưa dùng đến. Bạn có thể đưa vào bất kì loại định dạng nào mà Direct3D định nghĩa sẵn, và GetAdapterModeCount sẽ trả về số chế độ hiển thị phù hợp với định dạng này. Bảng 2.2 đưa ra một vài định dạng D3DFORMATs có thể dùng trong DirectX: EnumAdapterModes Hàm cuối cùng mà bạn cần dùng là EnumAdapterModes. Hàm này sẽ lấy thông tin vào biến có cấu trúc D3DDISPLAYMODE ứng với từng chế độ có thể. Sau đây là định nghĩa của hàm: HRESULT EnumAdapterModes( UINT Adapter, D3DFORMAT Format, UINT Mode, D3DDISPLAYMODE* pMode ); 32
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Như ở trên, tham số đầu tiên ta đưa vào D3DADAPTER_DEFAULT. Tham số thứ hai là định dạng D3DFORMAT mà bạn đang kiểm tra. Tham số thứ ba, là số thứ tự của chế độ mà bạn muốn lấy thông tin. Nhớ rằng, hàm GetAdapterModeCount trả về số chế độ hiển thị mà thiết bị có. Và tham số thứ ba sẽ nằm trong khoảng từ 0 đến số này. Tham số cuối cùng là con trỏ đến biến có cấu trúc D3DDISPLAYMODE. Cấu trúc này lưu thông tin về chế độ đồ hoạ, như chiều rộng, chiều cao, độ quét và định dạng. A Code Example for Querying the Video Adapter Đoạn code dưới đây được lấy từ ví dụ 4, ở thư mục chapter2\example4 trên đĩa CD. Đoạn code này chỉ ra chính xác từng bước và các lệnh cần thiết để đưa ra một hộp thoại chưa danh sách những chế độ hiển thị có thể cho riêng từng định dạng D3DFORMAT. Tôi đã lấy hàm initDirect3D ở ví dụ trên và chỉnh sửa lại để nó có thể lấy ra thông tin về thiết bị đồ hoạ: bool initDirect3D() { pD3D = NULL; // Create the DirectX object if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) return false; }; Trước tiên bạn cần tạo ra một đối tượng Direct3D. Thông qua nó bạn sẽ sử dụng được những hàm cần thiết. // Phần này dùng để lấy thông tin chi tiết về thiết bị đồ hoạ D3DADAPTER_IDENTIFIER9 ident; pD3D->GetAdapterIdentifier(D3DADAPTER_DEFAULT, 0, &ident); Ở đây ta định nghĩa một cấu trúc IDENTIFIER9 và đưa vào hàm GetAdapterIdentifier. Dùng nó, tôi lấy được những thông tin sau: addItemToList(“Adapter Details”); addItemToList(ident.Description); addItemToList(ident.DeviceName); addItemToList(ident.Driver); Ở đây hàm addItemToList dùng để bổ sung thông tin vào hộp thoại sẽ sau này. // lấy ra các chế độ hiển thị mà thiết bị đồ hoạ có UINT numModes = pD3D->GetAdapterModeCount( D3DADAPTER_DEFAULT, D3DFMT_X8R8G8B8); Tiếp theo, ta sử dụng GetAdapterModeCount để lấy ra số chế độ tương ứng với định dạng trên (D3DFMT_X8R8G8B8). Sau đó, ta dùng con số này để thực hiện vòng lặp duyệt qua tất cả các chế độ và lấy thông tin cho từng chế độ đó. for (UINT i=0; I EnumAdapterModes(D3DADAPTER_DEFAULT, D3DFMT_X8R8G8B8, i, &mode); // In ra một dòng trống addItemToList(“”); // đưa ra chiều rộng màn hình sprintf(modeString, “Width=%d”,mode.Width); 33
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN addItemToList(modeString); // Đưa ra chiều cao màn hình sprintf(modeString, “Height=%d”,mode.Height); addItemToList(modeString); // Đưa ra độ quét sprintf(modeString, “Refresh Rate=%d”,mode.RefreshRate); addItemToList(modeString); } return true; } Đây chỉ đơn giản là một hàm trợ giúp việc xuất số liệu, với đầu vào là một chuỗi, và nó sẽ lưu chuỗi này vào vị trí cuối cùng của vecto. Ở cuối hàm initDirect3D, vector này sẽ chứa toàn bộ thông tin về thiết bị đồ hoạ // Biến adapterDetails là một vector chứa các chuỗi (string), mỗi chuỗi sẽ chứa thông tin // của các chế độ đồ hoạ khác nhau std::vector adapterDetails; void addItemToList(std::string item) { adapterDetails.push_back(item); } Hình 2.8 là hộp thoại chứa các thông tin chi tiết, nó sẽ hiện ra khi bạn chạy chương trình ví dụ này. Bởi vì mỗi người có những card đồ hoạ khác nhau, vì thế mà những chi tiết lấy được cũng thay đổi tuỳ thuộc vào cái máy mà chương trình đang chạy trên đó. Chú ý: Thư viện hàm mẫu chuẩn (STL – Standard Template Library) cung cấp cho bạn rất nhiều đối tượng hữu dụng, như kiểu đối tượng string hay vector mà bạn đã từng sử dụng. Việc sử dụng thư viện này sẽ làm đơn giản hoá công việc của bạn bởi vì nó hỗ trợ phát triển trên nhiều nền tảng khác nhau như UNIX hoặc các thiết bị chơi game cá nhân. 34
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Tổng kết chương Trong chương này chúng ta đã lần lượt đi qua rất nhiều các kiến thức cơ bản, từ làm thế nào để tạo một dự án mới trong Visual C cho tới xác lập và xây dựng một dự án sử dụng nền tảng DirectX. Những ví dụ mà chúng ta đã thực hiện có thể khá đơn giản nhưng nó thực sự là những bước mà chúng ta sẽ phải làm khi xây dựng bất kỳ một dự án nào. Những vấn đề đã đề cập trong chương này Trong chương này, chúng ta đã lần lượt đi qua các lý thuyết cơ bản về các phần: Làm thế nào để khởi tạo một đối tượng và thiết bị Direct3D. Phương thức để xoá hàm hình ứng dụng. Thay đổi hàm quản lý sự kiện ứng dụng, bắt các sự kiện chính phục vụ cho quá trình phát triển một ứng dụng game. Thực hiện các quá trình khai báo cho một ứng dụng sử dụng DirectX. Làm thế nào để nhận biết khả năng hỗ trợ của các thiết bị đồ hoạ của hệ thống. Trong chương tiếp theo, chúng ta sẽ đề cập tới một số vấn đề về bề mặt và hoạt cảnh cũng như làm thế nào để xây dựng đối tượng chuyển động trong game. Hỏi đáp Những câu trả lời của phần Hỏi đáp và Bài tập tự làm bạn có thể tra cứu trong phụ lục A “Giải đáp các câu hỏi cuối mỗi chương” để so sánh. 1. Đối tượng DirectX đầu tiên mà bạn cần phải tạo cho bất kỳ ứng dụng nào là gì? 2. Chức năng chính của hàm GetAdapterCount? 3. Xác lập D3DFMT_A8R8G8B8 định nghĩa sử dụng bao nhiêu bit màu cho mỗi một điểm ảnh trên màn hình? 4. Hàm nào trong DirectX có khả năng xoá và xác lập mầu nền hiển thị màn hình? 5. Hàm nào bạn sử dụng để nhận biết số lượng kiểu độ phân giải màn hình mà thiết bị đồ hoạ hỗ trợ? Bài tập tự làm 1. Thay đổi ví dụ 2 trên CD-ROM sao cho giá trị màu cần xoá là màu xanh lá cây. 2. Cập nhật lại ví dụ 4 trên CD-ROM để tìm các kiểu màu mà thiết bị đồ hoạ hỗ trợ (các giá trị kiểu D3DFORMAT khác mà thiết bị hỗ trợ ngoài D3DFMT_X8R8G8B8). 35
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN CHƯƠNG 3 PHÔNG NỀN, KHUNG HÌNH, HOẠT CẢNH hung hình (sprite) – nền tảng của kỹ thuật game 2D, thể hoại game vẫn còn chiếm một thị phần lớn trong ngành công nghiệp game hiện nay. Không phải tất K cả các game đều cần thiết phải có những hệ thống phần cứng 3D mới nhất mới chạy được; những trò chơi vui nhộn và chiếm ít thời gian như Tetris vẫn được viết hoàn toàn 2D và vẫn tỏ ra rất phổ biến hiện này. Trong chương trình sẽ giới thiệu tới bạn những cách đơn giản nhất để sử dụng DirectX cho quá trình tạo một khung hình cơ sở của game. Những phần mà bạn sẽ được giới thiệu trong chương này: Thế nào là phông nền và làm thế nào để sử dụng nó Làm thế nào để thực hiện truy cập tới bộ nhớ đệm (back buffer) Làm thế nào để tạo một offscreen surface. Làm thế nào để tải một file ảnh vào bộ nhớ một cách đơn giản Làm thế nào để tạo và sử dụng sprites (những khung hình) Làm thế nào để tạo một hoạt cảnh sprites. Làm thế nào để xác lập các thông số thời gian sao cho hiệu quả thể hiện hoạt cảnh tốt nhất. You’ve just touched the surface Surfaces là một phần của DirectX. Surface bao gồm những vùng bên trong bộ nhớ được sử dụng để lưu trữ dữ liệu thông tin ảnh. Chúng cỏ thể lưu trữ các bức ảnh hay vật liệu tô phục vụ quá trình hiển thị sau này. Surface được lưu trữ trong bộ nhớ dưới dạng từng khối liên tiếp nhau và thông thường là trên card đồ hoạ, tuy nhiên đôi khi nó cũng có thể được lưu trữ trên bộ nhớ chính của máy. Chương này bao gồm hay vấn đề chính: bộ đệm hiển thị và bộ đệm nền (offscreen). 36
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Bộ đệm hiển thị Có hai dạng bộ nhớ đệm mà bạn cần xem xét: bộ đệm chính (front buffer) và bộ đệm phụ (back buffer). Cả hai đều là vùng bộ nhớ để ứng dụng game của bạn thực hiện các thao tác vẽ lên nó. Bộ đệm chính là những vùng bề mặt có thể nhìn trên cửa sổ ứng dụng game. Tất cả những gì bạn có thể thấy trong các ứng dụng windows đều sử dụng bộ đệm chính hay vùng vẽ. Trong chế độ chạy toàn màn hình, vùng bộ nhớ đệm chính được mở rộng ra và chiếm toàn bộ màn hình. Vùng đệm thứ hai là vùng đệm phụ (vùng đệm nền). Như chúng ta đã đề cập ở trên, vùng đệm phụ - back buffer là nơi bạn thực hiện tất cả các thao tác vẽ. Sau khi quá trình vẽ hoàn tất, bạn sẽ sử dụng hàm Present để thể hiện chúng (copy dữ liệu từ vùng đệm phụ lên vùng đệm chính). Vùng đệm phụ được tạo trong quá trình gọi tới hàm CreateDevice bằng cách xác lập tham số BackBufferCount với kiểu dữ liệu D3DPRESENT_PARAMETERS. Chú ý: Việc thực hiện vẽ trực tiếp lên bộ đệm chính sẽ làm cho hình ảnh thể hiện bị nháy và giật. Các đối tượng đồ hoạ thông thường phải được vẽ lên bộ đệm phụ trước, sau đó gọi tới hàm Present đề thể hiện. Offscreen Surfaces Offscreen surfaces là vùng trên bộ nhớ đồ hoạ hay hệ thống được dùng đề lưu trữ những đối tượng hình hoạ mà game cần sử dụng. Có thể lấy vì dụ, nếu bạn đang tiến hành khởi tạo một game nhập vai, bạn sẽ cần phải có một vùng để lưu trữ những dữ liệu để thể hiện nhiều dạng địa hình khác nhau, hay những hình ảnh cho nhận vật của bạn. Offscreen surface có lẽ là sự lựa chọn tốt nhất cho công việc này. Thông thường các hình ảnh sử dụng trong DirectX đều là dạng bitmaps. Hình minh hoạ 3.1 kế bên là ví dụ cho các ảnh bitmaps có thể được sử dụng trong ứng dụng game của bạn để thể hiện các dạng địa hình khác nhau. Chú ý: Một vài thiết bị đồ hoạ cũ chỉ hỗ trợ tạo offscreen surfaces phù hợp với bộ đệm (primary buffer). Các thiết bị đồ hoạ mới hơn cho phép bạn có thể tạo được các surface lớn hơn. Offscreen surface, được sử dụng thông qua giao diện IDirect3DSurface9 và được tạo bởi lời gọi tới hàm CreateOffscreenPlainSurface. Bạn phải gọi tới hàm này cho mỗi đối tượng surface mà bạn muốn sử Hình 3.1 Các ảnh hay được sử dụng. Hàm CreateOffscreenPlainSureface này được dụng trong game nhập vai định nghĩa như sau: HRESULT CreateOffscreenPlainSurface( UINT Width, // bề ngang của surface UINT Height, // chiều cao của the surface D3DFORMAT Format, // đối tượng có kiểu D3DFORMAT DWORD Pool, // bộ nhớ dùng chung pool IDirect3DSurface9 ppSurface, // con trỏ đối tượng kết quả trả về 37
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN HANDLE* pHandle // luôn luôn có giá trị NULL ); Hàm CreateOffscreenPlainSurface yêu cầu 6 tham số đầu vào: Width. Tham số này xác lập bề rộng tính theo pixel của bộ đệm. Height. Tham số xác lập chiều cao tính theo pixel của bộ đệm. Format. Tham số định dạng kiểu bộ đêm có cấu trúc D3DFORMAT. Pool. Vùng bộ nhớ sử dụng lưu trữ surface. Bạn có thể lựa chọn một trong các kiểu sau đây. o D3DPOOL_DEFAULT. Hệ thống sẽ lựa chọn vùng nhớ phù hợp nhất (trên thiết bị đồ hoạ hoặc bộ nhớ hệ thống) để lưu chữ surface. o D3DPOOL_MANAGED. Dữ liệu sẽ được copy vào bộ nhớ chính khi cần thiết. o D3DPOOL_SYSTEMMEM. Surface sẽ được khởi tạo trên bộ nhớ hệ thống. o D3DPOOL_SCRATCH. Quá trình khởi tạo sẽ được thực hiện trên bộ nhớ hệ thống nhưng không thể truy cập trực tiếp bằng DirectX. PpSurface. Đây là con trỏ trỏ tới đối tượng có giao diện IDirect3DSurface9. Biến này dùng để quản lý đôi tượng surface sau khi được tạo ra. pHandle. Đây là tham số dùng để dự phòng và nó luông được gán giá trị NULL. Ví dụ mẫu dưới đây sẽ minh hoạ quá trình gọi tới hàm CreateOffscreenPlainSurface. Trong đó đối tượng surface sẽ có độ phân giải 640x480 và định dạng kiểu thể hiện là D3DFMT_X8R8G8B8. hResult = CreateOffscreenPlainSurface( 640, // Bề rộng của surface được tạo ra 480, // Chiều cao của surface được tạo ra D3DFMT_X8R8G8B8, // Định dạng thể hiện của surface D3DPOOL_DEFAULT, // kiểu dữ liệu bộ nhớ pool được sử dụng &surface, // con trỏ lưu surface đã được tạo ra NULL); // tham số dự phòng, mặc định luôn gán cho giá trị NULL // Kiểm tra xem kết quả trả về của hàm có thành công hay không if (FAILED(hResult)) return NULL; Tải ảnh Bitmap cho Surface Bởi vì định dạng ảnh kiểu Bitmap rất hay được sử dụng trong các ứng dụng đồ hoạ Windows. Chính vì thế chúng ta cũng sẽ sử dụng định dạng này trong các ví dụ tiếp theo. DirectX cung cấp ta khá nhiều hàm trong thư viện D3DX để trợ giúp chúng ta có thể dễ dàng tải nhanh những bức ảnh này để thực hiện quá trình vẽ tiếp theo. Chú ý: Có rất nhiều kiểu định dạng ảnh đang được sử dụng trong quá trình phát triển game hiện nay. Thông thường một số công ty thường hay sử dụng kiểu định dạng Bitmap hoặc Targa, tuy nhiên cũng rất nhiều công ty tự xây dựng cho mình một kiểu định dạng ảnh khác nhau nhằm bảo vệ những dữ liệu ảnh của họ. Ngăn cản người dùng hoặc những người phát triển game khác có thể chỉnh sửa hoặc sử dụng lại dữ liệu ảnh đó. 38
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Những chức năng chính của thư viện D3DX Hệ thống thư viện D3DX bao gồm tập hợp những hàm thường hay dùng được Microsoft cung cấp kèm them với bộ DirectX SDK. Nó bao gồm tập hợp các hàm với chức năng: o Các hàm quản lý quá trình đọc các dữ liệu ảnh o Đọc và xử lý làm mịn các đối tượng 3D o Các hàm thực hiện hiệu ứng shader o Các hàm phục vụ quá trình biến đổi và xoay các đối tượng Bạn có thể sử dụng những hàm trong thư viện D3DX này bằng cách thêm dòng lệnh khai báo #include và liên kết tới tệp tin thư viện d3dx9.lib Hàm D3DXLoadSurfaceFromFile được dùng để đọc dữ liệu ảnh bitmap từ tệp tin vào vùng đệm offscreen surface. Cấu trúc lời họi hàm có dạng như sau: HRESULT D3DXLoadSurfaceFromFile( LPDIRECT3DSURFACE9 pDestSurface, CONST PALETTEENTRY* pDestPalette, CONST RECT* pDestRect, LPCTSTR pSrcFile, CONST RECT* pSrcRect, DWORD Filter, D3DCOLOR ColorKey, D3DXIMAGE_INFO* pSrcInfo ); Hàm D3DLoadSurfaceFromFile này yêu cầu 8 tham số đầu vào: pDestSurface. Con trỏ đối tượng surface quản lý các ảnh bitmap được tải vào. pDestPalette. Con trỏ đối tượng kiểu PALLETTEENTRY. Tham số này chỉ sử dụng cho loại ảnh bitmapp 256 màu. Đối với loại ảnh 16-, 24-, 32-bit màu tham số này phải được xác lập là NULL. pDestRect. Con trỏ đối tượng có cấu trúc RECT dùng để thể hiện vùng chữ nhật của surface mà ảnh bitmap sẽ tải vào. pSrcFile. Tên tệp tin ảnh bitmap được tải vào (bao gồm cả đường dẫn nếu tệp tin ảnh khác thư mục với tệp tin chương trình). pSrcRect. Con trỏ đối tượng kiểu RECT lưu trữ vị trí vùng dữ liệu ảnh gốc sẽ được tải vào cho đối tượng surface. Filter. Tham số có kiểu D3DX_FILTER dùng để xác định kiểu bọ lọc được sử dụng trong quá trình tải ảnh bitmap gốc. ColorKey. Đối tượng kiểu D3DCOLOR nhằm xác lập giá trị màu được gán kiểu thể hiện “trong xuốt” (transparency color). Giá trị mặc định của tham số là 0. pSrcInfo. Con trỏ đối tượng kiểu D3DIMAGE_INFO chứa các thông tin thuộc tính của tệp tin ảnh bitmap như chiều cao, bề ngang và chất lượng màu của một điểm ảnh (số lượng màu mà một điểm ảnh có thể thể hiện – được tính bằng -bit). 39
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Sau đây là một ví dụ đơn giản gọi tới hàm D3DLoadSurfaceFromFile, nó thực hiện tải ảnh bitmap từ tệp tin test.bmp vào trong vùng đệm offscreen surface. Chú ý là bạn phải tạo đối tượng surface lưu trữ bằng hàm CreateSurfaceFromFile trước khi gọi tới hàm này. IDirect3DSurface9* surface; hResult = D3DXLoadSurfaceFromFile( surface, NULL, NULL, “test.bmp”, NULL, D3DX_DEFAULT, 0, NULL ); if ( FAILED( hResult ) ) return NULL; Sau lời gọi trên đây, dữ liệu toàn bộ ảnh bitmap trong tệp tin test.bmp sẽ được tải vào bộ nhớ và sẵn sàng để bạn sử dụng. Sử dụng DirectX để thể hiện một hình ảnh Chúng ta đã học cách tạo một surface cũng như làm thế nào để tải một ảnh bitmap vào trong nó, bây giờ là lúc chúng ta sẽ thể hiện nó. Để làm được điều này, bạn phải tạo một số thay đổi trong hàm Render mà chúng ta đã tạo trước đó Trong phần trước, chúng ta đã xây dựng hàm Render như đoạn mã minh hoạ dưới đây: / * Render(void) / void Render(void) { // Kiểm tra xem đối tượng Direct3D device đã thực sự được khởi tạo hay chưa. if( NULL == pd3dDevice ) return; // Xoá bộ đệm màn hình (back buffer) bằng màu xanh nước biển pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 ); // Thể hiện hình ảnh từ dữ liệu trên bộ nhớ đệm màn hình pd3dDevice->Present( NULL, NULL, NULL, NULL ); } Để hiển thị ảnh bitmap đó lên màn hình, bạn cần phải sử dụng hàm StretchRect, hàm này sẽ thực hiện việc sao chép và kéo dãn, thay đổi tỷ lệ hình ảnh nếu 2 vùng chữ nhật lưu trữ ảnh gốc và ảnh đích có kích thước khác nhau. Hàm StretchRect này được định nghĩa như sau: HRESULT StretchRect( IDirect3DSurface9 *pSourceSurface, CONST RECT *pSourceRect, IDirect3DSurface9 *pDestSurface, CONST RECT *pDestRect, D3DTEXTUREFILTERTYPE Filter ); Các tham số đầu vào của hàm StretchRect này bao gồm: pSourceSurface. Con trỏ đối tượng surface quản lý các ảnh bitmap được tải vào. 40
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN pSourceRect. Con trỏ kiểu RECT chứa dữ liệu vùng được sao chép. Néu tham số này là NULL, toàn bộ dữ liệu surface gốc sẽ được sao chép. pDestSurface. Con trỏ chứa đối tượng surface đích. Trong hầu hết các trường hợp, nó là con trỏ của bộ đệm phụ back buffer. pDestRect. Con trỏ kiểu RECT chứa dữ liệu vùng thể hiện đối tượng được sao chép lên surface đích. Tham số này có thể là NULL nếu bạn kô muốn xác lập vùng kết xuất. Filter. Kiểu lọc sử dụng trong quá trình sao chép. Bạn có thể xác lập giá trị này là D3DTEXF_NONE nếu không muốn xác lập kiểu lọc. Có lẽ bạn sẽ tự hỏi làm thế nào đế có thể lấy được con trở chứa dữ liệu của bộ đệm back buffer surface. Hàm để thực hiện chức năng này có tên là GetBackBuffer, cấu trúc của nó có dạng như sau: GetBackBuffer( 0, // giá trị thể hiện kiểu cháo đổi 0, // chỉ số của bộ đệm // 0 nếu chỉ có một bộ đệm được sử dụng D3DBACKBUFFER_TYPE_MONO, // một đối số định kiểu &backbuffer); // đối tượng trả về có kiểu IDirect3DSurface9 Kết hợp thêm các lời gọi tới hàm StretchRect và GetBackBuffer, hàm Render của chúng ta lúc này sẽ có dạng tương tự dưới đây: / * Render / void Render(void) { // Con trỏ bộ đệm back buffer IDirect3DSurface9* backbuffer = NULL; // Kiểm tra đối tượng Direct3D device đã tồn tại if( NULL == pd3dDevice ) return; // Xoá toàn bộ bộ đệm về màu xanh nước biển pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 ); // Lấy con trỏ bộ đệm back buffer pd3dDevice->GetBackBuffer( 0, 0, D3DBACKBUFFER_TYPE_MONO, &backbuffer ); // Sao chép toàn bộ dữ liệu offscreen surface vào bộ đệm pd3dDevice->StretchRect( srcSurface, NULL, backbuffer, NULL, D3DTEXF_NONE ); // Thể hiện hình ảnh từ bộ đệm lên màn hình pd3dDevice->Present ( NULL, NULL, NULL, NULL ); } Bạn có thể tìm thấy mã nguồn của ví dụ này trong thư mục chapter3\example1 trên CD- ROM. Biên dịch và chạy ứng dụng, một cửa sổ chương trình sẽ xuất hiện có dạng sau: 41
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Hình 3.2 Hiển thị ảnh nền cho cửa sổ ứng dụng Xem xét lại hàm StretchRect Trong phần trước, chúng ta đã sử dụng hàm StretchRect để sao chép toàn bộ ảnh offscreen surface vào bộ đệm, nhưng đó không phải là toàn bộ chức năng hữu dụng nhất của hàm này. StretchRect còn cho phép bạn sao cheo một hoặc nhiều khung hình nhỏ của ảnh offscreen surface, nó cho phép một surface có thể tạo nền từ nhiều hình nhỏ hơn. Ví dụ, một ảnh offscreen surface có thể chứa rất nhiều khung hình chuyển động nhỏ của một nhân vật, hoặc các hình ảnh nhỏ để tạo nên một puzzle game. Hàm StretchRect có 2 tham số - pSourceRect và pDesRect – dùng để xác định vùng dữ liệu sẽ copy từ đâu đến đâu. Hình 3.3 sẽ minh hoạ việc sử dụng chức năng này. Hình 3.3 Ảnh bên tay trái là ảnh gốc và hình chữ nhật miêu tả vùng dữ liệu sẽ được sao chép. Ảnh bên phải mô tả vị trí và vùng ảnh được sao chép lên bộ đệm. Trong ví dụ tiếp theo chúng ta sẽ sử dụng chức năng này để thể hiện một thông điệp lên màn hình từ dữ liệu ảnh gốc chứa toàn bộ các chữ trong bảng chữ cái. Hình 3.4 minh hoạ ảnh dữ liệu gốc này, như bạn có thể thấy tất cả các chữ cái được lưu trong các vùng chữ nhật có kích thước giống nhau. Việc xác lập lưu các chữ cái với cùng một kích cỡ sẽ giúp chúng ta tiện lợi hơn rất nhiều trong quá trình xử lý và tìm tới chữ cái cần thiết một cách nhanh nhất. 42
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Bởi vì chúng ta sẽ phải sao chép nhiều khung hình nên chúng ta sẽ phải gọi tới hàm StretchRect nhiều lần. Để mọi thứ trở nên đơn giản, chúng ta sẽ đặt tất cả những lời gọi đó vào trong một vòng lặp bên trong hàm Render. / * Render / void Render(void) { int letterWidth=48; // Thông số bề ngang mặc đinh của một ô chữ cái int letterHeight=48; // chiều cao mặc định của một ô chữ cái int destx = 48; // Toạ độ X của đểm trên cùng phía bên trái của chữ cái đầu tiên int desty = 96; // Toạ độ Y của đểm trên cùng phía bên trái của chữ cái đầu tiên // Biến chứa con trỏ bộ đệm IDirect3DSurface9* backbuffer = NULL; // Kiểm tra đối tượng Direct3D device if( NULL == pd3dDevice ) return; // Xoá bộ đệm bằng màu xanh nước biển pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 ); // Lấy con trỏ của bộ đệm pd3dDevice->GetBackBuffer(0,0,D3DBACKBUFFER_TYPE_MONO, &backbuffer); // Xác lập biến chứa vị trí chữ cái đang được xem xét int count=0; // Thực hiện vòng lặp trên từng ký tự của chuỗi thông điệp đầu vào for ( char *c = message; c != “ “; c++ ) { RECT src; // Dữ liệu vùng chữ nhật gốc và kết xuất RECT dest; int srcY = ( ( ( *c - ‘A’ ) / 6 ) ) * letterHeight; // Tìm toạ độ vùng dữ liệu sao chép gốc int srcX = ( ( ( *c - ‘A’ ) %7 ) * letterWidth ); src.top = srcY ; src.left = srcX; src.right = src.left + letterWidth; src.bottom = src.top + letterHeight; // Tìm toạ độ vùng dữ liệu sẽ được sao chép tới dest.top = desty; dest.left = destx + ( letterWidth * count ); dest.right = dest.left + letterWidth; dest.bottom = dest.top + letterHeight; // Tăng biến đếm lên 1 count++; // sao chép dữ liệu vào bộ đệm pd3dDevice->StretchRect( srcSurface, // Dữ liệu surface gốc src, // Vùng dữ liệu muôn sao chép backbuffer, // Dữ liệu được sao chép vào dest, // vùng dữ liệu được sao chép D3DTEXF_NONE); // kiểu bộ lọc sử dụng } // Thể hiện dữ liệu từ bộ đệm lên màn hình pd3dDevice->Present( NULL, NULL, NULL, NULL ); } Kết quả của ví dụ trên là thể hiện dòng chữ “HELLO WORLD” và đem lại cho người dùng cảm giác của một bức thư tống tiền :). Hình 3.5 minh hoạ kết quả kết xuất của chương trình. Bạn có thể tìm mã nguồn đầy đủ trong thư mục chapter3\example2. 43
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Trước khi vòng lặp được thực hiện đọc từng chữ cái của thông điệp, nó phải được định nghĩa trước ở bên ngoài hàm render có dạng như sau: char *message = “HELLO WORLD”; Trong mỗi bước nhảy của vòng lặp, chúng ta sẽ tiến hành lọc trên từng chữ cái một. Ví dụ, trong lần lặp đầu tiên, chúng ta sẽ chỉ làm việc với chữ cái H trong từ “HELLO”. Đoạn mã tiếp theo sẽ tính toán vùng chữ nhật gốc bao gồm toạ độ X và Y của đỉnh trên cùng góc bên trái của hình. int srcY = ( ( ( *c - ‘A’ ) / 6 ) ) * letterHeight; int srcX = ( ( ( *c - ‘A’ ) %7 ) * letterWidth); Sau khi chúng ta đã có toạ độ của điểm này, chúng ta sẽ biết được đoạ độ điểm dưới cùng bên phải của hình chữ nhật chứa chữ cái đó thông qua chiều cao và chiều rộng của nó. src.top = srcY ; src.left = srcX; src.right = src.left + letterWidth; src.bottom = src.top + letterHeight; Tiếp đến chúng ta sẽ phải xác định vị trí mà chữ cái sẽ được đặt trên bộ đệm. dest.top = desty; dest.left = destx + ( letterWidth * count ); dest.right = dest.left + letterWidth; dest.bottom = dest.top + letterHeight; Chúng ta đã xác lập biến cout dùng để kiểm tra xem bao nhiêu chữ cái đã được vẽ trên màn hình. Thông qua biến count này, chúng ta sẽ tính toán được toạ độ điểm của các chữ cái cần chèn. Hình 3.5 Hình minh hoạ sử dụng ảnh bitmap các chữ cái để thể hiện chuỗi, Hello world. 44
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Chú ý: Bạn cũng có thể sử dụng hàm StretchRect để thực hiện quá trình kéo dãn, phóng to một hình ảnh trong quá trình sao chép. Nếu vùng chữ nhật đích có kích thước khác: lớn hơn hay nhỏ hơn thì hình ảnh đó sẽ tự động điều chỉnh lại sao cho được phủ đầy vùng ảnh kết xuất đó. Sprites Như đã đề cập ở trong phần trước, bạn có thể thực hiện quá trình sao chép những vùng ảnh giữa các surfaces. Trong ví dụ thứ 2, chúng ta đã thể hiện một dòng thông bao từ các ảnh bitmap. Sử dụng phương pháp tương tự, chúng ta cũng sẽ có thể xây dựng được một hệ thống để thể hiện các sprites. Sprites là những đối tượng đồ hoạ dạng 2D và nó thường được sử dụng trong các trò chơi dưới dạng hình ảnh của các nhân vật hay bất kỳ một đối tượng nào. Ví dụ, trong chế độ nền của game, một sprite có thể được thể hiện quá trình nhân vật di chuyển trên màn hình. Sprites đơn giản là một chuỗi các khung hình của hoạt cảnh về một nhân vật hay đối tượng nào đó trong game, nó có thể được di chuyển bởi người chơi, có thể tương tác với các đối tượng khác trong thế giới game đó. Trong phần này chúng ta sẽ đi qua các khái niệm cơ bản cũng như học cách tạo và sử dụng chúng trong game. Chú ý: Một khung hình đơn giản chỉ là một hình ảnh trong chuỗi các hình ảnh của một hoạt cảnh. Việc thể hiện liên tiếp các hình ảnh này sẽ tạo nên hiệu ứng chuyển động tương tự trong kỹ thuật làm film. Một sprite cần phải có những gì? Điều đầu tiên mà tất cả các sprites đều cần đó là hình ảnh để thể hiện. Hình ảnh này có thể được sử dụng làm một hay nhiều khung hình thể hiện trong một hoạt cảnh. Sprites cũng cần một thông số khác đó là vị trí mà sprites được hiển thị trên màn hình. Giá trị này thường là tham số X, Y trong hệ toạ độ. Để có thể sử dụng được trong các trò chơi, sprites có thể còn phải lưu trữ thêm một vài thông tin khác nữa tuy nhiên 2 thông số trên thực sự cần thiết cho bất kỳ một sprites nào. Mã nguồn mô tả đối tượng sprite Cấu trúc của đối tượng sprite sẽ lưu giữ toàn bộ thông tin về từng sprite mà bạn muốn tạo, cấu trúc cơ bản có thể có dạng sau: struct { RECT sourceRect; // Vùng lưu giữ vị trí của sprit (trong offscreen surface) int X; // toạ độ X của sprite trên màn hình int Y; // toạ độ Y của sprite trên màn hình } spriteStruct ; Trong cấu trúc spriteStruct ở trên, dữ liệu ảnh của sprite được xác định thông quá biến có kiểu cấu trúc RECT. Biến sourceRect này nắm giữ vị trí của sprite trong ảnh gốc. Giá trị toạ độ X và Y được sử dụng là giá trị kiểu nguyên. Bởi vì trong ví dụ này chúng ta chỉ hỗ trợ độ phân giải 640x480, nên giá trị kiểu nguyên là đủ để lưu trữ toạ độ sprite. 45
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Những gì chúng ta đã làm ở trên chỉ là một xác lập ban đầu hết sức đơn giản cho một sprite. Tiếp đến chúng ta sẽ thực hiện quá trình thể hiện chúng lên màn hình. Tạo một đối tượng Sprite Để tạo một sprite, bạn sẽ cần phải sử dụng một vài hàm mà chúng ta đã đề cập trước đó. D3DXLoadSurfaceFromFile CreateOffscreenPlainSurface StretchRect Mỗi một hàm được liệt kê ở trên đều có một chức năng riêng trợ giúp quá trình tạo và sử dụng sprites. Hàm D3DLoadSurfaceFromFile thực hiện quá trình đọc dữ liệu ảnh của sprites từ tệp tin, hàm CreateOffscreenPlainSurface tạo một vùng trên bộ nhớ để lưu trữ những hình ảnh bạn cần sử dụng và hàm StretchRect trợ giúp hiện thị hình ảnh lên màn hình ứng dụng. Đọc dữ liệu ảnh của Sprites Bạn có thể sử dụng hàm D3DXLoadSurfaceFromFile để đọc dữ liệu ảnh cho một đối tượng IDirect3DSurface9 đã được tạo trước đó bằng hàm CreateOffscreenPlainSurface. Để thực hiện quá trình trên, chúng ta sẽ đặt chúng vào trong một hàm duy nhất có tên là getSurfaceFromBitmap. Hàm này chỉ có một tham số đầu vào duy nhất là một chuỗi string chứa tên của file cần đọc. / * getSurfaceFromBitmap / IDirect3DSurface9* getSurfaceFromBitmap(std::string filename) { HRESULT hResult; IDirect3DSurface9* surface = NULL; D3DXIMAGE_INFO imageInfo; // tạo biến lưu giữ thông tin của ảnh gốc // Lấy thông tin chiều rộng, chiều cao của hình ảnh gốc hResult = D3DXGetImageInfoFromFile(filename.c_str(), &imageInfo); // Chắc chắn quá trình gọi hàm D3DXGetImageInfoFromFile đã thành công if FAILED (hResult) return NULL; // Tạo một offscreen surface lưu giữ liệu ảnh gốc hResult = pd3dDevice->CreateOffscreenPlainSurface( width, height, D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT, &surface, NULL ) // Chắc chắn quá trình gọi hàm kô thất bại if ( FAILED( hResult ) ) return NULL; // Đọc dữ liệu ảnh cho đối tượng surface vừa tạo từ tệp tin hResult = D3DXLoadSurfaceFromFile( surface, NULL, NULL, filename.c_str( ), NULL, D3DX_DEFAULT, 46
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN 0, NULL ); if ( FAILED( hResult ) ) return NULL; return surface; } Hàm getSurfaceFromBitmap được sử dụng như sau: IDirect3DSurface9* spriteSurface; spriteSurface = getSurfaceFromBitmap( “sprites.bmp”); If (spriteSurface == NULL) return false; Trước tiên bạn tạo một biến để lưu một surface mới, sao đó gọi tới hàm getSurfaceFromBitmap với tham số là tên của file ảnh bitmap cần đọc. Bạn nên chú ý kiểm tra kết quả trả về để chắc chắn hàm getSurfaceFromBitmap đã Hình 3.6 Một ảnh bitmap chứa các hình ảnh được thực hiện thành công. Hình 3.6 minh của sprite hoạ một ảnh có chứa nhiều sprites. Hàm GetSurfaceFromBitmap sẽ được sử dụng trong toàn bộ các phần tiếtp theo của quấn sách mỗi khi cần thiết. Khởi tạo các Sprites. Sau khi bạn đã đọc dữ liệu ảnh của sprite, bây giờ là lúc chúng ta xác lập thông tin chính xác cho từng sprite. Bời vì chúng ta sẽ sử dụng từng đối tượng surface đơn lẻ để chứa đựng lần lượt tất cả các sprité, một ý tưởng tốt là đặt tất cả những đoạn mã khởi tạo cho từng sprite này vào trong một vòng lặp for. Hàm initSpites dưới đây minh hoạ kỹ thuật này. #define SPRITE_WIDTH 48 #define SPRITE_HEIGHT 48 #define SCRN_WIDTH 640 #define SCRN_HEIGHT 480 / / * bool initSprites(void) / bool initSprites(void) { // Vòng lặp thực hiện khởi tạo cho 10 sprite for (int i = 0; i < 10; i++ ) { spriteStruct[i].srcRect.top = 0; spriteStruct[i].srcRect.left = i * SPRITE_WIDTH; spriteStruct[i].srcRect.right = spriteStruct[i].srcRect.left + SPRITE_WIDTH; spriteStruct[i].srcRect.bottom = SPRITE_HEIGHT; spriteStruct[i].posX = rand()% SCRN_WIDTH – SPRITE_WIDTH; spriteStruct[i].posY = rand()% SCRN_HEIGHT – SPRITE_HEIGHT; } return true; } Vòng lặp for sẽ thực hiện quá trình khởi tạo trong 10 lần lặp, kết quả trả về là 10 sprite khác nhau được đọc. 47
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Bên trong vòng lặp, biến srcRect phải được xác lập và thay đổi. Nó lưu giữ vị trí của sprite bên trong ảnh gốc. Tham số cuối cùng X và Y lưu toạ độ hiển thị của sprite, trong trường hợp này chúng ta sẽ đặt một vị trí ngẫu nhiên để hiển thị sprite trên màn hình. Hiển thị Sprites Đến thời điểm này, công việc cuối cùng bạn phải làm là thể hiện các sprites đó lên màn hình. Một lần nữa, chúng ta sẽ thực hiện một vài thay đổi trong hàm render. Lần này, vòng lặp for đã được tạo trước đó sẽ gọi tới hàm StretchRect nhiều lần, mỗi một lần đó tương ứng với một sprite được hiển thị. / * Render(void) / void Render(void) { // Biến lưu con trỏ của bộ đệm IDirect3DSurface9* backbuffer = NULL; if( NULL == pd3dDevice ) return; // Xoá bộ đệm với màu đen pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0 ); // Lấy về con trỏ của bộ đệm pd3dDevice->GetBackBuffer(0,0,D3DBACKBUFFER_TYPE_MONO, &backbuffer); // Vòng lặp hiển thị tất cả các sprites for ( int i = 0; i StretchRect( spriteSurface, srcRect, backbuffer, destRect, D3DTEXF_NONE); } // Hiển thị dữ liệu trên bộ đệm lên màn hình pd3dDevice->Present( NULL, NULL, NULL, NULL ); } Trong đoạn mãn trên một lần nữa chúng ta đã thực hiện một vòng lặp chạy lần lượt trên 10 sprites. Bên trong vòng lặp chúng ta đã thực hiện tạo và xác lập giá trị cho một biến RECT chứa dữ liệu tạm. Hàm StretchRect sẽ sử dụng biến kiểu RECT mà bạn đã tạo này để yêu cầu DirectX thể hiện đúng toạ độ sprite sẽ được hiển thị. Cuối cùng là lời gọi của hàm StretchRect như đã nói, quá trình này được thực hiện lần lượt từng bước, từng bước. Dữ liệu của sprites sẽ được đặt lên bộ nhớ đệm trước. Hình 3.7 minh hoạ toàn bộ 10 sprites được hiển thị lên màn hình sau khi kết thúc lời gọi tới hàm render. 48
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Bạn có thể tìm thấy toàn bộ mã nguồn của ví dụ này trong thư mục chapter3\example3 trên đĩa CD-ROM đi kèm với sách. Hình 3.7 Hình ảnh minh hoạ 10 sprites được hiển thị ngẫu nhiên Di chuyển các Sprites Các sprites của bạn đã được hiển thị toàn bộ lên màn hình, liệu bạn đã hài lòng với cách hiển thị này? Có lẽ là chưa. Một trong những kỹ thuật nổi bật của một đối tượng sprite là nó có thể di chuyển được. Tôi chắc chắn rằng trò chơi Sonic the Hedgehog sẽ chẳng có gì hấp dẫn nếu Sonic chẳng thể di chuyển được. Các Sprites cần phẩi biết đường đi, khoảng cách và hướng đi mà nó cần phải di chuyển trong mỗi một khung hình. Để khắc phục vấn đề này, bạn cần khai báo thêm một vài biến khác trong cấu trúc của sprite mà chúng ta đã định nghĩa trước đó. struct { RECT srcRect; // Lưu giữ vị trí của sprite trên vùng ảnh gốc // Vị trí hiển thị của sprite int posX; // Toạ độ của sprite theo phương X int posY; // Toạ độ của sprite theo phương Y // Khả năng di chuyển trong mỗi một khung hình int moveX; // khoảng cách tính theo pixel mà sprite có thể di chuyển theo phương X int moveY; // tương tự đối với phương Y } spriteStruct; Như bạn đã thấy, hai biến moveX và moveY đã được thêm vào. Hai biến này sẽ được sử dụng để lưu giữ giá trị số lượng pixels trên mỗi khung hình mà bạn muốn sprite đó di chuyển. Giá trị moveX và moveY này sẽ được cộng thêm vào cho giá trị posX và posY tương ứng trong quá trình gọi tới hàm StretchRect trên mỗi sprite. Ví dụ như, trên mỗi một khung hình chúng ta có thể sử dụng đoạn mã nguồn sau: for (int i = 0; i < 10; i++) { spriteStruct[ i ].posX += spriteStruct[ i ].moveX; spriteStruct[ i ].posY += spriteStruct[ i ].moveY; } 49
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN Các sprites sau đó sẽ được gửi tới hàm Render để thực hiện quá trình hiển thị. Trên mỗi khung hình, biến lưu trữ toạ độ sẽ được cập nhật, kết quả là sprite đó sẽ được di chuyển trên màn hình ứng dụng. Dĩ nhiên, bạn sẽ phải kiếm tra vị trí posX và posY để chắc chắn sprite đó được hiển thị đúng với độ phân giải màn hình. Ví dụ, trong đoạn mã ví dụ trên có thể được thay đổi để đảm bảo các sprites luôn nằm trong vùng hiển thị 640x480 của ứng dụng: for (int i = 0; i SCRN_WIDTH) { // Nếu giá trị posX lớn hơn thì thực hiện đảo dấu của moveX bằng cách nhân // với giá trị -1. Điều này sẽ làm cho sprite sẽ chuyển động theo chiều ngược // lại tại các khung hình sau spriteStruct[ i ].moveX *= -1; } // Tương tự đối với toạ độ theo phương Y của sprite spriteStruct[ i ].posY += spriteStruct[ i ].moveY; // Kiểm tra posY với SCRN_HEIGHT (=480) if (spriteStruct[ i ].posY > SCRN_HEIGHT) { spriteStruct[ i ].moveY *= -1; } // Bởi vì sprite cũng có thể chuyển động ngược lại nên một ý tưởng tốt là chúng ta // sẽ kiểm tra cả trường hợp toạ độ của sprite có nhỏ hơn 0 không if (spriteStruct[ i ].posX < 0) { // Nếu trường hợp này xảy ra thì đảo chiều chuyển động của sprite spriteStruct[ i ].moveX *= -1; } // Tương tự đối với posY if (spriteStruct[ i ].posY < 0) { spriteStruct[ i ].moveY *= -1; } } Đoạn mã nguồn trên sẽ làm cho sprite luôn chuyển động trên vùng hiển thị 640x480 của màn hình. Chú ý: Nếu trong quá trình ứng dụng game đang chạy mà bạn thay đổi lại kích thước của cửa sổ ứng dụng, bạn phải chắc chắn giá trị posX và posY phải được kiểm tra lại để chắc chắn sprite vẫn còn nằm trong vùng hiển thị. Tạo các sprite chuyển động Trong phiên bản trước của sprite chúng ta đã cập nhật để nó hỗ trợ khả năng di chuyển của sprite trên màn hình, nhưng thực sự thì những sprites này vẫn chưa hấp dẫn. Những sprites này chỉ thể hiện một hình ảnh tĩnh duy nhất. Trong phần tiếp theo, bạn sẽ thực 50
- Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN hiện chèn thêm nhiều khung hình của sprite hơn để tạo một sprite chuyển động thực sự sinh động. Để thực hiện được khả năng này, chúng ta sẽ tiến hành cập nhật lại cấu trúc của sprite, mã nguồn mới sẽ được liệt kê dưới đây và hình minh hoạ Hình 3.8. Dữ liệu ảnh của một sprite chuyển động 3.8 ở bên là dữ liệu của một sprite mẫu. struct { RECT srcRect; // lưu trữ vị trí vùng ảnh gốc của sprite // dữ liệu về toạ độ của sprite int posX; // toạ độ theo phương X int posY; // toạ độ theo phương Y // dữ liệu di chuyển của sprite int moveX; // khoảng cách di chuyển tính bằng pixels theo phương X int moveY; // khoảng cách di chuyển tính bằng pixels theo phương Y // dữ liệu animation sprite int numFrames; // số lượng khung hình của animation sprite int curFrame; // khung hình đang được hiển thị của sprite } spriteStruct; Để hỗ trợ khả năng hiển thị chuyển động của sprite, chúng ta thêm vào 2 biến: numFrames. Số lượng khung hình của một animation sprite. curFrame. Khung hình hiện tại đang được hiển thị của animation sprite. Hai biến này sẽ giúp chúng ta quản lý và lưu giữ trạng thái hiển thị các ảnh động của sprite trong vòng lặp hiển thị sprite. Bởi vì chúng ta đã thêm vào các biến mới trong kiểu cấu trúc của sprite nên chúng ta sẽ phải thay đổi lại mã nguồn hàm initSprite để hỗ trợ nó. Mã nguồn mới của hàm initSprite được liệt kê dưới đây: / * bool initSprites(void) / bool initSprites(void) { // Vòng lặp khởi tạo tất cả các sprite for (int i=0; i < 10; i++) { // Xác lập vị trí dữ liệu chứa sprite trên ảnh gốc spriteStruct[i].srcRect.top = 0; spriteStruct[i].srcRect.left = i * 64; spriteStruct[i].srcRect.right = spriteStruct[i].srcRect.left + 64; spriteStruct[i].srcRect.bottom = 23; // Xác lập toạ độ hiển thị ngẫu nhiên cho sprite spriteStruct[i].posX = rand()%600; // spriteStruct[i].posY = rand()%430; // Xác lập dữ liệu ảnh động của sprite spriteStruct[i].curFrame = 0; // Start at frame 0 spriteStruct[i].numFrames = 4; // Số lượng khung hình của một sprite // Xác lập dữ liệu di chuyển của sprite spriteStruct[i].moveX = 1; // Di chuyển sprite theo từng pixel // chỉ cho sprite di chuyển theo chiều trái – phải spriteStruct[i].moveY = 0; // sprite không thể di chuyển theo phương Y } return true; 51



