This question doesn't really apply if JavaScript is disabled. Just keep reading.
Please check the zenilib commit log to see if it has been addressed since the most recent release. If it has, please pester me to package and release an update.
Always remember that a reproducable test case is worth a lot more than a bug report alone, but any report is better than nothing.
To the same place you extracted zenilib. Please see the page explaining how to Build a Game.
Please make sure that you have Set Up Your System correctly. Then, be sure to extract the supporting files as described on the page explaining how to Build a Game.
It is possible that you need to add paths for the Windows SDK. If you manually installed the Windows SDK, you may also need Include/Lib paths from C:\Program Files (x86)\Microsoft SDKs\Windows\
.
Ensure that you have #include <zenilib.h>
at the top of all your source files. If you have and are still getting the error, it is likely that you are missing an actual function or static/global variable definition. It is possible that the source file in question has not been added to your project, so ensure that it is actually getting built.
Another possibility is that you are attempting to access code from a shared object file or DLL that Application
is not linking against. Ensure that Application
is linking against the code in question.
Yet another possibility is that you have attempted to modify zenilib itself, and you did not do a --build=all
. If this is the case, I would advise against carelessly modifying zenilib. However, if you're certain that you wish to do so, rebuild with multi-build_sh.bat --build=all
.
zenilib/
directory.zenilib/
directory if you haven't already.zenilib/assets/* zenilib/dev/pc_/* zenilib/jni/application/*Note that some of these files may have been updated in the new version, and your existing files will need to be discarded or updated in order for zenilib to function. For example, 0.5.1.0 adds a sound alias to
zenilib/config/sounds.xml
.
On other platforms, the zenilib/
directory essentially is the build of the game with extra stuff, like source code, included. If you're using the Xcode IDE, most of your buid is stored in a DerivedData directory (or at the location specified in your Xcode preferences).
The App bundle itself should appear at zenilib/game_d.app
for the Debug build, or zenilib/game.app
, regardless of whether you build with the command-line or with Xcode.
In the process of building the App bundle, your assets/
directory is mirrored into the App bundle at Contents/assets/
. As a result, any files output by your game, including stderr.txt
and stdout.txt
will appear inside the App bundle rather than inside your zenilib/
directory. Every time you recompile, the files in your App bundle will be reset.
Please read the last section of What's special about Mac OS X?.
cout
is dumped to stdout.txt
and cerr
is dumped to stderr.txt
. In Windows and Linux, both files are found in the root of your zenilib/
directory. In Mac OS X, both files are buried in your App bundle, as described in What's special about Mac OS X?
If you wish to output text you can see while in-game, outputting to the console might be more useful:
#ifndef NDEBUG get_Game().write_to_console("Hello World!"); #endif
<!-- Make sure you didn't add it inside of a comment block. -->
zenilib/
directory on the desktop.zenilib/
directory. Replace all files when asked. If it does not ask to overwrite files, you have made an error. Examine the file structure of the example .zip and see how it corresponds to the existing zenilib/
directory.Heap Corruption often implies that Visual Studio needed to rebuild something, but wasn't smart enough to figure it out. Doing a clean+rebuild often solves that problem. You are most likely to encounter this error when exiting a Game_State
.
There is also a chance that you need to add a virtual
destructor to at least one of your base classes -- especially if derived classes contain instances of Chronometer
, Vertex_Buffer
, or Model
.
After the initial bootstrap to your Title_State
or other Gamestate_Base
-derived type, subsequent transitions simply involve the use of get_Game().push_state(...)
optionally preceded by get_Game().pop_state()
. A popped Gamestate
(counting smart pointer), returned by pop_state()
, can be saved for later use if desired.
You could render your framerate, for example, with get_Fonts()["title"].render_text("FPS: " + ulltoa(get_Game().get_fps()), Point2f(), Color());
Using an ostringstream
would be a good alternative to using functions like itoa
, ftoa
, ... and String
concatenation. Text_Box
es are another option, but they would be overkill for most purposes.
After you're done rendering your scene in 2D or 3D, you can call get_Video().set_2d(...)
and render whatever HUD elements you like. Doing this even in 2D is a good idea because it keeps code simple and prevents a class of rendering artifact caused by floating point error. Calling get_Video().clear_depth_buffer()
can prevent 3D geometry rendered earlier from popping in front of your HUD.
You must approximate the geometry using a set of Triangle
s centered around the center of the circle, or render a Texture
of the primitive.
Open the image file in the GIMP (or in GIMP Portable).
If you don't see a strange checkered background, you failed to create your image correctly (with an alpha channel), or you failed to export it correctly. Be sure not to flatten your image as you export it. And remember, always use .png, and always powers of 2 for width and height.
Set tile
to 1
in textures.xml and use texture coordinates outside the range [0,1].
// In render() Quadrilateral<Vertex2f_Texture> quad( Vertex2f_Texture(Point2f(x , y ), Point2f(-1.0f, 0.0f)), Vertex2f_Texture(Point2f(x , y + h), Point2f(-1.0f, 2.0f)), Vertex2f_Texture(Point2f(x + w, y + h), Point2f( 3.0f, 2.0f)), Vertex2f_Texture(Point2f(x + w, y ), Point2f( 3.0f, 0.0f))); Material material("texture"); quad.lend_Material(&material); get_Video().render(quad);
Make sure the width and height of the image being loaded are both powers of 2. Make sure you disabled tiling of the particular texture in zenilib/assets/config/textures.xml
. Finally, be precise in your texture coordinates.
The only way to be absolutely sure is to use a fixed time step. So long as all changes to game state are made in perform_logic
, you can accumulate the amount of time that has passed, and whenever it gets above a certain threshold, subtract off that fixed amount of time and perform a game step.
However, this isn't optimal for smoothness or performance, generally speaking. It is usually good enough to multiply all changes in motion by the amount of time that has passed since the previous frame. (e.g. position = time_step * velocity
) The one caveat is that you probably need a bound on the size of a step to ensure stability, or you might just find yourself able to jump though a wall one time in a hundred.
Ctrl+F5
and not F5
, to run rather than debug your game.Anisotropy
to 0
in zenilib/config/zenilib.xml
if your game is 2D, it won't make things look prettier in 2D anyway.zenilib/config/zenilib.xml
, it gives a slight performance boost on some systems.Try storing Quadrilaterals and rendering them manually:
// Possibly in a constructor Vertex2f_Texture ul(Point2f(x , y ), Point2f(0.0f, 0.0f)); Vertex2f_Texture ll(Point2f(x , y + h), Point2f(0.0f, 1.0f)); Vertex2f_Texture lr(Point2f(x + w, y + h), Point2f(1.0f, 1.0f)); Vertex2f_Texture ur(Point2f(x + w, y ), Point2f(1.0f, 0.0f)); Material material("texture"); Quadrilateral<Vertex2f_Texture> quad(ul, ll, lr, ur); quad.fax_Material(&material); // In render() get_Video().render(quad);
If you are rendering a number of primitives using the same Texture
, relying on zenilib to automatically set and unset a Material
per primitive is inefficient. Furthermore, it sets a number of parameters which only matter when lighting is enabled. In the future,batch calls may be supported but, for now, you can get an order of magnitude performance improvement by setting a Texture
once, doing a number of rendering calls, and then unsetting the Texture
, as follows:
Quadrilateral<Vertex2f_Texture> quad(ul, ll, lr, ur); //quad.fax_Material(&material); get_Video().apply_Texture(get_Textures()["texture"]); get_Video().render(quad); //get_Video().render(quad2); //get_Video().render(quad3); get_Video().unapply_Texture();
Note that no Material
was ever given to the Quadrilateral
in this case, because the Texture
is being set manually at render time.
If that isn't good enough, and you have a number of primitives which are guaranteed to stay the same from frame to frame, it's time to learn how to use Vertex Buffer Objects (VBOs). class Vertex_Buffer allows you to insert Triangle and Quadrilateral objects. After you give it all the Triangles and Quadrilaterals that you want it to be able to render, the Vertex_Buffer will be ready to render. Note that there will be no performance boost if the number of triangles in a Vertex_Buffers is very small. Note also that building a Vertex_Buffer is expensive, and should not be done every frame. (Recreating a Vertex_Buffer every frame defeats the purpose of using one.)
// Possibly in a constructor Vertex_Buffer *vbo_ptr = new Vertex_Buffer; vbo_ptr->give_quadrilateral(quad_ptr); // this pointer is dead to you vbo_ptr->fax_quadrilateral(quad_ptr); // you can keep the pointer // In render() vbo_ptr->render();
Rendering a VBO is much faster than rendering a number of Triangles or Quadrilaterals individually. However, you can't change values within a VBO after it is created. So, if you want to move one around, the correct approach is to use Model/World matrix transformations.
Video &vr = get_Video(); vr.push_world_stack(); //vr.translate_scene(...); //vr.rotate_scene(...); //vr.scale_scene(...); vbo_ptr->render(); vr.pop_world_stack();
You don't. Once a Vertex_Buffer is rendered, it becomes fixed. You must delete the old one and make a new one if you need to change the contents. Note that it is possible to create vertex buffers which can be modified--I just don't provide this functionality.
Any Widget can be given a new Widget_Renderer
. A Text_Button tb
can have its Color
s changed, for example, by tb.give_Renderer(new Widget_Renderer_Tricolor(...))
.
More drastic theming, for example switching to Textures, would require creating a new derived type of Widget_Renderer
, with a render_to(...)
function that knows how to render the desired type of Widget
. Note that type information is lost in the call to render_to(...)
, and that Widget
s do not enforce that they receive only Widget_Renderer
s that they know how to render them. It is up to you to ensure that your render_to(...)
function knows how to dynamic_cast
to the appropriate Widget
type, and to use the information within to render the Widget.
Zeni::play_sound(...)
defined in Sound_Source_Pool.h
is the simplest way to play a one-shot sound effect, loaded using the Sounds
database. An alternative is to manually create a Sound_Source
and pass it a Sound_Buffer
from Sounds
manually. This is necessary only for looping sounds and for positional audio, in cases where it would be required to move a Sound_Source
before it finishes playing.
Music can be handled simply by the BGM functions get_Sound().set_BGM(...)
and get_Sound().play_BGM().
Note that these functions are not tied to the Sounds
database, so a relative path is expected rather than an alias from sounds.xml
.
The Chronometer<Time> class is helpful.
// Possibly in a constructor m_seconds_passed = 0.0f; m_chrono.start(); // In perform_logic() const float seconds_passed = m_chrono.seconds(); const float time_step = seconds_passed - m_seconds_passed; m_seconds_passed = seconds_passed;
Additionally, the Time values from Timer can be used directly when convenient, or when you want to keep your values from being affected when the user pauses the game.
The first parameter of get_Video().set_2d(...)
is an std::pair
of Point2f
s. By default, the first is (0, 0) and the second is (screen_width, screen_height). However, you can them to arbitrary coordinates in your 2D world.
Alternatively, the scene transformations described in Rendering Triangles/Quadrilaterals is too slow! can have the same effect.
Pass in the upper left corner as the first parameter. For the next two parameters, pass in Vector3f(width, 0.0f, 0.0f)
and Vector3f(0.0f, height, 0.0f)
. Finally, pass in Vector3f(0.0f, 0.0f, 1.0f)
for the fourth and final parameter. Without specifying a height, the Parallelepipeds are degenerate and will give erroneous intersection/distance values. Of course, if your Parallelepipeds are not axis-aligned, you will have to do some extra work manipulating the parameters.
User-specific files should, ideally, be stored in files saved the directory path returned from get_File_Ops().get_appdata_path()
. This directory is likely to be writable in the case that your game has been installed. Writing to relative paths within the assets/
directory is safe so long as installation is not intended.
Distribute a zenilib Game
.zenilib/multi-clean_sh.bat
or./zenilib/multi-clean_sh.bat
. If you want to clear out the binaries for other operating systems, you can run it with the option --build=all
, but you will have to rebuild your project afterwards, as it will delete the files for your operating system also.zenilib/
directory can grow by hundreds of megabytes. This reduces the default zenilib/
directory substantially.Remember that as the zenilib is licensed to you under LGPL v3.0, you are legally obligated to provide a copy of zenilib, and the source files for the DLLs I provide to you, or to provide a written offer to provide them at no cost upon request. Again, if submitting to a class, the instructor probably wants zenilib and your source code intact, but a written offer is probably enough for the source for the accompanying DLLs.
Don't build from a network drive. If you wish to store your files on a network drive, relocate the build directory with multi-premake_sh.bat --dir
. See Build a zenilib Game for details.
It is also possible that at least one of your files probably has a file modification time that is in the future. This can happen if you edit files on different computers that disagree about how to keep time. The solution is to update the modification times of all your header and source files. (This can be done by opening the offending files, modifying them, and resaving them, or with the help of a command-line tool such as touch
.)
M
to open the Material Editor.Material
menu, click on Change Material/Map Type...
.Standard
in the window that opens.OK
.Pick Material from Object
button (looks like a dropper).Ambient
and Diffuse
colors to white.Specular
color to the approximate color of the object (or white if it is plastic).Maps
tab and check the box next to Diffuse Color
.None
on the same line.Bitmap
in the window that opens and click OK
.Coordinates
tab that appears after the Texture is set, uncheck Use Real-World Scale
.U
and V
to be 1.0 by default.Real-World Map Size
for the selected mesh. (You can uncheck this in the Create
tab as well, but it will apply only to newly created meshes.)Assign Material to Selection
button (looks like Sphere->Box
).Preserve 3ds Max's Texture Coordinates
.Bitmap Parameter
you added to your mesh to textures.xml. To ensure that you get it right, I recommend reimporting the .3ds file and seeing what value is inside. It won't match what was set initially. Of course, if you get it wrong, zenilib will Error and tell you what you need to add in zenilib/stderr.txt
.tile
set to 1
.3DS files seem to refer to textures by all caps file names. (At least this is the result when exporting from 3ds Max.) zenilib blindly looks in the Textures
database to find the corresponding textures.
e.g. Applying car.jpg to part of a model in 3ds Max will result in a reference to CAR.JPG in the 3DS file. CAR.JPG can refer to whatever you like in the Textures
database. (It could be an image loaded from car.png. It could just as easily be a Sprite
.)
If your program throws an Error
when you try to render the Model
, you can check zenilib/stderr.txt
for an error message telling you what Texture
it was looking for.