I'm writing a game using CCProgressTimer for HP bar.
Following is my code to add a HP bar:
_hpBar = [CCProgressTimer progressWithSprite:[CCSprite spriteWithFile:#"hp_bar.png"]];
_hpBar.type = kCCProgressTimerTypeBar;
_hpBar.barChangeRate = ccp(1, 0); // Y stays the same
_hpBar.midpoint = ccp(0, 0);
_hpBar.percentage = 100;
_hpBar.position = ccp(_hpBar.contentSize.width * scale / 2, self.contentSize.height + _hpBar.contentSize.height / 2 + 2);
[self addChild:_hpBar];
Add this is my code to increase HP:
- (int)increaseHP:(int)amount {
_life = MIN(_life + amount, 100);
if (_life > 30) [_hpBar setSprite:[CCSprite spriteWithFile:#"hp_bar.png"]];
[_hpBar setPercentage:_life];
return _life;
}
However, when the HP is full, namely _life=100, and then I increase some more HP, i.e. calling [self increaseHP:1], the HP bar will disappear.
Could anyone help?
Because the percentage goes from 0 to 100 and the behavior for >100% is undefined (not safeguarded).
If your character can have, say 250 HP max, then you need to calculate the percentage of health the character has. If he has 125 HP for example that would make 50%.
Related
I am using a custom game engine created by one of my lecturers.
I have 4 inputs that the user can have for a pong game. These inputs work as intended however as soon as the sprite (paddle for pong) touches either the top or the bottom they get stuck there and are unable to move. Adding an else statement saying w_keypress = false; doesn't work.
if (w_keypress)
{
if (player_one->yPos() >= 0 && player_one->yPos() + player_one->height() <= game_height)
{
yspeed_player_one = -500;
s_keypress = false;
y_pos_player_one += yspeed_player_one * (game_time.delta.count() / 1000.f);
player_one->yPos(y_pos_player_one);
std::cout << "keypress w" << std::endl;
}
}
EDIT: The problem can easily be fixed by setting the y value to a y value that doesn't make the sprite interfere with the top or the bottom of the screen.
e.g.
if (player_one-yPos() > game_height)
{
player_one->yPos(game_height - (player_one->height() / 2)
}
else if (player_one->yPos() < 0)
{
player_one->yPos(0 + (player_one->height() / 2)
}
This code detects if the player has gone off the top or the bottom of the screen and then moves the player half of its height down or up depending on which y value you are.
Let's take a closer look at
if (player_one->yPos() >= 0 && player_one->yPos() + player_one->height() <= game_height)
{
yspeed_player_one = -500;
s_keypress = false;
y_pos_player_one += yspeed_player_one * (game_time.delta.count() / 1000.f);
player_one->yPos(y_pos_player_one);
}
bounds check position. If in bounds,
Update position by adding current velocity
Else
Do nothing
The problem is step 1.1 is too naive. If your sprite is zipping along toward a wall at sufficient speed, it can enter or completely pass through the wall as soon as you update its position. The next test of the bounds will trap the sprite because it is out of bounds.
Eg: Sprite is at 1000. Its height is 50 and its velocity per tick is 50. The wall is at 1080.
Step 1 tests 1000 + 50 <= 1080. This is true, so step 1.1 updates the position: 1000 + 50 = 1050. The sprite now occupies 1050 to 1099 and is inside the wall.
On the next button press, Step 1 tests 1050 + 50 <= 1080. This is false, so 2.1 is executed and the sprite does not move.
The test for collision with the wall is effectively performed after the sprite's gone out of bounds and by then it is too late.
You want a function that does something like
TYPE clamp_move(TYPE max_move,
TYPE distance_to_wall)
{
if (max_move < distance_to_wall)
{
max_move = distance_to_wall;
}
return max_move;
}
to prevent over shooting the bounds. Note this is pretty much std::min, so use std::min.
You wind up with
deltapos = std::min(yspeed_player_one * (game_time.delta.count() / 1000.f),
player_one->yPos());
y_pos_player_one -= deltapos;
or
deltapos = std::min(yspeed_player_one * (game_time.delta.count() / 1000.f),
game_height - (player_one->yPos() + player_one->height()));
y_pos_player_one += deltapos;
depending on which way the sprite is moving. Or by catching the overshoot and clamping before the next test.
y_pos_player_one += yspeed_player_one * (game_time.delta.count() / 1000.f);
if (y_pos_player_one <0)
{
y_pos_player_one = 0;
}
else if (y_pos_player_one > game_height - player_one->height())
{
y_pos_player_one = game_height - player_one->height();
}
whichever is easier on your brain.
I am making a physics-based where balls (gdi+ ellipses) will collide. There are 4 conditions I need to program to make the collisions work correctly. To do this I'm using if statements.
I noticed when debugging that in some cases, the code inside the if block is NOT executed as if the condition was not met - however - changing the code inside that if block will still alter the way my program works.
I've extensively debugged to ensure this is what's happening. I've also switched up the way I'm writing my if statements to ensure the logic is correct, but the problem persists.
When the game is started, I add a couple of balls to a vector.
/**
* Start a new game
*/
void CGame::NewGame()
{
this->mBalls.clear();
shared_ptr<CBall> cueBall = make_shared<CBall>(this);
cueBall->SetX(410.0);
cueBall->SetY(379.0 - (25 / 2) - 110);
cueBall->SetVelocityX(0);
cueBall->SetVelocityY(160.0);
this->mGameItems.push_back(cueBall);
shared_ptr<CBall> ball1 = make_shared<CBall>(this);
ball1->SetX(420.0);
ball1->SetY(379.0 - (25 / 2));
this->mGameItems.push_back(ball1);
}
They are then drawn on the screen by iterating through the vector CGame.mGameItems and calling a Draw function on each one.
/**
* Draw the ball with as a circle
* \param graphics The GDI+ graphics context to draw on
*/
void CBall::Draw(Gdiplus::Graphics* graphics)
{
SolidBrush colorBrush(Color(255, 1000, 1000, 1000));
graphics->FillEllipse(&colorBrush, Rect(this->GetX(), this->GetY(), this->diameter, this->diameter));
}
The redrawing and updating of the window is done in CChildView::OnPaint.
/** This function is called to draw in the window.
*
* This function is called in response to a drawing message
* whenever we need to redraw the window on the screen.
* It is responsible for painting the window.
*/
void CChildView::OnPaint()
{
CPaintDC paintDC(this); // device context for painting
CDoubleBufferDC dc(&paintDC); // device context for painting
Graphics graphics(dc.m_hDC); // Create GDI+ graphics context
mGame.OnDraw(&graphics);
// TODO: Add your message handler code here
// Do not call CWnd::OnPaint() for painting messages
if (mFirstDraw)
{
mFirstDraw = false;
SetTimer(1, FrameDuration, nullptr);
/*
* Initialize the elapsed time system
*/
LARGE_INTEGER time, freq;
QueryPerformanceCounter(&time);
QueryPerformanceFrequency(&freq);
mLastTime = time.QuadPart;
mTimeFreq = double(freq.QuadPart);
}
/*
* Compute the elapsed time since the last draw
*/
LARGE_INTEGER time;
QueryPerformanceCounter(&time);
long long diff = time.QuadPart - mLastTime;
double elapsed = double(diff) / mTimeFreq;
mLastTime = time.QuadPart;
mGame.Update(elapsed);
}
To handle collisions of the objects, a function calls a function called Update() on a CGame object which uses the following code to check if a collision happened.
/** Handle updates for animation
* \param elapsed The time since the last update
*/
void CGame::Update(double elapsed)
{
// Collisions of balls
for (shared_ptr<CBall> ball1 : this->GetBalls())
{
for (shared_ptr<CBall> ball2 : this->GetBalls())
{
double centerXBall1 = ball1->GetX() + ball1->GetDiameter() / 2;
double centerYBall1 = ball1->GetY() + ball1->GetDiameter() / 2;
double centerXBall2 = ball2->GetX() + ball2->GetDiameter() / 2;
double centerYBall2 = ball2->GetY() + ball2->GetDiameter() / 2;
double distance = sqrt((pow((centerXBall2 - centerXBall1), 2) +
pow((centerYBall2 - centerYBall1), 2)));
if (ball1 != ball2 && (distance <= ball1->GetDiameter() || distance <= ball2->GetDiameter()))
{
// The slope of the line that passes through the center of both
// balls
double slopeThroughCenters =
(centerYBall2 - centerYBall1) / (centerXBall2 -
centerXBall1);
// The angle between a vertical and the deflection direction of
// ball 1
double angleOfVelocity1 = 0.0;
// The angle between a horizontal line and the deflection
// direction of ball 2
double angleOfVelocity2 = 0.0;
double complimentaryAngle = atan(slopeThroughCenters);
if (centerXBall1 > centerXBall2 && centerYBall1 > centerYBall2)
{
angleOfVelocity1 = PI / 2 - complimentaryAngle;
angleOfVelocity2 = (PI / 2 - complimentaryAngle) + PI / 2;
}
double initialVelocityBall1 = sqrt(pow(ball1->GetVelocityX(), 2) +
pow(ball1->GetVelocityY(), 2));
double initialVelocityBall2 = sqrt(pow(ball2->GetVelocityX(), 2) +
pow(ball2->GetVelocityY(), 2));
double finalVelocityBall1 = ((ball1->GetMass() - ball2->GetMass()) * initialVelocityBall1) /
(ball1->GetMass() + ball2->GetMass());
double finalVelocityBall2 = (2 * ball1->GetMass() * initialVelocityBall1) /
(ball1->GetMass() + ball2->GetMass());
double velocityXBall1 = finalVelocityBall2 * cos(angleOfVelocity1);
double velocityYBall1 = finalVelocityBall2 * sin(angleOfVelocity1);
double velocityXBall2 = finalVelocityBall2 * cos(angleOfVelocity2);
double velocityYBall2 = finalVelocityBall2 * sin(angleOfVelocity2);
ball1->SetVelocityX(velocityXBall1);
ball1->SetVelocityY(velocityYBall1);
ball2->SetVelocityX(velocityXBall2);
ball2->SetVelocityY(velocityYBall2);
}
}
}
for (auto ball : this->mBalls)
{
ball->Update(elapsed);
}
}
When I run the debugger, the if block:
if (centerXBall1 > centerXBall2 && centerYBall1 > centerYBall2)
{
angleOfVelocity1 = PI / 2 - complimentaryAngle;
angleOfVelocity2 = PI - complimentaryAngle;
}
Doesn't seem to be executed, however, if I change the variable's values, for example,
angleOfVelocity1 = PI;
angleOfVelocity2 = PI;
My program actually changes according to the values of those variables. How is this possible if the debugger shows that the if block isn't executed in the first place?
Sorry if my code isn't minimalistic and concise enough, but I did not want to leave out anything important for solving this problem.
I'm trying out some sample code for a bigger project, and I'm having trouble getting my rectangle to bounce between two lines.
function draw() {
print(frameCount)
background(255)
var x = 150 + frameCount;
rect(x,200,15,15);
line(150,0,150,400);
line(250,0,250,400);
if (x >= 250) {
background(255)
x = 350-frameCount;
rect(x,200,15,15);
line(250,0,250,400);
line(150,0,150,400);
} if (x <= 145) {
background(255)
x = 145 + (frameCount % 100);
rect(x,200,15,15);
line(250,0,250,400);
line(150,0,150,400);
}
}
I'm getting the feeling that after the first instance, it's disregarding the original if statement, which dictates a bounce to the left. I'm really not sure what's going wrong, and any help would be appreciated.
You probably just want to store the current position and speed in a set of variables, and then move the rectangle based on those. Here's an example:
var x = 0;
var speed = 1;
function draw(){
x += speed;
if(x < 0 || x > width){
speed *= -1;
}
background(64);
line(x, 0, x, height);
}
I've written a tutorial on this available here. That's for regular Processing, but the ideas are the same in P5.js.
This slider is used in my rpg to get rid of X amount of items so at pos 1 it will junk 1 item at max pos it will junk all the items.
slider image
My current formula is not correct
int pos_int = std::ceil(106.00 / (double)amount);
int base_pos = 318 - (amount - this->dj_slider_pos) * pos_int;
122 pixels is the total width of the draw area but after considering the width of the slider is 16 pixels the last draw position is at 106 pixels. Amount is the total number of items you have and dj_slider_pos tracks the position of the slider.
I have tried a few different variations but figured I might as well try and get help here instead of continuing to spin my wheels. Here is a little more code.
int amount = 1;
std::string itname = "";
UTIL_PTR_VECTOR_FOREACH(this->character->inventory_items,Character_Item,it)
{
if(it->id == this->character_inventory->selected_id)
{
itname = util::ucfirst(this->world->eif->Get(it->id)->name);
amount = it->amount;
break;
}
}
int pos_int = std::ceil(106.00 / (double)amount);
int base_pos = 318 - (amount - this->dj_slider_pos) * pos_int;
if(base_pos < 212) base_pos = 212;
else if(base_pos > 318) base_pos = 318;
this->DrawSplitBmp(base_pos,165, this->mouse_sliders,this->screen, &drop_junktabs[7]);
Problem solved! I initialized dj_slider_pos to 0 instead of 1 and changed the following lines.
double pos_int = 106.00 / (double)(amount-1);
int base_pos = 212 + (this->dj_slider_pos * pos_int);
I have a problem due to my terrible math abilities, that I cannot figure out how to scale a graph based on the maximum and minimum values so that the whole graph will fit onto the graph-area (400x420) without parts of it being off the screen (based on a given equation by user).
Let's say I have this code, and it automatically draws squares and then the line graph based on these values. What is the formula (what do I multiply) to scale it so that it fits into the small graphing area?
vector<int> m_x;
vector<int> m_y; // gets automatically filled by user equation or values
int HeightInPixels = 420;// Graphing area size!!
int WidthInPixels = 400;
int best_max_y = GetMaxOfVector(m_y);
int best_min_y = GetMinOfVector(m_y);
m_row = 0;
m_col = 0;
y_magnitude = (HeightInPixels/(best_max_y+best_min_y)); // probably won't work
x_magnitude = (WidthInPixels/(int)m_x.size());
m_col = m_row = best_max_y; // number of vertical/horizontal lines to draw
////x_magnitude = (WidthInPixels/(int)m_x.size())/2; Doesn't work well
////y_magnitude = (HeightInPixels/(int)m_y.size())/2; Doesn't work well
ready = true; // we have values, graph it
Invalidate(); // uses WM_PAINT
////////////////////////////////////////////
/// Construction of Graph layout on WM_PAINT, before painting line graph
///////////////////////////////////////////
CPen pSilver(PS_SOLID, 1, RGB(150, 150, 150) ); // silver
CPen pDarkSilver(PS_SOLID, 2, RGB(120, 120, 120) ); // dark silver
dc.SelectObject( pSilver ); // silver color
CPoint pt( 620, 620 ); // origin
int left_side = 310;
int top_side = 30;
int bottom_side = 450;
int right_side = 710; // create a rectangle border
dc.Rectangle(left_side,top_side,right_side,bottom_side);
int origin = 310;
int xshift = 30;
int yshift = 30;
// draw scaled rows and columns
for(int r = 1; r <= colrow; r++){ // draw rows
pt.x = left_side;
pt.y = (ymagnitude)*r+top_side;
dc.MoveTo( pt );
pt.x = right_side;
dc.LineTo( pt );
for(int c = 1; c <= colrow; c++){
pt.x = left_side+c*(magnitude);
pt.y = top_side;
dc.MoveTo(pt);
pt.y = bottom_side;
dc.LineTo(pt);
} // draw columns
}
// grab the center of the graph on x and y dimension
int top_center = ((right_side-left_side)/2)+left_side;
int bottom_center = ((bottom_side-top_side)/2)+top_side;
You are using ax^2 + bx + c (quadratic equation). You will get list of (X,Y) values inserted by user.
Let us say 5 points you get are
(1,1)
(2,4)
(4,1)
(5,6)
(6,7)
So, here your best_max_y will be 7 and best_min_y will be 1.
Now you have total graph area is
Dx = right_side - left_side //here, 400 (710 - 310)
Dy = bottom_side - top_side //here, 420 (450 - 30)
So, you can calculate x_magnitude and y_magnitude using following equation :
x_magnitude = WidthInPixels / Dx;
y_magnitude = HeightInPixels / Dy;
What I did was to determine how many points I had going in the x and y directions, and then divide that by the x and y dimensions, then divide that by 3, as I wanted each minimum point to be three pixels, so it could be seen.
The trick then is that you have to aggregate the data so that you are showing several points with one point, so it may be the average of them, but that depends on what you are displaying.
Without knowing more about what you are doing it is hard to make a suggestion.
For this part, subtract, don't add:
best_max_y+best_min_y as you want the difference.
The only other thing would be to divide y_magnitude and x_magnitude by 3. That was an arbitrary number I came up with, just so the users could see the points, you may find some other number to work better.