Related
I am trying to adopt the Marco Monster's Physics Demo (document: http://www.asawicki.info/Mirror/Car%20Physics%20for%20Games/Car%20Physics%20for%20Games.html and C reference code: https://github.com/spacejack/carphysics2d/blob/master/marco/Cardemo.c) in C++.
I ran into the problem that the car spins around itself and moves along the axis in an unpredictable manner (as it reacts to the input but does so unpredictably). I have spend the last 4 days trying to find the problem and got nothing. Please help as I am getting desperate with that. I have separated functionality of the car into separate classes (for better maintenance) and deduced that the problem occurs within the Wheel class and in Car class. Here is the code:
Wheel.h
class Wheel
{
public:
Wheel(const bool &isABSOn, const float &frontAxleToCG, const float &rearAxleToCG, const float &tireGripValue, const float &lockedTireGripCoef,
const float &lateralStiffnessFront, const float &lateralStiffnessRear, const float &brakeForceCoef, const float &ebrakeForceCoef,
const float &brakeTorque);
void SetValues(bool &isEbrakeOn, float &drivetrainTorque, float &steeringAngle, float &brakingInput,
float &frontAxleLoad, float &rearAxleLoad, float &surfaceCoefficient, float &angularVelocity, Vector2f &localVelocity);
void Update();
Vector2f GetSumForce();
float GetLateralTorque();
private:
bool m_IsEBrakeOn;
const bool m_IsABSOn;
float m_YawSpeed, m_VehicleAngularVelocity, m_VehicleRotationAngle, m_VehicleSideSlip, m_VehicleSlipAngleFrontAxle, m_VehicleSlipAngleRearAxle,
m_VehicleSteeringAngleRadInput,
m_SurfaceTypeGripCoefficient, m_DrivetrainTorqueNm, m_BrakingForceInputPercentage, m_FrontAxleLoad, m_RearAxleLoad;
const float m_CGtoFrontAxle, m_CGtoRearAxle, m_BaseTireGripValue, m_LockedTireGripCoefficent, m_LateralStiffnessFront,
m_LateralStiffnessRear, m_BreakForceCoefficent, m_EBrakeForceCoefficent, m_BrakeTorqueLimit, m_StableSpeedBoundary;
Vector2f m_LocalVehicleVelocity, m_VehicleLateralForceFront, m_VehicleLateralForceRear, m_VehicleLongtitudonalForceRear;
float FrontTireGripValue();
float RearTireGripValue();
float CombinedBrakingForceValueRearAxle();
};
Wheel.cpp
Wheel::Wheel(const bool &isABSOn, const float &frontAxleToCG, const float &rearAxleToCG, const float &tireGripValue, const float &lockedTireGripCoef,
const float &lateralStiffnessFront, const float &lateralStiffnessRear, const float &brakeForceCoef, const float &ebrakeForceCoef,
const float &brakeTorque)
: m_IsABSOn{ isABSOn }
, m_CGtoFrontAxle{ frontAxleToCG }
, m_CGtoRearAxle{ rearAxleToCG }
, m_BaseTireGripValue{ tireGripValue }
, m_LockedTireGripCoefficent{ lockedTireGripCoef }
, m_LateralStiffnessFront { lateralStiffnessFront }
, m_LateralStiffnessRear{ lateralStiffnessRear }
, m_BreakForceCoefficent{ brakeForceCoef }
, m_EBrakeForceCoefficent{ ebrakeForceCoef }
, m_BrakeTorqueLimit{ brakeTorque }
, m_StableSpeedBoundary{ 40.f } {}
void Wheel::Update()
{
if ((-0.01f < m_LocalVehicleVelocity.x) || (m_LocalVehicleVelocity.x < 0.01f))
{
m_YawSpeed = 0.f;
}
else
{
m_YawSpeed = ((m_CGtoFrontAxle + m_CGtoRearAxle) / 2.f) * m_VehicleAngularVelocity;
}
if ((-0.01f < m_LocalVehicleVelocity.x) || (m_LocalVehicleVelocity.x < 0.01f))
{
m_VehicleRotationAngle = 0.f;
}
else
{
m_VehicleRotationAngle = std::atan2(m_YawSpeed, m_LocalVehicleVelocity.x);
}
if ((-0.01f < m_LocalVehicleVelocity.x) || (m_LocalVehicleVelocity.x < 0.01f))
{
m_VehicleSideSlip = 0.f;
}
else
{
m_VehicleSideSlip = std::atan2(m_LocalVehicleVelocity.y, m_LocalVehicleVelocity.x);
}
m_VehicleSlipAngleFrontAxle = m_VehicleSideSlip + m_VehicleRotationAngle - m_VehicleSteeringAngleRadInput;
m_VehicleSlipAngleRearAxle = m_VehicleSideSlip - m_VehicleRotationAngle;
m_VehicleLateralForceFront.x = 0.f;
m_VehicleLateralForceFront.y = m_LateralStiffnessFront * m_VehicleSlipAngleFrontAxle;
m_VehicleLateralForceFront.y = std::fminf(FrontTireGripValue(), m_VehicleLateralForceFront.y);
m_VehicleLateralForceFront.y = std::fmaxf(-FrontTireGripValue(), m_VehicleLateralForceFront.y);
m_VehicleLateralForceFront.y *= m_FrontAxleLoad;
m_VehicleLateralForceRear.x = 0.f;
m_VehicleLateralForceRear.y = m_LateralStiffnessRear * m_VehicleSlipAngleRearAxle;
m_VehicleLateralForceRear.y = std::fminf(RearTireGripValue(), m_VehicleLateralForceRear.y);
m_VehicleLateralForceRear.y = std::fmaxf(-RearTireGripValue(), m_VehicleLateralForceRear.y);
m_VehicleLateralForceRear.y *= m_RearAxleLoad;
m_VehicleLongtitudonalForceRear.x = m_SurfaceTypeGripCoefficient * (m_DrivetrainTorqueNm - (CombinedBrakingForceValueRearAxle() * utils::Sign(m_LocalVehicleVelocity.x)));
m_VehicleLongtitudonalForceRear.y = 0.f;
}
Vector2f Wheel::GetSumForce()
{
if (m_LocalVehicleVelocity.Length() < 1.0f && m_DrivetrainTorqueNm < 0.5f)
{
m_LocalVehicleVelocity.x = m_LocalVehicleVelocity.y = 0.f;
m_VehicleLateralForceFront.x = m_VehicleLateralForceFront.y = m_VehicleLateralForceRear.x = m_VehicleLateralForceRear.y = 0.f;
}
return Vector2f
{
m_VehicleLongtitudonalForceRear.x + std::sinf(m_VehicleSteeringAngleRadInput) * m_VehicleLateralForceFront.x + m_VehicleLateralForceRear.x,
m_VehicleLongtitudonalForceRear.y + std::cosf(m_VehicleSteeringAngleRadInput) * m_VehicleLateralForceFront.y + m_VehicleLateralForceRear.y
};
}
float Wheel::GetLateralTorque()
{
return m_CGtoFrontAxle * m_VehicleLateralForceFront.y - m_CGtoRearAxle * m_VehicleLateralForceRear.y;
}
void Wheel::SetValues(bool &isEbrakeOn, float &drivetrainTorque, float &steeringAngle, float &brakingInput,
float &frontAxleLoad, float &rearAxleLoad, float &surfaceCoefficient, float &angularVelocity, Vector2f &localVelocity)
{
m_IsEBrakeOn = isEbrakeOn;
m_DrivetrainTorqueNm = drivetrainTorque;
m_VehicleSteeringAngleRadInput = steeringAngle;
m_BrakingForceInputPercentage = brakingInput;
m_FrontAxleLoad = frontAxleLoad;
m_RearAxleLoad = rearAxleLoad;
m_SurfaceTypeGripCoefficient = surfaceCoefficient;
m_LocalVehicleVelocity = localVelocity;
m_VehicleAngularVelocity = angularVelocity;
}
float Wheel::CombinedBrakingForceValueRearAxle()
{
return (m_BrakeTorqueLimit * m_BrakingForceInputPercentage);
}
float Wheel::FrontTireGripValue()
{
return m_BaseTireGripValue * m_SurfaceTypeGripCoefficient;
}
float Wheel::RearTireGripValue()
{
if ((CombinedBrakingForceValueRearAxle() > m_DrivetrainTorqueNm) && (!m_IsABSOn) && (m_LocalVehicleVelocity.Length() > m_StableSpeedBoundary))
{
return m_BaseTireGripValue * m_LockedTireGripCoefficent * m_SurfaceTypeGripCoefficient;
}
else
{
return m_BaseTireGripValue * m_SurfaceTypeGripCoefficient;
}
}
Car.h
class Car
{
public:
Car(VehicleCfg *pVehicleSpecs);
InputControl *m_pThisSteeringAndPedals;
void Draw() const;
void Update(float &elapsedSec);
private:
bool m_NOSStatus, m_IsEBrakeOn;
int m_GearShifterInput;
float m_VehicleThrottleInpute, m_VehicleSteeringAngleRadInput, m_VehicleBrakeInput,
m_DrivetrainTorqueOutput, m_FrontAxleLoad, m_RearAxleLoad,
m_ElapsedSec, m_VehicleHeadingDirectionAngleRad, m_CSHeading, m_SNHeading,
m_VehicleRotationAngle, m_YawSpeed, m_VehicleAngularVelocity, m_VehicleSideSlip,
m_VehicleSlipAngleFrontAxle, m_VehicleSlipAngleRearAxle,
m_SurfaceCoefficent, m_AngularTorque, m_AngularAcceleration, m_VehicleHealthStatus;
const float m_FrontToCG, m_RearToCG, m_CarMass, m_Inertia, m_RollingResistance, m_DragCoefficient;
Point2f m_WorldVehicleCoordinate;
Vector2f m_LocalVehicleVelocity, m_WorldVehicleVelocity, m_VehicleLocalAcceleration, m_VehicleWorldAcceleration,
m_WheelForces, m_ResistanceForces, m_TotalForce;
Suspension *m_pThisSuspension;
Drivetrain *m_pThisDrivetrain;
Wheel *m_pThisWheel;
ModularRenderer *m_pThisVehicleDrawn;
};
Car.cpp
void Car::Update(float &elapsedSec)
{
m_ElapsedSec = elapsedSec;
m_GearShifterInput = m_pThisSteeringAndPedals->GetCurrentGearValue();
m_VehicleThrottleInpute = m_pThisSteeringAndPedals->GetCurrentThrottleValue(m_ElapsedSec, m_VehicleThrottleInpute);
m_VehicleSteeringAngleRadInput = m_pThisSteeringAndPedals->GetCurrentSteeringValue(m_ElapsedSec);
m_VehicleBrakeInput = m_pThisSteeringAndPedals->GetCurrrentBrakeValue(m_ElapsedSec);
m_NOSStatus = m_pThisSteeringAndPedals->GetIsNOSOnValue();
m_IsEBrakeOn = m_pThisSteeringAndPedals->GetIsEBrakeOnValue();
m_CSHeading = std::cosf(m_VehicleHeadingDirectionAngleRad);
m_SNHeading = std::sinf(m_VehicleHeadingDirectionAngleRad);
m_LocalVehicleVelocity.x = m_CSHeading * m_WorldVehicleVelocity.y + m_SNHeading * m_WorldVehicleVelocity.x;
m_LocalVehicleVelocity.y = -m_SNHeading * m_WorldVehicleVelocity.y + m_CSHeading * m_WorldVehicleVelocity.x;
m_pThisDrivetrain->SetValues(m_NOSStatus, m_GearShifterInput, m_VehicleThrottleInpute, m_LocalVehicleVelocity.Length());
m_DrivetrainTorqueOutput = m_pThisDrivetrain->GetDrivetrainOutput(m_ElapsedSec);
m_pThisSuspension->SetValues(m_VehicleLocalAcceleration, m_LocalVehicleVelocity.Length());
m_FrontAxleLoad = m_pThisSuspension->GetFrontAxleWeight();
m_RearAxleLoad = m_pThisSuspension->GetRearAxleWeight();
m_pThisWheel->SetValues(m_IsEBrakeOn, m_DrivetrainTorqueOutput, m_VehicleSteeringAngleRadInput, m_VehicleBrakeInput, m_FrontAxleLoad,
m_RearAxleLoad, m_SurfaceCoefficent, m_VehicleAngularVelocity, m_LocalVehicleVelocity);
m_pThisWheel->Update();
m_WheelForces = m_pThisWheel->GetSumForce();
m_AngularTorque = m_pThisWheel->GetLateralTorque();
m_ResistanceForces.x = -((m_RollingResistance * m_LocalVehicleVelocity.x) + (m_DragCoefficient * m_LocalVehicleVelocity.x * std::abs(m_LocalVehicleVelocity.x)));
m_ResistanceForces.y = -((m_RollingResistance * m_LocalVehicleVelocity.y) + (m_DragCoefficient * m_LocalVehicleVelocity.y * std::abs(m_LocalVehicleVelocity.y)));
m_TotalForce.x = m_WheelForces.x + m_ResistanceForces.x;
m_TotalForce.y = m_WheelForces.y + m_ResistanceForces.y;
m_VehicleLocalAcceleration.x = m_TotalForce.x / m_CarMass;
m_VehicleLocalAcceleration.y = m_TotalForce.y / m_CarMass;
if (m_WorldVehicleVelocity.Length() < 1.0f && m_VehicleThrottleInpute < 0.5f)
{
m_LocalVehicleVelocity.x = m_LocalVehicleVelocity.y = 0.f;
m_VehicleAngularVelocity = m_AngularTorque = m_AngularAcceleration = 0.f;
}
m_AngularAcceleration = m_AngularTorque / m_Inertia;
m_VehicleWorldAcceleration.x = m_CSHeading * m_VehicleLocalAcceleration.y + m_SNHeading * m_VehicleLocalAcceleration.x;
m_VehicleWorldAcceleration.y = -(m_SNHeading) * m_VehicleLocalAcceleration.y + m_CSHeading * m_VehicleLocalAcceleration.x;
m_WorldVehicleVelocity.x += m_ElapsedSec * m_VehicleWorldAcceleration.x;
m_WorldVehicleVelocity.y += m_ElapsedSec * m_VehicleWorldAcceleration.y;
m_WorldVehicleCoordinate.x += m_ElapsedSec * m_WorldVehicleVelocity.x;
m_WorldVehicleCoordinate.y += m_ElapsedSec * m_WorldVehicleVelocity.y;
std::cout << "m_WorldVehicleCoordinate: " << m_WorldVehicleCoordinate.x << ", " << m_WorldVehicleCoordinate.y << "\n";
m_VehicleAngularVelocity += m_ElapsedSec * m_AngularAcceleration;
m_VehicleHeadingDirectionAngleRad += m_ElapsedSec * m_VehicleAngularVelocity;
m_pThisVehicleDrawn->SetVariables(int(0), int(0), int(0), int(0), m_VehicleHeadingDirectionAngleRad, m_VehicleSteeringAngleRadInput, m_WorldVehicleCoordinate);
}
void Car::Draw() const
{
m_pThisVehicleDrawn->DrawTheVehicle();
}
I think that the error occurs due to some sort of singularity that occurs in the calculations but I fail to see where that occurs.
Since the car spins around, I looked at your use of angular velocity. The m_VehicleAngularVelocity value is not initialized in either class, so it has an indeterminate value. The only time it has a value set is in your check for the car being stopped.
The unpredictable motion is likely a similar problem.
You should initialize all your class members in a constructor to avoid those problems.
Why does Wheel::SetValues take all its parameters by reference? Since it is just copying them to internal variables, and they are basic types, just pass them in by value.
When I try to compile the following code:
Alien::Action::Action(ActionType type, float x, float y) {
Action::type = type;
pos = Vec2(x, y);
}
Alien::Alien(float x, float y, int nMinions) {
srand(time(NULL));
sp = Sprite("img/alien.png");
box = Rect(x, y, sp.GetWidth(), sp.GetHeight());
box.x = x - box.h/2;
box.y = y - box.w/2;
hitpoints = 100;
speed.x = 0.5;
speed.y = 0.5;
minionArray = std::vector<Minion>();
for(int i = 0; i < nMinions; i++) {
int a = rand()%501;
float b = a/1000.0;
float c = b+1;
minionArray.emplace_back(Minion(get(), i*(360/nMinions), c));
}
taskQueue = std::queue<Action>();
}
The IDE (Eclipse) gives the following error message: "undefined reference to 'vtable for Alien'" (line 6 of the code). Since there's no virtual method inside Alien, I don't know the cause of the error. The following is the header file for Alien:
#ifndef ALIEN_H_
#define ALIEN_H_
#include "GameObject.h"
class Alien: public GameObject {
private:
class Action {
public:
enum ActionType {MOVE, SHOOT};
ActionType type;
Action(ActionType type, float x, float y);
Vec2 pos;
};
int hitpoints;
std::queue<Action> taskQueue;
std::vector<Minion> minionArray;
public:
Alien(float x, float y, int nMinions);
~Alien();
void Update(float dt);
void Render();
Alien* get();
bool IsDead();
};
#endif
The code for GameObject is :
#include "GameObject.h"
#include "InputManager.h"
#include "Camera.h"
#include "State.h"
GameObject::~GameObject() {
}
GameObject* GameObject::get() {
return this;
}
Minion::~Minion() {
}
Minion::Minion(GameObject* minionCenter, float arcOffset, float minionSize) {
sp = Sprite("img/minion.png");
center = minionCenter;
translation = arcOffset;
box = Rect(center->box.GetCenter().x+(cos(translation*M_PI/180)*200)-(sp.GetWidth()/2),
center->box.GetCenter().y+(sin(translation*M_PI/180)*200)-(sp.GetHeight()/2),
sp.GetWidth(), sp.GetHeight());
}
void Minion::Shoot(Vec2 pos) {
State::AddObject(new BulletWheel(box.GetCenter().x, box.GetCenter().y, center->box.GetCenter().GetDX(pos.x),
center->box.GetCenter().GetDY(pos.y), center->box.GetCenter().GetDS(pos), 0.3,
translation, center->box.GetCenter(), "img/minionbullet1.png"));
}
void Minion::Update(float dt) {
if(translation < 360)
translation += 0.03*dt;
else
translation += 0.03*dt-360;
/*rotation = translation-90;*/
if(rotation < 360)
rotation += 0.15*dt;
else
rotation += 0.15*dt-360;
box.x = center->box.GetCenter().x+(200*cos((translation)*M_PI/180))-(box.w/2);
box.y = center->box.GetCenter().y+(200*sin((translation)*M_PI/180))-(box.h/2);
}
void Minion::Render() {
sp.Render(box.x - Camera::GetInstance().pos.x, box.y - Camera::GetInstance().pos.y, rotation);
}
bool Minion::IsDead() {
return false;
}
Bullet::Bullet(float x, float y, float dx, float dy, float maxDistance, float speed, std::string sprite) {
sp = Sprite(sprite);
box = Rect(x-(sp.GetWidth()/2), y-(sp.GetHeight()/2), sp.GetWidth(), sp.GetHeight());
Bullet::speed = Vec2(speed*(dx/maxDistance), speed*(dy/maxDistance));
distanceLeft = maxDistance;
rotation = atan2(dy, dx)*(180/M_PI);
}
void Bullet::Update(float dt) {
if(distanceLeft > 0) {
box.x += speed.x*dt;
box.y += speed.y*dt;
distanceLeft -= pow(pow(speed.x*dt,2)+pow(speed.y*dt,2),0.5);
}
}
void Bullet::Render() {
sp.Render(box.x - Camera::GetInstance().pos.x, box.y - Camera::GetInstance().pos.y, rotation);
}
bool Bullet::IsDead() {
return (distanceLeft < 1) ? true : false;
}
Bullet* Bullet::get() {
return this;
}
BulletWheel::BulletWheel(float x, float y, float dx, float dy, float maxDistance, float speed, float arcOffset, Vec2 center, std::string sprite) {
sp = Sprite(sprite);
sp.SetScaleX(2);
sp.SetScaleY(2);
box = Rect(x-(sp.GetWidth()/2), y-(sp.GetHeight()/2), sp.GetWidth(), sp.GetHeight());
BulletWheel::speed = Vec2(speed*(dx/maxDistance), speed*(dy/maxDistance));
distanceLeft = maxDistance;
rotation = atan2(dy, dx)*(180/M_PI);
translation = arcOffset;
BulletWheel::center = center;
}
void BulletWheel::Update(float dt) {
if(translation < 360)
translation += 0.1*dt;
else
translation += 0.1*dt-360;
if(distanceLeft > 0.01) {
center.x += speed.x*dt;
center.y += speed.y*dt;
box.x = center.x+(200*cos((translation)*M_PI/180))-(box.w/2);
box.y = center.y+(200*sin((translation)*M_PI/180))-(box.h/2);
distanceLeft -= pow(pow(speed.x*dt,2)+pow(speed.y*dt,2),0.5);
}
}
void BulletWheel::Render() {
sp.Render(box.x - Camera::GetInstance().pos.x, box.y - Camera::GetInstance().pos.y, rotation);
}
bool BulletWheel::IsDead() {
return distanceLeft < 1;
}
BulletWheel* BulletWheel::get() {
return this;
}
and its header file is:
#ifndef GAMEOBJECT_H_
#define GAMEOBJECT_H_
#include "Sprite.h"
#include "Rect.h"
#include "Vec2.h"
#include <queue>
#include <vector>
#include <cmath>
#include <ctime>
class GameObject{
private:
public:
virtual ~GameObject();
virtual void Update(float dt) = 0;
virtual void Render() = 0;
virtual bool IsDead() = 0;
virtual GameObject* get();
int rotation = 0;
int translation = 0;
Sprite sp = Sprite();
Vec2 speed = Vec2();
Rect box = Rect();
};
class Minion : public GameObject {
private:
GameObject* center;
public:
Minion(GameObject* minionCenter, float arcOffset, float minionSize = 1);
~Minion();
void Shoot(Vec2 pos);
void Update(float dt);
void Render();
bool IsDead();
Minion* get();
};
class Bullet : public GameObject {
private:
float distanceLeft;
public:
Bullet(float x, float y, float dx, float dy, float maxDistance, float speed, std::string sprite);
void Update(float dt);
void Render();
bool IsDead();
Bullet* get();
};
class BulletWheel : public GameObject {
private:
float distanceLeft;
Vec2 center;
public:
BulletWheel(float x, float y, float dx, float dy, float maxDistance, float speed, float arcOffset, Vec2 center, std::string sprite);
void Update(float dt);
void Render();
bool IsDead();
BulletWheel* get();
};
#endif /* GAMEOBJECT_H_ */
There are the virtual functions of GameObject, declared inside Alien.cpp:
void Alien::Update(float dt) {
if(rotation > 0)
rotation -= 0.1*dt;
else
rotation -= 0.1*dt+360;
if(InputManager::GetInstance().MousePress(RIGHT_MOUSE_BUTTON)) {
taskQueue.push(Action(Action::MOVE,(InputManager::GetInstance().GetMouseX() + Camera::GetInstance().pos.x - (box.w/2)),
(InputManager::GetInstance().GetMouseY() + Camera::GetInstance().pos.y - (box.h/2))));
}
if(InputManager::GetInstance().MousePress(LEFT_MOUSE_BUTTON)) {
taskQueue.push(Action(Action::SHOOT,(InputManager::GetInstance().GetMouseX() + Camera::GetInstance().pos.x),
(InputManager::GetInstance().GetMouseY() + Camera::GetInstance().pos.y)));
}
if(taskQueue.size() > 0) {
Vec2 pos = taskQueue.front().pos;
if(taskQueue.front().type == Action::MOVE) {
float cos = (box.GetDX(pos.x)/box.GetDS(pos));
float sin = (box.GetDY(pos.y)/box.GetDS(pos));
if(cos != cos) {
cos = 0;
}
if(sin != sin) {
sin = 0;
}
if((box.x+speed.x*cos*dt > pos.x && pos.x > box.x) || (box.x+speed.x*cos*dt < pos.x && pos.x < box.x)) {
box.x = pos.x;
}
else {
box.x += speed.x*cos*dt;
}
if((box.y+speed.y*sin*dt > pos.y && pos.y > box.y) || (box.y+speed.y*sin*dt < pos.y && pos.y < box.y)) {
box.y = pos.y;
}
else {
box.y += speed.y*sin*dt;
}
if(box.x == pos.x && box.y == pos.y) {
taskQueue.pop();
}
}
else {
for(unsigned i = 0; i < minionArray.size(); i++) {
minionArray.at(i).Shoot(pos);
taskQueue.pop();
}
}
}
for(unsigned i = 0; i < minionArray.size(); i++) {
minionArray.at(i).Update(dt);
}
}
void Alien::Render() {
sp.Render(box.x - Camera::GetInstance().pos.x, box.y - Camera::GetInstance().pos.y, rotation);
if(minionArray.size() > 0) {
for(unsigned i = 0; i < Alien::minionArray.size(); i++) {
minionArray.at(i).Render();
}
}
}
bool Alien::IsDead() {
return (Alien::hitpoints <= 0);
}
EDIT: the destructor of Alien was missing.
All classes derived from GameObject must define all pure virtual functions in GameObject. In your case, this is:
virtual void Update(float dt) = 0;
virtual void Render() = 0;
virtual bool IsDead() = 0;
Here is a similar question with more information. Hope this helps!
#pragma once
#include "Globals.h"
#include "ResourceManager.h"
#include "ResourceIdentifier.h"
#include "Entity.h"
#include <iostream>
#include <cmath>
class Shooter : public Entity{
private:
bool m_isClicked;
bool m_ableToMove;
float m_lastAngle;
bool m_mouseInBounds;
sf::Vector2i m_targetPos;
public:
Shooter();
void initialize(const TextureManager&);
void moveShip(float dt);
void angleShipToMouse(const sf::Vector2i&);
void handleInput(const sf::Event&, const sf::Vector2i&);
void update(const sf::Vector2i&, float);
void adjustingTarget();
};
#include "SpaceShooter.h"
Shooter::Shooter() : m_ableToMove(false), m_isClicked(false), m_lastAngle(0)
{
}
void Shooter::initialize(const TextureManager& text)
{
m_speed = sf::Vector2i(500, 500);
this->m_sprite.setTexture(text.get(TextureID::Shooter));
this->m_sprite.setOrigin(m_sprite.getGlobalBounds().width / 2.0f, m_sprite.getGlobalBounds().height / 2.0f);
this->m_sprite.setPosition(Globals::_windowWidth / 2.0f, Globals::_windowHeight / 2.0f); //start 50 units out
}
void Shooter::moveShip(float dt) {
if (m_isClicked && !m_ableToMove) {
m_ableToMove = true;
m_isClicked = false;
}
if (m_ableToMove) {
adjustingTarget();
sf::Vector2f shipComponentDistances(abs(m_targetPos.x - getX()), abs(m_targetPos.y - getY()));
sf::Vector2f shipVelocity(cos(m_angle * Globals::deg2rad) * dt * m_speed.x,
sin(m_angle*Globals::deg2rad) * dt * m_speed.y);
float targetDistance = sqrt(pow(shipComponentDistances.x, 2) + pow(shipComponentDistances.y, 2));
float distanceToTravel = sqrt(pow(shipVelocity.x, 2) + pow(shipVelocity.y, 2));
if (targetDistance > distanceToTravel) {
this->m_sprite.move(shipVelocity);
m_lastAngle = m_angle;
// std::cout << distance << std::endl;
}
else {
this->m_sprite.setPosition(m_targetPos.x - 0.01, m_targetPos.y - 0.01);
std::cout << m_lastAngle << std::endl;
this->m_sprite.setRotation(m_lastAngle);
std::cout << this->m_sprite.getRotation() << std::endl;
m_ableToMove = false;
m_isClicked = false;
}
}
}
void Shooter::angleShipToMouse(const sf::Vector2i& mousePosition) {
//position of mouse relative to ship in 0 rotation reference frame(absolute)
sf::Vector2f mouseRelativeShip(mousePosition.x - getX(), mousePosition.y - getY());
float rotation = atan2(mouseRelativeShip.y, mouseRelativeShip.x)*Globals::rad2deg; //atan2 produces negative angles if vector is in QUADS 1&2, positive in QUADS 3&4
this->m_sprite.setRotation(rotation);
m_angle = this->m_sprite.getRotation();
}
void Shooter::handleInput(const sf::Event& event, const sf::Vector2i& mousePos) {
if (event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Left) {
if (!m_ableToMove && !m_isClicked) {
m_isClicked = true;
m_targetPos = mousePos;
}
}
}
void Shooter::update(const sf::Vector2i& mousePosition, float dt) {
angleShipToMouse(mousePosition);
moveShip(dt);
}
void Shooter::adjustingTarget()
{
if (m_targetPos.x < spriteWidth() / 2.0f) {
m_targetPos.x = spriteWidth() / 2.0f;
}
else if (m_targetPos.x > Globals::_windowWidth - spriteWidth() / 2.0f) {
m_targetPos.x = Globals::_windowWidth - spriteWidth() / 2.0f;
}
else if(m_targetPos.y < spriteHeight() / 2.0f){
m_targetPos.y = spriteHeight() / 2.0f;
}
else if (m_targetPos.y > Globals::_windowHeight - spriteHeight() / 2.0f) {
m_targetPos.y = Globals::_windowHeight - spriteHeight() / 2.0f;
}
}
The problems with my spaceship are:
1. despite not allowing for changing of targetPos when !moving, clicking while its moving to a different location will cause it to redirect the ship. Holding down the ship causes it to follow the mouse cursor.
once ship reaches the targetPosition, the rotation of the ship gets set to 0, im trying to make it retain the rotation of the angle just prior to it reaching its destination (e.g. distance to targetPos is 0).
When it reaches the boundaries sometimes it can go through it despite me adjusting the targetPosition if the mouse is too far into a deadzone the ship cannot enter.
LeftCollision in CheckCollision() goes true, when ever Object1 collides with Object2(square which is scrolling from right to left of screen) left side. But in the GameObject::Update()
Leftcollision never updates to True, though it changes to true in the CheckCollision section!!
What i want is, whenever the LeftCollision is true, Update should stop, until it becomes false again!!
Below is the code for Header file and CPP file!!
The problem is with LeftCollision updation!! Why is the value not reflected in the Update if condition!!
#pragma once
#include <iostream>
#include <allegro5/allegro5.h>
#include <allegro5/allegro_primitives.h>
#include "Globals.h"
class GameObject
{
private :
int ID;
bool alive;
bool collidable;
protected :
float velX;
float velY;
int dirX;
int dirY;
int boundX;
int boundY;
int maxFrame;
int curFrame;
int frameCount;
int frameDelay;
int frameWidth;
int frameHeight;
int animationColumns;
int animationDirection;
ALLEGRO_BITMAP *image;
public :
float x;
float y;
bool LeftCollision;
bool pause;
GameObject();
void virtual Destroy();
void Init(float x, float y, float velX, float velY, int dirX, int dirY, int boundX, int boundY);
void virtual Update();
void virtual Render();
float GetX() {return x;}
float GetY() {return y;}
void SetX(float x) {GameObject ::x = x;}
void SetY(float y) {GameObject ::y = y;}
int GetBoundX() {return boundX;}
int GetBoundY() {return boundY;}
int GetID() {return ID;}
void SetID(int ID) {GameObject::ID = ID;}
bool GetAlive() {return alive;}
void SetAlive(bool alive) {GameObject :: alive = alive;}
bool GetCollidable() {return collidable;}
void SetCollidable(bool collidable) {GameObject :: collidable = collidable;}
bool CheckCollisions(GameObject *otherObject);
void virtual Collided(int objectID);
bool Collidable();
};
#include "GameObject.h"
GameObject :: GameObject()
{
x = 0;
y = 0;
velX = 0;
velY = 0;
dirX = 0;
dirY = 0;
boundX = 0;
boundY = 0;
LeftCollision = false;
maxFrame = 0;
curFrame = 0;
frameCount = 0;
frameDelay = 0;
frameWidth = 0;
frameHeight = 0;
animationColumns = 0;
animationDirection = 0;
image = NULL;
alive = true;
collidable = true;
}
void GameObject :: Destroy()
{
}
void GameObject :: Init(float x, float y, float velX, float velY, int dirX, int dirY, int boundX, int boundY)
{
GameObject :: x = x;
GameObject :: y = y;
GameObject :: velX = velX;
GameObject :: velY = velY;
GameObject :: dirX = dirX;
GameObject :: dirY = dirY;
GameObject :: boundX = boundX;
GameObject :: boundY = boundY;
}
void GameObject :: Update()
{
if(LeftCollision == false)
{
x += velX * dirX;
y += velY * dirY;
}
}
void GameObject :: Render()
{
}
bool GameObject :: CheckCollisions(GameObject *otherObject)
{
float oX = otherObject->GetX();
float oY = otherObject->GetY();
int obX = otherObject->GetBoundX();
int obY = otherObject->GetBoundY();
if( x + boundX > oX - obX &&
x - boundX < oX + obX &&
y + boundY > oY - obY &&
y - boundY < oY + obY && otherObject->GetID() == TRIANGLE)
{
return true;
}
else if(((oX < x + boundX + 14)&&(oX+ 40 >x - boundX))&&!((oY < y + boundY)&&(oY+40 > y - boundY))
&& otherObject->GetID() == SQUARE)
{
y = oY - boundX - 10;
//x = oX + 40;
return true;
}
else if((oX < x + boundX + 14) && (oX > x - boundX) && otherObject->GetID() == SQUARE)
{
LeftCollision = true;
return false;
}
else
{
return false;
LeftCollision = false;
}
}
void GameObject :: Collided(int objectID)
{
}
bool GameObject :: Collidable()
{
return alive && collidable;
}
Is this part of your problem - returning without setting the value
else
{
return false;
LeftCollision = false;
}
The only thing that jumps to mind is that you've got more than one object going around and you're not always using the one you expect. Try putting something like:
printf("here(%s:%d) this=%p\n", __FILE__, __LINE__, this);
at the start of each method to make sure that the this point is what you think it is. The problem may lie in the calling code that you haven't shown.
I am working with some code to create a triangle that moves with arrow keys. I want to create a second object that moves independently. This is where I am having trouble, I have created the second actor, but cannot get it to move. There is too much code to post it all so I will just post a little and see if anyone can help at all.
ogl_test.cpp
#include "platform.h"
#include "srt/scheduler.h"
#include "model.h"
#include "controller.h"
#include "model_module.h"
#include "graphics_module.h"
class blob : public actor {
public:
blob(float x, float y) : actor(math::vector2f(x, y)) { }
void render() {
transform();
glBegin(GL_TRIANGLES);
glVertex3f(0.25f, 0.0f, -5.0f);
glVertex3f(-.5f, 0.25f, -5.0f);
glVertex3f(-.5f, -0.25f, -5.0f);
glEnd();
end_transform();
}
void update(controller& c, float dt) {
if (c.left_key) {
rho += pi / 9.0f * dt;
c.left_key = false;
}
if (c.right_key) {
rho -= pi / 9.0f * dt;
c.right_key = false;
}
if (c.up_key) {
v += .1f * dt;
c.up_key = false;
}
if (c.down_key) {
v -= .1f * dt;
if (v < 0.0) { v = 0.0; }
c.down_key = false;
}
actor::update(c, dt);
}
};
class enemyOne : public actor {
public:
enemyOne(float x, float y) : actor(math::vector2f(x, y)) { }
void render() {
transform();
glBegin(GL_TRIANGLES);
glVertex3f(0.25f, 0.0f, -5.0f);
glVertex3f(-.5f, 0.25f, -5.0f);
glVertex3f(-.5f, -0.25f, -5.0f);
glEnd();
end_transform();
}
void update(controller& c, float dt) {
if (c.left_key) {
rho += pi / 9.0f * dt;
c.left_key = false;
}
if (c.right_key) {
rho -= pi / 9.0f * dt;
c.right_key = false;
}
if (c.up_key) {
v += .1f * dt;
c.up_key = false;
}
if (c.down_key) {
v -= .1f * dt;
if (v < 0.0) { v = 0.0; }
c.down_key = false;
}
actor::update(c, dt);
}
};
int APIENTRY WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
char* lpCmdLine,
int nCmdShow
)
{
model m;
controller control(m);
srt::scheduler scheduler(33);
srt::frame* model_frame = new srt::frame(scheduler.timer(), 0, 1, 2);
srt::frame* render_frame = new srt::frame(scheduler.timer(), 1, 1, 2);
model_frame->add(new model_module(m, control));
render_frame->add(new graphics_module(m));
scheduler.add(model_frame);
scheduler.add(render_frame);
blob* prime = new blob(0.0f, 0.0f);
m.add(prime);
m.set_prime(prime);
enemyOne* primeTwo = new enemyOne(2.0f, 0.0f);
m.add(primeTwo);
m.set_prime(primeTwo);
scheduler.start();
control.start();
return 0;
}
model.h
#include <vector>
#include "vec.h"
const double pi = 3.14159265358979323;
class controller;
using math::vector2f;
class actor {
public:
vector2f P;
float theta;
float v;
float rho;
actor(const vector2f& init_location) :
P(init_location),
rho(0.0),
v(0.0),
theta(0.0)
{ }
virtual void render() = 0;
virtual void update(controller&, float dt) {
float v1 = v;
float theta1 = theta + rho * dt;
vector2f P1 = P + v1 * vector2f(cos(theta1), sin(theta1));
if (P1.x < -4.5f || P1.x > 4.5f) { P1.x = -P1.x; }
if (P1.y < -4.5f || P1.y > 4.5f) { P1.y = -P1.y; }
v = v1;
theta = theta1;
P = P1;
}
protected:
void transform() {
glPushMatrix();
glTranslatef(P.x, P.y, 0.0f);
glRotatef(theta * 180.0f / pi, 0.0f, 0.0f, 1.0f); //Rotate about the z-axis
}
void end_transform() {
glPopMatrix();
}
};
class model {
private:
typedef std::vector<actor*> actor_vector;
actor_vector actors;
public:
actor* _prime;
model() { }
void add(actor* a) {
actors.push_back(a);
}
void set_prime(actor* a) {
_prime = a;
}
void update(controller& control, float dt) {
for (actor_vector::iterator i = actors.begin(); i != actors.end(); ++i) {
(*i)->update(control, dt);
}
}
void render() {
for (actor_vector::iterator i = actors.begin(); i != actors.end(); ++i) {
(*i)->render();
}
}
};
Your blob is erasing the keypresses before the second actor sees them:
if (c.left_key) {
rho += pi / 9.0f * dt;
c.left_key = false; // <-- HERE
}
Clearing the keyboard state after each frame should not be the concern of any single actor, the controller itself should do that.
Both blob and enemyOne both have an update function that checks the arrow keys to see whether to move. If you commented out all of the if statements inside one of the update function then it should not move anymore. Alternatively, you could change the if statements in the update function to check other keys and use those to control the other object.
EDIT: As noted in another answer, when you set c.left_key = false; (or one on the other keys) you prevent any of the other actors from seeing that the key was pressed. Either not passing the controller around as a reference, or removing these lines of code should fix this.