Learning drawing target systems using Head Shooter in Dart
Head Shooter is a timing based arcade game where players must hit rotating targets at the correct moment.
The entire experience depends on visual clarity, movement accuracy, and responsive target rendering.
One of the most important systems in this game is drawing the target correctly on the screen.
A good target system helps players understand movement, predict timing, and react naturally during gameplay.
In this tutorial you will learn how to create rotating targets using Dart.
You will understand positioning, circular movement, target rendering, collision zones, and visual alignment.
This guide focuses on beginner friendly logic while still teaching real arcade game development concepts.
Everything here can later be connected to Flutter or Flame for complete gameplay.
Understanding the role of targets in Head Shooter
In Head Shooter the targets rotate around a fixed center point.
The player waits for perfect alignment before tapping the screen.
This means the target system must be extremely stable.
Even small positioning mistakes can make the game feel unfair.
A target system usually needs four important parts.
Position tracking
Circular movement
Rendering logic
Collision detection
Once these systems work together correctly the game becomes smooth and responsive.
Creating a target model
Before drawing anything you need a clean structure to represent targets.
Every target should know its position, size, angle, and active state.
class TargetData {
double x;
double y;
double radius;
double angle;
bool active;
TargetData({
required this.x,
required this.y,
required this.radius,
required this.angle,
this.active = true,
});
}
This structure creates reusable target objects for the game.
The angle variable is especially important because circular movement depends on rotation mathematics.
Setting the center position
Head Shooter revolves around a center point.
Every target rotates around this position.
The center normally stays fixed during gameplay.
double centerX = 250;
double centerY = 350;
These coordinates represent the middle of the screen.
All target calculations will use this center position as the anchor point.
Understanding circular movement
Circular movement may look difficult at first, but the logic is actually very simple.
Targets move using sine and cosine calculations.
The angle changes continuously while the radius distance stays fixed.
import 'dart:math';
The math library gives access to sine and cosine functions.
These functions are essential for smooth rotational movement.
void updateTargetPosition(
TargetData target,
double orbitRadius,
) {
target.x =
centerX + cos(target.angle) * orbitRadius;
target.y =
centerY + sin(target.angle) * orbitRadius;
}
This logic places the target around the center in a circular path.
As the angle changes the target moves smoothly around the screen.
Creating multiple rotating targets
Head Shooter uses several rotating heads instead of a single object.
Multiple targets create pressure and force players to focus carefully.
List<TargetData> targets = [];
Now you can generate targets dynamically.
void createTargets() {
for (int i = 0; i < 6; i++) {
double angle = (pi * 2 / 6) * i;
targets.add(
TargetData(
x: 0,
y: 0,
radius: 25,
angle: angle,
),
);
}
}
This creates six targets equally spaced around a circular path.
Even spacing makes movement easier to understand visually.
Updating target rotation
Static targets would make the game boring.
Continuous rotation creates challenge and excitement.
double rotationSpeed = 0.03;
This value controls how quickly the targets rotate.
Small values produce smooth motion while larger values create faster gameplay.
void rotateTargets() {
for (final target in targets) {
target.angle += rotationSpeed;
updateTargetPosition(
target,
140,
);
}
}
Every frame the angle increases slightly.
Since the angle changes continuously the targets orbit around the center.
Learning how drawing works
Drawing targets means visually rendering them on the screen.
In Flutter and Flame this normally happens inside a rendering function.
The game engine redraws objects repeatedly to create animation.
void drawTarget(TargetData target) {
print(
'Draw target at ${target.x}, ${target.y}',
);
}
This simple example represents a rendering call.
Real projects would use Canvas or Sprite rendering instead of print statements.
Creating smooth animation loops
Arcade games rely on update loops.
The update loop repeatedly refreshes movement and rendering.
void gameLoop() {
rotateTargets();
for (final target in targets) {
drawTarget(target);
}
}
This structure updates all movement before rendering targets.
Separating logic and rendering keeps the project organized.
Improving visual clarity
Good target design helps players react naturally.
If targets blend into the background the game becomes frustrating.
Proper spacing and sizing improve gameplay readability.
double targetSize = 30;
Larger targets feel easier while smaller targets increase difficulty.
Visual balance is important because difficulty should feel fair instead of random.
Creating hit detection
Drawing alone is not enough.
The game also needs a way to detect successful hits.
Collision detection checks if the target aligns with the center area.
bool isTargetAligned(TargetData target) {
double dx = target.x - centerX;
double dy = target.y - centerY;
double distance =
sqrt(dx * dx + dy * dy);
return distance < 20;
}
This calculates the distance between the target and the center point.
If the target moves close enough the hit becomes valid.
Handling player taps
The player taps when they believe a target aligns correctly.
The game must instantly check whether the tap was successful.
int score = 0;
int lives = 3;
void handleTap() {
bool success = false;
for (final target in targets) {
if (isTargetAligned(target)) {
success = true;
score++;
break;
}
}
if (!success) {
lives--;
}
}
Correct taps increase the score while failed attempts reduce lives.
This creates tension and encourages accurate timing.
Making targets disappear after hits
A successful hit should feel rewarding.
One good way to create feedback is hiding targets after impact.
void deactivateTarget(TargetData target) {
target.active = false;
}
Inactive targets stop appearing during rendering.
This gives players visual confirmation that the hit succeeded.
void drawAllTargets() {
for (final target in targets) {
if (target.active) {
drawTarget(target);
}
}
}
The rendering system ignores disabled targets.
This keeps the screen clean and improves gameplay feedback.
Adding difficulty progression
Head Shooter becomes more exciting when movement speeds increase over time.
Gradual difficulty scaling keeps players engaged.
void increaseDifficulty() {
rotationSpeed += 0.005;
}
Small speed increases feel natural and fair.
Players gradually adapt while still feeling challenged.
Creating responsive timing
Timing games require accurate input handling.
Delayed reactions make the experience frustrating.
A responsive game loop helps maintain fairness.
double deltaTime = 0.016;
Delta time represents frame timing.
Using frame independent movement keeps gameplay smooth across different devices.
void rotateTargetsSmoothly() {
for (final target in targets) {
target.angle +=
rotationSpeed * deltaTime * 60;
updateTargetPosition(
target,
140,
);
}
}
This keeps movement stable even when frame rates change.
Understanding target spacing
Proper spacing improves gameplay readability.
Targets that overlap too closely create confusion.
Wide spacing gives players enough time to react.
Good arcade design balances pressure and readability carefully.
Creating visual rhythm
Head Shooter works because rotation creates rhythm.
Players slowly memorize movement timing and improve through repetition.
Smooth target drawing strengthens this rhythm.
Sudden jumps or inconsistent movement break immersion immediately.
Adding visual effects
Visual feedback makes successful hits more satisfying.
You can add scaling effects, flashes, or particles during impacts.
void playHitEffect() {
print('Hit effect triggered');
}
Small feedback effects dramatically improve player satisfaction.
Even simple arcade games feel more polished with responsive visuals.
Learning why target systems matter
The target system controls the entire gameplay experience.
If movement feels unstable the game becomes frustrating.
If rendering feels smooth the game becomes addictive.
Great arcade games often depend more on movement quality than graphics quality.
Players naturally enjoy games that feel responsive and readable.
Expanding the system further
Once the basic target system works you can add advanced features.
You can create reverse rotations for harder gameplay.
You can introduce speed bursts during later rounds.
You can add moving center points for advanced difficulty.
You can also create target patterns with different orbit sizes.
Small gameplay changes can completely transform the player experience.
Optimizing performance
Smooth rendering is extremely important in timing games.
Lag or dropped frames make accurate taps difficult.
Efficient rendering and organized update logic improve stability.
Always avoid unnecessary calculations during every frame.
Clean architecture becomes increasingly important as projects grow larger.
Final thoughts
Learning how to draw rotating targets in Head Shooter teaches important arcade game development concepts.
You learn circular movement, rendering systems, timing logic, collision handling, and responsive input management.
Even though the gameplay looks simple, the systems behind smooth motion require careful structure and planning.
Once you understand target systems you can apply the same knowledge to many other game genres including rhythm games, shooter games, reaction games, and puzzle mechanics.
Head Shooter demonstrates how strong movement design and accurate timing systems can transform a simple concept into a highly engaging arcade experience.