Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.


Topics - luiscubal

Pages: [1] 2 3
1
Tips, Tutorials, Examples / OpenGL/GLSL example: noise graphics/audio
« on: February 12, 2012, 02:13:10 PM »
I wrote a simple OpenGL/GLSL program to generate a TV-like noise effect. (using SFML to setup the opengl context and for audio purposes).
The idea is simple: we use a fragment shader to compute the color of each pixel randomly. We can also force "groups" of pixels to have a single color to give an illusion of a lower resolution.

The requirements: basic OpenGL and GLSL knowledge. Some code is taken from Durian Software's OpenGL tutorial.

Our source code is made of four files: The Makefile, the C++ code, the fragment shader and the vertex shader.
I named them "Makefile", "noise.cpp", "noise.f.glsl" and "noise.v.glsl".

Let's start with the Makefile(tested under Linux GCC)
Code: [Select]
CXX:=g++
CXXFLAGS:=-Wall -Wextra -Werror -g -O0
LDFLAGS:=-lsfml-window -lsfml-audio -lsfml-system -lGLEW -lGL
OUTFILES:=noise.cpp.o
APP:=noise

all: $(OUTFILES)
$(CXX) $(CXXFLAGS) $(OUTFILES) $(LDFLAGS) -o $(APP)

clean:
rm -f $(OUTFILES) $(APP)

%.cpp.o: %.cpp
$(CXX) $(CXXFLAGS) -c $< -o $@

If you're familiar with Makefiles, this should be trivial to understand.

Our program basically just uses a shape with four vertexes - the window rectangle. Since we use no matrixes nor any tricks, the vertex shader is super-simple:
Code: [Select]
#version 110

attribute vec2 position;

void main() {
gl_Position = vec4(position, 0.0, 1.0);
}
Essentially: The position of the vertex is the position the program sends, with Z=0.0 and W=1.0.

Our fragment shader is, unfortunately, more complex. Although GLSL does include some noise(random) functions, it seems some(most? all?) implementations - including the proprietary driver for my NVidia GPU - are very xkcd-ish.

(Seriously - they actually do this)

So, unfortunately, if we want reliable random, we'll have to get our own. stackoverflow to the rescue!

Our second problem is this:
Quote
We can also force "groups" of pixels to have a single color to give an illusion of a lower resolution.
So how do we do this?
For this, I used floor. Essentially, I have a variable "pixels_per_block"(which would perhaps be more adequately called "sqrt_pixels_per_block", since the actual number of pixels is the square of that variable).
OpenGL provides a variable named gl_FragCoord which gives us the position of the pixel in the screen. By dividing gl_FragCoord.x or y by pixels_per_block and then using floor, we obtain the desired effect.
If pixels per block is 3, we get this
0 -> 0
1 -> 0
2 -> 0
3 -> 1
4 -> 1
5 -> 1
6 -> 2
which is exactly what we wanted.

Keep in mind that GLSL(at least the version I used) does not allow implicit type casts, and we can't divide a float by an integer, so we'll have to explicitly cast pixels_per_block to float.

Finally, we want the noise pattern to change on each frame. To achieve this effect I used something I called "seed", computed on the C++ side.
So, let's see what we've got:
Code: [Select]
#version 110

uniform int seed1, seed2;
uniform int pixels_per_block;

float rand(vec2 co) {
    return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}

void main() {
float xpos = floor(gl_FragCoord.x/float(pixels_per_block));
float ypos = floor(gl_FragCoord.y/float(pixels_per_block));
vec2 src = vec2(xpos, ypos) * vec2(seed1, seed2);
float val = rand(src);
gl_FragColor = vec4(val, val, val, 1.0);
}

To generate audio noise, we also use random. SFML provides us with a class named SoundStream that enables us to compute sounds as they are needed - in this case, using the rand() function

If you haven't read the Durian Software OpenGL tutorial, this would be a good time to do so, since I'm going to skip lots of OpenGL explanations and assume you understand it all.
I provide the code here, for those who are interested:
Code: [Select]
#include <iostream>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <GL/glew.h>
#include <SFML/Window.hpp>
#include <SFML/Audio.hpp>

#define PIXELS_PER_BLOCK 2
#define NOISE_SOUND_NBSAMPLES 16000

using std::cerr;
using std::endl;
using std::vector;

class NoiseAudioStream : public sf::SoundStream
{
public:
vector<sf::Int16> myBuffer;

NoiseAudioStream() {
myBuffer.reserve(NOISE_SOUND_NBSAMPLES*2);
Initialize(2, 32000);
}

virtual bool OnStart() {
for (int i = 0; i < NOISE_SOUND_NBSAMPLES*2; ++i) {
myBuffer[i] = rand();
}
return true;
}

virtual bool OnGetData(sf::SoundStream::Chunk& data) {
data.NbSamples = NOISE_SOUND_NBSAMPLES;
data.Samples = &myBuffer[0];
return true;
}
};

GLuint vertex_buffer, element_buffer;
GLuint vertex_shader, fragment_shader, program;
GLuint shader_position;
GLuint shader_seed1, shader_seed2, shader_pixels_per_block;

static const GLfloat vertex_buffer_data[] = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f
};
static const GLushort element_buffer_data[] = { 0, 1, 2, 3 };

static GLuint make_buffer(GLenum target, const void* buffer_data, GLsizei buffer_size) {
GLuint buffer;

glGenBuffers(1, &buffer);
glBindBuffer(target, buffer);
glBufferData(target, buffer_size, buffer_data, GL_STATIC_DRAW);

return buffer;
}

static GLchar* file_contents(const char* filename, GLint* length) {
FILE* f = fopen(filename, "r");
if (!f) {
cerr << "Could not open " << filename << endl;
return 0;
}
fseek(f, 0, SEEK_END);
*length = ftell(f);
fseek(f, 0, SEEK_SET);

void* buffer = malloc(*length + 1);
*length = fread(buffer, 1, *length, f);
fclose(f);
((char*) buffer)[*length] = '\0';

return (GLchar*) buffer;
}

static void show_info_log(GLuint object, PFNGLGETSHADERIVPROC glGet__iv, PFNGLGETSHADERINFOLOGPROC glGet__InfoLog) {
    GLint log_length;
    char *log;

    glGet__iv(object, GL_INFO_LOG_LENGTH, &log_length);
    log = (char*) malloc(log_length);
    glGet__InfoLog(object, log_length, NULL, log);
    cerr << log << endl;
    free(log);
}

static GLuint make_shader(GLenum type, const char* filename) {
GLint length;
GLchar* source = file_contents(filename, &length);
GLuint shader;
GLint shader_ok;

if (!source)
return 0;

shader = glCreateShader(type);
glShaderSource(shader, 1, (const GLchar**)&source, &length);
free(source);
glCompileShader(shader);

glGetShaderiv(shader, GL_COMPILE_STATUS, &shader_ok);
if (!shader_ok) {
cerr << "Failed to compile " << filename << ":" << endl;
show_info_log(shader, glGetShaderiv, glGetShaderInfoLog);
glDeleteShader(shader);
return 0;
}

return shader;
}

static GLuint make_program(GLuint vertex_shader, GLuint fragment_shader) {
GLint program_ok;

GLuint program = glCreateProgram();
glAttachShader(program, vertex_shader);
glAttachShader(program, fragment_shader);
glLinkProgram(program);

glGetProgramiv(program, GL_LINK_STATUS, &program_ok);
if (!program_ok) {
cerr << "Failed to link shader program:" << endl;
show_info_log(program, glGetProgramiv, glGetProgramInfoLog);
glDeleteProgram(program);
return 0;
}
return program;
}

static void make_resources() {
vertex_buffer = make_buffer(GL_ARRAY_BUFFER, vertex_buffer_data, sizeof(vertex_buffer_data));
   element_buffer = make_buffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer_data, sizeof(element_buffer_data));

vertex_shader = make_shader(GL_VERTEX_SHADER, "noise.v.glsl");
fragment_shader = make_shader(GL_FRAGMENT_SHADER, "noise.f.glsl");
program = make_program(vertex_shader, fragment_shader);

shader_seed1 = glGetUniformLocation(program, "seed1");
shader_seed2 = glGetUniformLocation(program, "seed2");
shader_pixels_per_block = glGetUniformLocation(program, "pixels_per_block");
shader_position = glGetAttribLocation(program, "position");
}

static void init() {
glewInit();

glClearDepth(1.f);
glClearColor(0.f, 0.f, 0.f, 0.f);

make_resources();
}

int main() {
srand(time(NULL));

sf::Window App(sf::VideoMode(800, 600, 32), "SFML OpenGL");
App.SetFramerateLimit(60);

NoiseAudioStream noise;

init();
noise.Play();

while (App.IsOpened()) {
sf::Event Event;
while (App.GetEvent(Event)) {
if (Event.Type == sf::Event::Resized)
glViewport(0, 0, Event.Size.Width, Event.Size.Height);
else if (Event.Type == sf::Event::Closed)
App.Close();
else if ((Event.Type == sf::Event::KeyPressed) && (Event.Key.Code == sf::Key::Escape))
App.Close();
}

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glUseProgram(program);
glUniform1i(shader_seed1, rand());
glUniform1i(shader_seed2, rand());
glUniform1i(shader_pixels_per_block, PIXELS_PER_BLOCK);

glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
glVertexAttribPointer(shader_position, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 2, (void*)0);
glEnableVertexAttribArray(shader_position);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer);
glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, (void*)0);

glDisableVertexAttribArray(shader_position);

App.Display();
}

return 0;
}

If there's something you do not understand, feel free to ask.

2
Tips, Tutorials, Examples / Undefined behavior
« on: November 08, 2011, 02:15:37 PM »
Some guys from LLVM/Clang have published a bunch of posts explaining what undefined behavior in C is and what are its implications.

http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html
http://blog.llvm.org/2011/05/what-every-c-programmer-should-know_14.html
http://blog.llvm.org/2011/05/what-every-c-programmer-should-know_21.html

This gets tricky because they add info about optimizers, so undefined behavior really doesn't always behave like one would expect.
For instance, one of the examples they give is this:

Code: [Select]
void contains_null_check(int *P) {
  int dead = *P;
  if (P == 0)
    return;
  *P = 4;
}{/code]
In the above example, one could expect that "dead" would be removed by the optimizer, so the code would be safe.
However, depending on how the optimizer is made, this may not be the case. This is a perfectly valid optimizer result:
[code]void contains_null_check_after_RNCE(int *P) {
  int dead = *P;
  if (false)  // P was dereferenced by this point, so it can't be null
    return;
  *P = 4;
}
Code: [Select]
void contains_null_check_after_RNCE_and_DCE(int *P) {
//removed dead variable since it's never used.
//if(false) removed
*P = 4;
}

The whole thing is very interesting and, in my opinion, a must-read for all C/C++ programmers.

3
Proposals / EDL V.Next
« on: November 03, 2011, 05:21:02 PM »
In the announcement post Josh made, I wrote about making EDL independent of C++, with only a few select features and some interoperability functions.
I think I ought to be more specific. So I decided to post some thoughts here for discussion.

So here goes:

some script:
Code: [Select]
//Basic EDL
var x = 1; //Single-line comment
/*block comment*/
x = 2; //reassign

//Enter static typing
int y = 3;
y = x;

x = "str";
y = x; //enigma::cast_exception

const z = 2; //type of z is inferred.
z = 3; //error

if z > 1 then begin //GML compatibility
    show_message("foo");
end

So, essentially, GML with a few new features.
But that's not all.

some function file. Different from plain scripts since it can contain multiple functions.
Code: [Select]
int foo = 2;
string bar = "xyz";
vector<float> baz;

type xyz = struct __cpp_pod__ /*plain old data*/ {
   enigma::cpp_interop::type_int x;
   enigma::cpp_interop::type_int y;
   enigma::cpp_interop::type_float z;
};

foreign(cpp) void some_namespace::some_cpp_function(xyz foo, xyz* bar, xyz& baz);

C++ file:
Code: [Select]
#include <iostream>
#include "enigma.h"

using std::cout;
using std::endl;

struct xyz{
    int x;
    int y;
    float z;
};

namespace some_namespace {
    ENIGMA_EXPORT void some_cpp_function(xyz foo, xyz* bar, xyz& baz) {
          cout << "hello, world\n" << endl;
    }
}

So, basic types: void, int, uint, char, byte, sbyte, short, ushort, long, ulong, float, double, string, vector<T>, vector<T>::iterator/const_iterator, var(if it can be considered a type at all), T*, T&, map<K, V>, map<K, V>::iterator, lambda types(?), maybe others, such as types from C++ people might find useful. Maybe vector types too? I'm pretty sure LLVM handles vector types really well.
Extra types would be defined in enigma::cpp_interop, to ensure maximum compatibility.
Additionally, one could define other types (enigma::object_id?)

Code: [Select]
type mycallback = void(var sender, string message);
...
mycallback self.on_talk; //statically typed fields? Maybe some other syntax?
...
self.on_talk = [](var sender, string message) { /* do something here*/ };

Code: [Select]
//Unified API to easily handle save games, in the proper directories
var app = new GameApplication("my-game-name");

var saveslot = app.createSaveSlot("slot-" + 3); //c++-style strings are the default, although I guess one could make C-style strings
let(const savegame = saveslot.open("mydata", FileAccess::Write, FileMode::Binary)) {

byte bytes[] = {12, 13, 14, 15};
savegame.write(bytes);

} //savegame goes out of scope here, so it's destructed(and therefore closed)

//also, app.deleteSlot, app.slotExists, app.openSlot, etc.
//Essentially, each game has a global GameApplication instance, each save slot has a SaveSlot, and each save slot has as many files as the developer sees fit.
//Internally, ENIGMA may decide to compress all save slot files in a single zlib file, if Josh wishes to do so

I thought about all of this literally as I was writing it, so except inconsistencies, bad solutions, etc.
Fell free to discuss and take only the good parts. As you may have noticed, I have quickly scripted not only a language, but also some APIs, feel free to discard some parts of this post.

4
Off-Topic / Terrena a14 - my game
« on: October 10, 2011, 01:10:39 PM »
EDIT 5: Alpha 14 released - MUCH improved artificial intelligence(special thanks Wikipedia for helping me implement a binary heap that allowed me to implement a fast dijkstra's algorithm).
Fixed bug that caused AI to be aborted when taking a card from the deck after doing some actions before.
Added new terrain: Midnight Field, which is really hard to get right now
Changed some terrain costs/gains. I believe the game is a bit more balanced now.
Added new unit: War Balloon, which added some needed diversity to the Sky Civs deck units.

EDIT 4: Alpha 13 released - Made threaded loading optional - disabled by default because it was causing trouble on some computers. Both threaded loading and render textures can be activated by editing the Data/config.xml file.
Added the "Sea Adventures" deck, which includes several new cards, including one new terrain, some previously hard-to-get terrains, new units and a new event/magic card.
Now using mipmaps to ensure smoother zoom out appearance.

EDIT 3: Alpha 12 released - Improved general interface, added a few new cards and added a timeout to computer actions that forces AI to end turn if "next move" computations take too long.

EDIT 2: Alpha 11 released - Fixed a bug in the unit "quick action" menu, added a couple of new cards, modified a few old ones and introduced the concept of "picking a deck". As a result of these changes, the game should be much more balanced, but some harder-to-use cards(such as the "Swamp") will be harder to come by now. I intend to fix this by adding further decks in the future.

EDIT: Alpha 10 released - New version includes a number of improvements, including support for Intel GPUs and .NET 3.5. The new version is now a bit more configurable, a little bit prettier and (I hope) easier to understand(actions like "Move unit" should now be easier to figure out)

Not ENIGMA related, but I figured I'd post it here anyway.

Today, I'm releasing the 7th alpha of my game - Terrena - to the world.
This is the first public version of my game. I'm hoping to get valuable feedback so I can improve the game.



So what is it?
Terrena is a strategy game that combines elements of TCG(trading card games) with board games.
Players start with a tile called "capital" which they must defend from other players and the goal is to conquer all capitals.
For that, players must expand their territory by placing on the board terrain and unit cards, and balancing it all so they have enough resources
Additionally, event cards allow special effects to happen to improve the gameplay experience.

How does it work?
There are seven types of resources: Money, Food, Earth, Water, Fire, Air and Ether.
To place a terrain in the game, one must spend resources(typically money for some of the basic types of terrain) but, in exchange, terrains may give resources at the start of a turn.
With enough resources, one can also place units on the game, which can be used to conquer enemy territory(and so get more terrain). Conquering all enemy capitals means victory.
Each player has a deck of cards and one may take a card from the deck to his/her hand by "buying it"(losing 1 of the resource money). Once in the hand, the players can use the cards they have.

Is it complete?
Not even close. There are not enough types of terrain, units and events. Additionally, there are some images that could use improvement.
The AI is also not perfect and there are some gameplay features that I haven't added yet.
Also, the game is not very balanced right now. I hope future new cards will improve this.

Computer requirements
In spite of being a 2D game, Terrena seems to be pretty demanding in terms of requirements. On one hand, you'll need a decent resolution(which pretty much everyone has anyway), but even on some GPUs the game can sometimes drop to 10FPS. Generally, the more resolution the slower the game.
Also, owners of old Intel cards with no FBO support are out of luck. If the game is essentially an empty black window after taking a card from the deck, you may not have the minimum requirements.
In terms of software, I tested this on Linux Mono and it probably works on Windows too. It requires .NET framework 4.0(tested in Mono 2.10)

Note: If you are using Linux, please run the game with "sh terrena.sh" instead of directly running the executable with Mono.

Download
http://dl.dropbox.com/u/6459451/terrena-a14.zip

5
Tips, Tutorials, Examples / How to properly use OpenGL
« on: October 04, 2011, 03:35:58 PM »
This is really a must-have these days.
Seriously, if you use or are planning to ever use OpenGL, this tutorial is a MUST.

http://duriansoftware.com/joe/An-intro-to-modern-OpenGL.-Table-of-Contents.html

It's sad that it was never completed, but every single chapter is totally worth it.

Remember: if you use glBegin/glVertex/glEnd, then you are doing it WRONG.

This has been a public service announcement. Thank you and have a nice day.

6
Announcements / ENIGMA forums are dead again
« on: September 24, 2011, 03:34:56 PM »

This is merely a parody.
All characters appearing in this work are fictitious. Any resemblance to real persons, living or dead, is purely coincidental.


It happens once in a while, people stop posting, and then nothing happens.
So, now, this is the time when Josh comes along and posts an Announcement with a title like "Totally New Stuff".
But since he's taking a while(maybe it's the curse. I mentioned Aurora a while back and this might have killed ENIGMA), I figured I'd save him the trouble and do it myself.

Quote
[insert troll/obscure reference greeting here]

I know I haven't posted in a while, but that's because I was busy rewriting the parser again. It now handles stuff like "int x; = 2; 3" which turns out to be valid GML. It also serves coffee to the user, just not Java coffee, because Java sucks.
The new parser is much better than the old one, although it causes about 20 regressions.
I was also working in implementing user events, but those don't work because IsmAvatar refuses to be my slave. She claims she has a life besides LGM. *BLASPHEMY*
I also managed to get a new LGM option to improve compatibility with GM. It says "Be slow". Because GM is so slow ROFLCOPTER, and being fast breaks compatibility with some GM games. So this option injects a sleep() every 20 statements, to keep things balanced. But it's not just any sleep. It's a C++ sleep. Yeah! Damn straight.
Also, Windows 8 is retarded.

[insert random see you later note here]

Then Rusky comes along and posts
Quote
If you used decent parsers and LLVM, you wouldn't have to keep rewriting the parser. It'd be perfect and flawless in the first try.

Then an ENIGMA fanboy a honorable gentleman comes along and says something like
Quote
LLVM sucks! It's interpreted and JIT compiled, so it's slower than C++. Everybody knows GCC is the best thing ever. Except GCJ. GCJ sucks.

Then Josh replies to Rusky
Quote
My goal is to create a simple and powerful non-garbage collected C++ environment, and LLVM pollutes that idea. I can add Clang to the config later, but having ENIGMA directly emit LLVM opcodes? No way, it pollutes my idea of a perfect world.

IsmAvatar comes in and comments
Quote
You do know the code is open-source and you could patch it yourself, right?

Josh replies
Quote
I'm allergic to Java. Even using a computer with Java installed makes me itchy.
I once wrote a Java Hello World program and had to stay in the hospital for one whole week.

Then a noob an early adopter comes in:
Quote
I tried to compile a simple game but it gives me an error:
Code: [Select]
GCC error 1: Make love not war
What does this even mean?

Josh:
Quote
Are you using the very latest SVN revision?
What are the contents of your platform.ini file?

Early adopter:
Quote
I'm using revision XYZ.
Here's my file:
Code: [Select]
(Contents of platform.ini here)

Josh:
Quote
I'll look into it.
But meanwhile you can use revision ABC.

Me:
Quote
The next C# will have async. Will ENIGMA also add something like that in a future version?

Josh:
Quote
Are you MAD? I haven't even got C++0x lambdas to work.

Me:
Quote
Quote
Are you MAD?
Possibly.

(insert 3 more pages of essentially the same)

Then a few more topics come along, and after a while the forums die again.

And then Josh posts a new announcement. And it all starts all over again. A never ending cycle.
I wonder if it keeps going on forever or if it ever reaches a stack overflow...

That's it. Nothing else to see here. Move along.

7
Proposals / GM-incompatible ENIGMA features - what to do
« on: August 10, 2011, 08:15:11 AM »
Quote
At some point, though, before we start mass introducing these GM-incompatible features, we need some proposals on how to deal with when the user tries to press "Save As GMK" on a game full of EGM-specific features... This is not the topic to discuss that, though.

Ism posted that in another post. Since this *is* an interesting topic, I guess it would be good to discuss, so here's a brand new topic for it.

I suppose GM-incompatible features can be classified in threetwo categories:
1. Entirely and hopelessly incompatible with GM;
2. Potentially compatible with GM. For instance, let's say Josh implements an "animation order" in the sprite editor(just an example). If the animation order is 1-2-3-2, then this can be emulated by repeating 2 at the end of 3;

For 1, I'd say LGM should either show an error message and refuse to support GMK at all, or strip those features and risk a completely broken game.
For 2, LGM can bother to try to become compatible with GM, or not(depending on how much of an effort it is).

So what do you say?

8
Proposals / Animated tiles/tilesets
« on: August 08, 2011, 08:34:45 AM »
Using RPG Maker 2000, I remembered the concept of animated tiles.
Of course, I don't suggest a tiling algorithm nearly as complex as RPG Maker's, but still I figured a small useful subset of it could be interesting in ENIGMA.

Here's what I propose for ENIGMA:
Create an image. Specify that the tileset has only one column, and each tile has 32x32.
Now, make the tileset have (32*N)x32, and specify in the tile editor that the animation has N frames.

Of course, some extra options would be useful, to better specify animations and ensure backwards compatibility with GM, but I just wanted to give an idea of what I meant.

Here's some options ENIGMA would need to have:
Number of animations
X delta per animation(32 in the example above)
Y delta per animation(0 in the example above)
Animation speed?
Animation order?(1-2-3 or 1-2-3-2) RPG Maker has this but I don't know if it's really necessary. What do you guys think?

9
Off-Topic / Will somebody please kill that damn protected keyword!?
« on: June 30, 2011, 07:15:37 PM »
There are several things I hate in programming, such as regular expressions, segmentation faults, those annoying Swing exceptions that pop once in a while out of nowhere and contain only Swing/AWT functions in the stack trace, massive compile times, GCC C++ template error messages, the lack of a standard UI library in .NET, Java's pseudo-generics, the loading times of slow IDEs and the protected access modifier.

Today, I feel like insulting protected to hell. Why? Because it's worthless. Because I hate it almost as much as I hate regular expressions, and because I apparently have nothing better to do with my time.
It's worthless in C++(I'm talking about fields/methods, I haven't thought much about "class foo : protected bar" so I won't comment on that). It's worthless in Java. It's worthless in C#. It's worthless in pretty much any language.

Seriously, we can classify classes in two categories: Those that can be inherited, and those that can not.
Often languages provide special keywords(such as "sealed" and "final") to prevent a class from being inherited. Even if that is not used, private constructors can often put a stop to inheritance.

Protected is worthless in both types of classes.

Let's assume field(or method) Foo is declared in class Bar as protected.
Now class Bar can either be inherited in a given context or it can not.

If, in that context, Bar can't be inherited, then Foo is effectively private(or, at most, the equivalent of C#'s "internal", which is a much better approach to this problem).

If, in that context, Bar can be inherited, then one can create a dummy class BarExtension with only dummy constructors, dummy implementations of abstract methods, and functions declared like "TypeOfFoo GetFoo(Bar* bar)" and "void SetFoo(Bar* bar, TypeOfFoo foo)". These functions can possibly be static(or not, whatever, both would work).
Since now one can modify the Foo of ANY Bar with the help of BarExtension(which can be defined/declared pretty much anywhere), this means that Foo is effectively "public".

So, in any given context, Foo is either private or public.
It just gives a false sensation of security, without solving anything.

Just wanted to share this. Feel free to move along with your lives now.

10
Issues Help Desk / Help with GM's instance ID system
« on: February 20, 2011, 05:13:38 PM »
I guess I understand how room/object/script IDs work. GM sets them at compile time, then adds some variable that contains the last ID and then "object_add"-like functions just increment that variable and use it as ID.

However, instance IDs are more problematic. Instances can be persistent, there are multiple rooms to be considered, etc.
How are instance IDs handled? In particular: instances in different rooms, having a persistent instance and going back to the room where it was created, and interaction with instance_create.

GM is in general relatively well documented, but the GML language itself and these kinds of "small details" are not. Knowing this sort of thing would be helpful.

11
Tips, Tutorials, Examples / RPG Maker-style games in Game Maker
« on: February 20, 2011, 12:46:19 PM »
**Split into multiple posts because it exceeds the forums max size limit**

For quite some time, I've used GM for platform and puzzle games. However, I never used GM to make RPGs. I wanted to change that. Here's how I implemented a basic RPG engine in Game Maker. Much of this design can easily be ported to SDL, SFML and other APIs.

Before getting started
1. Make sure you have Game Maker installed. I don't think ENIGMA has the required functionality to run this yet.
2. Make sure you have some knowledge of GML, as I use it heavily.

Getting Started
The goal is to have a "classic" 2D RPG. I will not be covering random encounters here and the game will have no battles. Just moving around the world
The world must be organized in 32x32 tiles.
Each instance is precisely located in one of these tiles. However, when moving between tiles, the sprite should have a moving animation and gradually move between tiles, rather than instantly "jump". For all collision detection purposes, however, each instance is always in one of these tiles - only the drawing subsystem moves gradually.
The system should nicely fit with windows and pauses. This means freezing all movement on pause and some kind of "keyboard focus" system.
We want to apply the DRY(Don't Repeat Yourself) principle to this engine, so we'll be heavily using base classes("parent objects" in GM).
The engine reserves two base objects - "actor_obj" and "window_obj".
An Actor is an instance that is located in the world and interacts with other actors. An actor can be the main game hero, NPCs, doors, rocks and walls. It is important to know that only actors exist in the collision system.

Because of our very specific requirements, we will not be using GM's built-in hspeed/vspeed nor collision engine. Also, no object in our game will be "solid". We will be implementing our own hspeed/vspeed/collision engine.

Since only actors exist in the collision engine, the first thing we'll need is a function("script") to find if a given instance is an actor. We'll soon need a similar function for windows, so we'll have multiple scripts as a way to ensure compliance with DRY.

bool instance_is_actor(instance)
Code: ( (Unknown Language)) [Select]
return object_type_is_actor(argument0.object_index);
bool object_type_is_actor(object_type)
Code: ( (Unknown Language)) [Select]
return object_is_ancestor(argument0, actor_obj);
Now, we'll go to the actor_obj object. Our actor_obj is essentially a big part of our DRY plan, since we'll put all common stuff here. actor_obj is not meant to be used directly and, instead, should be seen as an abstract base class.
We will require several variables in actor_obj, related to the multiple features we want to have.
We are going to put sensible defaults for some of those variables in the Create Event.

First of all, we have to address the problem of mismatch between draw(GM) location and collision engine location.
For that, we will create the "target_x" and "target_y" variables. In addition, we will have to know when to stop("!moving") and where we're heading to("orientation").
Also, different actors may move at different speeds.

actor_obj Create Event
Code: ( (Unknown Language)) [Select]
self.target_x = x;self.target_y = y;self.moving = false;self.orientation = 0;self.motion_speed = 2; //In tiles per second
We will need some sort of convention for orientation. I used down=0, left=1, right=2, up=3. Choosing this order has the nice property that 3 - orientation equals the inverse orientation.

One important part of our engine is in the step event.
In our step event, we will have to move our character if needed and also we'll have to stop it when the motion is complete.

actor_obj Step Event
Code: ( (Unknown Language)) [Select]
if (self.moving) {        if (orientation == 0) {        self.y += self.motion_speed * 32 / room_speed;    }    if (orientation == 1) {        self.x -= self.motion_speed * 32 / room_speed;    }    if (orientation == 2) {        self.x += self.motion_speed * 32 / room_speed;    }    if (orientation == 3) {        self.y -= self.motion_speed * 32 / room_speed;    }        if (self.x mod 32 == 0 && (self.orientation == 1 || self.orientation == 2)) {        self.moving = false;        self.target_x = x;    }        if (self.y mod 32 == 0 && (self.orientation == 0 || self.orientation == 3)) {        self.moving = false;        self.target_y = y;    }}
One important thing to note is that we're trying to be somewhat independent from room_speed. However, it is possible that this results in rounding trouble, so I recommend using a power of 2 room_speed for your rooms, such as 16 or 32.
I have tested this engine with 32.
Here we move as much as needed, and check if we arrived to wherever we want to arrive.
However, notice we don't yet have a good way to "start moving". In particular, we can't just set the moving variable since we also need to update target_x/target_y.
In the name of DRY, we'll isolate "start moving" in a separated method - "perform_motion":

void perform_motion()
Code: ( (Unknown Language)) [Select]
self.moving = true;self.target_x = x_in_direction(self.orientation);self.target_y = y_in_direction(self.orientation);
real x_in_direction(direction)
Code: ( (Unknown Language)) [Select]
if (argument0 == 1)    return x - 32;if (argument0 == 2)    return x + 32;return x;
real y_in_direction(direction)
Code: ( (Unknown Language)) [Select]
if (argument0 == 0)    return y + 32;if (argument0 == 3)    return y - 32;return y;
Now, let's say we want to have something to test.
We're going to create our room(room_speed=32), and we'll want in it an instance of hero_obj. It is important to always place actors in x/y that are multiple of 32. GM room editor's grid will certainly help here.
So, first we'll be creating our sprite. Put *anything* there, just to test it. Remember, however, that the engine expects a 32*32 actor. If you pick a different size, you may also want to take a look at the sprite origin.
Now, our hero_obj will have actor_obj as parent.
The first thing we'll want to have is our hero move when the player presses one of the directional keys.
Again, we want to abide by the DRY principle, so we'll want as much of the 4 keys code to be shared.

What happens when the user presses a directional key?
If the hero is moving, nothing happens.
If the hero is stopped and looking elsewhere, then the hero should look at the direction of the key.
If the hero is stopped and looking at the pressed direction, then the hero should start moving in that direction.
So we'll implement this:

void perform_dirkeypress(direction)
Code: ( (Unknown Language)) [Select]
if (!self.moving) {    if (self.orientation == argument0)        perform_motion();    else        self.orientation = argument0;}
Now, we'll have to implement keyboard events(Keyboard, NOT KeyPress). It'll be very simple KeyDown=perform_dirkeypress(0), KeyUp=perform_dirkeypress(3), etc.
In addition, we don't necessarily have to use the directional keys. We can instead decide to use the ASDW keys, for example.

Now, it's time to test our game.
Our lonely hero should be moving across an empty room in whatever direction you tell it to go.

12
Ideas and Design / Extension API
« on: January 22, 2011, 05:34:42 PM »
I propose an extension API for ENIGMA based on the following concepts:

1. Each extension will typically have a Java/LGM part and an "execution" part(C++/ENIGMA or whatever the backend is)
3. Each execution component has a part of it that describes its function. This might be in Java, XML or any other cross-platform language that LGM can understand.

-------------------------------------

Core Extension API

Code: [Select]
LGM.requestRestart(String msg); //If the extension requires a restart
LGM.installExtension(String path, Object params); //Install "with args"
LGM.installExtension(String path); //Same as installExtension(path, null)
LGM.uninstallExtension(Extension extension);

Code: [Select]
class Extension
public String getName()
public String getDescription()
public String[] getAuthors()
public java.awt.Image getIcon()
public boolean getEnabled()
public void setEnabled(boolean enabled);
public ExtensionContent load();

Code: [Select]
interface or class ExtensionContent
public void install();
public void load(); //Extension code and resources were loaded, now handle the load logic
public void enable(); //Enable the extension

This is the barebones, now the interesting part:

Code: [Select]
void LGM.registerResourceType(ResourceType resource);
void LGM.unregisterResourceType(ResourceType resource);
GameFolder LGM.createGameFolder(String folderName, ResourceType resource, boolean canRename);
void LGM.renameGameFolder(GameFolder folder);
void LGM.deleteGameFolder(GameFolder folder);

Code: [Select]
class or interface ResourceType
public String getName();
public Image getIcon();
public Designer createDesigner(Object resource);
//Missing is the serialization API for loading/saving. I don't think I can properly design this without also designing a file format, which is not within the scope of this topic, so someone else will have to do this part.

Code: [Select]
class Designer extends JPanel
//Stuff like designer window title, etc.

Now for the "execution" part:
Code: [Select]
LGM.registerExecutionPlugin(ExecutionPlugin plugin);

Code: [Select]
class or interface BackendInfo
public String getName(); //return "enigma";
public String getLanguage(); //return "cpp";
public boolean isDebugMode();
public String getFullInfo(); //something like "enigma/cpp debug windows+opengl+openal"
//Possibly more functions

Code: [Select]
class or interface ExecutionPlugin
public String getName();
public boolean acceptsBackend(BackendInfo info); //Whether the execution plugin was made for this backend
public Script[] getScripts(); //Creates GML scripts for the plugin, useful for pure GML plugins
//The rest would be backend-specific. For instance, ENIGMA could provide a EnigmaExecutionPlugin.
//Do note that even if a plugin was not EnigmaExecutionPlugin, ENIGMA would accept it based ONLY on the acceptsBackend function, in order to allow pure GML extensions.

I am not sure how to allow pure GML scripts to handle custom resources, though.

This is just a proposal. Feel free to use, modify and/or ignore it. I release this proposal under the WTFPL.

13
Ideas and Design / Rooms versus Panes (formerly Windows)
« on: January 21, 2011, 02:31:29 PM »
Disclaimer: This is NOT an operating system discussion.

ENIGMA uses rooms, a concept it inherits from Game Maker.
I'm going to ignore the issue of compatibility for now.

The idea behind Windows/Panes(a concept based on RPG Maker) is *NOT* to have a game with multiple windowing system windows. That'd be awful. I mean in-game windows/panes, such as dialogs and menus.
If you think about it, most games feature some sort of controls system, which I typically implement in Game Maker using the Draw event.
"Pause" is typically kind of an ugly hack, and so is keyboard management with menus.

Windows/Panes(along with focus) solve this problem by ensuring only the focused window/pane receives keyboard events(or something like having a "Global Keyboard Event" like what happens with the mouse). For RPGs, this means messages are incredibly simple to implement.
For some windows/panes, this might require some more effort, to ensure the world "stops" when e.g. pausing the game(set hspeed=0, etc.)
Still, simple compared to the situation of what we have in GM today, where I find myself reimplementing the system for some feature subset.

What do you think about this? Surely, Windows/Panes can be implemented using Rooms and Objects. But... what about doing things the other way around? Like implementing views using windows/panes.

14
OpenAL is a 3D audio library that is available across multiple platforms.
Although it supports 3D sound, it can also be used as a 2D audio library.

First, some concepts:

1. The context: Pretty much like OpenGL, you first need a context to work with. You have to create at least one context before having any sound. I'd recommend creating one for the default device(NULL) and then just keep using that one.
2. The listener: It doesn't matter what is playing around the world if there's nobody there to hear it. To have OpenAL working we have to initialize a listener.
3. The source: Similarly, it doesn't matter who is listening if there's nothing being played. Just like listeners *receive* sounds, sources *emit* sounds.
4. The buffer: So, we have a context, a listener and a source. The listener listens to sounds and the source plays those sounds. But exactly what sounds does the source play? The buffers store data to be played by sources. We will load our audio data to buffers, and then assign those buffers to sources.

So, let's get started:
Code: (C) [Select]
#include <AL/al.h>
#include <AL/alc.h>
#include <cstdio>

int main() {
        return 0;
}

Compile the code above using:
Code: [Select]
g++ -Wall -lopenal file.cpp -o program
Make sure you have everything you need installed, in particular the headers and libraries.

Now, our program doesn't do anything.
So we'll start by creating a context.

Code: (C) [Select]
ALCdevice* device = alcOpenDevice(NULL);
ALCcontext* context = alcCreateContext(device, NULL);
alcMakeContextCurrent(context);

In the example above, we open the default audio device(NULL), and then create a context for that device.
Finally, we use that context.

Now, we're going to define our listener:

Code: (C) [Select]
alListener3f(AL_POSITION, 0, 0, 0);
alListener3f(AL_VELOCITY, 0, 0, 0);
alListener3f(AL_ORIENTATION, 0, 0, -1);

Since we only want 2D sound, leave those values as they are. We're telling OpenAL where the listener is, where it is moving, etc.
We're going to put all listeners and sources in the origin with no speed.

Finally, we are going to have to load a audio file to a buffer and play it.
In this case, we're going to load the entire file to memory and play it all at once.

So first we are going to create a source:

Code: (C) [Select]
ALuint source;
alGenSources(1, &source);

alSourcef(source, AL_PITCH, 1);
alSourcef(source, AL_GAIN, 1);
alSource3f(source, AL_POSITION, 0, 0, 0);
alSource3f(source, AL_VELOCITY, 0, 0, 0);
alSourcei(source, AL_LOOPING, AL_FALSE);

Note how you first allocate a source. We're also setting the properties of the source.
In particular, you might find the LOOPING value to be interesting. Set it to AL_TRUE to loop the audio instead of just playing it once.
AL_PITCH means how "fast" the sound is. 1 is the normal speed. Below 1 the sound will take longer to play. For instance, a 1 minute sound with pitch 0.5 will take 2 minutes, and only 30 seconds with a pitch of 2.
Do note that modifying the pitch of the sound will make the track sound differently.  Try it yourself and you'll see what I mean.

However, you won't listen to anything yet because the source has no buffer.
So, we must create a buffer first:

Code: (C) [Select]
alGenBuffers(1, &buffer);

//TODO Load data to buffer

alSourcei(source, AL_BUFFER, buffer);

So our big problem is how to load data to the buffer.
I am going to be using the WAV sound format for now. Other formats may be added later.

The code I am using is the following. I will paste it once and then explain it:

Code: (C) [Select]
FILE* f = fopen("audio.wav", "fb");
char xbuffer[5];
xbuffer[4] = '\0';
if (fread(xbuffer, sizeof(char), 4, file) != 4 || strcmp(xbuffer, "RIFF") != 0)
        throw "Not a WAV file";

file_read_int32_le(xbuffer, file);

if (fread(xbuffer, sizeof(char), 4, file) != 4 || strcmp(xbuffer, "WAVE") != 0)
        throw "Not a WAV file";

if (fread(xbuffer, sizeof(char), 4, file) != 4 || strcmp(xbuffer, "fmt ") != 0)
        throw "Invalid WAV file";

file_read_int32_le(xbuffer, file);
short audioFormat = file_read_int16_le(xbuffer, file);
short channels = file_read_int16_le(xbuffer, file);
int sampleRate = file_read_int32_le(xbuffer, file);
int byteRate = file_read_int32_le(xbuffer, file);
file_read_int16_le(xbuffer, file);
short bitsPerSample = file_read_int16_le(xbuffer, file);

if (audioFormat != 16) {
        short extraParams = file_read_int16_le(xbuffer, file);
        file_ignore_bytes(file, extraParams);
}

if (fread(xbuffer, sizeof(char), 4, file) != 4 || strcmp(xbuffer, "data") != 0)
        throw "Invalid WAV file";

int dataChunkSize = file_read_int32_le(xbuffer, file);
unsigned char* bufferData = file_allocate_and_read_bytes(file, (size_t) dataChunkSize);

float duration = float(dataChunkSize) / byteRate;
alBufferData(buffer, GetFormatFromInfo(channels, bitsPerSample), bufferData, dataChunkSize, sampleRate);
free(bufferData);
fclose(f);

So now what is that big thing?
First we read the WAV header and extract the information from it, such as number of channels and rate.
Then, when we reach the section that contains the actual audio data, we load the entire thing to memory and load it to the buffer using alBufferData.

I have used multiple auxiliary functions:

1. file_read_int32_le(xbuffer, file) - I am using this function to read an integer of 32 bits from a file in little endian
2. file_read_int16_le(xbuffer, file) - This one is used to read 16 bits in little endian
3. file_ignore_bytes(file, nbytes) - Ignores N bytes from the file
4. file_allocate_and_read_bytes(file, nbytes) - Allocates a char* with N bytes and loads those bytes from the file
5. GetFormatFromInfo(channels, bitsPerSample) - Gets the AL format for the sound.

One possible (though incomplete) implementation of GetFormatFromInfo is:

Code: (C) [Select]
static inline ALenum GetFormatFromInfo(short channels, short bitsPerSample) {
        if (channels == 1)
                return AL_FORMAT_MONO16;
        return AL_FORMAT_STEREO16;
}

file_ignore_bytes can be implemented with a while+fgetc, or more efficiently in other ways.
file_allocate_and_read_bytes is essentially a malloc and a fread.
file_read_int32_le/file_read_int16_le is essentially a fread to the buffer, using count=4, and then using bit shifts and bitwise ors to format the data.

These functions are pretty easy to implement, so I'll leave them to you (the reader) as a C exercise.
You can also load WAV using ALUT, if you have it installed.

So now that we have this working, we're going to play the sound (and let it keep playing)

Code: (C) [Select]
alSourcePlay(source);
fgetc(stdin);

This will keep playing the sound until the user presses enter in the console.

Finally, we'll do some cleanup:

Code: (C) [Select]
alDeleteSources(1, &source);
alDeleteBuffers(1, &buffer);
alcDestroyContext(context);
alcCloseDevice(device);

Now, try it. It should be playing whatever file named "audio.wav" you have in your current path.

Some gems:
1. You can use alSourcePause(source) to pause the source, and then alSourcePlay(source) to start it again
2. You can use alSourceStop(source) to stop the source. Calling alSourcePlay(source) after that will start it over from the beginning
3. You can change your mind about looping at the middle of the stream. Want to loop the sound? No problem, just set AL_LOOPING to AL_TRUE.
4. You can also change the pitch while the sound is playing. For instance, if you have a game and your character dies, you could have the pitch go progressively lower to indicate a "Game Over".

Some limitations:
1. GetFormatFromInfo is incomplete. For instance, MONO8 and STEREO8 aren't properly supported.
2. The entire file is loaded to memory. Depending on the size of the file it might be a problem.
3. Loading the entire file at once might be slow. However, in my experience, even for large files, this isn't much of a problem for WAV files. Memory consumption, as indicated in 2, might be significantly worse.

Bonus Tricks:

1. How to find the current position of the sound being played?

Code: (C) [Select]
int byteoffset;
alGetSourcei(source, AL_BYTE_OFFSET, &byteoffset);
return float(byteoffset) / byteRate;

Will return the number of seconds since the beginning of the sound file.

2. How to change the current position (e.g. skip some part of the sound)?

Code: (C) [Select]
alSourcei(source, AL_BYTE_OFFSET, int(position * byteRate));
Both of these tricks only work when the entire file is in a single buffer.

EDIT 27 Apr 2012 - Fixed bug that could potentially corrupt memory. (Credits to Stephan Z.)

15
Function Peer Review / file_delete
« on: December 30, 2010, 07:46:53 PM »
This one looks so easy that it is a shame that ENIGMA doesn't have it already.

Code: (C++) [Select]
#include <stdio.h>
#include <string>

using namespace std;

inline void file_delete(string filename) {
    remove(filename.c_str());
}

Pages: [1] 2 3