/*(LGPL) * Project Spitfire sprites + scrolling engine for SDL ---------------------------------------------------------------------- cs.h - Simplistic Control System ---------------------------------------------------------------------- * Copyright (C) 2001, 2003, 2007 David Olofson * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or (at * your option) any later version. * * This program 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 GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* * Note on coordinates: * Normally, cs assumes that the world continues * indefinitely in every direction. (Actually, * it wraps at 4 Gpixel intervals... :-) * * However, by calling cs_engine_set_wrap(x, y), * it's possible to make the engine wrap * automatically, to nicely support old style * endless scrolling games. The movement system * will automatically wrap around edges, and * most importantly, *interpolation* will work * correctly in conjunction with wrapping. This * applies to all points in the system, user * points included. * * Note that cs doesn't have an internal notion * of layers when it comes to wrapping, which * means that it's not trivial to implement * wrapping for parallax scrolling correctly. * All parallax layers have to be of the same * size, as cs cannot tell which points belong * to which layer when performing the internal * calculations. * * Call cs_engine_set_wrap(0, 0); to disable * wrapping. Wrapping can be set or disabled * individually for each axis. * * Note on graphics coordinates: * Graphics coordinates don't always wrap at the * same time as their corresponding object * coordinates! This is because the engine must * consider the view rectangle and the bounding * rectangles of objects, and adjust the wrapping * so that all objects that should be visible * are. * * Note on wrapping (with wrapping enabled only): * When a layer is scrolled so that it's limits * end up inside the display window, the engine * automatically makes objects wrap around from * the far edge, so that continuous looping is * possible. * However, note that this breaks if objects can * be seen at more than one edge at a time! That * is, (wrap size - display size) must be bigger * than the size of the largest object. * * TODO: * * Scrap the animation system and throw * something nicer in. A primitive VM based * "scripting" system with * commands and switches based on speed, * direction, state etc would be incredibly * powerful, and still not too complicated. * * * Implement collision detection. There should * be flags to enable/disable it on per-layer * as well as per-object basis. * * * Group all per-layer stuff into a struct, * and then use an array of structs. * * * Split each layer into two lists; one for * visible objects and one for hidden objects, * so they can be handled more efficiently. * (Hidden objects are skipped one by one in * the render loop now, for example - it will * become worse over time, I think.) * * * Implement some sort of zone based collision * detection engine that scales better than * "check everything against everything". */ /* * * BUGS: * * There is a restriction on object movement * speed in a wrapping world: No object must * move more than half of the extent of the * world on either axis in one engine cycle. * STATUS: Who cares? * * * Objects that are not free or active (they * do *not* have to be visible!) are "missing * in action" as far as the engine is concerned. * It won't find them unless you free them, * or make them visible. * STATUS: Can and should be fixed. */ #ifndef _CS_H_ #define _CS_H_ #ifdef __cplusplus extern "C" { #endif #include #define __CS_SHIFT 8 #define CS2PIXEL(x) ( (x) >> __CS_SHIFT ) #define PIXEL2CS(x) ( (x) << __CS_SHIFT ) #define CS_LAYERS 8 #define CS_DEFAULT_LAYER 1 /*0 is normally for overlays*/ #define CS_USER_POINTS 16 /* FIXME: This fixpoint crap should probably be changed to float... FIXME: Would break the "changed" mask bonus feature described FIXME: below, though. */ typedef struct { /* 24:8 fixpoint values */ int x, y; /* logic position */ int xv, yv; int xa, ya; } cs_vector_t; /* * Some handy vector construction macros... * * NOTE: position (x,y) in integer format, not fixpoint! */ /* Point */ static inline cs_vector_t cs_vector_p(int x, int y) { cs_vector_t v; v.x = PIXEL2CS(x); v.y = PIXEL2CS(y); v.xv = v.yv = v.xa = v.ya = 0; return v; } /* Point + velocity */ static inline cs_vector_t cs_vector_pv(int x, int y, int xv, int yv) { cs_vector_t v; v.x = PIXEL2CS(x); v.y = PIXEL2CS(y); v.xv = xv; v.yv = yv; v.xa = v.ya = 0; return v; } /* Point + velocity + acceleration */ static inline cs_vector_t cs_vector_pva(int x, int y, int xv, int yv, int xa, int ya) { cs_vector_t v; v.x = PIXEL2CS(x); v.y = PIXEL2CS(y); v.xv = xv; v.yv = yv; v.xa = xa; v.ya = ya; return v; } /* * Generic coordinate + speed and acceleration type. Position filtering * support is built-in as well, for smooth animation regardless of * control system / video frame rate ratio. FIXME: Interpolation can break detailed animations involving multiple FIXME: sprites that move and animate (changing images) at the same FIXME: time. Provide a "CS_OBJ_NO_INTERPOLATION" flag? */ typedef struct { /* 24:8 fixpoint values here as well */ cs_vector_t v; int ox, oy; /* last position (for filtering) */ int gx, gy; /* graphic position */ /* (changed != 0) if gx or gy changed during the last update. * This is a bit mask with the same format as the coords, * which means that you can mask some low bits off to set * a coarse movement sensitivity threshold. * Ex. (changed & 0xffffff00) == "gx or gy moved to * another pixel posn." */ int changed; } cs_point_t; /* * Force the interpolation filter of a point to the current * position of the vector. (This is required when activating * new objects.) */ static inline void cs_point_force(cs_point_t *p) { p->ox = p->gx = p->v.x; p->oy = p->gy = p->v.y; } /* * This stuff is translated from Object Pascal to "Object * Oriented Style C" with my new naming conventions... * I don't consider this code needing advanced OO, so I * don't want to drag C++ in. */ struct cs_engine_t; struct cs_obj_t; /* ---------------------------------------------------------------------- * cs_obj_t (used to be object + sprite) ---------------------------------------------------------------------- * * "The Basic Move Object. * * This object reperesents a moving object on the screen. * You just tell it in what direction to go, and the Game Graphics * system will do the job." * * What to do in the callbacks: * collision() Check accurately if two objects really collide. * (The control system will ask when bounding rects * are overlapping and the collision masks indicate * that a collision would affect one or both objects.) */ typedef struct cs_obj_t { struct cs_engine_t *owner; struct cs_obj_t **head; struct cs_obj_t *next, *prev; cs_point_t point; /* Position, speed, acceleration */ int w, h; /* Sprite bounding rect size (pixels) */ int flags; int layer; /* Collision filter (new) */ int group_mask; /* bit set = "belongs to group" */ int hit_mask; /* bit set = "can hit group" */ struct { int bank; /* Image bank */ int frame; /* Image frame */ int fframe; /* First frame (integer) */ int aframes; /* # of frames; from fframe (integer) */ int cframe; /* Current frame; from fframe (28:4 fixp) */ int aspeed; /* Frames / engine cycle (28:4 fixp) */ } anim; struct { cs_vector_t v; /* weapon offset + vel + acc */ int rate; /* frames/shot */ int timer; int ammo; /* bullet type */ } fire; int score; /*Score for destroy*/ int health; /*Health points*/ /* * Event callbacks */ int (*on_hit)(struct cs_obj_t *o); /* Ouch! */ int (*on_dying)(struct cs_obj_t *o); /* Destructing... */ int (*on_fire)(struct cs_obj_t *o); /* BOOM! */ /* * Collision detection callbacks * Called when bounding rect intersection is detected. * Should return 1 if there actually is a collision. */ int (*collision)(struct cs_obj_t *o1, struct cs_obj_t *o2); /* * Rendering callback * NOTE: Use (gx,gy) when rendering, *NOT* (x,y)! */ void (*render)(struct cs_obj_t *o); /* * User destructor; Object is freed after you return. */ int (*on_free)(struct cs_obj_t *o); /* User data */ int tag; /* user ID */ void *user; /* user data */ } cs_obj_t; /* cs_obj_t flags */ /* * True when an object is "in the system"; ie when it's in one of * the sprite lists, as opposed to in the free pool, or unattached. * ("Unattached" is the state an object is in when you've just got * it from cs_engine_get_obj().) */ #define CS_OBJ_ACTIVE 0x00000001 /* * Note that an object with a 0 visible flag is still *active*! It's * coordinates are updated, animation, collisions, spawning/shooting * etc runs - it's just not visible. (Well, whatever is spawned is.) */ #define CS_OBJ_VISIBLE 0x00000002 /* * Set by the collision detection system whenever any part of the * object's bounding rectangle is inside the bounding rectangle * of the screen. */ #define CS_OBJ_ONSCREN 0x00000004 /* * Set whenever the cs animation system is to be used to generate * image frame numbers for this object. */ #define CS_OBJ_DOANIM 0x00000008 /* * Set while the object is marked as dying. Normally, you would * run some animation and perhaps spawn some explosion objects, * and then free the object. */ #define CS_OBJ_DYING 0x00000010 /* Basic methods */ /* * Note: You only really need cs_obj_layer(), cs_obj_show() * and cs_obj_free(). The others are just for more fine * grained life cycle control. */ void cs_obj_layer(cs_obj_t *o, unsigned int layer); void cs_obj_activate(cs_obj_t *o); /* Add to cs */ void cs_obj_show(cs_obj_t *o); /* Enable display */ void cs_obj_hide(cs_obj_t *o); /* Disable display */ void cs_obj_deactivate(cs_obj_t *o); /* Disengage from cs */ void cs_obj_free(cs_obj_t *o); /* Remove and throw back in the pool */ void cs_obj_pos(cs_obj_t *o, int x, int y); /* Set new position */ void cs_obj_vel(cs_obj_t *o, int xs, int ys); /* Set movement speed */ void cs_obj_acc(cs_obj_t *o, int xa, int ya); /* Set movement acceleration */ void cs_obj_vector(cs_obj_t *o, cs_vector_t *v);/* Set all */ void cs_obj_clear(cs_obj_t *o); /* Animation methods */ void cs_obj_image(cs_obj_t *o, int bank, int frame); void cs_obj_anim(cs_obj_t *o, int bank, int first, int num, int start, int spd); void cs_obj_explode(cs_obj_t *o); /*Start death sequence*/ /* Fire/spawning control */ /* NOTE: In the original version, spawned object speed was absolute, while here, it's relative to object 'o'. */ void cs_obj_shoot(cs_obj_t *o, int xo, int yo, int xs, int ys, int rate, int delay, int ammo); typedef struct { int w, h; } image_info_t; /* ---------------------------------------------------------------------- * cs_engine_t (new) ---------------------------------------------------------------------- */ typedef struct cs_engine_t { /* Display window size (pixels) */ int w, h; /* Wrapping limits (pixels) */ int wx, wy; /* Motion filtering */ int filter; /* 0: newest, * 1: linear interpolation */ /* Current time in frames */ double time; /* * Callback that's called once per Control System * frame from within cs_engine_advance(). */ void (*on_frame)(struct cs_engine_t *e); /* Active objects */ cs_obj_t *objects[CS_LAYERS]; /* Layer position/offset control */ cs_point_t offsets[CS_LAYERS]; /* Position control for custom stuff */ cs_point_t points[CS_USER_POINTS]; /* * Combined "changed" flag masks for the object groups above. */ int changed[CS_LAYERS]; /* Pool of unused objects */ cs_obj_t *pool; int pool_free; /* # of objects in here */ int pool_total; /* # of objects in system */ /* Image info table */ image_info_t *imageinfo; int nimageinfo; } cs_engine_t; cs_engine_t *cs_engine_create(int w, int h, int objects); void cs_engine_set_size(cs_engine_t *e, int w, int h); void cs_engine_set_image_size(cs_engine_t *e, int bank, int w, int h); void cs_engine_set_wrap(cs_engine_t *e, int x, int y); void cs_engine_delete(cs_engine_t *e); void cs_engine_reset(cs_engine_t *e); void cs_engine_advance(cs_engine_t *e, double to_frame); /* 0 == reset timer*/ void cs_engine_render(cs_engine_t *e); cs_obj_t *cs_engine_get_obj(cs_engine_t *e); #ifdef __cplusplus }; #endif #endif /* _CS_H_ */