Class Player



This class is meant to hold data and functions relevant to the player. For every instance of this class there is a companion c_FighterSprite and c_PlayerStats. Of the three, c_Player serves as the control class, directing the functions of the other 2 companion classes.


AXIS_LIMIT - Used in c_Player::CheckAxis to initialize values. Should be larger than any value used in the function
EPS - a very small number, in many cases a collision will not be recorded if the result is less than this number.
RIGHT - Used to correspond indexed numbers with the cardinal directions

DATK - - Used to correspond indexed numbers with the various attacks a player can make.

Control Classes

c_FighterSprite fighter - associated class-player-sprite
c_HitBox hitbox - axis aligned hitbox, defining where the player is vulnerable to attack
c_HitBox grabBox - hitbox used for edgegrabs
c_AttkBox attackBox; - used to store the attack box of the player, if an attack is active
c_GameController gCont; - associated controller
c_PlayerStats stats; - associated class-player-stats
c_Console *console; -a pointer to the console, used in debug mode

Graphics Engine Functions

virtual HRESULT c_Player::LoadPlayer(GrEngine *eng)

Loads the player using the graphics engine. This is not defined in the base class, as it depends on which fighter the player has selected. It will be defined in derived classes.

virtual void c_Player::ReleaseResources(GrEngine* eng)

Tells the Graphics Engine to release the player's resources

virtual bool c_Player::RenderHitbox(GrEngine *eng)

Paints the player's axis aligned hitbox to the screen. Used in debug mode.

HRESULT c_Player::RenderEffects(GrEngine* eng);

Handles rendering special effects associated with the player. May return error information in event thereof.

HRESULT ProcessInput(GrEngine *eng)

Polls the gamepad, then processes input. This is phase 1 of the Main match loop

code retrieved on 8/15/2014, from prototype

Polling the controller

        HRESULT hr= eng->GetGamepadState(&gCont);
            return hr;
    }else//no connection
        gCont.connect = false;

A check to see if the controller is connected. If it is, the state is polled. else the controller is marked as disconnected and this phase is ignored

int toward=RIGHT;//for toward actions

This loads the variable 'toward' with either LEFT or RIGHT, depending on which way the player is facing

    else if(fighter.InState(fighter.CROUCH)){//crouch released

handles crouching and uncrouching

        stats.walk.gndSpeed=stats.walk.maxSpeed*gCont.lStick.x;//mult by stuck
        //because this is the quickest way to change the sign for not facing right 

handles dashing

        if(!fighter.EdgeClimb(stats.motion.pos)){ //check for edgeclimb
            if(fighter.Jump(stats.jump.tmr.Length())){//will jump, if true
                if(!fighter.airborne){//delay on non airborne jumps
                }else{//airjump w/o delay
                        stats.motion.vel.y = gCont.lStick.y*stats.jump.vel;
                        stats.motion.vel.x = gCont.lStick.x*stats.walk.maxSpeed/2;
                    else if((stats.walk.gndSpeed<-WALK_THRESH)&&(gCont.lStick.x>0))
                        stats.motion.vel.x = gCont.lStick.x*stats.walk.maxSpeed/2;
                        stats.motion.vel.x = gCont.lStick.x*stats.walk.maxSpeed;
            stats.edgegrab.flag = false; //edgeclimbed

this handles either jumping or edgeclimbing, both actions are taken by an 'UP' directional button or a D button tap.

if(!fighter.EdgeClimb(stats.motion.pos)){ //check for edgeclimb

stats.edgegrab.flag = false; //edgeclimbed

looks tricker than it is . fighter.EdgeClimb will perform an edgeclimb and return true if the player can perform an edgeclimb. It will return false if it cannot and the function will then continue to check whether a jump is valid input. If an edgeclimb was made a flag is set in class player stats


Works in the same way EdgeClimb did; returning true if a jump is possible and doing nothing if one cannot be made. If one is possible the function continues into the nest. If the fighter is not airborne, a jump will take several frames and is queued here. If a player is airborne however a jump will be immediate. After this, the lateral velocity of the player is capped to a portion of it's former value, preventing the player from drastically changing directions.

        if(fighter.EdgeRoll(stats.motion.pos)) //check for edgeclimb

Checks for an edgeroll.

The remainder of this function is pretty straightforward. A check for a guard as well as all three of the attack buttons are checked as well.

S_OK is returned; this value may be used to hold more information about the connection state of the controller.

Motion Functions

bool c_Player::CheckAxis(int iInd, SPoint origin, float dir, SPoint* plBox);


a helper function to CollisionDetect(). This uses the Separating Axis theorem. The axis is defined by origin and dir. plBox holds a player's axis-aligned hitbox. iInd selects the index of the desired polygon to check. The diagram to the left visually shows what these parameters represent.

CheckAxis will then project these shapes onto a line perpendicular to the axis, to determine if they overlap. This can be seen by the measured lengths in example number 2. It only determines where they overlap; the Axis is merely used for the projection and nothing else! So in an example like the third one for this function will have both shapes on one side of the axis (normally bad) but it is trivial to see from the projection that there is no overlap. So in this case, even though it does not satisfy the separating axis for the parameter axis a solution is known and this function can return as such.


void c_Player::Attack(c_Player* plr, int aHit,int tr, c_AttkTrack &aBox, c_AttkData& data)


c_Player* plr : a pointer to the (potential) victim of this attack
int tr : Identity of the current track being used
c_AttkTrack &aBox A modified track that is a snapshot of the attack at the current frame. This will contain all the current data about the attack.
c_AttkData& data A specialized class to hold the data to transfer an attack. This will be used later to inflict the attack on the victim.

Step by step through the code

if(attackBox.trackList[fighter.atkType].hit[tr][plr->]){//if this attack was recorded already
        data.noHit=true; //flag so this does not trigger impact
        return;//don't hit again

attackBox.trackList[fighter.atkType].hit[tr][plr->] is a confusing term at first glance. 'fighter.atkType' is simply the index of the current attack, stored in c_FighterSprite. 'tr' is the current track. So, taking the current attack, there is a boolean array used, using the current track from 'tr'. 'plr->' is the last index for the boolean hit array; it is the index of the victim. This boolean flag will be true if this victim was hit by this track of this attack already, in which case the function comes to an immediate end.

data.noHit is flagged so that this will not be mistakenly used (Since nothing was defined, null pointer errors would be likely). Ekse, the mentioned boolean flag is then set to true

variables are set and initialized:

SPoint* hb = plr->GetHitBox();// double[4]
    for(int i=0;i<4;i++){
        hb[i].x = hb[i].x-GetPos().x;//translate to origin
        hb[i].y = hb[i].y-GetPos().y;
    bool emptyFlag = true;
    bool fullFlag =true;//assume true, disprove by counterexample

places the potential victim player's hitbox into hb, and translates this accoding to the player's origin. Doing math on stuff oriented along the origin becomes much faster, when the data is conditioned like this.

'emptyFlag' and 'fullFlag' are used to hold 2 fringe cases of attack detection; whether the attack has no vertices inside the player's hitbox (empty) or whether the attack has all it's vertices located within the box (full)

double nX, nY, lY, lX, vX, vY, ppX, ppY, ph2, pw2, diDist,vDir, vDmg, vMag, vWgt;

There are a lot of placeholder variables like this defined. This function involves a lot of math. These will be covered as they are used.

positions the opponent according to the centre of the attack, and according to which direction the attack is executed in.

for(int p=0; p<aBox.iLength;p++){

defiines the main loop. aBox.iLength; is the number of polygons inside aBox

        jl = aBox.GetJlength(p);
        for(int v = 0; v < jl;v++){
            vPos = aBox.aVtx[p][v].pos;
            if((abs(vPos.x-ppX)<pw2)&&(abs(vPos.y-ppY)<ph2))//v[i].x and v[i].y fall within hitbox
                emptyFlag = false;//can't be empty
                fullFlag = false;//can't be full

Loops through the second index of aBox and checks the position of the vertex, determining whether or not it falls within the player's hitbox. uses many helper variables

ppX, ppY; x and position of victim player
pw2 : width of victim player, divided by 2. used to offset the sides, as the player position is defined by the bottom middle of a hitbox

Here is where either emptyFlag or fullFlag can be disproven. this first loop is used to determine which case applies.

Case: fullFlag=true

            for(int v = 0; v < jl;v++){
                int before = v-1;
                    diDist = sqrt(pow(aBox.aVtx[p][before].pos.x-aBox.aVtx[p][v].pos.x, 2) + pow(aBox.aVtx[p][before].pos.y-aBox.aVtx[p][v].pos.y, 2));
                vMag = aBox.aVtx[p][v].mag;
                vDir = aBox.aVtx[p][v].dir;
                vDmg = aBox.aVtx[p][v].dmg;
                vWgt = aBox.aVtx[p][v].wgt;
                data.addVal(aBox.aVtx[p][v].pos, vMag, vDir, diDist, vDmg, vWgt);
                for(int i=0;i<4;i++)
                    cornerFlag[i]=false;//none possible to be included

A simple case; all vertices of the attack fall within the hitbox. This means the entire attack can be used , and all the verts are lifted straight into data.addVal. the cornerFlag array is also set to false, since no corner of the player's hitbox can be relevant in the intersection between the two shapes.

Case: fullFlag=true

else if(emptyFlag){
            for(int j = 0;j<jl;j++){
                vMag = aBox.aVtx[p][i2].mag;
                vDir = aBox.aVtx[p][i2].dir;
                vDmg = aBox.aVtx[p][i2].dmg;
                vWgt = aBox.aVtx[p][i2].wgt;
                vPos = aBox.aVtx[p][i2].pos;
                data.addVal(vPos, vMag, vDir, diDist, vDmg, vWgt);

This is admittedly a section that needs to be redone. For now it simply drops the whole hitbox in

Intersection Case

for(int v = 0; v < jl;v++){
vNext = (v+1)%jl;//circular array
vPos = aBox.aVtx[p][vNext].pos;
vNextInBox = ((abs(vPos.x-ppX)<pw2)&&(abs(vPos.y-ppY)<ph2));

once again, through the polygon of this loop, in more detail this pass around. The remainder of this loops makes extensive use of the neighbouring vertices. These are kept track of as they are passed and the intersection shape is formed. Because the shape is an intersection, information about it is only known as it is constructed and it cannot be retrieved from either the attack polygon nor the victim hitbox alone

The distance between the vertices is used to weight each vertex, and this is recorded by c_AttkData. Because this is an interesction of 2 shapes, there are other potential vertices that need to be found. Anywhere the boundaries intersect, or a corner of the player's hitbox may be a vertex of the polygon formed by the intersection of the attack and victim's hitbox.

[[f> image Intersect1.png size="small"]]

'vNext' is used to define the index of a circular array, sanitized to prevent indexoutofbound errors when looking for '[v+1]' vPos takes this vert values and is used to check to see whether it falls within the victim's hitbox as before in the first pass around. vLast is sanitized to avoid pointing to the index '-1'.

vMag, vDir, vDmg, vWgt, vPos are all loaded up with values for legibility. vX and vY are also used, holding values in vPos that are offset to the player's origin.


ppY; victim player's y position
ph2; victim player's height divided by 2

This is taken from the dead center of the hitbox, because then measurements can be made at once using abs(). This determines if the vertex is within the victim's hitbox.

'diDist' is loaded up with the distance from the last vertex, or 0 if it is the first. data is then loaded up with this vertex since it is located within the player's hitbox. This is marked as the last vertex to keep track of it over the next iteration.

                        iVt = InterpVert(aBox.aVtx[p][v], aBox.aVtx[p][vNext], plr);
                        iVt.pos.x-=GetPos().x;//all values translated to origin
                        diDist = sqrt(pow(iVt.pos.x-vX, 2)+pow(iVt.pos.y-vY, 2));
                        data.addVal(iVt.pos, iVt.mag, iVt.dir, diDist, iVt.dmg, iVt.wgt);

vNextInBox was a check done earlier and recorded; if it is true then the next vertex will not fall in the box and an intersecting vertex will need to be calculated. InterpVert is the function built to do just this, and the intersecting vertex is then adjusted to the origin. the distance between this and the last vertex is recorded and data is loaded with the prepared data. lX and lY also get updated to reflect the location of the new last vertex.

[[f> image Intersect3.png size="small"]]

else {
                        iVt = InterpVert(aBox.aVtx[p][vNext], aBox.aVtx[p][v], plr);
                        iVt.pos.x-=GetPos().x;//all values translated to origin
                        diDist = sqrt(pow(iVt.pos.x-lX, 2)+pow(iVt.pos.y-lY, 2));
                        data.addVal(iVt.pos, iVt.mag, iVt.dir, diDist, iVt.dmg, iVt.wgt);

Covers the case where this vertex was not located in the player's hitbox. If the next vertex will be, then there is another case of intersection as detailed above, but entering the polygon [[f> image Intersect2.png size="small"]] this is handled in much the same way as before.
for(int i = 0; i<4;i++){//check player corner axis
                    vX = aBox.aVtx[p][v].pos.x-GetPos().x-hb[i].x;//all values translated to origin
                    vY = aBox.aVtx[p][v].pos.y-GetPos().y-hb[i].y;
                    nX = aBox.aVtx[p][vNext].pos.x-GetPos().x-hb[i].x;
                    nY = aBox.aVtx[p][vNext].pos.y-GetPos().y-hb[i].y;
                    if((fighter.IsFacingRight())&&(nX*vY-vX*nY<0))//second part is a shortcut formula
                    else if((!fighter.IsFacingRight())&&(nX*vY-vX*nY>0))

This portion of the algorithm checks to see if any corner of the victim's hitbox is located within the hitbox. This makes use of a shortcut formula; nX*vY-vX*nY<0 is really fast to calculate, and determines whether the corner is inside the player's axis-aligned hitbox. It works because it is axis aligned.

the emptyflag case throws in all the relevant corners into the data, now that is it relevant.


rollIntoAvg() is the last step; data takes all the information it was fed about this polygon and does it's math. The origin is also updated and the player's hit box is added; this is purely for debug purposes so the attack display can be updated.

AttkVtx c_Player::InterpCorner(AttkVtx* arrV, int arrLen, SPoint oPos)

Helper Function to c_Player::Attack(…)

AttkVtx c_Player::InterpVert(AttkVtx vI, AttkVtx vO, c_Player* plr)

Helper Function to c_Player::Attack(…)

void c_Player::AttackDetect(c_Player* plr)

Used for attack detection, this function runs the second phase of the main match loop. AttackDetect checks to see if an attack is currently out and intersecting an opponent's hitbox. A hit is registered if this is the case.

The first if statement checks to see if an attack is active. If one is, variables are initilized and the main loop is entered with one iteration per track of the current attack.

for(int t=0;t<il;t++){//iterate through all sub tr
            attackBox.FetchFrame(fighter.GetAtkType(), fighter.frame, t, SPoint(GetPos().x, GetPos().y+stats.size.y/2), fighter.IsFacingRight(), aBox);//fetches frame into aBox
            //aBox has its polygons separated into different tracks for efficiency
            //make a new SPoint array, spBox, to use checkAxis.
            spBox=new SPoint*[aBox.iLength];
            for(int i=0;i<aBox.iLength;i++)
                spBox[i]=new SPoint[aBox.jLength[i]];

loads up sPBox, a 2 dimensional SPoint array with the coordinates found in Box. when aBox.noHit is true, this means that aBox contains no information and that the function can end. If it is set to false however there is data and an attack may be possible.

                for(pInd=0;pInd<numPoly;pInd++){//iterate per poly
                    atkLen =  aBox.GetJlength(pInd);
                    for(int j = 0; j < atkLen; j++)
                        if(!CheckAxis(aBox.aVtx[pInd][j].pos, aBox.atkAng[pInd][j], plBox, spBox[pInd],aBox.GetJlength(pInd)) ) //no axis intersection
                            hitflag = false;
                    if(hitflag)//test on the axis of the player hit box to confirm
                        for(int j = 0; j < 4; j++)
                            if(!CheckAxis(plBox[j], plAng[j], plBox, spBox[pInd], aBox.GetJlength(pInd))) //no axis intersection
                                hitflag = false;

Loops through the attack polygons and the index of the player using CheckAxis and the Separating Axis Theorem. More information on how this theorem is used for collision detection can be seen at c_Stage::CollisionDetect

                c_AttkData retOb  = c_AttkData(aBox.GetJlength(t));
                Attack(plr, t, aBox, retOb);
                        hitText = CharName() + " multi-hit\r\n" + plr->CharName() + "!";
                        hitText =  CharName() + " hit \r\n" + plr->CharName() + "!";
                        console->PostHit(retOb.GetSPArr(), retOb.curInd, retOb.aBds[0], retOb.attLen, retOb.pBds, retOb.GetConOrigin(), retOb.GetConInter());
                    t=il;//force exit; one track hit

This code is triggered if there was a collision detected. c_AttkData is initialized and then loaded up with the attack in Attack(…) which will determine the specifics of the hit. If a hit is confirmed the console gets updated and the victim will receive the attack in c_Player::GetHit(..)

multiHitFlag prevents more than one track from hitting the player at a time; they will all likely hit the victim, but this will happen over different frames. This keeps detection tidier and breaks processing down along the match better.

the Last bit of code determines player pushback; if 2 player hitboxes intersect, the players will push against each other until there is no longer intersection.

void c_Player::GetHit(c_AttkData hit)

++step by step through the code
takes dmg information from hit and uses it to update the player's dmg, velocity and directional values.

[[[code type="cpp"]]
return;attack blocked
}else if(stats.invuln)

First a check to see if the attack can be blocked or dodged occurs. This will result in the player ignoring the attack. Else the function will continue

double dmgScale=1+stats.damage/stats.defence;

This will scale the magnitude of the launch/knockback based on how much damage has already been taken. All players have a defence value that helps determine the rate of scaling.

double uVel, vVel, xVel, yVel;
    uVel = 7.2*hit.mag*cos(hit.dir);//this determines the height to move up to
    vVel = 7.2*hit.mag*sin(hit.dir);//this determines rough distance

uVel is the intended x velocity, and yVel is the intended y velocity. This step translades a polar vector into a cartesian vector.

        xVel= sqrt(uVel*stats.grav/2.0);
        xVel= -sqrt(-uVel*stats.grav/2.0);
        yVel = sqrt(vVel*2*stats.grav);
        yVel = -sqrt(-vVel*2*stats.grav);

x and Y vel are a further transformation. The u and v coordinates determine direction and magnitude, but do not take into account how fast a player will fall. this takes that into account. The dmgScale calculated earlier is then applied.


Helper function to turn the player if needed.

if((fighter.airborne)||(yVel>abs(xVel)+0.1)){//check to see if the hit was
        fighter.airborne=true; //hard enough to launch the player into 
        stats.motion.accl.y+=yVel; //airborne state
    }else if(abs(xVel) < 1.2)

This will launch the player if they were hit with sufficient impact. else, the velocity will be put into gndSpeed and the victim will recoil along the ground.


If the player is hit laterally hard enough, they will launch a little vertically to soar through the air, preventing friction from letting them have a hasty recovery along the ground.

double tumTime = 0.15 +(0.5+hit.dmg/100)*dmgScale;

Sends the player into tumble.

void c_Player::StartAttack(int aI, LPCSTR aS)

initiates an attack.


aI : index of the attack.
aS: string representation of the attack, for resource retrieval.

virtual void c_Player::Update(double)

The update function for the player and the 3rd of the 5 phases in the main match loop.

Step by step through the code

        if((fighter.lastState==fighter.EDGEROLL)||(fighter.lastState==fighter.DODGE))//kills invulnerabilty after a dodge
            stats.invuln=false;//just in case it was not reset

switch invulnerability to off once a player's dodge has ended.


Updates the fighter and the controller with the time elapsed. fighter.Update runs the player through a variety of checks and recalculates what actions are taken depending on input. Player variables/stats are then updated according to the new values.

if(fighter.InState(fighter.GUARD)){//special case for guard
        fighter.Animate("Guard", true, 0);
        if((stats.guard.dir ==0)&&(!fighter.IsFacingRight()) )
            stats.guard.dir = -M_PI;    

handles guard animations. Since guard is a weighted anim it requires a special case; how far along the anim is ties to the direction of the control stick. This allows a player to guard up and down.

    if((!fighter.InState(fighter.ATTK))&&(stats.motion.vel.y < 0.8)&&(fighter.airborne)&&(stats.edgegrab.delayTmr.IsReady()))
        grabBox.isActive = true;
        grabBox.isActive = false;

determines whether the player's grabbox is active or not. Depends entirely upon the player's y velocity; the box can be active only if the player is falling

Walk() is a separate function on it's own, used to determine lateral motion both on the ground and in the air.

RunTimers(timeLapsed); takes the elaspes time, and applies to to each case of STimer

        stats.motion.accl.y -= stats.grav*timeFr;    
            stats.motion.accl.y -= 2*stats.grav*timeFr;    
        stats.motion.vel.x += stats.motion.accl.x*timeFr;
        stats.motion.vel.y += stats.motion.accl.y*timeFr;

the first 2 lines above check to see whether an idle state should apply; fighter.idle() applies it.

if(fighter.airborne) checks to see if the fighter is airborne, applying gravity if the player is. If the fighter is fast falling, this gravity is tripled.

if(!dmgFlag) implies the player was not damaged during the second phase of the [[[main combat loop]]. If it is set to true then other flags are also set.

The remainder of this function updates the player's velocity.

virtual void c_Player::PostUpdate()

The final phase of the main combat loop

This function performs some clean up, and updates variables like lastPos meant to hold information from the last frame.

As it is simple, detailed explanation would be redundant. It is meant to be easy to read and follow

Final Variables

STimer actionTmr; : a timer, active for any action the player takes
STimer atkTmr; : a timer, active for any attack the player makes

double EDGE_DELAY; : time that must pass between the time a player drops from a ledge and the time that the edgegrab box will become active again. A small delay is needed, so that the player does not [[immediately]] grab the edge again after letting go.
double VERT_THRESH; //downward threshold for the control stick when performing a spotdodge. (Block + tapping the control stick down)
double UP_THRESH; //beyond this threshold, a jump or edgeclimb will be recorded
double SPRINT_THRESH; //beyond this threshhold (laterally) a sprint will be recorded