Provides information for a MovementAnimation or a ColliderBase about the direction and rotation when moving towards a target. More...
Import Statement: | import Felgo 4.0 |
This component provides information about the direction and rotation when moving towards a target. This is a frequent requirement in games. Moving towards a fixed point, or to a moving entity is possible.
The strength of this component is, that it does all the calculations how far the target is away (distanceToTarget), how many degrees it is away considering the current rotation (absoluteRotationDifference) and in which direction (to the right or to the left) the target is (when outputXAxis is >0 it is to the right, if <0 it is to the left, relative to the current direction the owningEntity is facing).
This component alone does not move the owningEntity towards the target, but it sets the output properties outputXAxis and outputYAxis. You can connect these to a movement component. Examples for movement components (i.e. components that move an entity) are MovementAnimation, CircleCollider or BoxCollider. In other words, you can simulate what the player would do when moving towards a target. So this component allows using the same game logic for human players and for AI controlled entities.
The output properties of this component can be used to achieve the desired game logic. In most cases, connecting the outputXAxis and outputYAxis with a movement component is sufficient.
You can compare the outputXAxis and outputYAxis with the outputs of 2-axis controllers used by players, like the thumbstick on a gamepad. In case of the analog thumbstick, the normalized values range from -1 (on the very left) to +1 (on the right side). If the outputXAxis is -1, this means the connected movement component should rotate left, and to the right when outputXAxis is +1. A value of 0 means, there is no need for rotating, because the owningEntity already is rotated towards the target. While outputXAxis shows the required rotation, outputYAxis states the required forward or backward movement. If it is +1, a forward movement should be performed in order to reach the target. When it is -1 a backward movement should be performed.
Similarly, pressing the Up/Down/Left/Right buttons for player input could also be abstracted with a 2-axis controller, which is exactly what the TwoAxisController provides for player-controlled entities. It has the same output properties like this component, so it is possible to connect the same game logic with different input controllers. For example, one instance of a car entity might be controlled by the user with a TwoAxisController from keyboard or with a JoystickControllerHUD, whereas an AI controlled instance of the car gets controlled by a MoveToPointHelper.
You can add arbitrary complex game logic based on the outputs of this component, like the rotation towards the target or the distance to it. In summary, writing AI logic gets easier with the MoveToPointHelper.
You have 2 options to define the target of your entity: Setting a point or an object. If you accidentally set both, the target object's position is used and the targetPoint is ignored.
One use-case in games is to move towards another game entity. The other game entity might be moving as well, so the MoveToPointHelper also considers position updates of the target entity. You can set the targetObject property to any object with a position, so it needs not to be a game entity derived from EntityBase, but any QML Item.
When you do not want to follow a moving game entity, you can also set the targetPoint property to a fixed point. This point can then be changed, however, which might come handy for setting the targetPoint to the touch position for instance. So for a game entity to move towards the position where the user tapped onto, the following code can be used:
Scene { MouseArea { anchors.fill: parent onClicked: { moveToPointHelper.targetPoint = Qt.point(mouseX, mouseY) } } GameEntity { id: followerEntity MoveToPointHelper { id: moveToPointHelper // the targetPoint gets set from MouseArea } BoxCollider { width: 30; height:20 // move forwards and backwards, with a multiplication factor for the desired speed force: Qt.point(moveToPointHelper.outputYAxis*1000, 0) // rotate left and right torque: moveToPointHelper.outputXAxis*300 } } }
For example, you might have a game entity that can rotate around its axis. It should always rotate towards a moving game entity. To achieve this, the following code can be used:
Scene { GameEntity { id: targetEntity // this entity moves around, and the followerEntity should follow it } GameEntity { id: followerEntity MoveToPointHelper { id: moveToPointHelper // the entity to move towards targetObject: targetEntity } MovementAnimation { target: followerEntity property: "rotation" // outputXAxis is +1 if owningEntity should rotate right, -1 when it should rotate left and 0 when its aiming towards the target velocity: 300*moveToPointHelper.outputXAxis // alternatively, also the acceleration could be set, depends on how you want the followerEntity to behave // start rotating towards the target immediately running: true // this avoids over-rotating, so rotating further than allowed maxPropertyValueDifference: moveToPointHelper.absoluteRotationDifference } } }
If your movement should be done with a physics collider like BoxCollider or CircleCollider, you can set the input values for ColliderBase::force and ColliderBase::torque also by connecting it with the MoveToPointHelper:
Scene { GameEntity { id: targetEntity // this entity moves around, and the followerEntity should follow it } GameEntity { id: followerEntity MoveToPointHelper { id: moveToPointHelper // the entity to move towards targetObject: targetEntity // the following all are optional properties for customization: // when the distance to the target gets below this value, the targetReached signal is emitted, default is 20 distanceToTargetThreshold: 20 // this allows getting the outputYAxis bigger than 0, which is the default allowSteerForward: true // if this is set and the target is more than 90 away, allow to move backwards, default is false allowSteerBackward: false // if this is set, the follower would only rotate and not move backwards when the target is more than 90 degrees away, default is false stopForwardMovementAtDifferentDirections: false } BoxCollider { width: 30; height:20 // move forwards and backwards, with a multiplication factor for the desired speed force: Qt.point(moveToPointHelper.outputYAxis*1000, 0) // rotate left and right torque: moveToPointHelper.outputXAxis*300 } } }
[read-only] absoluteRotationDifference : real |
This property holds the absolute value of the rotation difference between the owningEntity and the target. It is always bigger or equal than 0 and smaller than 180. If it is bigger than 90 degrees, that means the target is in the different direction. To find out if the target is on the right (clockwise) or on the left (counterclockwise) side of the current direction the owningEntity is facing, check if outputXAxis is positive or negative. If it is positive, it is on the right side, otherwise on the left.
[since 1.5.1] allowSteerBackward : bool |
Set this property to allow the the outputYAxis property to become -1 when the target is back of the owningEntity. You can set it false, if you do not want the owningEntity to move forward, so limit the output properties to only rotating. The default value is true.
This property was introduced in Qt 1.5.1.
See also outputYAxis.
allowSteerForward : bool |
Set this property to allow the the outputYAxis property to become 1 when the target is in front of the owningEntity. You can set it false, if you do not want the owningEntity to move forward, so limit the output
properties to only rotating. The default value is true
.
See also outputYAxis.
[read-only] distanceToTarget : real |
This property holds the current distance from owningEntity to the target. The distance is the pythagorean distance of the x and y difference. For example, when
the owningEntity position is (0/0) and the targetEntity position is (30/40), the distance will be 50 because Math.sqrt((30-0)^2 + (40-0)^2 ) = 50
.
When the distanceToTarget gets below distanceToTargetThreshold, the targetReached() signal is emitted.
This property is always positive, so no negative distances can occur.
See also distanceToTargetThreshold.
distanceToTargetThreshold : real |
When the distanceToTarget gets below this threshold, the targetReached() signal is emitted. You can handle that in onTargetReached() and perform game code when that happens. The default value is 20.
See also distanceToTarget.
enabled : bool |
This property holds whether the output properties are updated. By default, this property is true. You can set it to false to avoid calculating the output properties in order to improve performance.
[read-only] outputXAxis : real |
This property holds the imaginary x axis value of a 2-axis controller which can be connected to a rotation input property of supported components. When it is 0, that is equal to no required rotation, similar to no key being pressed on the keyboard. When it is +1, a clockwise rotation should be performed similar to a right key press. When it is -1, a counterclockwise rotation should be performed similar to a left key press.
Currently, outputXAxis will not take values in between -1 and 0 or 0 and +1. If you want to use different output properties based on the absoluteRotationDifference, you can set your own properties based on the rotation difference value.
This property should be connected with a movement component like BoxCollider, CircleCollider or MovementAnimation. This example shows how to connect the outputXAxis property with the rotation property of a MovementAnimation:
Scene { GameEntity { id: targetEntity // this entity moves around, and the followerEntity should follow it } GameEntity { id: followerEntity MoveToPointHelper { id: moveToPointHelper // the entity to move towards targetObject: targetEntity } MovementAnimation { target: followerEntity property: "rotation" // outputXAxis is +1 if owningEntity should rotate right, -1 when it should rotate left and 0 when its aiming towards the target velocity: 300*moveToPointHelper.outputXAxis // alternatively, also the acceleration could be set, depends on how you want the followerEntity to behave // start rotating towards the target immediately running: true // this avoids over-rotating, so rotating further than allowed maxPropertyValueDifference: moveToPointHelper.absoluteRotationDifference } } }
[read-only] outputYAxis : real |
This property holds the imaginary y axis value of a 2-axis controller which can be connected to the velocity or force input property of supported components. When it is 0, that is equal to no required movement, similar to no key being pressed on the keyboard. When it is +1, a forward movement should be performed similar to an up key press. When it is -1, a backward movement should be performed similar to a down key press.
Currently, outputYAxis will not take values in between -1 and 0 or 0 and +1. If you want to use different output properties based on the distanceToTarget, you can set your own properties based on the distanceToTarget value.
This property should be connected with a movement component like BoxCollider, CircleCollider or MovementAnimation. This example shows how to connect the outputYAxis property with the force property of a BoxCollider:
Scene { GameEntity { id: targetEntity // this entity moves around, and the followerEntity should follow it } GameEntity { id: followerEntity MoveToPointHelper { id: moveToPointHelper // the entity to move towards targetObject: targetEntity } BoxCollider { width: 30; height:20 // move forwards and backwards, with a multiplication factor for the desired speed force: Qt.point(moveToPointHelper.outputYAxis*1000, 0) // rotate left and right, optional torque: moveToPointHelper.outputXAxis*300 } } }
owningEntity : Object |
The object whose current position and rotation is used for calculating the rotation difference and distance to the target. By default, this is set to the parent object where the component is added. But it can be set to any object.
rotationThreshold : real |
This property holds when the owningEntity should stop rotating. When the absoluteRotationDifference gets below this threshold, the outputXAxis property is set to 0. This is handy to avoid tiny constant rotations in one direction and then the other, if the angle just changes slightly.
If the movement component with which you connect this property supports to avoid overrotating, then you do not need to set this property because the connected component handles this issue. The MovementAnimation for example supports the overrotation avoidance, by setting the MovementAnimation::maxPropertyValue property to absoluteRotationDifference.
By default, this is set to 5 degrees. If you want to make the owningEntity aim narrower, decrease this value.
stopForwardMovementAtDifferentDirections : bool |
Set this property to set the outputYAxis to 0 when the target is more than 90 degrees away from the owningEntity. An absoluteRotationDifference bigger than 90 means the owningEntity points in a different direction than the target. So when set to true, the owningEntity will rotate without moving forward, and start moving forward again when the absoluteRotationDifference gets below 90 degrees.
By default, this is set to false. That means the owningEntity will always move forwards by default, also when the target is in the different direction. So the default setting leads to a bigger distance to travel.
targetObject : Object |
The target object to which position the owningEntity should move to. Based on this position, the output properties are set.
This property or targetPoint must be set for this component to work.
targetPoint : point |
The point where the owningEntity should move to. Based on this position, the output properties are set.
This property or targetPoint must be set for this component to work. If both targetObject and targetPoint are set, the targetPoint is ignored.
onTargetReached() |
This handler is called when the distanceToTarget property gets smaller than distanceToTargetThreshold. You can use this function to implement game logic when the target is reached.
Note: Once the signal is fired, it is not fired again unless the used targetObject or targetPoint changes.
For example steering towards the next point in a path when a waypoint is reached can be done with this code:
GameWindow { property variant waypoints: [ { x:16, y:48}, { x: 112, y: 48 }, { x: 112, y: 144}, { x: 16, y: 144 }, { x: 16, y: 240 }, { x: 144, y: 240 }, { x: 144, y: 176 }, { x: 208, y: 176 }, { x: 208, y: 16 }, { x: 336, y: 16 }, { x: 336, y: 80 }, { x: 432, y: 80 }, { x: 432, y: 112 }, { x: 304, y: 112 }, { x: 304, y: 208 }, { x: 464, y: 208 } ] function getPointFromIndex(index) { var wp = waypoints[index]; return Qt.point(wp.x, wp.y); } // this can be increased property int currentWaypointIndex: 0 EntityBase { // the visual representation of the entity, just for demo purposes Rectangle { id: rectangle; width: 20; height: 20} MoveToPointHelper { id: movetoPointHelper // this gets updated automatically, when currentWaypointIndex changes targetPoint: waypoints[currentWaypointIndex] onTargetReached: { // when the waypoint is reached, increase the index // check if we have reached the last waypoint // if so, move to the first again if(currentWaypointIndex+1 == waypoints.length) currentWaypointIndex = 0 else currentWaypointIndex++ } } BoxCollider { width: rectangle.width; height:rectangle.height // move forwards and backwards, with a multiplication factor for the desired speed force: Qt.point(moveToPointHelper.outputYAxis*1000, 0) // rotate left and right torque: moveToPointHelper.outputXAxis*300 } } // end of EntityBase }
This is essentially what the PathMovement does.
Note: The corresponding handler is onOnTargetReached
.