1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
|
=======================================
Pong - A Three Dimensional Pong Game
=======================================
Written By Johannes Jordan (sijojord)
SUMMARY:
Pong gives you the opportunity to revamp the old and famous
gameplay of Atari's first game. It uses OpenGL for drawing, has
really freaky visuals and adds another dimension. Now every
player can move along two axes; and instead of watching the
playing field from the side, you're perspective is first person.
Therefore everyone needs his own PC - this is a networked
multiplayer game :)
MANUAL:
To play this game, you have to start it regulary on one machine
as "server" and tell your friend to connect from his box to yours.
While you're in server mode, you also have the opportunity to do
some practicing and get a feeling about the gameplay, your paddle
and the ball's movement: You can play against "Mr. Wand", but be
warned: nobody could beat Mr. Wand before!
First important thing to get started are the command line options,
which you can ask for by using for example the "-?" option:
-------------------------------------------------------------------------------
Usage: pong2 [-n <name>] [-c <server>] [-p <port>] [-w <width> -h <height>]
[-b <bitsperpixel>] [-f]
-n set your name (default: Hans)
-c connect to already running server (default: act as server)
-p set alternative udp networking port (default: 6642)
-w set x resolution in pixels (default: 1024)
-h set y resolution in pixels (default: 768)
-b set individual bitsperpixel (default: 32)
-f operate in fullscreen mode (default: windowed, toggle with 'f' key)
-------------------------------------------------------------------------------
Now while you're running the game, you can press several keys:
-------------------------------------------------------------------------------
q Quit the game
f Toggle fullscreen mode
p /
ESC Toggle pause mode. Note that this affects also your opponent. In
fact, really the whole game is halted in paused mode. Toggling pause
also toggles wether your mouse is grabbed or not.
F1 Set camera mode to "Free". This means, that no automatic camera
movement will be done.
F2 Set camera mode to "Follow Paddle Reverse". This means, the camera
will follow the movement of your paddle by giving sight onto your
paddle.
F3 Set camera mode to "Follow Paddle". This lets the camera follow the
paddle a little like you're standing behind the paddle and want to
look through it.
-------------------------------------------------------------------------------
Let's see what you can do with your mouse. Moving the paddle along is
a kinda easy. Please notice that the paddle has a maximum possible
speed to be moved with.
To adjust the camera, you now can hold down the right mouse key and
change the viewing angle by moving around. Also, by holding the middle
mouse key, you're able to adjust the distance of the camera to the
action going on.
By pressing the left mouse key, you serve the ball, if one was
attached to your paddle. Be aware of how the ball leaves you're
paddle. If you got some speed on your paddle pointing in a specific
direction, the ball will drift to that direction.
This is also important for bouncing the ball. You can give it some
additional speed by moving your paddle along and also reduce it's
speed. It won't help you against Mr. Wand - but to stamp down a
regular opponent, you will need a little knowledge about the ball's
reaction to your paddle movement.
Additionally to this, as your paddle is bent, the position on which
the ball hits your paddle is also influencing its flight path.
While playing, you can count on the reflections on the walls to get a
better feeling about where the ball actually travels around.
One additional note: Every player gets 5 rounds to serve at a time,
regardlessly who won or lost the last round.
Now have fun playing and kick some ass!
IMPLEMENTATION:
I want to discuss several topics here to describe the main challenges
of the implementation and how we get beyond:
* Abstraction of SDL / object orientation
I wanted the different modules to be strictly _not_ dependant on the
SDL or anything alike. They also should never know anything about our
type of networking, and so on. The only thing reasonably for me to
let them depend on was OpenGL for their drawing code. So one big
point of the OO information hiding model was to let both Server and
Client to be inherited from Framework. This not only made it possible
for them to not know anything about SDL, but also to not make it
necessary to let Framework know anything about Client or Server. So
whenever the environment makes it necessary to kick either Server or
Client to do something (like the user want to move the paddle with
the mouse), virtual functions help to get across that border. The
Server never knows how actually the user generates the movement and
the Framework doesn't know wether the server uses the data for cal-
culations or the client just sends it over the network.
So there are functions defined in the objects which only get called
by the Server or by the Client and not both. To not hevily rely on
this, I decided to let stuff like the global detectCol() function to
remain in Framework (no matter it will only be called by a function
only called by the Server).
To make the program extensible, the Framework holds vectors of
objects like Balls and Players. The Server yet knows there are only
two Players - and which one is which one - but the Framework doesn't
need to know and still can satisfy the needs of processing all of
the objects.
* Getting Timers to work
This one also belongs to the last point a little bit. I didn't want
to make any object aware of what an SDL timer is or alike. So I had
to think about layering around it without too much overhead and
hassle. First, we have to think of how a called Timer ever reaches
it's calling object, without knowing it. So all Timer calling
functions have to be inherited from EventReceiver, which is nothing
more then a pure virtual action() function and an enum to describe
different Timer Event types; everything else is to be handled by
the object itself. To add a layer above the pure SDL Timer, we need
another structure as part of the Framework, which has to be
accessable, in our implementation by an index over a pointer holding
vector, just for one purpose: to be able to stop the timer. A pointer
to this Struct is also given to the timer itself as userdata to handle
it's event call. We can't operate in the timer's processing function
itself, as the Timer ends in it's own thread and nothing is thread
save here. So we have to push an SDL User Event, which gets passed the
struct pointer and then finally collect the timer in the loop()
function - which is a cleaner way anyway. Deleting this structure on
the requested removal of the timer (mostly when it's done) is a very
bad idea - it works most of the time, but if a timer's event gets
delayed too long, this will crash. So we can only cleanup the SDL
Timer in time and need to clean up the rest afterwards.
* Getting Networking to work
Our networking protocol is rather easy - after some small talk on
the beginning, the server continously reports ball and paddle
positions, while the client reports if it wants to move the paddle.
Some random additional packages are needed too, like to tell the
Pause state or score.
We have to be aware that UDP loses packages; but in fact, if in our
implementation packages are lost during initialization, it would
just not work out (it's very unlikely to happen as this is the state
before we start to send lots of packages). If gamedata packages are
lost, this isn't a really big issues. The ball for example would not
be updated as often as it could be, or there could be a scoring
get lost and when the player scores again, it will again show up
correctly..
To have a clean way of building network packages, I wrote a little
Buffer called helper class which understands every packet having a
type which is sent first, and then random data which is pushed and
popped (on the other side) according to the packet's type. We don't
have error handling here and have to assure ourselves that packages
really contain the variables the receiver expects from them. To do
this, a versioning mechanism prevents two instances of different
game versions to get in touch with each other.
* Colliding
This is a bad chapter. First I thought I could do this in a very
easy way for walls, a not-that-easy way for paddles and a nearly
impossible(?) way for other balls (future!). After having some not
really working code, I had to rethink the whole issue. Now the ball
doesn't tell anyone where it is from and where it wants to be, but
instead iterates through alle positions on it's way and ask if it's
already stuck inside anything. How many positions are tested against
is determined by the ball's speed factor (the biggest one of x, y, z),
which eventually will lead to less fps on a high ball speed.
Because I think of having very little time and place difference
between these steps, the collided object is allowed to just move the
ball a little bit aside in the case of a collision, which seems to
indeed work good.
Collinding can be done very simple and very extensive with that model.
As there is no real need to be physically correct or something like
that, here simple methods are preferenced. While rewriting the
collision code for the paddle I found out that even the simplest
approach - doing without more expensive tests - can lead to very
well results.
To go even further, some sluttery can indeed assist in a pleasing
gameplay.
* Reflections
The main purpose, apart from the visual appeal, of the reflections
is to help the player track the ball. So I chose not to spend any
work on having the reflections go 'right' dependent on from where
you look. In fact, they are drawn just are mirrored on the appropriate
axis (X or Y). To get them drawn on the walls and have everything
look nice, I draw the ball reflection first and overdraw it with the
real, translucent, wall. This worked out very well, after not only
filling the stencil buffer, but also drawing a solid background to
where the reflection and finally the wall itself will go. But it
resulted in a little contrasted wall texture. To get beyond that, the
wall is drawed two times fully textured - while filling the stencil
buffer and after drawing the reflection. The first time, I darken it,
the second time it has a decreased alpha value.
|