Commit f3839865 authored by Jere Tuliniemi's avatar Jere Tuliniemi

Add a tennis game as a behavior example

Task-number: QT3DS-1255
parent 77cbf11f
<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://qt.io/qt3dstudio/uia">
<assets initial="game">
<presentation id="game" src="game.uip"/>
</assets>
<statemachine ref="#logic">
<visual-states>
<state ref="Initial">
<enter>
<goto-slide element="main:Scene" rel="next"/>
</enter>
</state>
</visual-states>
</statemachine>
</application>
<?xml version="1.0" encoding="UTF-8" ?>
<UIP version="3" >
<Project >
<ProjectSettings author="" company="" presentationWidth="800" presentationHeight="480" maintainAspect="False" >
<CustomColors count="16" >#ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff #ffffff</CustomColors>
</ProjectSettings>
<Classes >
<Behavior id="Movement" name="Movement" sourcepath="scripts\Movement.qml" />
<Behavior id="Score_001" name="Score" sourcepath="scripts\Score.qml" />
</Classes>
<Graph >
<Scene id="Scene" >
<Layer id="Layer" >
<Camera id="Camera" />
<Light id="Light" />
<Model id="Player" >
<Material id="Material" >
<Image id="Material_diffusemap" />
</Material>
<Behavior id="Movement_001" class="#Movement" />
</Model>
<Model id="Computer" >
<Material id="Material_002" >
<Image id="Material_002_diffusemap" />
</Material>
<Behavior id="Movement_003" class="#Movement" />
</Model>
<Model id="Ball" >
<Material id="Material_001" >
<Image id="Material_001_diffusemap" />
</Material>
<Behavior id="Movement_002" class="#Movement" />
</Model>
<Model id="Cube" >
<Material id="Material_003" >
<Image id="Material_003_diffusemap" />
</Material>
</Model>
<Model id="Cube2" >
<Material id="Material_004" >
<Image id="Material_004_diffusemap" />
</Material>
</Model>
<Text id="Score" >
<Behavior id="Score_002" class="#Score_001" />
</Text>
<Model id="Cube_001" >
<Material id="Material_005" >
<Image id="Material_005_diffusemap" />
</Material>
</Model>
<Text id="Text" />
<Text id="Text_001" />
</Layer>
</Scene>
</Graph>
<Logic >
<State name="Master Slide" component="#Scene" >
<Add ref="#Layer" aostrength="0" background="Transparent" blendtype="Normal" disabledepthprepass="False" disabledepthtest="False" height="480" heightunits="pixels" horzfields="Left/Width" left="0" leftunits="percent" progressiveaa="None" sourcepath="" temporalaa="False" width="800" widthunits="pixels" />
<Add ref="#Camera" orthographic="False" />
<Add ref="#Light" brightness="100" castshadow="True" lightambient="0.266667 0.266667 0.266667" lighttype="Point" position="0 0 -200" rotation="0 0 0" shdwfactor="73.3938" shdwfilter="15.85" shdwmapfov="90" shdwmapres="8" />
<State id="Scene-Game" name="Game" playmode="PingPong" >
<Set ref="#Scene" >
<Action id="Scene-Action" eyeball="True" triggerObject="#Scene" event="onPressureDown" targetObject="#Movement_001" handler="onMouseDown" />
<Action id="Scene-Action_001" eyeball="True" triggerObject="#Scene" event="onPressureUp" targetObject="#Movement_001" handler="onMouseUp" />
</Set>
<Set ref="#Layer" endtime="4000" />
<Set ref="#Camera" endtime="4000" />
<Set ref="#Light" castshadow="True" endtime="4000" />
<Add ref="#Player" name="Player" endtime="4000" position="-400 0 0" scale="0.5 1 0.5" sourcepath="#Cube" />
<Add ref="#Material" diffusemap="#Material_diffusemap" emissivecolor="1 1 1" emissivepower="0" />
<Add ref="#Material_diffusemap" sourcepath="maps\Wood7.png" subpresentation="" />
<Add ref="#Movement_001" name="Movement" endtime="4000" obstacle1="#Ball" speed="300" />
<Add ref="#Computer" name="Computer" endtime="4000" position="400 0 0" scale="0.5 1 0.5" sourcepath="#Cube" />
<Add ref="#Material_002" diffusemap="#Material_002_diffusemap" />
<Add ref="#Material_002_diffusemap" sourcepath="maps\Wood7.png" />
<Add ref="#Movement_003" name="Movement" endtime="4000" hasAI="True" obstacle1="#Ball" speed="300" />
<Add ref="#Ball" name="Ball" endtime="4000" position="0 0 0" scale="0.5 0.5 0.5" sourcepath="#Sphere" />
<Add ref="#Material_001" diffusemap="#Material_001_diffusemap" />
<Add ref="#Material_001_diffusemap" sourcepath="maps\Wood7.png" />
<Add ref="#Movement_002" name="Movement" endtime="4000" hasPhysics="True" obstacle1="#Player" obstacle2="#Computer" >
<Action id="Movement-Action" eyeball="True" triggerObject="#Movement_002" event="onPlayerScore" targetObject="#Score_002" handler="addPlayerScore" />
<Action id="Movement-Action_001" eyeball="True" triggerObject="#Movement_002" event="onComputerScore" targetObject="#Score_002" handler="addComputerScore" />
</Add>
<Add ref="#Cube" name="WallTop" endtime="4000" position="-2.59808 327.598 -2.59808" scale="20 0.1 1" sourcepath="#Cube" />
<Add ref="#Material_003" diffusemap="#Material_003_diffusemap" />
<Add ref="#Material_003_diffusemap" sourcepath="maps\Wood7.png" />
<Add ref="#Cube2" name="WallBottom" endtime="4000" position="0 -325 0" scale="20 0.1 1" sourcepath="#Cube" />
<Add ref="#Material_004" diffusemap="#Material_004_diffusemap" />
<Add ref="#Material_004_diffusemap" sourcepath="maps\Wood7.png" />
<Add ref="#Score" name="Score" endtime="4000" font="TitilliumWeb-Regular" leading="0" position="0 250 0" rotation="0 0 0" scale="1 1 1" size="72" textstring="0 - 0" tracking="0" />
<Add ref="#Score_002" name="Score" endtime="4000" >
<Action id="Score-Action" eyeball="True" triggerObject="#Score_002" event="onPlayerWins" targetObject="#Scene" handler="Go to Slide" >
<HandlerArgument name="Slide" type="String" argtype="Slide" value="Win" />
</Action>
<Action id="Score-Action_001" eyeball="True" triggerObject="#Score_002" event="onComputerWins" targetObject="#Scene" handler="Go to Slide" >
<HandlerArgument name="Slide" type="String" argtype="Slide" value="Lose" />
</Action>
</Add>
<Add ref="#Cube_001" name="Wall" endtime="4000" position="0 0 75" scale="20 10 1" sourcepath="#Cube" />
<Add ref="#Material_005" diffusemap="#Material_005_diffusemap" />
<Add ref="#Material_005_diffusemap" scaleu="4" scalev="2" sourcepath="maps\Cork1.png" tilingmodehorz="Tiled" tilingmodevert="Tiled" />
</State>
<State id="Scene-Win" name="Win" initialplaystate="Play" playmode="Stop at end" playthroughto="Previous" >
<Set ref="#Scene" >
<Action id="Scene-Action_002" eyeball="True" triggerObject="#Scene" event="onPressureDown" targetObject="#Scene" handler="Go to Slide" >
<HandlerArgument name="Slide" type="String" argtype="Slide" value="Game" />
</Action>
</Set>
<Set ref="#Layer" endtime="4000" />
<Set ref="#Camera" endtime="4000" />
<Set ref="#Light" castshadow="True" endtime="4000" />
<Add ref="#Text" name="Text" endtime="4000" font="TitilliumWeb-Regular" pivot="0 0 0" scale="1 1 1" size="72" textstring="You win!" />
</State>
<State id="Scene-Lose" name="Lose" playthroughto="Previous" >
<Set ref="#Scene" >
<Action id="Scene-Action_003" eyeball="True" triggerObject="#Scene" event="onPressureDown" targetObject="#Scene" handler="Go to Slide" >
<HandlerArgument name="Slide" type="String" argtype="Slide" value="Game" />
</Action>
</Set>
<Set ref="#Layer" endtime="4000" />
<Set ref="#Camera" endtime="4000" />
<Set ref="#Light" castshadow="True" endtime="4000" />
<Add ref="#Text_001" name="Text" endtime="4000" font="TitilliumWeb-Regular" pivot="0 0 0" scale="1 1 1" size="72" textstring="You lose!" />
</State>
</State>
</Logic>
</Project>
</UIP>
# Qt 3D Studio Tennis Game Demo
This demo application is created with Qt 3D Studio.
Behaviors and actions are used to implement a tennis game. The player is pitted against a computer opponent
with a mission of getting the ball behind the opponent to score. Whoever gets 3 points first wins.
The paddle of the player is controlled with a mouse by clicking on the screen and moving around.
Two custom behaviors are used: Movement.qml and Score.qml.
Movement.qml is the most complex script used. It handles the movement of the player, AI and the ball.
It includes collision detection and handles score detection and ball serving also.
Properties that can be set from the studio for Movement.qml are:
- Obstacle 1: Object that can be collided with
- Obstacle 2: Another object that can be collided with
- Has Physics: Determines if the object is handled as a ball
- Has AI: Determines if the object is handled as the AI paddle
The important handlers for Movement.qml are "onMouseDown" and "onMouseUp".
The Scene object in the presentation includes actions that listen for mouse input to the screen.
If such events occur the functions "onMouseDown" and "onMouseUp" from Movement.qml are called.
The script can then use the inputs to move the player around.
Movement.qml can also output two events: "onPlayerScore" and "onComputerScore".
The Movement script for the ball includes two actions in the studio that listen for these events.
When one side scores the functions that increment scores in the Score script, owned by the score text object, are called.
Score.qml itself also outputs two events: "onPlayerWins" and "onComputerWins".
The Score scripts increments the scores and, if one score is equal to 3, fires the win event for the owner of that score.
The actions that listen for the win events then tell the Scene object to jump to a victory or a defeat slide.
\ No newline at end of file
/*[[
<Property name="obstacle1" formalName="Obstacle 1" type="ObjectRef" />
<Property name="obstacle2" formalName="Obstacle 2" type="ObjectRef" />
<Property name="hasPhysics" formalName="Has Physics?" animatable="False" type="Boolean" default="False" />
<Property name="hasAI" formalName="Has AI?" animatable="False" type="Boolean" default="False" />
<Property name="speed" formalName="Speed" animatable="False" type="Float" default="300" />
<Handler name="onMouseDown" formalName="On Mouse Down" />
<Handler name="onMouseUp" formalName="On Mouse Up" />
<Handler name="moveUp" formalName="Move Up" />
<Handler name="moveDown" formalName="Move Down" />
<Handler name="moveLeft" formalName="Move Left" />
<Handler name="moveRight" formalName="Move Right" />
<Handler name="stopHorizontal" formalName="Stop Horizontal" />
<Handler name="stopVertical" formalName="Stop Vertical" />
<Event name="onPlayerScore" />
<Event name="onComputerScore" />
]]*/
import QtStudio3D.Behavior 1.0
Behavior {
//Properties exposed to the studio
property var obstacle1
property var obstacle2
property var hasPhysics
property var hasAI
property var speed
//Properties used as global variables
property var mouseDown: false
property var input: Qt.vector3d(0, 0, 0);
property var direction: Qt.vector3d(-1, Math.random() * 2 - 1, 0);
property var physicsSpeed
property var serveTimer: 1
onInitialize: {
//Only the ball speed has to be reset during initalization
//since the ball movement speeds up during gameplay
physicsSpeed = speed;
}
onUpdate: {
//Pause the ball movement before the serve
if (hasPhysics && serveTimer > 0) {
serveTimer -= getDeltaTime();
return;
}
//Get the position of the script owner
var transform = calculateGlobalTransform();
var position = transform.row(3).toVector3d();
//Check if ball is out of bounds
var levelWidth = 600;
if (position.x > levelWidth)
fireEvent("onPlayerScore");
else if (position.x < -levelWidth)
fireEvent("onComputerScore");
if (position.x < -levelWidth || position.x > levelWidth) {
//Reset properties if out of bounds
setAttribute("position.x", 0);
setAttribute("position.y", 0);
transform = calculateGlobalTransform();
position = transform.row(3).toVector3d();
direction.y = Math.random() * 2 - 1;
physicsSpeed = speed;
serveTimer = 1;
return;
}
if (mouseDown) {
//Handle movement with mouse input
var perspectiveFactor = 1.4;
var halfScreenHeight = 240;
var target = (halfScreenHeight - getMousePosition().y) * perspectiveFactor;
if (Math.abs(position.y - target) > 6) {
if (position.y > target)
moveDown();
else
moveUp();
} else {
stopVertical();
}
} else {
stopVertical();
}
var next = Qt.vector3d(position.x, position.y, position.z);
direction = direction.normalized();
if (hasAI) {
//Handle AI movement input
var obstacleTransform = calculateGlobalTransform(obstacle1);
var obstaclePosition = obstacleTransform.row(3).toVector3d();
if (Math.abs(obstaclePosition.y - position.y) > 25)
input.y = Math.sign(obstaclePosition.y - position.y);
else
input.y = 0;
}
//Separate collision solving to X and Y axis
//Even if a collision happens on the X axis one can move along the Y axis and vice versa
var canMoveX = true;
var canMoveY = true;
if (hasPhysics) {
//Handle ball movement
next.x += direction.x * physicsSpeed * getDeltaTime();
next.y += direction.y * physicsSpeed * getDeltaTime();
if (next.y > 300 || next.y < -300)
canMoveY = false;
} else {
//Handle paddle movement
next.y += input.y * speed * getDeltaTime();
}
//Check collisions against two objects
var collisions = [collidesWith(obstacle1, position, next),
collidesWith(obstacle2, position, next)];
var collisionPosition;
for (var i = 0; i < collisions.length; ++i) {
if (collisions[i].x || collisions[i].y)
collisionPosition = collisions[i].position;
if (collisions[i].x)
canMoveX = false;
if (collisions[i].y)
canMoveY = false;
}
if (canMoveX) {
setAttribute("position.x", next.x);
} else {
//Change ball direction and the angle if collision happens
if (hasPhysics) {
direction.x = -direction.x;
direction.y = -Math.sin(Math.atan2(collisionPosition.y - position.y,
collisionPosition.x - position.x));
physicsSpeed += 20;
}
}
if (canMoveY) {
setAttribute("position.y", next.y);
}
else {
if (hasPhysics)
direction.y = -direction.y;
}
}
function collidesWith(obstacle, position, next) {
if (obstacle === "")
return {x: false, y: false};
//Get obstacle position
var obstacleTransform = calculateGlobalTransform(obstacle);
var obstaclePosition = obstacleTransform.row(3).toVector3d();
var ballSize = 50;
var paddleWidth = 50;
var paddleHeight = 100;
var xCollides = true;
var yCollides = true;
//Rectangular collision check
//All collisions are between the ball and a paddle so sizes can be hard-coded
if (next.x + paddleWidth / 2 < obstaclePosition.x - ballSize / 2
|| next.x - paddleWidth / 2 > obstaclePosition.x + ballSize / 2
|| position.y + paddleHeight / 2 < obstaclePosition.y - ballSize / 2
|| position.y - paddleHeight / 2 > obstaclePosition.y + ballSize / 2) {
xCollides = false;
}
if (position.x + paddleWidth / 2 < obstaclePosition.x - ballSize / 2
|| position.x - paddleWidth / 2 > obstaclePosition.x + ballSize / 2
|| next.y + paddleHeight / 2 < obstaclePosition.y - ballSize / 2
|| next.y - paddleHeight / 2 > obstaclePosition.y + ballSize / 2) {
yCollides = false;
}
return {position: obstaclePosition, x: xCollides, y: yCollides};
}
function onMouseDown() {
mouseDown = true;
}
function onMouseUp() {
mouseDown = false;
}
function moveUp() {
input.y = 1;
}
function moveDown() {
input.y = -1;
}
function moveLeft() {
input.x = -1;
}
function moveRight() {
input.x = 1;
}
function stopHorizontal() {
input.x = 0;
}
function stopVertical() {
input.y = 0;
}
}
/*[[
<Handler name="addPlayerScore" formalName="Add Player Score" />
<Handler name="addComputerScore" formalName="Add Computer Score" />
<Handler name="resetScores" formalName="Reset Scores" />
<Event name="onPlayerWins" />
<Event name="onComputerWins"/>
]]*/
import QtStudio3D.Behavior 1.0
Behavior {
property var playerScore: 0
property var computerScore: 0
onUpdate: {
setAttribute("textstring", playerScore + " - " + computerScore);
}
function addPlayerScore() {
playerScore++;
if (playerScore >= 3)
fireEvent("onPlayerWins");
}
function addComputerScore() {
computerScore++;
if (computerScore >= 3)
fireEvent("onComputerWins");
}
function resetScores() {
playerScore = 0;
computerScore = 0;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment