What do you do to revitalize a slowing project?

•February 23, 2013 • Leave a Comment

The following draft of a post has been sitting around since early 2012, and I had never published it. Since I think it still contains some potentially useful ideas, though some of the Leges Motus-specific details are a bit out-of-date, I have decided to publish it as-is, without being edited to reflect the time gap. Hopefully it will be useful to someone.

– Greywhind

—-

It’s been seven months since the most recent stable release of Leges Motus. The release is almost ready – there are only a few more bugs to fix before it can be packaged up and sent out there.

This time has mostly been filled with work on new features, bug fixes, testing, and the like, but some of it has also been downtime. The developers are all very busy people, with schedules that can often leave little time or energy to work on more coding.

So how, then, do you keep a project alive when it’s going slowly? That’s the question I’ve been asking myself, and I think there are a few important elements:

1. At least one or two members of the development team have to be willing to put in some extra time. Why? Encouraging others to work on the project is much more effective when you’re doing cool things yourself. They won’t listen if you’re also sitting around doing nothing.

2. You have to be understanding of their schedules. Nagging won’t help – you just need to show your continued involvement, produce some new features or fix some bugs, and keep them informed of what’s going on with the project. As they hear of exciting new developments, they’ll hopefully start to move the project up on their priority list.

3. If tasks assigned to another developer are blocking other progress and making things slow down, you have a few options. First, you should probably try talking to them. Often, offering to help or just giving them a polite reminder that you care about and need their work can do the trick (though they may still eventually slow down or stop work, you can probably get at least the current blocker resolved). But if that doesn’t work, you’ll need to consider trying to take over their assignment. You should try to do this in a way that makes them still feel useful (not just kicking them out of the project, but instead asking their permission to do the task for them). If you don’t know enough about their work to do this, you might need to either spend a lot of time getting to know their code or find another developer (either already associated with or completely new to the project) to do the task. This can be the hardest part.

4. Bringing a new developer into the mix can also be a great way to pump some energy into the project. If you have at least one dev with enough time to show the new person the ropes, you can get them moving and bring some enthusiasm and new ideas into the mix. Just make sure they don’t end up pulling all the weight, because they’ll get burnt out.

5. Finally, sometimes there’s nothing to do but give it some time. Interest flows in cycles – if you give it a rest, doing some small things when you feel like it, you will probably end up with a growing feeling that you want to work on the project. If you’re lucky, everyone else will too, and interest will pick back up.

The trick to revitalizing a slowing project is to keep your contributions steady and interesting, to talk to the other developers about the project, and to avoid nagging too much – if someone is too busy, work around it rather than alienating them with constant pushing.

So what have I been doing for Leges Motus? I’ve been making a new online server browser, which can now be seen on the website. I’ve added a scroll-wheel key binding to switch to the next and previous weapons. I’ve planned/opened a few new to-do tickets on the bug tracker. And I’ve been talking about the project with other developers, keeping them in the loop and keeping Leges Motus on their minds. It takes more than one man to make a project, but only one to keep it kicking.

Leges Motus Progress – 2D Particle Effects, GL_POINT_SPRITE

•November 27, 2011 • Leave a Comment

A few days ago, I posted a brief description of the beginnings of the new particle effects system in Leges Motus. Since then, I have been working hard to finish up the system to the point where it’s fully usable in-game. It has now reached that state, and is fully integrated with gameplay (though it may be used in more places in the future). Particle effects spawn whenever a player fires, and can be specified for each weapon in a config file.

For this purpose, I have made several improvements and additions to the particle effects system. I have added a “SimpleLineEmitter” class, which can be used to randomly spawn particles along a given line, instead of just at a specific point. This works nicely for tracer trails, and I am currently using it for the rifle’s effect.

Additionally, I have changed the drawing code for particle effects. The original code drew each particle like any other sprite, in immediate mode and with a quad. This was extremely inefficient, so I created a new method in DrawContext that allows drawing an image repeatedly in many places at once using GL_POINT_SPRITE and VertexArrays. This should allow drawing many more particles at a time, because it reduces the number of draw calls significantly.

I keep arrays of the particle centers and colors in the emitter, trying to initialize it to the max number of particles that will be spawned by the emitter at a time, but increasing the size if necessary. These are simply float arrays, with the centers array being twice the size of the number of particles, so it can hold the x, y for each one in sequence, and the colors array being four times the size of the number of particles, so it can hold the RGBA values for each particle in sequence. I bind the sprite I want to draw, and then pass these arrays into the following function (LM_gl and LM_GL are just macros around gl_ and GL_):


void GLESContext::draw_bound_point_sprites(const float vertices[], int n, int size_x, int size_y, const float colors[]) {
LM_gl(Enable, (LM_GL(POINT_SPRITE)));
LM_gl(TexEnvi, (LM_GL(POINT_SPRITE), LM_GL(COORD_REPLACE), LM_GL(TRUE)));

// Set the size of all the point sprites
LM_gl(PointSize, (max(size_x,size_y)));

unbind_vbo();

// Configure the point array
LM_gl(VertexPointer, (2, LM_GL(FLOAT), 0, vertices));

// Enable the use of the color array
LM_gl(EnableClientState, (LM_GL(COLOR_ARRAY)));

// Configure the color array
LM_gl(ColorPointer, (4, LM_GL(FLOAT), 0, colors));

// use glDrawArrays to draw the points
LM_gl(DrawArrays, (LM_GL(POINTS), 0, n));

// Disable the client states
LM_gl(DisableClientState, (LM_GL(COLOR_ARRAY)));
LM_gl(Disable, (LM_GL(POINT_SPRITE)));
}

Unfortunately, in standard OpenGL, there does not seem to be an easy way (other than shaders) to specify the size of each image point separately. OpenGLES has glPointSizePointerOES, which would allow specifying a size for each particle alongside the x/y coordinates.

I am very satisfied with the results of this new particle effect system. I think it will add a lot of visual interest to the game. Screenshot:

A bunch of AIs shooting each other, showing off the new particle effects system in Leges Motus.

A bunch of AIs shooting each other, showing off the new particle effects system in Leges Motus.

Leges Motus Progress – Basic Particle Effects

•November 24, 2011 • Leave a Comment

One of the main reasons we wanted to complete the Leges Motus revamp was to improve the graphical quality and special effects capabilities of the game. One of my top-priority goals in this vein was to create a particle-effect system that would be able to add some visual flair to firing, getting hit, and so forth.

So, a couple of days ago, I got it into my head to make this happen, and I have now implemented a basic particle effects system, which, while still likely needing some expansion and optimization, will allow a wide range of cool effects to be created with relative ease. Below is a screenshot of a test application using the new system to create a fireworks-like effect.

Leges Motus particle system test.

A test of the new basic particle effects system for Leges Motus, showing some simple fireworks-like bursts.

To create this system, I took some inspiration from the following papers: “The Ocean Spray in Your Face” (Jeff Lander, 1998) and “Building an Advanced Particle System” (John Van der Burg, 2007). These are excellent resources, and I highly recommend them for their simple and understandable explanations, though I found that they left a few crucial details lacking.

First, they provide little information on rendering questions, such as adding shaders to particle effects and optimizing your drawing routine. I have not yet switched to rendering particles with GL_POINT_SPRITE, instead of quads like a normal sprite, but it will probably be important to do so for performance reasons. I have also not yet really experimented with shaders on particle effects, but I expect to look into that eventually as an addition to the system.

The basic system is as follows:

There is a simple Particle class, with important properties for each particle to keep track of separately:


Point m_pos;
Point m_prev_pos;
Vector m_vel;
uint64_t m_energy_left;
uint64_t m_initial_energy;
Color m_color;
float m_size;

ParticleEmitter is a base class, which provides the basic functionality that is required to spawn a bunch of particles. This functionality is then extended by each specific type of emitter. Currently, I have only implemented a single specific emitter type, a basic burst-like emitter that is surprisingly flexible.


class ParticleEmitter {
private:
Image* m_image;
DrawContext::BlendMode m_blend_mode;
int m_alive_count;
Point m_center;
ParticleManager* m_manager;
protected:
std::list m_particles;
public:
ParticleEmitter(ParticleManager* manager, Point center, Image* image, DrawContext::BlendMode mode = DrawContext::BLEND_ADD);
virtual ~ParticleEmitter();

void draw(DrawContext* context);

std::list& get_particles();

void clear();

Particle* request_particle();
void free_particle(Particle* particle, std::list::iterator it);

virtual bool update(uint64_t timediff);

Point get_center();

void set_center(Point center);
void set_center(float center_x, float center_y);
};

Each specific emitter type will be given its own settings, in the form of a struct. The struct for the SimpleRadialEmitter class looks like:


struct SimpleRadialEmitterSettings {
float particle_speed;
float speed_variance;
int spawn_per_second;
int spawn_variance;
uint64_t lifetime_millis;
uint64_t lifetime_variance;
float rotation_rads;
float rotation_variance;
Vector global_force;
int max_spawn;
uint64_t emitter_stop_spawning_millis;
uint64_t emitter_lifetime_millis;
};

Finally, all the emitters currently active are managed by the ParticleManager class, which runs their update and draw loops and deletes them when they’re done. It also contains a (potentially resizable) pool of particles, to avoid object creation/deletion too frequently during gameplay.

The ParticleManager is also a “Widget,” which means it can be placed into any other widget in the Leges Motus graphics framework. This allows the particle system to be easily overlaid on the existing game display, or anywhere else we need it.

This development is moving slowly, due to the final other programmer, Archaemic, no longer having time to work on the project. Going it alone, it’ll take me quite a while to get all the features (especially menus) done. I don’t plan to give up, though.

Leges Motus Progress – AI and Player Update Setup

•August 27, 2011 • Leave a Comment

I have been very remiss in posting updates about Leges Motus for the last few months, and for that, I apologize. The game has been moving forward slowly since late May, but it should be picking up some more speed again soon.

Our most recent updates saw the way that clients send information to the server about their actions changed somewhat, reducing the load on the network as less information needs to be sent frequently to the server.

Earlier, before the end of May, we focused heavily on getting AI support fully implemented. The AI is now working well, though there are still a few things that could be improved, and it has proven very fun to play against for human players, which is exactly what we were aiming for.

Our artist (http://www.stevensugar.com/) has sent us a few new concept pieces, and we expect to get him working on making final in-game art soon.

One of the concept art pieces created for Leges Motus.

One of the concept art pieces created for Leges Motus.

Keep watching for more information!

Leges Motus Progress: Pathfinding with Ray Casting and A*

•March 30, 2011 • Leave a Comment

Since our last update, the Leges Motus team has been working hard on a new portion of the AI: pathfinding. Having an AI that jumps around randomly, trying to slowly get towards the goal, won’t get you very far in terms of realistic simulation or believable gameplay. We decided to make a pathfinding system that would allow our AIs to find paths to any point on the map.

Here is a screenshot of our pathfinding system in progress – you can see the line showing the calculated path to the goal:

Testing the Leges Motus pathfinding system

A test version of the Leges Motus pathfinding system.

We could not simply use a standard A* algorithm based on a grid of connected space, because it would assume the ability to turn in mid-air, which is not possible in zero-gravity. We decided, however, that we still wanted to try to use A* as the final pathfinding algorithm. To do so, we had to make a connected graph of all points on walls in the map that could be reached from other walls. We created a data structure, which we call a SparseIntersectMap, to hold this information. It contains x, y, and angle coordinates, mapped to other x, y, angle coordinates, with distances. It also contains the means to find the correct bin for an arbitrary x, y, and angle value, with a specific granularity. This allows us to take an approximate location, reducing the storage costs to a manageable number from the unimaginable cost of storing connections at every pixel.

Another class is used to create this graph, casting rays from each bin, getting results, and storing them in the SparseIntersectMap. Another shortcut is taken here – once a location is reachable from another bin, we store the back-connection as well, avoiding casting again when we reach that bin.

Finally, once we have the graph, a standard A* algorithm can be used to find the paths. This is the most standard part of the process.

We found that there were some problems with this approach. Casting at large angles could cause bounces between obstacles that were almost touching, leading to paths between walls that were not actually passable. Therefore, we cast only at angles that are somewhat closer to the normal of the wall. Additionally, ray casting from within an object causes the cast to ignore that object, so we ensure that all casts are done slightly inside the wall, avoiding ignoring any other objects that may be touching the wall.

The result is a fairly decent pathfinding system. It still could use more tweaks: it sometimes finds paths that go very close to a wall, where the player will actually hit the wall instead of getting past. This is somewhat less of a problem than it might seem at first – even real players cannot always calculate their trajectories properly to get there on the first jump. Nonetheless, we are looking into ways to reduce this problem.

We have also added a non-standard extra weighting to the A* algorithm – we found that paths would often be placed in such a way that the player could get stuck jumping from one wall to another, always calculating a new path that led back to the old location before continuing. Therefore, we put weights such that the most recent old location is less desirable. This has reduced this problem.

I’ll keep posting as things progress – the next step will be to create a finite state machine system, allowing us to add some more strategy to our reactions, following an overall ‘personality’ or ‘current objective.’

Leges Motus Progress: Simple and Fuzzy Logic AIs

•March 3, 2011 • Leave a Comment

Time has gotten away from me once again, and I have not posted a recent update on our progress with Leges Motus’ AI creation. First of all, the most recent code for Leges Motus is now on GitHub (https://github.com/jpfau/legesmotus), instead of SourceForge’s SVN. Secondly, we have been making significant progress on the AI creation.

We started with a very simple, deterministic AI, to test out our ability to make a completely player-less client. This was very easy, due to the design of the re-written client and logic code. The simple AI essentially works as follows:

  1. Find the nearest enemy we can see.
  2. Find out if we can see the enemy gate.
  3. If we are not on the enemy gate and we can see it, turn the gun towards the gate and then jump towards it.
  4. If we can see an unfrozen enemy, turn the gun towards them and fire (with some random inaccuracy).
  5. If we can’t do either of those, jump randomly (with a chance of jumping towards the gate, even if we can’t see it, in hopes of getting closer).

That’s about it.  It’s simple, it looks a bit silly jumping randomly into walls, but it works. It can even win games against human players. But really, it’s just a baseline test.

The Fuzzy Logic AI is more interesting, though still not up to the level we will be trying to reach for our final AI. The system is approximately as follows:

  1. A config file specifies the ranges and edge functions for each fuzzy logic category/bin within that category. Example: dist_to_player.melee is somewhere around 0-64 pixels away, with dist_to_player being the category and melee being the bin.
  2. The categories are loaded, and put into a FuzzyLogic object. A FuzzyEnvironment is passed in each frame with the appropriate values set for each category.
  3. The FuzzyLogic passes the bin values into various Rules, which are constructed in a hierarchical structure (Example: two terminal bin values inside an Or rule inside an AND rule, all in a NOT).
  4. The Rules each output a float value, which is used to determine the importance of various actions or the value of various things. For example, a rule is used to determine which enemy player is most dangerous.
  5. The output actions are similar to the possible output actions of the Simple AI, but decided upon in a much more flexible and interesting way. This makes the AI seem less computer-like and more like it is making informed decisions.

We are also looking into making the FuzzyLogicAI switch weapons as appropriate to the situation. This could be slightly more difficult, since the weapon system was not designed to allow easy external determinations of weapon damage at a specific range, exact effects, etc. We will be considering how best to make these decisions general enough to work with any weapon setup.

Some Fuzzy Logic AIs helping me capture the enemy's gate.

Some Fuzzy Logic AIs helping me capture the enemy's gate.

Unfortunately, the FuzzyLogicAI is still unable to jump in a useful manner (other than randomly) except when it sees an enemy gate. This means that our next step will be pathfinding. We plan to store information about the reachability graph for each map on disk, and to load that into memory when the AI joins. This could have a lot of space/time tradeoff issues to look at, and we will then need to determine the best algorithm (probably A* of some sort) to find the paths through the map once the graph is made.

I’ll keep updates coming as we continue the AI work.

Managing RayCasts in Box2D

•February 26, 2011 • Leave a Comment

The Box2D physics library is quite impressively simple to use, for the most part. It manages all the necessary calculations internally to provide for standard movement and collisions. When your project needs to go outside this standard behavior, however, Box2D can sometimes be a little bit more complicated. Specifically, the RayCast system is slightly cumbersome to deal with, at times. Since I have recently been dealing with this for Leges Motus’ AI code (which I’ll talk about in another post), I will present my thoughts on managing RayCasts in Box2D.

To call a ray cast, you use the method:

RayCast(b2RayCastCallback& listener, b2Vec2& start_point, b2Vec2& end_point);

It uses a callback, defined by having your class implement b2RayCastCallback, with the signature

float32 ReportFixture(b2Fixture* fixture, const b2Vec2& point, const b2Vec2& normal, float32 fraction);

This callback reports the current fraction of the ray at which the hit has occurred, as well as which fixture, which point in space, and the normal of the surface on the fixture. You need to return a fraction, after which the RayCast will ignore all points. If you return 1, it will continue to give you all additional hit results. If you return 0, it will immediately stop giving you further results. If you return the current fraction, it no longer returns anything beyond that fraction, but will continue to check within the remaining part of the ray.

Unfortunately, the callback is entirely unordered – there is no requirement that the closest points will be given first. This makes dealing with it somewhat annoying, at times. Additionally, it stores no information about the ray cast after the callback is done, unless you store it yourself, and returns no information from the ray cast call itself. You have to use an instance field or global variable to keep the results you care about. This can get somewhat ugly, if you do not want to add random global variables in random classes that need a ray cast for something.

I recommend making a class specifically to handle ray casts. It should wrap the ray cast method itself (possibly providing a simple way to cast in a direction, instead of specifying the exact end point). Additionally, it should implement the callback and keep global info about the ray cast in a struct or inner class. You might want to store a sorted list of all hit points, or just the closest. It depends on your needs. Then, make that data accessible to the object that called in for the ray cast. This will make it much easier to write small amounts of code to do a ray cast, then get the results. Processing the results might still take some work, but it won’t be as messy.

Also, I recommend that you have all your physical objects extend some sort of “physics object” base class, so that you can set them as user data on their physics bodies, and you can therefore check their type and such.

Hopefully this will help you manage ray casts in Box2D. If you have other suggestions or questions, feel free to leave a comment.

Some current example code for a class we use for this purpose in Leges Motus follows (note: you can check the current code for Leges Motus at https://github.com/jpfau/legesmotus, if you want an updated version or to see the context):

RayCast.hpp:

/*
 * common/RayCast.hpp
 *
 * This file is part of Leges Motus, a networked, 2D shooter set in zero gravity.
 * 
 * Copyright 2009-2011 Andrew Ayer, Nathan Partlan, Jeffrey Pfau
 * 
 * Leges Motus is free and open source software.  You may redistribute it and/or
 * modify it under the terms of version 2, or (at your option) version 3, of the
 * GNU General Public License (GPL), as published by the Free Software Foundation.
 * 
 * Leges Motus is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the full text of the GNU General Public License for
 * further detail.
 * 
 * For a full copy of the GNU General Public License, please see the COPYING file
 * in the root of the source code tree.  You may also retrieve a copy from
 * <http://www.gnu.org/licenses/gpl-2.0.txt>, or request a copy by writing to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 * 02111-1307  USA
 * 
 */

#ifndef LM_COMMON_RAYCAST_HPP
#define LM_COMMON_RAYCAST_HPP

#include "common/physics.hpp"

namespace LM {
	class PhysicsObject;
	class Player;
	class MapObject;

	class RayCast : public b2RayCastCallback {
	public:
	
	struct RayCastResult {
		const PhysicsObject*	start_object;	// The object (if any) where this ray started
		b2Vec2		ray_start;	// The starting point of the ray cast
		float		ray_direction;	// The angle (in radians) at which the ray was cast
		b2Vec2		ray_end;	// The maximum point on the ray
		PhysicsObject* 	closest_object; // The closest object
		float		shortest_dist;	// The closest hit-point on that object
		b2Vec2		hit_point;	// The point where the ray hit
	};

	private:
		RayCastResult m_ray_cast;
		const b2World* m_physics;
		bool m_ignore_collidable;

	public:
		RayCast();
		RayCast(const b2World* physics);
		
		~RayCast();
		
		RayCastResult& get_result();
		
		void set_physics(const b2World* physics);
	
		float cast_at_player(const Point& ray_start, const Player* other_player, float max_radius = -1);
		float cast_at_player(const b2Vec2& ray_start, const Player* other_player, float max_radius = -1);
		
		float cast_at_obstacle(const Point& ray_start, const MapObject* object, float max_radius = -1, bool ignore_collidable = false);
		float cast_at_obstacle(const b2Vec2& ray_start, const MapObject* object, float max_radius = -1, bool ignore_collidable = false);
		
		float cast_in_vel_dir(const Player* player);
	
		float do_ray_cast(const b2Vec2& start_point, float direction, float distance, const PhysicsObject* starting_object = NULL, bool ignore_collidable = false);
	
		// Box2D Physics Callbacks
		float32 ReportFixture(b2Fixture* fixture, const b2Vec2& point, const b2Vec2& normal, float32 fraction);
	};
}

#endif

RayCast.cpp:

/*
 * common/RayCast.cpp
 *
 * This file is part of Leges Motus, a networked, 2D shooter set in zero gravity.
 * 
 * Copyright 2009-2011 Andrew Ayer, Nathan Partlan, Jeffrey Pfau
 * 
 * Leges Motus is free and open source software.  You may redistribute it and/or
 * modify it under the terms of version 2, or (at your option) version 3, of the
 * GNU General Public License (GPL), as published by the Free Software Foundation.
 * 
 * Leges Motus is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the full text of the GNU General Public License for
 * further detail.
 * 
 * For a full copy of the GNU General Public License, please see the COPYING file
 * in the root of the source code tree.  You may also retrieve a copy from
 * <http://www.gnu.org/licenses/gpl-2.0.txt>, or request a copy by writing to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 * 02111-1307  USA
 * 
 */

#include "RayCast.hpp"
#include "common/misc.hpp"
#include "common/PhysicsObject.hpp"
#include "common/MapObject.hpp"
#include "common/Player.hpp"

using namespace LM;
using namespace std;


RayCast::RayCast() {
	m_physics = NULL;
	m_ignore_collidable = false;
}

RayCast::RayCast(const b2World* physics) {
	m_physics = physics;
	m_ignore_collidable = false;
}

RayCast::~RayCast() {
}

RayCast::RayCastResult& RayCast::get_result() {
	return m_ray_cast;
}

void RayCast::set_physics(const b2World* physics) {
	m_physics = physics;
}

float RayCast::do_ray_cast(const b2Vec2& start_point, float direction, float distance = -1, const PhysicsObject* starting_object, bool ignore_collidable) {
	m_ignore_collidable = ignore_collidable;
	
	if (distance == -1) {
		distance = 20000;
	}
	float end_x = start_point.x + cos(direction) * distance;
	float end_y = start_point.y + sin(direction) * distance;
	
	m_ray_cast.ray_start = start_point;
	m_ray_cast.ray_end = b2Vec2(end_x, end_y);
	m_ray_cast.ray_direction = direction;
	m_ray_cast.start_object = starting_object;
	m_ray_cast.closest_object = NULL;
	m_ray_cast.shortest_dist = -1;
	
	if (m_physics == NULL) {
		return -1;
	}
	
	m_physics->RayCast(this, start_point, m_ray_cast.ray_end);
	
	return m_ray_cast.shortest_dist;
}

float RayCast::cast_at_player(const Point& ray_start, const Player* other_player, float max_radius) {
	b2Vec2 start = b2Vec2(ray_start.x, ray_start.y);

	return cast_at_player(start, other_player, max_radius);
}

float RayCast::cast_at_player(const b2Vec2& ray_start, const Player* other_player, float max_radius) {
	b2Vec2 target_pos = b2Vec2(to_physics(other_player->get_x()), to_physics(other_player->get_y()));
	float x_end = target_pos.x - ray_start.x;
	float y_end = target_pos.y - ray_start.y;
	float wanted_angle = atan2(y_end, x_end);
	
	// Perform the first raycast, at the center of the player.
	do_ray_cast(ray_start, wanted_angle, max_radius);

	b2Body* body = other_player->get_physics_body();
	// XXX: Do we just want to use the first fixture?
	b2Fixture* fixture = &body->GetFixtureList()[0];
	b2Shape* shape = fixture->GetShape();
	if (shape->GetType() == b2Shape::e_polygon) {
		b2PolygonShape* polyshape = static_cast<b2PolygonShape*>(shape);
		int index = 0;
		while (index < polyshape->GetVertexCount()) {
			// Check if we've seen the player
			PhysicsObject* hitobj = m_ray_cast.closest_object;
			if (hitobj != NULL) {
				if (hitobj->get_type() == PhysicsObject::PLAYER && (max_radius == -1 || to_game(m_ray_cast.shortest_dist) < max_radius)) {
					Player* hitplayer = static_cast<Player*>(hitobj);
					if (hitplayer->get_id() == other_player->get_id()) {
						break;
					}
				}
			}
			
			b2Vec2 vertex = polyshape->GetVertex(index);
		
			float x_end = to_physics(other_player->get_x()) + vertex.x * cos(other_player->get_rotation_radians());
			float y_end = to_physics(other_player->get_y()) + vertex.y * sin(other_player->get_rotation_radians());
			float wanted_angle = atan2(y_end, x_end);
			
			do_ray_cast(ray_start, wanted_angle, max_radius);
			index++;
		}
	}
	
	PhysicsObject* hitobj = m_ray_cast.closest_object;
	if (hitobj == NULL) {
		return numeric_limits<float>::max();
	}
	
	if (hitobj->get_type() != PhysicsObject::PLAYER || (max_radius != -1 && to_game(m_ray_cast.shortest_dist) > max_radius)) {
		return numeric_limits<float>::max();
	}
	
	Player* hitplayer = static_cast<Player*>(hitobj);
	if (hitplayer->get_id() != other_player->get_id()) {
		return numeric_limits<float>::max();
	}
	
	return m_ray_cast.shortest_dist;
}

float RayCast::cast_at_obstacle(const Point& ray_start, const MapObject* object, float max_radius, bool ignore_collidable) {
	b2Vec2 start = b2Vec2(ray_start.x, ray_start.y);

	return cast_at_obstacle(start, object, max_radius, ignore_collidable);
}

float RayCast::cast_at_obstacle(const b2Vec2& ray_start, const MapObject* object, float max_radius, bool ignore_collidable) {
	Point object_pos = object->get_position();
	b2Vec2 target_pos = b2Vec2(to_physics(object_pos.x), to_physics(object_pos.y));
	float x_end = target_pos.x - ray_start.x;
	float y_end = target_pos.y - ray_start.y;
	float wanted_angle = atan2(y_end, x_end);
	
	// Perform the first raycast, at the center of the object
	do_ray_cast(ray_start, wanted_angle, max_radius, NULL, ignore_collidable);
	
	// Cast at the other corners
	const b2Shape* shape = object->get_bounding_shape();
	if (shape->GetType() == b2Shape::e_polygon) {
		const b2PolygonShape* polyshape = static_cast<const b2PolygonShape*>(shape);
		int index = 0;
		while (index < polyshape->GetVertexCount()) {
			// Check if we've seen the object
			PhysicsObject* hitobj = m_ray_cast.closest_object;
			if (hitobj != NULL) {
				if (hitobj->get_type() == PhysicsObject::MAP_OBJECT && (max_radius == -1 || to_game(m_ray_cast.shortest_dist) < max_radius)) {
					if (hitobj == object) {
						break;
					}
				}
			}
			
			b2Vec2 vertex = polyshape->GetVertex(index);
		
			float x_end = to_physics(object_pos.x) + vertex.x * cos(to_radians(object->get_rotation()));
			float y_end = to_physics(object_pos.y) + vertex.y * sin(to_radians(object->get_rotation()));
			float wanted_angle = atan2(y_end, x_end);
			
			do_ray_cast(ray_start, wanted_angle, max_radius, NULL, ignore_collidable);
			index++;
		}
	}
	
	PhysicsObject* hitobj = m_ray_cast.closest_object;
	if (hitobj == NULL) {
		return numeric_limits<float>::max();
	}
	
	if (hitobj->get_type() != PhysicsObject::MAP_OBJECT || (max_radius != -1 && to_game(m_ray_cast.shortest_dist) > max_radius) || hitobj != object) {
		return numeric_limits<float>::max();
	}
	
	return to_game(m_ray_cast.shortest_dist);
}

float RayCast::cast_in_vel_dir(const Player* player) {
	b2Vec2 ray_start = b2Vec2(to_physics(player->get_x()), to_physics(player->get_y()));
	float wanted_angle = atan2(player->get_y_vel(), player->get_x_vel());
	
	float found_distance = 0;
	
	// Perform the first raycast, from the center of the player.
	do_ray_cast(ray_start, wanted_angle, -1.0f, player);
	
	found_distance = m_ray_cast.shortest_dist;

	b2Body* body = player->get_physics_body();
	// XXX: Do we just want to use the first fixture?
	b2Fixture* fixture = &body->GetFixtureList()[0];
	b2Shape* shape = fixture->GetShape();
	if (shape->GetType() == b2Shape::e_polygon) {
		b2PolygonShape* polyshape = static_cast<b2PolygonShape*>(shape);
		int index = 0;
		while (index < polyshape->GetVertexCount()) {			
			b2Vec2 vertex = polyshape->GetVertex(index);
		
			float x_start = to_physics(player->get_x()) + vertex.x * cos(player->get_rotation_radians());
			float y_start = to_physics(player->get_y()) + vertex.y * sin(player->get_rotation_radians());
			b2Vec2 start = b2Vec2(x_start, y_start);
			
			do_ray_cast(start, wanted_angle, -1.0f, player);
			
			if (m_ray_cast.shortest_dist < found_distance) {
				found_distance = m_ray_cast.shortest_dist;
			}
			
			index++;
		}
	}
	
	return to_game(found_distance);
}

float32 RayCast::ReportFixture(b2Fixture* fixture, const b2Vec2& point, const b2Vec2& normal, float32 fraction) {
	b2Body* body = fixture->GetBody();
	
	if (body->GetUserData() == NULL) {
		WARN("Body has no user data!");
		return 1;
	}
	
	if (fraction < 0) {
		return 1;
	}
	
	PhysicsObject* hitobj = static_cast<PhysicsObject*>(body->GetUserData());
	Point end = Point(point.x, point.y);
	float dist = (end-Point(m_ray_cast.ray_start.x, m_ray_cast.ray_start.y)).get_magnitude();
	
	if (fixture->IsSensor()) {
		return 1;
	}
	
	if (m_ray_cast.shortest_dist != -1 && dist > m_ray_cast.shortest_dist) {
		return 1;
	}
	
	if (hitobj->get_type() == PhysicsObject::MAP_OBJECT) {
		MapObject* object = static_cast<MapObject*>(hitobj);
		
		if (!m_ignore_collidable && !object->is_collidable()) {
			return 1;
		}
	}
	
	m_ray_cast.shortest_dist = dist;
	
	m_ray_cast.closest_object = hitobj;
	
	m_ray_cast.hit_point = b2Vec2(end.x, end.y);
	
	return 1;
}

Leges Motus Progress – January Wrap-up, AI Begins

•February 2, 2011 • 2 Comments

We have been hard at work on Leges Motus, since mid-December, and I have posted many of the important features we have re-implemented and discussed a few of the topics related to the refactor in my previous posts. Today, I want to give an overview of what we have completed recently, and start on the topic of AI.

Since my last post, the heavyweight server has been fully implemented and tested, and everything seems to be working very well with it. We now use it as the mainline server, and it has, as we hoped, improved the synchrony and centralization of the control over what the clients see at any given time. We have already seen the effects of the centralized server on player collisions – they now look natural, with no weird behavior such as players pushing past each other. This will enable a lot of cool things in the future, including moving map objects, and potentially even throwable weapons such as grenades, someday.

On the HUD front, we have redesigned the look and feel of the HUD, and we are very happy with how it is coming together. We have working health and weapon status bars, gate status indicators, and a radar. The theme is much more standardized and polished, and we expect to continue this focus on graphical improvement throughout the project. The following screenshot shows the current status of the HUD:

The in-development Leges Motus 0.5 HUD, as of February 2011.

The in-development Leges Motus 0.5 HUD, as of February 2011.

Additional elements will be added to the HUD as we go forward, such as weapon indicators, chat, etc.

The gameplay features have been fully completed, including energy regeneration and gameplay parameters (which the server sends to the client, for flexibility in running custom game styles).

We still need to work on menus and configuration options, but we will be putting those off for now. They will be completed before 0.5 is released.

We are taking a break from the implementation of gameplay, HUD, menu, and other features (or at least decreasing our focus on them) over the next few months, to work mostly on the development of an AI (or, more likely, several iterations of different approaches to AI).

We will be starting with an AI somewhat similar to that seen in the now-defunct AI branch of LM 0.2 (except this time, with much better framework around it so that we don’t have to drop it next version!), which will not act with strategy but will react appropriately to the current situation. For example, if an enemy is in sight, it will turn towards the enemy and fire at him. If the enemy’s gate is available, it will move to capture it.

Later, we will implement path-finding, so that the AI can move towards specific points on the map intelligently, and we will eventually work on strategic action, finding a good search algorithm (or algorithms) to determine the best movement and firing decisions.

As we continue towards these goals, I’ll be sure to post about our thoughts and our work. Keep watching for more!

Lightweight vs. Heavyweight Servers: A Question of Design

•January 19, 2011 • Leave a Comment

In the vein of my previous post on Box2D and explosions, I’d like to discuss a topic that has been on my mind over the past few days: that of heavyweight versus lightweight servers… and possibly even a third server-architecture contender.

Recently, I have been working on a branch of the Leges Motus code that makes a radical change to the operation of the game as a whole. One of the main purposes of our large refactor has been to make the game much more modular, less tied to the original design. This has been extremely helpful in the process of this change: I am working on making the Leges Motus server, which previously served almost exclusively to relay packets and keep track of client addresses and scores, actually execute the game logic and take precedence over the clients in that respect.

In other words, the server will control where players move, holding a canonical game state and sending information about it to the clients, rather than allowing the clients to send it all of their own game data and just passing that information around to the others.

This is the difference between a lightweight server (more of a peer-to-peer relay node than a full game server) and a heavyweight server (which runs all the game logic and controls what the clients see).

There are certainly some advantages and disadvantages to both types of server:

Lightweight Server:

Advantages:

  • Very small server storage and CPU requirements – easy to run your own server.
  • Simple code on the server side.
  • Very little data must be transmitted to a client about its own game state – and potentially little about other clients, if their state can be approximated somewhat by client game-logic – so network bandwidth can be reduced.
  • What the player sees is exactly what he gets. Since the client controls bullet hits, movement, etc., lag is less of a factor when determining what the result of an action is.

Disadvantages:

  • No canonical game state – clients can be somewhat out of sync with each other, and it might require more networking to get them to sync properly.
  • There is almost no way to prevent cheating. Clients can send anything they want to the server, and it cannot verify that they are doing reasonable things while retaining the advantage of a small, lightweight server.
  • Physics can act strangely when clients see different information about the current state of various physics objects. For instance, players might seem to push past each other when colliding, because they have no way to cooperate on the results – one might see the collision later, or have it resolve differently, leading to a strange-looking outcome.
  • A client that is lagging or dropping packets can seem like it is cheating, e.g. shooting other players when they can’t tell that it’s moving into range.

Heavyweight Server:

Advantages:

  • There is a canonical game state, so all movement and collisions, among other things, can be made to make sense.
  • The server can get a better handle on cheating, because it knows everything about the game state and can tell when a client is acting strangely.
  • Clients can be somewhat reduced in complexity – if CPU or RAM is a significant performance limiter, the client computers may not need to be as powerful to run the game (as long as GPU isn’t the limiting factor).
  • Updates or in-game advertising may be easier, since the server can sometimes deploy new game logic or content without client version updates.

Disadvantages:

  • The server will require more resources to run.
  • If clients are lagging, they may feel that they can’t get snappy/proper results from actions.
  • Network communication could be increased in volume, as each client must be told about relevant game state.

Given all of these factors, it is difficult to make a decision about the best type of server-client relationship for your game. Originally, we chose a lightweight server for Leges Motus, because the only important changing game-state was controlled by each client – none of it had to be shared or mediated, aside from the gates. This meant that snappy responses were more important than canonical game state. Additionally, we wanted to ensure that the server would be easy to put up and run from anywhere, to encourage the creation of new servers around the world. This situation was ideal for a lightweight server.

Now, however, we have much more intricate physics requirements. We can’t have clients disagreeing amongst themselves about the game state, or things could come out looking very strange. Therefore, a heavyweight server is looking more useful.

But a few problems remain with the heavyweight server approach – we don’t want players trying to shoot and then getting a miss from the server because they had a little bit of lag, for instance. Therefore, we’re taking a hybrid server approach. The server will control all positions and motion, and it will keep track of all the information about the game. The clients, however, will also store their own game logic and physics, and they will tell the server when they fire and who they hit. This sort of hybrid heavy-server-heavy-client approach might take somewhat more resources, but other benefits are maximized.

At the moment, it looks like this theory is paying off. The heavyweight server branch seems to be working fairly well, and most of the small problems that have cropped up were fairly simple to resolve. I will be continuing to test and improve the branch, and, if all goes well, we may see it get merged into the main trunk and become the standard Leges Motus architecture. It’s certainly been extremely helpful that we implemented the game logic in such a way that it is mostly self-contained and there was no dependency on a specific client design – it merely needs to know some information about the game, and then it can run mostly on its own. This made running it on the server quite easy, all things considered.

If you have any thoughts on this topic, please leave a comment and tell me what sorts of client-server architecture have worked well for you.

    Area Weapons and Explosions in Box2D

    •January 14, 2011 • Leave a Comment

    I’m going to deviate slightly from just posting about progress on Leges Motus to talk in slightly more technical detail for a bit. Recently, for the game, I decided to implement area-of-effect weapons (explosion-like or otherwise blast-like kinds of guns).

    The main goal here was not to create a realistic-looking explosion with particles and exact, perfect rotational energy on the items hit – there are some other good discussions of the issues involved in things like cover, rotational motion, and so forth here, here, and here.

    Instead, I want to talk about simply determining which bodies are hit by the explosion/area of effect, when it is non-circular. For things like cone-shaped blasts, you can’t just use the AABB to find the objects affected.

    I decided to create a new body, with a sensor (not a fully physical fixture) using the position and shape (as a b2Shape) defined for the blast. This has the advantage that it can detect hits of any shape and size, but it has the disadvantage that it requires waiting until after the next physics tick to determine what has been hit. Fortunately, if you set the shape up before the tick and apply the results immediately afterward, it takes only one frame extra, so it’s not a big problem. The other complication because of this, however, is that you must keep track of all the information about the shot until you get the results. For this, I used a “Bullet” class, storing an ID, position, and the weapon ID of the fired weapon, as well as which player fired it.

    This works best if you have a PhysicsObject wrapper class that is passed in as userdata on your Box2D bodies, so that you can always determine what type of body is hit and cast to it, then call the appropriate methods to resolve the hit.

    Now, you’ve got a list of players or other objects that are within the area, but you’re not done yet. Whenever you receive information about a hit, you have to make sure that the object isn’t blocked by others. To do this, I used a simple raycast at each vertex of the object (not just the center, because the center could be blocked without all the edges being safe from the blast). If any of the rays hit, the object is affected. Note: Though I don’t bother with it, as it isn’t terribly essential for Leges Motus, you can take this further and actually determine the percentage of the object that is affected and apply force/damage appropriately, but it’s a bit tricky.

    So now, we’ve got a method to find all objects affected by an explosion or area of effect, using nothing more than the built-in Box2D functionality. Pretty cool, eh?

    If you want to see the actual code, it’s available in the open-source Leges Motus project. You can either check out the SVN repository or view the specific files (common/AreaGun, common/Bullet, and newclient/GameLogic) using the online SVN viewer.