Volleyball Tutorial Scripts
Learn the basics of game development through various script examples provided by OasisW.
Ball Controller
Script that handles ball physics simulation and tap input.
var Ball = pc.createScript('ball');
// Default attributes
Ball.attributes.add('gravity', {
type: 'number',
default: -9.8,
title: 'Gravity'
});
Ball.attributes.add('defaultTap', {
type: 'number',
default: 5,
title: 'Default Tap Speed'
});
Ball.attributes.add('impactEffect', {
type: 'entity',
title: 'Impact Effect'
});
Ball.attributes.add('ballMinimum', {
type: 'number',
default: -6,
title: 'Ball Minimum Y'
});
Ball.attributes.add('speedMult', {
type: 'number',
default: 4,
title: 'Speed Multiplier'
});
Ball.attributes.add('angMult', {
type: 'number',
default: -6,
title: 'Angular Speed Multiplier'
});
// Initialize code
Ball.prototype.initialize = function () {
this.paused = true;
this.game = this.app.root.findByName('Game');
this.app.on('game:start', this.unpause, this);
this.app.on('game:gameover', this.pause, this);
this.app.on('game:reset', this.reset, this);
this._vel = new pc.Vec3(0, 0, 0);
this._acc = new pc.Vec3(0, this.gravity, 0);
this._angSpeed = 0;
this._origin = this.entity.getLocalPosition().clone();
this._rotation = this.entity.getLocalRotation().clone();
if (!Ball._tmp) {
Ball._tmp = new pc.Vec3();
}
};
// Update function
Ball.prototype.update = function (dt) {
if (this.paused) {
this.entity.rotate(0, 30 * dt, 0);
return;
}
const position = this.entity.getLocalPosition();
const tmp = Ball._tmp;
tmp.copy(this._acc).scale(dt);
this._vel.add(tmp);
tmp.copy(this._vel).scale(dt);
position.add(tmp);
this.entity.setLocalPosition(position);
this.entity.rotate(0, 0, this._angSpeed);
if (position.y < this.ballMinimum) {
if (this.game.script && this.game.script.game && this.game.script.game.gameOver) {
this.game.script.game.gameOver();
}
}
};
// Tap function
Ball.prototype.tap = function (dx, dy, notScoring) {
if (notScoring === undefined) notScoring = false;
this._vel.set(this.speedMult * dx, this.defaultTap, 0);
this._angSpeed += this.angMult * dx;
const tmp = Ball._tmp;
tmp.copy(this.entity.getLocalPosition());
tmp.x -= dx;
tmp.y -= dy;
if (this.impactEffect && this.impactEffect.particlesystem) {
this.impactEffect.setLocalPosition(tmp);
this.impactEffect.particlesystem.reset();
this.impactEffect.particlesystem.play();
this.impactEffect.lookAt(this.entity.getPosition());
}
if (this.entity.sound) {
this.entity.sound.play("bounce");
}
if (!notScoring && this.game.script && this.game.script.game && this.game.script.game.addScore) {
this.game.script.game.addScore(1);
}
};
// Game control functions
Ball.prototype.unpause = function () {
this.paused = false;
this.tap(0, 0, true);
};
Ball.prototype.pause = function () {
this.paused = true;
};
Ball.prototype.reset = function () {
this.entity.setLocalPosition(this._origin);
this.entity.setLocalRotation(this._rotation);
this._vel.set(0, 0, 0);
this._acc.set(0, this.gravity, 0);
this._angSpeed = 0;
};
Input Handling
Script that detects mouse and touch input to tap the ball.
var Input = pc.createScript('input');
// Attribute definitions
Input.attributes.add('ball', {
type: 'entity',
title: 'Ball Entity'
});
Input.attributes.add('camera', {
type: 'entity',
title: 'Camera Entity'
});
Input.attributes.add('ballRadius', {
type: 'number',
default: 0.5,
title: 'Ball Tap Radius'
});
// Initialization
Input.prototype.initialize = function () {
this._paused = true;
// Event listener registration
this.app.on("game:start", function () {
this._paused = false;
}, this);
this.app.on("game:gameover", function () {
this._paused = true;
}, this);
// Input handling registration
if (this.app.touch) {
this.app.touch.on("touchstart", this._onTouchStart, this);
}
this.app.mouse.on("mousedown", this._onMouseDown, this);
// Shared vector initialization
if (!Input._worldPos) {
Input._worldPos = new pc.Vec3();
}
};
// Touch input handling
Input.prototype._onTouchStart = function (event) {
if (this._paused) return;
var touch = event.changedTouches[0];
this._onTap(touch.x, touch.y);
event.event.preventDefault(); // Prevent mouse event duplication
};
// Mouse click handling
Input.prototype._onMouseDown = function (event) {
if (this._paused) return;
this._onTap(event.x, event.y);
};
// Tap handling logic
Input.prototype._onTap = function (x, y) {
var ballPos = this.ball.getPosition();
var camPos = this.camera.getPosition();
var worldPos = Input._worldPos;
// Z depth correction: use distance between ball and camera
this.camera.camera.screenToWorld(x, y, camPos.z - ballPos.z, worldPos);
var dx = ballPos.x - worldPos.x;
var dy = ballPos.y - worldPos.y;
var lenSqr = dx * dx + dy * dy;
// Check if within tap radius
if (lenSqr < this.ballRadius * this.ballRadius) {
if (this.ball.script && this.ball.script.ball && this.ball.script.ball.tap) {
this.ball.script.ball.tap(dx, dy);
}
}
};
Menu UI
Script that manages the game start menu screen.
var UiMenu = pc.createScript('uiMenu');
// Attribute definitions
UiMenu.attributes.add('overlay', {
type: 'entity',
title: 'Overlay Entity'
});
// Initialization
UiMenu.prototype.initialize = function () {
this.on('enable', this.onEnable, this);
this.on('disable', this.onDisable, this);
this.onEnable(); // Handle initial activation as well
};
// When activated
UiMenu.prototype.onEnable = function () {
if (this.overlay) {
this.overlay.enabled = true;
if (this.overlay.element) {
this.overlay.element.on('click', this.start, this);
}
}
if (this.ball) {
var meshInstance = this.ball.model && this.ball.model.meshInstances && this.ball.model.meshInstances[0];
if (meshInstance && meshInstance.material) {
meshInstance.material.depthTest = false;
}
}
};
// When deactivated
UiMenu.prototype.onDisable = function () {
if (this.overlay) {
this.overlay.enabled = false;
if (this.overlay.element) {
this.overlay.element.off('click', this.start, this);
}
}
};
// 시작 이벤트
UiMenu.prototype.start = function (event) {
this.app.fire('ui:start');
if (event && event.stopPropagation) {
event.stopPropagation();
}
};
Game Over UI
Script that manages the UI displayed when the game ends and the restart functionality.
var UiGameover = pc.createScript('uiGameover');
// Attribute definitions
UiGameover.attributes.add('overlay', {
type: 'entity',
title: 'Overlay Entity'
});
// Initialization
UiGameover.prototype.initialize = function () {
// Find and store Score text entity
this.score = this.entity.findByName('Score');
this._score = 0;
// Listen for game score update events
this.app.on('game:score', function (score) {
this._score = score;
if (this.score && this.score.element) {
this.score.element.text = this._score.toString();
}
}, this);
this.on('enable', this.onEnable, this);
this.on('disable', this.onDisable, this);
this.onEnable(); // Manually execute on initial activation
};
// Show UI and connect click events when activated
UiGameover.prototype.onEnable = function () {
if (this.overlay) {
this.overlay.enabled = true;
if (this.overlay.element) {
this.overlay.element.on('click', this.reset, this);
}
}
};
// Hide UI and disconnect events when deactivated
UiGameover.prototype.onDisable = function () {
if (this.overlay) {
this.overlay.enabled = false;
if (this.overlay.element) {
this.overlay.element.off('click', this.reset, this);
}
}
};
// Reset handling
UiGameover.prototype.reset = function (event) {
this.app.fire('ui:reset');
if (event && event.stopPropagation) {
event.stopPropagation();
}
};
Score UI
UI script that displays the score during the game.
var UiScore = pc.createScript('uiScore');
UiScore.prototype.initialize = function() {
// Find and store score text entity
this.score = this.entity.findByName('Score');
this._changeScore = this._changeScore.bind(this);
this.on('enable', this.onEnable, this);
this.on('disable', this.onDisable, this);
this.onEnable();
};
UiScore.prototype.onEnable = function() {
// Listen for game score update events
this.app.on('game:score', this._changeScore, this);
this._changeScore(0);
};
UiScore.prototype.onDisable = function() {
// Stop listening for game score update events
this.app.off('game:score', this._changeScore, this);
};
UiScore.prototype._changeScore = function(newScore) {
// Update score display
if (this.score && this.score.element) {
this.score.element.text = newScore.toString();
}
};
UiScore.prototype.update = function(dt) {
};
Game Manager
Main script that manages the overall game state and flow.
var Game = pc.createScript('game');
// Attribute definitions
Game.attributes.add('uiMenu', {
type: 'entity',
title: 'UI Menu'
});
Game.attributes.add('uiInGame', {
type: 'entity',
title: 'UI In-Game'
});
Game.attributes.add('uiGameOver', {
type: 'entity',
title: 'UI Game Over'
});
Game.attributes.add('audio', {
type: 'entity',
title: 'Audio Entity'
});
// State constants (defined as global constants instead of static)
Game.STATE_MENU = 'menu';
Game.STATE_INGAME = 'ingame';
Game.STATE_GAMEOVER = 'gameover';
// Initialization
Game.prototype.initialize = function () {
this._state = Game.STATE_MENU;
this._score = 0;
this.setResolution();
// Adjust resolution when window size changes
window.addEventListener('resize', this.setResolution.bind(this));
// Event listener registration
this.app.on('ui:start', this.start, this);
this.app.on('ui:reset', this.reset, this);
};
// Resolution setting
Game.prototype.setResolution = function () {
var w = window.screen.width;
var h = window.screen.height;
// Set resolution for mobile devices
if (w < 640) {
this.app.setCanvasResolution(pc.RESOLUTION_AUTO, w, h);
this.app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
}
};
// Start game
Game.prototype.start = function () {
this._state = Game.STATE_INGAME;
this.app.fire('game:start');
this.uiMenu.enabled = false;
this.uiInGame.enabled = true;
if (this.audio && this.audio.sound) {
this.audio.sound.play('music');
}
};
// 게임 오버
Game.prototype.gameOver = function () {
this._state = Game.STATE_GAMEOVER;
this.app.fire('game:gameover');
this.uiInGame.enabled = false;
this.uiGameOver.enabled = true;
this.app.fire('game:score', this._score);
if (this.audio && this.audio.sound) {
this.audio.sound.stop();
this.audio.sound.play('gameover');
}
};
// Game reset
Game.prototype.reset = function () {
this.app.fire('game:reset');
this.resetScore();
this._state = Game.STATE_MENU;
this.uiGameOver.enabled = false;
this.uiMenu.enabled = true;
if (this.audio && this.audio.sound) {
this.audio.sound.stop();
}
};
// 점수 가져오기
Game.prototype.getScore = function () {
return this._score;
};
// 점수 추가
Game.prototype.addScore = function (v) {
this._score += v;
this.app.fire('game:score', this._score);
};
// Score initialization
Game.prototype.resetScore = function () {
this._score = 0;
this.app.fire('game:score', this._score);
};
Create your own game logic based on these examples!