This lecture was a beast. As others have mentioned the material went fast and a lot more was covered than I was ready to absorb. At 4:14 he talks about changing direction
to velocity
in the Character::Tick()
but quietly deletes an entire line. This slipped by me glancing back over at my own code on the other screen and I fell into the same problem that @Mark_Burke had.
As to @sleepykraken 's question I think we zero out velocity
because by this point we’ve already used it to move the character and we need it to be ready to use so the change doesn’t accumulate.
After that, I was working fine again and able to slog my way to the end only to come up with this. I still don’t quite understand this one so if you can offer any clues it’d be much appreciated.
BaseCharacter.h
#ifndef BASE_CHARACTER_H
#define BASE_CHARACTER_H
#include "raylib.h"
class BaseCharacter
{
public:
BaseCharacter();
Vector2 GetWorldPos() { return WorldPos; }
void UndoMovement();
Rectangle GetCollisionRec();
virtual void Tick(float DeltaTime);
virtual Vector2 GetScreenPos() = 0;
protected:
Vector2 ScreenPos{};
Vector2 WorldPos{};
Vector2 WorldPosLastFrame{};
Texture2D Texture{LoadTexture("characters/knight_idle_spritesheet.png")};
Texture2D Idle{LoadTexture("characters/knight_idle_spritesheet.png")};
Texture2D Run{LoadTexture("characters/knight_run_spritesheet.png")};
// 1: facing right, -1 : facing left
float RightLeft{1.f};
// animation variables
float RunningTime{};
int Frame{};
int MaxFrames{6};
float UpdateTime{1.f / 12.f};
float Speed{4.f};
float Width{};
float Height{};
float Scale{4.0f};
bool IsRunning{};
Vector2 Velocity{};
private:
};
#endif
BaseCharacter.cpp
#include "BaseCharacter.h"
#include "raymath.h"
BaseCharacter::BaseCharacter()
{
};
void BaseCharacter::Tick(float DeltaTime)
{
WorldPosLastFrame = WorldPos;
// Update Animation
RunningTime += DeltaTime;
if (RunningTime >= UpdateTime)
{
Frame++;
RunningTime = 0.f;
if (Frame >= MaxFrames)
Frame = 0;
}
// Swap Sprite w/direction & idle/run Sheet w/movement
if (Vector2Length(Velocity) != 0.0)
{
// Take Normalized Velocity, scale it to speed and add it to my world position to move the character
WorldPos = Vector2Add(WorldPos, Vector2Scale(Vector2Normalize(Velocity), Speed));
// Direction.x <= 0.f ? RightLeft = -1 : RightLeft = 1.f;
if (Velocity.x < 0.f)
RightLeft = -1;
else if (Velocity.x > 0.f)
RightLeft = 1;
Texture = Run;
}
else
{
Texture = Idle;
}
Velocity = {}; // Zero out Velocity after movement
// Draw Character
Rectangle Source{Frame * Width, 0.f, RightLeft * Width, Height};
Rectangle Dest{GetScreenPos().x, GetScreenPos().y, Scale * Width, Scale * Height};
DrawTexturePro(Texture, Source, Dest, Vector2{}, 0.f, WHITE);
}
void BaseCharacter::UndoMovement()
{
WorldPos = WorldPosLastFrame;
}
Rectangle BaseCharacter::GetCollisionRec()
{
return Rectangle
{
GetScreenPos().x,
GetScreenPos().y,
Width * Scale,
Height * Scale
};
}
Character.h
#ifndef CHARACTER_H
#define CHARACTER_H
#include "raylib.h"
#include "BaseCharacter.h"
class Character : public BaseCharacter
{
public:
Character(int WinWidth, int WinHeight);
virtual void Tick(float DeltaTime) override;
virtual Vector2 GetScreenPos() override;
private:
int WindowWidth{};
int WindowHeight{};
};
#endif
Character.cpp
#include "Character.h"
#include "raymath.h"
Character::Character(int WinWidth, int WinHeight) : WindowWidth(WinWidth),
WindowHeight(WinHeight)
{
Width = Texture.width / MaxFrames;
Height = Texture.height;
}
Vector2 Character::GetScreenPos()
{
return Vector2{
static_cast<float>(WindowWidth) / 2.0f - Scale * (0.5f * Width),
static_cast<float>(WindowHeight) / 2.0f - Scale * (0.5f * Height)};
}
void Character::Tick(float DeltaTime)
{
// Handle Movement
if (IsKeyDown(KEY_A))
Velocity.x -= 1.0;
if (IsKeyDown(KEY_D))
Velocity.x += 1.0;
if (IsKeyDown(KEY_W))
Velocity.y -= 1.0;
if (IsKeyDown(KEY_S))
Velocity.y += 1.0;
BaseCharacter::Tick(DeltaTime);
}
Enemy.h
#include "raylib.h"
#include "BaseCharacter.h"
#include "Character.h"
class Enemy : public BaseCharacter
{
public:
Enemy(Vector2 Pos, Texture2D idle_texture, Texture2D run_texture);
virtual void Tick(float DeltaTime) override;
void SetTarget(Character* Character) {Target = Character;}
virtual Vector2 GetScreenPos() override;
private:
Character* Target;
};
Enemy.cpp
#include "Enemy.h"
// #include "Character.h"
#include "raymath.h"
Enemy::Enemy(Vector2 Pos, Texture2D idle_texture, Texture2D run_texture)
{
WorldPos = Pos;
Texture = idle_texture;
Idle = idle_texture;
Run = run_texture;
Speed = 3.5f;
Width = Texture.width / MaxFrames;
Height = Texture.height;
}
void Enemy::Tick(float DeltaTime)
{
// Get "ToTarget"
Velocity = Vector2Subtract(Target->GetScreenPos(), GetScreenPos());
BaseCharacter::Tick(DeltaTime);
}
Vector2 Enemy::GetScreenPos()
{
return Vector2Subtract(WorldPos, Target->GetWorldPos());
}
Main.cpp
#include "raylib.h"
#include "raymath.h"
#include "Character.h"
#include "Prop.h"
#include "Enemy.h"
const int WindowDimensions[]{384, 384};
bool GameStarted{};
bool WinGame{};
double GameTimer{};
// This displays the button prompt and returns false until it is pressed
bool PromptSpace()
{
DrawText("Press Space", (WindowDimensions[0] * .4), (WindowDimensions[1] * .65), 15, WHITE);
DrawText("Esc to Quit", (WindowDimensions[0] * .70), (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)
{
int TrimX = -2;
int TrimY = -1;
int ShadowX = -3;
int ShadowY = -2;
int FontSize = 60;
DrawText(text, PercentWindowPosX * WindowDimensions[0] - TrimY, PercentWindowPosY * WindowDimensions[1] - TrimY, FontSize, DARKPURPLE);
DrawText(text, PercentWindowPosX * WindowDimensions[0], PercentWindowPosY * WindowDimensions[1], FontSize, PURPLE);
DrawText(text, PercentWindowPosX * WindowDimensions[0] + TrimX, PercentWindowPosY * WindowDimensions[1] + TrimY, FontSize, {0, 255, 255, 255});
DrawText(text, PercentWindowPosX * WindowDimensions[0] + ShadowX, PercentWindowPosY * WindowDimensions[1] + ShadowY, FontSize, WHITE);
}
void DisplayTitleWaitForInput()
{
ShadowAndOutline("Untitled", .20, .20);
ShadowAndOutline("Game", .32, .40);
// 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;
}
}
int main()
{
// bool Collision{}; // initialize collision Game State as false
InitWindow(WindowDimensions[0], WindowDimensions[1], "Top Down Game");
// texture variables
Texture2D Map = LoadTexture("nature_tileset/WorldMap.png");
Vector2 MapPos = {0.0, 0.0};
const float MapScale{4.0};
//int NumberOfProps{2};
Character Knight{WindowDimensions[0], WindowDimensions[1]};
Enemy Goblin{
Vector2{},
LoadTexture("characters/goblin_idle_spritesheet.png"),
LoadTexture("characters/goblin_run_spritesheet.png")
};
Prop Props[2]{
Prop{Vector2 {250.f, 250.f}, LoadTexture("nature_tileset/Rock.png")},
Prop{Vector2 {400.f, 500.f}, LoadTexture("nature_tileset/Log.png") }
};
SetTargetFPS(60);
while (!WindowShouldClose())
{
BeginDrawing();
ClearBackground(SKYBLUE);
// if game has not started
if (!GameStarted)
{
DisplayTitleWaitForInput(); // display title and keys and wait for input
}
// if the game is afoot
else if (GameStarted)
{
MapPos = Vector2Scale(Knight.GetWorldPos(), -1.f);
// Draw Map
DrawTextureEx(Map, MapPos, 0, MapScale, WHITE);
// Draw Props
for (auto Prop : Props)
{
Prop.Render(Knight.GetWorldPos());
}
Knight.Tick(GetFrameTime());
// check map bounds
if ((Knight.GetWorldPos().x < 0.f) ||
(Knight.GetWorldPos().y < 0.f) ||
(Knight.GetWorldPos().x + WindowDimensions[0] > Map.width * MapScale) ||
(Knight.GetWorldPos().y + WindowDimensions[1] > Map.height * MapScale))
{
Knight.UndoMovement();
}
// loop through props for Collisions
for (auto Prop : Props)
{
if (CheckCollisionRecs(Prop.GetCollisionRec(Knight.GetWorldPos()), Knight.GetCollisionRec()))
{
Knight.UndoMovement();
}
}
Goblin.Tick(GetFrameTime());
Goblin.SetTarget(&Knight);
}
// if my player has won
else if (WinGame)
{
// CheckForCheatingDisplayVictory();
// DelayPromptAndResetGame(dT);
// Checkpoint = CheckpointDistance;
}
EndDrawing();
}
// UnloadTexture(Texture);
// UnloadTexture(Idle);
// UnloadTexture(Run);
UnloadTexture(Map);
}
Prop.h
#include "raylib.h"
class Prop
{
public:
Prop(Vector2 Pos, Texture2D Tex);
void Render(Vector2 KnightPos);
Rectangle GetCollisionRec(Vector2 KnightPos);
private:
Vector2 WorldPos{};
Texture2D Texture{};
Vector2 ScreenPos{};
float Scale{4.f};
};
Prop.cpp
#include "Prop.h"
#include "raymath.h"
Prop::Prop(Vector2 Pos, Texture2D Tex) : WorldPos(Pos),
Texture(Tex)
{
}
void Prop::Render(Vector2 KnightPos)
{
Vector2 ScreenPos{Vector2Subtract(WorldPos, KnightPos) };
DrawTextureEx(Texture, ScreenPos, 0.f, Scale, WHITE);
}
Rectangle Prop::GetCollisionRec(Vector2 KnightPos)
{
Vector2 ScreenPos{Vector2Subtract(WorldPos, KnightPos) };
return Rectangle{
ScreenPos.x,
ScreenPos.y,
Texture.width * Scale,
Texture.height * Scale
};
}