First Game TutorialChapter 7: Enemy Spawning: The Source of ChallengeOn this pageChapter 7: Enemy Spawning: The Source of Challenge In this chapter, we will enhance the game's challenge by dynamically spawning enemies. Building on the code from previous chapters, we will focus on designing and implementing enemy spawning logic and behaviors. 1. Enemy Spawning Design Concept In games, enemies are a primary source of challenge. We aim to implement the following features: Randomized Enemy Spawning: Enemies spawn from various directions (top, bottom, left, right). Dynamic Speed Adjustment: Enemy speed increases with the player's score. Collision Handling: The game ends when enemies collide with the player. 2. Implementing Enemy Spawning Logic To dynamically spawn enemies, we need to write an Enemy spawning function. Below is the breakdown of the code logic: Spawn Position: Randomly select one of the four edges for enemy spawning. Movement Direction: Randomly determine the enemy's movement direction based on its spawn position. Speed Adjustment: Dynamically increase the speed based on the current score. Here is the complete Enemy function code: dodge_the_creeps/init.tsxconst Enemy = (world: PhysicsWorld.Type, score: number) => { const dir = math.random(0, 3); // Randomly choose direction const angle = math.random(dir * 90 + 25, dir * 90 + 180 - 25); // Avoid axis-aligned spawns let pos = Vec2.zero; const minW = -hw - 40; const maxW = hw + 40; const minH = -hh - 40; const maxH = hh + 40; const randW = math.random(minW, maxW); const randH = math.random(minH, maxH); // Determine initial position based on direction switch (dir) { case 0: pos = Vec2(minW, randH); break; // Spawn from left case 1: pos = Vec2(randW, maxH); break; // Spawn from bottom case 2: pos = Vec2(maxW, randH); break; // Spawn from right case 3: pos = Vec2(randW, minH); break; // Spawn from top } const radian = math.rad(angle); // Convert angle to radians const velocity = Vec2(math.sin(radian), math.cos(radian)) .normalize() .mul(200 + score * 2); // Speed scales with score // Create enemy entity toNode( <body world={world} group={0} type={BodyMoveType.Dynamic} linearAcceleration={Vec2.zero} x={pos.x} y={pos.y} velocityX={velocity.x} velocityY={velocity.y} angle={angle} onMount={node => { const enemys = [Animation.enemyFlyingAlt, Animation.enemySwimming, Animation.enemyWalking]; playAnimation(node, enemys[math.random(0, 2)]); // Randomly select an animation }}> <disk-fixture radius={40}/> {/* Set collision body */} </body> )?.addTo(world);}; 3. Dynamic Enemy Spawning In the main game logic, we need to spawn enemies periodically. The following code demonstrates how to use world.loop to spawn enemies based on the player's score: Example Codeworld.loop(() => { sleep(0.5); // Spawn an enemy every 0.5 seconds Enemy(world, score); // Pass the current score return false; // Repeat indefinitely}); 4. Balancing the Game To optimize the gameplay experience, you can tweak the following parameters: Initial Speed: Modify the 200 + score * 2 base speed value. Spawn Frequency: Adjust the interval in sleep(0.5). Score Impact on Speed: Change the weight of the score in the speed formula, e.g., 200 + score * 4. 5. Verifying the Gameplay After implementing enemy spawning, test the following: Enemy spawn directions and speeds meet expectations. Collision detection works properly. Increasing score makes the game progressively challenging. 6. Handling Off-Screen Enemies Design Concept: To prevent the buildup of off-screen enemies, we need to detect when they leave the scene boundaries. Enemies leaving the scene will be removed. We can detect off-screen enemies by adding a static body with a rectangular sensor in the <physics-world>. When enemies enter or leave the sensor area, events will trigger accordingly. Here is the implementation code: Example Code<physics-world> <body type={BodyMoveType.Static} group={1} onBodyLeave={() => { // Increment score when an enemy leaves the scene score++; }}> <rect-fixture sensorTag={0} width={width} height={height}/> {/* Sensor matches scene size */} </body></physics-world> Explanation: onBodyLeave is a callback triggered when an enemy leaves the scene. score++ increments the score and updates the score display in real time. 7. Score Display Design Concept: Players earn points by avoiding collisions and letting enemies leave the scene. Each enemy that exits the scene increases the player's score. To display the score, we use a Label in the game interface. Ensure the label appears only after the "Get Ready!" prompt. Example Codeconst label = useRef<Label.Type>(); // Create a reference for the score label<label ref={label} fontName='Xolonium-Regular' fontSize={60} text='0' y={300} visible={false}/> Explanation: useRef allows dynamic updates to the label. The visible property is initially set to false, showing the label only after the game starts. When the game starts, the label becomes visible after the "Get Ready!" prompt: Example Codeworld.once(() => { const msg = toNode( <label fontName='Xolonium-Regular' fontSize={80} text='Get Ready!' y={200}/> ); sleep(1); // Remove the prompt after 1 second msg?.removeFromParent(); if (label.current) { label.current.visible = true; // Show the score label } // Start spawning enemies // ...}); 8. Complete Enemy Logic Integration Below is the core integrated logic: dodge_the_creeps/init.tsxconst Game = () => { inputManager.popContext(); inputManager.pushContext('Game'); let score = 0; const label = useRef<Label.Type>(); // Create a reference for the score label Audio.playStream('Audio/House In a Forest Loop.ogg', true); return ( <clip-node stencil={<Rect/>}> <physics-world onMount={world => { Player(world); // Create player world.once(() => { // Show "Get Ready!" prompt const msg = toNode( <label fontName='Xolonium-Regular' fontSize={80} text='Get Ready!' y={200}/> ); sleep(1); msg?.removeFromParent(); if (label.current) { label.current.visible = true; } // Periodically spawn enemies world.loop(() => { sleep(0.5); Enemy(world, score); return false; }); }); }}> <contact groupA={0} groupB={0} enabled={false}/> {/* Disable collisions between enemies */} <contact groupA={0} groupB={1} enabled/> {/* Enable collisions between player and enemies */} {/* Sensor to detect off-screen enemies */} <body type={BodyMoveType.Static} group={1} onBodyLeave={() => { score++; if (label.current) { label.current.text = score.toString(); } }}> <rect-fixture sensorTag={0} width={width} height={height}/> </body> </physics-world> <label ref={label} fontName='Xolonium-Regular' fontSize={60} text='0' y={300} visible={false}/> </clip-node> );}; 9. Debugging and Verification After finishing, test the game to ensure: Off-screen enemies trigger score increment. Score updates correctly. Score display logic syncs with game prompts. 10. Chapter Summary In this chapter, we implemented off-screen enemy detection and the player's scoring mechanism, making the game more complete and engaging. In the next chapter, we will design the game's user interface, including pause functionality, restart buttons, and more.