I am pretty new to godot so I have a question about how you cant subtract from a position.
What I am trying to do is getting setting the global position of a object to the global position of my player but about 20 pixels in front.
So what I would like to do is:
Object.global_position = global_position
but then add 20 to the x of that global position.
Short way: Object.global_position = global_position + Vector2(20, 0) (adding a vector directly to the position)
Long way:
var pos = global_position
pos.x += 20
Object.global_position = pos
(adding 20 to x)
Related
I was studying a Mizizizi project and it doesn't even have a function implemented that I found very valuable (which is the Bresenham algorithm). The code and video of the game he developed are accessible via GitHub.
In the code there is a commented function, which I believe draws the lines (circles) that are shown in the video in order to show how the enemy is orienting himself in relation to the player's movement. In fact, I would like help on how to implement, if possible, counting these circles that are drawn from the enemy to the player.
#func _draw():
# for sp in sight_points:
# draw_circle(sp, 4, Color.red)
My intention is to use this information to calculate whether or not I can hit an enemy in a ranged attack action.
Searching for sight_points in the linked repository takes me to Enemy.gd, which I proceed to copy below:
extends Node2D
var alerted = false
func alert():
alerted = true
$AnimationPlayer.play("alert")
$AlertSounds.get_child(randi() % $AlertSounds.get_child_count()).play()
var sight_points = []
# line of sight algorithm found here: http://www.roguebasin.com/index.php?title=Bresenham%27s_Line_Algorithm
func has_line_of_sight(start_coord, end_coord, tilemap: TileMap):
var x1 = start_coord[0]
var y1 = start_coord[1]
var x2 = end_coord[0]
var y2 = end_coord[1]
var dx = x2 - x1
var dy = y2 - y1
# Determine how steep the line is
var is_steep = abs(dy) > abs(dx)
var tmp = 0
# Rotate line
if is_steep:
tmp = x1
x1 = y1
y1 = tmp
tmp = x2
x2 = y2
y2 = tmp
# Swap start and end points if necessary and store swap state
var swapped = false
if x1 > x2:
tmp = x1
x1 = x2
x2 = tmp
tmp = y1
y1 = y2
y2 = tmp
swapped = true
# Recalculate differentials
dx = x2 - x1
dy = y2 - y1
# Calculate error
var error = int(dx / 2.0)
var ystep = 1 if y1 < y2 else -1
# Iterate over bounding box generating points between start and end
var y = y1
var points = []
for x in range(x1, x2 + 1):
var coord = [y, x] if is_steep else [x, y]
points.append(coord)
error -= abs(dy)
if error < 0:
y += ystep
error += dx
if swapped:
points.invert()
sight_points = []
for p in points:
sight_points.append(to_local(Vector2.ONE * 8 + tilemap.map_to_world(Vector2(p[0], p[1]))))
#update()
for point in points:
if tilemap.get_cell(point[0], point[1]) >= 0:
return false
return true
#
#func _draw():
# for sp in sight_points:
# draw_circle(sp, 4, Color.red)
func get_grid_path(start_coord, end_coord, astar: AStar2D, astar_points_cache: Dictionary):
#sight_points=[]
#update()
var path = astar.get_point_path(astar_points_cache[str(start_coord)], astar_points_cache[str(end_coord)])
return path
I remind you that the code is under MIT license, by Miziziziz. I'm taking it here study purposes only.
We can see the quoted comment in the code above.
Also know that sight_points is not used outside this file. In fact, outside form the initialization and a commented use in get_grid_path, the sight_points is only used in the method has_line_of_sight.
According to the comment on has_line_of_sight it is based on Bresenham's Line Algorithm (It is not clear to me under which license the code on that page is). Although the page does not have a GDScript version of the algorithm, the Python version seems similar enough to what Miziziziz did (you can see that it has some of the same comments, and variable names), feel free to compare it in more detail.
Now, since your are interested in knowing how many items are there in the sight_points variable, notice that it is initialized as an array sight_points = [], so you can query its size with sight_points.size().
And that leaves us with the problem of where/when to do it.
Searching for has_line_of_sight in the linked repository we find that it is called in Game.gd, which I will partially quote copy below:
for enemy in enemies.values():
var enemy_pos = world_pos_to_map_coord(enemy.global_position)
if !enemy.alerted and enemy.has_line_of_sight(player_pos, enemy_pos, tilemap):
enemy.alert()
The above snipped is from the _process method. Once more, I remind you that the code is under MIT license, by Miziziziz. I'm taking it here study purposes only.
In the code enemies is a dictionary. We are iterating over its values (not its keys), each one being an enemy object (which are objects whose class is the script we saw before).
The code calls has_line_of_sight between the position of the player and the enemy, and depending on the result it will call alert on the enemy. Having another look at Enemy.gd we see that the alert method marks the enemy as alerted and plays some animation and sound.
We will use this as example of how to use has_line_of_sight. Also, if you recall, has_line_of_sight populates sight_points… It is not a great API but we can do this:
var enemy_pos = world_pos_to_map_coord(enemy.global_position)
if enemy.has_line_of_sight(player_pos, enemy_pos, tilemap) and enemy.sight_points.size() < shoot_distance:
# shoot
pass
Where shoot_distance is some variable or constant you would have to declare.
The important thing here is that we access sight_points right after calling has_line_of_sight. However, that might be not necessary after all. If there is a line of sight, the path is a straight line, so the distance between their global positions might be good enough.
We saw that we had an enemies dictionary. Turns out the keys of the dictionary are the positions on the world. So when the code moves an enemy it removes it from the dictionary and adds it back at another key (from the move_character method):
enemies.erase(str(old_coords))
enemies[str(coords)] = character
So, I believe that would be the way to kill an enemy: remove it from the dictionary… And presumably call queue_free on it.
I don't know how you want to pick the enemy to shoot. But, recall you can iterate over them, so if you want - for example - shoot the nearest one, you can do something like this (not tested code):
var nearest_distance := INF
var nearest_coords := null
for enemy in enemies.values():
var enemy_pos = world_pos_to_map_coord(enemy.global_position)
if enemy.has_line_of_sight(player_pos, enemy_pos, tilemap):
var distance := enemy.sight_points.size()
if distance < shoot_distance and distance < nearest_distance:
nearest_distance = distance
nearest_coords = enemy_pos
if nearest_coords != null:
var enemy = enemies[nearest_coords]
enemies.erase(nearest_coords)
enemy.queue_free()
And if you want enemies to shoot the player, then you would use similar checks, but this time call the kill method (again see move_character method for reference).
Finally notice that the moves_left variable controls when it is the turn of the enemies. When if moves_left == 0: the enemies move (I did not copy the relevant code to this answer). So, if shooting should take a move, you can do moves_left -= 1
Godot Version: v4.0-alpha15
I have a terrain being generated using the SurfaceTool and the MeshInstance3D. I am also moving a purple decal acrossed the surface based on the 3D mouse position. Below is a screenshot of what this looks like.
I want to take the 3D mouse position and raise/lower the terrain on an action press. I found the MeshDataTool but am not quite sure if this allows for that and also not completely sure how to convert the 3D mouse position to the corresponding vertices.
At this point I am sort of completely stuck as there's not a whole lot of documentation that I could find that helps.
I appreciate the help in advance!
I was actually able to figure this out using the MeshDataTool as I mentioned in the original post.
I use the Vector3::distance_to method to get any vertex within 3 of the target position, then I use MeshDataTool::set_vertex in combination with Vector3 methods to add and subtract each of those vertex positions.
var target_position = Vector3(0, 0, 0)
var mdt = MeshDataTool.new()
var mesh = terrain_mesh_instance.mesh
mdt.create_from_surface(mesh, 0)
var points = get_vertex_id(mdt, target_position)
for i in points:
if Input.is_action_pressed("shift"):
mdt.set_vertex(i, mdt.get_vertex(i) + Vector3(0, 1 * delta,0))
elif Input.is_key_pressed(KEY_ALT):
var coords = mdt.get_vertex(i)
coords.y = 0
mdt.set_vertex(i, coords)
else:
mdt.set_vertex(i, mdt.get_vertex(i) + Vector3(0, -1 * delta,0))
# This fixes the normals so the shadows work properly
for face in mdt.get_face_count():
var vertex = mdt.get_face_vertex(face, 0)
var normal = mdt.get_face_normal(face)
mdt.set_vertex_normal(vertex, normal)
mesh.clear_surfaces()
mdt.commit_to_surface(mesh)
I have a a 2d Line render that creates loops, a Issue I have noticed is that when looping at speed it sometimes doesn't detect, what I want to know is how to stop this from happening, another issue is how to make it more forgiving for example if the line is really close to a previous line I would like it to count that as a loop, and also how would I make the line actually be the mouse pointer instead of having it ghost behind and this might be a issue in the future I currently have it create an area 2d to detect items/objects inside its self I was wondering if there is a better way to accurately detect them inside said loop.
I have a video link I uploaded to show you the issue visually: https://www.youtube.com/watch?v=Jau7YDpZehY
extends Node2D
var points_array = PoolVector2Array()
#var check_array = []
var colision_array = []
var index : int = 0
onready var Line = $Line2D
onready var collision = $Area2D/CollisionPolygon2D
onready var collision_area = $Loop_collider
func _physics_process(delta):
Line.points = points_array # Link the array to the points and polygons
collision.polygon = points_array
if Input.is_action_just_pressed("left_click"): #This sets a position so that the next line can work together
points_array.append(get_global_mouse_position()) # This makes a empty vector and the mouse cords is assigned too it
#points_array.append(Vector2()) #This is the vector that follows the mouse
if Input.is_action_pressed("left_click"): #This checks the distance between last vector and the mouse vector
#points_array[-1] = get_global_mouse_position() # Gets the last position of the array and sets the mouse cords
var mouse_pos = get_global_mouse_position()
var distance = 20
while points_array[-1].distance_to(mouse_pos) > distance:
var last_point = points_array[-1]
var cords = last_point + last_point.direction_to(mouse_pos) * distance
points_array.append(cords)
create_collision()
if points_array.size() > 80: # This adds a length to the circle/line so it wont pass 18 mini lines
points_array.remove(0) #Removes the first array to make it look like it has a length
#check_array = []
colision_array[0].queue_free()
colision_array.remove(0)
if Input.is_action_just_released("left_click"): # This just clears the screen when the player releases the button
points_array = PoolVector2Array()
#check_array = []
for x in colision_array.size():
colision_array[0].queue_free()
colision_array.remove(0)
#index = 0
if points_array.size() > 3: # If the loop is long enough, to detect intersects
if points_array[0].distance_to(get_global_mouse_position()) < 5: # Checks if the end of the loop is near the end, then start new loop
new_loop()
for index in range(0, points_array.size() - 3):
if _segment_collision(
points_array[-1],
points_array[-2],
points_array[index],
points_array[index + 1]
):
new_loop()
break
#if check_array.size() != points_array.size():
# check_array = points_array
#create_collision()
func _segment_collision(a1:Vector2, a2:Vector2, b1:Vector2, b2:Vector2) -> bool:
# if both ends of segment b are to the same side of segment a, they do not intersect
if sign(_wedge_product(a2 - a1, b1 - a1)) == sign(_wedge_product(a2 - a1, b2 - a1)):
return false
# if both ends of segment a are to the same side of segment b, they do not intersect
if sign(_wedge_product(b2 - b1, a1 - b1)) == sign(_wedge_product(b2 - b1, a2 - b1)):
return false
# the segments must intersect
return true
func _wedge_product(a:Vector2, b:Vector2) -> float:
# this is the length of the cross product
# it has the same sign as the sin of the angle between the vectors
return a.x * b.y - a.y * b.x
func new_loop(): # Creates a new loop when holding left click and or loop is complete
var new_start = points_array[-1]
collision.polygon = points_array
points_array = PoolVector2Array()
collision.polygon = []
#check_array = []
points_array.append(new_start)
for x in colision_array.size():
colision_array[0].queue_free()
colision_array.remove(0)
func create_collision(): # Creates collisions to detect when something hits the line renderer
var new_colision = CollisionShape2D.new()
var c_shape = RectangleShape2D.new()
var mid_point = Vector2((points_array[-1].x + points_array[-2].x) / 2,(points_array[-1].y + points_array[-2].y) / 2)
c_shape.set_extents(Vector2(10,2))
new_colision.shape = c_shape
if points_array.size() > 1:
colision_array.append(new_colision)
collision_area.add_child(new_colision)
new_colision.position = mid_point
#new_colision.position = Vector2((points_array[-1].x),(points_array[-1].y))
new_colision.look_at(points_array[-2])
func _on_Area2D_area_entered(area): # Test dummies
print("detect enemy")
func _on_Loop_collider_area_entered(area):
print("square detected")
Diagnosis
Symptom 1
I have noticed is that when looping at speed it sometimes doesn't detect
This is what happens:
When the mouse pointer moved too much between frames, it creates multiple segments, here:
var mouse_pos = get_global_mouse_position()
var distance = 20
while points_array[-1].distance_to(mouse_pos) > _distance:
var last_point = points_array[-1]
var cords = last_point + last_point.direction_to(mouse_pos) * distance
points_array.append(cords)
create_collision()
But the check for collisions is only comparing the last one, here:
for index in range(0, points_array.size() - 3):
if _segment_collision(
points_array[-1],
points_array[-2],
points_array[index],
points_array[index + 1]
):
new_loop()
break
*Remember that [-1] gives the last item, and [-2] gives the second to last.
As a consequence, the intersection can happen on one of the segments that weren't checked.
Symptom 2
how to make it more forgiving for example if the line is really close to a previous line I would like it to count that as a loop
We could check distance from point to segment.
Symptom 3
how would I make the line actually be the mouse pointer instead of having it ghost behind
Currently the segments are all of the same length. This seems to be a limitation of the way you create CollisionShape2D.
Treatment selection
We could address Symptom 1 by checking every segment. Symptom 2 by improving said checking. But we would still need a solution for Symptom 3 that allows variable segment lengths.
If we create a solution that supports variable segment lengths, we would not need to create multiple segments at once, which solves Symptom 1. We would still need to improve the checking to solve Symptom 2.
If we need to improve the way we check collisions and we are rewriting the collisions anyway, we might as well implement something that allows us to detect self intersections.
We are going to transplant a new way to define collision shapes which allows us to make rotated rectangles of the dimensions we want.
Surgery
I ended up rewriting the whole script. Because I'm like that, I guess.
I decided to have the script create its child nodes in the following structure:
Node
├_line
├_segments
└_loops
Here_line will be a Line2D, _segments will hold multiple Area2D, each a segment. And _loops will also hold Area2D, but they are the polygons of the loops traced.
This will be done in _ready:
var _line:Line2D
var _segments:Node2D
var _loops:Node2D
func _ready() -> void:
_line = Line2D.new()
_line.name = "_line"
add_child(_line)
_segments = Node2D.new()
_segments.name = "_segments"
add_child(_segments)
_loops = Node2D.new()
_loops.name = "_loops"
add_child(_loops)
Another decision I took was to consider the way of the data on the application: We are taking positions. The first position is when the click is just pressed. Subsequent positions are when it moves. From those positions we take points to add to both the line and the segments. From the segments we will get the loops. And we will continue in this manner until the click is released.
Well, if whether or not click was just pressed or it is held, it does not matter. Either way, we take the position of the mouse.
Now, _physics_process will look like this:
func _physics_process(_delta:float) -> void:
if Input.is_action_pressed("left_click"):
position(get_global_mouse_position())
# TODO
We also need to handle when the click is released. Let us make a function for that and worry later about it:
func _physics_process(_delta:float) -> void:
if Input.is_action_pressed("left_click"):
position(get_global_mouse_position())
if Input.is_action_just_released("left_click"):
total_purge()
On position we will follow that odd trick of moving the last point to match the most recent position. We need to make sure that there are at least two points. So the first point does not move, and we can safely move the last point.
var _points:PoolVector2Array = PoolVector2Array()
var _max_distance = 20
func position(pos:Vector2) -> void:
var point_count = _points.size()
if point_count == 0:
_points.append(pos)
_points.append(pos)
elif point_count == 1:
_points.append(pos)
else:
if _points[-2].distance_to(pos) > _max_distance:
_points.append(pos)
else:
_points[-1] = pos
Notice we check the distance to the second to last point. We cannot check against the last point because that is the one we are moving.
If the distance is greater than _max_dinstance then we add a new point, otherwise we move the last point.
We also need to add and update segments:
var _points:PoolVector2Array = PoolVector2Array()
var _max_distance = 20
func position(pos:Vector2) -> void:
var point_count = _points.size()
if point_count == 0:
_points.append(pos)
_points.append(pos)
add_segment(pos, pos)
elif point_count == 1:
_points.append(pos)
add_segment(_points[-2], pos)
else:
if _points[-2].distance_to(pos) > _max_distance:
_points.append(pos)
add_segment(_points[-2], pos)
else:
_points[-1] = pos
change_segment(_points[-2], pos)
You know, we worry later about how that works.
We also need to handle the case when there are too many points:
var _points:PoolVector2Array = PoolVector2Array()
var _max_points = 30
var _max_distance = 20
func position(pos:Vector2) -> void:
var point_count = _points.size()
if point_count == 0:
_points.append(pos)
_points.append(pos)
add_segment(pos, pos)
elif point_count == 1:
_points.append(pos)
add_segment(_points[-2], pos)
elif point_count > _max_points:
purge(point_count - _max_points)
else:
if _points[-2].distance_to(pos) > _max_distance:
_points.append(pos)
add_segment(_points[-2], pos)
else:
_points[-1] = pos
change_segment(_points[-2], pos)
We need to update the Line2D, and we need to handle any loops:
var _points:PoolVector2Array = PoolVector2Array()
var _max_points = 30
var _max_distance = 20
func position(pos:Vector2) -> void:
var point_count = _points.size()
if point_count == 0:
_points.append(pos)
_points.append(pos)
add_segment(pos, pos)
elif point_count == 1:
_points.append(pos)
add_segment(_points[-2], pos)
elif point_count > _max_points:
purge(point_count - _max_points)
else:
if _points[-2].distance_to(pos) > _max_distance:
_points.append(pos)
add_segment(_points[-2], pos)
else:
_points[-1] = pos
change_segment(_points[-2], pos)
_line.points = _points
process_loop()
Alright, let us talk about adding and updating segments:
var _width = 5
func add_segment(start:Vector2, end:Vector2) -> void:
var points = rotated_rectangle_points(start, end, _width)
var segment = Area2D.new()
var collision = create_collision_polygon(points)
segment.add_child(collision)
_segments.add_child(segment)
func change_segment(start:Vector2, end:Vector2) -> void:
var points = rotated_rectangle_points(start, end, _width)
var segment = (_segments.get_child(_segments.get_child_count() - 1) as Area2D)
var collision = (segment.get_child(0) as CollisionPolygon2D)
collision.set_polygon(points)
Here _width is the width of the collision polygons we want.
We are either adding an Area2D with a collision polygon (created via function we will worry about later), or we are taking the last Area2D and updating its collision polygon by the same means.
So, how do we get the points for the rotated rectangle?
static func rotated_rectangle_points(start:Vector2, end:Vector2, width:float) -> Array:
var diff = end - start
var normal = diff.rotated(TAU/4).normalized()
var offset = normal * width * 0.5
return [start + offset, start - offset, end - offset, end + offset]
So you take the vector that goes from the start to the end of the segment, and rotate it a quarter turn (a.k.a. 90º). That gives you a vector that is normal (perpendicular) to the segment, which we will use to give it width.
From the starting point, we find the first point of the rectangle by going half width in the normal direction, and we find the second by going the other half width in the opposite direction. Do the same with the ending point and we have the four corners of the rectangle.
And we return them in an order such that they go around the rectangle.
Creating a collision polygon with those points is straight forward:
static func create_collision_polygon(points:Array) -> CollisionPolygon2D:
var result = CollisionPolygon2D.new()
result.set_polygon(points)
return result
Ok, let us talk about purging. I added a function to purge points (of the line) and segments. That is part of the total purge. The other part will be removing the loops:
func total_purge():
purge(_points.size())
purge_loops()
That was easy.
Alright, to purge points and segment we iterate and remove them.
func purge(index:int) -> void:
var segments = _segments.get_children()
for _index in range(0, index):
_points.remove(0)
if segments.size() > 0:
_segments.remove_child(segments[0])
segments[0].queue_free()
segments.remove(0)
_line.points = _points
That check for if segments.size() > 0 is necessary, by the way. Sometimes the purge leaves points without segment, which cause problems later. And this is the simpler solution.
And, of course, we have to update the Line2D.
What about purging loops? Well, you remove them all:
func purge_loops() -> void:
for loop in _loops.get_children():
if is_instance_valid(loop):
loop.queue_free()
Finally we can process the loops. We will be checking the overlapping areas of the segments to find if they intersect with each other.
One caveat: we want to ignore overlaps of adjacent segments (which are bound to happen, and do not constitute loops).
So we iterate over the segments, check the overlapping areas, look for them among the segments (if they are there at all), and if they are not adjacent (the different of their index among the segments must be greater than 1). If all that happens, we have a loop:
func process_loop() -> void:
var segments = _segments.get_children()
for index in range(segments.size() - 1, 0, -1):
var segment = segments[index]
var candidates = segment.get_overlapping_areas()
for candidate in candidates:
var candidate_index = segments.find(candidate)
if candidate_index == -1:
continue
if abs(candidate_index - index) > 1:
push_loop(candidate_index, index)
purge(index)
return
So, when a loops happens we want to do something with it, right? That is what push_loop is for. We also want to remove the points and segments that were part of the loop (or were before the loop), so we call purge.
Only push_loop is left to discuss:
func push_loop(first_index:int, second_index:int) -> void:
purge_loops()
var loop = Area2D.new()
var points = _points
points.resize(second_index)
for point_index in first_index + 1:
points.remove(0)
var collision = create_collision_polygon(points)
loop.add_child(collision)
_loops.add_child(loop)
As you can see, it creates an Area2D, with a collision polygon that corresponds to the loop. I decide to use rezise to remove points that are after the loop, and a for loop to remove the points that are before. So only the points of the loop remain.
Also notice I'm calling purge_loops at the start, that ensures there will only be one loop at a time.
Going back to the symptoms: Symptoms 1 and 3 are solved by that trick of always moving the last point (and updating the segment). And Symptom 2 is addressed by the width of the rectangles. Tweak that value.
I have a data cube with 2 dimensions of coordinates and a third dimension for wavelength. My goal is to write a mask for coordinates outside a circle of given radius to the central coordinates (x0 and y0 in my code). For this, I'm trying to use a dictionary, but I'm having throuble because it seems that I'll have to make a double loop inside the dictionary to iterate over the two dimensions, and as a beginner with dictionaries, I don't know yet how to do that.
I wrote the following code
x0 = 38
y0 = 45
radius = 9
xcoords = np.arange(1,flux.shape[1]+1,1)
ycoords = np.arange(1,flux.shape[2]+1,1)
mask = {'xmask': [xcoords[np.sqrt((xcoords[:]-x0)**2 + (y-y0)**2) < radius] for y in ycoords], 'ymask': [ycoords[np.sqrt((x-x0)**2 + (ycoords[:]-y0)**2) < radius] for x in xcoords]}
And it returned several arrays, one for each value of y (for xmasks), and one for each value of x (for ymasks), although I want just one array for each one. Could anyone say what I made wrong and how to achieve my goal?
Note: I also made it without using a dictionary, as
xmask = []
for x in xcoords:
for y in ycoords:
if np.sqrt((x-x0)**2 + (y-y0)**2) < radius:
xmask.append(x)
break
ymask = []
for y in xcoords:
for x in ycoords:
if np.sqrt((x-x0)**2 + (y-y0)**2) < radius:
ymask.append(y)
break
but I hope it's possible to make it more efficiently.
Thanks for any help!
Edit: I realized that no loop was needed. If I select y = y0 and x = x0, I get the values of x and y that are inside the circle, respectively. So I stayed with
mask = {'xmask': [xcoords[abs(xcoords[:]-x0) < radius]], 'ymask': [ycoords[abs(ycoords[:]-y0) < radius]]}
The OP explains that assigning
mask = {'xmask': [xcoords[abs(xcoords[:] - x0) < radius]],
'ymask': [ycoords[abs(ycoords[:] - y0) < radius]]}
solves the problem.
So I've noticed that character only moves when my velocity is greater than 1 and is an integer but the question is why I can't use move_ip() function to have my character move slower than 1 velocity and in between 1 and 0?.
Here is the code that I use:
def Physics():
for k, v in enumerate(ENTITIES):
vel = ENTITIES[k].GetVelocity()
pos = ENTITIES[k].GetPos()
vel[1] = vel[1] + 0.1
ENTITIES[k].Entity.move_ip(vel)
The Rect class is typically used to describe an area of the screen, and pixels are integer values, and thus the properties of the Rect class are integers also (you can't draw something at position (1.3, 5.7), since that position does not exit on the screen).
If you want to keep track of a sub-pixel position, such as 4.5 or 1.2, you have to store it in another variable/field of your class.