Jump to content

Recommended Posts

Posted (edited)

I wanted to start using SDL2 libraries in C++.

This is a Hello World project that covers some of the essentials of programming with SDL2.

I will use it when I forget how to create an SDL2 project.

  1. Loads a scrolling background image for world.
  2. Mouse position, left click, and double click example.
  3. Hotkeys and cooldown timers to prevent hotkey spam.
  4. Plays background music. (ALT + P)
  5. Plays sound effects changing sprite type. (numpad: /, *, -, +)
  6. Features a Player class for multiplayer.
  7. Player collision detection.
  8. Uses a Window class from Lazyfoo for maximize button, fullscreen ext..
  9. Built using MSVS 2013 (free).  All dlls and libraries included and linked (for MSVS) using relative paths.
  10. You can probably run the SDL2_HelloWorld.sln without setting up any libraries.
  11. Note SDL_Net is setup but not used.
  12. Delta time used to create consistent frame rate across machines.
  13. Player animation sprite sheet:
Spoiler

DW3_Char_SpriteSheet.png


 

 

Demo: Slimy's First Kiss

Download Source and project: http://songersoft.com/programming/sdl2_helloworld/sdl2_helloworld_about.phtml

This isn't meant to be amazing.  It's just to help get started with SDL2.

I will consider any criticism.

main.cpp

// This example HelloWorld followed this video:
// https://www.youtube.com/watch?v=M4Jgz0wEQxY
// Turned into this
// https://www.youtube.com/watch?v=yRpen8jOa08&list=PL77-op_SRaiEuC0YC43ZAUJJwL_G_C2z8&index=15
// Window class was copied from Lazyfoo
// http://lazyfoo.net/tutorials/SDL/35_window_events/index.php

#include <iostream>
#include <string>

#include "SDL.h"
#include "SDL_image.h"
#include "SDL_ttf.h"
#include "SDL_mixer.h"
#include "SDL_net.h"
#include "Player.h"
#include "Window.h"

using namespace std;

// Prototypes
SDL_Texture *LoadTexture(string filepath, SDL_Renderer *renderTarget);
void logSDLError(const string);
SDL_Rect sdlrect(int, int, int, int);
SDL_Surface *OptimizedSurface(string filepath, SDL_Surface *windowSurface);

// if(okAlt) either Alt key is pressed OrKeyALT
#define okAlt (k[SDL_SCANCODE_RALT] || k[SDL_SCANCODE_LALT])

int main(int argc, char *argv[]) {

    // Needed for the initialize block
    Window window;
    SDL_Renderer *renderTarget = nullptr;

    // Initialize SDL2
    if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
        logSDLError("SDL_Init ");
    }

    // Initialize SDL_image
    if (!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG)) {
        cout << "Could not initialize SDL_image: " << IMG_GetError() << std::endl;
        SDL_Delay(5500);
    }

    // Initialize TTF
    if (TTF_Init() < 0)
        cout << "TTF_Init " << TTF_GetError() << endl;

    //Create window
    if (!window.init("SDL2 Hello World", 640, 480, "../../Graphics/iconbmp.bmp")) {
        printf("Window could not be created! SDL Error: %s\n", SDL_GetError());
    }
    else
    {
        //Create renderer for window
        renderTarget = window.createRenderer();
        if (renderTarget == NULL) {
            printf("Renderer could not be created! SDL Error: %s\n", SDL_GetError());
        }
    }

    // Audio
    if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) {
        cout << "Mix_OpenAudio: " << Mix_GetError() << endl;
    }

    // Load sounds: Run chunk if less than 10 sec, music if greater (generally)
    //Mix_Music *bgm = Mix_LoadMUS("../../Sounds/overworld.mid"); // Can't Pause MIDI
    Mix_Music *bgm = Mix_LoadMUS("../../Sounds/bensound-goinghigher.mp3");
    cout << Mix_GetError() << endl;

    // Font and text
    SDL_Texture *text_texture = nullptr;
    SDL_Color textColor = { 144, 77, 255, 255 };
    TTF_Font *font = TTF_OpenFont("../../Graphics/Fonts/VCR_OSD_MONO.ttf", 20);
    if (font) {
        SDL_Surface *textSurf = TTF_RenderText_Solid(font, "Hello world", textColor);
        text_texture = SDL_CreateTextureFromSurface(renderTarget, textSurf);
        SDL_FreeSurface(textSurf);
    } else
        cout << TTF_GetError() << endl;

    SDL_Rect textRect = { 0, 0, 0, 0 };// Position of text on window
    // Get the size of the text_texture
    SDL_QueryTexture(text_texture, NULL, NULL, &textRect.w, &textRect.h);

    const int playerMax = 8;
    int players = 2;

    float prevTime = 0, currentTime = 0, deltaTime = 0;
    bool fullscreen_toggle = 0;
    int i = 0, ii = 0;
    int redraw = 1;
    bool done = 0;

    // Load player sprite sheet and background texture
    SDL_Texture *spritesheet_texture = LoadTexture("../../Graphics/DW3_Char_SpriteSheet.png", renderTarget);
    SDL_Texture *bg_texture = LoadTexture("../../Graphics/dw3-over.png", renderTarget);
    int bg_texture_w = 0, bg_texture_h = 0;

    // Point k to Keyboard state (only needs done here, not in main loop)
    const Uint8 *k = SDL_GetKeyboardState(NULL);

    SDL_Event windowEvent;

    // Stop keys spamming (like music ON / OFF)
    float key_cooldown_timer_max = 500.0f, key_cooldown_timer = 0;

    SDL_Rect cameraRect = { 0, 0, window.getWidth(), window.getHeight()};

    // Construct default players
    Player player[playerMax];
    // Customize players (texture and position)
    for (i = 0; i < players; i++)//(sprite texture, position x, position y)
        player[i].Player_new(spritesheet_texture, window.getWidth() / 2 + i * 16, window.getHeight() / 2);

    // Get the size of bg_texture
    SDL_QueryTexture(bg_texture, NULL, NULL, &bg_texture_w, &bg_texture_h);

    // Main message loop
    while (!done) {
        prevTime = currentTime;
        currentTime = SDL_GetTicks();
        deltaTime = (currentTime - prevTime) / 1000.0f;
        while (SDL_PollEvent(&windowEvent) != 0) {
            // Check for events
            if (windowEvent.type == SDL_QUIT) {
                // Window Exit
                done = 1;
            } else if(windowEvent.type == SDL_MOUSEBUTTONDOWN) {
                // Mouse buttons
                if (windowEvent.button.clicks == 2) {
                    // Double left click
                    redraw = 1;
                } else if (windowEvent.button.button == SDL_BUTTON_LEFT) {
                    // Single left click
                    redraw = 1;
                }
                // Output mouse position
                cout << "Mouse: " << windowEvent.button.x << " " << windowEvent.button.y << endl;
            }
        }// if(SDL_PollEvent(&windowEvent))

        if (okAlt) {
            // Either ALT key
            if (k[SDL_SCANCODE_RETURN]) {
                // ALT + ENTER, toggle fullscreen
                // Consider matching current window resolution to closest preset array of valid full screen resolutions.
                fullscreen_toggle = 1;
            }
            if (k[SDL_SCANCODE_P]) {
                if (SDL_GetTicks() - key_cooldown_timer > key_cooldown_timer_max) {
                    // Action cooldown timer is ready.  Avoids ON / OFF flicker
                    // Play Music
                    if (!Mix_PlayingMusic()) {
                        cout << "PLAY";
                        Mix_PlayMusic(bgm, -1);
                    }
                    else if (Mix_PausedMusic()) {
                        cout << "RESUME";
                        Mix_ResumeMusic();
                    }
                    else {
                        cout << "PAUSE";
                        Mix_PauseMusic();
                    }
                    key_cooldown_timer = SDL_GetTicks();
                }
            }// SDL_SCANCODE_P
            if (k[SDL_SCANCODE_S]) {
                // Stop Music
                Mix_HaltMusic();
            }
        }// okAlt

        //Handle window events
        fullscreen_toggle = window.handleEvent(windowEvent, renderTarget, fullscreen_toggle);

        // Player Update and IntersectsWith
        for (i = 0; i < players; i++) {
            player[i].Update(deltaTime, k);
            for (ii = 0; ii < players; ii++) {
                if(i != ii)// Not self
                    player[i].IntersectsWith(player[ii]);
            }
        }

        // Camera
        cameraRect.x = player[0].GetOriginX() - window.getWidth() / 2;
        cameraRect.y = player[0].GetOriginY() - window.getHeight() / 2;
        // Normalize
        if (cameraRect.x < 0)
            cameraRect.x = 0;
        if (cameraRect.y < 0)
            cameraRect.y = 0;

        if (cameraRect.x + cameraRect.w >= bg_texture_w)
            cameraRect.x = bg_texture_w - window.getWidth();
        if (cameraRect.y + cameraRect.h >= bg_texture_h)
            cameraRect.y = bg_texture_h - window.getHeight();

        redraw = 1; // Don't judge me
        if (redraw) {
            // Redraw should definatly pretty much be at the end of loop
            // Clear window
            SDL_RenderClear(renderTarget);

            // Copy camera from bg_texture
            SDL_RenderCopy(renderTarget, bg_texture, &cameraRect, NULL);

            // Draw players
            for (i = 0; i < players; i++)
                player[i].Draw(renderTarget, cameraRect);

            // Print text
            SDL_RenderCopy(renderTarget, text_texture, NULL, &textRect);

            // Show the rendered content
            SDL_RenderPresent(renderTarget);
            redraw = 0;
        }// if(redraw)
        
    }// while(true)
    
    // Free windows
    window.free();

    SDL_DestroyTexture(bg_texture);
    SDL_DestroyTexture(spritesheet_texture);
    SDL_DestroyTexture(text_texture);
    TTF_CloseFont(font);

    // Free Renderers
    SDL_DestroyRenderer(renderTarget);

    // Free music
    Mix_FreeMusic(bgm);

    // Close systems
    Mix_Quit();
    TTF_Quit();
    IMG_Quit();
    SDL_Quit();
    return EXIT_SUCCESS;
}// main()

SDL_Texture *LoadTexture(string filepath, SDL_Renderer *renderTarget) {
    // Create texture
    SDL_Texture *texture = nullptr;
    // Load surface
    SDL_Surface *surface = IMG_Load(filepath.c_str());
    if (surface == NULL) {
        // surface didn't load
        logSDLError("LoadTexture surface ");
    } else {
        SDL_SetColorKey(surface, SDL_TRUE, SDL_MapRGB(surface->format, 0, 0, 0));
        texture = SDL_CreateTextureFromSurface(renderTarget, surface);
        //SDL_SetTextureColorMod(texture, 120, 150, 140);
        if (texture == NULL)
            logSDLError("LoadTexture texture ");
    }

    SDL_FreeSurface(surface);

    return texture;
}

void logSDLError(const string msg){
    cout << msg << " Error: " << SDL_GetError() << endl;
    SDL_Delay(4000);
}

SDL_Rect sdlrect(int x, int y, int w, int h) {
    SDL_Rect rect = {x, y, w, h};
    return rect;
}

SDL_Surface *OptimizedSurface(string filepath, SDL_Surface *windowSurface) {
    SDL_Surface *optimizedSurf = nullptr;
    SDL_Surface *surface = IMG_Load(filepath.c_str());
    if (surface == NULL) {
        cout << "Error: " << endl;
    }
    else {
        optimizedSurf = SDL_ConvertSurface(surface, windowSurface->format, 0);
        if (optimizedSurf == NULL)
            cout << "Error: " << endl;
    }

    SDL_FreeSurface(surface);

    return optimizedSurf;
}

Player.h

#pragma once

#include <iostream>

#include "SDL.h"
#include <String>
#include "SDL_mixer.h"

using namespace std;

enum player_control {
    ePlayer_control_up,
    ePlayer_control_down,
    ePlayer_control_left,
    ePlayer_control_right,
    ePlayer_control_type_minus,
    ePlayer_control_type_plus
};

class Player
{
private:
    SDL_Rect cropRect;
    SDL_Texture *texture;
    int spritesheet_texture_w, spritesheet_texture_h;//size of spritesheet texture
    bool isActive;//animation timer
    SDL_Scancode keys[6];
    int frame_w, frame_h;//16, 16
    float frameTimerMax;
    float frameTimer;//0..frameTimerMax
    int frame;//0..1
    int frameMax;//2
    int way;//0..3 
    int classType;//0..53
    float moveSpeed;
    float cast_cooldown_max;
    Mix_Chunk *soundEffect;
    int originX, originY;
    int radius;
    Uint32 key_cooldown_timer;
    string name;

public:
    void Player_new(SDL_Texture *spritesheet_texture, int x, int y);
    Player();
    ~Player();

    void Update(float delta, const Uint8 *keyState);
    void Draw(SDL_Renderer *renderTarget, SDL_Rect cameraRect);
    bool IntersectsWith(Player &p);
    int GetOriginX();
    int GetOriginY();
    int GetRadius();

    SDL_Rect positionRect;
    
};

Player.cpp

#pragma once

#include "Player.h"
#include "SDL_image.h"
#include "SDL_mixer.h"
#include <iostream>
#include <String>
#include <cmath>
using namespace std;

Player::Player() {
    cropRect = {0, 0, 16, 16};
    texture = nullptr;
    spritesheet_texture_w = spritesheet_texture_h = 0;//size of spritesheet texture
    isActive = false;//animation timer
    keys[6];
    frame_w = frame_h = 16;//16, 16
    frameTimerMax = 0.5f;
    frameTimer = 0;//0..frameTimerMax
    frame = 0;//0..1
    frameMax = 2;//2
    way = 0;//0..3 
    classType = 0;//0..53
    moveSpeed = 200.0f;
    radius = frame_w / 2;
    soundEffect = nullptr;
    key_cooldown_timer = 0;
    cast_cooldown_max = 190.0f;

    static int playerNumber = 0;
    playerNumber++;

    name = "Player:" + to_string(playerNumber);

    // Setup keys for player 1
    // ** I see the enum didn't exist when this was written **
    if (playerNumber == 1) {
        keys[0] = SDL_SCANCODE_W;
        keys[1] = SDL_SCANCODE_S;
        keys[2] = SDL_SCANCODE_A;
        keys[3] = SDL_SCANCODE_D;
        keys[4] = SDL_SCANCODE_KP_MINUS;
        keys[5] = SDL_SCANCODE_KP_PLUS;
    }
    else {
        // Setup keys all other players (not 1)
        keys[0] = SDL_SCANCODE_UP;
        keys[1] = SDL_SCANCODE_DOWN;
        keys[2] = SDL_SCANCODE_LEFT;
        keys[3] = SDL_SCANCODE_RIGHT;
        keys[4] = SDL_SCANCODE_KP_DIVIDE;
        keys[5] = SDL_SCANCODE_KP_MULTIPLY;
    }

}//Player::Player()

void Player::Player_new(SDL_Texture *spritesheet_texture, int x, int y)
{
    // This is new player
    isActive = false;

    // Read size of spritesheet texture
    SDL_QueryTexture(spritesheet_texture, NULL, NULL, &spritesheet_texture_w, &spritesheet_texture_h);

    texture = spritesheet_texture;

    // spritesheet rect
    cropRect = { 0, 0, 16, 16 };

    // player screen position
    positionRect = { x, y, 16, 16 };

    // Defaults
    moveSpeed = 200.0f;
    radius = frame_w / 2;
    frameTimer = 0;
    frame = 0;
    frameMax = 2;
    way = 0;
    classType = 0;
    originX = frame_w / 2;
    originY = frame_h / 2;

    soundEffect = Mix_LoadWAV("../../Sounds/whipcrack1.wav");

    cout << "Name: " << name << endl;

}//Player::Player_new()

void Player::Update(float delta, const Uint8 *keyState) {
    isActive = true;
    if (keyState[keys[ePlayer_control_up]]) {
        way = 0;
        positionRect.y -= moveSpeed * delta;
        cropRect.x = (way * cropRect.w * frameMax) + (frame * cropRect.w);
    }
    if (keyState[keys[ePlayer_control_down]]) {
        way = 1;
        positionRect.y += moveSpeed * delta;
        cropRect.x = (way * cropRect.w * frameMax) + (frame * cropRect.w);
    }
    if (keyState[keys[ePlayer_control_left]]) {
        way = 3;
        positionRect.x -= moveSpeed * delta;
        cropRect.x = (way * cropRect.w * frameMax) + (frame * cropRect.w);
    }
    if (keyState[keys[ePlayer_control_right]]) {
        way = 2;
        positionRect.x += moveSpeed * delta;
        cropRect.x = (way * cropRect.w * frameMax) + (frame * cropRect.w);
    }
    if (keyState[keys[ePlayer_control_type_minus]]) {
        if (SDL_GetTicks() - key_cooldown_timer > cast_cooldown_max) {
            key_cooldown_timer = SDL_GetTicks();
            Mix_PlayChannel(-1, soundEffect, 0);
            classType--;
            if (classType < 0) classType = spritesheet_texture_h / cropRect.h - 1;
            cropRect.y = classType * cropRect.h;
        }
    }   
    if (keyState[keys[ePlayer_control_type_plus]]) {
        if (SDL_GetTicks() - key_cooldown_timer > cast_cooldown_max) {
            key_cooldown_timer = SDL_GetTicks();
            Mix_PlayChannel(-1, soundEffect, 0);
            classType++;
            if (classType >= spritesheet_texture_h / cropRect.h) classType = 0;
            cropRect.y = classType * cropRect.h;
        }
    }

    if (isActive) {
        frameTimer += delta;
        
        if (frameTimer >= frameTimerMax){
            frameTimer = 0;
            cropRect.x = (way * cropRect.w * frameMax) + (frame * cropRect.w);
            if (frame >= frameMax - 1)
                frame = 0;
            else
                frame++;
        }
    }
    else {
        frameTimer = 0;
    }
}//Player::Update()

void Player::Draw(SDL_Renderer *renderTarget, SDL_Rect cameraRect) {
    SDL_Rect drawingRect = { positionRect.x - cameraRect.x, positionRect.y - cameraRect.y, positionRect.w, positionRect.h };
    SDL_RenderCopy(renderTarget, texture, &cropRect, &drawingRect);
}

bool Player::IntersectsWith(Player &p){
    if (sqrt(pow(GetOriginX() - p.GetOriginX(), 2) + pow(GetOriginY() - p.GetOriginY(), 2)) >= radius + p.GetRadius()) {
        SDL_SetTextureColorMod(texture, 255, 255, 255);
        return false;
    }
    SDL_SetTextureColorMod(texture, 255, 0, 0);
    return true;
}

Player::~Player()
{
    SDL_DestroyTexture(texture);
    Mix_FreeChunk(soundEffect);
}

int Player::GetOriginX() { return positionRect.x + originX; }

int Player::GetOriginY() { return positionRect.y + originY; }

int Player::GetRadius() { return radius; }

 

Edited by Xandy

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...