This commit is contained in:
Vasyl Kazakov
2026-05-25 16:42:52 +03:00
commit 97eb0b4410
36 changed files with 1419 additions and 0 deletions
+50
View File
@@ -0,0 +1,50 @@
import { ConfigUiParamsCategories } from "@24tools/ads_common";
export const analytics: ConfigUiParamsCategories[] = [
{
name: "Analytics params",
id: "analytics_params",
params: [
{
id: "bite_brew",
name: "Byte Brew",
type: "group",
params: [
{
name: "Enabled",
id: "isAnalyticsBB_Enabled",
type: "bool",
values: [false],
},
{
name: "App ID",
id: "AnalyticsBB_AppID",
type: "string",
values: [""],
uneditable: true,
dontIncludeToLocalisation: true,
disable: ["isAnalyticsBB_Enabled", "values !== true"],
},
{
name: "Key",
id: "AnalyticsBB_Key",
type: "string",
values: [""],
uneditable: true,
dontIncludeToLocalisation: true,
disable: ["isAnalyticsBB_Enabled", "values !== true"],
},
{
name: "version",
id: "AppVersion",
type: "string",
values: ["1.0.0"],
dontIncludeToLocalisation: true,
disable: ["isAnalyticsBB_Enabled", "values !== true"],
regExp: "^d+.d+.d+$",
},
],
},
],
},
];
+14
View File
@@ -0,0 +1,14 @@
import { ConfigUiParamsCategories } from "@24tools/ads_common";
import { sounds } from "./sounds";
//@ts-ignore
import { analytics } from "./analytics";
//@ts-ignore
import { installBanner } from "./installBanner";
import { globalSettings } from "./globalSettings";
export const configUIParams: ConfigUiParamsCategories[][] = [
analytics,
installBanner,
globalSettings,
sounds,
];
+144
View File
@@ -0,0 +1,144 @@
import { ConfigUiParamsCategories } from "@24tools/ads_common";
export const globalSettings: ConfigUiParamsCategories[] = [
{
id: "global",
name: "Scene settings",
params: [
{
id: "light_intensity",
name: "Light intensity",
type: "float",
values: [
0,
5,
1
]
},
{
id: "light_color",
name: "Main light color",
type: "color",
values: [
"0xFFFFFF"
]
},
{
id: "camera_fov_p",
name: "Camera FOV (portrait)",
type: "int",
values: [
30,
150,
60
]
},
{
id: "camera_position_p",
name: "Camera position (portrait)",
type: "float",
array: true,
visible: "position",
values: [
[
-10,
10,
0
],
[
-10,
10,
1
],
[
-10,
10,
5
]
]
},
{
id: "camera_rotation_p",
name: "Camera rotation (portrait)",
type: "int",
array: true,
visible: "position",
values: [
[
-360,
360,
0
],
[
-360,
360,
0
],
[
-360,
360,
0
]
]
},
{
id: "camera_fov_l",
name: "Camera FOV (landscape)",
type: "int",
values: [
30,
150,
60
]
},
{
id: "camera_position_l",
name: "Camera position (landscape)",
type: "float",
array: true,
visible: "position",
values: [
[
-10,
10,
0
],
[
-10,
10,
1
],
[
-10,
10,
5
]
]
},
{
id: "camera_rotation_l",
name: "Camera rotation (landscape)",
type: "int",
array: true,
visible: "position",
values: [
[
-360,
360,
0
],
[
-360,
360,
0
],
[
-360,
360,
0
]
]
}
]
}
]
File diff suppressed because one or more lines are too long
+35
View File
@@ -0,0 +1,35 @@
import { ConfigUiParamsCategories } from "@24tools/ads_common";
export const sounds: ConfigUiParamsCategories[] = [
{
name: "Audio Settings",
id: "sounds",
params: [
{
id: "global_volume",
name: "Global, %",
type: "int",
values: [0, 100, 80],
},
{
id: "background_head",
name: "Background",
type: "group",
params: [
{
id: "background_volume",
name: "Background, %",
type: "int",
values: [0, 100, 50],
},
{
id: "upload_background_music",
name: "Upload background music",
type: "upload_file",
file_type: "sound",
},
],
},
],
},
];
+26
View File
@@ -0,0 +1,26 @@
import { CameraC_internal, Helper, Template } from "@24tools/playable_template";
import { PerspectiveCamera } from "three";
export class CameraC extends CameraC_internal {
static setCamera(portraitOrientation: boolean) {
const CATEGORY = Template.getCategory("global");
if (this.camera !== null) {
let position = portraitOrientation
? Helper.returnVectorCamera(CATEGORY["camera_position_p"] as number[])
: Helper.returnVectorCamera(CATEGORY["camera_position_l"] as number[]);
const rotation = portraitOrientation
? Helper.returnEulerCamera(CATEGORY["camera_rotation_p"] as number[])
: Helper.returnEulerCamera(CATEGORY["camera_rotation_l"] as number[]);
this.camera.rotation.x = rotation.x;
this.camera.rotation.y = rotation.y;
this.camera.rotation.z = rotation.z;
this.camera.position.copy(position.clone());
if (this.camera instanceof PerspectiveCamera) {
this.camera.fov = portraitOrientation
? Number(CATEGORY["camera_fov_p"])
: Number(CATEGORY["camera_fov_l"]);
this.camera.updateProjectionMatrix();
}
}
}
}
+129
View File
@@ -0,0 +1,129 @@
import {
Delegate,
Physics_internal,
UpdateController,
} from "@24tools/playable_template";
import { Box3, Object3D, Vector3 } from "three";
import { Body, Box, Quaternion, Sphere, Vec3 } from "cannon-es";
export enum PhysicsLayer {
Player = 1,
Wall = 2,
Trigger = 4,
Enemy = 8,
}
export class PhysicsBody {
private body: Body;
private pair: PhysicsObjPair | null = null;
constructor(
threeObj: Object3D,
trigger: boolean,
mass: number,
col_group: PhysicsLayer,
col_mask: PhysicsLayer,
player_sphere: number = 0.3
) {
let isPlayer = col_group === PhysicsLayer.Player;
let oldQuaternion = threeObj.quaternion.clone();
let nullQuaternion = new Quaternion();
threeObj.quaternion.copy(nullQuaternion);
let bbox = new Box3().setFromObject(threeObj);
let size = new Vector3();
bbox.getSize(size);
// if you need custom size
// if (col_group === PhysicsLayer.wall) {
// size.x = size.z = 1;
// size.y = 1;
// }
threeObj.quaternion.copy(oldQuaternion);
this.body = new Body({
isTrigger: trigger,
mass: mass,
//shape: shape,
shape: isPlayer
? new Sphere(player_sphere)
: new Box(new Vec3(size.x / 2, size.y / 2, size.z / 2)),
collisionFilterGroup: col_group,
collisionFilterMask: col_mask,
});
let worldPos = threeObj.getWorldPosition(new Vector3());
this.body.position.set(worldPos.x, worldPos.y, worldPos.z);
this.body.quaternion.setFromEuler(
threeObj.rotation.x,
threeObj.rotation.y,
threeObj.rotation.z,
"XYZ"
);
// if you need sync three obj and physics body
// if (isEnemy) {
// let pair = new PhysicsObjPair(threeObj, this.body);
// PhysicsC_Instance.addPhysicsPair(pair);
// this.pair = pair;
// }
Physics_internal.physicsWorld &&
Physics_internal.physicsWorld.addBody(this.body);
return this;
}
disablePhysicsPair() {
if (this.pair) {
this.pair.destroyed = true;
}
}
getPhysicsBody() {
return this.body;
}
destroy() {
if (!Physics_internal.physicsWorld) return;
Physics_internal.physicsWorld.removeBody(this.body);
(this.body as any) = null;
}
}
export class PhysicsObjPair {
threeObj: Object3D;
physicsObj: Body;
destroyed: boolean;
delegateId: null | Delegate<number>;
constructor(threeObj: Object3D, physicsObj: Body) {
this.threeObj = threeObj;
this.physicsObj = physicsObj;
this.destroyed = false;
this.delegateId = UpdateController.Instance.onUpdate.addDelegate(() => {
this.update();
});
}
update() {
if (this.destroyed) return;
if (this.threeObj && this.physicsObj) {
// @ts-ignore
this.threeObj.position.copy(this.physicsObj.position);
// @ts-ignore
this.threeObj.quaternion.copy(this.physicsObj.quaternion);
}
}
}
+42
View File
@@ -0,0 +1,42 @@
import { BoxGeometry, Mesh, MeshStandardMaterial, Vector3 } from "three";
import { ThreeC } from "./ThreeC";
import { InputC, JoystickC } from "@24tools/playable_template";
export class TestSceneC {
static init() {
this.createPrimitive();
// example of using InputC events
InputC.onTouchDown.addDelegate((event) => {
console.log("onMouseDown", event);
});
// if you have update in your controller
// UpdateController.Instance.onUpdate.addDelegate(() => {
// this.update();
// });
}
private static createPrimitive() {
const geometry = new BoxGeometry(1, 1, 1);
const material = new MeshStandardMaterial({ color: 0xcc0000 });
const cube = new Mesh(geometry, material);
const geometryPlane = new BoxGeometry(5, 0.1, 7);
const materialPlane = new MeshStandardMaterial({ color: 0xaaaaaa });
const plane = new Mesh(geometryPlane, materialPlane);
let planePosition = plane.position.clone();
ThreeC.setShadowsStateForChildren(cube, true, false);
ThreeC.setShadowsStateForChildren(plane, false, true);
plane.position.copy(
new Vector3(planePosition.x, planePosition.y - 0.5, planePosition.z - 1.5)
);
ThreeC.addToScene(cube);
ThreeC.addToScene(plane);
}
}
+68
View File
@@ -0,0 +1,68 @@
import { ColorFormat } from "@24tools/ads_common";
import { ThreeC_internal, Template } from "@24tools/playable_template";
import { AmbientLight, DirectionalLight } from "three";
import { Color } from "three/src/math/Color";
export class ThreeC extends ThreeC_internal {
static createBaseLights() {
this.defaultDirectionalLight = new DirectionalLight(0xffffff, 3.5);
this.defaultAmbientLight = new AmbientLight(0xffffff, 1);
this.defaultAmbientLight.color = new Color().setHex(
Number(Template.getValue<ColorFormat>("global", "light_color"))
);
this.defaultAmbientLight.intensity =
Template.getValue<number>("global", "light_intensity")
}
static setupDirectionalLightFromScene(dirLightObj:DirectionalLight) {
if (!dirLightObj) return;
ThreeC.removeFromScene(this.defaultDirectionalLight);
let light = dirLightObj;
let d = 15;
light.shadow.camera.left = -10;
light.shadow.camera.right = d;
light.shadow.camera.top = d;
light.shadow.camera.bottom = -2;
light.castShadow = true;
light.shadow.normalBias = 0.04;
light.intensity = 1;
light.shadow.mapSize.width = 1024;
light.shadow.mapSize.height = 1024;
const ambLight = new AmbientLight(0xffffff, 0.4); // soft white light
this.addToScene(ambLight);
this.addToScene(light);
this.defaultDirectionalLight = light;
}
static setupDirectionalLight() {
let dirLight = this.defaultDirectionalLight;
dirLight.position.set(8, 10, 4); //default; light shining from top
let d = 10;
dirLight.shadow.camera.left = -d;
dirLight.shadow.camera.right = d;
dirLight.shadow.camera.top = d;
dirLight.shadow.camera.bottom = -d;
dirLight.target.position.set(0, 0, 0);
dirLight.castShadow = true;
this.addToScene(dirLight);
this.addToScene(this.defaultAmbientLight);
this.addToScene(dirLight.target);
dirLight.shadow.mapSize.width = 1024;
dirLight.shadow.mapSize.height = 1024;
dirLight.shadow.camera.near = 0.5;
dirLight.shadow.camera.far = 200;
}
}
+108
View File
@@ -0,0 +1,108 @@
@keyframes disableLoader {
from {
opacity: 1;
}
to {
opacity: 0;
display: none;
}
}
.loader {
pointer-events: none;
display: flex;
flex-direction: column;
justify-content: space-between;
position: fixed;
width: 100.5vw;
height: 100.5vh;
z-index: 5000;
display: flex;
align-items: center;
background: linear-gradient(30deg, #0c284e 0%, #4291f3 100%);
}
.loader_block {
padding-top: 18%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
width: 55%;
height: 100%;
padding-bottom: 2vw;
}
.loader_box {
pointer-events: none;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
margin-top: auto;
margin-bottom: auto;
}
.loader_icon {
pointer-events: none;
width: 100%;
height: auto;
display: block;
border-radius: 5vw;
border: 2vw solid white;
box-shadow: 0px 2vw 50px rgba(0, 0, 0, 0.4);
box-sizing: border-box;
}
.loader_wrapper {
pointer-events: none;
height: 6vw;
width: 100%;
border-radius: 2.7vw;
border: 0.6vw solid #fff;
overflow: hidden;
box-sizing: border-box;
}
.loader_line {
pointer-events: none;
background-color: #ffc530;
height: 100%;
width: 0;
margin-bottom: 20%;
transition: width 200ms;
}
.loader_text {
pointer-events: none;
font-size: 8vw;
color: #fff;
display: block;
margin: 0;
margin-bottom: 2vw;
}
.logo24 {
pointer-events: none;
width: 60%;
margin-bottom: 10px;
fill: white;
}
@media (orientation: landscape) {
.loader_block {
padding-top: 3%;
width: 50vh;
}
.loader_icon {
border-radius: 5vh;
border: 2vh solid white;
box-shadow: 0px 2vh 50px rgba(0, 0, 0, 0.4);
}
.loader_wrapper {
height: 2.8vw;
width: 100%;
border-radius: 2.5vh;
border: 0.6vh solid #fff;
}
.loader_line {
height: 100%;
width: 0;
transition: width 250ms;
}
.loader_text {
font-size: 6vh;
margin-bottom: 2vh;
}
}
+137
View File
@@ -0,0 +1,137 @@
html,
body {
overflow-x: hidden;
overflow-y: hidden;
-moz-user-select: none;
-khtml-user-select: none;
user-select: none;
}
* {
touch-action: none;
/* mintegral problem */
user-select: none;
-webkit-touch-callout: none;
/* iOS Safari */
-webkit-user-select: none;
/* Chrome/Safari/Opera */
-khtml-user-select: none;
/* Konqueror */
-moz-user-select: none;
/* Firefox */
-ms-user-select: none;
-webkit-tap-highlight-color: transparent;
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
margin: 0;
background: #ffffff;
}
html {
font-size: 10vw;
font-family: gameFont, sans-serif;
position: fixed;
}
canvas {
width: 100%;
height: 100%;
}
#debug_label {
position: absolute;
top: 5%;
left: 30%;
width: 40%;
text-align: center;
font-size: 5vw;
color: black;
}
#ui_screen {
position: fixed;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 2;
display: flex;
flex-direction: column;
justify-content: space-between;
padding-top: 0vh;
padding-bottom: 0vh;
}
#constructor {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
}
#videoPlayable {
height: auto;
width: 100vw;
}
#interactive {
z-index: 10;
position: fixed;
inset: 0;
width: 100%;
height: 100%;
opacity: 0;
transition: opacity 500ms ease-out;
background-color: whitesmoke;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
#editor {
pointer-events: none;
}
#ui,
#editor {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: calc(100vh * 9 / 16);
height: 100vh;
}
#modal {
z-index: 10000;
}
.interactive-text {
color: rgb(83, 38, 0);
font-size: 5vh;
font-family: Arial, Helvetica, sans-serif;
text-align: center;
}
#joystick_zone {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
z-index: 900;
}
@media (orientation: landscape) {
#videoPlayable {
height: 100vh;
width: auto;
}
}
+4
View File
@@ -0,0 +1,4 @@
#ui {
/* flex-basis: 60%; */
flex-grow: 1;
}
+3
View File
@@ -0,0 +1,3 @@
import { FontFamily, formFontFamily } from "@24tools/ads_common";
export const customFont: undefined | Promise<FontFamily> = undefined
+49
View File
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width,user-scalable=no,initial-scale=1.0, minimum-scale=1.0,maximum-scale=1.0"
/>
<meta name="ad.orientation" content="portrait,landscape" />
<title>Playable</title>
<link href="./css/loader.css" rel="stylesheet" />
<link href="./css/main.css" rel="stylesheet" />
<link href="./css/ui.css" rel="stylesheet" />
<style id="animations"></style>
</head>
<body>
<div class="loader">
<div class="loader_block">
<img class="loader_icon" src="./resources/images/icon.webp" />
<div class="loader_box">
<p class="loader_text">loading</p>
<div class="loader_wrapper">
<div id="loader" class="loader_line"></div>
</div>
</div>
<img class="logo24" src="./resources/images/logo24.png" />
</div>
</div>
<div id="interactive">
<h3 class="interactive-text">Redirected to store...</h3>
</div>
<div id="constructor">
<div id="ui"></div>
<div id="editor"></div>
</div>
<script id="dev-start">
window.onload = function () {
window.setupConfig();
};
window.openStorePage = function (url) {
console.log("openStorePage:", url);
};
</script>
<script type="module" src="./index.ts"></script>
</body>
</html>
+37
View File
@@ -0,0 +1,37 @@
import { formConfigForPlayable, formConfigUI, formConfigUIParams, TemplateType } from "@24tools/ads_common";
import { resources } from "./resources/resources";
import { resizeCb } from "./templateConfig/resizeCb";
import { gameRedirectedCb } from "./templateConfig/gameRedirectedCb";
import { playableIsVisibleCb } from "./templateConfig/playableIsVisibleCb";
import { beforeResourcesLoadedCb } from "./templateConfig/beforeResourcesLoadedCb";
import { afterResourcesLoadedCb } from "./templateConfig/afterResourcesLoadedCb";
import { customFont } from "./fonts/customFont";
import { configUIParams } from "./configUIParams/configUIParams";
import { Template, Template3d } from "@24tools/playable_template";
import { firstClickCb } from "./templateConfig/firstClickCb";
Template.set24ADSControls();
window.setupConfig = async function (config) {
Template.initConfig({
templateType: TemplateType["3d"],
redirectOptions: {},
ticker: Template3d.ticker,
debug: {
physics: false,
// set true if you want to enable physics debugger
logger: false // set true if you want to enable logger
}
}).init({
config: config || formConfigForPlayable(formConfigUI({
type_default: Template.templateType,
params_categories: formConfigUIParams(...configUIParams),
font_custom_default: await customFont
})),
resources,
afterResourcesLoadedCb,
beforeResourcesLoadedCb,
resizeCb,
playableIsVisibleCb,
gameRedirectedCb,
firstClickCb
});
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

+14
View File
@@ -0,0 +1,14 @@
// import { ConvertToBase64WhenRelease } from "@24tools/ads_common";
import { ConvertResourceType, Template3d } from "@24tools/playable_template";
export const meshes : ConvertResourceType = {
type: "mesh",
resources: [
// {
// name: "scene",
// value: ConvertToBase64WhenRelease("./SceneСombo.glb"),
// },
],
loader: Template3d.meshLoader
}
+5
View File
@@ -0,0 +1,5 @@
import { ConvertResourcesType } from "@24tools/playable_template";
import { meshes } from "./meshes/meshes";
import { sounds } from "./sounds/sounds";
export const resources: ConvertResourcesType = [meshes, sounds];
+13
View File
@@ -0,0 +1,13 @@
// import { ConvertToBase64WhenRelease } from "@24tools/ads_common";
import { ConvertResourceType } from "@24tools/playable_template";
export const sounds : ConvertResourceType = {
type: "sounds",
resources: [
// {
// name: "background",
// value: ConvertToBase64WhenRelease(`./background.mp3`),
// },
],
}
@@ -0,0 +1,8 @@
import { TestSceneC } from "../controllers/TestSceneC";
import { SoundC, Template } from "@24tools/playable_template";
export const afterResourcesLoadedCb: (() => void) | undefined = () => {
TestSceneC.init();
SoundC.init();
Template.disableLoader();
};
@@ -0,0 +1,74 @@
import {
CameraC_internal,
CameraType,
// JoystickC,
Physics_internal,
Template,
Template3d,
ThreeC_internal,
FilterScene,
InstallBanner,
} from "@24tools/playable_template";
import { CameraC } from "../controllers/CameraC";
import { ThreeC } from "../controllers/ThreeC";
import { Color } from "three";
import { Vec3 } from "cannon-es";
export const beforeResourcesLoadedCb = () => {
FilterScene.init();
InstallBanner.init();
Template3d.init();
CameraC_internal.init(CameraType.perspective);
CameraC.setCamera(window.screenSize.portrait);
ThreeC_internal.init();
ThreeC.createBaseLights();
ThreeC.setupDirectionalLight();
let physicsWorld = Physics_internal.init(new Vec3(0, -9.81, 0));
// example of using joystick. Uncomment if you need joystick
// JoystickC.init({
// zone: document.getElementById("joystick_zone") as HTMLDivElement,
// fadeTime: 0,
// mode: "dynamic",
// restJoystick: true,
// catchDistance: 1,
// restOpacity: 0,
// follow: false,
// });
// JoystickC.onJoysticMove.addDelegate(({event, data}) => {
// console.log('onJoysticMove', event, data);
// })
Template.updateVariableConfig.addDelegate(([category, variable, value]) => {
if (category === "global") {
switch (variable) {
case "light_intensity":
ThreeC_internal.defaultAmbientLight.intensity = Number(value);
break;
case "light_color":
ThreeC_internal.defaultAmbientLight.color = new Color(value as any);
break;
default:
if (
[
"camera_fov_p",
"camera_position_p",
"camera_rotation_p",
"camera_fov_l",
"camera_position_l",
"camera_rotation_l",
].includes(variable)
) {
CameraC.setCamera(window.screenSize.portrait);
}
break;
}
return;
}
});
};
+3
View File
@@ -0,0 +1,3 @@
export const firstClickCb: () => void = () => {
console.log("First click");
};
+1
View File
@@ -0,0 +1 @@
export const gameRedirectedCb : (() => void) | undefined = undefined
@@ -0,0 +1 @@
export const playableIsVisibleCb : (() => void) | undefined = undefined
+5
View File
@@ -0,0 +1,5 @@
import { Template3d } from "@24tools/playable_template";
export const resizeCb = () => {
Template3d.resize();
};
+37
View File
@@ -0,0 +1,37 @@
import type { ConvertResourcesType, RedirectOptions, screenSize } from '@24tools/playable_template';
import { ConfigCreative } from '@24tools/ads_common';
declare global {
interface Window extends Window {
CustomizationRedirectCallback: any;
registerRedirectCallback: (el: any) => void;
updateVariableConfig: (
category: string,
variable: string,
value: any
) => void;
setupConfig: (config?: ConfigCreative) => void;
soundConfig: (
category: "setMute" | "getMute" | "setVolume" | "getVolume",
value?: boolean | number
) => boolean | number | undefined;
isStarted?: boolean;
CONFIG: ConfigCreative;
gameStart: () => void;
gameClose: () => void;
gameReady: () => void;
gameEnd: () => void;
screenSize: screenSize;
showLoader: (state: boolean) => void;
setRedirectOptions: (option: RedirectOptions | null) => void;
openGameStorePage: (cb: () => void) => void;
start: () => void
mraid: any
convertResources: ConvertResourcesType
startTime: number
updateVideoState: (cbUpdate: (state: "play" | "pause") => void) => void;
updateCurrentStep: (cbUpdate: (videoStep: VideoStep) => void) => void;
}
}
export {};