目录
- [Merge Big Watermelon](#Merge Big Watermelon)
- [Space Shooter](#Space Shooter)
- [Flappy Bird](#Flappy Bird)
- Pong
- Asteroids
The games in this practice series are all intentionally small. The goal is to get comfortable with Unity’s APIs, sharpen my problem-solving mindset, and explore different ways of thinking. The top priority is to quickly build working prototypes or extract reusable code snippets. Things like overall architecture or long-term scalability aren’t the focus here.
Merge Big Watermelon
Tutorial Reference and Assets Source:Bilibili-YouChiHui
Source Code: on Gitee
Game Overview
Merge Big Watermelon is a casual browser game released by Weisun Games in early 2021. It quickly went viral thanks to its easy-to-learn mechanics, addictive gameplay, and strong social sharing appeal.
Core Gameplay:
- Players tap the screen to drop random fruits from the top—grapes, oranges, apples, and so on.
- When two identical fruits collide, they merge into a larger one: two grapes become a cherry, two cherries become an orange, and so on. The ultimate goal is to create the biggest fruit—the watermelon.
- The game blends mechanics from Tetris and 2048: fruits fall and stack under gravity, and the game ends if the pile crosses the top of the screen.
Game Breakdown
Content
-
Background image
-
Walls and floor
-
Fruits (active and standby)
-
Game-over line
-
UI (score display)
-
Sound effects (landing, merging)
Core Logic
- The standby fruit follows the mouse along the X-axis while the mouse is held down, and drops when released
- Fruits bounce and collide with each other when they land. If two identical fruits touch, they merge into a bigger one
- If any fruit crosses the game-over line, the game ends and the scene reloads
- The current score is displayed on screen.
Development Steps
- Set up the scene layout
- Add gravity, collision, and bounce physics to fruits and other objects
- Turn fruits into prefabs
- Make the standby fruit follow the mouse
- Drop the fruit when the mouse is released
- Handle fruit merging logic
- Detect when fruits hit or cross the game-over line
- Display the score in the UI
- Add sound effects
Problems & Solutions
1. No Clear Plan
When I got stuck, I used the classic “How do you put an elephant in a fridge?” mindset—break things down into the tiniest possible steps. For example, Step 4 in the development plan (“make the standby fruit follow the mouse”) can be broken down like this:
- Create an array of standby fruits (types 1–4)
- Define a spawn point for the standby fruit
- Randomly pick a fruit from the array to spawn
- On mouse down, get the mouse’s position and apply it to the fruit (only update the X-axis; keep Y and Z unchanged)
- Add boundary checks so the fruit doesn’t move past the left/right walls
2. Converting Screen Coordinates to World Coordinates
|
|
One thing to watch out for: since the main camera in Unity is positioned at Z = -10 in world space, you need to manually set the Z value to 0 when converting screen coordinates to world coordinates for 2D objects. Otherwise, the object might not be visible—it’ll be behind the camera.
In Unity, the origin of screen coordinates is at the bottom-left corner
3. Delayed Execution
|
|
4. Assigning the Singleton Instance
|
|
5. Fruit Merging Logic
Each fruit has a unique ID, so it can be used to identify individual objects. To avoid both fruits triggering a merge at the same time, only the one with the higher ID is allowed to spawn the next-level fruit.
6. Custom Font Scaling Issue
Custom fonts didn’t scale properly using the usual font size settings, it can be fixed by scaling the entire object instead.
7. Sound Effect Spam on Landing
To avoid messy audio from tiny movements, a velocity threshold can be set to help with this—only play the landing sound if the fruit’s velocity is above a certain value.
Reflection
This was my first hands-on Unity project. I tried to write all the code myself, only checking tutorials when I got stuck—then I’d go back and implement the logic on my own. Whenever I hit an unfamiliar API, I’d dig into my old notes or check the official docs. That really helped the knowledge stick.
The demo covers the core gameplay, though there are still a few bugs. For example, sometimes the game doesn’t restart even when fruits stack past the game-over line. Also, after merging into the final watermelon, I still need to add a victory screen and scene. I’ll revisit to polish those once I’ve finished a few more practice projects.
Code Reuse
1. Use Singleton Pattern for All “Manager” Classes
|
|
2. Make the game object follow the mouse
|
|
Space Shooter
Tutorial Reference and Assets Source:Bilibili-QiQiKer-Plane
Source Code: on Gitee
Game Overview
Space Shooter is an official Unity 3D beginner tutorial project designed to help newcomers quickly grasp the basics of Unity development. It covers core concepts such as scene setup, player control, enemy spawning, collision detection, UI display, and audio integration. This version builds on the original tutorial by adding upgraded features—like enemies that can fire bullets.
Core Gameplay:
- Genre: 2D space shooter (with a 3D top-down view)
- Objective: Pilot your spaceship and survive as long as possible while destroying waves of enemies and asteroids.
- Controls:
- Movement: Use WASD or arrow keys to move freely within the screen
- Shooting: Left mouse button fires bullets
- Enemy Behavior:
- Asteroids and enemy ships spawn randomly from the top of the screen, falling at different speeds
- Enemies move side-to-side and shoot bullets at the player
- The player must dodge or destroy them to avoid collisions or being hit by enemy fire
- Game Over: The game ends if the spaceship is hit by an asteroid or a bullet
- Scoring System: Each destroyed enemy adds points, with the UI showing the score in real time
Game Breakdown
Content
- Scene setup
-
Background slowly scrolling downward
-
Animations: enemy explosions, asteroid explosions, ship tilting when moving left/right, asteroid rotation
-
UI: score display
-
Audio: background music, shooting, hit effects
Core Logic
- Player can move in all four directions and shoot bullets
- Asteroids spawn randomly at the top of the screen and fall at a constant speed
- Enemies spawn randomly at the top, move down faster, dodge left/right, and fire bullets
- Current score is displayed in the UI
- If the player is hit by enemy bullets or collides with asteroids/enemies, the ship is destroyed and the scene reloads
Development Steps
-
Background Setup
- Looping background movement
- Add starfield particle effects
- Add background music
-
Player Implementation
- Player movement controls
- Tilting effect when moving left/right (greater speed = larger tilt angle)
- Shooting mechanics
- Player bullets with sound effects
-
Enemies & Asteroids
- Random spawning of asteroids and enemies
- Random asteroid rotation effect
- Enemy shooting logic
- Enemy side-to-side dodging movement
-
Effects
- Attack and collision effects
-
UI
- Build the UI layout
- Implement score display logic
Problems & Solutions
1. Moving the Background
Should the background move, or should the camera move? In most cases, it’s easier to move the background. Since the scene contains asteroids, enemies, the player, and other objects, keeping the camera still simplifies everything.
One thing to keep in mind:
- When doing any kind of back‑and‑forth or looping movement that needs to stay in sync with absolute time, use
Time.time. - For regular movement, timers, or accumulative values that must stay frame‑rate independent, use
Time.deltaTime.
|
|
2. Rigidbody calculations should be placed in
Since the time between frames in Update() can vary depending on device performance and workload, running physics or Rigidbody operations there may cause jitter or inconsistent behavior. Using FixedUpdate() ensures physics calculations run at a stable, fixed timestep.
|
|
3. A Simple Way to Handle Boundary Checking
It’s often easier to visually adjust boundary positions in the Inspector rather than controlling everything purely through code. The idea is to create a struct inside the class, mark it as serializable, and then reference it in your logic. (Don’t forget to instantiate the Border class before using it.)
|
|
4. Apply tilt to the object through Rigidbody-based rotation
|
|
5. Component-Based (“Feature Hook”) Architecture
Break reusable functionality into small, focused components (e.g., MoveController), then let higher-level behavior scripts (e.g., PlayerController / EnemyController) compose and use them.
Pros: High reusability, clear responsibilities, easy to test, easy to maintain; follows composition over inheritance.
Cons: Some objects may end up with multiple small scripts attached, but this is normal for single‑responsibility design and has minimal performance impact.
Best Practices: Keep each component focused on a single responsibility, use interfaces/events for decoupling, use RequireComponent, store shared data in ScriptableObjects or config classes, and reuse behavior through Prefabs.
The “best practices” part is still a bit abstract for me. I’ve seen it once in the “2048” project, and I’ll need more hands‑on experience to fully internalize how to apply it.
6. When to Call GetComponent
A commonly recommended pattern is:
- Use
Awake()when retrieving and caching components on the same GameObject. - Use
Start()when the component depends on other objects that also initialize inAwake()
|
|
7. General Coroutine Usage
A few things to keep in mind:
- Define a method that returns
IEnumerator, and useyield returninside it to wait (e.g.,null,new WaitForSeconds(...),WaitForEndOfFrame, etc.) - Start it with
StartCoroutine(MyCoroutine()); you can stop it withStopCoroutineif needed - Never write a
while(true)loop without anyyieldinside a main-thread method (likeStart()), or it will freeze the entire game loop
|
|
8. Implementing Asteroid Self‑Rotation
Use the Random.insideUnitSphere API, which returns a random Vector3 whose direction and magnitude are uniformly distributed inside a unit sphere. Multiplying this vector by rotationSpeed scales it to the desired angular velocity. Finally, assign it to Rigidbody.angularVelocity: the vector’s direction represents the rotation axis, and its magnitude represents the angular speed (in radians per second). As a result, the asteroid rotates around a random axis at the given speed.
|
|
9. Two Ways to Implement Enemy Shooting
Method 1: Use the same approach as the player’s shooting logic—accumulate time and fire when the timer exceeds a threshold.
Method 2: Use the InvokeRepeating(string methodName, float time, float repeatRate) API. It calls methodName after time seconds, and then repeats the call every repeatRate seconds.
|
|
10. Implementing Enemy Dodging Behavior
After an enemy has existed for a certain amount of time (a random value), it begins a dodge action. If the enemy spawns on the left half of the screen, it moves to the right; if it spawns on the right half, it moves to the left. After dodging for another random duration, it stops the horizontal movement, continues descending, and then repeats the dodge cycle until it is naturally destroyed.
- Use a coroutine to perform the dodge for a period of time, then return to straight downward movement
- To make the dodge motion smooth and natural, apply acceleration using
Mathf.Lerp(float a, float b, float t), whereais the start value,bis the target value, andtis the interpolation factor between 0 and 1 - Remember to perform boundary checks while the enemy is moving
The tutorial uses Mathf.MoveTowards(float current, float target, float maxDelta) instead. This API is conceptually similar to Lerp in that it moves current toward target smoothly. However, using this method requires a much larger acceleration value; otherwise, the enemy may appear to not dodge at all. The reason lies in the fundamental difference between the two:
Lerpuses proportional interpolation (t is a 0–1 ratio), which pulls the value toward the target proportionally—even if the target is on the opposite side, it still produces a small counter‑directional velocityMoveTowardsuses an absolute step size (maxDelta), moving only a fixed amount per frame. IfmaxDeltais too small (e.g., acceleration is low), the per‑frame velocity change is overshadowed by other factors (friction, resets, threshold checks), making it look like “no dodge happened”
|
|
11. When to Use OnTrigger vs. OnCollision
Both can be used for collision detection.
OnCollision is used when actual physical interaction is required, such as collisions, bouncing, or applying forces. Since it calculates contact points and impulses, it has a higher performance cost and is suitable for real physics interactions.
OnTrigger only detects overlap and does not automatically produce physical responses. Because it usually doesn’t compute contact points or impulses, it’s cheaper than OnCollision. It’s ideal for area detection, trigger zones, pickups, damage areas, and similar cases.
12. How to Handle Effect Destruction (Separation of Concerns)
Effect destruction should be handled in a dedicated script rather than mixing it into the CollisionCheck script. This keeps responsibilities separated (CollisionCheck only handles triggering and detection), improves reusability, and makes it easier to switch to an object‑pooling system instead of destroying effects every time.
Reflection
The tutorial introduced several fresh ideas. In this project, I started writing my own coroutines and continued practicing the singleton pattern learned from the previous game. Whenever I had questions or felt the tutorial’s approach exceeded my current understanding, I asked AI for clarification—and the answers were generally satisfying. This project‑driven learning style feels very efficient, and the constant feedback helps prevent mental fatigue.
There are still concepts I don’t fully grasp yet, such as “single‑responsibility components” and “decoupling with interfaces/events.” I’ll need more hands‑on experience in future projects to internalize these ideas.
Another fun discovery: the Unity splash screen logo can be replaced! I immediately created a small custom logo, and it looks great.
Code Reuse
1. Scrolling Infinite Background
|
|
2. Rotation Effect
|
|
3. MoveController
Using a Rigidbody allows this logic to be reused for any object moving along the Z‑axis.
|
|
4. DestroyByTime
This logic can be reused for automatically destroying visual effects or other similar things.
|
|
5. AudioManager
Reusable for managing and playing all sound effects.
|
|
Flappy Bird
Tutorial Reference and Assets Source:Youtube-Zigurous
Source Code: on Gitee
Game Overview
Flappy Bird is a simple 2D mobile game released in 2013 by Vietnamese developer Dong Nguyen. The game became a global phenomenon due to its extremely simple visuals, extremely high difficulty, and the uniquely “addictive frustration” it creates.
Core Gameplay:
- Genre: Pixel‑style 2D game with a scrolling background; the player controls a small pixel bird
- Goal: Guide the bird through pairs of pipes; the farther you fly, the higher you score
- Controls:
- Tap the screen (or press any key) to make the bird flap upward briefly
- Without input, the bird continuously falls due to gravity
- Obstacle System:
- Randomly generated green pipes of varying heights appear in the scene
- The bird must pass through the gap between each pair of pipes
- Touching a pipe or the ground results in an immediate game over
- Failure Condition:
- The bird hits an obstacle or the ground
- Scoring:
- Passing through each pair of pipes awards 1 point
- There is no upper limit to the score
Game Breakdown
Content
- Scene layout (parallax background, infinite scrolling)
- Animation (bird flapping)
- UI (score display)
- Sound effects (scoring, collision)
Core Logic
- The bird moves forward automatically and falls naturally under gravity
- The player can press
W, theUp Arrow, or click theleft mouse buttonto make the bird move upward - If the bird hits a pipe or the ground while moving forward, the game ends
- Display the current score
Development Steps
- Bird
- Bird movement
- Bird flapping animation
- Background
- Background creation
- Parallax scrolling
- Pipes
- Pipe prefab creation
- Pipe generation
- Pipe movement
- Scoring
- UI design / setup
- Menu interaction implementation
- Adding sound effects
Problems & Solutions
1. Implementing Touch Controls
|
|
2. Using InvokeRepeating to Implement a Simple Animation
It’s important to note that InvokeRepeating schedules a method to be called at fixed intervals within Unity’s main loop. Each invocation finishes and returns control to the engine, and the method is called again after the next interval. Although it is not an actual infinite loop, if you write a while(true) (or any non‑terminating loop) inside the invoked method, the method will never return, blocking the main thread and freezing the game/editor.
In addition, InvokeRepeating is typically placed in Start (or any one‑time initialization after Awake). You only need to call it once to schedule the repeated execution, and Unity will continue calling the method at the specified interval. If you place InvokeRepeating inside Update, it will register a new repeating call every frame, resulting in a huge number of concurrent scheduled calls. This leads to the method being executed multiple times, more frequently than intended, causing logic errors and performance issues.
Using InvokeRepeating to animate the bird’s flapping:
|
|
3. An Approach to Implementing Parallax + Infinite Background
In the tutorial, all background elements are created as 3D Quads, and the 2D images are turned into Materials using the Unlit/Texture shader. The movement effect is achieved by adjusting the built‑in Offset property of the material. This method is very simple and highly reusable.
|
|
4. An Alternative Approach to Destroying Pipes
The common approach is to place a trigger collider at the left edge of the screen, and when a pipe prefab enters that trigger, it gets destroyed. The tutorial uses a simpler method: destroying the pipe based on its pixel position.
|
|
5. Coordinate Systems in Unity
World Coordinates (World)
- Unity’s world coordinates are based on the global origin (0,0,0), measured in Unity units (commonly treated as meters)
- Axis directions: X to the right, Y upward, Z forward (positive Z points toward the front of the Scene view)
transform.positionrepresents a GameObject’s world position;transform.localPositionis its position relative to its parent
Screen Coordinates (Screen)
- Measured in pixels, with the origin at the bottom‑left corner (0,0); the top‑right corner is (
Screen.width,Screen.height) - Legacy
IMGUI (OnGUI)uses the top‑left corner as the origin; UI coordinate behavior also depends on theCanvas Render Mode(Screen Space / World Space)
Viewport Coordinates (Viewport)
- Normalized coordinates in the range [0,1], with (0,0) at the bottom‑left and (1,1) at the top‑right
- Conversion available via
Camera.ViewportToWorldPointandWorldToViewportPoint
In the pipe‑destruction example above, Camera.ScreenToWorldPoint(Vector3 screenPoint) is used to convert the screen‑space origin (0,0,0)—i.e., Vector3.zero—into world coordinates. This ensures that the value can be compared with transform.position.x inside Update, since both are then in the same coordinate system.
6. Restarting and Pausing the Game
The simplest approach is to use Time.timeScale.
Reflection
Because this is a 2D game, directions like Vector3.up and Vector3.left can be used directly, which greatly simplifies the code. The tutorial introduced several elegant and practical design ideas—for example, using 3D quads for the background and applying _meshRenderer.material.mainTextureOffset to achieve infinite scrolling and parallax; converting a point from screen coordinates to world coordinates to determine when an object should be destroyed, instead of relying on traditional collision detection; and implementing simple animations using InvokeRepeating combined with iterating through sprites, avoiding the need for Unity’s built‑in animation system.
In my own practice, I revisited the singleton pattern for both the GameManager and AudioManager, and refreshed my understanding of sound playback and UI interaction design. Even though this is a beginner‑level mini‑game, I still learned a lot from it.
Code Reuse
A Spawner Implementation
Using InvokeRepeating to spawn game objects at fixed intervals.
|
|
Other reusable code snippets have already been covered in the “Problems & Solutions” section above, so they won’t be repeated here.
Pong
Tutorial Reference and Assets Source:Youtube-Zigurous
Source Code: on Gitee
Game Overview
Pong is one of the earliest and most influential arcade games in video game history, released by Atari in 1972. It simulates the real‑world sport of table tennis and is widely regarded as the first commercially successful video game.
Core Gameplay:
- The game takes place on a 2D plane with a vertical paddle on each side and a moving ball in the center
- The player controls one paddle, moving it up and down (typically via joystick or buttons) to bounce the ball back toward the opponent
- If a player fails to return the ball, the opponent scores a point
- The ball bounces off the top and bottom boundaries; hitting the left or right boundary results in a score
- As the game progresses, the ball may gradually increase in speed, raising the difficulty
Game Breakdown
Content
- Scene layout
-
Player paddle
-
Ball
-
AI paddle
-
UI (score display)
Core Logic
- The player moves the paddle using
W / Up ArrowandS / Down Arrowto hit the ball - The ball bounces off the top and bottom boundaries
- If the ball enters the player’s side, the opponent scores a point, and vice versa
- Display the current score
Development Steps
- Scene layout
- Player paddle movement
- Ball movement implementation
- AI paddle behavior
- Gradual increase in ball speed after each bounce
- Displaying the UI score
Problems & Solutions
1. Angular Drag and Linear Drag
In a Rigidbody, Angular Drag represents the angular damping coefficient. If you don’t want the object to rotate, you should manually set it to 0 (its default value is 0.05) and also disable rotation on the Z‑axis in the Constraints section. This is a common setup for 2D games like this one.
Linear Drag, on the other hand, applies to positional movement. A higher drag value causes the object to stop moving or rotating more quickly after being affected by collisions or forces.
2. Notes on Using FixedUpdate
Avoid reading input directly inside FixedUpdate. Since FixedUpdate runs at the physics timestep, input events may be missed or become inconsistent.
The correct approach is to read input inside Update(), store the result in a field, and then apply forces based on that field inside FixedUpdate().
|
|
FixedUpdateis primarily used for updates that need to stay in sync with the physics system.
Tasks appropriate for FixedUpdate:
- Applying forces/torque (
Rb.AddForce / AddRelativeForce) or directly settingRigidbody.velocity - Using
Rigidbody.MovePosition / MoveRotation(for Kinematic Rigidbodies) - Timers, constraints, and solver logic that must match the physics timestep (deterministic simulation, server‑side physics steps)
- Physics‑based queries (collision checks, overlap tests, raycasts performed within the physics step)
- Physics prediction/interpolation/extrapolation logic (synchronized with the fixed timestep)
Tasks not appropriate for FixedUpdate (require per‑frame or lower‑latency handling):
- Input reading
- Rendering / camera follow
- UI updates
- Animation (typically handled in
UpdateorLateUpdate)
3. Implementing Ball Bounce Behavior
Using code like this will not produce the correct effect. The issue is likely incorrect direction calculation: using -rb.velocity is not equivalent to reflecting the velocity based on the collision normal. As a result, the ball may be pushed in an unintended direction.
|
|
It can be resolved by using the collision contact normal.
|
|
4. Using the EventSystem to Handle Player and AI Scoring
The implementation feels very similar to how UI buttons work.
Explanation of the code:
TriggerEventis essentially an alias/subclass ofUnityEvent<BaseEventData>, allowing callbacks that receiveBaseEventDatato be registered either in the Inspector or via code- A
BaseEventDatainstance is created with the currentEventSystemas its source; the callback can read thiseventDatato obtain context - Calling
scoreTrigger.Invoketriggers all registered callbacks and passes in theeventData
|
|
Reflection
The tutorial uses a base class Paddle along with two subclasses, PlayerPaddle and ComputerPaddle. Shared logic—such as component retrieval—is placed in the parent class, while each subclass implements its own behavior. In C++ projects, I frequently rely on inheritance, but when working in Unity I often forget to apply it. In future projects, I need to remind myself to use inheritance more often when it fits the design.
Code Reuse
Reusable code in this project has already been discussed in the “Problems and Solutions” section:
- Implementation of ball bounce behavior
- Using the
EventSystemto flexibly trigger multiple callbacks
Asteroids
Tutorial Reference and Assets Source:Youtube-Zigurous
Source Code: on Gitee
Game Overview
Asteroids is a classic arcade shooter released by Atari in 1979. With its minimalist vector graphics and fast‑paced gameplay, it became a landmark title in video game history.
Core Gameplay:
- The player controls a small spaceship that moves freely in zero‑gravity space (able to thrust, rotate, and shoot)
- Press
W / Up ArroworS / Down Arrowto rotate - Press
A / Left ArroworD / Right Arrowto thrust left or right - Press
Spaceor click the left mouse button to fire bullets
- Press
- Asteroids of various sizes continuously appear, drifting in from the edges of the screen
- Objective: destroy all asteroids with laser shots
- Large and medium asteroids split into smaller fragments when hit (e.g., Large → Medium → Small → Disappear)
- Failure:
- The spaceship is destroyed instantly upon colliding with an asteroid (typically with limited lives)
- Additional Features:
- The ship has momentum; it continues drifting even after thrust stops, requiring reverse thrust to slow down
Game Breakdown1
Content
-
Scene layout
-
Player
-
Asteroids
-
Explosion effects
Core Logic
- Player movement and shooting
- Random asteroid spawning and movement
- Asteroids split when hit by the player’s bullets, until they disappear
- The round ends if the player collides with an asteroid
- Display the current score
- When all lives are used up, the game ends
Development Steps
- Scene setup
- Player implementation
- Movement
- Shooting
- Asteroid implementation
- Asteroid prefabs (different shapes, sizes, masses, initial angles)
- Spawning asteroids
- Implementing split/destroy logic
- Ending a round
- Player death and respawn
- Player explosion effect
- Displaying the UI score and player’s lives
- Ending the game when all lives are exhausted
Problems & Solutions
1. Configuring Collision Layers
Use Project Settings > Physics 2D to ignore collisions between the player and bullets, as well as between bullets themselves.
2. Setting Initial Asteroid States
Since the asteroids in this project have multiple variants—different sizes, sprites, masses, and initial angles—the strategy is to configure all these parameters directly in the asteroid prefabs. This allows the Spawner.cs script to simply instantiate the prefabs, keeping responsibilities clean and separated:
Asteroid.cshandles the asteroid’s own propertiesSpawner.cshandles asteroid generation
|
|
Initial Rotation of Asteroids:
Use Random.value, which returns a value between 0 and 1. Since the asteroid rotates around the Z‑axis, the rotation can be set simply as: transform.eulerAngles = new Vector3(0, 0, Random.value * 360);
Handling Asteroid Size:
This logic cannot be placed inside Asteroid.cs, because that script later handles the splitting behavior. If size is randomized inside Start(), it will break the splitting logic. The correct approach is to randomize the size inside Spawner.cs. This was a major pitfall I encountered during the project.
3. Approach to Asteroid Spawning and Movement
Asteroids are spawned within a circular area centered on the screen. They move toward the center. To introduce randomness, an offset angle is added to the direction toward the center. Since the offset is applied around the Z‑axis, Quaternion.AngleAxis(float angle, Vector3.forward) can be used.
For asteroid movement, the movement logic itself is handled by Asteroid.cs, while Spawner.cs instantiates the asteroid and calls its movement behavior.
|
|
It’s important to note that the final movement direction of an asteroid is computed as rotation * dir. Since rotation is a quaternion, this operation applies the rotation to the vector:
Rotated Vector = Quaternion * Original Vector
The quaternion must be on the left side of the multiplication operator.
The underlying mathematical principles will be explored in more detail when studying the related math topics, so they won’t be expanded on here.
Additional Notes on Quaternions
Avoid modifying the x, y, z, or w components of a quaternion directly. These values do not represent angles; they are part of a complex mathematical structure. Directly editing them will lead to incorrect behavior.
Always use Unity’s built‑in API.
Commonly Used API Methods:
| Scenario | Method |
|---|---|
| Rotate a vector | rotatedVector = quaternion * vector; |
| Make an object face a direction | Quaternion.LookRotation(vector); |
| Convert (0, 90, 0) to a quaternion | Quaternion.Euler(0, 90, 0); |
| Get the Euler angles of a quaternion | quaternion.eulerAngles; |
4. Designing Player Invincibility Frames
This is implemented by changing the player’s physics collision layer using LayerMask.NameToLayer(layerName);. It’s straightforward, and Invoke is used to schedule the duration of the invincibility period.
5. Particle System Configuration
Since the UI logic uses Time.timeScale to control game start and end states, the particle system must not accidentally play at the moment a new round begins. To prevent this, set the particle system’s Delta Time to Unscaled in the Inspector.
Reflection
This project requires extensive use of rotation‑related methods, so becoming comfortable with the commonly used APIs is essential. It also highlights the need to strengthen the underlying math knowledge.
There are also two techniques in this project that are worth remembering and using more often:
Passing references to objects from other scripts through method parameters
|
|
Calling a function from another script:
You can obtain the reference either through a GameObject or directly from the other script’s component attached to a game object:
|
|
Code Reuse
1. Designing Player Invincibility Frames
This is implemented by changing the player’s physics collision layer, combined with the use of Invoke:
|
|
2. Clearing All Objects in the Scene
Both Flappy Bird and Asteroids use the same approach when resetting the game: use FindObjectsOfType to retrieve all relevant objects in the scene, iterate through the resulting array, and destroy each one.
|
|