Dans cet article, nous examinerons toutes les étapes de développement: de la conception de l'idée à la mise en œuvre des différentes parties de l'application, y compris sélectivement certains morceaux de code personnalisés seront fournis.
Cet article peut être utile pour ceux qui réfléchissent ou commencent à développer des jeux ou des applications mobiles.
- , .
, . , , , , "" , - , .
( ) , -, 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.
PS Un merci spécial au musicien Anton Zvarych pour sa musique de fond.