- Game Programming using Qt 5 Beginner's Guide
- Pavel Strakhov Witold Wysota Lorenz Haas
- 1199字
- 2021-08-27 18:31:11
What just happened?
In the key press event handler, we first check whether the key event was triggered because of an auto-repeat. If this is the case, we exit the function, because we only want to react on the first real key press event. Also, we do not call the base class implementation of that event handler since no item on the scene needs to get a key press event. If you do have items that could and should receive events, do not forget to forward them while reimplementing event handlers at the scene.
As soon as we know that the event was not delivered by an auto repeat, we react to the different key presses. Instead of calling the setDirection() method of the Player *m_player field directly, we use the m_horizontalInput class field to accumulate the input value. Whenever it's changed, we ensure the correctness of the value before passing it to setDirection(). For that, we use qBound(), which returns a value that is bound by the first and the last arguments. The argument in the middle is the actual value that we want to get bound, so the possible values in our case are restricted to -1, 0, and 1.
You might wonder, why not simply call m_player->setDirection(1) when the right key is pressed? Why accumulate the inputs in the m_horizontalInput variable? Well, Benjamin is moved by the left and right arrow keys. If the right key is pressed, 1 is added; if it gets released, -1 is added. The same applies for the left key, but only the other way around. The addition of the value rather than setting it is now necessary because of a situation where a user presses and holds the right key, and the value of m_direction is therefore 1. Now, without releasing the right key, they also press and hold the left key. Therefore, the value of m_direction is getting decreased by one; the value is now 0 and Benjamin stops. However, remember that both keys are still being pressed. What happens when the left key is released? How would you know in this situation in which direction Benjamin should move? To achieve that, you would have to find out an additional bit of information—whether the right key is still pressed down or not, which seems too much trouble and overhead. In our implementation, when the left key is released, 1 is added and the value of m_direction becomes 1, making Benjamin move right. Voilà! All without any concern about what the state of the other button might be.
After calling setDirection(), we call the checkTimer() function:
void MyScene::checkTimer() { if (m_player->direction() == 0) { m_timer.stop(); } else if (!m_timer.isActive()) { m_timer.start(); } }
This function first checks whether the player moves. If not, the timer is stopped, because nothing has to be updated when our elephant stands still. Otherwise, the timer gets started, but only if it isn't already running. We check this by calling isActive() on the timer.
When the user presses the right key, for example, at the beginning of the game, checkTimer() will start m_timer. Since its timeout signal was connected to movePlayer(), the slot will be called every 30 milliseconds till the key is released.
Since the movePlayer() function is a bit longer, let's go through it step by step:
const int direction = m_player->direction(); if (0 == direction) { return; }
First, we cache the player's current direction in a local variable to avoid multiple calls of direction(). Then, we check whether the player is moving at all. If they aren't, we exit the function because there is nothing to animate:
const int dx = direction * m_velocity; qreal newX = qBound(m_minX, m_currentX + dx, m_maxX); if (newX == m_currentX) { return; } m_currentX = newX;
Next, we calculate the shift the player item should get and store it in dx. The distance the player should move every 30 milliseconds is defined by the int m_velocity member variable, expressed in pixels. You can create setter and getter functions for that variable if you like. For us, the default value of 4 pixels will do the job. Multiplied by the direction (which could only be 1 or -1 at this point), we get a shift of the player by 4 pixels to the right or to the left. Based on this shift, we calculate the new x position of the player. Next, we check whether that new position is inside the range of m_minX and m_maxX, two member variables that are already calculated and set up properly at this point. Then, if the new position is not equal to the actual position, which is stored in m_currentX, we proceed by assigning the new position as the current one. Otherwise, we exit the function since there is nothing to move.
The next question to tackle is whether the view should always move when the elephant is moving, which means that the elephant would always stay, say, in the middle of the view. No, he shouldn't stay at a specific point inside the view. Rather, the view should be fixed when the elephant is moving. Only if he reaches the borders should the view follow. Let's say that when the distance between the elephant's center and the window's border is less than 150 pixels, we will try to shift the view:
const int shiftBorder = 150; const int rightShiftBorder = width() - shiftBorder; const int visiblePlayerPos = m_currentX - m_worldShift; const int newWorldShiftRight = visiblePlayerPos - rightShiftBorder; if (newWorldShiftRight > 0) { m_worldShift += newWorldShiftRight; } const int newWorldShiftLeft = shiftBorder - visiblePlayerPos; if (newWorldShiftLeft > 0) { m_worldShift -= newWorldShiftLeft; } const int maxWorldShift = m_fieldWidth - qRound(width()); m_worldShift = qBound(0, m_worldShift, maxWorldShift); m_player->setX(m_currentX - m_worldShift);
The int m_worldShift class field shows how much we have already shifted our world to the right. First, we calculate the actual coordinate of our elephant in the view and save it to the visiblePlayerPos variable. Then, we calculate its position relative to the allowed area defined by the shiftBorder and rightShiftBorder variables. If visiblePlayerPos is beyond the right border of the allowed area, newWorldShiftRight will be positive, we need to shift the world by newWorldShiftRight to the right. Similarly, when we need to shift it to the left, newWorldShiftLeft will be positive, and it will contain the needed amount of shift. Finally, we update the position of m_player using a setX() helper method that is similar to setPos() but leaves the y coordinate unchanged.
The last important part to do here is to apply the new value of m_worldShift by setting positions of the other world items. While we're at it, we will implement parallax scrolling.