Make a Neon Vector Shooter for iOS: The Warping Grid
2016-01-07 10:09
796 查看
http://gamedevelopment.tutsplus.com/tutorials/make-a-neon-vector-shooter-for-ios-the-warping-grid--gamedev-14637
This post is part of a series called Cross-Platform
Vector Shooter: iOS.
Make
a Neon Vector Shooter for iOS: Particle Effects
Make
a Neon Vector Shooter for iOS: First Steps
In this series of tutorials, I'll show you how to make a Geometry Wars-inspired twin-stick shooter, with neon graphics, crazy particle effects, and awesome music, for iOS using C++ and OpenGL ES
2.0. In this final part, we'll add the background grid which warps based on the in-game action.
In the series
so far we've created the gameplay, virtual gamepad, and particle effects. In this final part, we will create a dynamic, warping background grid.
Warning: Loud!
As mentioned in the previous
part, you'll notice a dramatic drop in framerate if you're still running the code in debug mode. See that tutorial for details on how to switch to release mode for full compiler optimization (and a faster build).
One of the coolest effects in Geometry Wars is the warping background grid. We'll examine how to create a similar effect in Shape Blaster. The grid will react to bullets, black holes, and the player respawning.
It's not difficult to make and it looks awesome.
We'll make the grid using a spring simulation. At each intersection of the grid, we'll put a small weight and attach a spring on each side. These springs will only pull and never push, much like a rubber band. To
keep the grid in position, the masses at the border of the grid will be anchored in place. Below is a diagram of the layout.
We'll create a class called
create this effect. However, before we work on the grid itself, we need to make two helper classes:
The
represents the masses to which we will attach the springs. Springs never connect directly to other springs; instead, they apply a force to the masses they connect, which in turn may stretch other springs.
There are a few interesting points about this class. First, notice that it stores the inverseof the mass,
Second, the class also contains a damping variable. This is used roughly as friction or air resistance; it gradually slows the mass down. This helps make the grid eventually come to rest and
also increases the stability of the spring simulation.
The
the work of moving the point mass each frame. It begins by doing symplectic Euler integration,
which just means we add the acceleration to the velocity and then add the updated velocity to the position. This differs from standard Euler integration in which we would update the velocity after updating the position.
Tip: Symplectic Euler is better for spring simulations because it conserves energy. If you use regular Euler integration and create springs without damping, they will tend to stretch further
and further each bounce as they gain energy, eventually breaking your simulation.
After updating the velocity and position, we check whether the velocity is very small, and if so we set it to zero. This can be important for performance due to the nature ofdenormalized
floating-point numbers.
(When floating-point numbers get very small, they use a special representation called adenormalized number. This has the advantage of allowing floats to represent smaller numbers, but it comes
at a price. Most chipsets can't use their standard arithmetic operations on denormalized numbers and instead must emulate them using a series of steps. This can be tens to hundreds of times slower than performing operations on normalized floating-point numbers.
Since we multiply our velocity by our damping factor each frame, it will eventually become very small. We don't actually care about such tiny velocities, so we simply set it to zero.)
The
is used to temporarily increase the amount of damping. We will use this later for certain effects.
A spring connects two point masses, and, if stretched past its natural length, applies a force pulling the masses together. Springs follow a modified version of Hooke's
Lawwith damping:
f=−kx−bvf=−kx−bv
ff is
the force produced by the spring.
kk is
the spring constant, or the stiffness of the spring.
xx is
the distance the spring is stretched beyond its natural length.
bb is
the damping factor.
vv is
the velocity.
The code for the
follows:
When we create a spring, we set the natural length of the spring to be just slightly less than the distance between the two end points. This keeps the grid taut even when at rest, and improves the appearance somewhat.
The
whether the spring is stretched beyond its natural length. If it is not stretched, nothing happens. If it is, we use the modified Hooke's Law to find the force from the spring and apply it to the two connected masses.
Now that we have the necessary nested classes, we're ready to create the grid. We start by creating
at each intersection on the grid. We also create some immovable anchor
to hold the grid in place. We then link up the masses with springs.
The first
masses and immovable masses at each intersection of the grid. We won't actually use all of the immovable masses, and the unused masses will simply be garbage collected some time after the constructor ends. We could optimize by avoiding creating unnecessary
objects, but since the grid is usually only created once, it won't make much difference.
In addition to using anchor point masses around the border of the grid, we will also use some anchor masses inside the grid. These will be used to very gently help pull the grid back to its original position after
being deformed.
Since the anchor points never move, they don't need to be updated each frame; we can simply hook them up to the springs and forget about them. Therefore, we don't have a member variable in the
for these masses.
There are a number of values you can tweak in the creation of the grid. The most important ones are the stiffness and damping of the springs. (The stiffness and damping of the border anchors and interior anchors
are set independently of the main springs.) Higher stiffness values will make the springs oscillate more quickly, and higher damping values will cause the springs to slow down sooner.
In order for the grid to move, we must update it each frame. This is very simple as we already did all the hard work in the
Now we will add some methods that manipulate the grid. You can add methods for any kind of manipulation you can think of. We will implement three types of manipulations here: pushing part of the grid in a given
direction, pushing the grid outwards from some point, and pulling the grid in towards some point. All three will affect the grid within a given radius from some target point. Below are some images of these manipulations in action:
Bullets repelling the grid outwards.
Sucking the grid inwards.
Wave created by pushing the grid along the z-axis.
We will use all three of these methods in Shape Blaster for different effects.
We'll draw the grid by drawing line segments between each neighbouring pair of points. First, we'll add an extension method taking a
as a parameter that allows us to draw line segments by taking a texture of a single pixel and stretching it into a line.
Open the
for the pixel:
You can set the pixel texture the same way we set up the other images, so we'll add
a 1x1px image with the sole pixel set to white) to the project and load it into the
Now let's add the following method to the
This method stretches, rotates, and tints the pixel texture to produce the line we desire.
Next, we need a method to project the 3D grid points onto our 2D screen. Normally this might be done using matrices, but here we'll transform the coordinates manually instead.
Add the following to the
This transform will give the grid a perspective view where far away points appear closer together on the screen. Now we can draw the grid by iterating through the rows and columns and drawing lines between them:
In the above code,
point on the grid,
the point directly above it. We draw every third line thicker both horizontally and vertically for visual effect.
We can optimize the grid by improving the visual quality for a given number of springs without significantly increasing the performance cost. We are going to do two such optimizations.
We will make the grid denser by adding line segments inside the existing grid cells. We do so by drawing lines from the midpoint of one side of the cell to the midpoint of the opposite side. The image below shows
the new interpolated lines in red.
Drawing the interpolated lines is straightforward. If you have two points,
their midpoint is
code inside the
The second improvement is to perform interpolation on our straight line segments to make them into smoother curves. In the
original XNA version of this game, the code relied on XNA's
which performs Catmull-Rom interpolation. You pass the method four sequential
points on a curved line, and it will return points along a smooth curve between the second and third points you provided.
Since this algorithm doesn't exist in either C or C++'s standard library, we'll have to implement it ourselves. Luckily there is a reference
implementation available to use. I've provided a
based on this reference implementation:
The fifth argument to
a weighting factor that determines which point on the interpolated curve it returns. A weighting factor of
respectively return the second or third point you provided, and a weighting factor of
return the point on the interpolated curve halfway between the two points. By gradually moving the weighting factor from zero to one and drawing lines between the returned points, we can produce a perfectly smooth curve. However, to keep the performance cost
low, we will only take a single interpolated point into consideration, at a weighting factor of
We then replace the original straight line in the grid with two lines that meet at the interpolated point.
The diagram below shows the effect of this interpolation:
Since the line segments in the grid are already small, using more than one interpolated point generally does not make a noticeable difference.
Often, the lines in our grid will be very straight and won't require any smoothing. We can check for this and avoid having to draw two lines instead of one: we check if the distance between the interpolated point
and the midpoint of the straight line is greater than one pixel; if it is, we assume the line is curved and we draw two line segments.
The modification to our
for adding Catmull-Rom interpolation for the horizontal lines is shown below.
The image below shows the effects of the smoothing. A green dot is drawn at each interpolated point to better illustrate where the lines are smoothed.
Advertisement
Now it's time to use the grid in our game. We start by declaring a public, static
in
We'll create a grid with roughly 600 points like so.
Though the original XNA version of the game uses 1,600 points (rather than 600), this becomes way too much to handle just even for the powerful hardware in the iPhone. Fortunately, the original code left the amount
of points customizable, and at about 600 grid points, we can still render them and still maintain an optimal frame rate.
Then we call
the
This will allow us to see the grid when we run the game. However, we still need to make various game objects interact with the grid.
Bullets will repel the grid. We already made a method to do this called
Add the following line to the
This will make bullets repel the grid proportionally to their speed. That was pretty easy.
Now let's work on black holes. Add this line to
This makes the black hole suck in the grid with a varying amount of force. We've reused the
which will cause the force on the grid to pulsate in sync with the angle it sprays particles (although at half the frequency due to the division by two). The force passed in will vary sinusoidally between
Finally, we will create a shockwave in the grid when the player's ship respawns after death. We will do so by pulling the grid along the z-axis and then allowing the force to propagate and bounce through the springs.
Again, this only requires a small modification to
We have the basic gameplay and effects implemented. It's up to you to turn it into a complete and polished game with your own flavour. Try adding some interesting new mechanics, some cool new effects, or a unique
story. In case you aren't sure where to start, here are a few suggestions:
Tweak and customize the touch controls to your personal preferences.
Add support for hardware game controllers in iOS 7 via the GameController
Framework.
Profile and optimize all the slow parts of the code using Xcode's built-in Instruments tool.
Attempt to add post-processing effects like the Bloom
Shader present in the original XNA version.
Create new enemy types such as snakes or exploding enemies.
Create new weapon types such as seeking missiles or a lightning
gun.
Add a title screen and main menu, and a high score table.
Add some powerups such as a shield or bombs.
Experiment to find powerups that are fun and help your game stand out.
Create multiple levels. Harder levels can introduce tougher enemies and more advanced weapons and powerups.
Add environmental hazards such as lasers.
The sky's the limit!
This post is part of a series called Cross-Platform
Vector Shooter: iOS.
Make
a Neon Vector Shooter for iOS: Particle Effects
Make
a Neon Vector Shooter for iOS: First Steps
In this series of tutorials, I'll show you how to make a Geometry Wars-inspired twin-stick shooter, with neon graphics, crazy particle effects, and awesome music, for iOS using C++ and OpenGL ES
2.0. In this final part, we'll add the background grid which warps based on the in-game action.
Overview
In the seriesso far we've created the gameplay, virtual gamepad, and particle effects. In this final part, we will create a dynamic, warping background grid.
Warning: Loud!
As mentioned in the previous
part, you'll notice a dramatic drop in framerate if you're still running the code in debug mode. See that tutorial for details on how to switch to release mode for full compiler optimization (and a faster build).
The Warping Grid
One of the coolest effects in Geometry Wars is the warping background grid. We'll examine how to create a similar effect in Shape Blaster. The grid will react to bullets, black holes, and the player respawning.It's not difficult to make and it looks awesome.
We'll make the grid using a spring simulation. At each intersection of the grid, we'll put a small weight and attach a spring on each side. These springs will only pull and never push, much like a rubber band. To
keep the grid in position, the masses at the border of the grid will be anchored in place. Below is a diagram of the layout.
We'll create a class called
Gridto
create this effect. However, before we work on the grid itself, we need to make two helper classes:
Springand
PointMass.
The PointMass Class
The PointMassclass
represents the masses to which we will attach the springs. Springs never connect directly to other springs; instead, they apply a force to the masses they connect, which in turn may stretch other springs.
1 / mass. This is often a good idea in physics simulations because physics equations tend to use the inverse of the mass more often, and because it gives us an easy way to represent infinitely heavy, immovable objects by setting the inverse mass to zero.
Second, the class also contains a damping variable. This is used roughly as friction or air resistance; it gradually slows the mass down. This helps make the grid eventually come to rest and
also increases the stability of the spring simulation.
The
PointMass::update()method does
the work of moving the point mass each frame. It begins by doing symplectic Euler integration,
which just means we add the acceleration to the velocity and then add the updated velocity to the position. This differs from standard Euler integration in which we would update the velocity after updating the position.
Tip: Symplectic Euler is better for spring simulations because it conserves energy. If you use regular Euler integration and create springs without damping, they will tend to stretch further
and further each bounce as they gain energy, eventually breaking your simulation.
After updating the velocity and position, we check whether the velocity is very small, and if so we set it to zero. This can be important for performance due to the nature ofdenormalized
floating-point numbers.
(When floating-point numbers get very small, they use a special representation called adenormalized number. This has the advantage of allowing floats to represent smaller numbers, but it comes
at a price. Most chipsets can't use their standard arithmetic operations on denormalized numbers and instead must emulate them using a series of steps. This can be tens to hundreds of times slower than performing operations on normalized floating-point numbers.
Since we multiply our velocity by our damping factor each frame, it will eventually become very small. We don't actually care about such tiny velocities, so we simply set it to zero.)
The
PointMass::increaseDamping()method
is used to temporarily increase the amount of damping. We will use this later for certain effects.
The Spring Class
A spring connects two point masses, and, if stretched past its natural length, applies a force pulling the masses together. Springs follow a modified version of Hooke'sLawwith damping:
f=−kx−bvf=−kx−bv
ff is
the force produced by the spring.
kk is
the spring constant, or the stiffness of the spring.
xx is
the distance the spring is stretched beyond its natural length.
bb is
the damping factor.
vv is
the velocity.
The code for the
Springclass is as
follows:
The
Spring::update()method first checks
whether the spring is stretched beyond its natural length. If it is not stretched, nothing happens. If it is, we use the modified Hooke's Law to find the force from the spring and apply it to the two connected masses.
Creating the Grid
Now that we have the necessary nested classes, we're ready to create the grid. We start by creating PointMassobjects
at each intersection on the grid. We also create some immovable anchor
PointMassobjects
to hold the grid in place. We then link up the masses with springs.
forloop creates both regular
masses and immovable masses at each intersection of the grid. We won't actually use all of the immovable masses, and the unused masses will simply be garbage collected some time after the constructor ends. We could optimize by avoiding creating unnecessary
objects, but since the grid is usually only created once, it won't make much difference.
In addition to using anchor point masses around the border of the grid, we will also use some anchor masses inside the grid. These will be used to very gently help pull the grid back to its original position after
being deformed.
Since the anchor points never move, they don't need to be updated each frame; we can simply hook them up to the springs and forget about them. Therefore, we don't have a member variable in the
Gridclass
for these masses.
There are a number of values you can tweak in the creation of the grid. The most important ones are the stiffness and damping of the springs. (The stiffness and damping of the border anchors and interior anchors
are set independently of the main springs.) Higher stiffness values will make the springs oscillate more quickly, and higher damping values will cause the springs to slow down sooner.
Manipulating the Grid
In order for the grid to move, we must update it each frame. This is very simple as we already did all the hard work in the PointMassand
Springclasses:
direction, pushing the grid outwards from some point, and pulling the grid in towards some point. All three will affect the grid within a given radius from some target point. Below are some images of these manipulations in action:
Bullets repelling the grid outwards.
Sucking the grid inwards.
Wave created by pushing the grid along the z-axis.
Rendering the Grid
We'll draw the grid by drawing line segments between each neighbouring pair of points. First, we'll add an extension method taking a tSpriteBatchpointer
as a parameter that allows us to draw line segments by taking a texture of a single pixel and stretching it into a line.
Open the
Artclass and declare a texture
for the pixel:
pixel.png(
a 1x1px image with the sole pixel set to white) to the project and load it into the
tTexture:
Extensionsclass:
Next, we need a method to project the 3D grid points onto our 2D screen. Normally this might be done using matrices, but here we'll transform the coordinates manually instead.
Add the following to the
Gridclass:
pis our current
point on the grid,
leftis the point directly to its left and
upis
the point directly above it. We draw every third line thicker both horizontally and vertically for visual effect.
Interpolation
We can optimize the grid by improving the visual quality for a given number of springs without significantly increasing the performance cost. We are going to do two such optimizations.We will make the grid denser by adding line segments inside the existing grid cells. We do so by drawing lines from the midpoint of one side of the cell to the midpoint of the opposite side. The image below shows
the new interpolated lines in red.
Drawing the interpolated lines is straightforward. If you have two points,
aand
b,
their midpoint is
(a + b) / 2. So, to draw the interpolated lines, we add the following
code inside the
forloops of our
Grid::draw()method:
original XNA version of this game, the code relied on XNA's
Vector2.CatmullRom()method
which performs Catmull-Rom interpolation. You pass the method four sequential
points on a curved line, and it will return points along a smooth curve between the second and third points you provided.
Since this algorithm doesn't exist in either C or C++'s standard library, we'll have to implement it ourselves. Luckily there is a reference
implementation available to use. I've provided a
MathUtil::catmullRom()method
based on this reference implementation:
MathUtil::catmullRom()is
a weighting factor that determines which point on the interpolated curve it returns. A weighting factor of
0or
1will
respectively return the second or third point you provided, and a weighting factor of
0.5will
return the point on the interpolated curve halfway between the two points. By gradually moving the weighting factor from zero to one and drawing lines between the returned points, we can produce a perfectly smooth curve. However, to keep the performance cost
low, we will only take a single interpolated point into consideration, at a weighting factor of
0.5.
We then replace the original straight line in the grid with two lines that meet at the interpolated point.
The diagram below shows the effect of this interpolation:
Since the line segments in the grid are already small, using more than one interpolated point generally does not make a noticeable difference.
Often, the lines in our grid will be very straight and won't require any smoothing. We can check for this and avoid having to draw two lines instead of one: we check if the distance between the interpolated point
and the midpoint of the straight line is greater than one pixel; if it is, we assume the line is curved and we draw two line segments.
The modification to our
Grid::draw()method
for adding Catmull-Rom interpolation for the horizontal lines is shown below.
Advertisement
Using the Grid in Shape Blaster
Now it's time to use the grid in our game. We start by declaring a public, static Gridvariable
in
GameRootand creating the grid in the
GameRoot::onInitView.
We'll create a grid with roughly 600 points like so.
of points customizable, and at about 600 grid points, we can still render them and still maintain an optimal frame rate.
Then we call
Grid::update()and
Grid::draw()from
the
GameRoot::onRedrawView()method in
GameRoot.
This will allow us to see the grid when we run the game. However, we still need to make various game objects interact with the grid.
Bullets will repel the grid. We already made a method to do this called
Grid::applyExplosiveForce().
Add the following line to the
Bullet::update()method.
Now let's work on black holes. Add this line to
BlackHole::update():
mSprayAnglevariable,
which will cause the force on the grid to pulsate in sync with the angle it sprays particles (although at half the frequency due to the division by two). The force passed in will vary sinusoidally between
10and
30.
Finally, we will create a shockwave in the grid when the player's ship respawns after death. We will do so by pulling the grid along the z-axis and then allowing the force to propagate and bounce through the springs.
Again, this only requires a small modification to
PlayerShip::update().
What's Next?
We have the basic gameplay and effects implemented. It's up to you to turn it into a complete and polished game with your own flavour. Try adding some interesting new mechanics, some cool new effects, or a uniquestory. In case you aren't sure where to start, here are a few suggestions:
Tweak and customize the touch controls to your personal preferences.
Add support for hardware game controllers in iOS 7 via the GameController
Framework.
Profile and optimize all the slow parts of the code using Xcode's built-in Instruments tool.
Attempt to add post-processing effects like the Bloom
Shader present in the original XNA version.
Create new enemy types such as snakes or exploding enemies.
Create new weapon types such as seeking missiles or a lightning
gun.
Add a title screen and main menu, and a high score table.
Add some powerups such as a shield or bombs.
Experiment to find powerups that are fun and help your game stand out.
Create multiple levels. Harder levels can introduce tougher enemies and more advanced weapons and powerups.
Add environmental hazards such as lasers.
The sky's the limit!
相关文章推荐
- iOS 调用系统 电话
- iOS应用启动时不占满全屏,上下有黑边!
- iOS开发之利用AsyncSocket实现即时通信(一)
- ios第三方输入法崩溃问题
- iOS基础 归档和解归档
- iOS,frame和bounds的区别
- 地图定位 - 转换不同标准坐标系
- ios collectionView
- IOS学习资源汇总
- IOS中延时执行的几种方式的比较和汇总
- ios开发中类方法与self的注意点 与实例方法区别
- ios常用方法
- iOS第三方集成之jpush极光推送
- iOS多线程的实现方案
- iOS多线程之NSThread
- iOS多线程之互斥锁
- ios 推送证书制作成P12格式
- iOS 9音频应用播放音频之iOS 9音频播放进度
- iOS安全攻防(一):Hack必备的命令与工具
- iOS隐藏状态栏