En este art铆culo, consideraremos todas las etapas de desarrollo: desde la concepci贸n de la idea hasta la implementaci贸n de partes individuales de la aplicaci贸n, incluyendo selectivamente, se proporcionar谩n algunas piezas de c贸digo personalizadas.
Este art铆culo puede ser 煤til para aquellos que est谩n pensando o comenzando a desarrollar juegos o aplicaciones m贸viles.
- , .
, . , , , , "" , - , .
( ) , -, 8+ . - , . , - , - , . , , .
, . " " , JavaScript React, , .
. , . -, .
. 64x64 . , :
.rotate {
transform: rotateX(60deg) rotateZ(45deg);
transform-origin: left top;
}
, "" , , . , , :
const cellOffsets = {};
export function getCellOffset(n) {
if (n === 0) {
return 0;
}
if (cellOffsets[n]) {
return cellOffsets[n];
}
const result = 64 * (Math.floor(n / 2));
cellOffsets[n] = result;
return result;
}
:
import { getCellOffset } from 'libs/civilizations/helpers';
// ...
const offset = getCellOffset(columnIndex);
// ...
style={{
transform: `translateX(${(64 * rowIndex) + (64 * columnIndex) - offset}px) translateY(${(64 * rowIndex) - offset}px)`,
}}
, . FixedSizeGrid
react-window
, . , - . / . . , .
, , png-. , - . :
- , . , :
4 , , , react-i18next
. , 100 , , . redux
, . , , . , react-i18next
( ) .
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import get from 'lodash/get';
import set from 'lodash/set';
import size from 'lodash/size';
import { emptyObj, EN, LANG, PROPS, langs } from 'defaults';
import { getLang } from 'reducers/global/selectors';
import en from './en';
export function getDetectedLang() {
if (!global.navigator) {
return EN;
}
let detected;
if (size(navigator.languages)) {
detected = navigator.languages[0];
} else {
detected = navigator.language;
}
if (detected) {
detected = detected.substring(0, 2);
if (langs.indexOf(detected) !== -1) {
return detected;
}
}
return EN;
}
const options = {
lang: global.localStorage ?
(localStorage.getItem(LANG) || getDetectedLang()) :
getDetectedLang(),
};
const { lang: currentLang } = options;
const translations = {
en,
};
if (!translations[currentLang]) {
try {
translations[currentLang] = require(`./${currentLang}`).default;
} catch (err) {} // eslint-disable-line
}
export function setLang(lang = EN) {
if (langs.indexOf(lang) === -1) {
return;
}
if (global.localStorage) {
localStorage.setItem(LANG, lang);
}
set(options, [LANG], lang);
if (!translations[lang]) {
try {
translations[lang] = require(`./${lang}`).default;
} catch (err) {} // eslint-disable-line
}
}
const mapStateToProps = (state) => {
return {
lang: getLang(state),
};
};
export function t(path) {
const { lang = get(options, [LANG], EN) } = get(this, [PROPS], emptyObj);
if (!translations[lang]) {
try {
translations[lang] = require(`./${lang}`).default;
} catch (err) {} // eslint-disable-line
}
return get(translations[lang], path) || get(translations[EN], path, path);
}
function i18n(Comp) {
class I18N extends Component {
static propTypes = {
lang: PropTypes.string,
}
static defaultProps = {
lang: EN,
}
constructor(props) {
super(props);
this.t = t.bind(this);
}
componentWillUnmount() {
this.unmounted = true;
}
render() {
return (
<Comp
{...this.props}
t={this.t}
/>
);
}
}
return connect(mapStateToProps)(I18N);
}
export default i18n;
:
import i18n from 'libs/i18n';
// ...
static propTypes = {
t: PropTypes.func,
}
// ...
const { t } = this.props;
// ...
{t(['path', 'to', 'key'])}
// ... ,
{t('path.to.key')}
// ...
export default i18n(Comp);
Android 9 (, 8-, ) .
, , , , requestAnimationFrame
. Android 7 - - .
, requestAnimationFrame
, ( , , ):
import isFunction from 'lodash/isFunction';
let lastTime = 0;
const vendors = ['ms', 'moz', 'webkit', 'o'];
for (let x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[`${vendors[x]}RequestAnimationFrame`];
window.cancelAnimationFrame = window[`${vendors[x]}CancelAnimationFrame`] || window[`${vendors[x]}CancelRequestAnimationFrame`];
}
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = (callback) => {
const currTime = new Date().getTime();
const timeToCall = Math.max(0, 16 - (currTime - lastTime));
const id = window.setTimeout(() => { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
}
if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = (id) => {
clearTimeout(id);
};
}
let lastFrame = null;
let raf = null;
const callbacks = [];
const loop = (now) => {
raf = requestAnimationFrame(loop);
const deltaT = now - lastFrame;
// do not render frame when deltaT is too high
if (deltaT < 160) {
let callbacksLength = callbacks.length;
while (callbacksLength-- > 0) {
callbacks[callbacksLength](now);
}
}
lastFrame = now;
};
export function registerRafCallback(callback) {
if (!isFunction(callback)) {
return;
}
const index = callbacks.indexOf(callback);
// remove already existing the same callback
if (index !== -1) {
callbacks.splice(index, 1);
}
callbacks.push(callback);
if (!raf) {
raf = requestAnimationFrame(loop);
}
}
export function unregisterRafCallback(callback) {
const index = callbacks.indexOf(callback);
if (index !== -1) {
callbacks.splice(index, 1);
}
if (callbacks.length === 0 && raf) {
cancelAnimationFrame(raf);
raf = null;
}
}
:
import { registerRafCallback, unregisterRafCallback } from 'client/libs/raf';
// ...
registerRafCallback(this.cooldown);
// ...
componentWillUnmount() {
unregisterRafCallback(this.cooldown);
}
Lobby
, websocket- , websocket-, , , primus
. , npm primus-client
. save
.
:
- , . - ( - ):
import { SOUND_VOLUME } from 'defaults';
const Sound = {
audio: null,
volume: localStorage.getItem(SOUND_VOLUME) || 0.8,
play(path) {
const audio = new Audio(path);
audio.volume = Sound.volume;
if (Sound.audio) {
Sound.audio.pause();
}
audio.play();
Sound.audio = audio;
},
};
export function getVolume() {
return Sound.volume;
}
export function setVolume(volume) {
Sound.volume = volume;
localStorage.setItem(SOUND_VOLUME, volume);
}
export default Sound;
:
import Sound from 'client/libs/sound';
// ...
Sound.play('/mp3/win.mp3');
web- . , , Cordova file://
, :
const replace = require('replace-in-file');
const path = require('path');
const options = {
files: [
path.resolve(__dirname, './app/*.css'),
path.resolve(__dirname, './app/*.js'),
path.resolve(__dirname, './app/index.html'),
],
from: [/url\(\/img/g, /href="\//g, /src="\//g, /"\/mp3/g],
to: ['url(./img', 'href="./', 'src="./', '"./mp3'],
};
replace(options)
.then((results) => {
console.log('Replacement results:', results);
})
.catch((error) => {
console.error('Error occurred:', error);
});
, Google Play Store , . - 46, , . , . , .
, , :
, , Unity, tactical rts.
?
. - Google Play Store.
PD Un agradecimiento especial al m煤sico Anton Zvarych por proporcionar m煤sica de fondo.