logologologologo
  • home
  • learn to code
    • Space Invaders – Beginner’s Course
    • Asteroids – Beginner’s Level 2
    • Space Commander – Beginners Level 3
    • Snake – a Beginner’s Challenge
  • learn to make
    • Arduino Tutorials
    • Raspberry Retro Games Console
  • blog
    • Computer Science
    • Coding
      • Collision Detection
    • Retro Gaming
    • Computer Games for Vision Therapy
  • contact me
GPIO Buttons for RetroPie
Using GPIO Pins For Button Inputs in RetroPie – Building Your Own Retro Gaming Handheld
9th May 2021
Fix Raspberry Pi Zero Sound Problems
Fix Sound Problems on Raspberry Pi Zero With GPIONext – Building Your Own Retro Gaming Handheld
24th May 2021
Published by Bob at 20th May 2021
Categories
  • Learn To Code
  • Learn to Code Space Commander
Tags
  • games programming
  • learn to code
  • tic80
  • vector graphics
Vector Graphics for Space Commander

Vector Graphics for Space Commander – Learn to Code Your Own Games

Vector Graphics  – Space Commander Part 1

Most computer games make use of sprites. These are small bitmap images that we use the represent the game elements such as ships, bullets, etc. Modern coding systems are able to rotate and scale bitmap images, but TIC80 forces us to think old school.

We can rotate bitmaps in 90 degree increments and we can scale in whole numbers (integers) this means that we can take our player ship and rotate and scale as shown below.

scaling and rotating bitmaps

If we want smoother rotation we need to create versions of our sprites at the in between angles. But even this has limits as we generally only redraw a couple of versions, maybe just the 45 degree angle version, which still gives jerky movement. If you’ve ever played retro games you’ll have seen this effect.

Galaxian sprite sheet

This is the sprite sheet for the game Galaxians. You can see multiple images for each alien type to allow it to be rotated on screen.

Vector Graphics to the Rescue

In Space Commander we can use bitmap sprites for the main player ship as that stays stationary in the centre of the screen. But the enemy ships need to be able to rotate freely to make the game ‘feel right’. If we had a ship pointing at 45 degrees but moving at 20 degrees it would spoil the gameplay.

Vector graphics provides a solution.

In vector graphics we use a series of lines to draw the image. We define a number of end points and then draw lines between them. To rotate an image we just rotate the end points around the centre of the element and then redraw the lines using the rotated points.

ship on axesship on axes rotated

But we then need a way of describing these lines to the software. We also want to be able to use colours and have separate line sections rather than just one continuous line per game element.

Defining and Drawing Vector Shapes

To define our shape we can create a simple, sort of, language that describes our lines.  Each point in the image can be encoded with a set of data. For each point we need an x and y coordinate, a colour and a flag to say if this is a new line or if it should be drawn from the previous point.

We can then create a list of these points that describe the lines needed to draw the image. For example, to draw a red triangle inside a blue square we could code… (pseudo code)

Lua
1
2
3
4
5
6
7
8
9
10
11
shape = array [ // use array to hold a list of points
[x=10, y=10, colour=blue, newline=true], // sets first point in square
[x=-10, y=10, colour=blue, newline=false], // defines second corner but draws a line from the first point
[x=-10, y=-10, colour=blue, newline=false], // second side
[x=10, y=-10, colour=blue, newline=false], // third side
[x=10, y=10, colour=blue, newline=false], // completes the blue square
[x=0, y=5, colour=red, newline=true], // first point in the triangle, but starts a new line
[x=-5, y=-5, colour=red, newline=false], // first edge of red triangle
[x=5, y=-5, colour=red, newline=false], // second edge
[x=0, y=5, colour=red, newline=false] // last edge of triangle
]

And this would describe the following image.

square triangle

Once we’ve encoded the point and line data our rendering code simply needs to step through each element in our shape array (we use tables in Lua) and either draw lines from the previous point or not.

Placing a Shape On the Screen

If you notice in the above example all my shapes were centred round the origin. This makes it very easy to place vector objects on screen. The local origin for the shape definition creates a centre point or insertion point. We can then place this shape anywhere on the screen by simply adding the coordinates of where we want it to appear to the coordinates of each point.

Lua
1
Screen Position = Point Position + Position on Screen

Rotating a Shape

To rotate a shape by a defined angle we simply need to rotate each point in the object around its centre point. We can then use these rotated points to draw the image as before.

To rotate a point in 2D space we need a bit of mathematics. We need to define the point around which we’re going to rotate and the angle we want to rotate by.

rotate point

The coordinates of the new point are then given by :-

x2 = x1 cos Ɵ – y1 sin Ɵ

y2 = y1 cos Ɵ + x1 sin Ɵ

(for a proof of this formula please go to https://matthew-brett.github.io/teaching/rotation_2d.html)

For our vector shapes we defined the coordinates of the points around a local origin which becomes the centre of rotation. To place a rotated version of the shape on the screen we first rotate the points around the shape’s local origin and then move the shape to the required position on screen.

Creating a General Purpose Rotate Function

In our game we’re going to have to rotate points around different origins. For example the background stars will need to be rotated around the player ship position as we animate the ship turning. So we need a general purpose rotation function that will cope with this, as well as our vector shape requirement above.

To do this we need to know the coordinates of the centre of rotation. We then need to recalculate the coordinates of our points relative to this new origin. We can then apply our rotation equations which give us the rotated point relative to the new origin. And finally we can move our rotated point back into position relative to the normal origin.

Lua
1
2
3
translated coordinates = point coordinates - centre of rotation coordinates
rotated point = rotatePoint (translated coordinates)
final point = rotated point + centre of rotation coordinates

Summary

Using vector graphics techniques lets us create much smoother rotations of the game elements. We loose a bit on the detail and complexity of the images compared to bitmap graphics, but we gain the ability to have our ships travel at any angle we want.

Download the cartridge for this tutorial

Code

Lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
-- title: Space Commander
-- author: Bob Grant
-- desc:
-- script: lua
 
ship = {
position = {x=120, y=70},
velocity = {magnitude=0, angle=0},
scale = 1
}
 
function TIC()
 
cls()
 
if btn(0) then
-- bigger
ship.scale = ship.scale + 0.1
elseif btn(1) then
-- smaller
ship.scale = ship.scale - 0.1
end
 
if btn(2) then
-- left
ship.velocity.angle = ship.velocity.angle + 0.05
elseif btn(3) then
-- right
ship.velocity.angle = ship.velocity.angle - 0.05
end
 
ship.velocity.angle = wrap_angle(ship.velocity.angle)
 
draw_object(1, ship.position, ship.velocity.angle, ship.scale)
 
end -- TIC
 
function draw_object(object_type, origin, angle, scale)
 
local object_shape = get_object_shape(object_type)
local point={}
local last_point={}
local rotated_point
 
for index,point in ipairs(object_shape) do
-- rotate object point around {0,0}
rotated_point = rotate_point({x=0, y=0}, point, angle)
point.x = rotated_point.x
point.y = rotated_point.y
-- scale
point.x = point.x * scale
point.y = point.y * scale
-- move object point to correct screen position
point.x = point.x + origin.x
point.y = -point.y + origin.y
if (point.newline) then
last_point = point
else
line(last_point.x, last_point.y, point.x, point.y, point.colour)
last_point=point
end
end -- for
end -- draw_object
 
function get_object_shape(object_type)
local object_shape = {}
-- level 1 ship
object_shape = {}
table.insert(object_shape, {x=-5, y=-5,newline=true, colour=5})
table.insert(object_shape, {x=0, y=-4,newline=false, colour=5})
table.insert(object_shape, {x=5, y=0, newline=false, colour=5})
table.insert(object_shape, {x=0, y=4, newline=false, colour=5})
table.insert(object_shape, {x=-5, y=5, newline=false, colour=5})
table.insert(object_shape, {x=-4, y=3, newline=false, colour=5})
table.insert(object_shape, {x=-3, y=0, newline=false, colour=5})
table.insert(object_shape, {x=-4, y=-3, newline=false, colour=5})
table.insert(object_shape, {x=-5, y=-5, newline=false, colour=5})
table.insert(object_shape, {x=4, y=0, newline=true, colour=14})
table.insert(object_shape, {x=2, y=1, newline=false, colour=14})
table.insert(object_shape, {x=0, y=0, newline=false, colour=14})
table.insert(object_shape, {x=2, y=-1, newline=false, colour=14})
table.insert(object_shape, {x=4, y=0, newline=false, colour=14})
table.insert(object_shape, {x=0, y=0, newline=false, colour=14})
 
return object_shape
end -- get_object_shape
 
function rotate_point(origin, point, angle)
local transposed_point = {}
transposed_point.x = point.x - origin.x
transposed_point.y = point.y - origin.y
local rotated_point = {}
rotated_point.x =
(transposed_point.x * math.cos(angle))
- (transposed_point.y * math.sin(angle))
rotated_point.y =
(transposed_point.y * math.cos(angle))
+ (transposed_point.x * math.sin(angle))
 
-- update provided point
rotated_point.x = rotated_point.x + origin.x
rotated_point.y = rotated_point.y + origin.y
return rotated_point
end -- rotate_point
 
function wrap_angle(angle)
local new_angle = angle
if (new_angle > (2*math.pi)) then
new_angle = new_angle - (2*math.pi)
elseif(new_angle < 0) then
new_angle = new_angle + (2*math.pi)
end --if
return new_angle
end -- wrap_angle

Share
2
Bob
Bob

Related posts

Modelling the World
18th July 2021

Modelling the Game World in Space Commander – Learn to Code Your Own Games


Read more
Space Commander Overview
9th May 2021

Space Commander Overview – Learn to Code Your Own Games


Read more
Learn to code Snake
8th October 2020

Learn to Code Snake in TIC80 – Code Walkthrough


Read more

Comments are closed.

  • Privacy Policy
  • Contact Me
© 2020 Bytes N Bits