\documentclass{article}


\usepackage[T1]{fontenc} % encodage
\renewcommand{\familydefault}{\sfdefault} % police en sans-serif

\usepackage[french]{babel} % langue
\frenchsetup{SmallCapsFigTabCaptions=false}

\usepackage[hidelinks]{hyperref} % liens cliquable dans la table des matières

\usepackage{graphicx} % images
\usepackage{caption}

\usepackage[a4paper, left=20mm, top=20mm]{geometry} % dimensions de la page

\usepackage{minted} % intégration code
\usemintedstyle{emacs}

\title{Projet - GeometryDash 3D}
\author{\href{mailto:anri.kennel@etud.univ-paris8.fr}{Anri Kennel}
    \thanks{Numéro d'étudiant : 20010664}\, (L3-A)
    \\Moteurs de jeu $\cdot$ Université Paris 8}
\date{Année universitaire 2022-2023}

\begin{document}
\maketitle
\tableofcontents
\clearpage

\section[Projet]{Explication du projet}
Le but du projet est de créer un jeu du type
\href{https://fr.wikipedia.org/wiki/Geometry_Dash}{Geometry Dash} en vue 3D.
Geometry Dash est un jeu de plateforme dans lequel le joueur dirige un cube
glissant sur le sol et qui doit sauter pour éviter des obstacles.

Le but de \textbf{ce} jeu est de faire le plus gros score, chaque seconde
passée dans le jeu ajoute 1 point au compteur, le score étant affiché en haut de l'écran.

\section[Rendu 3D]{Rendu 3D avec THREE.JS}
La \autoref{fig:expls} schématise grossièrement comment est rendu le jeu.

\begin{figure}[h]
    \centering
    \includegraphics[width=0.8\textwidth]{presentation/imgs/explications.png}
    \caption{Explications du rendu d'une scène}
    \label{fig:expls}
\end{figure}

L'environnement du jeu s'occupe de tout. Elle garde en mémoire la liste des
éléments présents dans le jeu :
\begin{itemize}
    \item Joueur (le cube)
    \item Ennemis (les piques)
\end{itemize}

Elle lance l'animation de ces derniers chacun son tour dans la fonction
\texttt{animate}, qui est appelée juste avant de rendre la scène (cf. \autoref{cod:envupdate}).

\begin{figure}[h]
    \centering
    \begin{minipage}{0.3\textwidth}
        \begin{minted}[autogobble,linenos]{js}
            update = (demo) => {
                if (this.animate(demo)) {
                    return true;
                }
                this.render();

                return false;
            };
        \end{minted}
    \end{minipage}
    \caption{fonction \texttt{update}, \texttt{Env.js}}
    \label{cod:envupdate}
\end{figure}


\section[Logique]{Logique du jeu}
Lorsque l'on joue, on a l'impression que le personnage avance sur un plan
infini. Ce n'est pas exactement ce qui se passe.

Le joueur est un cube fixe, il ne fait que sauté et tourné sur lui-même.
Le plan est fixe, ce qui bouge sont les piques ennemis.

\subsection[Génération procédurale]{Génération du terrain}
Le sol est un plan simple sur lequel une texture en dégradé est appliquée,
du blanc (proche de la caméra) au noir. Il y a une lumière pour appuyer l'effet
3D de la scène (il y a donc les ombres des objets projetés sur le sol).

Le monde est généré via la fonction \texttt{generateRandomMap} de la classe
\texttt{Env}. Elle permet de générer $n$ obstacles pour le joueur placés devant
le joueur. Quand les obstacles passent derrière le joueur et sortent de l'écran,
ils sont téléportés devant le joueur en étant déplacé, ils ne sont pas justes
replacés à leur position initiale, créant une carte infinie. La taille du pique est choisie de façon aléatoire.

\begin{figure}[h]
    \centering
    \begin{minipage}{0.7\textwidth}
        \begin{minted}[autogobble,linenos]{js}
            const spade = new Spade(
                Math.random() * 0xffffff,
                Math.round(Math.random()) ? Size.little : Size.big,
                startDelta + (index - 1) * 10
            );
        \end{minted}
        \dots lors de l'animation des piques\dots
        \begin{minted}[autogobble,linenos,firstnumber=last]{js}
            if (ennemy.data.position.x <= -10) {
                ennemy.data.position.x = ennemy.startPos + Math.random() * 20;
            }
        \end{minted}
    \end{minipage}
    \caption{Création d'un pique et retéléportation lors de la sortie de l'écran, \texttt{Env.js}}
    \label{cod:spade}
\end{figure}

\subsection{Saut du joueur}
Dans le cas d'un joueur humain, on écoute via \texttt{addEventListener}
l'événement de la touche \textit{espace} pressée. Dans le cas de la démo, on utilise
un évènement interne \textit{nommé \texttt{jumpKey}}.

Les deux événements sont enregistrés et lanceront la fonction de saut.

\begin{figure}[h]
    \centering
    \begin{minipage}{0.7\textwidth}
        \begin{minted}[autogobble,linenos]{js}
            if (demo) {
                addEventListener("jumpKey", (e) => player.controlUser(e.detail));
            } else {
                addEventListener("keypress", player.controlUser);
            }
        \end{minted}
    \end{minipage}
    \caption{Enregistrement des événements de saut, \texttt{Game.js}}
    \label{cod:eventjump}
\end{figure}

La fonction \texttt{controlUser} définit le point de saut maximal ainsi que la
rotation du joueur finale.

\begin{figure}[h]
    \centering
    \begin{minipage}{0.7\textwidth}
        \begin{minted}[autogobble,linenos]{js}
            controlUser = (key) => {
                if (key.code == "Space" && !this.movementData.state) {
                    this.movementData.changeRotation(
                        this.data.rotation.y - Math.PI / 2
                    );
                    this.movementData.changeJump(
                        3 * this.data.position.y + Math.PI / 2
                    );
                    this.movementData.changeState();
                }
            };
        \end{minted}
    \end{minipage}
    \caption{Définition du saut max et de la rotation finale, \texttt{Player.js}}
    \label{cod:defjumprot}
\end{figure}

Les coordonnées de transition (le saut et la descente) ainsi que l'angle sont calculées lors de l'animation du joueur, dans la fonction \texttt{animation} de la
classe \texttt{Player}.

\subsubsection[Démo]{Cas de la démonstration}
On vérifie si un ennemi est devant lors de l'animation du joueur, si c'est le
cas alors on saute.

\begin{figure}[h]
    \centering
    \begin{minipage}{0.7\textwidth}
        \begin{minted}[autogobble,linenos]{js}
            if (demo) {
                listEnnemies.forEach((ennemy) => {
                    const pos = ennemy.position.x - joueur.position.x;
                    // Aléatoire entre 0.5 (proche du pique) et 2 (loin du pique)
                    if (pos > 0 && pos < Math.random() * 1.5 + 0.5) {
                        // Événement "jumpKey"
                        dispatchEvent(jumpDemo);
                    }
                });
            }
        \end{minted}
    \end{minipage}
    \caption{Vérification IA : saut automatique, \texttt{Player.js}}
    \label{cod:ia}
\end{figure}

\subsection{Collisions}
À chaque frame du jeu, le joueur vérifie qu'il n'entre pas en collision avec
les ennemies via du
\href{https://threejs.org/docs/#api/en/core/Raycaster}{ray casting}.

\begin{figure}[h]
    \centering
    \begin{minipage}{0.7\textwidth}
        \begin{minted}[autogobble,linenos]{js}
            const rc = new THREE.Raycaster(
                this.data.position,
                directionVector.normalize()
            );

            // Contient les éléments en collisions avec le joueur
            const collisionResults = rc.intersectObjects(listEnnemies, false);
            if (
                collisionResults.length > 0 &&
                collisionResults[0].distance < directionVector.length()
            ) {
                return true;
            }
            return false;
        \end{minted}
    \end{minipage}
    \caption{Vérification des collisions, \texttt{Player.js}}
    \label{cod:raycast}
\end{figure}

Avec le \texttt{raycaster}, je vérifie les intersections des objets, donc il faut
impérativement, pour éviter tout problème de collisions, que mon joueur soit moins
large que mes piques. C'est pour cela que mes piques, petit ou grand, ont la même
largeur.

\subsection{Paramètres}
Pour rendre le jeu jouable sur n'importe quel ordinateur, en plus de baser le
taux d'actualisation sur le temps et non sur la vitesse du PC, il y a un
mini-menu en haut à droite pour changer la qualité de rendu du jeu en temps réel.

\subsection{Audio}
Le son est joué au début du jeu et arrêter quand le jeu s'arrête.

\subsection{Fin de jeu}
Le jeu se termine dès que le joueur entre en collision avec un ennemi. Quand
c'est le cas, un menu de fin apparaît au-dessus du jeu, invitant le joueur à
rejouer. Peu importe si c'est une démo ou non.

\newpage
\appendix
\section*{Appendix}
\listoffigures

\end{document}
