In top down design we decide what the end product is to be and then break it into a set of small easy to understand, bite sized pieces. The first step is of course to figure out what Yerushalem is going to do. Next the project is subdivided into broad areas such as user interface, networking, and etcetera. Thought is given to how all these interact. Then each is digested into smaller and smaller units until we reach the level of each unit representing a C++ method. At various points along the way, depending on how large a unit is, units get delegated out to others to flesh out.
This section is for fleshing out what we want Yerushalem to do.
This is for nebulous, general goals.
This is where easy to define goals go.
This section pins down what we need in order to meet the goals above.
This section pins down how we meet our requirments.
YERUSHALEM
game-structure
========
CONTENTS
========
1. STRUCTURAL OVERVIEW
1.1 SERVER (sv)
1.2 CLIENT (cg)
1.3 WORLD (world)
1.4 sv/cg CONNECTION (ServerCon / ClientCon)
1. STRUCTURAL OVERVIEW
=======================
There are over all 4 classes that interact with each other:
CLIENT is the client interface
SERVER is the server that handles all client-interactions and sends them to the clients
and his WORLD. sending data is done through the CONNECTION, where the client only has
a single one, while the server has one for each client.
this structure makes it possible to have a client and a server running simultaneously on
one machine: the connection will not be a network-connection but possibly something like a
simple socket or even just a function-call. the world will be the same for both sides.
because of this the client has to check if he is a pure client or a server-client-combo.
if he is a combo, he possibly won't be allowed to call the world to update itself (as the
server does this)
DATA FLOW
=========
CLIENTS SIDE SERVERS SIDE
---------- ----------
| CLIENT | XXXXXXXXXXXXXXXXXXXXXXXXX | SERVER |
------------------------------------------------------------------
| X sv/cg CONNECTION X |
-----------------------------------------------------------------
| WORLD | XXXXXXXXXXXXXXXXXXXXXXXXX | WORLD |
--------- ---------
data will flow -
- from clients to the server (containing all clients interactions)
- from the server to the servers world (changing the world due to clients
interactions
- from the servers world to the clients world
(updating the clients status or requesting new sector-data)
- from the clients world to the client (e.g. when looking which interactions
are possible)
- from the server to the client (global messages (chatting) may be exchanged that way)
1.1 SERVER (sv)
the main server-loop will look something like this
{
- retrieve player-state information from clients
- take care for players that want to (dis)connect.
- call the world so it cares for itself
- loop through all DYNAMIC ENTITIES 'ent' {
- look if ent needs to change due to a client-event,
timer or other events
(!! BUT NOT DUE TO SPEED OR FORCE as the world does this itself !!)
if so, change its status and mark it.
}
- all marked objects` status need to be send to the clients.
(in form of a 'game-snapshot' which would be a complete status
- or in form of single entity-specific information)
- if there is a request to stop the game, break.
}
for easier handling lets call one iteration of this loop a single cycle.
be aware that events happening in the same cycle will possibly not
affect each other the way we think, as we go through the virtual world
entity for entity. interactions of separate events will possibly
need a multiple of 1 cycle. but that shouldn't be too bad, as in
one second the server should (hopefully) calculate hundreds of cycles.
1.2 CLIENT (cg)
due to the event-driven character of the engine, the client-structure needs to
look different:
when using CS to render grafics, we need to enter the csDefaultRunLoop().
due to this, we have to use the EventHandler.
=============================================================================================
bool Client::csEventHandler(iEvent& ev)
/** this function is our major entry from CS
* for a list of all predefined events, look CS/include/iutil/evdefs.h
* for a little description on event, look the CS/docs/html
* especially 5.2.4, which talks about cscmd***Process
*/
{
if((ev.Type == csevKeyDown) || (ev.Type == csevKeyUp)) {
/// here we can easily handle keypresses and
/// possibly UI-events
self.ProcessClientInteractions();
return true;
} else
if(ev.Type == csevBroadcast) /// cscmdXX are broadcasted
if(ev.Command.Code == cscmdPreProcess) {
/// here we can communicate with the sv
Connection.Interact()
/// or something like that
/// and ask the world to recalculate itself
World.Calc();
return true;
} else if(ev.Command.Code == cscmdProcess) {
Self.SetupFrame();
return true;
} else if(ev.Command.Code == cscmdPostProcess) {
/// add a HUD onto the image
self.DrawHUD();
return true;
} else if(ev.Command.Code == cscmdFinalProcess) {
self.FinishFrame();
return true;
} else return false; /// event wasn't handled
}
=============================================================================================
someone may argue that if the client is structured this event-driven way, it may also
be better to structure the server that way too. i'm not sure, yet, but i think we
can save at least a bit of overhead that way. i am also not sure if we must use
the csDefaultRunLoop() to work with registry and other CS-internal data. if so, we
must use the event-handler on the servers side, too.
1.3 WORLD (world)
as on both sides (the server and the client-side) movements and the effect of forces can
be calculated, we can do it on both sides to reduce network-traffic and packet-parsing.
WORLD will be a class holding all entities. we can easily replace the above mentioned tree
of all entities with the world. furthermore the world will calculate continuous changes of
itself (like a bullet flying with a constant speed in a constant direction).
WORLD will do a lot of things related to CS.
1.4 sv/cg CONNECTION (ServerCon / ClientCon)
the CONNECTION will take care for all interactions between the client and the server.
it will possibly split into a ClientConnection (the server may hold one for each
client) and a ServerConnection (one on each clients side).
YERUSHALEM
client/server architecture
========
CONTENTS
========
1.0 SECTOR-USAGE TO INCREASE PERFORMANCE
2.0 DISTRIBUTED SERVER NETWORKS
1.0 SECTOR-USAGE TO INCREASE PERFORMANCE
========================================
with CS a level is organized into sectors, which will make our work much easier.
a server (still need to think about a multi-server-network) will know the complete
world, while a client only needs to know about all entities in its immediate area.
so lets say client enters game and starts in sector F6:
(a 8x8 2-dimensional matrix)
a b c d e f g h
8 . . . . . . . .
7 . . . . . . . .
6 . . . . . c . .
5 . . . . . . . .
4 . . . . . . . .
3 . . . . . . . .
2 . . . . . . . .
1 . . . . . . . .
distance is the number of portals between two sectors
depth(n) is 0<=distance<=n. Or a distance between 0 and n inclusive.
sector_sum(n) is the sum of sectors reachable within some depth n
// much better, greg. thx
with a depth=1 he will only need to know about sectorsum(1) == e7,f7,g7,e6,f6,...
with a depth=2 he will also need to know about sectors e8,f8,h8,..
the higher the depth, the more information need to be exchanged with the server
and the more things need to be rendered
keeping the depth low will reduce network-traffic and increase
rendering performance.
BUT: for a small depth: the smaller the depth, the less realistic the game will
be, as events happening in the other sectors (like sounds, explosions, quakes whatever)
will not affect the player if the distance is too high.
so sector-width and/or depth should be chosen according to this.
/*
Gregged.
Perhaps depths could vary according to event type? For example an earth quake may need to
be distributed across many sectors, while a conversation between two people would possibly
stay in the sector of origin. Maybe the event object could have a varible associated with it.
As the event passes from sector(or server?) to sector(or server) the var--'s. Once var==0 it
dies on it's server.
*/
/* fleshyCPU
first i thought a client connected to a server would request data of specific sectors, but
that doesn't actually make a lot of sence. you are right. it will be much more effective
if the server sends event-data according to the position of the clients.
/*
2.0 DISTRIBUTED SERVER NETWORKS
===============================
possibly this will also be the key to a distributed server-network:
a b c d e f g h
8 a a a a b b b b
7 a a a a b b b b
6 a a a a b b b b
5 a a a a b b b b
4 c c c c d d d d
3 c c c c d d d d
2 c c c c d d d d
1 c c c c d d d d
this would be a possibility for a distributed server-network. we have 8x8 sectors and
4 game-server. each of them takes care for 4x4 of them. a client only would have to
contact multiple servers while being near a border. the servers themselves would only
need to exchange game-wide information like the playerinfos or the global game-status
(if someone has won etc) or if a single object moves from one sector into another,
where another server is reliable. this would reduce traffic to a minimum.
/*Gregged:
I like this concept. I'd add to it the possibility to vary depth by event type (which is alluded to
here) and use priority queques.
*/
YERUSHALEM
entity architecture
========
CONTENTS
========
1.0 INTERNAL ORGANISATION OF ENTITIES
1.1 IN CRYSTAL SPACE
1.2 OPTIMIZATION HOLDING ENTITIES IN A TREE
2.0 ENTITY PROPERTIES / FUNCTIONS
1.0 INTERNAL ORGANISATION OF ENTITIES
=====================================
1.1 IN CRYSTAL SPACE
entities should be organized - as CS says - with CS-internal structures
(see docs/html/* chapter 5.7.9: Attaching User Objects to CS Objects).
name of the interface should be iEntity.
this class iEntity could have an embedded interface iActions,
that holds all actions that a player can do with it.
possibly a single action would be defined something like this:
struct Action_s
{
const char* name;
const (void*)(do)(void*);
}
but for optimization purposes we should possibly use a global
table that holds all available actions someone can to. if
someone wants to act, we will look in the table, what
his character can do with the specific object he wants to act on.
1.2 OPTIMIZATION HOLDING ENTITIES IN A TREE
this way all objects will be in CS`s
object-registry (struct iObjectRegistry) and we don't have to create
our own container for them
/* fleshyCPU:
using this method it would be necessary to retrieve a complete
list of entities each cycle and free() or DecRef() it at the end of the
cycle. this is very ineffective.
in the beginning we should retrieve a list from the object registry (WITHOUT o->IncRef())
and from this we build a tree sorted by sectors containing all entities. this will also help
during collision detection: as we only have to check for collisions in sector-bounds we can
use this reduced entity-list.
when an entity is destroyed, we will remove it from our tree and o->DecRef() it out of the
object-registry.
another question is: should we handle static and dynamic entities in the same way?
(static entities will be things that can not change during the complete game, like
walls etc [destroying walls can be done using portals] ; dynamic entities are objects
that may interact).
at the second level of the tree we could split between dynamic and static entities
and thus when iterating through all entities we only need to check the dynamic ones.
*/
2.0 ENTITY PROPERTIES / FUNCTIONS
=================================
specific objects like the players should be able to 'hold' or contain
other objects. for example a player can hold a weapon.
the player could have a specific ability to hold a special number
or special 'weight' of objects.
should we take care of objects specially-believing
people would't touch/carry?
/*Gregged:
If an object can be touched or can be carried, it should be possible. It could become a
memory problem though. Imagine this situation: Player throws rock at glass window. Window
shatters, glass shards fall to ground. Player picks up a shard and slashes another player's
throat. Now perhaps we could make the window a fairly normal CS thing. But when player hits it
with rock, the engine reads additional properties from a file, finds out glass shatters, that
it makes a usable weapon (although cuts into users hand) etc. Once the glass is no longer
being used, these extra properties are freed from RAM.
*/
/* fleshyCPU
such an object could contain the action 'break',
which would window->DecRef() (destroy the window) and spawn() new glass falling down, destroying
itself after a period of time being not used. once it was used this timer should be reactivated
when the user drops it. this glass needs to be of different material, though, as it needs further
actions like 'useShortRangeWeapon' 'useThrow' etc, while the former material/glass would only
have a single action 'useBreak' or 'useDestroy'.
*/
skeleton-based movent
for a description of the human skeleton, see various books
or arch.skeleton.gif (one of you english guys may write down
the english names for the various bones).
if we want that our characters perform realistic movements,
we can choose between different levels of authenticity:
the less realistic, the higher will the performance be.
-the most realistic version one can choose is to simulate every bone
and every muscle, but thats of course impossible for us.
but this version gives us the possibility to create descriptive bodies,
we could generate skeletons based upon descriptions like
"20 year old 1.80 meter high, male, ..."
-the most efficient would be to use animated models, as games
like quake and half-life and most others do. but using this way
we would have to generate different models for different looking
people (except the skinning)
i don't know if this will be possibly with the performace someone may
have:
we will reduce the skeleton to a number of important parts and
create basic physical formulas to set up the correlation between them.
using these formulas we can calculate movements and animations.
a client 'C' will possibly consinst of an entity 'CE'. each bone-part B may be
an entity 'BE' itself, as a child of the client:
CE { BE (skull)
BE (arm)
BE (leg-bone)
...
}
for animation the client may have a seperate embedded interface iMovements
(or iAnimations or...) which will hold functions of different complexity:
go (direction, distance)
step (direction, length)
move_lower_leg (angle)
move_higher_leg (angle)
move_foot (angle)
...
this way we can simulate a lot of activities, like a person just going
somewhere or kicks, grabbing a thing, interactions between people
(like: one person giving a thing to anotherone, as the hand
may hold an entity). furthermore we can use realistic collision-detection
for the body and create hit-zones.
the question then is:
how do we wrap the skin above these entities?
hopefully someone of you has an idea.