Tutorial 3
Camera Movement
In this tutorial, we will enlarge our world view by moving around using the CameraSystem.
Recap
Remember what we did in the last tutorial?
We created aCityBuildingSystemwhich allows you to build Cities anywhere whenever you press F1.
Final Code
The final code for tutorial 3 is available here at GitHub.
The CameraSystem
The CameraSystem is unlike other systems we’ve seen (CityBuildingSystem, RenderSystem, MouseSystem). Instead,
this system is given to each Scene, automatically. It allows you to
change the location from which you’re looking onto the game. As this is a 2D-game engine, we’re only allowing you to
change the X-axis, Y-axis and the Z-axis is simply the viewing distance. You also have control over the angle of the camera.
Note
You may not add the
CameraSystemmanually to theWorld. This happens automatically.
There are a few ways to use this CameraSystem, and each one will be discussed separately:
- By adding the
common.KeyboardScroller, you can move around using predefined keys; - By adding the
common.EdgeScroller, you can move around by moving your cursor close to the edges of the screen; - By adding the
common.MouseZoomer, you can zoom in/out by using the scroll-wheel on your mouse; - By sending a
CameraMessagethrough theMailbox. What the Mailbox is will also be explained.
KeyboardScroller
The KeyboardScroller is another System - one that listens for keyboard input and moves the screen accordingly.
Let’s add one in the Setup function of our game:
// Setup is called before the main loop starts. It allows you to add entities and systems to your Scene.
func (*myScene) Setup(world *ecs.World) {
engo.SetBackground(color.White)
world.AddSystem(&engo.MouseSystem{})
world.AddSystem(&engo.RenderSystem{})
kbs := common.NewKeyboardScroller(
400, engo.DefaultHorizontalAxis,
engo.DefaultVerticalAxis)
world.AddSystem(kbs)
world.AddSystem(&systems.CityBuildingSystem{})
}The magic number 400 here is the speed at which the camera moves. It’s mostly a relative number, meaning:
at zoom level 1x (so everything is at 100%), it will move 400 units per second. You can change this to any value
you feel fits to your game.
We must also tell Engo what keys will move us around. For now, we can simply use the default settings to do so, by changing our run options:
opts := engo.RunOptions{
Title: "Traffic Manager",
Width: worldWidth,
Height: worldHeight,
StandardInputs: true,
}You can now move around using the arrow keys.
EdgeScroller
The EdgeScroller is just like the KeyboardScroller, a plug-and-play system which we can easily add to our game.
Its usage is pretty much the same as the KeyboardScroller:
world.AddSystem(&common.EdgeScroller{400, 20})It uses the same scroll-speed as the KeyboardScroller (400) in this case. The magic number 20 here is the number
of pixels the cursor has to be near the edge of the screen in order to count as being “near the edge”. The higher the
number, the sooner this system will decide to move the camera around.
MouseZoomer
As with the other two, the MouseZoomer is a System we can add to our game.
world.AddSystem(&common.MouseZoomer{-0.125})The magic number -0.125 is the zoom speed. The fact that this number is negative indicates that scrolling down
means zooming out.
Using CameraMessages and the Mailbox
The Mailbox in engo is a place where systems can Listen for certain message types, and other systems can send
such messages. In this specific scenario, the CameraSystem is constantly listening for CameraMessage messages to
arrive, and moves the camera around accordingly. The KeyboardScroller, EdgeScroller and ZoomScroller all make
use of this: they all send CameraMessage messages.
Each
Scenehas one Mailbox, one World and one CameraSystem. The mailbox of the currentScenecan always be accessed viaengo.Mailbox. TheWorldwill be given via theSetupandNewmethods, and theCameraSystemcan be invoked by usingCameraMessagemessages.
Let’s look at a few CameraMessage messages.
// Move 100 units to the bottom on the Y-axis.
engo.Mailbox.Dispatch(CameraMessage{
Axis: YAxis,
Value: 100,
Incremental: true,
})
// Go to Y-value 100.
engo.Mailbox.Dispatch(CameraMessage{
Axis: YAxis,
Value: 100,
Incremental: false,
})
// Zoom out a bit
engo.Mailbox.Dispatch(CameraMessage{
Axis: ZAxis,
Value: -1,
Incremental: true,
})
// Zoom in a bit
engo.Mailbox.Dispatch(CameraMessage{
Axis: ZAxis,
Value: 2,
Incremental: true,
})
// Let's view everything at 100% zoom (default)
engo.Mailbox.Dispatch(CameraMessage{
Axis: ZAxis,
Value: 1,
Incremental: false,
})
// Move 100 units to the right
engo.Mailbox.Dispatch(CameraMessage{
Axis: XAxis,
Value: 100,
Incremental: true,
})What if we wanted to listen for messages?
engo.Mailbox.Listen("CameraMessage", func(msg Message) {
// Do things with msg
// If you want to access values other than its
// type, use type-assertions
// e.g.
// camMsg, ok := msg.(CameraMessage)
// if !ok {
// return
// }
})Note that we’re using the string
"CameraMessage"here. A lot of interfaces inengohave aType() stringmethod. These will always be used to uniquely define a type (to avoid using the slow reflection of type assertions). This basically means: call this function as well, whenever you receive a message which says it is of typeCameraMessage.
World Bounds
You may have noticed you cannot zoom out or move to the left/right/top/bottom indefinitely.
This is because engo attempts to find out which “world bounds” would suit the game. Usually,
it looks at the window size, and uses that as a guideline. However, you will most likely always want to
set common.CameraBounds to something suitable for your game. This is usually the size of your map, so people can’t
move away from it. CameraBounds requires you to define two points: the upper-left corner (CameraBounds.Min), and
the lower-right corner (CameraBounds.Max).
We won’t be setting it to anything now, because we have no “map” at the moment.
Find out how to add a heads-up display that doesn’t move with the camera in our next tutorial!
engo