summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorThorbjørn Lindeijer <bjorn@lindeijer.nl>2024-10-04 11:32:15 +0200
committerThorbjørn Lindeijer <bjorn@lindeijer.nl>2024-10-08 21:03:44 +0200
commit573df67919ec5f65a6b2ad51ed8aed31c9cfe4a6 (patch)
treee50500d452fdb3a503d3257477facc38017f7e10 /src
parent7de0b165f196cb0c1f983b6d2ab26ef9791a2d36 (diff)
downloadmana-573df67919ec5f65a6b2ad51ed8aed31c9cfe4a6.tar.gz
mana-573df67919ec5f65a6b2ad51ed8aed31c9cfe4a6.tar.bz2
mana-573df67919ec5f65a6b2ad51ed8aed31c9cfe4a6.tar.xz
mana-573df67919ec5f65a6b2ad51ed8aed31c9cfe4a6.zip
Smoother being movement
There was a slight stutter in being movement, since each time a being reached the next position along its path, it would only continue to the following position with the next logic tick. Now the logic has been adjusted to keep moving until all the time for the current frame was used up, or the path was exhausted. A slight stutter remains for keyboard movement, as well as broken walk animation playback, since it will only set a new path once the current one is finished (see e554d9b2be1ec2fcb15065ae70151302adeef602). Also simplified some logic in Viewport::draw and removed some obsolete code in LocalPlayer::startWalking.
Diffstat (limited to 'src')
-rw-r--r--src/being.cpp76
-rw-r--r--src/being.h12
-rw-r--r--src/gui/viewport.cpp24
-rw-r--r--src/localplayer.cpp10
-rw-r--r--src/utils/time.cpp5
-rw-r--r--src/utils/time.h7
-rw-r--r--src/vector.h4
7 files changed, 71 insertions, 67 deletions
diff --git a/src/being.cpp b/src/being.cpp
index b622143a..c23a650d 100644
--- a/src/being.cpp
+++ b/src/being.cpp
@@ -128,6 +128,7 @@ void Being::setSubtype(Uint16 subtype)
break;
}
}
+
bool Being::isTargetSelection() const
{
return mInfo->targetSelection;
@@ -768,43 +769,59 @@ void Being::logic()
restoreAllSpriteParticles();
}
- if ((mAction != DEAD) && !mSpeedPixelsPerSecond.isNull())
+ if (mAction != DEAD && !mSpeedPixelsPerSecond.isNull())
+ {
+ updateMovement();
+ }
+
+ ActorSprite::logic();
+
+ // Remove it after 1.5 secs if the dead animation isn't long enough,
+ // or simply play it until it's finished.
+ if (!isAlive() && Net::getGameHandler()->removeDeadBeings() && getType() != PLAYER)
+ if (mActionTimer.elapsed() > std::max(getDuration(), 1500))
+ actorSpriteManager->scheduleDelete(this);
+}
+
+void Being::updateMovement()
+{
+ float dt = Time::deltaTime();
+
+ while (dt > 0.f)
{
- const Vector dest = (mPath.empty()) ?
- mDest : Vector(mPath.front().x,
- mPath.front().y);
+ const Vector dest = mPath.empty() ? mDest
+ : Vector(mPath.front().x,
+ mPath.front().y);
// Avoid going to flawed destinations
+ // We make the being stop move in that case.
if (dest.x <= 0 || dest.y <= 0)
{
- // We make the being stop move in that case.
mDest = mPos;
mPath.clear();
- // By returning now, we're losing one tick for the rest of the logic
- // but as we have reset the destination, the next tick will be fine.
- return;
+ break;
}
// The Vector representing the difference between current position
// and the next destination path node.
- Vector dir = dest - mPos;
-
- float distance = dir.length();
+ const Vector dir = dest - mPos;
// When we've not reached our destination, move to it.
- if (distance > 0.0f)
+ if (!dir.isNull())
{
+ const float distanceToDest = dir.length();
+
// The deplacement of a point along a vector is calculated
// using the Unit Vector (â) multiplied by the point speed.
// â = a / ||a|| (||a|| is the a length.)
// Then, diff = (dir/||dir||) * speed.
const Vector normalizedDir = dir.normalized();
- const int ms = Time::deltaTimeMs();
- Vector diff(normalizedDir.x * mSpeedPixelsPerSecond.x * ms / 1000.0f,
- normalizedDir.y * mSpeedPixelsPerSecond.y * ms / 1000.0f);
+ Vector diff(normalizedDir.x * mSpeedPixelsPerSecond.x * dt,
+ normalizedDir.y * mSpeedPixelsPerSecond.y * dt);
+ const float distanceToMove = diff.length();
// Test if we don't miss the destination by a move too far:
- if (diff.length() > distance)
+ if (distanceToMove > distanceToDest)
{
setPosition(dest);
@@ -812,13 +829,16 @@ void Being::logic()
// path point, if existing.
if (!mPath.empty())
mPath.pop_front();
+
+ // Set dt to the time left after performing this move.
+ dt -= dt * (distanceToDest / distanceToMove);
}
else
{
// Otherwise, go to it using the nominal speed.
setPosition(mPos + diff);
- // And reset the nominalLength to the actual move length
- distance = diff.length();
+ // And set the remaining time to 0.
+ dt = 0.f;
}
if (mAction != MOVE)
@@ -831,12 +851,11 @@ void Being::logic()
// 1. It is not the local_player
// 2. When it is the local_player but only by mouse
// (because in that case, the path can have more than one tile.)
- if ((local_player == this && local_player->isPathSetByMouse())
- || local_player != this)
+ if (local_player != this || local_player->isPathSetByMouse())
{
int direction = 0;
const float dx = std::abs(dir.x);
- float dy = std::abs(dir.y);
+ const float dy = std::abs(dir.y);
if (dx > dy)
direction |= (dir.x > 0) ? RIGHT : LEFT;
@@ -852,19 +871,13 @@ void Being::logic()
// remove it and go to the next one.
mPath.pop_front();
}
- else if (mAction == MOVE)
+ else
{
- setAction(STAND);
+ if (mAction == MOVE)
+ setAction(STAND);
+ break;
}
}
-
- ActorSprite::logic();
-
- // Remove it after 1.5 secs if the dead animation isn't long enough,
- // or simply play it until it's finished.
- if (!isAlive() && Net::getGameHandler()->removeDeadBeings() && getType() != PLAYER)
- if (mActionTimer.elapsed() > std::max(getDuration(), 1500))
- actorSpriteManager->scheduleDelete(this);
}
void Being::drawSpeech(int offsetX, int offsetY)
@@ -1198,7 +1211,6 @@ void Being::event(Event::Channel channel, const Event &event)
setShowName(config.getBoolValue("visiblenames"));
}
}
-
}
void Being::setMap(Map *map)
diff --git a/src/being.h b/src/being.h
index 5c939fcc..8f596810 100644
--- a/src/being.h
+++ b/src/being.h
@@ -319,14 +319,7 @@ class Being : public ActorSprite, public EventListener
* in ticks per tile for eAthena,
* in tiles per second for Manaserv (0.1 precision).
*/
- virtual void setMoveSpeed(const Vector &speed);
-
- /**
- * Gets the original Move speed.
- * in ticks per tile for eAthena,
- * in tiles per second for Manaserv (0.1 precision).
- */
- Vector getMoveSpeed() const { return mMoveSpeed; }
+ void setMoveSpeed(const Vector &speed);
/**
* Sets the attack speed.
@@ -530,6 +523,7 @@ class Being : public ActorSprite, public EventListener
bool mIsGM = false;
private:
+ void updateMovement();
const Type mType;
@@ -539,7 +533,7 @@ class Being : public ActorSprite, public EventListener
/**
* Walk speed for x and y movement values.
* In ticks per tile for eAthena,
- * In pixels per second for Manaserv.
+ * In tiles per second for Manaserv.
*/
Vector mMoveSpeed;
diff --git a/src/gui/viewport.cpp b/src/gui/viewport.cpp
index 8dbdf7c7..6c8016dc 100644
--- a/src/gui/viewport.cpp
+++ b/src/gui/viewport.cpp
@@ -41,6 +41,7 @@
#include "utils/stringutils.h"
+#include <algorithm>
#include <cmath>
Viewport::Viewport()
@@ -166,24 +167,21 @@ void Viewport::draw(gcn::Graphics *gcnGraphics)
};
// Don't move camera so that the end of the map is on screen
+ // Center camera on map if the map is smaller than the screen
const int mapWidthPixels = mMap->getWidth() * mMap->getTileWidth();
const int mapHeightPixels = mMap->getHeight() * mMap->getTileHeight();
const int viewXmax = mapWidthPixels - graphics->getWidth();
const int viewYmax = mapHeightPixels - graphics->getHeight();
- if (mPixelViewX < 0)
- mPixelViewX = 0;
- if (mPixelViewY < 0)
- mPixelViewY = 0;
- if (mPixelViewX > viewXmax)
- mPixelViewX = viewXmax;
- if (mPixelViewY > viewYmax)
- mPixelViewY = viewYmax;
- // Center camera on map if the map is smaller than the screen
- if (mapWidthPixels < graphics->getWidth())
- mPixelViewX = (mapWidthPixels - graphics->getWidth()) / 2;
- if (mapHeightPixels < graphics->getHeight())
- mPixelViewY = (mapHeightPixels - graphics->getHeight()) / 2;
+ if (viewXmax > 0)
+ mPixelViewX = std::clamp<float>(mPixelViewX, 0, viewXmax);
+ else
+ mPixelViewX = viewXmax / 2;
+
+ if (viewYmax > 0)
+ mPixelViewY = std::clamp<float>(mPixelViewY, 0, viewYmax);
+ else
+ mPixelViewY = viewYmax / 2;
// Draw black background if map is smaller than the screen
if ( mapWidthPixels < graphics->getWidth()
diff --git a/src/localplayer.cpp b/src/localplayer.cpp
index 1a91ffb7..5075c44c 100644
--- a/src/localplayer.cpp
+++ b/src/localplayer.cpp
@@ -746,16 +746,6 @@ void LocalPlayer::startWalking(unsigned char dir)
return;
}
- int dx = 0, dy = 0;
- if (dir & UP)
- dy--;
- if (dir & DOWN)
- dy++;
- if (dir & LEFT)
- dx--;
- if (dir & RIGHT)
- dx++;
-
nextTile(dir);
}
diff --git a/src/utils/time.cpp b/src/utils/time.cpp
index c89914fa..df20fc23 100644
--- a/src/utils/time.cpp
+++ b/src/utils/time.cpp
@@ -41,6 +41,11 @@ unsigned deltaTimeMs()
return s_deltaTimeMs;
}
+float deltaTime()
+{
+ return s_deltaTimeMs / 1000.f;
+}
+
static int32_t getElapsedTime(uint32_t timeMs)
{
return static_cast<int32_t>(s_absoluteTimeMs - timeMs);
diff --git a/src/utils/time.h b/src/utils/time.h
index ac7cd351..58b8164a 100644
--- a/src/utils/time.h
+++ b/src/utils/time.h
@@ -39,11 +39,16 @@ namespace Time
uint32_t absoluteTimeMs();
/**
- * The time in milliseconds since the last frame, maximized to 1000ms.
+ * The time in milliseconds since the last frame, but never more than 1000.
*/
unsigned deltaTimeMs();
/**
+ * The time in seconds since the last frame, but never more than 1.
+ */
+float deltaTime();
+
+/**
* Called at the start of each frame, updates the above variables.
*/
void beginFrame();
diff --git a/src/vector.h b/src/vector.h
index 5d7d63d7..6bf6d81a 100644
--- a/src/vector.h
+++ b/src/vector.h
@@ -140,7 +140,7 @@ class Vector
*/
float length() const
{
- return sqrtf(x * x + y * y + z * z);
+ return std::sqrt(x * x + y * y + z * z);
}
/**
@@ -156,7 +156,7 @@ class Vector
*/
float manhattanLength() const
{
- return fabsf(x) + fabsf(y) + fabsf(z);
+ return std::abs(x) + std::abs(y) + std::abs(z);
}
/**