Can I Improve Dapper Dasher? How?

Okay, this is where I’m at.

#include "raylib.h"

struct AnimData
{
    Rectangle Rec;
    Vector2 Pos;
    int Frame;
    float UpdateTime;
    float RunningTime;    
};

bool IsOnGround(AnimData Data, int WindowHeight)
{
    return Data.Pos.y >= WindowHeight - Data.Rec.height;
}

AnimData UpdateAnimData(AnimData Data, float DeltaTime, int MaxFrame)
{
    // update running time
    Data.RunningTime += DeltaTime;
    if (Data.RunningTime >= Data.UpdateTime)
    {
        Data.RunningTime = 0.0;
        // update animation data            
        Data.Rec.x = Data.Frame * Data.Rec.width;
        Data.Frame++;
        if (Data.Frame > MaxFrame)
        {
            Data.Frame = 0;
        }
    }
    return Data;
}

bool GameStarted = false;
int Score = 0;              // Game Score
int Delay = 120;            // 2 second timer
int Timer = Delay;          // Working variable for timer

int main()
{
    // window dimensions
    const int WindowDimensions[]{512, 380};
    // initialize the window
    InitWindow(WindowDimensions[0], WindowDimensions[1], "Single Button Game");

    // acceleration due to gravity (pixels/second)/second
    const int Gravity = 1'000;

    Texture2D Nebula = LoadTexture("textures/12_nebula_spritesheet.png");


    const int SizeOfNebulae{6};
    AnimData Nebulae[SizeOfNebulae]{};

    // loop through the Nebulae array to initialize all values
    for (int i = 0; i < SizeOfNebulae; i++)
    {
        Nebulae[i].Rec.x = 0.0;
        Nebulae[i].Rec.y = 0.0;
        Nebulae[i].Rec.width = Nebula.width/8;
        Nebulae[i].Rec.height = Nebula.width/8;
        Nebulae[i].Pos.x = WindowDimensions[0]+(i*300);
        Nebulae[i].Pos.y = WindowDimensions[1] - Nebula.height/8;
        Nebulae[i].Frame = 0;
        Nebulae[i].RunningTime = 0.0;
        Nebulae[i].UpdateTime = 0.0;
    }
    
    float FinishLine{Nebulae[SizeOfNebulae - 1].Pos.x};

    // Nebula X velocity (pixels/sec)
    int NebVel{-200};

    //Scarfy variables
    Texture2D Scarfy = LoadTexture("textures/scarfy.png");
    AnimData ScarfyData;
    ScarfyData.Rec.width = Scarfy.width/6;
    ScarfyData.Rec.height = Scarfy.height;
    ScarfyData.Rec.x = 0;
    ScarfyData.Rec.y = 0;
    ScarfyData.Pos.x = WindowDimensions[0]/2 - ScarfyData.Rec.width/2;
    ScarfyData.Pos.y = WindowDimensions[1] - ScarfyData.Rec.height;
    ScarfyData.Frame = 0;
    ScarfyData.UpdateTime = 1.0/10.0;
    ScarfyData.RunningTime = 0.0;

    // is the rectangle in the air
    bool IsInAir = false;
    // jump velocity (pixels/second)
    const int JumpVel = -600;

    int Velocity = 0;

    Texture2D Background = LoadTexture("textures/far-buildings.png");
    float BgX{};
    Texture2D Midground = LoadTexture("textures/back-buildings.png");
    float MgX{};
    Texture2D Foreground = LoadTexture("textures/foreground.png");
    float FgX{};

    bool Collision{}; // initialize collision as false

    SetTargetFPS(60);
    while (!WindowShouldClose())
    {
        // delta time (time since last frame)
        const float dT = GetFrameTime();

        //start drawing
        BeginDrawing();
        ClearBackground(WHITE);
        if (!GameStarted)           // if game has not started display title and keys and wait for input
        {
            // DisplayTitle(WindowWidth*.05, WindowHeight*.2, ObstacleColor, ScoreColor);
            DrawText("Press Space", (WindowDimensions[0]*.4), (WindowDimensions[1]*.65), 15, BLACK);
            if(IsKeyDown(KEY_SPACE))
            {
                GameStarted = true;
            }
        }
        else
        {

            // move background
            BgX -= 20 * dT;
            if (BgX <= -Background.width * 2)
            {
                BgX = 0.0;
            }
            // move midground
            MgX -= 40 * dT;
            if (MgX <= -Background.width * 2)
            {
                MgX = 0.0;
            }
            // move foreground
            FgX -= 80 * dT;
            if (FgX <= -Foreground.width * 2)
            {
                FgX = 0.0;
            }
            // move finish line
            FinishLine += NebVel * dT;


            // draw background
            Vector2 Bg1Pos{BgX, 0.0};
            DrawTextureEx(Background, Bg1Pos, 0.0, 2.0, WHITE);
            Vector2 Bg2Pos{BgX + Background.width*2, 0.0};
            DrawTextureEx(Background, Bg2Pos, 0.0, 2.0, WHITE);
            // draw midground
            Vector2 Mg1Pos{MgX, 0.0};
            DrawTextureEx(Midground, Mg1Pos, 0.0, 2.0, WHITE);
            Vector2 Mg2Pos{MgX + Midground.width*2, 0.0};
            DrawTextureEx(Midground, Mg2Pos, 0.0, 2.0, WHITE);
            // draw foreground
            Vector2 Fg1Pos{FgX, 0.0};
            DrawTextureEx(Foreground, Fg1Pos, 0.0, 2.0, WHITE);
            Vector2 Fg2Pos{FgX + Foreground.width*2, 0.0};
            DrawTextureEx(Foreground, Fg2Pos, 0.0, 2.0, WHITE);

            //ground check
            if (IsOnGround(ScarfyData, WindowDimensions[1]))
            {
                // rectangle is on the ground
                IsInAir = false;
                Velocity = 0;
                //PosY = WindowHeight-Height;   // snap to floor
            }
            else
            {
                // apply gravity
                Velocity += Gravity * dT;
                // rectangle is in the air
                IsInAir = true;
            }
            // check for jumping
            if (IsKeyPressed(KEY_SPACE) && !IsInAir)
            {
                Velocity += JumpVel;
            }
            
            // Loop through nebulae to update position
            for (int i=0; i < SizeOfNebulae; i++)
            {
                Nebulae[i].Pos.x += NebVel * dT;
            }

            // update Scarfy position and update animation frame
            ScarfyData.Pos.y += Velocity * dT;

            if (!IsInAir)
            {
                ScarfyData = UpdateAnimData(ScarfyData, dT, 5);            
            }
            
            // Loop through nebulae to update animation
            for (int i = 0; i < SizeOfNebulae; i++)
            {
                Nebulae[i] = UpdateAnimData(Nebulae[i], dT, 7);
            }

            // try to collide things
            for (AnimData Nebula : Nebulae)
            {
                float Pad{50};  // eliminates white space around obstacle from collision
                Rectangle NebRec{
                    Nebula.Pos.x + Pad,
                    Nebula.Pos.y + Pad,
                    Nebula.Rec.width - 2*Pad,
                    Nebula.Rec.height - 2*Pad
                };
                Rectangle ScarfyRec{
                    ScarfyData.Pos.x,
                    ScarfyData.Pos.y,
                    ScarfyData.Rec.width,
                    ScarfyData.Rec.height
                };
                if (CheckCollisionRecs(NebRec, ScarfyRec))
                {
                    Collision = true;
                }
            }

            if (Collision)
            {
                // lose the game
                DrawText("Game Over", WindowDimensions[0]/5, 2*WindowDimensions[1]/5, 50, BLACK);
                DrawText("Game Over", WindowDimensions[0]/5-4, 2*WindowDimensions[1]/5-3, 50, RED);
                DrawText("Game Over", WindowDimensions[0]/5-5, 2*WindowDimensions[1]/5-4, 50, WHITE);
            }
            else if (FinishLine <= WindowDimensions[0]/5)
            {
                DrawText("You Win!", WindowDimensions[0]/5, 2*WindowDimensions[1]/5, 50, BLACK);
                DrawText("You Win!", WindowDimensions[0]/5-4, 2*WindowDimensions[1]/5-3, 50, GREEN);
                DrawText("You Win!", WindowDimensions[0]/5-5, 2*WindowDimensions[1]/5-4, 50, WHITE);
            }
            else
            {
                // Loop through nebulae to update animation
                for (int i=0; i<SizeOfNebulae; i++)
                {
                    DrawTextureRec(Nebula, Nebulae[i].Rec, Nebulae[i].Pos, WHITE);
                }
                // draw scarfy
                DrawTextureRec(Scarfy, ScarfyData.Rec, ScarfyData.Pos, WHITE);
            }
        }
        // stop drawing
        EndDrawing();        
    }
     UnloadTexture(Scarfy);
     UnloadTexture(Nebula);
     UnloadTexture(Background);
     UnloadTexture(Midground);
     UnloadTexture(Foreground);
     CloseWindow();     
}

It’s basically the project they have you build plus a start screen (No title yet) just so that I’m ready before I have to jump. I improved the text a little with an outline and shadow but need to consider how to really make this shine. I’ll start with observations:

  • It’s kind of predictable and boring
  • I’m not encouraged to play more
  • I’d like to learn about 2D jumping

So what can I do?

  • I definitely want to wrap this in a replay thing
  • I think working in a pig mechanic could be ideal here
  • The game is about jumping and sidescroll jumping has a lot to dig into

So I’m off to refactor and experiment with new features but if you have ideas, suggestions or ways I can figure this stuff out I’d love to hear them!

I think my fancy text could be done with a loop but I can’t think how yet…

DrawText("You Win!", WindowDimensions[0]/5, 2*WindowDimensions[1]/5, 50, BLACK);
DrawText("You Win!", WindowDimensions[0]/5-4, 2*WindowDimensions[1]/5-3, 50, GREEN);
DrawText("You Win!", WindowDimensions[0]/5-5, 2*WindowDimensions[1]/5-4, 50, WHITE);

Maybe a data struct or array that holds the offset for X, Y and then the colors?

Loop would make the code a little harder to maintain if you want to make changes. Unless you wanted to add more to it then I’d recommend just making the text in paint or photoshop and drawing it as a texture instead.

Similar to your start screen, you would just need to add a key that resets the state of the game. Positions of the nebulae, scarfy and the value of GameStarted should be enough.

I guess maybe not a loop but at least a function so I could adjust the offset or font size all in one place. But how could you pass in the text for each function call if RayLib doesn’t use strings?

char gameTitle[] = "Desired Text";
void ShadowAndOutline(float PercentWindowPosX, float PercentWindowPosY, Color OutlineColor)
{
    int TrimX   = -3; 
    int TrimY   = -2; 
    int ShadowX = -4;
    int ShadowY = -3;
    int FontSize= 50;
    DrawText("Desired Text", PercentWindowPosX*WindowDimensions[0],           PercentWindowPosY*WindowDimensions[1],        FontSize, BLACK);
    DrawText("Desired Text", PercentWindowPosX*WindowDimensions[0]+TrimX,     PercentWindowPosY*WindowDimensions[1]+TrimY,  FontSize, OutlineColor);
    DrawText("Desired Text", PercentWindowPosX*WindowDimensions[0]+ShadowX,   PercentWindowPosY*WindowDimensions[1]+ShadowY,FontSize, WHITE); 
}

Let’s answer that by analyzing the signature of DrawText, more specifically the first argument. The first argument of DrawText is calling for a const char*, that is to say a pointer to some data of type char that is constant (more on pointers in Classy Clash if you haven’t done that section yet, but not important for our discussion here).

Strings and char* actually have a lot in common, so much so that a String is a fancy char* wrapped in extra functionality.

So to modify your function signature a little bit…

void ShadowAndOutline(const char* text, float PercentWindowPosX, float PercentWindowPosY, Color OutlineColor)

Then, if you wanted to use this with strings:
ShadowAndOutline("Game Over", ....)

OR

//This assumes you already included <string>
std::string gameOverText = "Game Over"
ShadowAndOutline(gameOverText.c_str(), ....)
1 Like

I didn’t #include anything and I don’t fully get why yet but your first option worked. Are the double qoutes making the array of chars for me? Now I’m really looking forward to the next project. I came here from the Unreal C++ course. It’s why I’ve kept all my variables Capitalized so as to maintain the habit after being accustomed to C# from the unity courses. Capitalizing everything still feels weird. Ultimately pointers and references broke me. I wanted a better understanding of the code I was writing when I could follow along but not actually wield it for my own purposes as I tried to take the project to the next level afterward.

But for now, I’ve gotten myself into new trouble with something called a “segmentation fault” as I tried to factor out the loop that populates the Nebulae array. It was complaining that it didn’t have access to Nebula or Scarfy anymore:
noAccess
…so I thought it would be a bright idea to bring these variables up out of the main() function. It looks like this was actually a super bad idea™. It probably has to do with that “unload” thing we had to do at the end of main(). Maybe you can help me understand more?

EDIT:
The second to last video in the class, “Strings and Displaying Health”, thoroughly explains this exact situation and Tuomo’s second solution.

I have made some progress here. Dapper Dasher now spawns Nebulae randomly on 3 tracks based on this random set VerticalOffset

    for (int i = 0; i < SizeOfNebulae; i++)
    {
        int VerticalOffset = GetRandomValue(0, 2);
        Nebulae[i].Rec.x = 0.0;
        Nebulae[i].Rec.y = 0.0;
        Nebulae[i].Rec.width = Nebula.width/8;
        Nebulae[i].Rec.height = Nebula.width/8;
        Nebulae[i].Pos.x = WindowDimensions[0]+(i*300);
        Nebulae[i].Pos.y = WindowDimensions[1] - Nebula.height/8 - VerticalOffset*Scarfy.height;
        Nebulae[i].Frame = 0;
        Nebulae[i].RunningTime = 0.0;
        Nebulae[i].UpdateTime = 0.0;
    }

To manage that Scarfy has an extra “double” jump as well as the ability control the jump based upon how long the button is held. Doing this I discovered the += to add the JumpVel to Velocity isn’t as useful as straight up assigning it. You can’t notice normally but it has a much snappier effect if he is falling when you hit it.

            // check for a jump
            if (IsKeyPressed(KEY_SPACE))
            {
                if(!IsInAir)
                {
                    Velocity += JumpVel;
                }
                else if (ExtraJump)
                {
                    Velocity = JumpVel;
                    ExtraJump = false;
                }
            }

            // reduce rising when jump is released
            if (IsKeyReleased(KEY_SPACE) && Velocity < 0)
            {
                Velocity = Velocity/2;
            }

As ever, feedback or questions are super welcome.

Okay so here’s my sticking point.
I want to factor out the code that makes the nebulas initialize so that I can call it again once the game ends and I want to restart. The problem is I can’t bring out the variables it needs. Reflexively, I can’t define a GenerateWaveAndGoal() function inside my main() function so I’m confused how to approach this.

A different alternative occurred to me.

I realized the key is to keep them coming. It seems to me that after they leave the screen, they still exist. After long enough if they keep coming that may become taxing, right? So now, at the point where I move the nebula further left I test if it is off the screen.

if (Nebulae[i].Pos.x + Nebulae->Rec.width < 0)

I don’t know why the arrow and not a dot but luckily VSCode did so it saved me there. If that works, I teleport it to the right and we play leapfrog much as we did with the backgrounds. The trick was figuring the math for how far. I’m not gonna lie, I played guess and check until I had it. But for spending the time to read my stuff I’ll give you the math.

Nebulae[i].Pos.x = (WindowDimensions[0]+300*(SizeOfNebulae-1));

I also found a bug where he gets stuck in the floor if falling from high enough which requires an extra jump to get out from. This might be fun with a good alternate animation but without, it just feels janky. So I fixed that with this in the ground check.

ScarfyData.Pos.y = WindowDimensions[1] - ScarfyData.Rec.height;

Next up I have to turn our “FinishLine” into a checkpoint where the betting mechanic occurs.

I think my attempts at refactor only made the code longer and I’m not even sure if it was clearer. In many cases I wonder if it was a good idea to pull variables out of main(). As I got to parts of the algorithm that used variables that had to be defined in main() I had less and less success factoring things out.
CenturyJumper

Things began to click more today and the game is really fun now so I’m taking the W and moving on. What do you think? Could I have factored something out or named something better? Do you have questions or answers about the gameplay or code? Did you just play, have fun and want to tell me about the funny bug you found? Whatever it is, I’d love to hear it.
https://docs.google.com/forms/d/17oilnI_CIQxXOs5vfqLTzx6YpKIA1YULaLyRIxS5N-M/prefill

#include "raylib.h"

struct AnimData
{
    Rectangle Rec;
    Vector2 Pos;
    int Frame;
    float UpdateTime;
    float RunningTime;
};

const int WindowDimensions[]{512, 380};
int RoundLength{6};             // How long is the Nebulae pattern before a checkpoint?
int RoundScore = 0; 
int TotalScore = 0; 
int Multiplier = 1; 
double GameTimer{}; 
const int Gravity = 1'000;
int ScoreOutlineOffset = 1;     //  This animates the HUD

// These are used as easily resettable timers I burn of by subtracting dT
float TextEffectCounterSetting{.125};
float TextEffectCounter{TextEffectCounterSetting};
float RoundChoiceCounterSetting{1};
float RoundChoiceCounter{RoundChoiceCounterSetting};
float KeyHoldCounterSetting{.5};
float KeyHoldCounter{KeyHoldCounterSetting};

// Game state flags
bool GameStarted = false;
bool RoundOver = false;
bool Cheated = false;
bool WinGame = false;

bool IsOnGround(AnimData Data, int WindowHeight)
{
    return Data.Pos.y >= WindowHeight - Data.Rec.height;
}

//  Convert AnimData to move an animation strip to the next frame
AnimData UpdateAnimData(AnimData Data, float DeltaTime, int MaxFrame)
{
    Data.RunningTime += DeltaTime;
    if (Data.RunningTime >= Data.UpdateTime)
    {
        Data.RunningTime = 0.0;       
        Data.Rec.x = Data.Frame * Data.Rec.width;
        Data.Frame++;
        if (Data.Frame > MaxFrame)
        {
            Data.Frame = 0;
        }
    }
    return Data;
}

// This displays the button prompt and returns false until it is pressed
bool PromptSpace()
{
    DrawText("Press Space", (WindowDimensions[0]*.4), (WindowDimensions[1]*.65), 15, MAROON);
    DrawText("Esc to Quit", (WindowDimensions[0]*.80), (WindowDimensions[1]*.95), 15, BLACK);
        if(IsKeyDown(KEY_SPACE))
        {
            return true;
        }
        return false;
}

//  Displays a large text layered with a highlight and shadow
void ShadowAndOutline(const char *text, float PercentWindowPosX, float PercentWindowPosY, Color OutlineColor)
{
    int TrimX   = -2; 
    int TrimY   = -1; 
    int ShadowX = -3;
    int ShadowY = -2;
    int FontSize= 60;
    DrawText(text, PercentWindowPosX*WindowDimensions[0],           PercentWindowPosY*WindowDimensions[1],        FontSize, BLACK);
    DrawText(text, PercentWindowPosX*WindowDimensions[0]+TrimX,     PercentWindowPosY*WindowDimensions[1]+TrimY,  FontSize, OutlineColor);
    DrawText(text, PercentWindowPosX*WindowDimensions[0]+ShadowX,   PercentWindowPosY*WindowDimensions[1]+ShadowY,FontSize, WHITE); 
}

// This draws game data on screen and animates the highlight
void DrawHUD(float dT)
                {
                    DrawText(TextFormat("Score: %i", TotalScore), WindowDimensions[0]*.05, WindowDimensions[1]*.05, 20, WHITE);
                    DrawText(TextFormat("Score: %i", TotalScore), WindowDimensions[0]*.05-ScoreOutlineOffset, WindowDimensions[1]*.05-ScoreOutlineOffset, 20, { 0, 138, 113, 255});
                    DrawCircle(WindowDimensions[0]*.16, WindowDimensions[1]*.16, 15, WHITE);
                    DrawCircle(WindowDimensions[0]*.16-ScoreOutlineOffset, WindowDimensions[1]*.16-ScoreOutlineOffset, 15, { 0, 138, 113, 255});
                    DrawText(TextFormat("x %i", Multiplier), WindowDimensions[0]*.14, WindowDimensions[1]*.14, 15, WHITE);
                    DrawText(TextFormat("x %i", Multiplier), WindowDimensions[0]*.14-ScoreOutlineOffset, WindowDimensions[1]*.14-ScoreOutlineOffset, 15, WHITE);
                    if (Cheated)
                    {
                        DrawText("Cheated", WindowDimensions[0]*.10, WindowDimensions[1]*.18, 20, RED);
                    }
                    TextEffectCounter -= dT;
                    if(TextEffectCounter <= 0)
                    {
                        ScoreOutlineOffset *= -1;
                        TextEffectCounter = TextEffectCounterSetting;
                    }
                    //DrawText(TextFormat("Time: %i", (int)GameTimer), WindowDimensions[0]*.5, WindowDimensions[1]*.05, 20, WHITE);
                }

// This simplifies and standardizes the display of crooked text
void DisplayTwistyText(const char *text, float PercentWindowPosX, float PercentWindowPosY, float Rotation, int Size, Color LetterColor)
                {
                    Vector2 Position{WindowDimensions[0]*PercentWindowPosX,WindowDimensions[1]*PercentWindowPosY};
                    Vector2 Origin{0,0};
                    Vector2 Origin2{2,2};
                    DrawTextPro(GetFontDefault(), text, Position, Origin, Rotation, Size, 2, BLACK);
                    DrawTextPro(GetFontDefault(), text, Position, Origin2, Rotation, Size, 2, LetterColor);
                }

void DisplayTitleWaitForInput()
{
    ShadowAndOutline("Century", .25, .20, RED);
    ShadowAndOutline("Jumper", .3, .40, RED);
    DrawText("Risk & Reward   Race & Record \n\n\n\n       The Quest to 100", WindowDimensions[0]*.3,  WindowDimensions[1]*.37, 14, BLACK);
    DisplayTwistyText("Keep Jumping!    Don't Stop Ever!", .1, .06, 80, 20, MAROON);
    DisplayTwistyText("More Jumps Means More Points!", .6, .9, -55, 20, MAROON);
    if(PromptSpace())
    {
        GameStarted = true;
        GameTimer = 0;
    }   
}

void DelayPromptAndResetGame(float dT)
{
    if(RoundChoiceCounter <= 0)
    {
        if(PromptSpace())
        {
            RoundChoiceCounter = RoundChoiceCounterSetting;
            WinGame = false;
            RoundOver = false;
            RoundScore = 0;
            TotalScore = 0;
            Multiplier = 1;
            GameTimer = 0;
            Cheated = false;
        }
    }
    else
    {
        RoundChoiceCounter -= dT;
    }
}

void CheckForCheatingDisplayVictory()
{
    ShadowAndOutline("You Win!", .3, .3, { 0, 138, 113, 255});
    if(Cheated)
    {
        DisplayTwistyText("Cheated", .55, .4, -25, 40, MAROON); 
        
    }
    else
    {
        DisplayTwistyText("Congratulations!", .1, .35, -30, 30, { 25, 69, 104, 255});
    }
    DrawText(TextFormat("100 Points Took %i Seconds", (int)GameTimer), WindowDimensions[0]*.23, WindowDimensions[1]*.5, 23, BLACK);
    DrawText("Can You Jump Faster Next Time?", WindowDimensions[0]*.20, WindowDimensions[1]*.57, 21, BLACK);
}                

void DisplayFailScreen()
{
    ShadowAndOutline("Ouch!", .34, .4, RED);
    DisplayTwistyText("Small Taps For Tiny Hops", .02, .2, -15, 20, MAROON);
    DisplayTwistyText("Double Jump For Extra Height!", .4, .2, 20, 20, MAROON);
}

void ShowCostOfFailure()
{
    if(Multiplier > 1)
    {
        DrawText(TextFormat("%ix Multiplier Lost", Multiplier), WindowDimensions[0]*.38, WindowDimensions[1]*.55, 15, BLACK);
    }
    if(RoundScore > 1)
    {
        DrawText(TextFormat("%i Points Lost", RoundScore), WindowDimensions[0]*.4, WindowDimensions[1]*.6, 15, BLACK);
    }
    else if(RoundScore)
    {
        DrawText("1 Point Lost", WindowDimensions[0]*.4, WindowDimensions[1]*.6, 15, BLACK);
    }
}

bool ResetRound()
{
    RoundScore = 0;
    Multiplier = 1;
    RoundOver = false;
    RoundChoiceCounter = RoundChoiceCounterSetting;
    return false;
}

void DisplayCheckpointScreen()
{
    DisplayTwistyText("Checkpoint Reached", .25, .25, 0, 20, { 0, 138, 113, 255});
    ShadowAndOutline(TextFormat("%i Points", RoundScore), .2, .3, { 25, 69, 104, 255});
    DisplayTwistyText("Play It Safe!", .02, .35, -45, 20, { 0, 138, 113, 255});
    DisplayTwistyText("Take a Chance!", .65, .02, 40, 20, { 0, 138, 113, 255});
}

void DelayAndPromptChoices(float dT)
{
    if(RoundChoiceCounter <= 0)
    {
        //draw choice
        DisplayTwistyText("Hold Space to Record Score", .14, .6,-3 , 30, { 25, 69, 104, 255});
        DisplayTwistyText("Tap to Increase Multiplier", .2, .7, 7 ,  30, { 25, 69, 104, 255});
        if(IsKeyDown(KEY_SPACE))
        {
            KeyHoldCounter -= dT;
        }
        if(KeyHoldCounter < 0)
        {
            // key was held Protect Score
            RoundOver = false;
            TotalScore += RoundScore;
            RoundScore = 0;
            Multiplier = 1;
            KeyHoldCounter = KeyHoldCounterSetting;
            RoundChoiceCounter = RoundChoiceCounterSetting;
        }
        else if(IsKeyReleased(KEY_SPACE))
        {
            //key was tapped Increase Multiplier
            RoundOver = false;
            Multiplier++;
            KeyHoldCounter = KeyHoldCounterSetting;
            RoundChoiceCounter = RoundChoiceCounterSetting;
        }
    }
    else
    {
        RoundChoiceCounter -= dT;
    }
}
void CheckForCheatInputs()
{
    if (IsKeyPressed(KEY_M))
    {
        Cheated = true;
        if(Multiplier < 9)
        {
            Multiplier++;
        }
    }
    if (IsKeyPressed(KEY_W))
    {
        Cheated = true;
        WinGame = true;
    }
    if (IsKeyDown(KEY_P))
    {
        Cheated = true;
        RoundScore++;
    }
    if ((IsKeyDown(KEY_P)) && (IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)))
    {
        Cheated = true;
        TotalScore++;
    }
}


int main()
{
    InitWindow(WindowDimensions[0], WindowDimensions[1], "Single Button Game");
    
    // Nebula X velocity (pixels/sec)
    int NebVel = -200;       
    bool Collision{};       // initialize collision Game State as false
    
    //Scarfy variables
    Texture2D Scarfy = LoadTexture("textures/scarfy.png");
    AnimData ScarfyData;
    ScarfyData.Rec.width = Scarfy.width/6;
    ScarfyData.Rec.height = Scarfy.height;
    ScarfyData.Rec.x = 0;
    ScarfyData.Rec.y = 0;
    ScarfyData.Pos.x = WindowDimensions[0]*.5 - ScarfyData.Rec.width*.5;
    ScarfyData.Pos.y = WindowDimensions[1] - ScarfyData.Rec.height;
    ScarfyData.Frame = 0;
    ScarfyData.UpdateTime = 1.0/10.0;
    ScarfyData.RunningTime = 0.0;
    Texture2D Nebula = LoadTexture("textures/12_nebula_spritesheet.png");
    const int SizeOfNebulae{RoundLength};
    AnimData Nebulae[RoundLength]{};
    // loop through the Nebulae array to initialize all values
    for (int i = 0; i < SizeOfNebulae; i++)
    {
        int VerticalOffset = GetRandomValue(0, 2);
        Nebulae[i].Rec.x = 0.0;
        Nebulae[i].Rec.y = 0.0;
        Nebulae[i].Rec.width = Nebula.width/8;
        Nebulae[i].Rec.height = Nebula.width/8;
        Nebulae[i].Pos.x = WindowDimensions[0]+(i*300);
        Nebulae[i].Pos.y = WindowDimensions[1] - Nebula.height/8 - VerticalOffset*Scarfy.height;
        Nebulae[i].Frame = 0;
        Nebulae[i].RunningTime = 0.0;
        Nebulae[i].UpdateTime = 0.0;
    }
    float Checkpoint{Nebulae[SizeOfNebulae - 1].Pos.x};
    float CheckpointDistance{Checkpoint};
    bool IsInAir = false;           // is the rectangle in the air
    bool ExtraJump = true;
    const int JumpVel = -650;       // jump velocity (pixels/second)
    int Velocity = 0;
    Texture2D Background = LoadTexture("textures/far-buildings.png");
    Texture2D Midground = LoadTexture("textures/back-buildings.png");
    Texture2D Foreground = LoadTexture("textures/foreground.png");
    float BgX{};
    float MgX{};
    float FgX{};

    SetTargetFPS(60);
    while (!WindowShouldClose())
    {
        // delta time (time since last frame)
        const float dT = GetFrameTime();

        //start drawing
        BeginDrawing();
        ClearBackground(LIGHTGRAY);
        if (!GameStarted)           // if game has not started display title and keys and wait for input
        {
            DisplayTitleWaitForInput();
            
        }
        else if (WinGame)
        {
            CheckForCheatingDisplayVictory();            
            DelayPromptAndResetGame(dT);
            Checkpoint = CheckpointDistance;
        }
        else if (Collision && RoundOver)
        {
            DisplayFailScreen();
            ShowCostOfFailure();
            if(RoundChoiceCounter <= 0)
            {
                if(PromptSpace())
                {
                    
                    Collision = ResetRound();
                    // Jump all Nebulae Left
                    for (int i=0; i < SizeOfNebulae; i++)
                    {    // leapfrog right if one goes off screen
                        Nebulae[i].Pos.x += 2*Nebulae[i].Rec.width;                    
                        if (Nebulae[i].Pos.x + Nebulae->Rec.width < 0)
                        {
                            Nebulae[i].Pos.x += (WindowDimensions[0]+300*(SizeOfNebulae-1));  
                        }
                    }
                }
            }
            else
            {
                RoundChoiceCounter -= dT;
            }
            
        }
        else if (RoundOver)
        {
            DisplayCheckpointScreen();            
            DelayAndPromptChoices(dT);            
            Checkpoint = CheckpointDistance;
        }
        
        else if (GameStarted && !RoundOver)
        {            
            // increment game timer
            GameTimer += dT;
            // move background
            BgX -= 20 * dT;
            if (BgX <= -Background.width * 2)
            {
                BgX = 0.0;
            }
            // move midground
            MgX -= 40 * dT;
            if (MgX <= -Background.width * 2)
            {
                MgX = 0.0;
            }
            // move foreground
            FgX -= 80 * dT;
            if (FgX <= -Foreground.width * 2)
            {
                FgX = 0.0;
            }
                        
            // draw background
            Vector2 Bg1Pos{BgX, 0.0};
            DrawTextureEx(Background, Bg1Pos, 0.0, 2.0, WHITE);
            Vector2 Bg2Pos{BgX + Background.width*2, 0.0};
            DrawTextureEx(Background, Bg2Pos, 0.0, 2.0, WHITE);
            // draw midground
            Vector2 Mg1Pos{MgX, 0.0};
            DrawTextureEx(Midground, Mg1Pos, 0.0, 2.0, WHITE);
            Vector2 Mg2Pos{MgX + Midground.width*2, 0.0};
            DrawTextureEx(Midground, Mg2Pos, 0.0, 2.0, WHITE);
            // draw foreground
            Vector2 Fg1Pos{FgX, 0.0};
            DrawTextureEx(Foreground, Fg1Pos, 0.0, 2.0, WHITE);
            Vector2 Fg2Pos{FgX + Foreground.width*2, 0.0};
            DrawTextureEx(Foreground, Fg2Pos, 0.0, 2.0, WHITE);

            // move checkpoint line
            Checkpoint += NebVel * dT;
            unsigned char Flicker = GetRandomValue(0,128);
            unsigned char Flicker2 = GetRandomValue(100,120);
            Vector2 CheckpointBottom{Checkpoint, (float)WindowDimensions[1]};
            Vector2 CheckpointTop{Checkpoint, 0};
            DrawLineEx(CheckpointTop, CheckpointBottom, 5, { 0, 138, Flicker2, Flicker});

            //ground check
            if (IsOnGround(ScarfyData, WindowDimensions[1]))
            {
                // rectangle is on the ground
                IsInAir = false;
                Velocity = 0;                
                ExtraJump = true;
                ScarfyData.Pos.y = WindowDimensions[1] - ScarfyData.Rec.height;
            }
            else
            {
                // apply gravity
                Velocity += Gravity * dT;
                // rectangle is in the air
                IsInAir = true;
                DrawText(TextFormat("+%i", RoundScore), (ScarfyData.Pos.x + ScarfyData.Rec.width/2), (ScarfyData.Pos.y + Scarfy.height), 15, { 0, 138, 113, 255});
            }

            // check for a jump
            if (IsKeyPressed(KEY_SPACE))
            {
                if(!IsInAir)
                {
                    Velocity = JumpVel;
                    RoundScore += Multiplier;
                }
                else if (ExtraJump)
                {
                    Velocity = JumpVel;
                    ExtraJump = false;
                    RoundScore += Multiplier;
                }
            }

            // reduce rising when jump is released
            if (IsKeyReleased(KEY_SPACE) && Velocity < 0)
            {
                Velocity = Velocity*.5;
            }
            
///CHEATS///////////////
            CheckForCheatInputs();
///////////////////////////////////
            // Loop through nebulae to update position
            for (int i=0; i < SizeOfNebulae; i++)
            {
                Nebulae[i].Pos.x += NebVel * dT;
                if (Nebulae[i].Pos.x + Nebulae->Rec.width < 0)
                {
                    Nebulae[i].Pos.x = (WindowDimensions[0]+300*(SizeOfNebulae-1));  
                }
            }

            // update Scarfy position and update animation frame
            ScarfyData.Pos.y += Velocity * dT;

            if (!IsInAir)
            {
                ScarfyData = UpdateAnimData(ScarfyData, dT, 5);            
            }
            
            // Loop through nebulae to update animation
            for (int i = 0; i < SizeOfNebulae; i++)
            {
                Nebulae[i] = UpdateAnimData(Nebulae[i], dT, 7);
            }

            // try to collide things
            for (AnimData Nebula : Nebulae)
            {
                float Pad{50};  // eliminates white space around obstacle from collision
                Rectangle NebRec{
                    Nebula.Pos.x + Pad,
                    Nebula.Pos.y + Pad,
                    Nebula.Rec.width - 2*Pad,
                    Nebula.Rec.height - 2*Pad
                };
                Rectangle ScarfyRec{
                    ScarfyData.Pos.x,
                    ScarfyData.Pos.y,
                    ScarfyData.Rec.width,
                    ScarfyData.Rec.height
                };
                if (CheckCollisionRecs(NebRec, ScarfyRec))
                {
                    Collision = true;
                }
            }

            if (Collision)
            {
                RoundOver = true;                
            }
            else if (Checkpoint <= WindowDimensions[0]*.5)
            {
                RoundOver = true;
            }
            else if (RoundScore + TotalScore >= 100)
            {
                // Win Game
                WinGame = true;
            }
            else
            {
                // Loop through nebulae to update animation
                for (int i=0; i<SizeOfNebulae; i++)
                {
                    DrawTextureRec(Nebula, Nebulae[i].Rec, Nebulae[i].Pos, WHITE);
                }
                // draw scarfy
                DrawTextureRec(Scarfy, ScarfyData.Rec, ScarfyData.Pos, WHITE);
                DrawHUD(dT);                
            }
        }
        EndDrawing();        
    }
     UnloadTexture(Scarfy);
     UnloadTexture(Nebula);
     UnloadTexture(Background);
     UnloadTexture(Midground);
     UnloadTexture(Foreground);
     CloseWindow();
}
1 Like

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms