DragonRuby: Following the Mouse
Recently I discovered it is very easy to have objects move towards (or away from) any points in DragonRuby.
This post might be a little easier if you’ve already read my post on moving in arbitrary directions, but actually the code here is even simpler.
If I skip any explanations here, the concepts should have been covered earlier in the series.
Setup
To get started, let’s just output a square (roughly) in the middle of the screen.
args.grid.center_x
and args.grid.center_y
are helpful for this instead of remembering/hardcoding the screen size.
In addition, the code uses args.state.tick_count == 0
to do some setup on the first tick.
def tick(args)
# On the first tick...
if args.state.tick_count == 0
# Create a 50x50 pixel square in the middle of the screen
args.state.player = { x: args.grid.center_x, y: args.grid.center_y, h: 50, w: 50}
# Output that square on every tick
args.outputs.static_solids << args.state.player
end
end
Pretty basic!
(Note I’m skipping straight to static_solids
because that’s what I’d prefer in a “real” game.)
Moving to a Point
Now we’ll move the “player” to a given point - in this case where the mouse is. In typical DragonRuby fashion, args.inputs.mouse
can be used to as a point, even though it has a bunch of other information attached to it.
To get the angle from the player to the mouse, there is a very convenient angle_to
method! (Also angle_from
depending on which way you’d like to go.)
Just like args.inputs.mouse
, the player
solid can be treated as if it is a point, too.
One the angle is calculated, vector_x
and vector_y
will provide the magnitude to move in the x
and y
directions.
def tick(args)
if args.state.tick_count == 0
args.state.player = {
x: args.grid.center_x,
y: args.grid.center_y,
h: 50,
w: 50
}
args.outputs.static_solids << args.state.player
end
# Find angle from the square to the current location of the mouse
angle = args.state.player.angle_to(args.inputs.mouse)
# Move towards the mouse using the unit vector
args.state.player.x += angle.vector_x
args.state.player.y += angle.vector_y
end
And… that’s it!! Less than 10 lines of code (without comments/spaces) and it just works.
So now let’s make it more complicated…
Centering
It is annoying that the player moves so the bottom, right-hand corner meets the mouse, there is a simple fix: use anchor_x
and anchor_y
. DragonRuby will automatically use the anchor point for calculations.
To learn more about anchor points, see this earlier post. But typically the values are set to 0.5
which means “middle of the object”.
def tick(args)
if args.state.tick_count == 0
args.state.player = {
x: args.grid.center_x,
y: args.grid.center_y,
h: 50,
w: 50,
anchor_x: 0.5,
anchor_y: 0.5,
}
args.outputs.static_solids << args.state.player
end
angle = args.state.player.angle_to(args.inputs.mouse)
args.state.player.x += angle.vector_x
args.state.player.y += angle.vector_y
end
Moving to Classes
I continue to prefer taking an object/class-based approach in my own code. This makes it much easier to manage as the code grows larger, even if it seems silly for these small examples.
In a little departure from previous posts, I am not walking through the code in detail. (Please check out my other posts in this series to learn more!)
The main difference from the above is adding a speed
to the movement calculation. Other than that, this is how I generally move from simple code like the above into a structure more suitable (in my opinion) as the code.
class Game
attr_gtk
def initialize(args)
# Separate setup method is easier if you need to
# reset during the game
setup(args)
end
def setup(args)
# Since static_sprites persists between ticks,
# need to clear it in case of reset
args.outputs.static_sprites.clear
@player = Player.new(x: args.grid.center_x, y: args.grid.center_y, h: 50, w: 50, speed: 2)
args.outputs.static_sprites << @player
end
def tick(args)
@player.tick(args)
end
end
class Player
attr_sprite
def initialize(x:, y:, h:, w:, speed:)
@x = x
@y = y
@h = h
@w = w
@anchor_x = 0.5
@anchor_y = 0.5
@speed = speed
end
def tick(args)
angle = self.angle_to(args.inputs.mouse)
@x += angle.vector_x * @speed
@y += angle.vector_y * @speed
end
end
def tick(args)
$Game ||= Game.new(args)
$Game.tick(args)
end
One minor note if you actually run this code - the square will be white (default for sprites) instead of black (default for solids).