saiam@333: \documentclass[a4paper,12pt]{article}
saiam@238:
saiam@238: \usepackage[utf8]{inputenc}
saiam@238: \usepackage[english]{babel}
saiam@331: \usepackage{amsmath}
ekku@349: \usepackage[pdftex]{graphicx}
saiam@238: \usepackage{listings} % For listing code
saiam@238:
saiam@238: % Fancy headers
saiam@238: \usepackage{fancyhdr}
saiam@238: \fancypagestyle{plain} {
saiam@238: \fancyhf{}
saiam@238: \renewcommand{\headrulewidth}{0.4pt}
saiam@238: \lhead{\textbf{Group 66}}
ekku@364: \chead{\textbf{Kishna Glista - Liero clone}}
saiam@238: \rhead{\textsl{AS-0.1102}}
saiam@238: \cfoot{\thepage}
saiam@238: }
saiam@238: \pagestyle{plain}
saiam@238:
saiam@238: % Paragraph formatting
saiam@238: \frenchspacing
saiam@238: \setlength{\parindent}{0pt}
saiam@238: \setlength{\parskip}{1ex plus 0.5ex minus 0.2ex}
saiam@238:
saiam@238: % Title information
saiam@356: \title{Kishna Glista - Liero Clone\\Project Documentation}
saiam@238: \author{\normalsize{\begin{tabular}{lll}
saiam@238: Atle Kivelä & 79171V & atle.kivela@tkk.fi \\
ekku@269: Eric Malmi & 80351A & eric.malmi@tkk.fi \\
terom@344: Tero Marttila & 79849E & tjmartti@cc.hut.fi \\
terom@344: Marko Rasa & 78726L & morasa@cc.hut.fi \\
saiam@238: \end{tabular}}}
saiam@238:
saiam@238: % The document begins
saiam@238: \begin{document}
saiam@356: \begin{titlepage}
saiam@238: \maketitle
saiam@356: \thispagestyle{empty}
saiam@356: \end{titlepage}
saiam@356:
saiam@356: \tableofcontents
saiam@356: \newpage
saiam@238:
saiam@238: \section{Instructions for compiling and use}
saiam@293:
terom@344: \subsection{Configuring CMake}
terom@344: You must first generate the project's CMake configuration inside of \textsl{build}. A simple script is provided to do
terom@344: so with some default settings. It will configure the install path as \textsl{~/opt}.
terom@344:
saiam@293: \begin{itemize}
saiam@293: \item \textsl{cd build}
saiam@293: \item \textsl{./mkcmake.sh}
terom@344: \end{itemize}
terom@344:
terom@344: \subsection{Compiling and Installing}
terom@344: Once you have the CMake scripts in place, compiling should be as simple as running make:
terom@344: \begin{itemize}
terom@344: \item \textsl{make}
saiam@293: \item \textsl{make install}
saiam@293: \end{itemize}
saiam@293:
terom@344: This will build the binary, and then copy the binary and data files to the install path configured above.
saiam@293:
terom@344: \subsection{Command-line Arguments}
terom@344: Running the game is done using command-line arguments to the executable
terom@344: \begin{center}
terom@344: \begin{tabular}{l|l|l|l|l}
terom@344: \textbf{Short} & \textbf{Long} & \textbf{Value} & \textbf{Description} & \textbf{Default} \\
terom@344: \hline
terom@344: -p & \verb|--port| & PORT & Set server TCP/UDP port & 9338 \\
terom@344: -s & \verb|--server| & & Run as a network server & false \\
terom@344: -c & \verb|--client| & SERVERHOST & Run as a network client on given server & false \\
terom@344: -g & \verb|--graphics| & & Enable graphics rendering & * \\
terom@344: \end{tabular}
terom@344: \end{center}
terom@344:
terom@344: * = true, except false when --server is given
terom@344:
terom@344: The options \verb|--server| and \verb|--client| are mutually exclusive, and both cannot be selected at the same time.
terom@344:
terom@344: \subsection{Keyboard Controls}
terom@344: The default controls are identical to the origional Liero default controls, with some additions.
terom@344:
saiam@293: \begin{center}
saiam@293: \begin{tabular}{l|l}
saiam@293: \textbf{Action} & \textbf{Default key(s)} \\
saiam@293: \hline
terom@344: Move Left & Arrow Left \\
terom@344: Move Right & Arrow Right \\
terom@344: Aim Up & Arrow Up \\
terom@344: Aim Down & Arror Down \\
terom@344: Dig & Move Left + Move Right \\
terom@344: Shoot & Right Control \\
terom@344: Jump & Right Shift \\
terom@344: Change Weapon & Enter + Arrow Left / Arrow Right \\
terom@344: Throw Rope & Change Weapon + Jump \\
terom@344: Release Rope & Jump \\
terom@344: Change Rope Length & Change Weapon + Aim Up / Aim Down \\
terom@344: Suicide & Left Control + K \\
terom@344: Exit Game & Esc \\
saiam@293: \end{tabular}
saiam@293: \end{center}
saiam@293:
terom@344: \subsection{Running the game}
saiam@347: To simple start a local singleplayer game, just run \verb|kg| without any arguments.
terom@344:
saiam@347: To start a network server on the default port, run \verb|kg --server|
saiam@347: (\verb|kg -s|). You may optionally also specify the \verb|--graphics|
saiam@347: argument to have the server passively draw the graphics.
terom@344:
saiam@347: To start a network client, connecting to a server running on the
saiam@347: default port, run \verb|kg --client=ADDRESS| (\verb|kg -c
|).
terom@344:
saiam@347: To use a non-default port, simply specify \verb|--port=PORT|
saiam@347: (\verb|-p |) on both client and server.
saiam@293:
saiam@238: \subsection{Configuration}
saiam@347: All global game constants are defined in \textit{src/Config.hh}, and may be
saiam@347: experimented with. Weapon parameters are defined in \textit{src/Weapons.cc}.
saiam@238:
saiam@238: \section{Program architecture}
saiam@348: The program consists of four main parts: Graphics\&Input, GameState,
terom@352: Network and Physics. Each part contains various classes; the relations
saiam@348: show up in the doxygen generated documentation of the program.
terom@344:
terom@352: The program starts from the Application class, which then starts the Engine,
terom@352: which creates GameState, Graphics and the Network Client/Server. Physics
terom@352: simulation is started when GameState is created.
saiam@347:
saiam@348: GameState contains PhysicsWorld which inherits from Terrain and
saiam@348: contains a list of PhysicsObjects. GameState also contains Player and
saiam@348: Projectile objects which inherit from PhysicsObject (and are contained
saiam@348: in PhysicsWorld).
terom@344:
saiam@348: Player objects have a list of Weapon objects which they can use to
saiam@348: create Projectiles. Every Player also has a Rope object, which is a
saiam@348: separate PhysicsObject, and is either folded away, being thrown or
saiam@348: attached to the terrain.
terom@344:
terom@352: Graphics and Input are handled in their own classes. Graphics has an
saiam@348: Input object which contains InputHandler objects for various classes
saiam@348: of input. One such object is PlayerInput which affects the GameState's
saiam@348: LocalPlayer. Other object is GuiInput which just modifies what the GUI
saiam@348: look like on the client side.
saiam@348:
saiam@350: The network code is a bit complicated and has several layers. There
saiam@350: are some nice diagrams about the program structure in the doxygen
saiam@350: documentation.
saiam@278:
terom@362: \begin{figure}[!ht]
terom@362: \centering \includegraphics[width=\textwidth]{images/class_Player_inherit_graph.png}
terom@362: \caption{Player class inheritance graph. \label{class_Player_inheritance}}
terom@362: \end{figure}
terom@362:
terom@362: \begin{figure}[!ht]
ekku@363: \centering \includegraphics[width=0.6\textwidth]{images/class_GameState_collaboration_graph.png}
terom@362: \caption{Relationships of core GameState class. \label{class_GameState_collaboration}}
terom@362: \end{figure}
terom@362:
ekku@361: \subsection{Network}
ekku@361:
ekku@361: The network code is implemented as separate NetworkServer and NetworkClient modules, which use a common high-level
ekku@361: network interface, NetworkSession and NetworkObject.
ekku@361:
ekku@361: The low-level details are implemented using ClanLib's CL\_IPAddress (referred to as NetworkAddress) and CL\_Socket.
ekku@361: NetworkUDP provides an interface to send and receive NetworkPackets to/from specific NetworkAddress's across a
ekku@361: NetworkSocket. NetworkTCP provides a NetworkTCPTransport interface, which can send/receive NetworkPackets on a
ekku@361: NetworkSocket (using NetworkBuffer to buffer socket I/O). NetworkTCPServer is a listen() socket which accepts client
ekku@361: connections as NetworkTCPTransports, and NetworkTCPClient is a NetworkTCPTransport that's connect()'d to some address.
ekku@361:
ekku@361: NetworkSession encapsulates some simple application server/client behaviour, it can function as both a server, and
ekku@361: represents remote NetworkSessions (either clients or servers) as NetworkNode objects. These then provide an interface
ekku@361: to send and receive NetworkPackets on specific NetworkChannelDs, using either TCP or UDP as a reliable/unreliable
ekku@361: transport.
ekku@361:
ekku@361: NetworkObject then implements a kind of object-oriented network protocol. A NetworkObjectController (with specific
ekku@361: subclasses for server/client behaviour) uses a NetworkSession to send messages on a specific NetworkChannelID. This
ekku@361: controller then creates and looks up NetworkObjects (again, with specific subclasses for server/client behaviour).
ekku@361: Clients and servers can then communicate by having the server construct new NetworkObjects (which are allocated an
ekku@361: unique id), and then sending NetworkPackets with a specific NetworkMessageID type on a specific object. The message is
ekku@361: then delivered directly to the NetworkObject instance on the remote end of the connection, or a new NetworkObject is
ekku@361: constructed using the data in the NetworkPacket. This enables an easy way to send events for specific objects, and
ekku@361: referr to other objects in these messages.
ekku@361:
ekku@361: NetworkServer then implements a core NetworkServer class which has a NetworkSession and a
ekku@361: NetworkObject\_ServerController. Players that connect are represented as NetworkServerPlayers, which inherit from
ekku@361: LocalPlayer and NetworkObject\_Server. This class then overrides methods in Player to deliver messages on the Player
ekku@361: object to the clients, or to create new NetworkServerProjectiles. NetworkServerProjectile inherits from Projectile and
ekku@361: NetworkObject\_Server, and sends messages when constructed, upon hitting a player, and upon being destroyed.
ekku@361:
ekku@361: NetworkClient is a bit more complicated as it must handle both the LocalPlayer, and a number of RemotePlayers. Again,
ekku@361: NetworkClient has a NetworkSession and a specialized NetworkClientController, which then creates objects of various
ekku@361: other NetworkClientClasses upon receiving messages from the server.
ekku@361:
ekku@361: Two of these classes are NetworkClientLocalPlayer and NetworkClientRemotePlayer. Both inherit from
ekku@361: NetworkClientPlayerBase, which inherits Player (virtually) and NetworkObject\_Client. NetworkClientLocalPlayer and
ekku@361: NetworkClientRemotePlayer then also inherit LocalPlayer and Remote player virtually, respectively.
ekku@361: NetworkClientPlayerBase contains the common methods that update the Player's state in response to messages received
ekku@361: from the server. NetworkClientLocalPlayer overrides handleInput to send the input mask to the server, and
ekku@361: NetworkClientRemotePlayer can handle remote clients disconnecting from the server.
ekku@361:
ekku@361: In addition, there is a NetworkClientProjectile class, which inherits from Projectile and NetworkObject\_Client.
ekku@361: this is created when a Player fires a Weapon on the server, and handles events received from the server like the
ekku@361: projectile hitting a player (inflicting damage), or being destroyed (by hitting the terrain or something similar).
ekku@361:
ekku@361: When the player first connects to the server, the server sends a large packet containing the terrain array to the
ekku@361: client, which updates its own GameState world's terrain array with the received data.
ekku@361:
ekku@361: Currently, the client only sends handleInput using unreliable UDP messages, and the server only sends position updates
ekku@361: (as sent in response to handleInput events) unreliably. All other events are sent using reliable TCP.
ekku@361:
terom@362: \begin{figure}[!ht]
terom@362: \centering \includegraphics[width=\textwidth]{images/class_NetworkObject_inherit_graph.png}
terom@362: \caption{Use of NetworkObject in network code. \label{class_NetworkObject_inherit}}
terom@362: \end{figure}
terom@362:
ekku@363: \clearpage
ekku@363:
saiam@238: \section{Data structures and algorithms}
ekku@269:
saiam@278: \subsection{Basic data structures}
saiam@278:
saiam@278: \begin{itemize}
saiam@278: \item Vector
saiam@278: \item List
saiam@278: \end{itemize}
saiam@278:
ekku@269: \subsection{World and Objects}
ekku@269:
saiam@301: % COMMENT: I think these should be in a very basic level and they
saiam@301: % shouldn't discuss too much about the game itself. The idea is just
saiam@301: % to explain different kinds of structures and algorithms.
ekku@269:
saiam@301: % Terrain
saiam@301: The terrain is an important part of our game. We represent our terrain
saiam@301: as a large array. Each shell of the array has a type that tells what
saiam@301: is in that position. Currently, possible terrain types are EMPTY, DIRT
saiam@301: and ROCK.
saiam@301:
saiam@301: % Usually, in side view 2D-games, the terrain is representated as a
saiam@301: % polygon. This allows us to implement collision detection and
saiam@301: % bouncing rather easily, and it is also a very compact way of storing
saiam@301: % the map. In our opinion Liero, however, is fundamentally a pixel
saiam@301: % based game and therefore we represent the terrain as a large array
saiam@301: % of pixels. These pixels are, however, abstract pixels, i.e. they are
saiam@301: % not necessarily in the same scale than the physical resolution. In
saiam@301: % other words, the server has an abstract resolution, which is the
saiam@301: % same for all clients, and the clients can visualize this abstract
saiam@301: % array of pixels in any resolution they wish.
saiam@301:
saiam@301: % Objects
saiam@301: In our physics simulation the shapes of the different elements in the
saiam@301: game are represented as polygons. A polygon is a vector of points that
saiam@301: define the edges of the shape.
saiam@301:
saiam@301: % On top of the terrain, the world also includes a list of all objects
saiam@301: % in it (players, projectiles and ropes). The objects have a polygon
saiam@301: % shape which is used in the collision detection between the objects
saiam@301: % and the terrain and the objects with each other.
ekku@269:
ekku@269: \subsection{Collision Detection}
ekku@269:
saiam@278: \begin{itemize}
saiam@278: \item Polygon collision detection
saiam@278: \item Pixel collision detection
saiam@278: \end{itemize}
ekku@269:
saiam@301: Collision detection algorithms check if objects in the physics
saiam@301: simulation are colliding with eachother. Because our terrain is
saiam@301: represented as an array and objects are represented as polygons we
saiam@301: have two different kinds collision detection algorithms.
saiam@301:
saiam@301: The pixel based collision detection used to check collisions with the
saiam@301: terrain is quite simple. It ``draws'' a line between two points A and
saiam@301: B. The algorithm iterates over the line from point A to B and on each
saiam@301: iteration it checks if theres a collision (i.e. the type of the
saiam@301: current point is ROCK or DIRT).
saiam@301:
ekku@357: In order to implement the bouncing from the terrain we have to be able
ekku@357: to calculate a normal for the slope of the terrain in the collision
ekku@357: point. We came up with the following algorithm which gives us an
ekku@357: approximation of the normal
ekku@357:
ekku@357: \begin{enumerate}
ekku@357: \item Take the collision point ($p_1$) and the point
ekku@357: before the collision ($p_2$) and consider a 3x3 array of pixels which has the
ekku@357: collision point in the middle of it.
ekku@357: \item Find the empty points that are connected to $p_2$ with bredth-first search algorithm
ekku@357: or something similar.
ekku@357: \item Calculate the vectors pointing from the collision point to the empty points. Sum of these vectors gives us the approximation of the normal.
ekku@357: \end{enumerate}
ekku@357:
ekku@357: Picture \ref{algo} explains the algorithm a lot. In the middle of the pictured we have zoomed to the collision point. The red arrow is the sum of the black arrows and thus it is our approximation for the normal.
ekku@349:
ekku@349: \begin{figure}[!ht]
terom@362: \centering \includegraphics[width=0.7\textwidth]{images/normaali_algo.png}
ekku@349: \caption{Visualizing the algorithm for approximating the normal. \label{algo}}
ekku@349: \end{figure}
ekku@269:
ekku@269: \subsection{Physics}
saiam@310:
saiam@310: The fourth-order Runge-Kutta method is a numerical method for
saiam@310: approximating the solution of an ordinary differential equation. In
saiam@310: our game the Runge-Kutta method is used to calculate positions and
saiam@310: velocities of physics objects when we apply forces to them.
saiam@310:
saiam@331: The mathematical formulation of the Runge-Kutta method:
saiam@331: If we have an initial value problem of the form
saiam@333: \begin{equation*}
saiam@331: y' = f(t, y), \quad y(t_0) = y_0.
saiam@333: \end{equation*}
saiam@331: The we can describe the RK4 method for this problem by equations
saiam@333: \begin{align*}
saiam@331: y_{n+1} &= y_n + \tfrac{1}{6}h\left(k_1 + 2k_2 + 2k_3 + k_4 \right) \\
saiam@331: t_{n+1} &= t_n + h \\
saiam@333: \end{align*}
saiam@331: where $y_{n+1}$ is the RK4 approximation of $y(t_{n+1})$, and
saiam@333: \begin{align*}
saiam@331: k_1 &= f(t_n, y_n) \\
saiam@331: k_2 &= f(t_n + \tfrac{1}{2}h, y_n + \tfrac{1}{2}h k_1) \\
saiam@331: k_3 &= f(t_n + \tfrac{1}{2}h, y_n + \tfrac{1}{2}h k_2) \\
saiam@331: k_4 &= f(t_n + h, y_n + h k_3) \\
saiam@333: \end{align*}
saiam@331: The next value $(y_n+1)$ is determined by the present value $(y_n)$,
saiam@331: the product of the interval $(h)$ and an estimated slope that is
saiam@331: defined as $\frac{1}{6}h\left(k_1+2k_2+2k_3+k_4\right)$.
saiam@310:
ekku@357: \subsection{Texture generation}
ekku@357:
ekku@357: In texture generation we use random fractal terrain generation algorithm \cite{fractal}.
ekku@357: In one dimension algorithm starts with one straight line. Then it affects to that
ekku@357: line's midpoint with some random multiplied with H-value. After those first steps
ekku@357: it simply calls itself to both parts of line changing H-value smaller.
ekku@357:
ekku@357: We use the two dimensional version of the algorithm. It needs step for point's between
ekku@357: last density and step diagonal to last density's points -- otherwise it is quite similar
ekku@357: to the one dimensional version. We decide to implement the algorithm as iterative instead of recursion.
ekku@357:
terom@352: \subsection{Input}
terom@352:
terom@352: All input is represented as a bitmask, composed of bits from \textsl{enum PlayerInputBits}. Additionally, the Graphics
terom@352: code uses some local-only flags defined in \textsl{enum GuiInputBits}. These bitmasks are built using the
terom@352: \textsl{InputHandler} class, which is defined as a generic template. On every update, it goes through its keymap,
terom@352: which is defined as an array of InputKeymapEntry structs. These contain the input bit, flags, and up to two keycodes.
terom@352: The InputHandler then reads keycodes from the keyboard and sets bits in the current input mask based on the entry
terom@352: keycodes (which may be negative to indicate the the specified key must NOT be pressed down) and any key-repetition
terom@352: rules defined by flags.
terom@352:
terom@352: Key repetition is implemented using InputKeyRepeatQueue, which contains a list of InputKeyRepeatEntry's. To rate-limit
terom@352: keypresses, the input code is push()'d to the queue, and it eventually removed from the queue once it expires, or
terom@352: the key is released.
terom@352:
terom@352: The Graphics code then reads the input mask from time to time, resetting the InputHandler's mask to zero, and passes
terom@352: it on to LocalPlayer, which then either handles it locally, or sends it to the remote server.
terom@352:
terom@352: Since some inputs like walking, aiming and moving up and down the rope are time-dependant, the InputHandler also tracks
terom@352: how many milliseconds the input mask has been held, and this time delta is applied by LocalPlayer.
terom@352:
saiam@238: \section{Known bugs}
saiam@340: \begin{enumerate}
saiam@340: \item If player dies while rope is attached the rope will still be
terom@352: attached when the player spawns.
nireco@345: \item If rope is thrown without releasing it first, rope will pull worm
terom@355: while in mid-air
ekku@357: \item Collisions with the terrain are only tested for the vertices
ekku@357: of the polygon. It is thus possible for the player to move through some pixels.
terom@352: \item Existing Player ropes and Projectiles are not sent to the client when it connects, which can cause apparent
terom@352: glitches in what the terrain looks like and how players move.
saiam@340: \end{enumerate}
saiam@238:
saiam@238: \section{Tasks sharing and schedule}
terom@352: We could have followed the schedule a lot better. We basically forgot the whole
terom@352: schedule and had a lapse in activity during the middle weeks, which caused us
terom@352: to be delayed in terms of the schedule. The positive side was that we almost
terom@352: always had all the team members working on their own things in parralel and
terom@352: communicating together; either at Maari or using our IRC channel.
saiam@340:
saiam@340: Tasks sharing worked pretty much as planned. Tero did all the network
terom@352: code and worked on keeping the rest of the code network-safe. Most of our
terom@352: eye-candy (like terrain textures) was done by Marko, who was responsible for
terom@352: the graphics. Marko, Eric and Atle worked on everything Physics related plus
terom@352: the GameState/Player/Rope/etc code. Most of the time all team members were
terom@352: working together, so the code was written using common agreement.
saiam@340:
terom@352: We feel that the workload was shared reasonably evenly. % Or does someone disagree with this?
saiam@238:
saiam@238: \section{Differences to the original plan}
saiam@340: The original plan was quite loose and it let us make decisions during
terom@352: development, which was a good thing. The basic structure of the program is
terom@352: pretty much as the one we thought about while planning, although the Network
terom@352: code ended up being a fair bit more simple-minded due to lack of time to
terom@352: implement more UDP-based behaviour.
saiam@238:
ekku@361: Currently, the program lacks an AI which was in the original plan and most of the optional features were also omitted. Liero clone proved to be, however, a very interesting software project and we are hoping to release Kishna Glista 2.0 one day.
ekku@361:
saiam@238: % References
saiam@238: \begin{thebibliography}{99}
ekku@361: \bibitem{gaffer} Gaffer on games. Game Physics. 2006. \\
ekku@361: http://gafferongames.wordpress.com/game-physics/ (read: 2008-12-08)
ekku@363: \bibitem{fractal} Terrain texture generation \\
ekku@363: http://www.gameprogrammer.com/fractal.html
saiam@238: \end{thebibliography}
saiam@238:
saiam@238: \end{document}