E D R S I H C RSS
ID
Password
Join
너의 자녀들에게 아버지와 친구가 되거라. 둘 중에 하나를 선택해야 될 것 같으면 아버지를 택해라. 친구는 너 말고도 많겠지만 아버지는 너 하나이기 때문이다. -- 아버지가 아들에게 보내는 26가지 삶의 지혜



Contents

1 Introduction
2 MaxScript 101
3 Writing MaxScripts
4 Writing a File Exporter in MaxScript
5 Scripting a Game Editor for Max
6 코드 예제

1 Introduction #

3D Studio Max and its predecessor, 3D Studio, have been important tools in game development for as long as I have been in the industry. These applications share many features with more expensive packages, and while they might not be quite as powerful as some of the others, for the price they simply can’t be beat.

In 3D Studio Max's latest incarnation, Max 3.0, Discreet (formerly Kinetix) revamped the MaxScript scripting language, making it more useful to game programmers. The language is easy to learn, very powerful, and provides an excellent starting point for building custom geometry tools. One of the more impressive features that the new version of MaxScript offers is the ability to quickly build a game editor into Max itself. MaxScript can also be used to develop Max plug-ins. And while learning a scripting language might seem more time consuming than learning a C-based SDK, I found the opposite to be true. Development with MaxScript is fast and easy, and Max 3.0 allows you to do everything you need to write a game editor inside of Max, without writing a line of C code.

This article provides a brief overview of MaxScript from a game programmer’s perspective. I’ll discuss some effective methods for learning and using MaxScript, which I uncovered in the process of learning the product. These techniques can legitimately be used to develop custom geometry tools or to build a game editor into Max. I’ll explain what steps are involved in the scripting process, talk about what differentiates Max's scripts from the scripting languages of other products, point out where I experienced difficulties, and wrap up with an evaluation of the entire process.

One caveat: it is not the purpose of this article to teach the reader MaxScript (the documentation that Discreet provides with Max 3 is by far the best way to learn the language). Rather, I try to illustrate its usefulness and point out how non-programmers (such as artists and level designers) can use it to create custom 3D game development tools. I stay away from presenting actual script excerpts, but if you are interested in specifics, please download the file exporter from here. If nothing else, my script is well commented, and it should be easy to follow even if you haven’t seen the language before.

2 MaxScript 101 #

There are a variety of tools available for one to learn MaxScript. Before getting started, familiarize yourself with the fundamentals of modeling within the application. Next, move on to navigating the MaxScript help file. Finally, Discreet provides an extensive collection of example scripts, as well as utilities that are written in script, to demonstrate the concepts in practice.

The most challenging part of working with MaxScript is learning Max itself. In fact, if you are not willing (or do not have the time) to learn basic modeling in Max, I would hesitate to recommend spending the time to learn MaxScript. It is difficult to understand how the language works without understanding how the application works. However, for the willing student, I believe the online tutorials inside of Max can impart the requisite understanding of basic modeling necessary in approximately 8-16 hours, depending on the individual’s familiarity with other modeling programs. This will translate to about 3-4 days for most people, as it is difficult to plow through more than 4 hours of tutorials in a single day.

The documentation for the core functionality of Max is easy to use and largely complete. On the other hand, the MaxScript documentation had several bad links, missing explanations and examples, and comments from the internal testers pointing out where things were broken. Add to that the lack of effective tutorials, and one might assume that learning the language would be an arduous task. Truth be told, however, I found the language to be very straightforward and easy to learn. Assuming Discreet cleans up these pre-release issues, things should only get better for the final product.

After spending some time learning basic modeling, I read the help files on MaxScript. In my experience, it helps to familiarize oneself with help files so that the user can quickly answer questions as they present themselves. The Max help files are quite lengthy, and I only skimmed some of the more involved sections. The goal is to know where to look when encountering problems down the road. In all, this took about 8 hours of reading.

Next, I looked at the example scripts included with Max. Discreet did a great job of providing a wide variety of them. I recommend going through the script called Demo.ms, which is a walkthrough of the language, and then reading the help files. The flow of the demo script is more suited to learning the language than just mucking through help files. I spent about four hours with the demo script, and at that point felt comfortable enough to start writing my own scripts.

3 Writing MaxScripts #

The process of writing a script in Max is quite different than writing one in other interpreted languages. Without the benefit of a substantive debugger, the code writing process breaks down into three stages. First, the code must be written into Max’s real-time interpreter, and the programmer watches it execute line by line. If a line does not perform as intended, it is modified and then executed again. The next step is to save this code into a script file, which typically involves wrapping the code with a simple interface description. With this interface description, one can proceed to the final stage, which is executing the script and making sure it performs as intended. If it doesn’t work, the user must return to stage one to attempt to reproduce and fix the error.

Inside Max there is a window called the Listener, in which you can type commands that are interpreted and executed. Return values are printed to the window for each command that is issued. By typing in the desired functionality line by line, you can ensure that your algorithm is working as desired. Variables can be examined at each step by printing them out into the listener window. While admittedly this process seemed a bit clunky at first, it was easy to get used to.

http://www.gamasutra.com/db_area/images/features/19991008/maxss.gif

Max Script lets Max users extend existing features add custom functionality to the modeling and animation environment.

Once a section of code has been debugged, it is added to the script and saved to disk. This script will usually need to define a GUI interface, especially if the user needs to enter options (such as the file name to save to). Defining an interface is easier in Max than in any other language or tool I have used. Alternatively, if no interface is required, the script could just be a sequence of commands that execute when the script is run. An example would be a script that rotated all of the objects in a scene 90 degrees. Such a script could be linked to a custom button inside the Max GUI for easy access.

Once a script is created with a GUI, it can be loaded into Max through the Utility panel. Clicking the Run Script button prompts the user to specify the MaxScript file to load. When your utility is loaded, its GUI is added to the Utility panel and it looks and acts as if it were a part of Max’s base code. If an error occurs during either the load and parse or the execution of a script, Max pops up the script source in an editor window and highlights the line on which the error occurred.

Fixing an error in a script can be frustrating, because it usually involves going back to the Listener, and typing your algorithm in line by line waiting to see the error. Only rarely was I able to debug the code by looking at the runtime error information supplied by Max. However, I did notice that as my familiarity with scripting increased, the time required for debugging decreased substantially, and I am now quite comfortable with the process.

4 Writing a File Exporter in MaxScript #

In order to better illustrate exactly how the development process works, I’ll describe the process of writing an fairly rudimentary script, and what problems arose. I chose to write a geometry exporter, because it forced me to learn how models were internally organized in Max.

When I began, I knew I wanted to export geometry to the Alias RTG (Real-Time Games) format. I have used this format for many years because of its simplicity. With the wide variety of example scripts to look at, I knew that I could probably find source code that illustrated the geometry traversal and access that I would require.

After poking around in the "scripts" directory, I found a file called MSXExport.ms which saves a scene (including geometry, cameras, and so on) as a script file. Running this script file rebuilds the entire scene inside Max, which was very close to what I wanted to do. The script opens and saves data to a file, traversing the internal data structures in Max to examine every object. However, the script saves to a file the sequence of commands issued in Max to create an object, not the polygonal data itself. I went through the help files to learn how to access the vertices, polygons, texture coordinates, and normals. I then modified the save code to output in the RTG file format, accessing these fields and writing the values. All that remained was some minor modifications to the GUI and the script was complete. It was very surprising just how easy it was.

I spent about ten hours writing and debugging my geometry export script. If I had to do it again, it would take about three hours, since I am now considerably more comfortable using the language. Familiarity with the help files was a big benefit when it came time to write my own scripts, but there were still some stumbling blocks. The most annoying problem involved file I/O: when there was an error executing the script, the file handle to the output file was left open. I tried typing a line into the Listener to close the files, but it said the variable was not defined. The only way to close the file so I could view the output was to close Max. I think there is probably a way to work around this, but I have not yet found it. The other problems I had were minor, and mainly stemmed from a lack of understanding of how to approach a problem. This can be greatly alleviated by scanning through the example scripts, which are generally very readable.

5 Scripting a Game Editor for Max #


The speed at which you can create new tools using MaxScript is impressive. Furthermore, MaxScript is not just limited to building plug-ins. The language has an object-oriented design that allows you to override and extend the provided objects. Since all of the objects inside Max are exposed to the scripting language, it is possible to create special cameras, materials, or geometric primitives that have customized values and methods associated with them. Using this feature one could build a custom toolkit to create AI paths, place enemy troops and place pickups in the environment. It would be a simple matter to build an entire game editor into Max, and even to have it communicate with your game engine while both applications are running.

I took advantage of MaxScript’s extendable objects to create a custom camera that only exposes the parameters that my game engine supports. In this regard, it looks and acts in Max exactly the way it will inside the game engine. I’m looking forward to overriding the material editor to do the same. It should be possible to make the rendered output from Max look nearly identical to the game engine screens. This will allow the artist to make adjustments to the art before passing it through the rest of the art path, saving time and energy. It should also be possible to associate material characteristics, such as flammable, inside the material editor and save the information into a database that the game loads.

Although it is not clear whether or not your entire game editor could be written in MaxScript, I haven’t found a reason why it wouldn’t be possible. MaxScript is capable of shell launching applications, such as your game engine, with a command line pointing to the file to be loaded. It can also use OLE to communicate with the game engine once it is running, provided you write a COM interface to the engine. I have not tested this, but the Discreet technical support staff claims it is very similar to using Microsoft Visual Basic, which in my experience has been very easy to learn and use.

Next up, a Game Editor

After using MaxScript for a few days it became clear to me that the next game editor I write will probably be built into Max. Recently, an artist I work with asked me to write a tool to simplify a repetitive task he was doing in Max. He was quite surprised when I mailed him a script in a matter of hours. This kind of turnaround saves time for the artists and the programmers, and is worth its weight in gold as part of a 3D-development effort. When this ease of use is combined with the fact that the pre-existing tools in Max can be used to work with objects, overall development time should be greatly reduced.

I have heard stories that MaxScript was tossed into Max 2.0 at the last minute, and became more popular than expected. With 3.0, much of the product’s user interface has been implemented in script, which goes to prove how much Discreet has embraced the early success of this design. It will be very interesting to see how many features will be added to Max 3.0 by the user base, who will no doubt be creating and distributing scripts on the Internet. This group will help ensure that game developers who commit to this development path won’t be left in the dark in a year or two. When you consider how affordable Max is, and how powerful Discreet has made this new version of MaxScript, it is clear that many new game editors and game development tools will be built using this technology.

6 코드 예제 #

-------------------------------------------------------------------------------------
-- RTGExport.ms
-- This utility exports geometry from Max 3.0 into the Alias text-based RTG v1.8 format.
-- The RTG (RealTimeGames) format is not very well supported, but it is so simple that
-- writing support for it is easy.
-------------------------------------------------------------------------------------
utility RTGExport "Alias RTG v1.8 Export"
(
	-- Define variables that are visible to all functions in the utility
	local ostream, tabs = ""

	-- Define the GUI interface
	group "Options"
	(
		checkbox cb_exportSelOnly "Export Selected Only"
		checkbox cb_outputVertNorms "Output Vertex Normals" checked:true
		checkbox cb_outputVertColors "Output Vertex Colors" checked:true
		checkbox cb_outputPolyNorms "Output Polygon Normals" checked:true
		checkbox cb_outputTexCoords "Output Texture Coords" checked:true
		checkbox cb_outputHierarchy "Output Hierarchy" checked:true
	)
	button btn_export "Save As..." width:100

	-- Now define the functions that the utility uses

	-------------------------------------------------------------------------------------
	-- This function exports a geometry object to the RTG file.
	function ExportMesh meshObj name =
	(
		-- Output the object header
		Format "\n\nOBJECT_START % v%" name meshObj.numVerts to:ostream
		if cb_outputVertNorms.checked then
		Format " n%" meshObj.numVerts to:ostream
		if cb_outputTexCoords.checked then
		Format " t%" meshObj.numtverts to:ostream
		Format " p%\n\n" meshObj.numFaces to:ostream

		-- Vertex output is in local space
		Format "VERTEX local\n" to:ostream
		if meshObj.numVerts > 0 then
		(
			for i = 1 to meshObj.numVerts do
			(
				vert = ((GetVert meshObj i)-meshObj.pos)
				Format "% % % %\n" (i-1) vert.x vert.y vert.z to:ostream
			)
		)

		-- Vertex normals
		if meshObj.numVerts > 0 and cb_outputVertNorms.checked then
		(
			Format "\nNORMAL\n" to:ostream
			for i = 1 to meshObj.numVerts do
			(
				normal = GetNormal meshObj i
				Format "% % % %\n" (i-1) normal.x normal.y normal.z to:ostream
			)
		)

		-- Texture coords
		if meshObj.numTVerts > 0 and cb_outputTexCoords.checked then
		(
			Format "\nTEXCOORD\n" to:ostream
			for i = 1 to meshObj.numTVerts do
			(
				uvw = GetTVert meshObj i
				Format "% % % %\n" (i-1) uvw.x uvw.y uvw.z to:ostream
			)
		)

		-- Polygon
		Format "\nPOLYGON\n" to:ostream
		if meshObj.NumFaces > 0 then
		(
			for i = 1 to meshObj.numFaces do
			(
				poly = GetFace meshObj i
				Format "% 3 v % % %" (i-1) (poly.x as integer -1) (poly.y as integer -1) (poly.z as integer -1) to:ostream
				-- Specify vert normals
				if cb_outputVertNorms.checked then
					Format " n % % %" (poly.x as integer -1) (poly.y as integer -1) (poly.z as integer -1) to:ostream
				-- Tex coord
				if cb_outputTexCoords.checked and meshObj.numTverts > 0 then
				(
					tvert = GetTVFace meshObj i
					Format " t % % %" (tvert.x as integer -1) (tvert.y as integer -1) (tvert.z as integer -1) to:ostream
				)
				-- Face normal
				if cb_outputPolyNorms.checked then
				(
					normal = GetFaceNormal meshObj i
					Format " N % % %" normal.x normal.y normal.z to:ostream
				)
				-- Texture ref
				Format " T 0\n" to:ostream
			)
		)

		Format "\nOBJECT_END %\n" name to:ostream
	)

	-------------------------------------------------------------------------------------
	-- This function is called once per node in the scene.
	-- A node in Max may be all sorts of things. We are only interested in geometry.
	function ExportNode node =
	(
		-- Create node and export class specific data
		if SuperClassOf node == GeometryClass and ClassOf node == Editable_mesh then
			ExportMesh node node.name
		else if SuperClassOf node == GeometryClass then
		(
			-- Build a mesh out of this object and save it
			local temp = copy node
			convertToMesh temp
			ExportMesh temp node.name
			delete temp
		)
		else	-- Not geometry.. could be a camera, light, etc.
			return false

		return true
	)

	-------------------------------------------------------------------------------------
	-- This function recurses down the node hierarchy calling ExportNode for each node.
	function RecursiveExportNode node =
	(
		if (ExportNode node) == false then
			return false

		-- Recurse children before writing this node
		for child in node.children do
			RecursiveExportNode child
	)
	-------------------------------------------------------------------------------------
	-- Output the RTG file header.
	function OutputHeader =
	(
		Format "OUTPUT_VERT_NORMS " to:ostream
		if cb_outputVertNorms.checked then ; Format "on\n" to:ostream
		else Format "off\n" to:ostream
		Format "OUTPUT_VERT_COLORS " to:ostream
		if cb_outputVertColors.checked then ; Format "on\n" to:ostream
		else Format "off\n" to:ostream
		Format "OUTPUT_POLY_NORMS " to:ostream
		if cb_outputPolyNorms.checked then ; Format "on\n" to:ostream
		else Format "off\n" to:ostream
		Format "OUTPUT_TEX_COORDS " to:ostream
		if cb_outputTexCoords.checked then ; Format "on\n" to:ostream
		else Format "off\n" to:ostream
		Format "OUTPUT_HIERARCHY " to:ostream
		if cb_outputHierarchy.checked then ; Format "on\n" to:ostream
		else Format "off\n" to:ostream

		Format "SHOW_INDEX_COUNTERS on\n" to:ostream
		Format "TEXTURE_MODE per_shader\n" to:ostream
	)
	-------------------------------------------------------------------------------------
	-- A helper function to write indentation to the file.
	function doTabs depth =
	(
		for x = 1 to depth do
		(
			Format "\t" to:ostream
		)
	)
	-------------------------------------------------------------------------------------
	-- A function to output the hierarchical relationship of geometric objects.
	function RecursiveWriteHierarchy node depth =
	(
		-- Write this node
		if SuperClassOf node == GeometryClass then
		(
			-- Geometry info
			doTabs (depth as integer -1)
			Format "% G %\n" depth node.name to:ostream
			doTabs (depth+1)
			Format "tran: % % %\n" node.pos.x node.pos.y node.pos.z to:ostream
			doTabs (depth+1)
			rot = node.rotation as eulerangles
			Format "rot: % % %\n" rot.x rot.y rot.z to:ostream
			doTabs (depth+1)
			Format "scal: % % %\n" node.scale.x node.scale.y node.scale.z to:ostream
			doTabs (depth+1)
			Format "sPiv: % % %\n" node.pivot.x node.pivot.y node.pivot.z to:ostream
			doTabs (depth+1)
			Format "rPiv: % % %\n" node.pivot.x node.pivot.y node.pivot.z to:ostream
		)
		-- Pivot info
		-- Recurse to children
	--	for children in node.children do
	--		RecursiveWriteHierarchy children
	)
	-------------------------------------------------------------------------------------
	-- The highest level function called to export an RTG file.
	function ExportRTG =
	(
		-- Write the RTG header
		Format "HEADER_TITLE\t\tMax 3.0 Real Times Game Output\n" to:ostream
		Format "HEADER_VERSION\t\tv1.8\n" to:ostream
		Format ("HEADER_DATE \t\t" + localtime + "\n") to:ostream
		Format "\n\n" to:ostream

		if cb_exportSelOnly.checked then
		(
			Format "NUMBER_OF_OBJECTS = %\n" selection.count to:ostream
			OutputHeader()

			for node in selection do
				ExportNode node
		)
		else
		(
			Format "NUMBER_OF_OBJECTS = %\n" rootnode.children.count to:ostream
			OutputHeader()

			if cb_outputHierarchy.checked then
			(
				Format "\nHIERARCHY_LIST HXP 1 top_level\n\n" to:ostream

				for node in rootnode.children do
					RecursiveWriteHierarchy node 0

				Format "END_HIERARCHY_LIST\n\n" to:ostream
			)

			for node in rootnode.children do
				RecursiveExportNode node
		)
	)
	-------------------------------------------------------------------------------------
	-- Open an prepare a file handle for writing.
	function GetSaveFileStream =
	(
		fname = GetSaveFileName types:"Alias RTG (*.rtg)|*.rtg|All Files(*.*)|*.*|"
		if fname == undefined then
			return undefined

		ostream = CreateFile fname
		if ostream == undefined then
		(
			MessageBox "Couldn't open file for writing !"
			return undefined
		)

		return ostream
	)

	-------------------------------------------------------------------------------------
	-- This is the function called when the user activates the utility by pressing on
	-- the export button. It opens the file and calls the export routine.
	on btn_export pressed do
	(
		ostream = GetSaveFileStream()
		if ostream != undefined then
		(
			ExportRTG()
			close ostream
		)
	)

) -- End RTGExport


Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2010-10-28 12:42:52
Processing time 0.9237 sec