Crea la tua prima Avventura Punta & Clicca
Prima di proseguire, è importante considerare che questa pagina presuppone i concetti spiegati in "Crea la tua prima Visual Novel" e che tutte le funzionalità descritte in quella pagina possono essere applicate anche qui. Inoltre, è essenziale possedere una conoscenza intermedia di JS/TS (come funzioni, classi, interfacce e utilizzo di file .d.ts).
Non proseguire se non possiedi queste conoscenze.
Questo tutorial ti guiderà attraverso il processo di creazione della tua prima Avventura Punta & Clicca.
Cos'è un'avventura punta e clicca (Point & Click Adventure)? Un'avventura punta e clicca è un genere di gioco in cui il giocatore interagisce con l'ambiente e gli oggetti tramite il puntamento e il clic del mouse. Questi giochi spesso presentano enigmi, dialoghi e una narrazione avvincente.
How to implement it in a Pixi’VN project? The Pixi’VN environment provides a utility library and base models, called NQTR (Navigation Quest Time Routine), which allow you to easily implement a navigation system, time management, and quests. Il funzionamento di NQTR si basa sulla creazione di istanze di classi conformi a interfacce specifiche, che verranno registrate e fornite dalle utility di NQTR. In questo modo è possibile creare un'interfaccia utente su misura per le esigenze del gioco. Alternatively, you can use the base models provided by NQTR and the UI included in the "Point & Click Adventure - React" template to create a complete game in a short time.
Additionally, it is very important to consider that elements can trigger labels (narratives), allowing you to switch to the narrative interface and back.
Crea un nuovo progetto
Il primo passo è creare un nuovo progetto. Puoi trovare maggiori informazioni su come creare un nuovo progetto partendo da un template qui. We will use the template "Point & Click Adventure - React".
Point & Click Adventure -> React
Una volta completata la creazione, è molto importante leggere il file README.md che si trova nella cartella principale del progetto. Questo file contiene informazioni importanti sul progetto e su come utilizzarlo.
Nel nostro caso, per avviare il progetto dovremo semplicemente eseguire i seguenti comandi:
npm install
npm startAggiungi elementi cliccabili
La caratteristica principale di un'avventura punta e clicca è la possibilità di interagire con l'ambiente tramite clic del mouse. Per aggiungere elementi cliccabili, si consiglia di utilizzare un'interfaccia utente (nel nostro caso React o PixiJS) piuttosto che del canvas di Pixi'VN, poiché è più facile gestire eventi e interazioni.
Nel template che stiamo utilizzando, durante la creazione degli elementi che vedremo in seguito, è possibile definire elementi grafici (come un'icona). Questi elementi grafici possono essere definiti utilizzando diversi tipi; in base a ciò, il sistema aggiungerà l'elemento grafico all'interfaccia utente PixiJS o all'interfaccia utente React. In questo modo è possibile creare elementi cliccabili in modo semplice e veloce.
Usually, it is possible to define a graphical element as a string representing the asset alias. Il template offre anche la possibilità di utilizzare la classe TimeSlotsImage, che consente di definire un'immagine diversa a seconda dell'ora del giorno.
export const nightcityMap = new Map("nightcity_map", {
name: "Nightcity",
background: "map-nightcity",
neighboringMaps: {
south: "main_map",
},
});Solitamente, è possibile definire un elemento grafico come un elemento React.
export const bed = new Activity(
"bed",
async (_, props) => {
await props.navigate(NARRATION_ROUTE);
if (timeTracker.nowIsBetween(5, 22)) {
await narration.jump(napLabel, props);
} else {
await narration.jump(sleepLabel, props);
}
},
{
name: "bed",
icon: (activity, props) => {
return (
<NqtrRoundIconButton
disabled={activity.disabled}
onClick={() => {
activity.run(props).then(() => {
props.invalidateInterfaceData();
});
}}
ariaLabel={props.uiTransition(activity.name)}
variant='solid'
color='primary'
<BedIcon
sx={{
fontSize: { sx: "1.5rem", sm: "2rem", md: "2.5rem", lg: "3rem", xl: "3.5rem" },
}}
</NqtrRoundIconButton>
);
},
}
);Inoltre, quando la proprietà sprite è disponibile, è possibile definire un elemento grafico come elemento PixiJS.
import { Assets, Sprite, Texture } from "@drincs/pixi-vn/pixi.js";
export const mcHome = new Location("mc_home", mainMap, {
name: "MC Home",
sprite: async (location, { navigate }) => {
const texture = await Assets.load<Texture>("icon_location_home");
const icon = new Sprite({
texture,
x: 300,
y: 200,
height: 120,
width: 120,
eventMode: "static",
cursor: "pointer",
});
icon.on("pointerdown", () => {
const entrance = location.entrance;
if (entrance) {
navigator.currentRoom = entrance;
navigate(NAVIGATION_ROUTE);
}
});
return icon;
},
});Navigazione e mappa
Puoi trovare una documentazione più dettagliata su questi elementi qui.
Il primo passo per creare un'avventura punta e clicca è creare gli elementi di navigazione. Questi elementi permettono al giocatore di muoversi all'interno del gioco e di interagire con l'ambiente.
Il sistema di navigazione è composto dai seguenti elementi:
rooms: Gli elementi fondamentali della navigazione, da cui si deduce la posizione dimcenpc.locations: Un contenitore distanze.maps: Un contenitore diluoghi.
Ad esempio:
import { RegisteredRooms } from "@drincs/nqtr";
import TimeSlotsImage from "../models/TimeSlotsImage";
import Room from "../models/nqtr/Room";
import { bed } from "./activities";
import { mcHome } from "./locations";
export const mcRoom = new Room("mc_room", mcHome, {
name: "MC room",
background: new TimeSlotsImage({
morning: "location_myroom-0",
afternoon: "location_myroom-1",
evening: "location_myroom-2",
night: "location_myroom-3",
}),
activities: [bed],
});
RegisteredRooms.add([mcRoom]);import { navigator, RegisteredLocations } from "@drincs/nqtr";
import { Assets, Sprite, Texture } from "@drincs/pixi-vn/pixi.js";
import { NAVIGATION_ROUTE } from "../constans";
import Location from "../models/nqtr/Location";
import { mainMap } from "./maps";
export const mcHome = new Location("mc_home", mainMap, {
name: "MC Home",
sprite: async (location, { navigate }) => {
const texture = await Assets.load<Texture>("icon_location_home");
const icon = new Sprite({
texture,
x: 300,
y: 200,
height: 120,
width: 120,
eventMode: "static",
cursor: "pointer",
});
icon.on("pointerdown", () => {
const entrance = location.entrance;
if (entrance) {
navigator.currentRoom = entrance;
navigate(NAVIGATION_ROUTE);
}
});
return icon;
},
});
RegisteredLocations.add([mcHome]);import { RegisteredMaps } from "@drincs/nqtr";
import TimeSlotsImage from "../models/TimeSlotsImage";
import Map from "../models/nqtr/Map";
export const mainMap = new Map("main_map", {
name: "Main Map",
background: new TimeSlotsImage({
morning: "map-0",
afternoon: "map-1",
evening: "map-2",
night: "map-3",
}),
neighboringMaps: {
north: "nightcity_map",
},
});
RegisteredMaps.add([mainMap]);Now, before you can use the navigation utilities, you need to set the current room. After that, it is possible to navigate to the navigation screen at the start of the game or after a short initial narrative.
Additionally, it is important to keep in mind that the current location and map are deduced from the current room, so it is sufficient to set the room to obtain all the necessary information for navigation.
You can find more detailed documentation on how to navigate between UI screens here.
Ad esempio:
=== start ===
# navigate /narration
Hello, welcome to the my first game!
# enter room mc_room
# navigate /navigation
-> DONETime system
You can find more detailed documentation about these elements here.
Now let's move on to another fundamental element: the time management system. The time management system is a key component in a Point & Click Adventure, as it allows you to create a dynamic and realistic world where the player's actions can have different consequences depending on the time of day.
Before you can use the time management features, it is necessary to initialize the timeTracker with the information required for the system to function. As shown in the following example, it is possible to define every aspect of the time management system, such as time slots, days of the week, the start and end time of the day, and so on.
import { timeTracker } from "@drincs/nqtr";
import { timeSlots } from "../constans";
const weekDaysNames = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"];
export function initializeNQTR() {
timeTracker.initialize({
defaultTimeSpent: 1,
dayStartTime: 0,
dayEndTime: 24,
timeSlots: [
{ name: timeSlots.morning.description, startTime: timeSlots.morning.value },
{ name: timeSlots.afternoon.description, startTime: timeSlots.afternoon.value },
{ name: timeSlots.evening.description, startTime: timeSlots.evening.value },
{ name: timeSlots.night.description, startTime: timeSlots.night.value },
],
getDayName: (weekDayNumber: number) => {
return weekDaysNames[weekDayNumber];
},
weekendStartDay: 6,
weekLength: 7,
});
}The main functionality of the time management system is the ability to advance time based on the player's actions.
// {timeValue} is a number representing the amount of time to increment
// {dateValue} is a number representing the amount of days to increment
# wait {timeValue} // Increment time by {value}
# wait // Increment time by defaultTimeSpent
# wait days {dateValue} // Increment date by {dateValue} and set time to dayStartTime
# wait days {dateValue} hours {timeValue} // Increment date by {dateValue} and time by {timeValue}Activity
You can find more detailed documentation about these elements here.
A fundamental element in Point & Click Adventures is interactive elements, which allow the player to interact with the environment and progress the story. In NQTR, these elements are represented by activity. activity are elements that can be executed by the player and can have different consequences depending on the context in which they are performed.
Ad esempio:
import { RegisteredActivities, timeTracker } from "@drincs/nqtr";
import { narration } from "@drincs/pixi-vn";
import BedIcon from "@mui/icons-material/Bed";
import NqtrRoundIconButton from "../components/NqtrRoundIconButton";
import { NARRATION_ROUTE } from "../constans";
import { napLabel, sleepLabel } from "../labels/sleepNapLabels";
import Activity from "../models/nqtr/Activity";
export const bed = new Activity(
"bed",
async (_, props) => {
await props.navigate(NARRATION_ROUTE);
if (timeTracker.nowIsBetween(5, 22)) {
await narration.jump(napLabel, props);
} else {
await narration.jump(sleepLabel, props);
}
},
{
name: "bed",
icon: (activity, props) => {
return (
<NqtrRoundIconButton
disabled={activity.disabled}
onClick={() => {
activity.run(props).then(() => {
props.invalidateInterfaceData();
});
}}
ariaLabel={props.uiTransition(activity.name)}
variant='solid'
color='primary'
>
<BedIcon
sx={{
fontSize: { sx: "1.5rem", sm: "2rem", md: "2.5rem", lg: "3rem", xl: "3.5rem" },
}}
/>
</NqtrRoundIconButton>
);
},
},
);
RegisteredActivities.add([bed]);Activities are connected to rooms in a many-to-many relationship, meaning that an activity can be present in multiple rooms and a room can contain multiple activities.
To use them, you must link them to a room through the activities property. This way, when the player is in that room, they will be able to see the activity and interact with it.
Alternatively, during the narrative session, it is possible to add an activity to a room using the addActivity function.
Ad esempio:
== start ==
# add activity bed in mc_roomRoutine
You can find more detailed documentation about these elements here.
Routine refers to the set of elements that make up the daily routine of non-player characters (NPC).
The routine is composed of commitment, which represent a character’s obligation to perform a specific activity at a certain moment of the day. These commitments are very similar to activity, as they represent an activity that the character performs.
Ad esempio:
import { RegisteredCommitments } from "@drincs/nqtr";
import { narration } from "@drincs/pixi-vn";
import QuestionAnswerIcon from "@mui/icons-material/QuestionAnswer";
import NqtrRoundIconButton from "../components/NqtrRoundIconButton";
import { NARRATION_ROUTE } from "../constans";
import { TALK_SLEEP_LABEL_KEY } from "../labels/variousActionsLabelKeys";
import Commitment from "../models/nqtr/Commitment";
import { alice } from "./characters";
export const aliceSleep = new Commitment("alice_sleep", alice, {
priority: 1,
timeSlot: {
from: 20,
to: 10,
},
background: "alice_roomsleep0A",
icon: (commitment, props) => {
return (
<NqtrRoundIconButton
disabled={commitment.disabled}
onClick={() => {
if (commitment.run) {
commitment.run(props);
}
}}
ariaLabel={commitment.name}
variant='solid'
color='primary'
>
<QuestionAnswerIcon
sx={{
fontSize: { sx: "1.5rem", sm: "2rem", md: "2.5rem", lg: "3rem", xl: "3.5rem" },
}}
/>
</NqtrRoundIconButton>
);
},
onRun: async (_, event) => {
await event.navigate(NARRATION_ROUTE);
await narration.jump(TALK_SLEEP_LABEL_KEY, event);
},
});
RegisteredCommitments.add([aliceSleep]);Commitments are linked to rooms in a one-to-many relationship, meaning that a room can have multiple commitments, but a commitment is present in only one room (a character cannot be in multiple rooms at the same time).
To use them, you need to link them to a room through the commitments property. This will also set the NPC positions, as the position of a commitment is deduced from the room it is linked to. When the player is in that room, they will see the active commitments and be able to interact with them.
Alternatively, during the narrative session, it is possible to add a commitment to a room using the addCommitment function.
Ad esempio:
== start ==
# add routine alice_sleep in alice_roomQuests
You can find more detailed documentation about these elements here.
Quest represent an objective or a series of objectives that the player can complete during the game. Quests are composed of stage, which represent the different phases of a quest. Each stage has a specific objective and can be completed in different ways depending on the player's choices.
Ad esempio:
import { RegisteredCommitments, RegisteredQuests, routine } from "@drincs/nqtr";
import { narration } from "@drincs/pixi-vn";
import { NARRATION_ROUTE } from "../../constans";
import { TALK_ALICE_QUEST_KEY } from "../../labels/variousActionsLabelKeys";
import TimeSlotsImage from "../../models/TimeSlotsImage";
import Commitment from "../../models/nqtr/Commitment";
import Quest from "../../models/nqtr/Quest";
import Stage from "../../models/nqtr/Stage";
import { orderProduct, takeProduct } from "../activities";
import { alice } from "../characters";
import { mcRoom, terrace } from "../rooms";
export const aliceQuest = new Quest(
"aliceQuest",
[
// stages
new Stage("talk_alice1", {
onStart: () => {
terrace.addCommitment(aliceQuest_talk);
},
name: "Talk to Alice",
description: "Talk to Alice on the terrace",
}),
new Stage("order_products", {
onStart: () => {
mcRoom.addActivity(orderProduct);
},
name: "Order products",
description: "Order the products with your PC",
}),
new Stage("take_products", {
onStart: (_, { notify }) => {
terrace.addActivity(takeProduct);
notify("You can take the products on the Terrace");
},
name: "Take products",
description: "Take products on the Terrace",
requestDescriptionToStart: "Wait for the products you ordered to arrive (2 day)",
deltaDateRequired: 2,
}),
new Stage("talk_alice2", {
name: "Talk to Alice",
description: "Talk to Alice on the terrace",
}),
],
{
// props
name: "Help Alice",
description:
'To learn more about how the repo works, Talk to Alice. \nGoing when she is there will automatically start an "Event" (see aliceQuest.tsx to learn more). \nAfter that an action will be added to open the pc, in MC room. \n\n(during the quest you can talk to Alice and you will see her talking during the quests of the same Quest)',
image: "alice_terrace0A",
onStart: (quest, { notify, uiTransition }) => {
notify(uiTransition("notify_quest_is_started", { quest: quest.name }));
},
onContinue: (stage, { notify, uiTransition }) => {
notify(uiTransition("notify_quest_is_updated", { quest: stage.name }));
},
},
);
RegisteredQuests.add(aliceQuest);
const aliceQuest_talk = new Commitment("alice_quest_talk", alice, {
timeSlot: {
from: 10,
to: 20,
},
image: new TimeSlotsImage("alice_terrace0A"),
executionType: "automatic",
priority: 1,
onRun: async (_, props) => {
await props.navigate(NARRATION_ROUTE);
await narration.jump(TALK_ALICE_QUEST_KEY, props);
routine.remove(aliceQuest_talk);
},
});
RegisteredCommitments.add(aliceQuest_talk);To start a quest, you need to call the start function on the quest itself. This will begin the first stage of the quest, allowing the player to start completing its objectives.
Ad esempio:
== start ==
# start quest aliceQuestTo continue a quest that has already been started, you need to call the continue function on the quest itself. This will complete the current stage of the quest and move on to the next one.
Ad esempio:
== start ==
# continue quest aliceQuestDuring gameplay or narrative sequences, it is very useful to perform checks on the state of a quest, such as verifying whether a quest has been completed or if a specific stage has been reached. To do this, you can use various query functions available in the Quest class, such as completed or failed.
You can find a complete list of query functions in the Quest class documentation here.
Ad esempio:
=== talkAliceQuest ===
# show image background alice_terrace0At
{ aliceQuest_currentStageIndex:
- 0: -> talkAliceQuest0
- 1: -> talkAliceQuest1
- 2: -> talkAliceQuest2
- else: alice: Thanks for the book.
}
-> DONE
= talkAliceQuest0
alice: Hi, can you order me a new book from pc?
mc: Ok
alice: Thanks
# continue quest aliceQuest
-> DONE
= talkAliceQuest1
mc: What book do you want me to order?
alice: For me it is the same.
-> DONE
= talkAliceQuest2
mc: I ordered the Book, hope you enjoy it.
alice: Great, when it arrives remember to bring it to me.
-> DONE
= talkAliceQuest3
mc: Here's your book.
alice: Thank you, I can finally read something new.
# continue quest aliceQuest
-> DONEConclusione
Now you know the basics for creating a Point & Click Adventure with Pixi’VN and NQTR. So far, we have only looked at the components provided by the template, but it is possible to create custom components to adapt them to the needs of your game. If you have some experience with JS/TS, I’m confident that by modifying the .d.ts files and creating classes that implement NQTR interfaces, you will be able to create custom components quickly and easily.
Make me proud by creating an amazing Point & Click Adventure with Pixi’VN and NQTR! 🚀
Now, you can try a small Point & Click Adventure created with the template to see in practice how the components work and interact with each other.