적 자동 스폰
Steps
1. 적 object는 클래스와 리스트를 통해 관리합니다.
2. 너무 많은 생성과 삭제로 최적화가 필요할 시 적 삭제 혹은 사망 이후 재활용 리스트를 만들어 생성과 삭제가 과도하게 이루지지 않도록 조정 합니다.
씬에 적 오브젝트를 추가합니다.
- 씬에 적 오브젝트나 추가합니다.
- 좌측 탐색기 패널에서 적 오브젝트 선택합니다.
- 오브젝트 이름 을 Enemy로 변경합니다.
- 우측 속성 패널에서 적의 세부적인 파라미터(위치, 각도, 크기 등)를 설정합니다.
- 값을 계속 변경해가며 마음에 드는 값을 찾습니다.
적이 리스폰할 장소를 지정해 코드를 작성해 줍니다.
const enemys = [];
const object = WORLD.getObject("Enemy");
const minSpawnRadius = 20;
const maxSpawnRadius = 100;
function SpawnRandomPos() {
// Select a random angle
const angle = Math.random() * 2 * Math.PI; // 0부터 2π까지의 랜덤 각도
const radius = Math.random() * (maxSpawnRadius - minSpawnRadius) + minSpawnRadius; // 20부터 100까지의 랜덤 반지름
// Calculate x, z coordinates based on the angle and radius
const x = PLAYER.position.x + radius * Math.cos(angle);
const z = PLAYER.position.z + radius * Math.sin(angle);
const y = 2; // y 좌표는 고정값으로 설정
const clone = THREEADDON.SkeletonUtils.clone(object);
object.animations.forEach((item) => {
clone.animations.push(item);
});
clone.position.set(x, y, z);
WORLD.add(clone);
const enemy = new Enemy(clone);
enemys.push(enemy);
}
적이 생성 될 시간을 설정해 일정시간마다 스폰 되도록 코드를 작성합니다.
let spawnInterval = 300;
let startSpawn;
function CreateEnemy() {
clearInterval(startSpawn);
startSpawn = setInterval(() => {
SpawnRandomPos();
}, spawnInterval);
}
적이 플레이어를 향해 이동하도록 코드를 작성해줍니다.
function Move(dt, object) {
const radius = 1.5;
const direction = new THREE.Vector3(
PLAYER.position.x - object.position.x,
0,
PLAYER.position.z - object.position.z
);
direction.normalize();
// move
object.position.add(direction.multiplyScalar(speed * dt));
const lookAtPosition = new THREE.Vector3(
PLAYER.position.x,
object.position.y,
PLAYER.position.z
);
object.lookAt(lookAtPosition);
}
겹치지 않도록 충돌 관련 코드를 추가해줍니다.
function Move(dt, object) {
const radius = 1.5;
const direction = new THREE.Vector3(
PLAYER.position.x - object.position.x,
0,
PLAYER.position.z - object.position.z
);
direction.normalize();
// maintain distance from other enemies
const separationForce = new THREE.Vector3();
for (let enemy of enemys) {
if (enemy !== object) {
const distance = object.position.distanceTo(enemy._object
.position);
if (distance < radius + enemy._radius) { // Collision avoidance distance
const diff = new THREE.Vector3().subVectors(object.position,
enemy._object.position);
diff.normalize();
separationForce.add(diff); // 서로 멀어지게 하는 힘 추가
}
}
}
direction.add(separationForce);
direction.normalize();
// move
object.position.add(direction.multiplyScalar(speed * dt));
const lookAtPosition = new THREE.Vector3(
PLAYER.position.x,
object.position.y,
PLAYER.position.z
);
object.lookAt(lookAtPosition);
}
코드를 이용하여 아래와 같은 코드를 완성합니다.
class Enemy {
constructor(object) {
this._object = object;
this._speed = 10;
this._hp = 100;
//Collision avoidance distance
this._radius = 1;
// animation
this._mixer = new THREE.AnimationMixer(this._object);
this._moveAction = this._mixer.clipAction(this._object.animations[1]);
// animation speed
this._moveAction.setEffectiveTimeScale(1.7); // 이동 애니메이션 속도
this.Init();
}
Init() {
// move action play
this._moveAction.play();
}
Move(dt) {
if (this._hp < 0) return;
// animation update
if (this._mixer) { this._mixer.update(dt); }
console.log("move")
const direction = new THREE.Vector3(
PLAYER.position.x - this._object.position.x,
0,
PLAYER.position.z - this._object.position.z
);
direction.normalize();
// maintain distance from other enemies
const separationForce = new THREE.Vector3();
for (let enemy of enemys) {
if (enemy !== this) {
const distance = this._object.position.distanceTo(enemy._object
.position);
if (distance < this._radius + enemy._radius) { // Collision avoidance distance
const diff = new THREE.Vector3().subVectors(this._object.position,
enemy._object.position);
diff.normalize();
separationForce.add(diff); // 서로 멀어지게 하는 힘 추가
}
}
}
direction.add(separationForce);
direction.normalize();
// move
this._object.position.add(direction.multiplyScalar(this._speed * dt));
const lookAtPosition = new THREE.Vector3(
PLAYER.position.x,
this._object.position.y,
PLAYER.position.z
);
this._object.lookAt(lookAtPosition);
}
}
const object = WORLD.getObject("Enemy");
const enemys = [];
let spawnInterval = 300;
const startButton = GUI.getObject("StartButton");
const minSpawnRadius = 20;
const maxSpawnRadius = 100;
function SpawnRandomPos() {
// Select a random angle
const angle = Math.random() * 2 * Math.PI; // 0부터 2π까지의 랜덤 각도
const radius = Math.random() * (maxSpawnRadius - minSpawnRadius) + minSpawnRadius; // 20부터 100까지의 랜덤 반지름
// Calculate x, z coordinates based on the angle and radius
const x = PLAYER.position.x + radius * Math.cos(angle);
const z = PLAYER.position.z + radius * Math.sin(angle);
const y = 2; // y 좌표는 고정값으로 설정
const clone = THREEADDON.SkeletonUtils.clone(object);
object.animations.forEach((item) => {
clone.animations.push(item);
});
clone.position.set(x, y, z);
WORLD.add(clone);
const enemy = new Enemy(clone);
enemys.push(enemy);
}
function CreateEnemy() {
clearInterval(spawnInterval);
spawnInterval = setInterval(() => {
SpawnRandomPos();
}, spawnInterval);
}
function Start() {
startButton.onClick(function() {
CreateEnemy();
startButton.hide();
})
}
function Update(dt) {
for (let i = 0; i < enemys.length; i++) {
enemys[i].Move(dt);
}
}