3/04 |
4/04 |
<-Open Closed->
Last Free Code |
5/04 |
6/04 |
Most Recent
1 April '04
- Today is the day. I swear it. I WILL get this frigging thing working both sides. So.. moved all vars from
the PC to the Pawn. That made things a lot easier, cos now only the pawn holds gesture related variables. Good.
The mutator assigns everything through checkReplacement(), since this way is much
much less annoying. Good. Added some checks for bots, and stopped giving them the gesture gun so there aren't
any more annoying warning messages. Good. Now we move on..
- Well, I got stuck / bored again, so instead i fiddled with the camera on the sword. It has some interpolation
on it now, and looks pretty cool. However, i can't take much credit since pretty much the entire code block was
ripped from the KVehicle class.
- OH I ALMOST FORGOT!! - I made a dumb little mutator when i lost interest in this. It makes onslaught
pretty awesome fun. HERE - no wait. here.
4 April '04
- Well, i've been pretty inactive the last few days with this thing, but now it's time for a *HUGE* update. And guess
what? Absolutely EVERYTHING works properly with gesture effects now. I'm not even kidding. I'll try to go over that code
but there will doubtlessly be parts i don't really remember. There's a link to the full source at the bottom of today's
post.
The most important change readability-wise i guess is that all the gesture functionality has been moved over to its own
class. This class is your standard inventory extension, loaded up through the pawn by the mutator. In other words, the
mutator says:
function ModifyPlayer(Pawn Other)
{
Other.GiveWeapon("grabin.sword");
if (Other.Controller.isA('grabinController')) //no bot gestures yet
{
Other.GiveWeapon("grabin.grabinGestureGun");
grabinPawn(Other).GiveGesture("grabin.gestureInventory");
}
}
paying close attention to the GiveGesture() call here. This function exists in the pawn class
and does the following:
static function GiveGesture(string aClassName )
{
local class<gestureInventory> InvClass;
InvClass = class<gestureInventory>(DynamicLoadObject(aClassName, class'Class'));
if( FindInventoryType(InvClass) != None )
return;
GestureInv = Spawn(InvClass);
if( GestureInv != None )
{
GestureInv.GiveTo(self);
GestureInv.P = self; //assignment so that inv class can reference back easily
GestureInv.SetupGesture();
}
}
Basically all that stuff just lets me spawn whatever the hell i want and have it attached automatically to the pawn. The
pawn's replication block becomes extremely important now, as this function must execute client-side only to not cause
problems. To make it a replicated function, all we need is:
reliable if (ROLE == ROLE_Authority)
giveGesture;
Now the most important class of all, the inventory class which handles all gesturing. It's basically just full of the functions
I had lying around everywhere before in the Controller and Pawn classes, but there is one very important function when it
comes to detecting the correct interaction...
simulated function getInteraction()
{
local PlayerController PCn;
local int i; //iterator
foreach DynamicActors(class'PlayerController', PCn)
{
if ( Viewport(PCn.Player) != None )
{
for (i = 0; i < PCn.Player.LocalInteractions.Length; i++)
{
if ( PCn.Player.LocalInteractions[i].IsA('gestureInter') )
gInteraction = PCn.Player.LocalInteractions[i];
}
}
}
}
I think its fairly self-explanatory how that works- iterate through all actors until you find the right PC, get its player
and make sure it is indeed, the current viewport (which as far as i can tell means the thing the player is looking through),
then iterate through all its interactions until you find the right one. Assign that and everything should be ok. This algorithm
is pretty crappy really when you think about complexity.. probably (O)n� or whatever, i can't remember how it works.. BUT
it doesnt really matter since this function need only be executed once.
The next thing i had to do was move all the functions to actually draw gesturing effects over to a
PlayerReplicationInfo class. I think the problem there was something to do with replication
inheritance.. since beforehand i was trying to call the functions from a timer, and they were both in the same class.
I think the timer's replication (none at all, i mean whod want messages being sent every tick?) overrode any other replication
i layed down for each of the effect drawing functions. Anyway, i moved them to a PRI and just call them now, and everything
seems to work just fine. I will have to smooth things out a little later though, maybe have a buffer of places to spawn
the emitters which gets passed to the server every now and again, because right now client effects are much less full-bodied
than server ones due to lag. And that is, in a nutshell, all that was needed to make the gesture effects show up properly
for everyone.
- What else? Ummm.. The sword deals damage properly now and has a lot of recoil.. i think that's about all for today really (:
Full source code for this stage.
5 April '04
- The sword now has a proper crosshair, which is implemented as an Emitter following the player's gaze by a simple trace.
6 April '04
- I haven't been doing much lately, I must admit. The next thing i do will either be to get some gesture recognition
implemented or do the proper sword animations. Animating with code impulses is definitely not recommended. I will
get to these things in the next few days maybe, but I have a lot of assignments due so probably not. Also, I have to get
drunk on my birthday. yes.
In the meantime, here's something to keep you amused -- Linkgun Grappling Hook.
13 April '04
- Started coding the gesture calculation functions into gestureInventory and grabinPRI. If you haven't worked
it out yet, gestureInventory contains all the client-side code and the PRI contains the server-side stuff.
This is a pretty handy way of doing things, i think, because all the hard computation can be done by clients
and then passed to the server when something BIG (yet low on computation) needs to happen
(ie the gesture has some effect).
- To fix sounds not playing properly, i moved those functions over to the PRI class so all players can hear them.
One function to play a release sound (which i may extend later to play different sounds for each gesture type),
and one to play the ambient sound of a gesture happening:
simulated function playRelease(pawn Other)
{ //chooses a casting sound and plays it
Other.PlaySound(Sound'gestureFX.cast', SLOT_None,255,,128,,true);
}
simulated function playAmbient(pawn Other, bool on)
{ //sets the ambient effect for gesturing
if (on)
Other.AmbientSound = Sound'gestureFX.gesture';
else
Other.AmbientSound = None;
}
- Now comes perhaps the most important coding step yet.
I should explain the mechanics of my gesture recognition algorithm so that others will know how to implement
it in other object classes, so that external entities in custom maps or whatever can be controlled by gestures.
I'm going to copy some documentation from the gesturing state first, the state which drives all gesture calculation
and does the calls for the effect display. To explain a little about states:
States are an important part of game programming and really make many
things much easier. Basically, an object in state 'x' only executes code from 'x', not from any other states. This
means that an object can have completely different behaviour in different states. In most games, 'walking' and
'swimming', for example, are two different player states. The Unreal engine has some really powerful state
functions, such as beginState() and endState(), which
funnily enough execute only when the state is entered and exited. There are also functions to check what state
an actor is in and change its state. I'll explain later anyway. The 'gesturing' state i have created in
gestureInventory:
//====================================================================
// is responsible for calculating the gesture ID array to
// be passed to the PRI to see what happens. the array generated
// takes this form:
// INDEX NUMBER
// 0 0 (zero indicates first pen down)
// 1 First directional number
// ... Second one... etc
// ... 0 This means the 'pen' was lifted and placed again
// ... More numbers for the next shape drawn
// Length-1 Last number in last gesture
//====================================================================
As you can see, I have implemented the gesture ID number as an array of integer. It works like this:
// rotator notes:
// 8192 == 45 degrees
// TOLERANCES:
// 1 61441 to 65535
// && 0 to 4095
// 2 4096 to 12288
// 3 12289 to 20480
// 4 20481 to 28672 6 7 8
// 5 28673 to 32768 \|/
// && -32678 to -28673 5 --+-- 1
// 6 -20481 to -28672 /|\
// 7 -12289 to -20480 4 3 2
// 8 -4096 to -12288
What this means is that when the player draws with the mouse, numbers are generated and passed to the 'gestureID'
array based on the angle between the mouse position and the previous mouse position. The little compass diagram
shows which numbers are generated at which angle. The list of numbers next to it is the tolerance in degrees
for each number (about 22.5 degrees each side of perfect, for a 45 degree angle tolerance. The large numbers are
because Unreal calculates angles with a 65535 unit circle, instead of 360). At this stage, gesture testing seems
to work pretty damn well. Curved shapes are even possible - a full clockwise circle starting at 12 o'clock gives the
array [0,1,2,3,4,5,6,7,8,1]. That said, however, it may be tweaked later on.
Now, on to the code that does all this!
The timer() function in the inventory is now a little different, and relies on the
calculation state for some of its elements.
simulated function timer()
{
.......
if(cClick)
{
grabinPRI(PC.PlayerReplicationInfo).playAmbient(P, true);
//play sndFX
drawing();
if (!cDraw)
{
grabinPRI(PC.PlayerReplicationInfo).drawCursor(gEffect,
spawnTo, playerCamLoc);
//only draw cursor when not actually drawing shapes
gotoState(''); //this stops calculation
}
} else {
grabinPRI(PC.PlayerReplicationInfo).playAmbient(P, false);
gotoState(''); //this stops calculation
if (gestureID.Length != 0)
{ //send gestureID, then purge out the old gesture buffer
gestureID.Remove(0, gestureID.Length);
gestureID.Length = 0;
}
}
if (cDraw)
{
if (!isinState('gesturing'))
gotoState('gesturing');
//handle gesture calculation and effect
}
.......
}
As you can see, it changes the inventory actor in and out of its 'gesturing' state
(gotoState(''); returns it to the default empty state).
It also clears out the 'gestureID' array when the right mouse button is released so that it is
ready for a new gesture next time. Note the calls to the PRI for playAmbient()
and playRelease() (not shown).
Now for the gesturing state itself.
state gesturing
{
simulated function BeginState()
{
firstTime = true;
//get tolerance of mouse movement
tolerance = calcTolerance();
}
simulated function Tick(float DeltaTime)
{
//vector calculation variables
local vector newVec, diffVec;
local rotator angleBetween;
local float diffSize;
grabinPRI(PC.PlayerReplicationInfo).drawEffect(hEffect, spawnTo, playerCamLoc);
//draw effect onscreen
if (firstTime)
{
//return values to defaults
prevVec.X = PC.Player.WindowsMouseX;
prevVec.Y = PC.Player.WindowsMouseY;
prevVec.Z = 0;
diffVec = vect(0,0,0);
//add a blank to the end of the array for pen down
gestureID.insert(gestureID.Length, 1);
gestureID[gestureID.Length - 1] = 0;
firstTime = false;
}
newVec.X = PC.Player.WindowsMouseX;
newVec.Y = PC.Player.WindowsMouseY;
newVec.Z = 0;
//set up the vector holding mouse location
diffSize = VSize(newVec - prevVec);
if (diffSize > tolerance)
{
//if distance is above tolerance change diff vector and reset prevVec
diffVec = newVec - prevVec;
prevVec = newVec;
//find angle between 2 vectors and compute
angleBetween = rotator(normal(diffVec));
computeOutput(angleBetween);
}
}
simulated function EndState()
{
}
function computeOutput(rotator angle)
{
local int newDir; //this is the new integer to add to array
local int Y; //angle's Y val, makes things easier
Y = angle.Yaw;
if ( 12288 >= Y && Y >= 4096 )
newDir = 2;
else if ( 20480 >= Y && Y >= 12289 )
newDir = 3;
else if ( 28672 >= Y && Y >= 20481 )
newDir = 4;
else if ( (32768 >= Y && Y >= 28673) || (-32768 <= Y && Y <= -28673) )
newDir = 5;
else if ( -28672 <= Y && Y <= -20481 )
newDir = 6;
else if ( -20480 <= Y && Y <= -12289 )
newDir = 7;
else if ( -12288 <= Y && Y <= -4096 )
newDir = 8;
else
newDir = 1; //find out which direction we draw in
if ( gestureID[gestureID.Length-1] != newDir )
{ //if direction has changed
gestureID.insert(gestureID.Length, 1); //add an element
gestureID[gestureID.Length - 1] = newDir; //fill the new element
}
}
function float calcTolerance()
{
local string CurrentRes;
//the screen resolution check for per-pixel tolerance calc
CurrentRes = PC.ConsoleCommand("GETCURRENTRES");
switch (CurrentRes)
{
case "320x240":
tolerance = 24;
break;
case "512x384":
tolerance = 38;
break;
case "640x480":
tolerance = 48;
break;
//larger resolutions can be the same tolerance
default:
tolerance = 50;
break;
}
return tolerance;
}
}
So, To examine the state flow. When it begins, it sets a global boolean, 'firstTime', to true. This simply tells us that
the state has just been entered, that the previous mouse location must be set to the current one, and that all the other
calculating variables must be reset. It also calls the state function calcTolerance().
This function is important for working out the allowance in mouse movement before a new integer is computed and passed
to the 'gestureID' array. Since the mouse pos is calculated in pixels, lower resolutions need a smaller tolerance so they
can draw the same complexity shapes. I decided that any resolution above 640x480 could use a 50 pixel tolerance range,
but i may have to change that after more beta testing.
After beginState(), the program jumps to its Tick(). If I haven't
explained before, Tick is a slightly different function than timer
in Unreal, as it is executed every processor tick in the game rather than at set times. I think. Anyway,
Tick() first draws the effect, then sets all its values back to default as explained before
if it is the beginning of a new state execution (remember, after each shape is drawn, the state is exited and re-entered).
Then it works out the current mouse position and checks if the difference between that position and the last mouse
position is greater than the tolerance calculated from calcTolerance(). If it isn't,
the player hasn't moved the mouse far enough yet so nothing further need happen. If it is, it calculates the angle
between the two points and updates the previous mouse position to the current one. It then calls
computeOutput().
This function simply pushes an integer onto the 'gestureID' array based on the angle between the 2 points. You may wonder
at the negative values here, well, it was something that caused me some problems. Turns out that after 180 degrees, Unreal
starts giving the rotation between the 2 points back in the opposite direction, you know, in order to get the smallest
rotation between them. Took me a while to work that out.. I should talk about a useful debugging tip for interactively
updated values.
It's the trusty HUD. Yep, that classic class thats oh-so very handy for showing you everything you need to know right
in front of your face. HUDs have a function in them called drawHud(), which takes a
canvas, 'C', as a parameter. What you can do is make your own custom HUD that extends HudBDeathMatch, and overload
the drawHud() method to give you some useful debugging info. It's really easy to do, too.
Just copy my readymade code.
local color tempColor;
local byte tempstyle;
tempColor = c.DrawColor;
tempstyle = c.Style;
c.DrawColor=WhiteColor;
c.Style=ERenderStyle.STY_Additive;
DrawDebug(c);
c.DrawColor = tempColor;//restore values
c.Style = tempstyle;
You may want to include a call to the superclasses method at the bottom if you still need the rest of the HUD. You can
do that by simply adding Super.drawHud(C). All you need in your
drawDebug() method are the lines
c.SetPos(Xpos,Ypos);
c.DrawText(YOUR VARIABLE TO TEST, true);
Where Xpos and Ypos are X and Y locations in pixels from the top left of the screen and your variable to test
is any variable which will be automatically casted to a string. I know this seems like a really stupidly simple thing,
but its good to draw attention to it since it can be so damn useful. Oh, I should add that HUDs have handy
'pawnOwner' and 'playerOwner' vars which return the pawn and PC which own them, respectively.
- Also, I moved the gesture effect back to the player's face again. I figure slower movement speed will make its
visibility issues while running forward much better.
14 April '04
- Tried to put if(!playerController(Controller).bBehindView) return false; at the start of
grabinPawn's specialCalcView so that it only does it in behindview. Also had to add
bSpecialCalcView=True to defaults. It didn't work. AND THEN...
- Gesturing works the whole way! I've got a gesture generating rockets.. at the map center cos I haven't done any
aiming code yet, but hey! Success! I think it might need refining in the computation algorithm though, cos it
seems a bit sensitive about the whole thing.
- SUCCESS!! delicious, wonderful success. Haven't bothered tweaking the recognition
yet, but I know how to do it so it's ok. I have made a gesture (backwards C from bottom up) which fires a standard
UT rocket. Simple and testiful. Basically, I copied a heap of variables and the doFireEffect()
(with supporting classes) over from projectileFire and modded it all to suit my needs. It's very handy cos all I
have to do to make new projectile - type attacks is reset the vars each time and change which class the projectile
should be. It takes care of the rest. I can see myself copying over the ones for instantFires and all that, as well
as adding some original functions to change other non-weapon things. It will all be waay too easy. I also looked into
the possibility of having configurable strings for each gesture type, so that people can set the game up differently
if they want to. It shouldn't be too hard, but I think i won't do it because people would be dicks and set everything
to a straight line.
Interpreting the array from gestureInventory proved to be seriously easy:
simulated function interp(array<int> gestureID)
{
local int i;
local string strID;
//this is kind of dumb, but better accessible. It stores the ID array
// as an easy, non-associative datatype. Stops us needing many iterators.
for ( i=0; i < gestureID.Length; i++ )
{ //converts to string
strID = strID $ gestureID[i];
}
debugStr = strID; //FOR DEBUGGING
setDefProj();
if ( strID == fireballStr )
fireBall();
}
That's it! All the hard work is already done in the inventory. setDefProj()
just resets all the projectile's settings (spread, number, damage mult and offset) so we don't have to do
it each time. Then we just have REALLY simple functions for each attack. For my test attack:
function fireBall()
{
ProjectileClass = Class'XWeapons.RocketProj';
ProjSpawnOffset.X = 50.000;
DoProjectile();
}
Oh, and the 'fireballStr' from the interp function is nice and easy, and it sits down at the bottom in
defaultproperties. Too. Friggin. Easy. And that's all for today!
15 April '04
- Set up a temporary HUD and also added in 'held gesture' functionality. Basically, some attacks need to be
'held' for a while before the player uses them. If you had an instant-effect rocket, for example, it would
be rather difficult to avoid blowing yourself up, particularly when you can't rotate your view while in
gesture mode. The HUD now brings up a crosshair when a held gesture happens, and also a little countdown
timer which tells you how much time you have left before your gesture expires, and hence before you have
to redraw it. Left-clicking fires a held gesture, and right-clicking automatically cancels it before attempting
to draw another. To do this, I made a new state in the PRI:
state holdProjAttack
{ //hold the projectile attack until the user clicks again or 5 seconds
simulated function beginState()
{
countDown = holdTime;
setTimer(1.0,true);
}
simulated function timer()
{
countDown--;
if (countDown <= 0)
gotoState('');
}
simulated function endState()
{
if ( grabinPawn(Controller(Owner).Pawn).GestureInv.cRelease )
DoProjectile();
grabinPawn(Controller(Owner).Pawn).GestureInv.cRelease = false;
countDown = holdTime+1;
}
}
This makes things easier for future expansion, as any projectile gesture can just call this state for it to
work. Of course, I'll need to add more similar states for other gestures that need to be held, but most likely
there won't be many more of those. 'countDown' is sent to the HUD class along with 'holdTime' to work out the timer.
The HUD checks if countDown is greater than holdTime before rendering:
if ( grabinPRI(PlayerController(PawnOwner.Controller).PlayerReplicationInfo)
.isinState('holdProjAttack') )
{
c.SetPos( c.ClipX/2, c.ClipY/2 );
c.DrawIcon(xHair,0.5);
if (grabinPRI(PlayerController(PawnOwner.Controller).PlayerReplicationInfo).countDown
<= grabinPRI(PlayerController(PawnOwner.Controller).PlayerReplicationInfo).holdTime)
{
c.SetPos( c.ClipX/2 + 32, c.ClipY/2 + 32 );
c.DrawText(grabinPRI(PlayerController(PawnOwner.Controller)
.PlayerReplicationInfo).countDown, true);
}
}
I of course needed to add a little to the interaction to work in this stuff, but it wasn't that
complicated so I wont go into it here. And that is essentially all there is to it.
- Fixed up the pawn's view calculation code and it works perfectly. Just a few little errors there,
nothing new.
- I implemented another tolerance in the gesture recognition algorithm which has eliminated the
'round corners on straight edged shapes' bug. Now, after a certain number of game ticks (i found through
testing that 8 was a good number) the prevVec variable is set to the mouse position. This means that
it is then calculated properly from the corner of the shape. Gesturing seems to work perfectly,
and looks pretty finalised (:
- Had to move the 'holdProjAttack' state over to the inventory class to get it to replicate properly.
There were a lot of horrible replication issues getting the PRI (which exists serverside) to communicate
with the inventory (which exists clientside, i think). But i got there without too much fuss. Grabin once
again works on LAN play.
- Augmented the HUD so that the crosshair looks like a little hand, and fades gently away when you fire
a gesture out.
- Changed the test projectile into the gasbag firemode so it looks more like a fireball (:
- Added some random projectile classes to test with (gasbag fireball, krall bolts, Juggernaught rocket and
instant-hit sentinel laser) and started some serious LAN action! First alpha test screenshots coming soon!!
Well, I think that about concludes the open-source part of the project. We have a working gesture system which
recognises player input without a hitch and the ability to easily expand it to create different effects and attacks
extremely easily. We've learnt a lot about interactions, replication, vector maths and weapon code - elements
of uScript which are understood to be particularly difficult. But now it is time to close down the see-all aspects
of this journal, for the element of surprise is still important in a mod release. More importantly though, i am
getting tired of long-winded write-ups when i could be doing more code.
As to what needs to be finalised/done, the mod's netcode needs some work- in particular it needs to cache some
gesture effect positions before sending them
to stop possible lag in that regard. The sword needs animating, and a weapon model needs to be created for the 'gesture gun'
dummy so gesturing looks cooler, and weapons need to be removed from UT's weapon selection dropdown lists. Gestures
with different outcomes than projectile weapons need to be implemented, probably really easily by new functions in
grabinPRI. The code to send the gestureID array to other world objects needs to be created and an interface between
those objects and the player decided upon. Player speed needs to be reduced to a more suitable level. The gesture
effect needs to be made more noticeable so that other players can see what people are drawing and prepare a counter to it.
Also, i cant say my code has stuck with the principles of OOP at all, in fact a lot
of it is pretty messy and uses a lot of stupid accessor functions. But the important thing we should always remember
is that it WORKS. After i fix these things, it is just a matter of designing additional gesture effects and the
levels, sounds, music etc before a Grabin release comes about.
Dont feel sad about this cessation of open-sourcedness though, i will keep updating this briefly with additions and
code changes i put into the mod. Also,
if you write to me and ask for the code i'll be more than happy to give it to you if you have a good reason for wanting it.
Here's the latest build of the mod.
18 April '04
- Set up the player speed and motion properties properly so the game is slower and more suited to its nature. Also
removed double jumping abilities.
- Removed the sword and gesture gun weapons from the UT weapon dropdown list. I was just missing some class keywords.
- Made the gesture effect a little more visible.
- Moved the gesture effect out in front of the player more and scaled it so that cursor position lines up with window position
again. It is much more visible and effective in multiplayer games now.
- Found out about the rather useful 'bCanHitOwner' property for projectiles that will be good for spawning them closer to
the player.
- Added a buffer to the gesture effect sending code so that slower connections won't die. Also, all the xEmitters spawned
are now held in an array on the PRI, which is destroyed with the PRI. Each xEmitter reduces the length of this array
when destroyed.
- Took all that buffer coding away when i realised that due to an unrealscript limitation, dynamic arrays are not replicated,
and so it doesnt exactly work.
25 April '04
- Well, not a lot has been happening here lately due to some kind of huge uni workload that really, really hurts my
free time. But, on the other hand, I have the following news:
- Fully optimised the gesture drawing effect so that is insanely smooth, low bandwith, and has a new look.
- Some ideas for a singleplayer plot are in my head now, i'll make a writeup soon. I will probably release a multiplayer
version of the mod first, then develop the singleplayer aspect of the game. It's gonna take a long time, and i want something
released sooner so that people can enjoy it.. erm, quicker.
- Got rid of the cursor effect emitter and replaced it with a HUD-type drawing crosshair. I won't talk much about these
for now, but some screenies will go up soon.
- Started work on the gravity redirection gesture and ran into some problems, but i'll look into it more later on
when i am satisfied with the new gesture look.
- Ok, im pretty satisfied. Here's a screenshot which i guess probably gives some indication of where I'm heading
with the mod right now.
3/04 |
4/04 |
<-Open Closed->
Last Free Code |
5/04 |
6/04 |
Most Recent
|