DracoLacertae

Members
  • Content count

    216
  • Joined

  • Last visited

Community Reputation

518 Good

About DracoLacertae

  • Rank
    Member
  1. I have a simple portal/sector rendered FPS maze that's around 'DOOM' or Quake 1 geometric complexity.  Of course, I'm using OpenGL and VBOs.  Shaders are optional in my engine.  (Fallback to fixed-function vertex lighting when needed/specified)   I also have an old PC with a k6-3 processor and a Voodoo 3 graphics card.  It dual boots windows 98 (for those 3dfx glide games I don't want to emulate) and lubuntu 10.04.   I decided to compile it on the voodoo machine for no reason other than curiousity.  I figured that glquake runs really good on this machine, so a homemade game that looks worse than gl DOOM should run good too.   After a little source tweaking, it built.  Run... segfault.  Fired up gdb:  glCreateBuffer (and glCreateBufferARB) are missing... Right... voodoo 3.  Ok, old-style vertex arrays look just like VBOs, just glVertexPointer is a real pointer instead of a screwy offset.  Very minor tweak to make this work.  (All my VBO stuff is abstracted, so I literally change it in one place...)   Next crash:  glActiveTexture was NULL.  What?  Oh, don't panic.  My card only supports glActiveTextureARB.  Ok, simple change, use ARB when core isn't available.  (I already do the same on my netbook; for whatever reason the Atom I have supports glCreateBufferARB but not glCreateBuffer...)   A little bit of tweaking and... well its a slideshow.   Turn on some optimizations.  (On my desktop PC I get away with "-g" because I'm not close to CPU bound.  Turn on -O3 and --fastmath) and wow, like 15-20 fps.   Next up:  I know I need to put in state sorting.  Instead of rendering, I'll put items into a queue, then sort by texture, then render.  In fact, right now even if I draw two things that are the same texture right after each other, I'll still re-bind the second item even though I'm not changing anything.  Bad, bad.   Why am I doing this?  Mostly fun.  But its also useful.  If a vertex-lit, FPS looks like DOOM, it better run at the speed of gl DOOM on a computer that can play gl DOOM, or I've done something horribly wrong.   That, and I'm exploring the possibility of a gracefully degrading engine.  When I run by terrain test app on my desktop, the triangles subdivide tiny to just being a few pixels wide.  Lots of procedural detail.  When I run it on my netbook, I divide even less.  You can see the polygons, but the core flying mechanic is the same.  I run it on voodoo.  The polygons are huge, but its still the same rough terrain and same core mechanics (though its much slower than I want; 5-10 fps for terrain, I'll get the numbers up there.)     Anyway its a lot of fun.  Oh, and this is in the lounge, because, well, who in the opengl forum would care about making it work on a voodoo card!
  2. can I go without events?

    Using WM_CHAR will save you a lot of trouble.  If you are going to interpret the keystrokes as text (player is entering their name), you should use WM_CHAR.  For player actions, use WM_KEYDOWN/UP.  Pressing, holding and releasing a key will generate many events.  If you press and hold shift-A you will get this:   shift keydown a keydown capital A char (repeated several times) capital A char (repeated several times) capital A char (repeated several times) a key up shift key up
  3. can I go without events?

    The problem with polling is you can miss something.  If the user presses and releases a key before you get around to polling again, you will completely miss it.  Mouse clicks are fast, and if your frame rate stalls for whatever reason (not even your program's fault; maybe the dreaded McAfee just started to do an update in the background...) and you're out of luck.   I get that you want to just poll to check if a key is pressed in your code.  I like to do that too.  But it's easy to do with events:  (Rough pseudocode; haven't done win32 events in a while: bool keystate[MAX_KEY]; bool mousebutton[MAX_MOUSEBUTTON]; int mousex, mousey; #define MOUSE_LEFT 0 #define MOUSE_RIGHT 1 eventhandler(...) { while (event = getnextevent(...)) { if(event==KEY_DOWN && keycode < MAX_KEY) keystate[keycode] = 1; if(event==KEY_UP && keycode < MAX_KEY) keystate[keycode] = 0; if (event==L_MOUSE_DOWN) mousebutton[MOUSE_LEFT] = 1; if (event==L_MOUSE_UP) mousebutton[MOUSE_LEFT] = 0; if (event==MOUSE_MOVE) { mousex = event_mouse_x; mousey = event_mouse_y; } } } Then, in your code you can do  if(keystate[KEY_L_CTRL]) all you want.  Or sprite.draw(mousex, mousey).   The above could still suffer from missing presses.  If the system slows down, and you call the event loop once per frame, if a keydown AND a keyup press bunch up against each other, then in one frame's processing of events you would set, and unset a single key in the same call to the loop.  One way to do that is handle the player's inputs in the event handler itself: if(event==KEY_DOWN && keycode < MAX_KEY) { keystate[keycode] = 1; if (keycode == playerconfig.firekey) player.fire=1; if (keycode == playerconfig.jump) player.jump= 1; } This way the event loop is looking for particular events, and sets those flags in the player object.  Each frame, after the event handler is run, when the player's control code is run, it checks it's action flags and does the appropriate action.  This way you can't miss a jump or fire event, even if the next event is the queue is releasing the button.   You can abstract that completely and do this: while (event = getnextevent(...)) { memset(keypressed, 0, sizeof(keypressed)); //clear pressed events if(event==KEY_DOWN && keycode < MAX_KEY) { keystate[keycode] = 1; keypressed[keycode] = 1; } if(event==KEY_UP && keycode < MAX_KEY) keystate[keycode] = 0; } Now, you can't miss an event.  If the key was pressed at all since the last frame, keypressed will be '1'.  And if its still pressed keystate will be '1'.  So now anywhere in your game loop you can do this: if (keypressed[ player_config.fire_key] || ( keystate[player_config.fire_key] && (this_frame - last_fire_frame >= 20)) {     last_fire_frame = this_frame;     fire_a_bullet(); } What this does is fire a bullet every time the player presses the fire key OR if the player holds down the fire key, one bullet every 20 frames.   Also this is all assume a single main loop thread, that looks something like this: while(not_quitting) { handle_window_events(); increment_game_logic(); draw(); }
  4. Games you would like to see exist

    Descent 4 w/ Oculus Rift support.  Will probably never happen.  It should be bundled with a coupon for free Dramamine.   I would like to see a puzzle game like Portal, but where there's time travel between the portals.  The engine sees the trajectory the ball is going to take, and lets it fly out of the 2nd portal a couple seconds before it enters the first one.  Self-consistent paradoxes would be the solutions to some of the puzzles.  You press a button, and you launch a ball.  The ball isn't going to make it to the portal, but right before it would miss, the counterpart ball (from the future) flys out of the 2nd portal and hits the ball into the 1st one, and gives the remaining ball just enough of a nudge that it can reach the button that triggers the exit.  If the player tries a non-consistent paradox (like the ball from the future prevents the ball from going in the portal), then there's some severe penalty, like the universe implodes or something.
  5. Wow thanks.  I didn't know I could forward reference a struct like that.  upvote for you!
  6. I have a struct with a function pointer in it.  This compiles fine: typedef struct gx_quadpatch_s {     gx_vbuffer_t* vb;     struct gx_quadpatch_s* children[4];     struct gx_quadpatch_s* parent;     //...     void (*detailer)(struct gx_quadpatch_s* dest, struct gx_quadpatch_s* source, int a_start, int a_end, int b_start, int b_end); } gx_quadpatch_t ; I wanted a pretty typedef for the function type:   typedef     void (*gx_quadpatch_detailer_f)(     struct gx_quadpatch_s* dest, struct gx_quadpatch_s* source, int a_start, int a_end, int b_start, int b_end); typedef struct gx_quadpatch_s {     gx_vbuffer_t* vb;     struct gx_quadpatch_s* children[4];     struct gx_quadpatch_s* parent;     //...     gx_quadpatch_detailer_f detailer; } gx_quadpatch_t ; GCC gives this warning:   In file included from gx_quadpatch.c:18:0: ../graphics/gx_quadpatch.h:5:80: warning: 'struct gx_quadpatch_s' declared inside parameter list [enabled by default]  typedef  void (*gx_quadpatch_detailer_f)(  struct gx_quadpatch_s* dest, struct gx_quadpatch_s* source, int a_start, int a_end, int b_start, int b_end);                                                                                 ^ ../graphics/gx_quadpatch.h:5:80: warning: its scope is only this definition or declaration, which is probably not what you want [enabled by default] This suggests I need to declare the struct quadpatch_s definition before the gx_quadpatch_detailer_f.  Circular dependency.  Not sure how to break it.   Usually if I have an interdependency between structs, I can do this: typedef struct foo_s { struct bar_s* my_bar; } foo_t; typedef struct bar_s { struct foo_s* my_foo; } bar_t; After that, I can refer them at foo_t and bar_t.  In its allowed for a struct to defined as containing a pointer to another struct that hasn't yet because all struct pointers are the same size.  Can I not do the same thing with function argument lists?   Is there something I can put above the gx_quadpatch_detailer_f typedef to resolve this? 
  7. Your language is very similar to C.  I would suggest you write a simple preprocessor that reads your langauge, applies a few tweaks to it, and outputs C.  Yes, generating C form a non-C language can be ugly, but generating C from almost-C could just be a matter of some basic text manipulation.  Even if you intend to write a full-features compiler, this exercise will be useful because it will allow you to write (and run) functioning programs in your model language, and quickly make changes to it as you discover new features you want to add, or discover that certain features just don't work they way you thought they would.  Then when your language design is finished, you can then write a full compiler for it, generate llvm code, or whatever.  That's exactly how C++ was made:  It started as a C++ to C converter.
  8. I crammed them together.  At my engine level its plain C, so it looks like this: gx_camera_set(&player.camera); //everything after this call will be drawn from the POV of the player //player is an entity: typedef struct entity_s{    gx_sector_t* current_sector;  //pointer to current sector player is in    gx_camera_t camera; //... other stuff } entity_t; //The camera contains position, orientation info: typedef struct gx_camera_s {    vec3   position;    mat33  orientation;    float view_angle; } gx_camera_t; gx_camera_set will set up the modelview matrix and projection matrix so things are drawn from the player's point of view, with the view frustum angle approximating the view_angle value.  If the player zooms in with a scope, view_angle shrinks.  View angle is used for first-pass culling, etc.   Non-player entities (enemies) ALSO have cameras for two reasons:  I can view the world from their point of view (its cool for debugging, you can watch the entity follow you, and see when it mistakenly 'sees' or 'doesnt see' you).  My code to see if something is in my field of view (and thus determines if I should draw it; 'it' being the other entities) is the same code the enemies use to see if something is in their field of view (and thus determines they should chase it; 'it' being the player of course), is the same code.
  9. deinstancing c-strings

    I did find out in gcc (at least on my version of linux), if I use at least -O1, and all my input files are passed at once( eg.  gcc -O1  bar.c foo.c baz.c ), all my identical constants were merged.  Is there any reason why you can't build it all 'at once', instead of making a bunch of .o files seperately?     fir, you should take a look at this:  http://stackoverflow.com/questions/7459939/what-do-single-quotes-do-in-c-when-used-on-multiple-characters   If you restrict yourself to 4 characters, you can do this:  foo(x,y, 'red' );  foo(x,y, 'grn' );  foo(x,y, 'quik' ); //i tried out out on gcc, and if its too long, it just takes the 1st 4: foo(x,y, 'red' ); foo(x,y, 'green' ); //really gree foo(x,y, 'quick' ); //really quic You 4 characters packed into an int.  Its as close to human-readable, not predefined values you'll be able to get in C without resorting to linker tricks.
  10. deinstancing c-strings

      I really like that.  I don't think enums are guaranteed to hold 32 bits, but this is more likeley to work than merging strings.  If the compiler can't produce 32 bit enums for whatever reason, it should at least spit out a warning or error to alert you of this.   Fir, I know everyone's already told you why this is a bad idea.  I used to code like this at one point.  I completely agree with you that the string literals are just turned into a magic number by the compiler.  Each time you compile, its different, but its a unique number because that literal needs a unique address in memory.  It would work perfectly well if all the constants could be merged together across files.  But, thats not guaranteed behavior, and even if one compiler does it today, it might not tomorrow.   If you want to keep the idea of an ad-hoc enum, where you can just use it without defining anything, then you could use a syntax like:     At the top of each file have: #include <adhoc_enums.h> Then in your code use a notation like: void color_something(thing* thing, adhoc color) { if (color == adhoc(red) )    ... if (color == adhoc(green) )   ... } Your makefile can call a short script, that finds all the adhoc references, and produces adhoc_enums.h: #define adhoc(x) adhoc_enum_ ## x typedef enum { adhoc_enum_null = 0, adhoc_enum_red = 1, adhoc_enum_green, adhoc_enum_blue ... } adhoc_enum; Advantages: You get real enums Your values are basically ints now.  Then can be safely =='d against each other You can use switch/case for if/else chains. Type safety against other char*.  Also probably compiler warning against ints or other enum types.  adhoc_enums only 'like' each other No dependence of weird compiler or linker options Disadvantages: Extra build step w/ an extra utility you need to write (not as gross as you think.  Ever use qmake? ) You also have the added advantage, that you could do this once, and keep the generated enum file.  When you use new enum values, you can add them to adhoc_enums.h manually.  Then maybe at some point you will decide to split your enums into different types (enum_color, enum_shape).  You might find yourself going down a standards-compliant path.
  11. How did you start in Game Development/Programming?

    QBASIC when I was in elementary school.  I did work with gwbasic for a short time, but jumped ship when I saw it didn't have line numbers.   I had a little 3d wireframe maze thing going, and that was a lot of fun.  In high school I wrote a lot of junky C, playing around with opengl on my voodoo 3.  I still mostly just do that as a hobby, just on much better hardware, and with a lot cleaner code.   I'm a software developer, but my job has nothing to do with games.  Boring systems stuff.  Actually systems stuff is quite fun.
  12. What is your favorite videogame and why?

    1.  If I had to pick one series, the Descent series.  Dizzying and disorienting, and I still play it from time to time.  It takes a really long time to master.  It has a similar 'feature' to quake where walking forward and strafing lets you move faster than just going forward.  1 unit forward + 1 unit sideways is sqrt(2) units of motion.  Except Descent let you also 'strafe' up, so you can move sqrt(3) units of motion if you do all three at once.  In Descent it actually makes sense; if you're firing thrusters in space, your 'up' thruster isn't automatically weak because you're also thrusting forward and to the right.   2.  Portal is next on my list.  It's just fun and silly.  Nonviolent FPS.  Well, if you don't count knocking over dumb turrets as violence.   3. Beyond Good and Evil:  Going around taking pictures.  Fun game, mostly non/minimally violent gameplay.   4. Ken's Labyrinth: This probably isn't on anyone else's list, but it was the 1st raycaster FPS I played back on my... 286.  It's also a bit silly.  This is the game that made me write a wireframe maze in qbasic and become interested in 3d graphics.   other classics I need to mention: Quake II, Tomb Raider (original), Prince of Persia (original)
  13. What don't you like about your programming language?

    Perl and PHP:  The damned dollar sign!  I get why its there, and I've written parsers that depend on it being there.  But I've also fingured out how to deal with it not being there in my own parsers, and it wasn't that hard.  And I'm talking about a recursive-descent parser with a simple hashmap for a symbol table, not a bison, yacc, or other animal-generated automata. C:  An operation I do sometimes, is given a pointer, take the address of a member.  So if I want a pointer to foo's X member, its  to_x = &(foo->x).  If you expand out the arrow shorthand, its really saying  to_x = &( (*foo) . x ) ;  It's dereference, and then take a pointer to a member.  We all know what its really doing.  It will compile to just an ADD instruction adding some constant offset to my foo pointer.  It seems like something that should be able to be expressed a little easier.  My proposal would be to expand the use of 'dot'.   We all know that if bar is a struct, then  bar.x returns the value of x.  If foo is a pointer, foo.x should return the pointer to x.  At least I think it would be cleaner. BASIC:  Yes, this, I mention it because this is where I learned programming.  'DIM' needs to die.  DIM x as integer.   x as integer is sufficient.  pascal-style x: integer.  integer x.  Anything is better than DIM.  Some variants (like basic stamp or PIC micro basic, use a var syntax.  Like var int x.  That's not too bad.)
  14. Most Unique Mental Experience

    A few interesting experiences:   Sleep paralysis: Sometimes I wake up and I can't move.  It was frightening the first time it happened, but after I learned what it was, no big deal.  I've figured out what when I open my eyes, I'm seeing mostly the real room around me.  My wife can confirm after the fact my eyes were open.  There are two interesting things that happen.  1) There is sometimes a hallucination 'overlay' where I see whats real AND other stuff at the same time.  and 2) if I close my eyes, while in this state, I can 'move.'  I'm not really moving, I'm moving in the dream.  I can't see anything because my eyes are closed, but I can feel the fake dream world around me like a blind person.  In this state, if I open my eyes, I 'snap' right back into being in bed in my original position, stuck.  If I keep my eyes closed, I will start to see dream visuals and either fall back asleep fully, or maintain a lucid dream state.   Sometimes I will wake up and anything anyone says to me is unintelligible gibberish for maybe a minute or so. Guess that part of me is still asleep.   Sometimes I will have an 'alternate reality' dream where my life is significantly different, but all the characters are the same. (Like on the show Sliders)  When I wake up, I'm confused and can't remember which one is real.  The confusion lifts in a couple minutes.   This one involves alcohol:  Out camping with a bunch of people at night, circled around the fire, with the stars above and pitch black to your back.  Knowing you're not to be afraid of the dark behind you because the person across the circle from you will see anything coming for you.  And realizing that as long as we've existed humans have sat in a circle around a fire and that by touching that experience I've bridged a connection spanning thousands of years and traveled through time.
  15. I like this.   I also like the idea of seperate data and program control stacks.  I've worked with FORTH, and there's two parallel stacks.  That wouldn't be hard to do with x86 either.  Call and ret can use the hardware stack, and it's downward growth wouldn't be a concern.  Then argument passing and temporary data would be on a completely different stack, one that has nothing to do the 'esp' stack pointer.  Actually, the data stack could just be managed using the 'ebp' pointer; you don't really need a base pointer anyway.  You sort of do to make debugging easier, but you can get by without it.     On x86 I don't think it would be too bad, at least for push/pop.   This page puts them at about 1% of each other.  http://qinsb.blogspot.com/2011/03/x86-register-save-performance.html Doing your own call and return might be a little bit of a hit, but I hope most of the program's time is spent doing something other than function call overhead.   But, I've found something that makes the whole thing moot.  Having an upwards growing stack is not the panacea of stack buffer overruns.  from   the tl;dr is that you can allocate a buffer too small on the stack, and then call a function that's supposed to write into that buffer.  As it writes into the previous frame's buffer, it will blast past the buffer and trash its own, including the return address.